1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 Closed,
257}
258
259impl ReportEditorEvent {
260 pub fn event_type(&self) -> &'static str {
261 match self {
262 Self::Saved { .. } => "Editor Saved",
263 Self::EditorOpened => "Editor Opened",
264 Self::Closed => "Editor Closed",
265 }
266 }
267}
268
269struct InlineValueCache {
270 enabled: bool,
271 inlays: Vec<InlayId>,
272 refresh_task: Task<Option<()>>,
273}
274
275impl InlineValueCache {
276 fn new(enabled: bool) -> Self {
277 Self {
278 enabled,
279 inlays: Vec::new(),
280 refresh_task: Task::ready(None),
281 }
282 }
283}
284
285#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
286pub enum InlayId {
287 EditPrediction(usize),
288 DebuggerValue(usize),
289 // LSP
290 Hint(usize),
291 Color(usize),
292}
293
294impl InlayId {
295 fn id(&self) -> usize {
296 match self {
297 Self::EditPrediction(id) => *id,
298 Self::DebuggerValue(id) => *id,
299 Self::Hint(id) => *id,
300 Self::Color(id) => *id,
301 }
302 }
303}
304
305pub enum ActiveDebugLine {}
306pub enum DebugStackFrameLine {}
307enum DocumentHighlightRead {}
308enum DocumentHighlightWrite {}
309enum InputComposition {}
310pub enum PendingInput {}
311enum SelectedTextHighlight {}
312
313pub enum ConflictsOuter {}
314pub enum ConflictsOurs {}
315pub enum ConflictsTheirs {}
316pub enum ConflictsOursMarker {}
317pub enum ConflictsTheirsMarker {}
318
319#[derive(Debug, Copy, Clone, PartialEq, Eq)]
320pub enum Navigated {
321 Yes,
322 No,
323}
324
325impl Navigated {
326 pub fn from_bool(yes: bool) -> Navigated {
327 if yes { Navigated::Yes } else { Navigated::No }
328 }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq)]
332enum DisplayDiffHunk {
333 Folded {
334 display_row: DisplayRow,
335 },
336 Unfolded {
337 is_created_file: bool,
338 diff_base_byte_range: Range<usize>,
339 display_row_range: Range<DisplayRow>,
340 multi_buffer_range: Range<Anchor>,
341 status: DiffHunkStatus,
342 },
343}
344
345pub enum HideMouseCursorOrigin {
346 TypingAction,
347 MovementAction,
348}
349
350pub fn init_settings(cx: &mut App) {
351 EditorSettings::register(cx);
352}
353
354pub fn init(cx: &mut App) {
355 init_settings(cx);
356
357 cx.set_global(GlobalBlameRenderer(Arc::new(())));
358
359 workspace::register_project_item::<Editor>(cx);
360 workspace::FollowableViewRegistry::register::<Editor>(cx);
361 workspace::register_serializable_item::<Editor>(cx);
362
363 cx.observe_new(
364 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
365 workspace.register_action(Editor::new_file);
366 workspace.register_action(Editor::new_file_vertical);
367 workspace.register_action(Editor::new_file_horizontal);
368 workspace.register_action(Editor::cancel_language_server_work);
369 workspace.register_action(Editor::toggle_focus);
370 },
371 )
372 .detach();
373
374 cx.on_action(move |_: &workspace::NewFile, cx| {
375 let app_state = workspace::AppState::global(cx);
376 if let Some(app_state) = app_state.upgrade() {
377 workspace::open_new(
378 Default::default(),
379 app_state,
380 cx,
381 |workspace, window, cx| {
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388 cx.on_action(move |_: &workspace::NewWindow, cx| {
389 let app_state = workspace::AppState::global(cx);
390 if let Some(app_state) = app_state.upgrade() {
391 workspace::open_new(
392 Default::default(),
393 app_state,
394 cx,
395 |workspace, window, cx| {
396 cx.activate(true);
397 Editor::new_file(workspace, &Default::default(), window, cx)
398 },
399 )
400 .detach();
401 }
402 });
403}
404
405pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
406 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
407}
408
409pub trait DiagnosticRenderer {
410 fn render_group(
411 &self,
412 diagnostic_group: Vec<DiagnosticEntry<Point>>,
413 buffer_id: BufferId,
414 snapshot: EditorSnapshot,
415 editor: WeakEntity<Editor>,
416 cx: &mut App,
417 ) -> Vec<BlockProperties<Anchor>>;
418
419 fn render_hover(
420 &self,
421 diagnostic_group: Vec<DiagnosticEntry<Point>>,
422 range: Range<Point>,
423 buffer_id: BufferId,
424 cx: &mut App,
425 ) -> Option<Entity<markdown::Markdown>>;
426
427 fn open_link(
428 &self,
429 editor: &mut Editor,
430 link: SharedString,
431 window: &mut Window,
432 cx: &mut Context<Editor>,
433 );
434}
435
436pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
437
438impl GlobalDiagnosticRenderer {
439 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
440 cx.try_global::<Self>().map(|g| g.0.clone())
441 }
442}
443
444impl gpui::Global for GlobalDiagnosticRenderer {}
445pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
446 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
447}
448
449pub struct SearchWithinRange;
450
451trait InvalidationRegion {
452 fn ranges(&self) -> &[Range<Anchor>];
453}
454
455#[derive(Clone, Debug, PartialEq)]
456pub enum SelectPhase {
457 Begin {
458 position: DisplayPoint,
459 add: bool,
460 click_count: usize,
461 },
462 BeginColumnar {
463 position: DisplayPoint,
464 reset: bool,
465 mode: ColumnarMode,
466 goal_column: u32,
467 },
468 Extend {
469 position: DisplayPoint,
470 click_count: usize,
471 },
472 Update {
473 position: DisplayPoint,
474 goal_column: u32,
475 scroll_delta: gpui::Point<f32>,
476 },
477 End,
478}
479
480#[derive(Clone, Debug, PartialEq)]
481pub enum ColumnarMode {
482 FromMouse,
483 FromSelection,
484}
485
486#[derive(Clone, Debug)]
487pub enum SelectMode {
488 Character,
489 Word(Range<Anchor>),
490 Line(Range<Anchor>),
491 All,
492}
493
494#[derive(Clone, PartialEq, Eq, Debug)]
495pub enum EditorMode {
496 SingleLine,
497 AutoHeight {
498 min_lines: usize,
499 max_lines: Option<usize>,
500 },
501 Full {
502 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
503 scale_ui_elements_with_buffer_font_size: bool,
504 /// When set to `true`, the editor will render a background for the active line.
505 show_active_line_background: bool,
506 /// When set to `true`, the editor's height will be determined by its content.
507 sized_by_content: bool,
508 },
509 Minimap {
510 parent: WeakEntity<Editor>,
511 },
512}
513
514impl EditorMode {
515 pub fn full() -> Self {
516 Self::Full {
517 scale_ui_elements_with_buffer_font_size: true,
518 show_active_line_background: true,
519 sized_by_content: false,
520 }
521 }
522
523 #[inline]
524 pub fn is_full(&self) -> bool {
525 matches!(self, Self::Full { .. })
526 }
527
528 #[inline]
529 pub fn is_single_line(&self) -> bool {
530 matches!(self, Self::SingleLine { .. })
531 }
532
533 #[inline]
534 fn is_minimap(&self) -> bool {
535 matches!(self, Self::Minimap { .. })
536 }
537}
538
539#[derive(Copy, Clone, Debug)]
540pub enum SoftWrap {
541 /// Prefer not to wrap at all.
542 ///
543 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
544 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
545 GitDiff,
546 /// Prefer a single line generally, unless an overly long line is encountered.
547 None,
548 /// Soft wrap lines that exceed the editor width.
549 EditorWidth,
550 /// Soft wrap lines at the preferred line length.
551 Column(u32),
552 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
553 Bounded(u32),
554}
555
556#[derive(Clone)]
557pub struct EditorStyle {
558 pub background: Hsla,
559 pub border: Hsla,
560 pub local_player: PlayerColor,
561 pub text: TextStyle,
562 pub scrollbar_width: Pixels,
563 pub syntax: Arc<SyntaxTheme>,
564 pub status: StatusColors,
565 pub inlay_hints_style: HighlightStyle,
566 pub edit_prediction_styles: EditPredictionStyles,
567 pub unnecessary_code_fade: f32,
568 pub show_underlines: bool,
569}
570
571impl Default for EditorStyle {
572 fn default() -> Self {
573 Self {
574 background: Hsla::default(),
575 border: Hsla::default(),
576 local_player: PlayerColor::default(),
577 text: TextStyle::default(),
578 scrollbar_width: Pixels::default(),
579 syntax: Default::default(),
580 // HACK: Status colors don't have a real default.
581 // We should look into removing the status colors from the editor
582 // style and retrieve them directly from the theme.
583 status: StatusColors::dark(),
584 inlay_hints_style: HighlightStyle::default(),
585 edit_prediction_styles: EditPredictionStyles {
586 insertion: HighlightStyle::default(),
587 whitespace: HighlightStyle::default(),
588 },
589 unnecessary_code_fade: Default::default(),
590 show_underlines: true,
591 }
592 }
593}
594
595pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
596 let show_background = language_settings::language_settings(None, None, cx)
597 .inlay_hints
598 .show_background;
599
600 HighlightStyle {
601 color: Some(cx.theme().status().hint),
602 background_color: show_background.then(|| cx.theme().status().hint_background),
603 ..HighlightStyle::default()
604 }
605}
606
607pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
608 EditPredictionStyles {
609 insertion: HighlightStyle {
610 color: Some(cx.theme().status().predictive),
611 ..HighlightStyle::default()
612 },
613 whitespace: HighlightStyle {
614 background_color: Some(cx.theme().status().created_background),
615 ..HighlightStyle::default()
616 },
617 }
618}
619
620type CompletionId = usize;
621
622pub(crate) enum EditDisplayMode {
623 TabAccept,
624 DiffPopover,
625 Inline,
626}
627
628enum EditPrediction {
629 Edit {
630 edits: Vec<(Range<Anchor>, String)>,
631 edit_preview: Option<EditPreview>,
632 display_mode: EditDisplayMode,
633 snapshot: BufferSnapshot,
634 },
635 Move {
636 target: Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Range<Anchor>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
730type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
731
732#[derive(Default)]
733struct ScrollbarMarkerState {
734 scrollbar_size: Size<Pixels>,
735 dirty: bool,
736 markers: Arc<[PaintQuad]>,
737 pending_refresh: Option<Task<Result<()>>>,
738}
739
740impl ScrollbarMarkerState {
741 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
742 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
743 }
744}
745
746#[derive(Clone, Copy, PartialEq, Eq)]
747pub enum MinimapVisibility {
748 Disabled,
749 Enabled {
750 /// The configuration currently present in the users settings.
751 setting_configuration: bool,
752 /// Whether to override the currently set visibility from the users setting.
753 toggle_override: bool,
754 },
755}
756
757impl MinimapVisibility {
758 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
759 if mode.is_full() {
760 Self::Enabled {
761 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
762 toggle_override: false,
763 }
764 } else {
765 Self::Disabled
766 }
767 }
768
769 fn hidden(&self) -> Self {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 ..
774 } => Self::Enabled {
775 setting_configuration,
776 toggle_override: setting_configuration,
777 },
778 Self::Disabled => Self::Disabled,
779 }
780 }
781
782 fn disabled(&self) -> bool {
783 matches!(*self, Self::Disabled)
784 }
785
786 fn settings_visibility(&self) -> bool {
787 match *self {
788 Self::Enabled {
789 setting_configuration,
790 ..
791 } => setting_configuration,
792 _ => false,
793 }
794 }
795
796 fn visible(&self) -> bool {
797 match *self {
798 Self::Enabled {
799 setting_configuration,
800 toggle_override,
801 } => setting_configuration ^ toggle_override,
802 _ => false,
803 }
804 }
805
806 fn toggle_visibility(&self) -> Self {
807 match *self {
808 Self::Enabled {
809 toggle_override,
810 setting_configuration,
811 } => Self::Enabled {
812 setting_configuration,
813 toggle_override: !toggle_override,
814 },
815 Self::Disabled => Self::Disabled,
816 }
817 }
818}
819
820#[derive(Clone, Debug)]
821struct RunnableTasks {
822 templates: Vec<(TaskSourceKind, TaskTemplate)>,
823 offset: multi_buffer::Anchor,
824 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
825 column: u32,
826 // Values of all named captures, including those starting with '_'
827 extra_variables: HashMap<String, String>,
828 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
829 context_range: Range<BufferOffset>,
830}
831
832impl RunnableTasks {
833 fn resolve<'a>(
834 &'a self,
835 cx: &'a task::TaskContext,
836 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
837 self.templates.iter().filter_map(|(kind, template)| {
838 template
839 .resolve_task(&kind.to_id_base(), cx)
840 .map(|task| (kind.clone(), task))
841 })
842 }
843}
844
845#[derive(Clone)]
846pub struct ResolvedTasks {
847 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
848 position: Anchor,
849}
850
851#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
852struct BufferOffset(usize);
853
854// Addons allow storing per-editor state in other crates (e.g. Vim)
855pub trait Addon: 'static {
856 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
857
858 fn render_buffer_header_controls(
859 &self,
860 _: &ExcerptInfo,
861 _: &Window,
862 _: &App,
863 ) -> Option<AnyElement> {
864 None
865 }
866
867 fn to_any(&self) -> &dyn std::any::Any;
868
869 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
870 None
871 }
872}
873
874struct ChangeLocation {
875 current: Option<Vec<Anchor>>,
876 original: Vec<Anchor>,
877}
878impl ChangeLocation {
879 fn locations(&self) -> &[Anchor] {
880 self.current.as_ref().unwrap_or(&self.original)
881 }
882}
883
884/// A set of caret positions, registered when the editor was edited.
885pub struct ChangeList {
886 changes: Vec<ChangeLocation>,
887 /// Currently "selected" change.
888 position: Option<usize>,
889}
890
891impl ChangeList {
892 pub fn new() -> Self {
893 Self {
894 changes: Vec::new(),
895 position: None,
896 }
897 }
898
899 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
900 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
901 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
902 if self.changes.is_empty() {
903 return None;
904 }
905
906 let prev = self.position.unwrap_or(self.changes.len());
907 let next = if direction == Direction::Prev {
908 prev.saturating_sub(count)
909 } else {
910 (prev + count).min(self.changes.len() - 1)
911 };
912 self.position = Some(next);
913 self.changes.get(next).map(|change| change.locations())
914 }
915
916 /// Adds a new change to the list, resetting the change list position.
917 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
918 self.position.take();
919 if let Some(last) = self.changes.last_mut()
920 && group
921 {
922 last.current = Some(new_positions)
923 } else {
924 self.changes.push(ChangeLocation {
925 original: new_positions,
926 current: None,
927 });
928 }
929 }
930
931 pub fn last(&self) -> Option<&[Anchor]> {
932 self.changes.last().map(|change| change.locations())
933 }
934
935 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
936 self.changes.last().map(|change| change.original.as_slice())
937 }
938
939 pub fn invert_last_group(&mut self) {
940 if let Some(last) = self.changes.last_mut()
941 && let Some(current) = last.current.as_mut()
942 {
943 mem::swap(&mut last.original, current);
944 }
945 }
946}
947
948#[derive(Clone)]
949struct InlineBlamePopoverState {
950 scroll_handle: ScrollHandle,
951 commit_message: Option<ParsedCommitMessage>,
952 markdown: Entity<Markdown>,
953}
954
955struct InlineBlamePopover {
956 position: gpui::Point<Pixels>,
957 hide_task: Option<Task<()>>,
958 popover_bounds: Option<Bounds<Pixels>>,
959 popover_state: InlineBlamePopoverState,
960 keyboard_grace: bool,
961}
962
963enum SelectionDragState {
964 /// State when no drag related activity is detected.
965 None,
966 /// State when the mouse is down on a selection that is about to be dragged.
967 ReadyToDrag {
968 selection: Selection<Anchor>,
969 click_position: gpui::Point<Pixels>,
970 mouse_down_time: Instant,
971 },
972 /// State when the mouse is dragging the selection in the editor.
973 Dragging {
974 selection: Selection<Anchor>,
975 drop_cursor: Selection<Anchor>,
976 hide_drop_cursor: bool,
977 },
978}
979
980enum ColumnarSelectionState {
981 FromMouse {
982 selection_tail: Anchor,
983 display_point: Option<DisplayPoint>,
984 },
985 FromSelection {
986 selection_tail: Anchor,
987 },
988}
989
990/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
991/// a breakpoint on them.
992#[derive(Clone, Copy, Debug, PartialEq, Eq)]
993struct PhantomBreakpointIndicator {
994 display_row: DisplayRow,
995 /// There's a small debounce between hovering over the line and showing the indicator.
996 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
997 is_active: bool,
998 collides_with_existing_breakpoint: bool,
999}
1000
1001/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1002///
1003/// See the [module level documentation](self) for more information.
1004pub struct Editor {
1005 focus_handle: FocusHandle,
1006 last_focused_descendant: Option<WeakFocusHandle>,
1007 /// The text buffer being edited
1008 buffer: Entity<MultiBuffer>,
1009 /// Map of how text in the buffer should be displayed.
1010 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1011 pub display_map: Entity<DisplayMap>,
1012 pub selections: SelectionsCollection,
1013 pub scroll_manager: ScrollManager,
1014 /// When inline assist editors are linked, they all render cursors because
1015 /// typing enters text into each of them, even the ones that aren't focused.
1016 pub(crate) show_cursor_when_unfocused: bool,
1017 columnar_selection_state: Option<ColumnarSelectionState>,
1018 add_selections_state: Option<AddSelectionsState>,
1019 select_next_state: Option<SelectNextState>,
1020 select_prev_state: Option<SelectNextState>,
1021 selection_history: SelectionHistory,
1022 defer_selection_effects: bool,
1023 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1024 autoclose_regions: Vec<AutocloseRegion>,
1025 snippet_stack: InvalidationStack<SnippetState>,
1026 select_syntax_node_history: SelectSyntaxNodeHistory,
1027 ime_transaction: Option<TransactionId>,
1028 pub diagnostics_max_severity: DiagnosticSeverity,
1029 active_diagnostics: ActiveDiagnostic,
1030 show_inline_diagnostics: bool,
1031 inline_diagnostics_update: Task<()>,
1032 inline_diagnostics_enabled: bool,
1033 diagnostics_enabled: bool,
1034 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1035 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1036 hard_wrap: Option<usize>,
1037 project: Option<Entity<Project>>,
1038 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1039 completion_provider: Option<Rc<dyn CompletionProvider>>,
1040 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1041 blink_manager: Entity<BlinkManager>,
1042 show_cursor_names: bool,
1043 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1044 pub show_local_selections: bool,
1045 mode: EditorMode,
1046 show_breadcrumbs: bool,
1047 show_gutter: bool,
1048 show_scrollbars: ScrollbarAxes,
1049 minimap_visibility: MinimapVisibility,
1050 offset_content: bool,
1051 disable_expand_excerpt_buttons: bool,
1052 show_line_numbers: Option<bool>,
1053 use_relative_line_numbers: Option<bool>,
1054 show_git_diff_gutter: Option<bool>,
1055 show_code_actions: Option<bool>,
1056 show_runnables: Option<bool>,
1057 show_breakpoints: Option<bool>,
1058 show_wrap_guides: Option<bool>,
1059 show_indent_guides: Option<bool>,
1060 placeholder_text: Option<Arc<str>>,
1061 highlight_order: usize,
1062 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1063 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1064 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1065 scrollbar_marker_state: ScrollbarMarkerState,
1066 active_indent_guides_state: ActiveIndentGuidesState,
1067 nav_history: Option<ItemNavHistory>,
1068 context_menu: RefCell<Option<CodeContextMenu>>,
1069 context_menu_options: Option<ContextMenuOptions>,
1070 mouse_context_menu: Option<MouseContextMenu>,
1071 completion_tasks: Vec<(CompletionId, Task<()>)>,
1072 inline_blame_popover: Option<InlineBlamePopover>,
1073 inline_blame_popover_show_task: Option<Task<()>>,
1074 signature_help_state: SignatureHelpState,
1075 auto_signature_help: Option<bool>,
1076 find_all_references_task_sources: Vec<Anchor>,
1077 next_completion_id: CompletionId,
1078 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1079 code_actions_task: Option<Task<Result<()>>>,
1080 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1082 document_highlights_task: Option<Task<()>>,
1083 linked_editing_range_task: Option<Task<Option<()>>>,
1084 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1085 pending_rename: Option<RenameState>,
1086 searchable: bool,
1087 cursor_shape: CursorShape,
1088 current_line_highlight: Option<CurrentLineHighlight>,
1089 collapse_matches: bool,
1090 autoindent_mode: Option<AutoindentMode>,
1091 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1092 input_enabled: bool,
1093 use_modal_editing: bool,
1094 read_only: bool,
1095 leader_id: Option<CollaboratorId>,
1096 remote_id: Option<ViewId>,
1097 pub hover_state: HoverState,
1098 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1099 gutter_hovered: bool,
1100 hovered_link_state: Option<HoveredLinkState>,
1101 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1102 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1103 active_edit_prediction: Option<EditPredictionState>,
1104 /// Used to prevent flickering as the user types while the menu is open
1105 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1106 edit_prediction_settings: EditPredictionSettings,
1107 edit_predictions_hidden_for_vim_mode: bool,
1108 show_edit_predictions_override: Option<bool>,
1109 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1110 edit_prediction_preview: EditPredictionPreview,
1111 edit_prediction_indent_conflict: bool,
1112 edit_prediction_requires_modifier_in_indent_conflict: bool,
1113 inlay_hint_cache: InlayHintCache,
1114 next_inlay_id: usize,
1115 _subscriptions: Vec<Subscription>,
1116 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1117 gutter_dimensions: GutterDimensions,
1118 style: Option<EditorStyle>,
1119 text_style_refinement: Option<TextStyleRefinement>,
1120 next_editor_action_id: EditorActionId,
1121 editor_actions: Rc<
1122 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1123 >,
1124 use_autoclose: bool,
1125 use_auto_surround: bool,
1126 auto_replace_emoji_shortcode: bool,
1127 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1128 show_git_blame_gutter: bool,
1129 show_git_blame_inline: bool,
1130 show_git_blame_inline_delay_task: Option<Task<()>>,
1131 git_blame_inline_enabled: bool,
1132 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1133 serialize_dirty_buffers: bool,
1134 show_selection_menu: Option<bool>,
1135 blame: Option<Entity<GitBlame>>,
1136 blame_subscription: Option<Subscription>,
1137 custom_context_menu: Option<
1138 Box<
1139 dyn 'static
1140 + Fn(
1141 &mut Self,
1142 DisplayPoint,
1143 &mut Window,
1144 &mut Context<Self>,
1145 ) -> Option<Entity<ui::ContextMenu>>,
1146 >,
1147 >,
1148 last_bounds: Option<Bounds<Pixels>>,
1149 last_position_map: Option<Rc<PositionMap>>,
1150 expect_bounds_change: Option<Bounds<Pixels>>,
1151 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1152 tasks_update_task: Option<Task<()>>,
1153 breakpoint_store: Option<Entity<BreakpointStore>>,
1154 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1155 hovered_diff_hunk_row: Option<DisplayRow>,
1156 pull_diagnostics_task: Task<()>,
1157 in_project_search: bool,
1158 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1159 breadcrumb_header: Option<String>,
1160 focused_block: Option<FocusedBlock>,
1161 next_scroll_position: NextScrollCursorCenterTopBottom,
1162 addons: HashMap<TypeId, Box<dyn Addon>>,
1163 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1164 load_diff_task: Option<Shared<Task<()>>>,
1165 /// Whether we are temporarily displaying a diff other than git's
1166 temporary_diff_override: bool,
1167 selection_mark_mode: bool,
1168 toggle_fold_multiple_buffers: Task<()>,
1169 _scroll_cursor_center_top_bottom_task: Task<()>,
1170 serialize_selections: Task<()>,
1171 serialize_folds: Task<()>,
1172 mouse_cursor_hidden: bool,
1173 minimap: Option<Entity<Self>>,
1174 hide_mouse_mode: HideMouseMode,
1175 pub change_list: ChangeList,
1176 inline_value_cache: InlineValueCache,
1177 selection_drag_state: SelectionDragState,
1178 next_color_inlay_id: usize,
1179 colors: Option<LspColorData>,
1180 folding_newlines: Task<()>,
1181}
1182
1183#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1184enum NextScrollCursorCenterTopBottom {
1185 #[default]
1186 Center,
1187 Top,
1188 Bottom,
1189}
1190
1191impl NextScrollCursorCenterTopBottom {
1192 fn next(&self) -> Self {
1193 match self {
1194 Self::Center => Self::Top,
1195 Self::Top => Self::Bottom,
1196 Self::Bottom => Self::Center,
1197 }
1198 }
1199}
1200
1201#[derive(Clone)]
1202pub struct EditorSnapshot {
1203 pub mode: EditorMode,
1204 show_gutter: bool,
1205 show_line_numbers: Option<bool>,
1206 show_git_diff_gutter: Option<bool>,
1207 show_code_actions: Option<bool>,
1208 show_runnables: Option<bool>,
1209 show_breakpoints: Option<bool>,
1210 git_blame_gutter_max_author_length: Option<usize>,
1211 pub display_snapshot: DisplaySnapshot,
1212 pub placeholder_text: Option<Arc<str>>,
1213 is_focused: bool,
1214 scroll_anchor: ScrollAnchor,
1215 ongoing_scroll: OngoingScroll,
1216 current_line_highlight: CurrentLineHighlight,
1217 gutter_hovered: bool,
1218}
1219
1220#[derive(Default, Debug, Clone, Copy)]
1221pub struct GutterDimensions {
1222 pub left_padding: Pixels,
1223 pub right_padding: Pixels,
1224 pub width: Pixels,
1225 pub margin: Pixels,
1226 pub git_blame_entries_width: Option<Pixels>,
1227}
1228
1229impl GutterDimensions {
1230 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1231 Self {
1232 margin: Self::default_gutter_margin(font_id, font_size, cx),
1233 ..Default::default()
1234 }
1235 }
1236
1237 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1238 -cx.text_system().descent(font_id, font_size)
1239 }
1240 /// The full width of the space taken up by the gutter.
1241 pub fn full_width(&self) -> Pixels {
1242 self.margin + self.width
1243 }
1244
1245 /// The width of the space reserved for the fold indicators,
1246 /// use alongside 'justify_end' and `gutter_width` to
1247 /// right align content with the line numbers
1248 pub fn fold_area_width(&self) -> Pixels {
1249 self.margin + self.right_padding
1250 }
1251}
1252
1253struct CharacterDimensions {
1254 em_width: Pixels,
1255 em_advance: Pixels,
1256 line_height: Pixels,
1257}
1258
1259#[derive(Debug)]
1260pub struct RemoteSelection {
1261 pub replica_id: ReplicaId,
1262 pub selection: Selection<Anchor>,
1263 pub cursor_shape: CursorShape,
1264 pub collaborator_id: CollaboratorId,
1265 pub line_mode: bool,
1266 pub user_name: Option<SharedString>,
1267 pub color: PlayerColor,
1268}
1269
1270#[derive(Clone, Debug)]
1271struct SelectionHistoryEntry {
1272 selections: Arc<[Selection<Anchor>]>,
1273 select_next_state: Option<SelectNextState>,
1274 select_prev_state: Option<SelectNextState>,
1275 add_selections_state: Option<AddSelectionsState>,
1276}
1277
1278#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1279enum SelectionHistoryMode {
1280 Normal,
1281 Undoing,
1282 Redoing,
1283 Skipping,
1284}
1285
1286#[derive(Clone, PartialEq, Eq, Hash)]
1287struct HoveredCursor {
1288 replica_id: u16,
1289 selection_id: usize,
1290}
1291
1292impl Default for SelectionHistoryMode {
1293 fn default() -> Self {
1294 Self::Normal
1295 }
1296}
1297
1298#[derive(Debug)]
1299/// SelectionEffects controls the side-effects of updating the selection.
1300///
1301/// The default behaviour does "what you mostly want":
1302/// - it pushes to the nav history if the cursor moved by >10 lines
1303/// - it re-triggers completion requests
1304/// - it scrolls to fit
1305///
1306/// You might want to modify these behaviours. For example when doing a "jump"
1307/// like go to definition, we always want to add to nav history; but when scrolling
1308/// in vim mode we never do.
1309///
1310/// Similarly, you might want to disable scrolling if you don't want the viewport to
1311/// move.
1312#[derive(Clone)]
1313pub struct SelectionEffects {
1314 nav_history: Option<bool>,
1315 completions: bool,
1316 scroll: Option<Autoscroll>,
1317}
1318
1319impl Default for SelectionEffects {
1320 fn default() -> Self {
1321 Self {
1322 nav_history: None,
1323 completions: true,
1324 scroll: Some(Autoscroll::fit()),
1325 }
1326 }
1327}
1328impl SelectionEffects {
1329 pub fn scroll(scroll: Autoscroll) -> Self {
1330 Self {
1331 scroll: Some(scroll),
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn no_scroll() -> Self {
1337 Self {
1338 scroll: None,
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn completions(self, completions: bool) -> Self {
1344 Self {
1345 completions,
1346 ..self
1347 }
1348 }
1349
1350 pub fn nav_history(self, nav_history: bool) -> Self {
1351 Self {
1352 nav_history: Some(nav_history),
1353 ..self
1354 }
1355 }
1356}
1357
1358struct DeferredSelectionEffectsState {
1359 changed: bool,
1360 effects: SelectionEffects,
1361 old_cursor_position: Anchor,
1362 history_entry: SelectionHistoryEntry,
1363}
1364
1365#[derive(Default)]
1366struct SelectionHistory {
1367 #[allow(clippy::type_complexity)]
1368 selections_by_transaction:
1369 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1370 mode: SelectionHistoryMode,
1371 undo_stack: VecDeque<SelectionHistoryEntry>,
1372 redo_stack: VecDeque<SelectionHistoryEntry>,
1373}
1374
1375impl SelectionHistory {
1376 #[track_caller]
1377 fn insert_transaction(
1378 &mut self,
1379 transaction_id: TransactionId,
1380 selections: Arc<[Selection<Anchor>]>,
1381 ) {
1382 if selections.is_empty() {
1383 log::error!(
1384 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1385 std::panic::Location::caller()
1386 );
1387 return;
1388 }
1389 self.selections_by_transaction
1390 .insert(transaction_id, (selections, None));
1391 }
1392
1393 #[allow(clippy::type_complexity)]
1394 fn transaction(
1395 &self,
1396 transaction_id: TransactionId,
1397 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1398 self.selections_by_transaction.get(&transaction_id)
1399 }
1400
1401 #[allow(clippy::type_complexity)]
1402 fn transaction_mut(
1403 &mut self,
1404 transaction_id: TransactionId,
1405 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1406 self.selections_by_transaction.get_mut(&transaction_id)
1407 }
1408
1409 fn push(&mut self, entry: SelectionHistoryEntry) {
1410 if !entry.selections.is_empty() {
1411 match self.mode {
1412 SelectionHistoryMode::Normal => {
1413 self.push_undo(entry);
1414 self.redo_stack.clear();
1415 }
1416 SelectionHistoryMode::Undoing => self.push_redo(entry),
1417 SelectionHistoryMode::Redoing => self.push_undo(entry),
1418 SelectionHistoryMode::Skipping => {}
1419 }
1420 }
1421 }
1422
1423 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1424 if self
1425 .undo_stack
1426 .back()
1427 .is_none_or(|e| e.selections != entry.selections)
1428 {
1429 self.undo_stack.push_back(entry);
1430 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1431 self.undo_stack.pop_front();
1432 }
1433 }
1434 }
1435
1436 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1437 if self
1438 .redo_stack
1439 .back()
1440 .is_none_or(|e| e.selections != entry.selections)
1441 {
1442 self.redo_stack.push_back(entry);
1443 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1444 self.redo_stack.pop_front();
1445 }
1446 }
1447 }
1448}
1449
1450#[derive(Clone, Copy)]
1451pub struct RowHighlightOptions {
1452 pub autoscroll: bool,
1453 pub include_gutter: bool,
1454}
1455
1456impl Default for RowHighlightOptions {
1457 fn default() -> Self {
1458 Self {
1459 autoscroll: Default::default(),
1460 include_gutter: true,
1461 }
1462 }
1463}
1464
1465struct RowHighlight {
1466 index: usize,
1467 range: Range<Anchor>,
1468 color: Hsla,
1469 options: RowHighlightOptions,
1470 type_id: TypeId,
1471}
1472
1473#[derive(Clone, Debug)]
1474struct AddSelectionsState {
1475 groups: Vec<AddSelectionsGroup>,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsGroup {
1480 above: bool,
1481 stack: Vec<usize>,
1482}
1483
1484#[derive(Clone)]
1485struct SelectNextState {
1486 query: AhoCorasick,
1487 wordwise: bool,
1488 done: bool,
1489}
1490
1491impl std::fmt::Debug for SelectNextState {
1492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1493 f.debug_struct(std::any::type_name::<Self>())
1494 .field("wordwise", &self.wordwise)
1495 .field("done", &self.done)
1496 .finish()
1497 }
1498}
1499
1500#[derive(Debug)]
1501struct AutocloseRegion {
1502 selection_id: usize,
1503 range: Range<Anchor>,
1504 pair: BracketPair,
1505}
1506
1507#[derive(Debug)]
1508struct SnippetState {
1509 ranges: Vec<Vec<Range<Anchor>>>,
1510 active_index: usize,
1511 choices: Vec<Option<Vec<String>>>,
1512}
1513
1514#[doc(hidden)]
1515pub struct RenameState {
1516 pub range: Range<Anchor>,
1517 pub old_name: Arc<str>,
1518 pub editor: Entity<Editor>,
1519 block_id: CustomBlockId,
1520}
1521
1522struct InvalidationStack<T>(Vec<T>);
1523
1524struct RegisteredEditPredictionProvider {
1525 provider: Arc<dyn EditPredictionProviderHandle>,
1526 _subscription: Subscription,
1527}
1528
1529#[derive(Debug, PartialEq, Eq)]
1530pub struct ActiveDiagnosticGroup {
1531 pub active_range: Range<Anchor>,
1532 pub active_message: String,
1533 pub group_id: usize,
1534 pub blocks: HashSet<CustomBlockId>,
1535}
1536
1537#[derive(Debug, PartialEq, Eq)]
1538
1539pub(crate) enum ActiveDiagnostic {
1540 None,
1541 All,
1542 Group(ActiveDiagnosticGroup),
1543}
1544
1545#[derive(Serialize, Deserialize, Clone, Debug)]
1546pub struct ClipboardSelection {
1547 /// The number of bytes in this selection.
1548 pub len: usize,
1549 /// Whether this was a full-line selection.
1550 pub is_entire_line: bool,
1551 /// The indentation of the first line when this content was originally copied.
1552 pub first_line_indent: u32,
1553}
1554
1555// selections, scroll behavior, was newest selection reversed
1556type SelectSyntaxNodeHistoryState = (
1557 Box<[Selection<usize>]>,
1558 SelectSyntaxNodeScrollBehavior,
1559 bool,
1560);
1561
1562#[derive(Default)]
1563struct SelectSyntaxNodeHistory {
1564 stack: Vec<SelectSyntaxNodeHistoryState>,
1565 // disable temporarily to allow changing selections without losing the stack
1566 pub disable_clearing: bool,
1567}
1568
1569impl SelectSyntaxNodeHistory {
1570 pub fn try_clear(&mut self) {
1571 if !self.disable_clearing {
1572 self.stack.clear();
1573 }
1574 }
1575
1576 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1577 self.stack.push(selection);
1578 }
1579
1580 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1581 self.stack.pop()
1582 }
1583}
1584
1585enum SelectSyntaxNodeScrollBehavior {
1586 CursorTop,
1587 FitSelection,
1588 CursorBottom,
1589}
1590
1591#[derive(Debug)]
1592pub(crate) struct NavigationData {
1593 cursor_anchor: Anchor,
1594 cursor_position: Point,
1595 scroll_anchor: ScrollAnchor,
1596 scroll_top_row: u32,
1597}
1598
1599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600pub enum GotoDefinitionKind {
1601 Symbol,
1602 Declaration,
1603 Type,
1604 Implementation,
1605}
1606
1607#[derive(Debug, Clone)]
1608enum InlayHintRefreshReason {
1609 ModifiersChanged(bool),
1610 Toggle(bool),
1611 SettingsChange(InlayHintSettings),
1612 NewLinesShown,
1613 BufferEdited(HashSet<Arc<Language>>),
1614 RefreshRequested,
1615 ExcerptsRemoved(Vec<ExcerptId>),
1616}
1617
1618impl InlayHintRefreshReason {
1619 fn description(&self) -> &'static str {
1620 match self {
1621 Self::ModifiersChanged(_) => "modifiers changed",
1622 Self::Toggle(_) => "toggle",
1623 Self::SettingsChange(_) => "settings change",
1624 Self::NewLinesShown => "new lines shown",
1625 Self::BufferEdited(_) => "buffer edited",
1626 Self::RefreshRequested => "refresh requested",
1627 Self::ExcerptsRemoved(_) => "excerpts removed",
1628 }
1629 }
1630}
1631
1632pub enum FormatTarget {
1633 Buffers(HashSet<Entity<Buffer>>),
1634 Ranges(Vec<Range<MultiBufferPoint>>),
1635}
1636
1637pub(crate) struct FocusedBlock {
1638 id: BlockId,
1639 focus_handle: WeakFocusHandle,
1640}
1641
1642#[derive(Clone)]
1643enum JumpData {
1644 MultiBufferRow {
1645 row: MultiBufferRow,
1646 line_offset_from_top: u32,
1647 },
1648 MultiBufferPoint {
1649 excerpt_id: ExcerptId,
1650 position: Point,
1651 anchor: text::Anchor,
1652 line_offset_from_top: u32,
1653 },
1654}
1655
1656pub enum MultibufferSelectionMode {
1657 First,
1658 All,
1659}
1660
1661#[derive(Clone, Copy, Debug, Default)]
1662pub struct RewrapOptions {
1663 pub override_language_settings: bool,
1664 pub preserve_existing_whitespace: bool,
1665}
1666
1667impl Editor {
1668 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1669 let buffer = cx.new(|cx| Buffer::local("", cx));
1670 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1671 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1672 }
1673
1674 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(EditorMode::full(), buffer, None, window, cx)
1678 }
1679
1680 pub fn auto_height(
1681 min_lines: usize,
1682 max_lines: usize,
1683 window: &mut Window,
1684 cx: &mut Context<Self>,
1685 ) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(
1689 EditorMode::AutoHeight {
1690 min_lines,
1691 max_lines: Some(max_lines),
1692 },
1693 buffer,
1694 None,
1695 window,
1696 cx,
1697 )
1698 }
1699
1700 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1701 /// The editor grows as tall as needed to fit its content.
1702 pub fn auto_height_unbounded(
1703 min_lines: usize,
1704 window: &mut Window,
1705 cx: &mut Context<Self>,
1706 ) -> Self {
1707 let buffer = cx.new(|cx| Buffer::local("", cx));
1708 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1709 Self::new(
1710 EditorMode::AutoHeight {
1711 min_lines,
1712 max_lines: None,
1713 },
1714 buffer,
1715 None,
1716 window,
1717 cx,
1718 )
1719 }
1720
1721 pub fn for_buffer(
1722 buffer: Entity<Buffer>,
1723 project: Option<Entity<Project>>,
1724 window: &mut Window,
1725 cx: &mut Context<Self>,
1726 ) -> Self {
1727 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1728 Self::new(EditorMode::full(), buffer, project, window, cx)
1729 }
1730
1731 pub fn for_multibuffer(
1732 buffer: Entity<MultiBuffer>,
1733 project: Option<Entity<Project>>,
1734 window: &mut Window,
1735 cx: &mut Context<Self>,
1736 ) -> Self {
1737 Self::new(EditorMode::full(), buffer, project, window, cx)
1738 }
1739
1740 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1741 let mut clone = Self::new(
1742 self.mode.clone(),
1743 self.buffer.clone(),
1744 self.project.clone(),
1745 window,
1746 cx,
1747 );
1748 self.display_map.update(cx, |display_map, cx| {
1749 let snapshot = display_map.snapshot(cx);
1750 clone.display_map.update(cx, |display_map, cx| {
1751 display_map.set_state(&snapshot, cx);
1752 });
1753 });
1754 clone.folds_did_change(cx);
1755 clone.selections.clone_state(&self.selections);
1756 clone.scroll_manager.clone_state(&self.scroll_manager);
1757 clone.searchable = self.searchable;
1758 clone.read_only = self.read_only;
1759 clone
1760 }
1761
1762 pub fn new(
1763 mode: EditorMode,
1764 buffer: Entity<MultiBuffer>,
1765 project: Option<Entity<Project>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 Editor::new_internal(mode, buffer, project, None, window, cx)
1770 }
1771
1772 fn new_internal(
1773 mode: EditorMode,
1774 buffer: Entity<MultiBuffer>,
1775 project: Option<Entity<Project>>,
1776 display_map: Option<Entity<DisplayMap>>,
1777 window: &mut Window,
1778 cx: &mut Context<Self>,
1779 ) -> Self {
1780 debug_assert!(
1781 display_map.is_none() || mode.is_minimap(),
1782 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1783 );
1784
1785 let full_mode = mode.is_full();
1786 let is_minimap = mode.is_minimap();
1787 let diagnostics_max_severity = if full_mode {
1788 EditorSettings::get_global(cx)
1789 .diagnostics_max_severity
1790 .unwrap_or(DiagnosticSeverity::Hint)
1791 } else {
1792 DiagnosticSeverity::Off
1793 };
1794 let style = window.text_style();
1795 let font_size = style.font_size.to_pixels(window.rem_size());
1796 let editor = cx.entity().downgrade();
1797 let fold_placeholder = FoldPlaceholder {
1798 constrain_width: true,
1799 render: Arc::new(move |fold_id, fold_range, cx| {
1800 let editor = editor.clone();
1801 div()
1802 .id(fold_id)
1803 .bg(cx.theme().colors().ghost_element_background)
1804 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1805 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1806 .rounded_xs()
1807 .size_full()
1808 .cursor_pointer()
1809 .child("⋯")
1810 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1811 .on_click(move |_, _window, cx| {
1812 editor
1813 .update(cx, |editor, cx| {
1814 editor.unfold_ranges(
1815 &[fold_range.start..fold_range.end],
1816 true,
1817 false,
1818 cx,
1819 );
1820 cx.stop_propagation();
1821 })
1822 .ok();
1823 })
1824 .into_any()
1825 }),
1826 merge_adjacent: true,
1827 ..FoldPlaceholder::default()
1828 };
1829 let display_map = display_map.unwrap_or_else(|| {
1830 cx.new(|cx| {
1831 DisplayMap::new(
1832 buffer.clone(),
1833 style.font(),
1834 font_size,
1835 None,
1836 FILE_HEADER_HEIGHT,
1837 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1838 fold_placeholder,
1839 diagnostics_max_severity,
1840 cx,
1841 )
1842 })
1843 });
1844
1845 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1846
1847 let blink_manager = cx.new(|cx| {
1848 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1849 if is_minimap {
1850 blink_manager.disable(cx);
1851 }
1852 blink_manager
1853 });
1854
1855 let soft_wrap_mode_override =
1856 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1857
1858 let mut project_subscriptions = Vec::new();
1859 if full_mode && let Some(project) = project.as_ref() {
1860 project_subscriptions.push(cx.subscribe_in(
1861 project,
1862 window,
1863 |editor, _, event, window, cx| match event {
1864 project::Event::RefreshCodeLens => {
1865 // we always query lens with actions, without storing them, always refreshing them
1866 }
1867 project::Event::RefreshInlayHints => {
1868 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1869 }
1870 project::Event::LanguageServerAdded(..)
1871 | project::Event::LanguageServerRemoved(..) => {
1872 if editor.tasks_update_task.is_none() {
1873 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1874 }
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1897 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1898 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1899 }
1900 }
1901
1902 project::Event::EntryRenamed(transaction) => {
1903 let Some(workspace) = editor.workspace() else {
1904 return;
1905 };
1906 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1907 else {
1908 return;
1909 };
1910 if active_editor.entity_id() == cx.entity_id() {
1911 let edited_buffers_already_open = {
1912 let other_editors: Vec<Entity<Editor>> = workspace
1913 .read(cx)
1914 .panes()
1915 .iter()
1916 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1917 .filter(|editor| editor.entity_id() != cx.entity_id())
1918 .collect();
1919
1920 transaction.0.keys().all(|buffer| {
1921 other_editors.iter().any(|editor| {
1922 let multi_buffer = editor.read(cx).buffer();
1923 multi_buffer.read(cx).is_singleton()
1924 && multi_buffer.read(cx).as_singleton().map_or(
1925 false,
1926 |singleton| {
1927 singleton.entity_id() == buffer.entity_id()
1928 },
1929 )
1930 })
1931 })
1932 };
1933
1934 if !edited_buffers_already_open {
1935 let workspace = workspace.downgrade();
1936 let transaction = transaction.clone();
1937 cx.defer_in(window, move |_, window, cx| {
1938 cx.spawn_in(window, async move |editor, cx| {
1939 Self::open_project_transaction(
1940 &editor,
1941 workspace,
1942 transaction,
1943 "Rename".to_string(),
1944 cx,
1945 )
1946 .await
1947 .ok()
1948 })
1949 .detach();
1950 });
1951 }
1952 }
1953 }
1954
1955 _ => {}
1956 },
1957 ));
1958 if let Some(task_inventory) = project
1959 .read(cx)
1960 .task_store()
1961 .read(cx)
1962 .task_inventory()
1963 .cloned()
1964 {
1965 project_subscriptions.push(cx.observe_in(
1966 &task_inventory,
1967 window,
1968 |editor, _, window, cx| {
1969 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1970 },
1971 ));
1972 };
1973
1974 project_subscriptions.push(cx.subscribe_in(
1975 &project.read(cx).breakpoint_store(),
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 BreakpointStoreEvent::ClearDebugLines => {
1979 editor.clear_row_highlights::<ActiveDebugLine>();
1980 editor.refresh_inline_values(cx);
1981 }
1982 BreakpointStoreEvent::SetDebugLine => {
1983 if editor.go_to_active_debug_line(window, cx) {
1984 cx.stop_propagation();
1985 }
1986
1987 editor.refresh_inline_values(cx);
1988 }
1989 _ => {}
1990 },
1991 ));
1992 let git_store = project.read(cx).git_store().clone();
1993 let project = project.clone();
1994 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1995 if let GitStoreEvent::RepositoryUpdated(
1996 _,
1997 RepositoryEvent::Updated {
1998 new_instance: true, ..
1999 },
2000 _,
2001 ) = event
2002 {
2003 this.load_diff_task = Some(
2004 update_uncommitted_diff_for_buffer(
2005 cx.entity(),
2006 &project,
2007 this.buffer.read(cx).all_buffers(),
2008 this.buffer.clone(),
2009 cx,
2010 )
2011 .shared(),
2012 );
2013 }
2014 }));
2015 }
2016
2017 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2018
2019 let inlay_hint_settings =
2020 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2021 let focus_handle = cx.focus_handle();
2022 if !is_minimap {
2023 cx.on_focus(&focus_handle, window, Self::handle_focus)
2024 .detach();
2025 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2026 .detach();
2027 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2028 .detach();
2029 cx.on_blur(&focus_handle, window, Self::handle_blur)
2030 .detach();
2031 cx.observe_pending_input(window, Self::observe_pending_input)
2032 .detach();
2033 }
2034
2035 let show_indent_guides =
2036 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2037 Some(false)
2038 } else {
2039 None
2040 };
2041
2042 let breakpoint_store = match (&mode, project.as_ref()) {
2043 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2044 _ => None,
2045 };
2046
2047 let mut code_action_providers = Vec::new();
2048 let mut load_uncommitted_diff = None;
2049 if let Some(project) = project.clone() {
2050 load_uncommitted_diff = Some(
2051 update_uncommitted_diff_for_buffer(
2052 cx.entity(),
2053 &project,
2054 buffer.read(cx).all_buffers(),
2055 buffer.clone(),
2056 cx,
2057 )
2058 .shared(),
2059 );
2060 code_action_providers.push(Rc::new(project) as Rc<_>);
2061 }
2062
2063 let mut editor = Self {
2064 focus_handle,
2065 show_cursor_when_unfocused: false,
2066 last_focused_descendant: None,
2067 buffer: buffer.clone(),
2068 display_map: display_map.clone(),
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 placeholder_text: None,
2113 highlight_order: 0,
2114 highlighted_rows: HashMap::default(),
2115 background_highlights: TreeMap::default(),
2116 gutter_highlights: TreeMap::default(),
2117 scrollbar_marker_state: ScrollbarMarkerState::default(),
2118 active_indent_guides_state: ActiveIndentGuidesState::default(),
2119 nav_history: None,
2120 context_menu: RefCell::new(None),
2121 context_menu_options: None,
2122 mouse_context_menu: None,
2123 completion_tasks: Vec::new(),
2124 inline_blame_popover: None,
2125 inline_blame_popover_show_task: None,
2126 signature_help_state: SignatureHelpState::default(),
2127 auto_signature_help: None,
2128 find_all_references_task_sources: Vec::new(),
2129 next_completion_id: 0,
2130 next_inlay_id: 0,
2131 code_action_providers,
2132 available_code_actions: None,
2133 code_actions_task: None,
2134 quick_selection_highlight_task: None,
2135 debounced_selection_highlight_task: None,
2136 document_highlights_task: None,
2137 linked_editing_range_task: None,
2138 pending_rename: None,
2139 searchable: !is_minimap,
2140 cursor_shape: EditorSettings::get_global(cx)
2141 .cursor_shape
2142 .unwrap_or_default(),
2143 current_line_highlight: None,
2144 autoindent_mode: Some(AutoindentMode::EachLine),
2145 collapse_matches: false,
2146 workspace: None,
2147 input_enabled: !is_minimap,
2148 use_modal_editing: full_mode,
2149 read_only: is_minimap,
2150 use_autoclose: true,
2151 use_auto_surround: true,
2152 auto_replace_emoji_shortcode: false,
2153 jsx_tag_auto_close_enabled_in_any_buffer: false,
2154 leader_id: None,
2155 remote_id: None,
2156 hover_state: HoverState::default(),
2157 pending_mouse_down: None,
2158 hovered_link_state: None,
2159 edit_prediction_provider: None,
2160 active_edit_prediction: None,
2161 stale_edit_prediction_in_menu: None,
2162 edit_prediction_preview: EditPredictionPreview::Inactive {
2163 released_too_fast: false,
2164 },
2165 inline_diagnostics_enabled: full_mode,
2166 diagnostics_enabled: full_mode,
2167 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2168 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2169 gutter_hovered: false,
2170 pixel_position_of_newest_cursor: None,
2171 last_bounds: None,
2172 last_position_map: None,
2173 expect_bounds_change: None,
2174 gutter_dimensions: GutterDimensions::default(),
2175 style: None,
2176 show_cursor_names: false,
2177 hovered_cursors: HashMap::default(),
2178 next_editor_action_id: EditorActionId::default(),
2179 editor_actions: Rc::default(),
2180 edit_predictions_hidden_for_vim_mode: false,
2181 show_edit_predictions_override: None,
2182 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2183 edit_prediction_settings: EditPredictionSettings::Disabled,
2184 edit_prediction_indent_conflict: false,
2185 edit_prediction_requires_modifier_in_indent_conflict: true,
2186 custom_context_menu: None,
2187 show_git_blame_gutter: false,
2188 show_git_blame_inline: false,
2189 show_selection_menu: None,
2190 show_git_blame_inline_delay_task: None,
2191 git_blame_inline_enabled: full_mode
2192 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2193 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2194 serialize_dirty_buffers: !is_minimap
2195 && ProjectSettings::get_global(cx)
2196 .session
2197 .restore_unsaved_buffers,
2198 blame: None,
2199 blame_subscription: None,
2200 tasks: BTreeMap::default(),
2201
2202 breakpoint_store,
2203 gutter_breakpoint_indicator: (None, None),
2204 hovered_diff_hunk_row: None,
2205 _subscriptions: (!is_minimap)
2206 .then(|| {
2207 vec![
2208 cx.observe(&buffer, Self::on_buffer_changed),
2209 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2210 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2211 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2212 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2213 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2214 cx.observe_window_activation(window, |editor, window, cx| {
2215 let active = window.is_window_active();
2216 editor.blink_manager.update(cx, |blink_manager, cx| {
2217 if active {
2218 blink_manager.enable(cx);
2219 } else {
2220 blink_manager.disable(cx);
2221 }
2222 });
2223 if active {
2224 editor.show_mouse_cursor(cx);
2225 }
2226 }),
2227 ]
2228 })
2229 .unwrap_or_default(),
2230 tasks_update_task: None,
2231 pull_diagnostics_task: Task::ready(()),
2232 colors: None,
2233 next_color_inlay_id: 0,
2234 linked_edit_ranges: Default::default(),
2235 in_project_search: false,
2236 previous_search_ranges: None,
2237 breadcrumb_header: None,
2238 focused_block: None,
2239 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2240 addons: HashMap::default(),
2241 registered_buffers: HashMap::default(),
2242 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2243 selection_mark_mode: false,
2244 toggle_fold_multiple_buffers: Task::ready(()),
2245 serialize_selections: Task::ready(()),
2246 serialize_folds: Task::ready(()),
2247 text_style_refinement: None,
2248 load_diff_task: load_uncommitted_diff,
2249 temporary_diff_override: false,
2250 mouse_cursor_hidden: false,
2251 minimap: None,
2252 hide_mouse_mode: EditorSettings::get_global(cx)
2253 .hide_mouse
2254 .unwrap_or_default(),
2255 change_list: ChangeList::new(),
2256 mode,
2257 selection_drag_state: SelectionDragState::None,
2258 folding_newlines: Task::ready(()),
2259 };
2260
2261 if is_minimap {
2262 return editor;
2263 }
2264
2265 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2266 editor
2267 ._subscriptions
2268 .push(cx.observe(breakpoints, |_, _, cx| {
2269 cx.notify();
2270 }));
2271 }
2272 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2273 editor._subscriptions.extend(project_subscriptions);
2274
2275 editor._subscriptions.push(cx.subscribe_in(
2276 &cx.entity(),
2277 window,
2278 |editor, _, e: &EditorEvent, window, cx| match e {
2279 EditorEvent::ScrollPositionChanged { local, .. } => {
2280 if *local {
2281 let new_anchor = editor.scroll_manager.anchor();
2282 let snapshot = editor.snapshot(window, cx);
2283 editor.update_restoration_data(cx, move |data| {
2284 data.scroll_position = (
2285 new_anchor.top_row(&snapshot.buffer_snapshot),
2286 new_anchor.offset,
2287 );
2288 });
2289 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2290 editor.inline_blame_popover.take();
2291 }
2292 }
2293 EditorEvent::Edited { .. } => {
2294 if !vim_enabled(cx) {
2295 let (map, selections) = editor.selections.all_adjusted_display(cx);
2296 let pop_state = editor
2297 .change_list
2298 .last()
2299 .map(|previous| {
2300 previous.len() == selections.len()
2301 && previous.iter().enumerate().all(|(ix, p)| {
2302 p.to_display_point(&map).row()
2303 == selections[ix].head().row()
2304 })
2305 })
2306 .unwrap_or(false);
2307 let new_positions = selections
2308 .into_iter()
2309 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2310 .collect();
2311 editor
2312 .change_list
2313 .push_to_change_list(pop_state, new_positions);
2314 }
2315 }
2316 _ => (),
2317 },
2318 ));
2319
2320 if let Some(dap_store) = editor
2321 .project
2322 .as_ref()
2323 .map(|project| project.read(cx).dap_store())
2324 {
2325 let weak_editor = cx.weak_entity();
2326
2327 editor
2328 ._subscriptions
2329 .push(
2330 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2331 let session_entity = cx.entity();
2332 weak_editor
2333 .update(cx, |editor, cx| {
2334 editor._subscriptions.push(
2335 cx.subscribe(&session_entity, Self::on_debug_session_event),
2336 );
2337 })
2338 .ok();
2339 }),
2340 );
2341
2342 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2343 editor
2344 ._subscriptions
2345 .push(cx.subscribe(&session, Self::on_debug_session_event));
2346 }
2347 }
2348
2349 // skip adding the initial selection to selection history
2350 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2351 editor.end_selection(window, cx);
2352 editor.selection_history.mode = SelectionHistoryMode::Normal;
2353
2354 editor.scroll_manager.show_scrollbars(window, cx);
2355 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2356
2357 if full_mode {
2358 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2359 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2360
2361 if editor.git_blame_inline_enabled {
2362 editor.start_git_blame_inline(false, window, cx);
2363 }
2364
2365 editor.go_to_active_debug_line(window, cx);
2366
2367 if let Some(buffer) = buffer.read(cx).as_singleton()
2368 && let Some(project) = editor.project()
2369 {
2370 let handle = project.update(cx, |project, cx| {
2371 project.register_buffer_with_language_servers(&buffer, cx)
2372 });
2373 editor
2374 .registered_buffers
2375 .insert(buffer.read(cx).remote_id(), handle);
2376 }
2377
2378 editor.minimap =
2379 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2380 editor.colors = Some(LspColorData::new(cx));
2381 editor.update_lsp_data(false, None, window, cx);
2382 }
2383
2384 if editor.mode.is_full() {
2385 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2386 }
2387
2388 editor
2389 }
2390
2391 pub fn deploy_mouse_context_menu(
2392 &mut self,
2393 position: gpui::Point<Pixels>,
2394 context_menu: Entity<ContextMenu>,
2395 window: &mut Window,
2396 cx: &mut Context<Self>,
2397 ) {
2398 self.mouse_context_menu = Some(MouseContextMenu::new(
2399 self,
2400 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2401 context_menu,
2402 window,
2403 cx,
2404 ));
2405 }
2406
2407 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2408 self.mouse_context_menu
2409 .as_ref()
2410 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2411 }
2412
2413 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2414 if self
2415 .selections
2416 .pending
2417 .as_ref()
2418 .is_some_and(|pending_selection| {
2419 let snapshot = self.buffer().read(cx).snapshot(cx);
2420 pending_selection
2421 .selection
2422 .range()
2423 .includes(range, &snapshot)
2424 })
2425 {
2426 return true;
2427 }
2428
2429 self.selections
2430 .disjoint_in_range::<usize>(range.clone(), cx)
2431 .into_iter()
2432 .any(|selection| {
2433 // This is needed to cover a corner case, if we just check for an existing
2434 // selection in the fold range, having a cursor at the start of the fold
2435 // marks it as selected. Non-empty selections don't cause this.
2436 let length = selection.end - selection.start;
2437 length > 0
2438 })
2439 }
2440
2441 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2442 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2443 }
2444
2445 fn key_context_internal(
2446 &self,
2447 has_active_edit_prediction: bool,
2448 window: &Window,
2449 cx: &App,
2450 ) -> KeyContext {
2451 let mut key_context = KeyContext::new_with_defaults();
2452 key_context.add("Editor");
2453 let mode = match self.mode {
2454 EditorMode::SingleLine => "single_line",
2455 EditorMode::AutoHeight { .. } => "auto_height",
2456 EditorMode::Minimap { .. } => "minimap",
2457 EditorMode::Full { .. } => "full",
2458 };
2459
2460 if EditorSettings::jupyter_enabled(cx) {
2461 key_context.add("jupyter");
2462 }
2463
2464 key_context.set("mode", mode);
2465 if self.pending_rename.is_some() {
2466 key_context.add("renaming");
2467 }
2468
2469 match self.context_menu.borrow().as_ref() {
2470 Some(CodeContextMenu::Completions(menu)) => {
2471 if menu.visible() {
2472 key_context.add("menu");
2473 key_context.add("showing_completions");
2474 }
2475 }
2476 Some(CodeContextMenu::CodeActions(menu)) => {
2477 if menu.visible() {
2478 key_context.add("menu");
2479 key_context.add("showing_code_actions")
2480 }
2481 }
2482 None => {}
2483 }
2484
2485 if self.signature_help_state.has_multiple_signatures() {
2486 key_context.add("showing_signature_help");
2487 }
2488
2489 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2490 if !self.focus_handle(cx).contains_focused(window, cx)
2491 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2492 {
2493 for addon in self.addons.values() {
2494 addon.extend_key_context(&mut key_context, cx)
2495 }
2496 }
2497
2498 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2499 if let Some(extension) = singleton_buffer
2500 .read(cx)
2501 .file()
2502 .and_then(|file| file.path().extension()?.to_str())
2503 {
2504 key_context.set("extension", extension.to_string());
2505 }
2506 } else {
2507 key_context.add("multibuffer");
2508 }
2509
2510 if has_active_edit_prediction {
2511 if self.edit_prediction_in_conflict() {
2512 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2513 } else {
2514 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2515 key_context.add("copilot_suggestion");
2516 }
2517 }
2518
2519 if self.selection_mark_mode {
2520 key_context.add("selection_mode");
2521 }
2522
2523 key_context
2524 }
2525
2526 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2527 if self.mouse_cursor_hidden {
2528 self.mouse_cursor_hidden = false;
2529 cx.notify();
2530 }
2531 }
2532
2533 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2534 let hide_mouse_cursor = match origin {
2535 HideMouseCursorOrigin::TypingAction => {
2536 matches!(
2537 self.hide_mouse_mode,
2538 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2539 )
2540 }
2541 HideMouseCursorOrigin::MovementAction => {
2542 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2543 }
2544 };
2545 if self.mouse_cursor_hidden != hide_mouse_cursor {
2546 self.mouse_cursor_hidden = hide_mouse_cursor;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn edit_prediction_in_conflict(&self) -> bool {
2552 if !self.show_edit_predictions_in_menu() {
2553 return false;
2554 }
2555
2556 let showing_completions = self
2557 .context_menu
2558 .borrow()
2559 .as_ref()
2560 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2561
2562 showing_completions
2563 || self.edit_prediction_requires_modifier()
2564 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2565 // bindings to insert tab characters.
2566 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2567 }
2568
2569 pub fn accept_edit_prediction_keybind(
2570 &self,
2571 accept_partial: bool,
2572 window: &Window,
2573 cx: &App,
2574 ) -> AcceptEditPredictionBinding {
2575 let key_context = self.key_context_internal(true, window, cx);
2576 let in_conflict = self.edit_prediction_in_conflict();
2577
2578 let bindings = if accept_partial {
2579 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2580 } else {
2581 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2582 };
2583
2584 // TODO: if the binding contains multiple keystrokes, display all of them, not
2585 // just the first one.
2586 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2587 !in_conflict
2588 || binding
2589 .keystrokes()
2590 .first()
2591 .is_some_and(|keystroke| keystroke.modifiers().modified())
2592 }))
2593 }
2594
2595 pub fn new_file(
2596 workspace: &mut Workspace,
2597 _: &workspace::NewFile,
2598 window: &mut Window,
2599 cx: &mut Context<Workspace>,
2600 ) {
2601 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2602 "Failed to create buffer",
2603 window,
2604 cx,
2605 |e, _, _| match e.error_code() {
2606 ErrorCode::RemoteUpgradeRequired => Some(format!(
2607 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2608 e.error_tag("required").unwrap_or("the latest version")
2609 )),
2610 _ => None,
2611 },
2612 );
2613 }
2614
2615 pub fn new_in_workspace(
2616 workspace: &mut Workspace,
2617 window: &mut Window,
2618 cx: &mut Context<Workspace>,
2619 ) -> Task<Result<Entity<Editor>>> {
2620 let project = workspace.project().clone();
2621 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2622
2623 cx.spawn_in(window, async move |workspace, cx| {
2624 let buffer = create.await?;
2625 workspace.update_in(cx, |workspace, window, cx| {
2626 let editor =
2627 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2628 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2629 editor
2630 })
2631 })
2632 }
2633
2634 fn new_file_vertical(
2635 workspace: &mut Workspace,
2636 _: &workspace::NewFileSplitVertical,
2637 window: &mut Window,
2638 cx: &mut Context<Workspace>,
2639 ) {
2640 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2641 }
2642
2643 fn new_file_horizontal(
2644 workspace: &mut Workspace,
2645 _: &workspace::NewFileSplitHorizontal,
2646 window: &mut Window,
2647 cx: &mut Context<Workspace>,
2648 ) {
2649 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2650 }
2651
2652 fn new_file_in_direction(
2653 workspace: &mut Workspace,
2654 direction: SplitDirection,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 let project = workspace.project().clone();
2659 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2660
2661 cx.spawn_in(window, async move |workspace, cx| {
2662 let buffer = create.await?;
2663 workspace.update_in(cx, move |workspace, window, cx| {
2664 workspace.split_item(
2665 direction,
2666 Box::new(
2667 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2668 ),
2669 window,
2670 cx,
2671 )
2672 })?;
2673 anyhow::Ok(())
2674 })
2675 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2676 match e.error_code() {
2677 ErrorCode::RemoteUpgradeRequired => Some(format!(
2678 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2679 e.error_tag("required").unwrap_or("the latest version")
2680 )),
2681 _ => None,
2682 }
2683 });
2684 }
2685
2686 pub fn leader_id(&self) -> Option<CollaboratorId> {
2687 self.leader_id
2688 }
2689
2690 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2691 &self.buffer
2692 }
2693
2694 pub fn project(&self) -> Option<&Entity<Project>> {
2695 self.project.as_ref()
2696 }
2697
2698 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2699 self.workspace.as_ref()?.0.upgrade()
2700 }
2701
2702 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2703 self.buffer().read(cx).title(cx)
2704 }
2705
2706 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2707 let git_blame_gutter_max_author_length = self
2708 .render_git_blame_gutter(cx)
2709 .then(|| {
2710 if let Some(blame) = self.blame.as_ref() {
2711 let max_author_length =
2712 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2713 Some(max_author_length)
2714 } else {
2715 None
2716 }
2717 })
2718 .flatten();
2719
2720 EditorSnapshot {
2721 mode: self.mode.clone(),
2722 show_gutter: self.show_gutter,
2723 show_line_numbers: self.show_line_numbers,
2724 show_git_diff_gutter: self.show_git_diff_gutter,
2725 show_code_actions: self.show_code_actions,
2726 show_runnables: self.show_runnables,
2727 show_breakpoints: self.show_breakpoints,
2728 git_blame_gutter_max_author_length,
2729 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2730 scroll_anchor: self.scroll_manager.anchor(),
2731 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2732 placeholder_text: self.placeholder_text.clone(),
2733 is_focused: self.focus_handle.is_focused(window),
2734 current_line_highlight: self
2735 .current_line_highlight
2736 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2737 gutter_hovered: self.gutter_hovered,
2738 }
2739 }
2740
2741 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2742 self.buffer.read(cx).language_at(point, cx)
2743 }
2744
2745 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2746 self.buffer.read(cx).read(cx).file_at(point).cloned()
2747 }
2748
2749 pub fn active_excerpt(
2750 &self,
2751 cx: &App,
2752 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2753 self.buffer
2754 .read(cx)
2755 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2756 }
2757
2758 pub fn mode(&self) -> &EditorMode {
2759 &self.mode
2760 }
2761
2762 pub fn set_mode(&mut self, mode: EditorMode) {
2763 self.mode = mode;
2764 }
2765
2766 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2767 self.collaboration_hub.as_deref()
2768 }
2769
2770 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2771 self.collaboration_hub = Some(hub);
2772 }
2773
2774 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2775 self.in_project_search = in_project_search;
2776 }
2777
2778 pub fn set_custom_context_menu(
2779 &mut self,
2780 f: impl 'static
2781 + Fn(
2782 &mut Self,
2783 DisplayPoint,
2784 &mut Window,
2785 &mut Context<Self>,
2786 ) -> Option<Entity<ui::ContextMenu>>,
2787 ) {
2788 self.custom_context_menu = Some(Box::new(f))
2789 }
2790
2791 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2792 self.completion_provider = provider;
2793 }
2794
2795 #[cfg(any(test, feature = "test-support"))]
2796 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2797 self.completion_provider.clone()
2798 }
2799
2800 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2801 self.semantics_provider.clone()
2802 }
2803
2804 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2805 self.semantics_provider = provider;
2806 }
2807
2808 pub fn set_edit_prediction_provider<T>(
2809 &mut self,
2810 provider: Option<Entity<T>>,
2811 window: &mut Window,
2812 cx: &mut Context<Self>,
2813 ) where
2814 T: EditPredictionProvider,
2815 {
2816 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2817 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2818 if this.focus_handle.is_focused(window) {
2819 this.update_visible_edit_prediction(window, cx);
2820 }
2821 }),
2822 provider: Arc::new(provider),
2823 });
2824 self.update_edit_prediction_settings(cx);
2825 self.refresh_edit_prediction(false, false, window, cx);
2826 }
2827
2828 pub fn placeholder_text(&self) -> Option<&str> {
2829 self.placeholder_text.as_deref()
2830 }
2831
2832 pub fn set_placeholder_text(
2833 &mut self,
2834 placeholder_text: impl Into<Arc<str>>,
2835 cx: &mut Context<Self>,
2836 ) {
2837 let placeholder_text = Some(placeholder_text.into());
2838 if self.placeholder_text != placeholder_text {
2839 self.placeholder_text = placeholder_text;
2840 cx.notify();
2841 }
2842 }
2843
2844 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2845 self.cursor_shape = cursor_shape;
2846
2847 // Disrupt blink for immediate user feedback that the cursor shape has changed
2848 self.blink_manager.update(cx, BlinkManager::show_cursor);
2849
2850 cx.notify();
2851 }
2852
2853 pub fn set_current_line_highlight(
2854 &mut self,
2855 current_line_highlight: Option<CurrentLineHighlight>,
2856 ) {
2857 self.current_line_highlight = current_line_highlight;
2858 }
2859
2860 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2861 self.collapse_matches = collapse_matches;
2862 }
2863
2864 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2865 let buffers = self.buffer.read(cx).all_buffers();
2866 let Some(project) = self.project.as_ref() else {
2867 return;
2868 };
2869 project.update(cx, |project, cx| {
2870 for buffer in buffers {
2871 self.registered_buffers
2872 .entry(buffer.read(cx).remote_id())
2873 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2874 }
2875 })
2876 }
2877
2878 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2879 if self.collapse_matches {
2880 return range.start..range.start;
2881 }
2882 range.clone()
2883 }
2884
2885 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2886 if self.display_map.read(cx).clip_at_line_ends != clip {
2887 self.display_map
2888 .update(cx, |map, _| map.clip_at_line_ends = clip);
2889 }
2890 }
2891
2892 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2893 self.input_enabled = input_enabled;
2894 }
2895
2896 pub fn set_edit_predictions_hidden_for_vim_mode(
2897 &mut self,
2898 hidden: bool,
2899 window: &mut Window,
2900 cx: &mut Context<Self>,
2901 ) {
2902 if hidden != self.edit_predictions_hidden_for_vim_mode {
2903 self.edit_predictions_hidden_for_vim_mode = hidden;
2904 if hidden {
2905 self.update_visible_edit_prediction(window, cx);
2906 } else {
2907 self.refresh_edit_prediction(true, false, window, cx);
2908 }
2909 }
2910 }
2911
2912 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2913 self.menu_edit_predictions_policy = value;
2914 }
2915
2916 pub fn set_autoindent(&mut self, autoindent: bool) {
2917 if autoindent {
2918 self.autoindent_mode = Some(AutoindentMode::EachLine);
2919 } else {
2920 self.autoindent_mode = None;
2921 }
2922 }
2923
2924 pub fn read_only(&self, cx: &App) -> bool {
2925 self.read_only || self.buffer.read(cx).read_only()
2926 }
2927
2928 pub fn set_read_only(&mut self, read_only: bool) {
2929 self.read_only = read_only;
2930 }
2931
2932 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2933 self.use_autoclose = autoclose;
2934 }
2935
2936 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2937 self.use_auto_surround = auto_surround;
2938 }
2939
2940 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2941 self.auto_replace_emoji_shortcode = auto_replace;
2942 }
2943
2944 pub fn toggle_edit_predictions(
2945 &mut self,
2946 _: &ToggleEditPrediction,
2947 window: &mut Window,
2948 cx: &mut Context<Self>,
2949 ) {
2950 if self.show_edit_predictions_override.is_some() {
2951 self.set_show_edit_predictions(None, window, cx);
2952 } else {
2953 let show_edit_predictions = !self.edit_predictions_enabled();
2954 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2955 }
2956 }
2957
2958 pub fn set_show_edit_predictions(
2959 &mut self,
2960 show_edit_predictions: Option<bool>,
2961 window: &mut Window,
2962 cx: &mut Context<Self>,
2963 ) {
2964 self.show_edit_predictions_override = show_edit_predictions;
2965 self.update_edit_prediction_settings(cx);
2966
2967 if let Some(false) = show_edit_predictions {
2968 self.discard_edit_prediction(false, cx);
2969 } else {
2970 self.refresh_edit_prediction(false, true, window, cx);
2971 }
2972 }
2973
2974 fn edit_predictions_disabled_in_scope(
2975 &self,
2976 buffer: &Entity<Buffer>,
2977 buffer_position: language::Anchor,
2978 cx: &App,
2979 ) -> bool {
2980 let snapshot = buffer.read(cx).snapshot();
2981 let settings = snapshot.settings_at(buffer_position, cx);
2982
2983 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2984 return false;
2985 };
2986
2987 scope.override_name().is_some_and(|scope_name| {
2988 settings
2989 .edit_predictions_disabled_in
2990 .iter()
2991 .any(|s| s == scope_name)
2992 })
2993 }
2994
2995 pub fn set_use_modal_editing(&mut self, to: bool) {
2996 self.use_modal_editing = to;
2997 }
2998
2999 pub fn use_modal_editing(&self) -> bool {
3000 self.use_modal_editing
3001 }
3002
3003 fn selections_did_change(
3004 &mut self,
3005 local: bool,
3006 old_cursor_position: &Anchor,
3007 effects: SelectionEffects,
3008 window: &mut Window,
3009 cx: &mut Context<Self>,
3010 ) {
3011 window.invalidate_character_coordinates();
3012
3013 // Copy selections to primary selection buffer
3014 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3015 if local {
3016 let selections = self.selections.all::<usize>(cx);
3017 let buffer_handle = self.buffer.read(cx).read(cx);
3018
3019 let mut text = String::new();
3020 for (index, selection) in selections.iter().enumerate() {
3021 let text_for_selection = buffer_handle
3022 .text_for_range(selection.start..selection.end)
3023 .collect::<String>();
3024
3025 text.push_str(&text_for_selection);
3026 if index != selections.len() - 1 {
3027 text.push('\n');
3028 }
3029 }
3030
3031 if !text.is_empty() {
3032 cx.write_to_primary(ClipboardItem::new_string(text));
3033 }
3034 }
3035
3036 let selection_anchors = self.selections.disjoint_anchors();
3037
3038 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3039 self.buffer.update(cx, |buffer, cx| {
3040 buffer.set_active_selections(
3041 &selection_anchors,
3042 self.selections.line_mode,
3043 self.cursor_shape,
3044 cx,
3045 )
3046 });
3047 }
3048 let display_map = self
3049 .display_map
3050 .update(cx, |display_map, cx| display_map.snapshot(cx));
3051 let buffer = &display_map.buffer_snapshot;
3052 if self.selections.count() == 1 {
3053 self.add_selections_state = None;
3054 }
3055 self.select_next_state = None;
3056 self.select_prev_state = None;
3057 self.select_syntax_node_history.try_clear();
3058 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3059 self.snippet_stack.invalidate(&selection_anchors, buffer);
3060 self.take_rename(false, window, cx);
3061
3062 let newest_selection = self.selections.newest_anchor();
3063 let new_cursor_position = newest_selection.head();
3064 let selection_start = newest_selection.start;
3065
3066 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3067 self.push_to_nav_history(
3068 *old_cursor_position,
3069 Some(new_cursor_position.to_point(buffer)),
3070 false,
3071 effects.nav_history == Some(true),
3072 cx,
3073 );
3074 }
3075
3076 if local {
3077 if let Some(buffer_id) = new_cursor_position.buffer_id
3078 && !self.registered_buffers.contains_key(&buffer_id)
3079 && let Some(project) = self.project.as_ref()
3080 {
3081 project.update(cx, |project, cx| {
3082 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3083 return;
3084 };
3085 self.registered_buffers.insert(
3086 buffer_id,
3087 project.register_buffer_with_language_servers(&buffer, cx),
3088 );
3089 })
3090 }
3091
3092 let mut context_menu = self.context_menu.borrow_mut();
3093 let completion_menu = match context_menu.as_ref() {
3094 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3095 Some(CodeContextMenu::CodeActions(_)) => {
3096 *context_menu = None;
3097 None
3098 }
3099 None => None,
3100 };
3101 let completion_position = completion_menu.map(|menu| menu.initial_position);
3102 drop(context_menu);
3103
3104 if effects.completions
3105 && let Some(completion_position) = completion_position
3106 {
3107 let start_offset = selection_start.to_offset(buffer);
3108 let position_matches = start_offset == completion_position.to_offset(buffer);
3109 let continue_showing = if position_matches {
3110 if self.snippet_stack.is_empty() {
3111 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3112 } else {
3113 // Snippet choices can be shown even when the cursor is in whitespace.
3114 // Dismissing the menu with actions like backspace is handled by
3115 // invalidation regions.
3116 true
3117 }
3118 } else {
3119 false
3120 };
3121
3122 if continue_showing {
3123 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3124 } else {
3125 self.hide_context_menu(window, cx);
3126 }
3127 }
3128
3129 hide_hover(self, cx);
3130
3131 if old_cursor_position.to_display_point(&display_map).row()
3132 != new_cursor_position.to_display_point(&display_map).row()
3133 {
3134 self.available_code_actions.take();
3135 }
3136 self.refresh_code_actions(window, cx);
3137 self.refresh_document_highlights(cx);
3138 self.refresh_selected_text_highlights(false, window, cx);
3139 refresh_matching_bracket_highlights(self, window, cx);
3140 self.update_visible_edit_prediction(window, cx);
3141 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3142 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3143 self.inline_blame_popover.take();
3144 if self.git_blame_inline_enabled {
3145 self.start_inline_blame_timer(window, cx);
3146 }
3147 }
3148
3149 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3150 cx.emit(EditorEvent::SelectionsChanged { local });
3151
3152 let selections = &self.selections.disjoint;
3153 if selections.len() == 1 {
3154 cx.emit(SearchEvent::ActiveMatchChanged)
3155 }
3156 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3157 let inmemory_selections = selections
3158 .iter()
3159 .map(|s| {
3160 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3161 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3162 })
3163 .collect();
3164 self.update_restoration_data(cx, |data| {
3165 data.selections = inmemory_selections;
3166 });
3167
3168 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3169 && let Some(workspace_id) =
3170 self.workspace.as_ref().and_then(|workspace| workspace.1)
3171 {
3172 let snapshot = self.buffer().read(cx).snapshot(cx);
3173 let selections = selections.clone();
3174 let background_executor = cx.background_executor().clone();
3175 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3176 self.serialize_selections = cx.background_spawn(async move {
3177 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3178 let db_selections = selections
3179 .iter()
3180 .map(|selection| {
3181 (
3182 selection.start.to_offset(&snapshot),
3183 selection.end.to_offset(&snapshot),
3184 )
3185 })
3186 .collect();
3187
3188 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3189 .await
3190 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3191 .log_err();
3192 });
3193 }
3194 }
3195
3196 cx.notify();
3197 }
3198
3199 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3200 use text::ToOffset as _;
3201 use text::ToPoint as _;
3202
3203 if self.mode.is_minimap()
3204 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3205 {
3206 return;
3207 }
3208
3209 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3210 return;
3211 };
3212
3213 let snapshot = singleton.read(cx).snapshot();
3214 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3215 let display_snapshot = display_map.snapshot(cx);
3216
3217 display_snapshot
3218 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3219 .map(|fold| {
3220 fold.range.start.text_anchor.to_point(&snapshot)
3221 ..fold.range.end.text_anchor.to_point(&snapshot)
3222 })
3223 .collect()
3224 });
3225 self.update_restoration_data(cx, |data| {
3226 data.folds = inmemory_folds;
3227 });
3228
3229 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3230 return;
3231 };
3232 let background_executor = cx.background_executor().clone();
3233 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3234 let db_folds = self.display_map.update(cx, |display_map, cx| {
3235 display_map
3236 .snapshot(cx)
3237 .folds_in_range(0..snapshot.len())
3238 .map(|fold| {
3239 (
3240 fold.range.start.text_anchor.to_offset(&snapshot),
3241 fold.range.end.text_anchor.to_offset(&snapshot),
3242 )
3243 })
3244 .collect()
3245 });
3246 self.serialize_folds = cx.background_spawn(async move {
3247 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3248 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3249 .await
3250 .with_context(|| {
3251 format!(
3252 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3253 )
3254 })
3255 .log_err();
3256 });
3257 }
3258
3259 pub fn sync_selections(
3260 &mut self,
3261 other: Entity<Editor>,
3262 cx: &mut Context<Self>,
3263 ) -> gpui::Subscription {
3264 let other_selections = other.read(cx).selections.disjoint.to_vec();
3265 self.selections.change_with(cx, |selections| {
3266 selections.select_anchors(other_selections);
3267 });
3268
3269 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3270 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3271 let other_selections = other.read(cx).selections.disjoint.to_vec();
3272 if other_selections.is_empty() {
3273 return;
3274 }
3275 this.selections.change_with(cx, |selections| {
3276 selections.select_anchors(other_selections);
3277 });
3278 }
3279 });
3280
3281 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3282 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3283 let these_selections = this.selections.disjoint.to_vec();
3284 if these_selections.is_empty() {
3285 return;
3286 }
3287 other.update(cx, |other_editor, cx| {
3288 other_editor.selections.change_with(cx, |selections| {
3289 selections.select_anchors(these_selections);
3290 })
3291 });
3292 }
3293 });
3294
3295 Subscription::join(other_subscription, this_subscription)
3296 }
3297
3298 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3299 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3300 /// effects of selection change occur at the end of the transaction.
3301 pub fn change_selections<R>(
3302 &mut self,
3303 effects: SelectionEffects,
3304 window: &mut Window,
3305 cx: &mut Context<Self>,
3306 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3307 ) -> R {
3308 if let Some(state) = &mut self.deferred_selection_effects_state {
3309 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3310 state.effects.completions = effects.completions;
3311 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3312 let (changed, result) = self.selections.change_with(cx, change);
3313 state.changed |= changed;
3314 return result;
3315 }
3316 let mut state = DeferredSelectionEffectsState {
3317 changed: false,
3318 effects,
3319 old_cursor_position: self.selections.newest_anchor().head(),
3320 history_entry: SelectionHistoryEntry {
3321 selections: self.selections.disjoint_anchors(),
3322 select_next_state: self.select_next_state.clone(),
3323 select_prev_state: self.select_prev_state.clone(),
3324 add_selections_state: self.add_selections_state.clone(),
3325 },
3326 };
3327 let (changed, result) = self.selections.change_with(cx, change);
3328 state.changed = state.changed || changed;
3329 if self.defer_selection_effects {
3330 self.deferred_selection_effects_state = Some(state);
3331 } else {
3332 self.apply_selection_effects(state, window, cx);
3333 }
3334 result
3335 }
3336
3337 /// Defers the effects of selection change, so that the effects of multiple calls to
3338 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3339 /// to selection history and the state of popovers based on selection position aren't
3340 /// erroneously updated.
3341 pub fn with_selection_effects_deferred<R>(
3342 &mut self,
3343 window: &mut Window,
3344 cx: &mut Context<Self>,
3345 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3346 ) -> R {
3347 let already_deferred = self.defer_selection_effects;
3348 self.defer_selection_effects = true;
3349 let result = update(self, window, cx);
3350 if !already_deferred {
3351 self.defer_selection_effects = false;
3352 if let Some(state) = self.deferred_selection_effects_state.take() {
3353 self.apply_selection_effects(state, window, cx);
3354 }
3355 }
3356 result
3357 }
3358
3359 fn apply_selection_effects(
3360 &mut self,
3361 state: DeferredSelectionEffectsState,
3362 window: &mut Window,
3363 cx: &mut Context<Self>,
3364 ) {
3365 if state.changed {
3366 self.selection_history.push(state.history_entry);
3367
3368 if let Some(autoscroll) = state.effects.scroll {
3369 self.request_autoscroll(autoscroll, cx);
3370 }
3371
3372 let old_cursor_position = &state.old_cursor_position;
3373
3374 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3375
3376 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3377 self.show_signature_help(&ShowSignatureHelp, window, cx);
3378 }
3379 }
3380 }
3381
3382 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3383 where
3384 I: IntoIterator<Item = (Range<S>, T)>,
3385 S: ToOffset,
3386 T: Into<Arc<str>>,
3387 {
3388 if self.read_only(cx) {
3389 return;
3390 }
3391
3392 self.buffer
3393 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3394 }
3395
3396 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3397 where
3398 I: IntoIterator<Item = (Range<S>, T)>,
3399 S: ToOffset,
3400 T: Into<Arc<str>>,
3401 {
3402 if self.read_only(cx) {
3403 return;
3404 }
3405
3406 self.buffer.update(cx, |buffer, cx| {
3407 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3408 });
3409 }
3410
3411 pub fn edit_with_block_indent<I, S, T>(
3412 &mut self,
3413 edits: I,
3414 original_indent_columns: Vec<Option<u32>>,
3415 cx: &mut Context<Self>,
3416 ) where
3417 I: IntoIterator<Item = (Range<S>, T)>,
3418 S: ToOffset,
3419 T: Into<Arc<str>>,
3420 {
3421 if self.read_only(cx) {
3422 return;
3423 }
3424
3425 self.buffer.update(cx, |buffer, cx| {
3426 buffer.edit(
3427 edits,
3428 Some(AutoindentMode::Block {
3429 original_indent_columns,
3430 }),
3431 cx,
3432 )
3433 });
3434 }
3435
3436 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3437 self.hide_context_menu(window, cx);
3438
3439 match phase {
3440 SelectPhase::Begin {
3441 position,
3442 add,
3443 click_count,
3444 } => self.begin_selection(position, add, click_count, window, cx),
3445 SelectPhase::BeginColumnar {
3446 position,
3447 goal_column,
3448 reset,
3449 mode,
3450 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3451 SelectPhase::Extend {
3452 position,
3453 click_count,
3454 } => self.extend_selection(position, click_count, window, cx),
3455 SelectPhase::Update {
3456 position,
3457 goal_column,
3458 scroll_delta,
3459 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3460 SelectPhase::End => self.end_selection(window, cx),
3461 }
3462 }
3463
3464 fn extend_selection(
3465 &mut self,
3466 position: DisplayPoint,
3467 click_count: usize,
3468 window: &mut Window,
3469 cx: &mut Context<Self>,
3470 ) {
3471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3472 let tail = self.selections.newest::<usize>(cx).tail();
3473 self.begin_selection(position, false, click_count, window, cx);
3474
3475 let position = position.to_offset(&display_map, Bias::Left);
3476 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3477
3478 let mut pending_selection = self
3479 .selections
3480 .pending_anchor()
3481 .expect("extend_selection not called with pending selection");
3482 if position >= tail {
3483 pending_selection.start = tail_anchor;
3484 } else {
3485 pending_selection.end = tail_anchor;
3486 pending_selection.reversed = true;
3487 }
3488
3489 let mut pending_mode = self.selections.pending_mode().unwrap();
3490 match &mut pending_mode {
3491 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3492 _ => {}
3493 }
3494
3495 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3496 SelectionEffects::scroll(Autoscroll::fit())
3497 } else {
3498 SelectionEffects::no_scroll()
3499 };
3500
3501 self.change_selections(effects, window, cx, |s| {
3502 s.set_pending(pending_selection, pending_mode)
3503 });
3504 }
3505
3506 fn begin_selection(
3507 &mut self,
3508 position: DisplayPoint,
3509 add: bool,
3510 click_count: usize,
3511 window: &mut Window,
3512 cx: &mut Context<Self>,
3513 ) {
3514 if !self.focus_handle.is_focused(window) {
3515 self.last_focused_descendant = None;
3516 window.focus(&self.focus_handle);
3517 }
3518
3519 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3520 let buffer = &display_map.buffer_snapshot;
3521 let position = display_map.clip_point(position, Bias::Left);
3522
3523 let start;
3524 let end;
3525 let mode;
3526 let mut auto_scroll;
3527 match click_count {
3528 1 => {
3529 start = buffer.anchor_before(position.to_point(&display_map));
3530 end = start;
3531 mode = SelectMode::Character;
3532 auto_scroll = true;
3533 }
3534 2 => {
3535 let position = display_map
3536 .clip_point(position, Bias::Left)
3537 .to_offset(&display_map, Bias::Left);
3538 let (range, _) = buffer.surrounding_word(position, false);
3539 start = buffer.anchor_before(range.start);
3540 end = buffer.anchor_before(range.end);
3541 mode = SelectMode::Word(start..end);
3542 auto_scroll = true;
3543 }
3544 3 => {
3545 let position = display_map
3546 .clip_point(position, Bias::Left)
3547 .to_point(&display_map);
3548 let line_start = display_map.prev_line_boundary(position).0;
3549 let next_line_start = buffer.clip_point(
3550 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3551 Bias::Left,
3552 );
3553 start = buffer.anchor_before(line_start);
3554 end = buffer.anchor_before(next_line_start);
3555 mode = SelectMode::Line(start..end);
3556 auto_scroll = true;
3557 }
3558 _ => {
3559 start = buffer.anchor_before(0);
3560 end = buffer.anchor_before(buffer.len());
3561 mode = SelectMode::All;
3562 auto_scroll = false;
3563 }
3564 }
3565 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3566
3567 let point_to_delete: Option<usize> = {
3568 let selected_points: Vec<Selection<Point>> =
3569 self.selections.disjoint_in_range(start..end, cx);
3570
3571 if !add || click_count > 1 {
3572 None
3573 } else if !selected_points.is_empty() {
3574 Some(selected_points[0].id)
3575 } else {
3576 let clicked_point_already_selected =
3577 self.selections.disjoint.iter().find(|selection| {
3578 selection.start.to_point(buffer) == start.to_point(buffer)
3579 || selection.end.to_point(buffer) == end.to_point(buffer)
3580 });
3581
3582 clicked_point_already_selected.map(|selection| selection.id)
3583 }
3584 };
3585
3586 let selections_count = self.selections.count();
3587 let effects = if auto_scroll {
3588 SelectionEffects::default()
3589 } else {
3590 SelectionEffects::no_scroll()
3591 };
3592
3593 self.change_selections(effects, window, cx, |s| {
3594 if let Some(point_to_delete) = point_to_delete {
3595 s.delete(point_to_delete);
3596
3597 if selections_count == 1 {
3598 s.set_pending_anchor_range(start..end, mode);
3599 }
3600 } else {
3601 if !add {
3602 s.clear_disjoint();
3603 }
3604
3605 s.set_pending_anchor_range(start..end, mode);
3606 }
3607 });
3608 }
3609
3610 fn begin_columnar_selection(
3611 &mut self,
3612 position: DisplayPoint,
3613 goal_column: u32,
3614 reset: bool,
3615 mode: ColumnarMode,
3616 window: &mut Window,
3617 cx: &mut Context<Self>,
3618 ) {
3619 if !self.focus_handle.is_focused(window) {
3620 self.last_focused_descendant = None;
3621 window.focus(&self.focus_handle);
3622 }
3623
3624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3625
3626 if reset {
3627 let pointer_position = display_map
3628 .buffer_snapshot
3629 .anchor_before(position.to_point(&display_map));
3630
3631 self.change_selections(
3632 SelectionEffects::scroll(Autoscroll::newest()),
3633 window,
3634 cx,
3635 |s| {
3636 s.clear_disjoint();
3637 s.set_pending_anchor_range(
3638 pointer_position..pointer_position,
3639 SelectMode::Character,
3640 );
3641 },
3642 );
3643 };
3644
3645 let tail = self.selections.newest::<Point>(cx).tail();
3646 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3647 self.columnar_selection_state = match mode {
3648 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3649 selection_tail: selection_anchor,
3650 display_point: if reset {
3651 if position.column() != goal_column {
3652 Some(DisplayPoint::new(position.row(), goal_column))
3653 } else {
3654 None
3655 }
3656 } else {
3657 None
3658 },
3659 }),
3660 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3661 selection_tail: selection_anchor,
3662 }),
3663 };
3664
3665 if !reset {
3666 self.select_columns(position, goal_column, &display_map, window, cx);
3667 }
3668 }
3669
3670 fn update_selection(
3671 &mut self,
3672 position: DisplayPoint,
3673 goal_column: u32,
3674 scroll_delta: gpui::Point<f32>,
3675 window: &mut Window,
3676 cx: &mut Context<Self>,
3677 ) {
3678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3679
3680 if self.columnar_selection_state.is_some() {
3681 self.select_columns(position, goal_column, &display_map, window, cx);
3682 } else if let Some(mut pending) = self.selections.pending_anchor() {
3683 let buffer = &display_map.buffer_snapshot;
3684 let head;
3685 let tail;
3686 let mode = self.selections.pending_mode().unwrap();
3687 match &mode {
3688 SelectMode::Character => {
3689 head = position.to_point(&display_map);
3690 tail = pending.tail().to_point(buffer);
3691 }
3692 SelectMode::Word(original_range) => {
3693 let offset = display_map
3694 .clip_point(position, Bias::Left)
3695 .to_offset(&display_map, Bias::Left);
3696 let original_range = original_range.to_offset(buffer);
3697
3698 let head_offset = if buffer.is_inside_word(offset, false)
3699 || original_range.contains(&offset)
3700 {
3701 let (word_range, _) = buffer.surrounding_word(offset, false);
3702 if word_range.start < original_range.start {
3703 word_range.start
3704 } else {
3705 word_range.end
3706 }
3707 } else {
3708 offset
3709 };
3710
3711 head = head_offset.to_point(buffer);
3712 if head_offset <= original_range.start {
3713 tail = original_range.end.to_point(buffer);
3714 } else {
3715 tail = original_range.start.to_point(buffer);
3716 }
3717 }
3718 SelectMode::Line(original_range) => {
3719 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3720
3721 let position = display_map
3722 .clip_point(position, Bias::Left)
3723 .to_point(&display_map);
3724 let line_start = display_map.prev_line_boundary(position).0;
3725 let next_line_start = buffer.clip_point(
3726 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3727 Bias::Left,
3728 );
3729
3730 if line_start < original_range.start {
3731 head = line_start
3732 } else {
3733 head = next_line_start
3734 }
3735
3736 if head <= original_range.start {
3737 tail = original_range.end;
3738 } else {
3739 tail = original_range.start;
3740 }
3741 }
3742 SelectMode::All => {
3743 return;
3744 }
3745 };
3746
3747 if head < tail {
3748 pending.start = buffer.anchor_before(head);
3749 pending.end = buffer.anchor_before(tail);
3750 pending.reversed = true;
3751 } else {
3752 pending.start = buffer.anchor_before(tail);
3753 pending.end = buffer.anchor_before(head);
3754 pending.reversed = false;
3755 }
3756
3757 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3758 s.set_pending(pending, mode);
3759 });
3760 } else {
3761 log::error!("update_selection dispatched with no pending selection");
3762 return;
3763 }
3764
3765 self.apply_scroll_delta(scroll_delta, window, cx);
3766 cx.notify();
3767 }
3768
3769 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3770 self.columnar_selection_state.take();
3771 if self.selections.pending_anchor().is_some() {
3772 let selections = self.selections.all::<usize>(cx);
3773 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3774 s.select(selections);
3775 s.clear_pending();
3776 });
3777 }
3778 }
3779
3780 fn select_columns(
3781 &mut self,
3782 head: DisplayPoint,
3783 goal_column: u32,
3784 display_map: &DisplaySnapshot,
3785 window: &mut Window,
3786 cx: &mut Context<Self>,
3787 ) {
3788 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3789 return;
3790 };
3791
3792 let tail = match columnar_state {
3793 ColumnarSelectionState::FromMouse {
3794 selection_tail,
3795 display_point,
3796 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3797 ColumnarSelectionState::FromSelection { selection_tail } => {
3798 selection_tail.to_display_point(display_map)
3799 }
3800 };
3801
3802 let start_row = cmp::min(tail.row(), head.row());
3803 let end_row = cmp::max(tail.row(), head.row());
3804 let start_column = cmp::min(tail.column(), goal_column);
3805 let end_column = cmp::max(tail.column(), goal_column);
3806 let reversed = start_column < tail.column();
3807
3808 let selection_ranges = (start_row.0..=end_row.0)
3809 .map(DisplayRow)
3810 .filter_map(|row| {
3811 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3812 || start_column <= display_map.line_len(row))
3813 && !display_map.is_block_line(row)
3814 {
3815 let start = display_map
3816 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3817 .to_point(display_map);
3818 let end = display_map
3819 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3820 .to_point(display_map);
3821 if reversed {
3822 Some(end..start)
3823 } else {
3824 Some(start..end)
3825 }
3826 } else {
3827 None
3828 }
3829 })
3830 .collect::<Vec<_>>();
3831
3832 let ranges = match columnar_state {
3833 ColumnarSelectionState::FromMouse { .. } => {
3834 let mut non_empty_ranges = selection_ranges
3835 .iter()
3836 .filter(|selection_range| selection_range.start != selection_range.end)
3837 .peekable();
3838 if non_empty_ranges.peek().is_some() {
3839 non_empty_ranges.cloned().collect()
3840 } else {
3841 selection_ranges
3842 }
3843 }
3844 _ => selection_ranges,
3845 };
3846
3847 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3848 s.select_ranges(ranges);
3849 });
3850 cx.notify();
3851 }
3852
3853 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3854 self.selections
3855 .all_adjusted(cx)
3856 .iter()
3857 .any(|selection| !selection.is_empty())
3858 }
3859
3860 pub fn has_pending_nonempty_selection(&self) -> bool {
3861 let pending_nonempty_selection = match self.selections.pending_anchor() {
3862 Some(Selection { start, end, .. }) => start != end,
3863 None => false,
3864 };
3865
3866 pending_nonempty_selection
3867 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3868 }
3869
3870 pub fn has_pending_selection(&self) -> bool {
3871 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3872 }
3873
3874 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3875 self.selection_mark_mode = false;
3876 self.selection_drag_state = SelectionDragState::None;
3877
3878 if self.clear_expanded_diff_hunks(cx) {
3879 cx.notify();
3880 return;
3881 }
3882 if self.dismiss_menus_and_popups(true, window, cx) {
3883 return;
3884 }
3885
3886 if self.mode.is_full()
3887 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3888 {
3889 return;
3890 }
3891
3892 cx.propagate();
3893 }
3894
3895 pub fn dismiss_menus_and_popups(
3896 &mut self,
3897 is_user_requested: bool,
3898 window: &mut Window,
3899 cx: &mut Context<Self>,
3900 ) -> bool {
3901 if self.take_rename(false, window, cx).is_some() {
3902 return true;
3903 }
3904
3905 if hide_hover(self, cx) {
3906 return true;
3907 }
3908
3909 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3910 return true;
3911 }
3912
3913 if self.hide_context_menu(window, cx).is_some() {
3914 return true;
3915 }
3916
3917 if self.mouse_context_menu.take().is_some() {
3918 return true;
3919 }
3920
3921 if is_user_requested && self.discard_edit_prediction(true, cx) {
3922 return true;
3923 }
3924
3925 if self.snippet_stack.pop().is_some() {
3926 return true;
3927 }
3928
3929 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3930 self.dismiss_diagnostics(cx);
3931 return true;
3932 }
3933
3934 false
3935 }
3936
3937 fn linked_editing_ranges_for(
3938 &self,
3939 selection: Range<text::Anchor>,
3940 cx: &App,
3941 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3942 if self.linked_edit_ranges.is_empty() {
3943 return None;
3944 }
3945 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3946 selection.end.buffer_id.and_then(|end_buffer_id| {
3947 if selection.start.buffer_id != Some(end_buffer_id) {
3948 return None;
3949 }
3950 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3951 let snapshot = buffer.read(cx).snapshot();
3952 self.linked_edit_ranges
3953 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3954 .map(|ranges| (ranges, snapshot, buffer))
3955 })?;
3956 use text::ToOffset as TO;
3957 // find offset from the start of current range to current cursor position
3958 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3959
3960 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3961 let start_difference = start_offset - start_byte_offset;
3962 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3963 let end_difference = end_offset - start_byte_offset;
3964 // Current range has associated linked ranges.
3965 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3966 for range in linked_ranges.iter() {
3967 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3968 let end_offset = start_offset + end_difference;
3969 let start_offset = start_offset + start_difference;
3970 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3971 continue;
3972 }
3973 if self.selections.disjoint_anchor_ranges().any(|s| {
3974 if s.start.buffer_id != selection.start.buffer_id
3975 || s.end.buffer_id != selection.end.buffer_id
3976 {
3977 return false;
3978 }
3979 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3980 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3981 }) {
3982 continue;
3983 }
3984 let start = buffer_snapshot.anchor_after(start_offset);
3985 let end = buffer_snapshot.anchor_after(end_offset);
3986 linked_edits
3987 .entry(buffer.clone())
3988 .or_default()
3989 .push(start..end);
3990 }
3991 Some(linked_edits)
3992 }
3993
3994 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3995 let text: Arc<str> = text.into();
3996
3997 if self.read_only(cx) {
3998 return;
3999 }
4000
4001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4002
4003 let selections = self.selections.all_adjusted(cx);
4004 let mut bracket_inserted = false;
4005 let mut edits = Vec::new();
4006 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4007 let mut new_selections = Vec::with_capacity(selections.len());
4008 let mut new_autoclose_regions = Vec::new();
4009 let snapshot = self.buffer.read(cx).read(cx);
4010 let mut clear_linked_edit_ranges = false;
4011
4012 for (selection, autoclose_region) in
4013 self.selections_with_autoclose_regions(selections, &snapshot)
4014 {
4015 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4016 // Determine if the inserted text matches the opening or closing
4017 // bracket of any of this language's bracket pairs.
4018 let mut bracket_pair = None;
4019 let mut is_bracket_pair_start = false;
4020 let mut is_bracket_pair_end = false;
4021 if !text.is_empty() {
4022 let mut bracket_pair_matching_end = None;
4023 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4024 // and they are removing the character that triggered IME popup.
4025 for (pair, enabled) in scope.brackets() {
4026 if !pair.close && !pair.surround {
4027 continue;
4028 }
4029
4030 if enabled && pair.start.ends_with(text.as_ref()) {
4031 let prefix_len = pair.start.len() - text.len();
4032 let preceding_text_matches_prefix = prefix_len == 0
4033 || (selection.start.column >= (prefix_len as u32)
4034 && snapshot.contains_str_at(
4035 Point::new(
4036 selection.start.row,
4037 selection.start.column - (prefix_len as u32),
4038 ),
4039 &pair.start[..prefix_len],
4040 ));
4041 if preceding_text_matches_prefix {
4042 bracket_pair = Some(pair.clone());
4043 is_bracket_pair_start = true;
4044 break;
4045 }
4046 }
4047 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4048 {
4049 // take first bracket pair matching end, but don't break in case a later bracket
4050 // pair matches start
4051 bracket_pair_matching_end = Some(pair.clone());
4052 }
4053 }
4054 if let Some(end) = bracket_pair_matching_end
4055 && bracket_pair.is_none()
4056 {
4057 bracket_pair = Some(end);
4058 is_bracket_pair_end = true;
4059 }
4060 }
4061
4062 if let Some(bracket_pair) = bracket_pair {
4063 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4064 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4065 let auto_surround =
4066 self.use_auto_surround && snapshot_settings.use_auto_surround;
4067 if selection.is_empty() {
4068 if is_bracket_pair_start {
4069 // If the inserted text is a suffix of an opening bracket and the
4070 // selection is preceded by the rest of the opening bracket, then
4071 // insert the closing bracket.
4072 let following_text_allows_autoclose = snapshot
4073 .chars_at(selection.start)
4074 .next()
4075 .is_none_or(|c| scope.should_autoclose_before(c));
4076
4077 let preceding_text_allows_autoclose = selection.start.column == 0
4078 || snapshot
4079 .reversed_chars_at(selection.start)
4080 .next()
4081 .is_none_or(|c| {
4082 bracket_pair.start != bracket_pair.end
4083 || !snapshot
4084 .char_classifier_at(selection.start)
4085 .is_word(c)
4086 });
4087
4088 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4089 && bracket_pair.start.len() == 1
4090 {
4091 let target = bracket_pair.start.chars().next().unwrap();
4092 let current_line_count = snapshot
4093 .reversed_chars_at(selection.start)
4094 .take_while(|&c| c != '\n')
4095 .filter(|&c| c == target)
4096 .count();
4097 current_line_count % 2 == 1
4098 } else {
4099 false
4100 };
4101
4102 if autoclose
4103 && bracket_pair.close
4104 && following_text_allows_autoclose
4105 && preceding_text_allows_autoclose
4106 && !is_closing_quote
4107 {
4108 let anchor = snapshot.anchor_before(selection.end);
4109 new_selections.push((selection.map(|_| anchor), text.len()));
4110 new_autoclose_regions.push((
4111 anchor,
4112 text.len(),
4113 selection.id,
4114 bracket_pair.clone(),
4115 ));
4116 edits.push((
4117 selection.range(),
4118 format!("{}{}", text, bracket_pair.end).into(),
4119 ));
4120 bracket_inserted = true;
4121 continue;
4122 }
4123 }
4124
4125 if let Some(region) = autoclose_region {
4126 // If the selection is followed by an auto-inserted closing bracket,
4127 // then don't insert that closing bracket again; just move the selection
4128 // past the closing bracket.
4129 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4130 && text.as_ref() == region.pair.end.as_str()
4131 && snapshot.contains_str_at(region.range.end, text.as_ref());
4132 if should_skip {
4133 let anchor = snapshot.anchor_after(selection.end);
4134 new_selections
4135 .push((selection.map(|_| anchor), region.pair.end.len()));
4136 continue;
4137 }
4138 }
4139
4140 let always_treat_brackets_as_autoclosed = snapshot
4141 .language_settings_at(selection.start, cx)
4142 .always_treat_brackets_as_autoclosed;
4143 if always_treat_brackets_as_autoclosed
4144 && is_bracket_pair_end
4145 && snapshot.contains_str_at(selection.end, text.as_ref())
4146 {
4147 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4148 // and the inserted text is a closing bracket and the selection is followed
4149 // by the closing bracket then move the selection past the closing bracket.
4150 let anchor = snapshot.anchor_after(selection.end);
4151 new_selections.push((selection.map(|_| anchor), text.len()));
4152 continue;
4153 }
4154 }
4155 // If an opening bracket is 1 character long and is typed while
4156 // text is selected, then surround that text with the bracket pair.
4157 else if auto_surround
4158 && bracket_pair.surround
4159 && is_bracket_pair_start
4160 && bracket_pair.start.chars().count() == 1
4161 {
4162 edits.push((selection.start..selection.start, text.clone()));
4163 edits.push((
4164 selection.end..selection.end,
4165 bracket_pair.end.as_str().into(),
4166 ));
4167 bracket_inserted = true;
4168 new_selections.push((
4169 Selection {
4170 id: selection.id,
4171 start: snapshot.anchor_after(selection.start),
4172 end: snapshot.anchor_before(selection.end),
4173 reversed: selection.reversed,
4174 goal: selection.goal,
4175 },
4176 0,
4177 ));
4178 continue;
4179 }
4180 }
4181 }
4182
4183 if self.auto_replace_emoji_shortcode
4184 && selection.is_empty()
4185 && text.as_ref().ends_with(':')
4186 && let Some(possible_emoji_short_code) =
4187 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4188 && !possible_emoji_short_code.is_empty()
4189 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4190 {
4191 let emoji_shortcode_start = Point::new(
4192 selection.start.row,
4193 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4194 );
4195
4196 // Remove shortcode from buffer
4197 edits.push((
4198 emoji_shortcode_start..selection.start,
4199 "".to_string().into(),
4200 ));
4201 new_selections.push((
4202 Selection {
4203 id: selection.id,
4204 start: snapshot.anchor_after(emoji_shortcode_start),
4205 end: snapshot.anchor_before(selection.start),
4206 reversed: selection.reversed,
4207 goal: selection.goal,
4208 },
4209 0,
4210 ));
4211
4212 // Insert emoji
4213 let selection_start_anchor = snapshot.anchor_after(selection.start);
4214 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4215 edits.push((selection.start..selection.end, emoji.to_string().into()));
4216
4217 continue;
4218 }
4219
4220 // If not handling any auto-close operation, then just replace the selected
4221 // text with the given input and move the selection to the end of the
4222 // newly inserted text.
4223 let anchor = snapshot.anchor_after(selection.end);
4224 if !self.linked_edit_ranges.is_empty() {
4225 let start_anchor = snapshot.anchor_before(selection.start);
4226
4227 let is_word_char = text.chars().next().is_none_or(|char| {
4228 let classifier = snapshot
4229 .char_classifier_at(start_anchor.to_offset(&snapshot))
4230 .ignore_punctuation(true);
4231 classifier.is_word(char)
4232 });
4233
4234 if is_word_char {
4235 if let Some(ranges) = self
4236 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4237 {
4238 for (buffer, edits) in ranges {
4239 linked_edits
4240 .entry(buffer.clone())
4241 .or_default()
4242 .extend(edits.into_iter().map(|range| (range, text.clone())));
4243 }
4244 }
4245 } else {
4246 clear_linked_edit_ranges = true;
4247 }
4248 }
4249
4250 new_selections.push((selection.map(|_| anchor), 0));
4251 edits.push((selection.start..selection.end, text.clone()));
4252 }
4253
4254 drop(snapshot);
4255
4256 self.transact(window, cx, |this, window, cx| {
4257 if clear_linked_edit_ranges {
4258 this.linked_edit_ranges.clear();
4259 }
4260 let initial_buffer_versions =
4261 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4262
4263 this.buffer.update(cx, |buffer, cx| {
4264 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4265 });
4266 for (buffer, edits) in linked_edits {
4267 buffer.update(cx, |buffer, cx| {
4268 let snapshot = buffer.snapshot();
4269 let edits = edits
4270 .into_iter()
4271 .map(|(range, text)| {
4272 use text::ToPoint as TP;
4273 let end_point = TP::to_point(&range.end, &snapshot);
4274 let start_point = TP::to_point(&range.start, &snapshot);
4275 (start_point..end_point, text)
4276 })
4277 .sorted_by_key(|(range, _)| range.start);
4278 buffer.edit(edits, None, cx);
4279 })
4280 }
4281 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4282 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4283 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4284 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4285 .zip(new_selection_deltas)
4286 .map(|(selection, delta)| Selection {
4287 id: selection.id,
4288 start: selection.start + delta,
4289 end: selection.end + delta,
4290 reversed: selection.reversed,
4291 goal: SelectionGoal::None,
4292 })
4293 .collect::<Vec<_>>();
4294
4295 let mut i = 0;
4296 for (position, delta, selection_id, pair) in new_autoclose_regions {
4297 let position = position.to_offset(&map.buffer_snapshot) + delta;
4298 let start = map.buffer_snapshot.anchor_before(position);
4299 let end = map.buffer_snapshot.anchor_after(position);
4300 while let Some(existing_state) = this.autoclose_regions.get(i) {
4301 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4302 Ordering::Less => i += 1,
4303 Ordering::Greater => break,
4304 Ordering::Equal => {
4305 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4306 Ordering::Less => i += 1,
4307 Ordering::Equal => break,
4308 Ordering::Greater => break,
4309 }
4310 }
4311 }
4312 }
4313 this.autoclose_regions.insert(
4314 i,
4315 AutocloseRegion {
4316 selection_id,
4317 range: start..end,
4318 pair,
4319 },
4320 );
4321 }
4322
4323 let had_active_edit_prediction = this.has_active_edit_prediction();
4324 this.change_selections(
4325 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4326 window,
4327 cx,
4328 |s| s.select(new_selections),
4329 );
4330
4331 if !bracket_inserted
4332 && let Some(on_type_format_task) =
4333 this.trigger_on_type_formatting(text.to_string(), window, cx)
4334 {
4335 on_type_format_task.detach_and_log_err(cx);
4336 }
4337
4338 let editor_settings = EditorSettings::get_global(cx);
4339 if bracket_inserted
4340 && (editor_settings.auto_signature_help
4341 || editor_settings.show_signature_help_after_edits)
4342 {
4343 this.show_signature_help(&ShowSignatureHelp, window, cx);
4344 }
4345
4346 let trigger_in_words =
4347 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4348 if this.hard_wrap.is_some() {
4349 let latest: Range<Point> = this.selections.newest(cx).range();
4350 if latest.is_empty()
4351 && this
4352 .buffer()
4353 .read(cx)
4354 .snapshot(cx)
4355 .line_len(MultiBufferRow(latest.start.row))
4356 == latest.start.column
4357 {
4358 this.rewrap_impl(
4359 RewrapOptions {
4360 override_language_settings: true,
4361 preserve_existing_whitespace: true,
4362 },
4363 cx,
4364 )
4365 }
4366 }
4367 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4368 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4369 this.refresh_edit_prediction(true, false, window, cx);
4370 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4371 });
4372 }
4373
4374 fn find_possible_emoji_shortcode_at_position(
4375 snapshot: &MultiBufferSnapshot,
4376 position: Point,
4377 ) -> Option<String> {
4378 let mut chars = Vec::new();
4379 let mut found_colon = false;
4380 for char in snapshot.reversed_chars_at(position).take(100) {
4381 // Found a possible emoji shortcode in the middle of the buffer
4382 if found_colon {
4383 if char.is_whitespace() {
4384 chars.reverse();
4385 return Some(chars.iter().collect());
4386 }
4387 // If the previous character is not a whitespace, we are in the middle of a word
4388 // and we only want to complete the shortcode if the word is made up of other emojis
4389 let mut containing_word = String::new();
4390 for ch in snapshot
4391 .reversed_chars_at(position)
4392 .skip(chars.len() + 1)
4393 .take(100)
4394 {
4395 if ch.is_whitespace() {
4396 break;
4397 }
4398 containing_word.push(ch);
4399 }
4400 let containing_word = containing_word.chars().rev().collect::<String>();
4401 if util::word_consists_of_emojis(containing_word.as_str()) {
4402 chars.reverse();
4403 return Some(chars.iter().collect());
4404 }
4405 }
4406
4407 if char.is_whitespace() || !char.is_ascii() {
4408 return None;
4409 }
4410 if char == ':' {
4411 found_colon = true;
4412 } else {
4413 chars.push(char);
4414 }
4415 }
4416 // Found a possible emoji shortcode at the beginning of the buffer
4417 chars.reverse();
4418 Some(chars.iter().collect())
4419 }
4420
4421 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4423 self.transact(window, cx, |this, window, cx| {
4424 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4425 let selections = this.selections.all::<usize>(cx);
4426 let multi_buffer = this.buffer.read(cx);
4427 let buffer = multi_buffer.snapshot(cx);
4428 selections
4429 .iter()
4430 .map(|selection| {
4431 let start_point = selection.start.to_point(&buffer);
4432 let mut existing_indent =
4433 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4434 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4435 let start = selection.start;
4436 let end = selection.end;
4437 let selection_is_empty = start == end;
4438 let language_scope = buffer.language_scope_at(start);
4439 let (
4440 comment_delimiter,
4441 doc_delimiter,
4442 insert_extra_newline,
4443 indent_on_newline,
4444 indent_on_extra_newline,
4445 ) = if let Some(language) = &language_scope {
4446 let mut insert_extra_newline =
4447 insert_extra_newline_brackets(&buffer, start..end, language)
4448 || insert_extra_newline_tree_sitter(&buffer, start..end);
4449
4450 // Comment extension on newline is allowed only for cursor selections
4451 let comment_delimiter = maybe!({
4452 if !selection_is_empty {
4453 return None;
4454 }
4455
4456 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4457 return None;
4458 }
4459
4460 let delimiters = language.line_comment_prefixes();
4461 let max_len_of_delimiter =
4462 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4463 let (snapshot, range) =
4464 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4465
4466 let num_of_whitespaces = snapshot
4467 .chars_for_range(range.clone())
4468 .take_while(|c| c.is_whitespace())
4469 .count();
4470 let comment_candidate = snapshot
4471 .chars_for_range(range.clone())
4472 .skip(num_of_whitespaces)
4473 .take(max_len_of_delimiter)
4474 .collect::<String>();
4475 let (delimiter, trimmed_len) = delimiters
4476 .iter()
4477 .filter_map(|delimiter| {
4478 let prefix = delimiter.trim_end();
4479 if comment_candidate.starts_with(prefix) {
4480 Some((delimiter, prefix.len()))
4481 } else {
4482 None
4483 }
4484 })
4485 .max_by_key(|(_, len)| *len)?;
4486
4487 if let Some(BlockCommentConfig {
4488 start: block_start, ..
4489 }) = language.block_comment()
4490 {
4491 let block_start_trimmed = block_start.trim_end();
4492 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4493 let line_content = snapshot
4494 .chars_for_range(range)
4495 .skip(num_of_whitespaces)
4496 .take(block_start_trimmed.len())
4497 .collect::<String>();
4498
4499 if line_content.starts_with(block_start_trimmed) {
4500 return None;
4501 }
4502 }
4503 }
4504
4505 let cursor_is_placed_after_comment_marker =
4506 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4507 if cursor_is_placed_after_comment_marker {
4508 Some(delimiter.clone())
4509 } else {
4510 None
4511 }
4512 });
4513
4514 let mut indent_on_newline = IndentSize::spaces(0);
4515 let mut indent_on_extra_newline = IndentSize::spaces(0);
4516
4517 let doc_delimiter = maybe!({
4518 if !selection_is_empty {
4519 return None;
4520 }
4521
4522 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4523 return None;
4524 }
4525
4526 let BlockCommentConfig {
4527 start: start_tag,
4528 end: end_tag,
4529 prefix: delimiter,
4530 tab_size: len,
4531 } = language.documentation_comment()?;
4532 let is_within_block_comment = buffer
4533 .language_scope_at(start_point)
4534 .is_some_and(|scope| scope.override_name() == Some("comment"));
4535 if !is_within_block_comment {
4536 return None;
4537 }
4538
4539 let (snapshot, range) =
4540 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4541
4542 let num_of_whitespaces = snapshot
4543 .chars_for_range(range.clone())
4544 .take_while(|c| c.is_whitespace())
4545 .count();
4546
4547 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4548 let column = start_point.column;
4549 let cursor_is_after_start_tag = {
4550 let start_tag_len = start_tag.len();
4551 let start_tag_line = snapshot
4552 .chars_for_range(range.clone())
4553 .skip(num_of_whitespaces)
4554 .take(start_tag_len)
4555 .collect::<String>();
4556 if start_tag_line.starts_with(start_tag.as_ref()) {
4557 num_of_whitespaces + start_tag_len <= column as usize
4558 } else {
4559 false
4560 }
4561 };
4562
4563 let cursor_is_after_delimiter = {
4564 let delimiter_trim = delimiter.trim_end();
4565 let delimiter_line = snapshot
4566 .chars_for_range(range.clone())
4567 .skip(num_of_whitespaces)
4568 .take(delimiter_trim.len())
4569 .collect::<String>();
4570 if delimiter_line.starts_with(delimiter_trim) {
4571 num_of_whitespaces + delimiter_trim.len() <= column as usize
4572 } else {
4573 false
4574 }
4575 };
4576
4577 let cursor_is_before_end_tag_if_exists = {
4578 let mut char_position = 0u32;
4579 let mut end_tag_offset = None;
4580
4581 'outer: for chunk in snapshot.text_for_range(range) {
4582 if let Some(byte_pos) = chunk.find(&**end_tag) {
4583 let chars_before_match =
4584 chunk[..byte_pos].chars().count() as u32;
4585 end_tag_offset =
4586 Some(char_position + chars_before_match);
4587 break 'outer;
4588 }
4589 char_position += chunk.chars().count() as u32;
4590 }
4591
4592 if let Some(end_tag_offset) = end_tag_offset {
4593 let cursor_is_before_end_tag = column <= end_tag_offset;
4594 if cursor_is_after_start_tag {
4595 if cursor_is_before_end_tag {
4596 insert_extra_newline = true;
4597 }
4598 let cursor_is_at_start_of_end_tag =
4599 column == end_tag_offset;
4600 if cursor_is_at_start_of_end_tag {
4601 indent_on_extra_newline.len = *len;
4602 }
4603 }
4604 cursor_is_before_end_tag
4605 } else {
4606 true
4607 }
4608 };
4609
4610 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4611 && cursor_is_before_end_tag_if_exists
4612 {
4613 if cursor_is_after_start_tag {
4614 indent_on_newline.len = *len;
4615 }
4616 Some(delimiter.clone())
4617 } else {
4618 None
4619 }
4620 });
4621
4622 (
4623 comment_delimiter,
4624 doc_delimiter,
4625 insert_extra_newline,
4626 indent_on_newline,
4627 indent_on_extra_newline,
4628 )
4629 } else {
4630 (
4631 None,
4632 None,
4633 false,
4634 IndentSize::default(),
4635 IndentSize::default(),
4636 )
4637 };
4638
4639 let prevent_auto_indent = doc_delimiter.is_some();
4640 let delimiter = comment_delimiter.or(doc_delimiter);
4641
4642 let capacity_for_delimiter =
4643 delimiter.as_deref().map(str::len).unwrap_or_default();
4644 let mut new_text = String::with_capacity(
4645 1 + capacity_for_delimiter
4646 + existing_indent.len as usize
4647 + indent_on_newline.len as usize
4648 + indent_on_extra_newline.len as usize,
4649 );
4650 new_text.push('\n');
4651 new_text.extend(existing_indent.chars());
4652 new_text.extend(indent_on_newline.chars());
4653
4654 if let Some(delimiter) = &delimiter {
4655 new_text.push_str(delimiter);
4656 }
4657
4658 if insert_extra_newline {
4659 new_text.push('\n');
4660 new_text.extend(existing_indent.chars());
4661 new_text.extend(indent_on_extra_newline.chars());
4662 }
4663
4664 let anchor = buffer.anchor_after(end);
4665 let new_selection = selection.map(|_| anchor);
4666 (
4667 ((start..end, new_text), prevent_auto_indent),
4668 (insert_extra_newline, new_selection),
4669 )
4670 })
4671 .unzip()
4672 };
4673
4674 let mut auto_indent_edits = Vec::new();
4675 let mut edits = Vec::new();
4676 for (edit, prevent_auto_indent) in edits_with_flags {
4677 if prevent_auto_indent {
4678 edits.push(edit);
4679 } else {
4680 auto_indent_edits.push(edit);
4681 }
4682 }
4683 if !edits.is_empty() {
4684 this.edit(edits, cx);
4685 }
4686 if !auto_indent_edits.is_empty() {
4687 this.edit_with_autoindent(auto_indent_edits, cx);
4688 }
4689
4690 let buffer = this.buffer.read(cx).snapshot(cx);
4691 let new_selections = selection_info
4692 .into_iter()
4693 .map(|(extra_newline_inserted, new_selection)| {
4694 let mut cursor = new_selection.end.to_point(&buffer);
4695 if extra_newline_inserted {
4696 cursor.row -= 1;
4697 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4698 }
4699 new_selection.map(|_| cursor)
4700 })
4701 .collect();
4702
4703 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4704 this.refresh_edit_prediction(true, false, window, cx);
4705 });
4706 }
4707
4708 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4709 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4710
4711 let buffer = self.buffer.read(cx);
4712 let snapshot = buffer.snapshot(cx);
4713
4714 let mut edits = Vec::new();
4715 let mut rows = Vec::new();
4716
4717 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4718 let cursor = selection.head();
4719 let row = cursor.row;
4720
4721 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4722
4723 let newline = "\n".to_string();
4724 edits.push((start_of_line..start_of_line, newline));
4725
4726 rows.push(row + rows_inserted as u32);
4727 }
4728
4729 self.transact(window, cx, |editor, window, cx| {
4730 editor.edit(edits, cx);
4731
4732 editor.change_selections(Default::default(), window, cx, |s| {
4733 let mut index = 0;
4734 s.move_cursors_with(|map, _, _| {
4735 let row = rows[index];
4736 index += 1;
4737
4738 let point = Point::new(row, 0);
4739 let boundary = map.next_line_boundary(point).1;
4740 let clipped = map.clip_point(boundary, Bias::Left);
4741
4742 (clipped, SelectionGoal::None)
4743 });
4744 });
4745
4746 let mut indent_edits = Vec::new();
4747 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4748 for row in rows {
4749 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4750 for (row, indent) in indents {
4751 if indent.len == 0 {
4752 continue;
4753 }
4754
4755 let text = match indent.kind {
4756 IndentKind::Space => " ".repeat(indent.len as usize),
4757 IndentKind::Tab => "\t".repeat(indent.len as usize),
4758 };
4759 let point = Point::new(row.0, 0);
4760 indent_edits.push((point..point, text));
4761 }
4762 }
4763 editor.edit(indent_edits, cx);
4764 });
4765 }
4766
4767 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4769
4770 let buffer = self.buffer.read(cx);
4771 let snapshot = buffer.snapshot(cx);
4772
4773 let mut edits = Vec::new();
4774 let mut rows = Vec::new();
4775 let mut rows_inserted = 0;
4776
4777 for selection in self.selections.all_adjusted(cx) {
4778 let cursor = selection.head();
4779 let row = cursor.row;
4780
4781 let point = Point::new(row + 1, 0);
4782 let start_of_line = snapshot.clip_point(point, Bias::Left);
4783
4784 let newline = "\n".to_string();
4785 edits.push((start_of_line..start_of_line, newline));
4786
4787 rows_inserted += 1;
4788 rows.push(row + rows_inserted);
4789 }
4790
4791 self.transact(window, cx, |editor, window, cx| {
4792 editor.edit(edits, cx);
4793
4794 editor.change_selections(Default::default(), window, cx, |s| {
4795 let mut index = 0;
4796 s.move_cursors_with(|map, _, _| {
4797 let row = rows[index];
4798 index += 1;
4799
4800 let point = Point::new(row, 0);
4801 let boundary = map.next_line_boundary(point).1;
4802 let clipped = map.clip_point(boundary, Bias::Left);
4803
4804 (clipped, SelectionGoal::None)
4805 });
4806 });
4807
4808 let mut indent_edits = Vec::new();
4809 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4810 for row in rows {
4811 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4812 for (row, indent) in indents {
4813 if indent.len == 0 {
4814 continue;
4815 }
4816
4817 let text = match indent.kind {
4818 IndentKind::Space => " ".repeat(indent.len as usize),
4819 IndentKind::Tab => "\t".repeat(indent.len as usize),
4820 };
4821 let point = Point::new(row.0, 0);
4822 indent_edits.push((point..point, text));
4823 }
4824 }
4825 editor.edit(indent_edits, cx);
4826 });
4827 }
4828
4829 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4830 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4831 original_indent_columns: Vec::new(),
4832 });
4833 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4834 }
4835
4836 fn insert_with_autoindent_mode(
4837 &mut self,
4838 text: &str,
4839 autoindent_mode: Option<AutoindentMode>,
4840 window: &mut Window,
4841 cx: &mut Context<Self>,
4842 ) {
4843 if self.read_only(cx) {
4844 return;
4845 }
4846
4847 let text: Arc<str> = text.into();
4848 self.transact(window, cx, |this, window, cx| {
4849 let old_selections = this.selections.all_adjusted(cx);
4850 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4851 let anchors = {
4852 let snapshot = buffer.read(cx);
4853 old_selections
4854 .iter()
4855 .map(|s| {
4856 let anchor = snapshot.anchor_after(s.head());
4857 s.map(|_| anchor)
4858 })
4859 .collect::<Vec<_>>()
4860 };
4861 buffer.edit(
4862 old_selections
4863 .iter()
4864 .map(|s| (s.start..s.end, text.clone())),
4865 autoindent_mode,
4866 cx,
4867 );
4868 anchors
4869 });
4870
4871 this.change_selections(Default::default(), window, cx, |s| {
4872 s.select_anchors(selection_anchors);
4873 });
4874
4875 cx.notify();
4876 });
4877 }
4878
4879 fn trigger_completion_on_input(
4880 &mut self,
4881 text: &str,
4882 trigger_in_words: bool,
4883 window: &mut Window,
4884 cx: &mut Context<Self>,
4885 ) {
4886 let completions_source = self
4887 .context_menu
4888 .borrow()
4889 .as_ref()
4890 .and_then(|menu| match menu {
4891 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4892 CodeContextMenu::CodeActions(_) => None,
4893 });
4894
4895 match completions_source {
4896 Some(CompletionsMenuSource::Words) => {
4897 self.show_word_completions(&ShowWordCompletions, window, cx)
4898 }
4899 Some(CompletionsMenuSource::Normal)
4900 | Some(CompletionsMenuSource::SnippetChoices)
4901 | None
4902 if self.is_completion_trigger(
4903 text,
4904 trigger_in_words,
4905 completions_source.is_some(),
4906 cx,
4907 ) =>
4908 {
4909 self.show_completions(
4910 &ShowCompletions {
4911 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4912 },
4913 window,
4914 cx,
4915 )
4916 }
4917 _ => {
4918 self.hide_context_menu(window, cx);
4919 }
4920 }
4921 }
4922
4923 fn is_completion_trigger(
4924 &self,
4925 text: &str,
4926 trigger_in_words: bool,
4927 menu_is_open: bool,
4928 cx: &mut Context<Self>,
4929 ) -> bool {
4930 let position = self.selections.newest_anchor().head();
4931 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4932 return false;
4933 };
4934
4935 if let Some(completion_provider) = &self.completion_provider {
4936 completion_provider.is_completion_trigger(
4937 &buffer,
4938 position.text_anchor,
4939 text,
4940 trigger_in_words,
4941 menu_is_open,
4942 cx,
4943 )
4944 } else {
4945 false
4946 }
4947 }
4948
4949 /// If any empty selections is touching the start of its innermost containing autoclose
4950 /// region, expand it to select the brackets.
4951 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4952 let selections = self.selections.all::<usize>(cx);
4953 let buffer = self.buffer.read(cx).read(cx);
4954 let new_selections = self
4955 .selections_with_autoclose_regions(selections, &buffer)
4956 .map(|(mut selection, region)| {
4957 if !selection.is_empty() {
4958 return selection;
4959 }
4960
4961 if let Some(region) = region {
4962 let mut range = region.range.to_offset(&buffer);
4963 if selection.start == range.start && range.start >= region.pair.start.len() {
4964 range.start -= region.pair.start.len();
4965 if buffer.contains_str_at(range.start, ®ion.pair.start)
4966 && buffer.contains_str_at(range.end, ®ion.pair.end)
4967 {
4968 range.end += region.pair.end.len();
4969 selection.start = range.start;
4970 selection.end = range.end;
4971
4972 return selection;
4973 }
4974 }
4975 }
4976
4977 let always_treat_brackets_as_autoclosed = buffer
4978 .language_settings_at(selection.start, cx)
4979 .always_treat_brackets_as_autoclosed;
4980
4981 if !always_treat_brackets_as_autoclosed {
4982 return selection;
4983 }
4984
4985 if let Some(scope) = buffer.language_scope_at(selection.start) {
4986 for (pair, enabled) in scope.brackets() {
4987 if !enabled || !pair.close {
4988 continue;
4989 }
4990
4991 if buffer.contains_str_at(selection.start, &pair.end) {
4992 let pair_start_len = pair.start.len();
4993 if buffer.contains_str_at(
4994 selection.start.saturating_sub(pair_start_len),
4995 &pair.start,
4996 ) {
4997 selection.start -= pair_start_len;
4998 selection.end += pair.end.len();
4999
5000 return selection;
5001 }
5002 }
5003 }
5004 }
5005
5006 selection
5007 })
5008 .collect();
5009
5010 drop(buffer);
5011 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5012 selections.select(new_selections)
5013 });
5014 }
5015
5016 /// Iterate the given selections, and for each one, find the smallest surrounding
5017 /// autoclose region. This uses the ordering of the selections and the autoclose
5018 /// regions to avoid repeated comparisons.
5019 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5020 &'a self,
5021 selections: impl IntoIterator<Item = Selection<D>>,
5022 buffer: &'a MultiBufferSnapshot,
5023 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5024 let mut i = 0;
5025 let mut regions = self.autoclose_regions.as_slice();
5026 selections.into_iter().map(move |selection| {
5027 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5028
5029 let mut enclosing = None;
5030 while let Some(pair_state) = regions.get(i) {
5031 if pair_state.range.end.to_offset(buffer) < range.start {
5032 regions = ®ions[i + 1..];
5033 i = 0;
5034 } else if pair_state.range.start.to_offset(buffer) > range.end {
5035 break;
5036 } else {
5037 if pair_state.selection_id == selection.id {
5038 enclosing = Some(pair_state);
5039 }
5040 i += 1;
5041 }
5042 }
5043
5044 (selection, enclosing)
5045 })
5046 }
5047
5048 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5049 fn invalidate_autoclose_regions(
5050 &mut self,
5051 mut selections: &[Selection<Anchor>],
5052 buffer: &MultiBufferSnapshot,
5053 ) {
5054 self.autoclose_regions.retain(|state| {
5055 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5056 return false;
5057 }
5058
5059 let mut i = 0;
5060 while let Some(selection) = selections.get(i) {
5061 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5062 selections = &selections[1..];
5063 continue;
5064 }
5065 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5066 break;
5067 }
5068 if selection.id == state.selection_id {
5069 return true;
5070 } else {
5071 i += 1;
5072 }
5073 }
5074 false
5075 });
5076 }
5077
5078 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5079 let offset = position.to_offset(buffer);
5080 let (word_range, kind) = buffer.surrounding_word(offset, true);
5081 if offset > word_range.start && kind == Some(CharKind::Word) {
5082 Some(
5083 buffer
5084 .text_for_range(word_range.start..offset)
5085 .collect::<String>(),
5086 )
5087 } else {
5088 None
5089 }
5090 }
5091
5092 pub fn toggle_inline_values(
5093 &mut self,
5094 _: &ToggleInlineValues,
5095 _: &mut Window,
5096 cx: &mut Context<Self>,
5097 ) {
5098 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5099
5100 self.refresh_inline_values(cx);
5101 }
5102
5103 pub fn toggle_inlay_hints(
5104 &mut self,
5105 _: &ToggleInlayHints,
5106 _: &mut Window,
5107 cx: &mut Context<Self>,
5108 ) {
5109 self.refresh_inlay_hints(
5110 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5111 cx,
5112 );
5113 }
5114
5115 pub fn inlay_hints_enabled(&self) -> bool {
5116 self.inlay_hint_cache.enabled
5117 }
5118
5119 pub fn inline_values_enabled(&self) -> bool {
5120 self.inline_value_cache.enabled
5121 }
5122
5123 #[cfg(any(test, feature = "test-support"))]
5124 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5125 self.display_map
5126 .read(cx)
5127 .current_inlays()
5128 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5129 .cloned()
5130 .collect()
5131 }
5132
5133 #[cfg(any(test, feature = "test-support"))]
5134 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5135 self.display_map
5136 .read(cx)
5137 .current_inlays()
5138 .cloned()
5139 .collect()
5140 }
5141
5142 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5143 if self.semantics_provider.is_none() || !self.mode.is_full() {
5144 return;
5145 }
5146
5147 let reason_description = reason.description();
5148 let ignore_debounce = matches!(
5149 reason,
5150 InlayHintRefreshReason::SettingsChange(_)
5151 | InlayHintRefreshReason::Toggle(_)
5152 | InlayHintRefreshReason::ExcerptsRemoved(_)
5153 | InlayHintRefreshReason::ModifiersChanged(_)
5154 );
5155 let (invalidate_cache, required_languages) = match reason {
5156 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5157 match self.inlay_hint_cache.modifiers_override(enabled) {
5158 Some(enabled) => {
5159 if enabled {
5160 (InvalidationStrategy::RefreshRequested, None)
5161 } else {
5162 self.splice_inlays(
5163 &self
5164 .visible_inlay_hints(cx)
5165 .iter()
5166 .map(|inlay| inlay.id)
5167 .collect::<Vec<InlayId>>(),
5168 Vec::new(),
5169 cx,
5170 );
5171 return;
5172 }
5173 }
5174 None => return,
5175 }
5176 }
5177 InlayHintRefreshReason::Toggle(enabled) => {
5178 if self.inlay_hint_cache.toggle(enabled) {
5179 if enabled {
5180 (InvalidationStrategy::RefreshRequested, None)
5181 } else {
5182 self.splice_inlays(
5183 &self
5184 .visible_inlay_hints(cx)
5185 .iter()
5186 .map(|inlay| inlay.id)
5187 .collect::<Vec<InlayId>>(),
5188 Vec::new(),
5189 cx,
5190 );
5191 return;
5192 }
5193 } else {
5194 return;
5195 }
5196 }
5197 InlayHintRefreshReason::SettingsChange(new_settings) => {
5198 match self.inlay_hint_cache.update_settings(
5199 &self.buffer,
5200 new_settings,
5201 self.visible_inlay_hints(cx),
5202 cx,
5203 ) {
5204 ControlFlow::Break(Some(InlaySplice {
5205 to_remove,
5206 to_insert,
5207 })) => {
5208 self.splice_inlays(&to_remove, to_insert, cx);
5209 return;
5210 }
5211 ControlFlow::Break(None) => return,
5212 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5213 }
5214 }
5215 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5216 if let Some(InlaySplice {
5217 to_remove,
5218 to_insert,
5219 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5220 {
5221 self.splice_inlays(&to_remove, to_insert, cx);
5222 }
5223 self.display_map.update(cx, |display_map, _| {
5224 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5225 });
5226 return;
5227 }
5228 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5229 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5230 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5231 }
5232 InlayHintRefreshReason::RefreshRequested => {
5233 (InvalidationStrategy::RefreshRequested, None)
5234 }
5235 };
5236
5237 if let Some(InlaySplice {
5238 to_remove,
5239 to_insert,
5240 }) = self.inlay_hint_cache.spawn_hint_refresh(
5241 reason_description,
5242 self.visible_excerpts(required_languages.as_ref(), cx),
5243 invalidate_cache,
5244 ignore_debounce,
5245 cx,
5246 ) {
5247 self.splice_inlays(&to_remove, to_insert, cx);
5248 }
5249 }
5250
5251 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5252 self.display_map
5253 .read(cx)
5254 .current_inlays()
5255 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5256 .cloned()
5257 .collect()
5258 }
5259
5260 pub fn visible_excerpts(
5261 &self,
5262 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5263 cx: &mut Context<Editor>,
5264 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5265 let Some(project) = self.project() else {
5266 return HashMap::default();
5267 };
5268 let project = project.read(cx);
5269 let multi_buffer = self.buffer().read(cx);
5270 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5271 let multi_buffer_visible_start = self
5272 .scroll_manager
5273 .anchor()
5274 .anchor
5275 .to_point(&multi_buffer_snapshot);
5276 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5277 multi_buffer_visible_start
5278 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5279 Bias::Left,
5280 );
5281 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5282 multi_buffer_snapshot
5283 .range_to_buffer_ranges(multi_buffer_visible_range)
5284 .into_iter()
5285 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5286 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5287 let buffer_file = project::File::from_dyn(buffer.file())?;
5288 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5289 let worktree_entry = buffer_worktree
5290 .read(cx)
5291 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5292 if worktree_entry.is_ignored {
5293 return None;
5294 }
5295
5296 let language = buffer.language()?;
5297 if let Some(restrict_to_languages) = restrict_to_languages
5298 && !restrict_to_languages.contains(language)
5299 {
5300 return None;
5301 }
5302 Some((
5303 excerpt_id,
5304 (
5305 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5306 buffer.version().clone(),
5307 excerpt_visible_range,
5308 ),
5309 ))
5310 })
5311 .collect()
5312 }
5313
5314 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5315 TextLayoutDetails {
5316 text_system: window.text_system().clone(),
5317 editor_style: self.style.clone().unwrap(),
5318 rem_size: window.rem_size(),
5319 scroll_anchor: self.scroll_manager.anchor(),
5320 visible_rows: self.visible_line_count(),
5321 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5322 }
5323 }
5324
5325 pub fn splice_inlays(
5326 &self,
5327 to_remove: &[InlayId],
5328 to_insert: Vec<Inlay>,
5329 cx: &mut Context<Self>,
5330 ) {
5331 self.display_map.update(cx, |display_map, cx| {
5332 display_map.splice_inlays(to_remove, to_insert, cx)
5333 });
5334 cx.notify();
5335 }
5336
5337 fn trigger_on_type_formatting(
5338 &self,
5339 input: String,
5340 window: &mut Window,
5341 cx: &mut Context<Self>,
5342 ) -> Option<Task<Result<()>>> {
5343 if input.len() != 1 {
5344 return None;
5345 }
5346
5347 let project = self.project()?;
5348 let position = self.selections.newest_anchor().head();
5349 let (buffer, buffer_position) = self
5350 .buffer
5351 .read(cx)
5352 .text_anchor_for_position(position, cx)?;
5353
5354 let settings = language_settings::language_settings(
5355 buffer
5356 .read(cx)
5357 .language_at(buffer_position)
5358 .map(|l| l.name()),
5359 buffer.read(cx).file(),
5360 cx,
5361 );
5362 if !settings.use_on_type_format {
5363 return None;
5364 }
5365
5366 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5367 // hence we do LSP request & edit on host side only — add formats to host's history.
5368 let push_to_lsp_host_history = true;
5369 // If this is not the host, append its history with new edits.
5370 let push_to_client_history = project.read(cx).is_via_collab();
5371
5372 let on_type_formatting = project.update(cx, |project, cx| {
5373 project.on_type_format(
5374 buffer.clone(),
5375 buffer_position,
5376 input,
5377 push_to_lsp_host_history,
5378 cx,
5379 )
5380 });
5381 Some(cx.spawn_in(window, async move |editor, cx| {
5382 if let Some(transaction) = on_type_formatting.await? {
5383 if push_to_client_history {
5384 buffer
5385 .update(cx, |buffer, _| {
5386 buffer.push_transaction(transaction, Instant::now());
5387 buffer.finalize_last_transaction();
5388 })
5389 .ok();
5390 }
5391 editor.update(cx, |editor, cx| {
5392 editor.refresh_document_highlights(cx);
5393 })?;
5394 }
5395 Ok(())
5396 }))
5397 }
5398
5399 pub fn show_word_completions(
5400 &mut self,
5401 _: &ShowWordCompletions,
5402 window: &mut Window,
5403 cx: &mut Context<Self>,
5404 ) {
5405 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5406 }
5407
5408 pub fn show_completions(
5409 &mut self,
5410 options: &ShowCompletions,
5411 window: &mut Window,
5412 cx: &mut Context<Self>,
5413 ) {
5414 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5415 }
5416
5417 fn open_or_update_completions_menu(
5418 &mut self,
5419 requested_source: Option<CompletionsMenuSource>,
5420 trigger: Option<&str>,
5421 window: &mut Window,
5422 cx: &mut Context<Self>,
5423 ) {
5424 if self.pending_rename.is_some() {
5425 return;
5426 }
5427
5428 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5429
5430 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5431 // inserted and selected. To handle that case, the start of the selection is used so that
5432 // the menu starts with all choices.
5433 let position = self
5434 .selections
5435 .newest_anchor()
5436 .start
5437 .bias_right(&multibuffer_snapshot);
5438 if position.diff_base_anchor.is_some() {
5439 return;
5440 }
5441 let (buffer, buffer_position) =
5442 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5443 output
5444 } else {
5445 return;
5446 };
5447 let buffer_snapshot = buffer.read(cx).snapshot();
5448
5449 let query: Option<Arc<String>> =
5450 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5451
5452 drop(multibuffer_snapshot);
5453
5454 let provider = match requested_source {
5455 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5456 Some(CompletionsMenuSource::Words) => None,
5457 Some(CompletionsMenuSource::SnippetChoices) => {
5458 log::error!("bug: SnippetChoices requested_source is not handled");
5459 None
5460 }
5461 };
5462
5463 let sort_completions = provider
5464 .as_ref()
5465 .is_some_and(|provider| provider.sort_completions());
5466
5467 let filter_completions = provider
5468 .as_ref()
5469 .is_none_or(|provider| provider.filter_completions());
5470
5471 let trigger_kind = match trigger {
5472 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5473 CompletionTriggerKind::TRIGGER_CHARACTER
5474 }
5475 _ => CompletionTriggerKind::INVOKED,
5476 };
5477 let completion_context = CompletionContext {
5478 trigger_character: trigger.and_then(|trigger| {
5479 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5480 Some(String::from(trigger))
5481 } else {
5482 None
5483 }
5484 }),
5485 trigger_kind,
5486 };
5487
5488 // Hide the current completions menu when a trigger char is typed. Without this, cached
5489 // completions from before the trigger char may be reused (#32774). Snippet choices could
5490 // involve trigger chars, so this is skipped in that case.
5491 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5492 {
5493 let menu_is_open = matches!(
5494 self.context_menu.borrow().as_ref(),
5495 Some(CodeContextMenu::Completions(_))
5496 );
5497 if menu_is_open {
5498 self.hide_context_menu(window, cx);
5499 }
5500 }
5501
5502 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5503 if filter_completions {
5504 menu.filter(query.clone(), provider.clone(), window, cx);
5505 }
5506 // When `is_incomplete` is false, no need to re-query completions when the current query
5507 // is a suffix of the initial query.
5508 if !menu.is_incomplete {
5509 // If the new query is a suffix of the old query (typing more characters) and
5510 // the previous result was complete, the existing completions can be filtered.
5511 //
5512 // Note that this is always true for snippet completions.
5513 let query_matches = match (&menu.initial_query, &query) {
5514 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5515 (None, _) => true,
5516 _ => false,
5517 };
5518 if query_matches {
5519 let position_matches = if menu.initial_position == position {
5520 true
5521 } else {
5522 let snapshot = self.buffer.read(cx).read(cx);
5523 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5524 };
5525 if position_matches {
5526 return;
5527 }
5528 }
5529 }
5530 };
5531
5532 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5533 buffer_snapshot.surrounding_word(buffer_position, false)
5534 {
5535 let word_to_exclude = buffer_snapshot
5536 .text_for_range(word_range.clone())
5537 .collect::<String>();
5538 (
5539 buffer_snapshot.anchor_before(word_range.start)
5540 ..buffer_snapshot.anchor_after(buffer_position),
5541 Some(word_to_exclude),
5542 )
5543 } else {
5544 (buffer_position..buffer_position, None)
5545 };
5546
5547 let language = buffer_snapshot
5548 .language_at(buffer_position)
5549 .map(|language| language.name());
5550
5551 let completion_settings =
5552 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5553
5554 let show_completion_documentation = buffer_snapshot
5555 .settings_at(buffer_position, cx)
5556 .show_completion_documentation;
5557
5558 // The document can be large, so stay in reasonable bounds when searching for words,
5559 // otherwise completion pop-up might be slow to appear.
5560 const WORD_LOOKUP_ROWS: u32 = 5_000;
5561 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5562 let min_word_search = buffer_snapshot.clip_point(
5563 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5564 Bias::Left,
5565 );
5566 let max_word_search = buffer_snapshot.clip_point(
5567 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5568 Bias::Right,
5569 );
5570 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5571 ..buffer_snapshot.point_to_offset(max_word_search);
5572
5573 let skip_digits = query
5574 .as_ref()
5575 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5576
5577 let omit_word_completions = match &query {
5578 Some(query) => query.chars().count() < completion_settings.words_min_length,
5579 None => completion_settings.words_min_length != 0,
5580 };
5581
5582 let (mut words, provider_responses) = match &provider {
5583 Some(provider) => {
5584 let provider_responses = provider.completions(
5585 position.excerpt_id,
5586 &buffer,
5587 buffer_position,
5588 completion_context,
5589 window,
5590 cx,
5591 );
5592
5593 let words = match (omit_word_completions, completion_settings.words) {
5594 (true, _) | (_, WordsCompletionMode::Disabled) => {
5595 Task::ready(BTreeMap::default())
5596 }
5597 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5598 .background_spawn(async move {
5599 buffer_snapshot.words_in_range(WordsQuery {
5600 fuzzy_contents: None,
5601 range: word_search_range,
5602 skip_digits,
5603 })
5604 }),
5605 };
5606
5607 (words, provider_responses)
5608 }
5609 None => {
5610 let words = if omit_word_completions {
5611 Task::ready(BTreeMap::default())
5612 } else {
5613 cx.background_spawn(async move {
5614 buffer_snapshot.words_in_range(WordsQuery {
5615 fuzzy_contents: None,
5616 range: word_search_range,
5617 skip_digits,
5618 })
5619 })
5620 };
5621 (words, Task::ready(Ok(Vec::new())))
5622 }
5623 };
5624
5625 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5626
5627 let id = post_inc(&mut self.next_completion_id);
5628 let task = cx.spawn_in(window, async move |editor, cx| {
5629 let Ok(()) = editor.update(cx, |this, _| {
5630 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5631 }) else {
5632 return;
5633 };
5634
5635 // TODO: Ideally completions from different sources would be selectively re-queried, so
5636 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5637 let mut completions = Vec::new();
5638 let mut is_incomplete = false;
5639 if let Some(provider_responses) = provider_responses.await.log_err()
5640 && !provider_responses.is_empty()
5641 {
5642 for response in provider_responses {
5643 completions.extend(response.completions);
5644 is_incomplete = is_incomplete || response.is_incomplete;
5645 }
5646 if completion_settings.words == WordsCompletionMode::Fallback {
5647 words = Task::ready(BTreeMap::default());
5648 }
5649 }
5650
5651 let mut words = words.await;
5652 if let Some(word_to_exclude) = &word_to_exclude {
5653 words.remove(word_to_exclude);
5654 }
5655 for lsp_completion in &completions {
5656 words.remove(&lsp_completion.new_text);
5657 }
5658 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5659 replace_range: word_replace_range.clone(),
5660 new_text: word.clone(),
5661 label: CodeLabel::plain(word, None),
5662 icon_path: None,
5663 documentation: None,
5664 source: CompletionSource::BufferWord {
5665 word_range,
5666 resolved: false,
5667 },
5668 insert_text_mode: Some(InsertTextMode::AS_IS),
5669 confirm: None,
5670 }));
5671
5672 let menu = if completions.is_empty() {
5673 None
5674 } else {
5675 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5676 let languages = editor
5677 .workspace
5678 .as_ref()
5679 .and_then(|(workspace, _)| workspace.upgrade())
5680 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5681 let menu = CompletionsMenu::new(
5682 id,
5683 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5684 sort_completions,
5685 show_completion_documentation,
5686 position,
5687 query.clone(),
5688 is_incomplete,
5689 buffer.clone(),
5690 completions.into(),
5691 snippet_sort_order,
5692 languages,
5693 language,
5694 cx,
5695 );
5696
5697 let query = if filter_completions { query } else { None };
5698 let matches_task = if let Some(query) = query {
5699 menu.do_async_filtering(query, cx)
5700 } else {
5701 Task::ready(menu.unfiltered_matches())
5702 };
5703 (menu, matches_task)
5704 }) else {
5705 return;
5706 };
5707
5708 let matches = matches_task.await;
5709
5710 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5711 // Newer menu already set, so exit.
5712 if let Some(CodeContextMenu::Completions(prev_menu)) =
5713 editor.context_menu.borrow().as_ref()
5714 && prev_menu.id > id
5715 {
5716 return;
5717 };
5718
5719 // Only valid to take prev_menu because it the new menu is immediately set
5720 // below, or the menu is hidden.
5721 if let Some(CodeContextMenu::Completions(prev_menu)) =
5722 editor.context_menu.borrow_mut().take()
5723 {
5724 let position_matches =
5725 if prev_menu.initial_position == menu.initial_position {
5726 true
5727 } else {
5728 let snapshot = editor.buffer.read(cx).read(cx);
5729 prev_menu.initial_position.to_offset(&snapshot)
5730 == menu.initial_position.to_offset(&snapshot)
5731 };
5732 if position_matches {
5733 // Preserve markdown cache before `set_filter_results` because it will
5734 // try to populate the documentation cache.
5735 menu.preserve_markdown_cache(prev_menu);
5736 }
5737 };
5738
5739 menu.set_filter_results(matches, provider, window, cx);
5740 }) else {
5741 return;
5742 };
5743
5744 menu.visible().then_some(menu)
5745 };
5746
5747 editor
5748 .update_in(cx, |editor, window, cx| {
5749 if editor.focus_handle.is_focused(window)
5750 && let Some(menu) = menu
5751 {
5752 *editor.context_menu.borrow_mut() =
5753 Some(CodeContextMenu::Completions(menu));
5754
5755 crate::hover_popover::hide_hover(editor, cx);
5756 if editor.show_edit_predictions_in_menu() {
5757 editor.update_visible_edit_prediction(window, cx);
5758 } else {
5759 editor.discard_edit_prediction(false, cx);
5760 }
5761
5762 cx.notify();
5763 return;
5764 }
5765
5766 if editor.completion_tasks.len() <= 1 {
5767 // If there are no more completion tasks and the last menu was empty, we should hide it.
5768 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5769 // If it was already hidden and we don't show edit predictions in the menu,
5770 // we should also show the edit prediction when available.
5771 if was_hidden && editor.show_edit_predictions_in_menu() {
5772 editor.update_visible_edit_prediction(window, cx);
5773 }
5774 }
5775 })
5776 .ok();
5777 });
5778
5779 self.completion_tasks.push((id, task));
5780 }
5781
5782 #[cfg(feature = "test-support")]
5783 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5784 let menu = self.context_menu.borrow();
5785 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5786 let completions = menu.completions.borrow();
5787 Some(completions.to_vec())
5788 } else {
5789 None
5790 }
5791 }
5792
5793 pub fn with_completions_menu_matching_id<R>(
5794 &self,
5795 id: CompletionId,
5796 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5797 ) -> R {
5798 let mut context_menu = self.context_menu.borrow_mut();
5799 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5800 return f(None);
5801 };
5802 if completions_menu.id != id {
5803 return f(None);
5804 }
5805 f(Some(completions_menu))
5806 }
5807
5808 pub fn confirm_completion(
5809 &mut self,
5810 action: &ConfirmCompletion,
5811 window: &mut Window,
5812 cx: &mut Context<Self>,
5813 ) -> Option<Task<Result<()>>> {
5814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5815 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5816 }
5817
5818 pub fn confirm_completion_insert(
5819 &mut self,
5820 _: &ConfirmCompletionInsert,
5821 window: &mut Window,
5822 cx: &mut Context<Self>,
5823 ) -> Option<Task<Result<()>>> {
5824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5825 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5826 }
5827
5828 pub fn confirm_completion_replace(
5829 &mut self,
5830 _: &ConfirmCompletionReplace,
5831 window: &mut Window,
5832 cx: &mut Context<Self>,
5833 ) -> Option<Task<Result<()>>> {
5834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5835 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5836 }
5837
5838 pub fn compose_completion(
5839 &mut self,
5840 action: &ComposeCompletion,
5841 window: &mut Window,
5842 cx: &mut Context<Self>,
5843 ) -> Option<Task<Result<()>>> {
5844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5845 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5846 }
5847
5848 fn do_completion(
5849 &mut self,
5850 item_ix: Option<usize>,
5851 intent: CompletionIntent,
5852 window: &mut Window,
5853 cx: &mut Context<Editor>,
5854 ) -> Option<Task<Result<()>>> {
5855 use language::ToOffset as _;
5856
5857 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5858 else {
5859 return None;
5860 };
5861
5862 let candidate_id = {
5863 let entries = completions_menu.entries.borrow();
5864 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5865 if self.show_edit_predictions_in_menu() {
5866 self.discard_edit_prediction(true, cx);
5867 }
5868 mat.candidate_id
5869 };
5870
5871 let completion = completions_menu
5872 .completions
5873 .borrow()
5874 .get(candidate_id)?
5875 .clone();
5876 cx.stop_propagation();
5877
5878 let buffer_handle = completions_menu.buffer.clone();
5879
5880 let CompletionEdit {
5881 new_text,
5882 snippet,
5883 replace_range,
5884 } = process_completion_for_edit(
5885 &completion,
5886 intent,
5887 &buffer_handle,
5888 &completions_menu.initial_position.text_anchor,
5889 cx,
5890 );
5891
5892 let buffer = buffer_handle.read(cx);
5893 let snapshot = self.buffer.read(cx).snapshot(cx);
5894 let newest_anchor = self.selections.newest_anchor();
5895 let replace_range_multibuffer = {
5896 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5897 let multibuffer_anchor = snapshot
5898 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5899 .unwrap()
5900 ..snapshot
5901 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5902 .unwrap();
5903 multibuffer_anchor.start.to_offset(&snapshot)
5904 ..multibuffer_anchor.end.to_offset(&snapshot)
5905 };
5906 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5907 return None;
5908 }
5909
5910 let old_text = buffer
5911 .text_for_range(replace_range.clone())
5912 .collect::<String>();
5913 let lookbehind = newest_anchor
5914 .start
5915 .text_anchor
5916 .to_offset(buffer)
5917 .saturating_sub(replace_range.start);
5918 let lookahead = replace_range
5919 .end
5920 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5921 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5922 let suffix = &old_text[lookbehind.min(old_text.len())..];
5923
5924 let selections = self.selections.all::<usize>(cx);
5925 let mut ranges = Vec::new();
5926 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5927
5928 for selection in &selections {
5929 let range = if selection.id == newest_anchor.id {
5930 replace_range_multibuffer.clone()
5931 } else {
5932 let mut range = selection.range();
5933
5934 // if prefix is present, don't duplicate it
5935 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5936 range.start = range.start.saturating_sub(lookbehind);
5937
5938 // if suffix is also present, mimic the newest cursor and replace it
5939 if selection.id != newest_anchor.id
5940 && snapshot.contains_str_at(range.end, suffix)
5941 {
5942 range.end += lookahead;
5943 }
5944 }
5945 range
5946 };
5947
5948 ranges.push(range.clone());
5949
5950 if !self.linked_edit_ranges.is_empty() {
5951 let start_anchor = snapshot.anchor_before(range.start);
5952 let end_anchor = snapshot.anchor_after(range.end);
5953 if let Some(ranges) = self
5954 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5955 {
5956 for (buffer, edits) in ranges {
5957 linked_edits
5958 .entry(buffer.clone())
5959 .or_default()
5960 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5961 }
5962 }
5963 }
5964 }
5965
5966 let common_prefix_len = old_text
5967 .chars()
5968 .zip(new_text.chars())
5969 .take_while(|(a, b)| a == b)
5970 .map(|(a, _)| a.len_utf8())
5971 .sum::<usize>();
5972
5973 cx.emit(EditorEvent::InputHandled {
5974 utf16_range_to_replace: None,
5975 text: new_text[common_prefix_len..].into(),
5976 });
5977
5978 self.transact(window, cx, |editor, window, cx| {
5979 if let Some(mut snippet) = snippet {
5980 snippet.text = new_text.to_string();
5981 editor
5982 .insert_snippet(&ranges, snippet, window, cx)
5983 .log_err();
5984 } else {
5985 editor.buffer.update(cx, |multi_buffer, cx| {
5986 let auto_indent = match completion.insert_text_mode {
5987 Some(InsertTextMode::AS_IS) => None,
5988 _ => editor.autoindent_mode.clone(),
5989 };
5990 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5991 multi_buffer.edit(edits, auto_indent, cx);
5992 });
5993 }
5994 for (buffer, edits) in linked_edits {
5995 buffer.update(cx, |buffer, cx| {
5996 let snapshot = buffer.snapshot();
5997 let edits = edits
5998 .into_iter()
5999 .map(|(range, text)| {
6000 use text::ToPoint as TP;
6001 let end_point = TP::to_point(&range.end, &snapshot);
6002 let start_point = TP::to_point(&range.start, &snapshot);
6003 (start_point..end_point, text)
6004 })
6005 .sorted_by_key(|(range, _)| range.start);
6006 buffer.edit(edits, None, cx);
6007 })
6008 }
6009
6010 editor.refresh_edit_prediction(true, false, window, cx);
6011 });
6012 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6013
6014 let show_new_completions_on_confirm = completion
6015 .confirm
6016 .as_ref()
6017 .is_some_and(|confirm| confirm(intent, window, cx));
6018 if show_new_completions_on_confirm {
6019 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6020 }
6021
6022 let provider = self.completion_provider.as_ref()?;
6023 drop(completion);
6024 let apply_edits = provider.apply_additional_edits_for_completion(
6025 buffer_handle,
6026 completions_menu.completions.clone(),
6027 candidate_id,
6028 true,
6029 cx,
6030 );
6031
6032 let editor_settings = EditorSettings::get_global(cx);
6033 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6034 // After the code completion is finished, users often want to know what signatures are needed.
6035 // so we should automatically call signature_help
6036 self.show_signature_help(&ShowSignatureHelp, window, cx);
6037 }
6038
6039 Some(cx.foreground_executor().spawn(async move {
6040 apply_edits.await?;
6041 Ok(())
6042 }))
6043 }
6044
6045 pub fn toggle_code_actions(
6046 &mut self,
6047 action: &ToggleCodeActions,
6048 window: &mut Window,
6049 cx: &mut Context<Self>,
6050 ) {
6051 let quick_launch = action.quick_launch;
6052 let mut context_menu = self.context_menu.borrow_mut();
6053 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6054 if code_actions.deployed_from == action.deployed_from {
6055 // Toggle if we're selecting the same one
6056 *context_menu = None;
6057 cx.notify();
6058 return;
6059 } else {
6060 // Otherwise, clear it and start a new one
6061 *context_menu = None;
6062 cx.notify();
6063 }
6064 }
6065 drop(context_menu);
6066 let snapshot = self.snapshot(window, cx);
6067 let deployed_from = action.deployed_from.clone();
6068 let action = action.clone();
6069 self.completion_tasks.clear();
6070 self.discard_edit_prediction(false, cx);
6071
6072 let multibuffer_point = match &action.deployed_from {
6073 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6074 DisplayPoint::new(*row, 0).to_point(&snapshot)
6075 }
6076 _ => self.selections.newest::<Point>(cx).head(),
6077 };
6078 let Some((buffer, buffer_row)) = snapshot
6079 .buffer_snapshot
6080 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6081 .and_then(|(buffer_snapshot, range)| {
6082 self.buffer()
6083 .read(cx)
6084 .buffer(buffer_snapshot.remote_id())
6085 .map(|buffer| (buffer, range.start.row))
6086 })
6087 else {
6088 return;
6089 };
6090 let buffer_id = buffer.read(cx).remote_id();
6091 let tasks = self
6092 .tasks
6093 .get(&(buffer_id, buffer_row))
6094 .map(|t| Arc::new(t.to_owned()));
6095
6096 if !self.focus_handle.is_focused(window) {
6097 return;
6098 }
6099 let project = self.project.clone();
6100
6101 let code_actions_task = match deployed_from {
6102 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6103 _ => self.code_actions(buffer_row, window, cx),
6104 };
6105
6106 let runnable_task = match deployed_from {
6107 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6108 _ => {
6109 let mut task_context_task = Task::ready(None);
6110 if let Some(tasks) = &tasks
6111 && let Some(project) = project
6112 {
6113 task_context_task =
6114 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6115 }
6116
6117 cx.spawn_in(window, {
6118 let buffer = buffer.clone();
6119 async move |editor, cx| {
6120 let task_context = task_context_task.await;
6121
6122 let resolved_tasks =
6123 tasks
6124 .zip(task_context.clone())
6125 .map(|(tasks, task_context)| ResolvedTasks {
6126 templates: tasks.resolve(&task_context).collect(),
6127 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6128 multibuffer_point.row,
6129 tasks.column,
6130 )),
6131 });
6132 let debug_scenarios = editor
6133 .update(cx, |editor, cx| {
6134 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6135 })?
6136 .await;
6137 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6138 }
6139 })
6140 }
6141 };
6142
6143 cx.spawn_in(window, async move |editor, cx| {
6144 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6145 let code_actions = code_actions_task.await;
6146 let spawn_straight_away = quick_launch
6147 && resolved_tasks
6148 .as_ref()
6149 .is_some_and(|tasks| tasks.templates.len() == 1)
6150 && code_actions
6151 .as_ref()
6152 .is_none_or(|actions| actions.is_empty())
6153 && debug_scenarios.is_empty();
6154
6155 editor.update_in(cx, |editor, window, cx| {
6156 crate::hover_popover::hide_hover(editor, cx);
6157 let actions = CodeActionContents::new(
6158 resolved_tasks,
6159 code_actions,
6160 debug_scenarios,
6161 task_context.unwrap_or_default(),
6162 );
6163
6164 // Don't show the menu if there are no actions available
6165 if actions.is_empty() {
6166 cx.notify();
6167 return Task::ready(Ok(()));
6168 }
6169
6170 *editor.context_menu.borrow_mut() =
6171 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6172 buffer,
6173 actions,
6174 selected_item: Default::default(),
6175 scroll_handle: UniformListScrollHandle::default(),
6176 deployed_from,
6177 }));
6178 cx.notify();
6179 if spawn_straight_away
6180 && let Some(task) = editor.confirm_code_action(
6181 &ConfirmCodeAction { item_ix: Some(0) },
6182 window,
6183 cx,
6184 )
6185 {
6186 return task;
6187 }
6188
6189 Task::ready(Ok(()))
6190 })
6191 })
6192 .detach_and_log_err(cx);
6193 }
6194
6195 fn debug_scenarios(
6196 &mut self,
6197 resolved_tasks: &Option<ResolvedTasks>,
6198 buffer: &Entity<Buffer>,
6199 cx: &mut App,
6200 ) -> Task<Vec<task::DebugScenario>> {
6201 maybe!({
6202 let project = self.project()?;
6203 let dap_store = project.read(cx).dap_store();
6204 let mut scenarios = vec![];
6205 let resolved_tasks = resolved_tasks.as_ref()?;
6206 let buffer = buffer.read(cx);
6207 let language = buffer.language()?;
6208 let file = buffer.file();
6209 let debug_adapter = language_settings(language.name().into(), file, cx)
6210 .debuggers
6211 .first()
6212 .map(SharedString::from)
6213 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6214
6215 dap_store.update(cx, |dap_store, cx| {
6216 for (_, task) in &resolved_tasks.templates {
6217 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6218 task.original_task().clone(),
6219 debug_adapter.clone().into(),
6220 task.display_label().to_owned().into(),
6221 cx,
6222 );
6223 scenarios.push(maybe_scenario);
6224 }
6225 });
6226 Some(cx.background_spawn(async move {
6227 futures::future::join_all(scenarios)
6228 .await
6229 .into_iter()
6230 .flatten()
6231 .collect::<Vec<_>>()
6232 }))
6233 })
6234 .unwrap_or_else(|| Task::ready(vec![]))
6235 }
6236
6237 fn code_actions(
6238 &mut self,
6239 buffer_row: u32,
6240 window: &mut Window,
6241 cx: &mut Context<Self>,
6242 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6243 let mut task = self.code_actions_task.take();
6244 cx.spawn_in(window, async move |editor, cx| {
6245 while let Some(prev_task) = task {
6246 prev_task.await.log_err();
6247 task = editor
6248 .update(cx, |this, _| this.code_actions_task.take())
6249 .ok()?;
6250 }
6251
6252 editor
6253 .update(cx, |editor, cx| {
6254 editor
6255 .available_code_actions
6256 .clone()
6257 .and_then(|(location, code_actions)| {
6258 let snapshot = location.buffer.read(cx).snapshot();
6259 let point_range = location.range.to_point(&snapshot);
6260 let point_range = point_range.start.row..=point_range.end.row;
6261 if point_range.contains(&buffer_row) {
6262 Some(code_actions)
6263 } else {
6264 None
6265 }
6266 })
6267 })
6268 .ok()
6269 .flatten()
6270 })
6271 }
6272
6273 pub fn confirm_code_action(
6274 &mut self,
6275 action: &ConfirmCodeAction,
6276 window: &mut Window,
6277 cx: &mut Context<Self>,
6278 ) -> Option<Task<Result<()>>> {
6279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6280
6281 let actions_menu =
6282 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6283 menu
6284 } else {
6285 return None;
6286 };
6287
6288 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6289 let action = actions_menu.actions.get(action_ix)?;
6290 let title = action.label();
6291 let buffer = actions_menu.buffer;
6292 let workspace = self.workspace()?;
6293
6294 match action {
6295 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6296 workspace.update(cx, |workspace, cx| {
6297 workspace.schedule_resolved_task(
6298 task_source_kind,
6299 resolved_task,
6300 false,
6301 window,
6302 cx,
6303 );
6304
6305 Some(Task::ready(Ok(())))
6306 })
6307 }
6308 CodeActionsItem::CodeAction {
6309 excerpt_id,
6310 action,
6311 provider,
6312 } => {
6313 let apply_code_action =
6314 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6315 let workspace = workspace.downgrade();
6316 Some(cx.spawn_in(window, async move |editor, cx| {
6317 let project_transaction = apply_code_action.await?;
6318 Self::open_project_transaction(
6319 &editor,
6320 workspace,
6321 project_transaction,
6322 title,
6323 cx,
6324 )
6325 .await
6326 }))
6327 }
6328 CodeActionsItem::DebugScenario(scenario) => {
6329 let context = actions_menu.actions.context;
6330
6331 workspace.update(cx, |workspace, cx| {
6332 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6333 workspace.start_debug_session(
6334 scenario,
6335 context,
6336 Some(buffer),
6337 None,
6338 window,
6339 cx,
6340 );
6341 });
6342 Some(Task::ready(Ok(())))
6343 }
6344 }
6345 }
6346
6347 pub async fn open_project_transaction(
6348 editor: &WeakEntity<Editor>,
6349 workspace: WeakEntity<Workspace>,
6350 transaction: ProjectTransaction,
6351 title: String,
6352 cx: &mut AsyncWindowContext,
6353 ) -> Result<()> {
6354 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6355 cx.update(|_, cx| {
6356 entries.sort_unstable_by_key(|(buffer, _)| {
6357 buffer.read(cx).file().map(|f| f.path().clone())
6358 });
6359 })?;
6360
6361 // If the project transaction's edits are all contained within this editor, then
6362 // avoid opening a new editor to display them.
6363
6364 if let Some((buffer, transaction)) = entries.first() {
6365 if entries.len() == 1 {
6366 let excerpt = editor.update(cx, |editor, cx| {
6367 editor
6368 .buffer()
6369 .read(cx)
6370 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6371 })?;
6372 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6373 && excerpted_buffer == *buffer
6374 {
6375 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6376 let excerpt_range = excerpt_range.to_offset(buffer);
6377 buffer
6378 .edited_ranges_for_transaction::<usize>(transaction)
6379 .all(|range| {
6380 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6381 })
6382 })?;
6383
6384 if all_edits_within_excerpt {
6385 return Ok(());
6386 }
6387 }
6388 }
6389 } else {
6390 return Ok(());
6391 }
6392
6393 let mut ranges_to_highlight = Vec::new();
6394 let excerpt_buffer = cx.new(|cx| {
6395 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6396 for (buffer_handle, transaction) in &entries {
6397 let edited_ranges = buffer_handle
6398 .read(cx)
6399 .edited_ranges_for_transaction::<Point>(transaction)
6400 .collect::<Vec<_>>();
6401 let (ranges, _) = multibuffer.set_excerpts_for_path(
6402 PathKey::for_buffer(buffer_handle, cx),
6403 buffer_handle.clone(),
6404 edited_ranges,
6405 DEFAULT_MULTIBUFFER_CONTEXT,
6406 cx,
6407 );
6408
6409 ranges_to_highlight.extend(ranges);
6410 }
6411 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6412 multibuffer
6413 })?;
6414
6415 workspace.update_in(cx, |workspace, window, cx| {
6416 let project = workspace.project().clone();
6417 let editor =
6418 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6419 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6420 editor.update(cx, |editor, cx| {
6421 editor.highlight_background::<Self>(
6422 &ranges_to_highlight,
6423 |theme| theme.colors().editor_highlighted_line_background,
6424 cx,
6425 );
6426 });
6427 })?;
6428
6429 Ok(())
6430 }
6431
6432 pub fn clear_code_action_providers(&mut self) {
6433 self.code_action_providers.clear();
6434 self.available_code_actions.take();
6435 }
6436
6437 pub fn add_code_action_provider(
6438 &mut self,
6439 provider: Rc<dyn CodeActionProvider>,
6440 window: &mut Window,
6441 cx: &mut Context<Self>,
6442 ) {
6443 if self
6444 .code_action_providers
6445 .iter()
6446 .any(|existing_provider| existing_provider.id() == provider.id())
6447 {
6448 return;
6449 }
6450
6451 self.code_action_providers.push(provider);
6452 self.refresh_code_actions(window, cx);
6453 }
6454
6455 pub fn remove_code_action_provider(
6456 &mut self,
6457 id: Arc<str>,
6458 window: &mut Window,
6459 cx: &mut Context<Self>,
6460 ) {
6461 self.code_action_providers
6462 .retain(|provider| provider.id() != id);
6463 self.refresh_code_actions(window, cx);
6464 }
6465
6466 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6467 !self.code_action_providers.is_empty()
6468 && EditorSettings::get_global(cx).toolbar.code_actions
6469 }
6470
6471 pub fn has_available_code_actions(&self) -> bool {
6472 self.available_code_actions
6473 .as_ref()
6474 .is_some_and(|(_, actions)| !actions.is_empty())
6475 }
6476
6477 fn render_inline_code_actions(
6478 &self,
6479 icon_size: ui::IconSize,
6480 display_row: DisplayRow,
6481 is_active: bool,
6482 cx: &mut Context<Self>,
6483 ) -> AnyElement {
6484 let show_tooltip = !self.context_menu_visible();
6485 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6486 .icon_size(icon_size)
6487 .shape(ui::IconButtonShape::Square)
6488 .icon_color(ui::Color::Hidden)
6489 .toggle_state(is_active)
6490 .when(show_tooltip, |this| {
6491 this.tooltip({
6492 let focus_handle = self.focus_handle.clone();
6493 move |window, cx| {
6494 Tooltip::for_action_in(
6495 "Toggle Code Actions",
6496 &ToggleCodeActions {
6497 deployed_from: None,
6498 quick_launch: false,
6499 },
6500 &focus_handle,
6501 window,
6502 cx,
6503 )
6504 }
6505 })
6506 })
6507 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6508 window.focus(&editor.focus_handle(cx));
6509 editor.toggle_code_actions(
6510 &crate::actions::ToggleCodeActions {
6511 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6512 display_row,
6513 )),
6514 quick_launch: false,
6515 },
6516 window,
6517 cx,
6518 );
6519 }))
6520 .into_any_element()
6521 }
6522
6523 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6524 &self.context_menu
6525 }
6526
6527 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6528 let newest_selection = self.selections.newest_anchor().clone();
6529 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6530 let buffer = self.buffer.read(cx);
6531 if newest_selection.head().diff_base_anchor.is_some() {
6532 return None;
6533 }
6534 let (start_buffer, start) =
6535 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6536 let (end_buffer, end) =
6537 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6538 if start_buffer != end_buffer {
6539 return None;
6540 }
6541
6542 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6543 cx.background_executor()
6544 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6545 .await;
6546
6547 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6548 let providers = this.code_action_providers.clone();
6549 let tasks = this
6550 .code_action_providers
6551 .iter()
6552 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6553 .collect::<Vec<_>>();
6554 (providers, tasks)
6555 })?;
6556
6557 let mut actions = Vec::new();
6558 for (provider, provider_actions) in
6559 providers.into_iter().zip(future::join_all(tasks).await)
6560 {
6561 if let Some(provider_actions) = provider_actions.log_err() {
6562 actions.extend(provider_actions.into_iter().map(|action| {
6563 AvailableCodeAction {
6564 excerpt_id: newest_selection.start.excerpt_id,
6565 action,
6566 provider: provider.clone(),
6567 }
6568 }));
6569 }
6570 }
6571
6572 this.update(cx, |this, cx| {
6573 this.available_code_actions = if actions.is_empty() {
6574 None
6575 } else {
6576 Some((
6577 Location {
6578 buffer: start_buffer,
6579 range: start..end,
6580 },
6581 actions.into(),
6582 ))
6583 };
6584 cx.notify();
6585 })
6586 }));
6587 None
6588 }
6589
6590 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6591 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6592 self.show_git_blame_inline = false;
6593
6594 self.show_git_blame_inline_delay_task =
6595 Some(cx.spawn_in(window, async move |this, cx| {
6596 cx.background_executor().timer(delay).await;
6597
6598 this.update(cx, |this, cx| {
6599 this.show_git_blame_inline = true;
6600 cx.notify();
6601 })
6602 .log_err();
6603 }));
6604 }
6605 }
6606
6607 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6608 let snapshot = self.snapshot(window, cx);
6609 let cursor = self.selections.newest::<Point>(cx).head();
6610 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6611 else {
6612 return;
6613 };
6614
6615 let Some(blame) = self.blame.as_ref() else {
6616 return;
6617 };
6618
6619 let row_info = RowInfo {
6620 buffer_id: Some(buffer.remote_id()),
6621 buffer_row: Some(point.row),
6622 ..Default::default()
6623 };
6624 let Some(blame_entry) = blame
6625 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6626 .flatten()
6627 else {
6628 return;
6629 };
6630
6631 let anchor = self.selections.newest_anchor().head();
6632 let position = self.to_pixel_point(anchor, &snapshot, window);
6633 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6634 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6635 };
6636 }
6637
6638 fn show_blame_popover(
6639 &mut self,
6640 blame_entry: &BlameEntry,
6641 position: gpui::Point<Pixels>,
6642 ignore_timeout: bool,
6643 cx: &mut Context<Self>,
6644 ) {
6645 if let Some(state) = &mut self.inline_blame_popover {
6646 state.hide_task.take();
6647 } else {
6648 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6649 let blame_entry = blame_entry.clone();
6650 let show_task = cx.spawn(async move |editor, cx| {
6651 if !ignore_timeout {
6652 cx.background_executor()
6653 .timer(std::time::Duration::from_millis(blame_popover_delay))
6654 .await;
6655 }
6656 editor
6657 .update(cx, |editor, cx| {
6658 editor.inline_blame_popover_show_task.take();
6659 let Some(blame) = editor.blame.as_ref() else {
6660 return;
6661 };
6662 let blame = blame.read(cx);
6663 let details = blame.details_for_entry(&blame_entry);
6664 let markdown = cx.new(|cx| {
6665 Markdown::new(
6666 details
6667 .as_ref()
6668 .map(|message| message.message.clone())
6669 .unwrap_or_default(),
6670 None,
6671 None,
6672 cx,
6673 )
6674 });
6675 editor.inline_blame_popover = Some(InlineBlamePopover {
6676 position,
6677 hide_task: None,
6678 popover_bounds: None,
6679 popover_state: InlineBlamePopoverState {
6680 scroll_handle: ScrollHandle::new(),
6681 commit_message: details,
6682 markdown,
6683 },
6684 keyboard_grace: ignore_timeout,
6685 });
6686 cx.notify();
6687 })
6688 .ok();
6689 });
6690 self.inline_blame_popover_show_task = Some(show_task);
6691 }
6692 }
6693
6694 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6695 self.inline_blame_popover_show_task.take();
6696 if let Some(state) = &mut self.inline_blame_popover {
6697 let hide_task = cx.spawn(async move |editor, cx| {
6698 cx.background_executor()
6699 .timer(std::time::Duration::from_millis(100))
6700 .await;
6701 editor
6702 .update(cx, |editor, cx| {
6703 editor.inline_blame_popover.take();
6704 cx.notify();
6705 })
6706 .ok();
6707 });
6708 state.hide_task = Some(hide_task);
6709 }
6710 }
6711
6712 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6713 if self.pending_rename.is_some() {
6714 return None;
6715 }
6716
6717 let provider = self.semantics_provider.clone()?;
6718 let buffer = self.buffer.read(cx);
6719 let newest_selection = self.selections.newest_anchor().clone();
6720 let cursor_position = newest_selection.head();
6721 let (cursor_buffer, cursor_buffer_position) =
6722 buffer.text_anchor_for_position(cursor_position, cx)?;
6723 let (tail_buffer, tail_buffer_position) =
6724 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6725 if cursor_buffer != tail_buffer {
6726 return None;
6727 }
6728
6729 let snapshot = cursor_buffer.read(cx).snapshot();
6730 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6731 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6732 if start_word_range != end_word_range {
6733 self.document_highlights_task.take();
6734 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6735 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6736 return None;
6737 }
6738
6739 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6740 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6741 cx.background_executor()
6742 .timer(Duration::from_millis(debounce))
6743 .await;
6744
6745 let highlights = if let Some(highlights) = cx
6746 .update(|cx| {
6747 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6748 })
6749 .ok()
6750 .flatten()
6751 {
6752 highlights.await.log_err()
6753 } else {
6754 None
6755 };
6756
6757 if let Some(highlights) = highlights {
6758 this.update(cx, |this, cx| {
6759 if this.pending_rename.is_some() {
6760 return;
6761 }
6762
6763 let buffer = this.buffer.read(cx);
6764 if buffer
6765 .text_anchor_for_position(cursor_position, cx)
6766 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6767 {
6768 return;
6769 }
6770
6771 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6772 let mut write_ranges = Vec::new();
6773 let mut read_ranges = Vec::new();
6774 for highlight in highlights {
6775 let buffer_id = cursor_buffer.read(cx).remote_id();
6776 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6777 {
6778 let start = highlight
6779 .range
6780 .start
6781 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6782 let end = highlight
6783 .range
6784 .end
6785 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6786 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6787 continue;
6788 }
6789
6790 let range = Anchor {
6791 buffer_id: Some(buffer_id),
6792 excerpt_id,
6793 text_anchor: start,
6794 diff_base_anchor: None,
6795 }..Anchor {
6796 buffer_id: Some(buffer_id),
6797 excerpt_id,
6798 text_anchor: end,
6799 diff_base_anchor: None,
6800 };
6801 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6802 write_ranges.push(range);
6803 } else {
6804 read_ranges.push(range);
6805 }
6806 }
6807 }
6808
6809 this.highlight_background::<DocumentHighlightRead>(
6810 &read_ranges,
6811 |theme| theme.colors().editor_document_highlight_read_background,
6812 cx,
6813 );
6814 this.highlight_background::<DocumentHighlightWrite>(
6815 &write_ranges,
6816 |theme| theme.colors().editor_document_highlight_write_background,
6817 cx,
6818 );
6819 cx.notify();
6820 })
6821 .log_err();
6822 }
6823 }));
6824 None
6825 }
6826
6827 fn prepare_highlight_query_from_selection(
6828 &mut self,
6829 cx: &mut Context<Editor>,
6830 ) -> Option<(String, Range<Anchor>)> {
6831 if matches!(self.mode, EditorMode::SingleLine) {
6832 return None;
6833 }
6834 if !EditorSettings::get_global(cx).selection_highlight {
6835 return None;
6836 }
6837 if self.selections.count() != 1 || self.selections.line_mode {
6838 return None;
6839 }
6840 let selection = self.selections.newest::<Point>(cx);
6841 if selection.is_empty() || selection.start.row != selection.end.row {
6842 return None;
6843 }
6844 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6845 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6846 let query = multi_buffer_snapshot
6847 .text_for_range(selection_anchor_range.clone())
6848 .collect::<String>();
6849 if query.trim().is_empty() {
6850 return None;
6851 }
6852 Some((query, selection_anchor_range))
6853 }
6854
6855 fn update_selection_occurrence_highlights(
6856 &mut self,
6857 query_text: String,
6858 query_range: Range<Anchor>,
6859 multi_buffer_range_to_query: Range<Point>,
6860 use_debounce: bool,
6861 window: &mut Window,
6862 cx: &mut Context<Editor>,
6863 ) -> Task<()> {
6864 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6865 cx.spawn_in(window, async move |editor, cx| {
6866 if use_debounce {
6867 cx.background_executor()
6868 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6869 .await;
6870 }
6871 let match_task = cx.background_spawn(async move {
6872 let buffer_ranges = multi_buffer_snapshot
6873 .range_to_buffer_ranges(multi_buffer_range_to_query)
6874 .into_iter()
6875 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6876 let mut match_ranges = Vec::new();
6877 let Ok(regex) = project::search::SearchQuery::text(
6878 query_text.clone(),
6879 false,
6880 false,
6881 false,
6882 Default::default(),
6883 Default::default(),
6884 false,
6885 None,
6886 ) else {
6887 return Vec::default();
6888 };
6889 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6890 match_ranges.extend(
6891 regex
6892 .search(buffer_snapshot, Some(search_range.clone()))
6893 .await
6894 .into_iter()
6895 .filter_map(|match_range| {
6896 let match_start = buffer_snapshot
6897 .anchor_after(search_range.start + match_range.start);
6898 let match_end = buffer_snapshot
6899 .anchor_before(search_range.start + match_range.end);
6900 let match_anchor_range = Anchor::range_in_buffer(
6901 excerpt_id,
6902 buffer_snapshot.remote_id(),
6903 match_start..match_end,
6904 );
6905 (match_anchor_range != query_range).then_some(match_anchor_range)
6906 }),
6907 );
6908 }
6909 match_ranges
6910 });
6911 let match_ranges = match_task.await;
6912 editor
6913 .update_in(cx, |editor, _, cx| {
6914 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6915 if !match_ranges.is_empty() {
6916 editor.highlight_background::<SelectedTextHighlight>(
6917 &match_ranges,
6918 |theme| theme.colors().editor_document_highlight_bracket_background,
6919 cx,
6920 )
6921 }
6922 })
6923 .log_err();
6924 })
6925 }
6926
6927 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6928 struct NewlineFold;
6929 let type_id = std::any::TypeId::of::<NewlineFold>();
6930 if !self.mode.is_single_line() {
6931 return;
6932 }
6933 let snapshot = self.snapshot(window, cx);
6934 if snapshot.buffer_snapshot.max_point().row == 0 {
6935 return;
6936 }
6937 let task = cx.background_spawn(async move {
6938 let new_newlines = snapshot
6939 .buffer_chars_at(0)
6940 .filter_map(|(c, i)| {
6941 if c == '\n' {
6942 Some(
6943 snapshot.buffer_snapshot.anchor_after(i)
6944 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6945 )
6946 } else {
6947 None
6948 }
6949 })
6950 .collect::<Vec<_>>();
6951 let existing_newlines = snapshot
6952 .folds_in_range(0..snapshot.buffer_snapshot.len())
6953 .filter_map(|fold| {
6954 if fold.placeholder.type_tag == Some(type_id) {
6955 Some(fold.range.start..fold.range.end)
6956 } else {
6957 None
6958 }
6959 })
6960 .collect::<Vec<_>>();
6961
6962 (new_newlines, existing_newlines)
6963 });
6964 self.folding_newlines = cx.spawn(async move |this, cx| {
6965 let (new_newlines, existing_newlines) = task.await;
6966 if new_newlines == existing_newlines {
6967 return;
6968 }
6969 let placeholder = FoldPlaceholder {
6970 render: Arc::new(move |_, _, cx| {
6971 div()
6972 .bg(cx.theme().status().hint_background)
6973 .border_b_1()
6974 .size_full()
6975 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6976 .border_color(cx.theme().status().hint)
6977 .child("\\n")
6978 .into_any()
6979 }),
6980 constrain_width: false,
6981 merge_adjacent: false,
6982 type_tag: Some(type_id),
6983 };
6984 let creases = new_newlines
6985 .into_iter()
6986 .map(|range| Crease::simple(range, placeholder.clone()))
6987 .collect();
6988 this.update(cx, |this, cx| {
6989 this.display_map.update(cx, |display_map, cx| {
6990 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6991 display_map.fold(creases, cx);
6992 });
6993 })
6994 .ok();
6995 });
6996 }
6997
6998 fn refresh_selected_text_highlights(
6999 &mut self,
7000 on_buffer_edit: bool,
7001 window: &mut Window,
7002 cx: &mut Context<Editor>,
7003 ) {
7004 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7005 else {
7006 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7007 self.quick_selection_highlight_task.take();
7008 self.debounced_selection_highlight_task.take();
7009 return;
7010 };
7011 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7012 if on_buffer_edit
7013 || self
7014 .quick_selection_highlight_task
7015 .as_ref()
7016 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7017 {
7018 let multi_buffer_visible_start = self
7019 .scroll_manager
7020 .anchor()
7021 .anchor
7022 .to_point(&multi_buffer_snapshot);
7023 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7024 multi_buffer_visible_start
7025 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7026 Bias::Left,
7027 );
7028 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7029 self.quick_selection_highlight_task = Some((
7030 query_range.clone(),
7031 self.update_selection_occurrence_highlights(
7032 query_text.clone(),
7033 query_range.clone(),
7034 multi_buffer_visible_range,
7035 false,
7036 window,
7037 cx,
7038 ),
7039 ));
7040 }
7041 if on_buffer_edit
7042 || self
7043 .debounced_selection_highlight_task
7044 .as_ref()
7045 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7046 {
7047 let multi_buffer_start = multi_buffer_snapshot
7048 .anchor_before(0)
7049 .to_point(&multi_buffer_snapshot);
7050 let multi_buffer_end = multi_buffer_snapshot
7051 .anchor_after(multi_buffer_snapshot.len())
7052 .to_point(&multi_buffer_snapshot);
7053 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7054 self.debounced_selection_highlight_task = Some((
7055 query_range.clone(),
7056 self.update_selection_occurrence_highlights(
7057 query_text,
7058 query_range,
7059 multi_buffer_full_range,
7060 true,
7061 window,
7062 cx,
7063 ),
7064 ));
7065 }
7066 }
7067
7068 pub fn refresh_edit_prediction(
7069 &mut self,
7070 debounce: bool,
7071 user_requested: bool,
7072 window: &mut Window,
7073 cx: &mut Context<Self>,
7074 ) -> Option<()> {
7075 if DisableAiSettings::get_global(cx).disable_ai {
7076 return None;
7077 }
7078
7079 let provider = self.edit_prediction_provider()?;
7080 let cursor = self.selections.newest_anchor().head();
7081 let (buffer, cursor_buffer_position) =
7082 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7083
7084 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7085 self.discard_edit_prediction(false, cx);
7086 return None;
7087 }
7088
7089 if !user_requested
7090 && (!self.should_show_edit_predictions()
7091 || !self.is_focused(window)
7092 || buffer.read(cx).is_empty())
7093 {
7094 self.discard_edit_prediction(false, cx);
7095 return None;
7096 }
7097
7098 self.update_visible_edit_prediction(window, cx);
7099 provider.refresh(
7100 self.project.clone(),
7101 buffer,
7102 cursor_buffer_position,
7103 debounce,
7104 cx,
7105 );
7106 Some(())
7107 }
7108
7109 fn show_edit_predictions_in_menu(&self) -> bool {
7110 match self.edit_prediction_settings {
7111 EditPredictionSettings::Disabled => false,
7112 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7113 }
7114 }
7115
7116 pub fn edit_predictions_enabled(&self) -> bool {
7117 match self.edit_prediction_settings {
7118 EditPredictionSettings::Disabled => false,
7119 EditPredictionSettings::Enabled { .. } => true,
7120 }
7121 }
7122
7123 fn edit_prediction_requires_modifier(&self) -> bool {
7124 match self.edit_prediction_settings {
7125 EditPredictionSettings::Disabled => false,
7126 EditPredictionSettings::Enabled {
7127 preview_requires_modifier,
7128 ..
7129 } => preview_requires_modifier,
7130 }
7131 }
7132
7133 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7134 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7135 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7136 self.discard_edit_prediction(false, cx);
7137 } else {
7138 let selection = self.selections.newest_anchor();
7139 let cursor = selection.head();
7140
7141 if let Some((buffer, cursor_buffer_position)) =
7142 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7143 {
7144 self.edit_prediction_settings =
7145 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7146 }
7147 }
7148 }
7149
7150 fn edit_prediction_settings_at_position(
7151 &self,
7152 buffer: &Entity<Buffer>,
7153 buffer_position: language::Anchor,
7154 cx: &App,
7155 ) -> EditPredictionSettings {
7156 if !self.mode.is_full()
7157 || !self.show_edit_predictions_override.unwrap_or(true)
7158 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7159 {
7160 return EditPredictionSettings::Disabled;
7161 }
7162
7163 let buffer = buffer.read(cx);
7164
7165 let file = buffer.file();
7166
7167 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7168 return EditPredictionSettings::Disabled;
7169 };
7170
7171 let by_provider = matches!(
7172 self.menu_edit_predictions_policy,
7173 MenuEditPredictionsPolicy::ByProvider
7174 );
7175
7176 let show_in_menu = by_provider
7177 && self
7178 .edit_prediction_provider
7179 .as_ref()
7180 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7181
7182 let preview_requires_modifier =
7183 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7184
7185 EditPredictionSettings::Enabled {
7186 show_in_menu,
7187 preview_requires_modifier,
7188 }
7189 }
7190
7191 fn should_show_edit_predictions(&self) -> bool {
7192 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7193 }
7194
7195 pub fn edit_prediction_preview_is_active(&self) -> bool {
7196 matches!(
7197 self.edit_prediction_preview,
7198 EditPredictionPreview::Active { .. }
7199 )
7200 }
7201
7202 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7203 let cursor = self.selections.newest_anchor().head();
7204 if let Some((buffer, cursor_position)) =
7205 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7206 {
7207 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7208 } else {
7209 false
7210 }
7211 }
7212
7213 pub fn supports_minimap(&self, cx: &App) -> bool {
7214 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7215 }
7216
7217 fn edit_predictions_enabled_in_buffer(
7218 &self,
7219 buffer: &Entity<Buffer>,
7220 buffer_position: language::Anchor,
7221 cx: &App,
7222 ) -> bool {
7223 maybe!({
7224 if self.read_only(cx) {
7225 return Some(false);
7226 }
7227 let provider = self.edit_prediction_provider()?;
7228 if !provider.is_enabled(buffer, buffer_position, cx) {
7229 return Some(false);
7230 }
7231 let buffer = buffer.read(cx);
7232 let Some(file) = buffer.file() else {
7233 return Some(true);
7234 };
7235 let settings = all_language_settings(Some(file), cx);
7236 Some(settings.edit_predictions_enabled_for_file(file, cx))
7237 })
7238 .unwrap_or(false)
7239 }
7240
7241 fn cycle_edit_prediction(
7242 &mut self,
7243 direction: Direction,
7244 window: &mut Window,
7245 cx: &mut Context<Self>,
7246 ) -> Option<()> {
7247 let provider = self.edit_prediction_provider()?;
7248 let cursor = self.selections.newest_anchor().head();
7249 let (buffer, cursor_buffer_position) =
7250 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7251 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7252 return None;
7253 }
7254
7255 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7256 self.update_visible_edit_prediction(window, cx);
7257
7258 Some(())
7259 }
7260
7261 pub fn show_edit_prediction(
7262 &mut self,
7263 _: &ShowEditPrediction,
7264 window: &mut Window,
7265 cx: &mut Context<Self>,
7266 ) {
7267 if !self.has_active_edit_prediction() {
7268 self.refresh_edit_prediction(false, true, window, cx);
7269 return;
7270 }
7271
7272 self.update_visible_edit_prediction(window, cx);
7273 }
7274
7275 pub fn display_cursor_names(
7276 &mut self,
7277 _: &DisplayCursorNames,
7278 window: &mut Window,
7279 cx: &mut Context<Self>,
7280 ) {
7281 self.show_cursor_names(window, cx);
7282 }
7283
7284 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7285 self.show_cursor_names = true;
7286 cx.notify();
7287 cx.spawn_in(window, async move |this, cx| {
7288 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7289 this.update(cx, |this, cx| {
7290 this.show_cursor_names = false;
7291 cx.notify()
7292 })
7293 .ok()
7294 })
7295 .detach();
7296 }
7297
7298 pub fn next_edit_prediction(
7299 &mut self,
7300 _: &NextEditPrediction,
7301 window: &mut Window,
7302 cx: &mut Context<Self>,
7303 ) {
7304 if self.has_active_edit_prediction() {
7305 self.cycle_edit_prediction(Direction::Next, window, cx);
7306 } else {
7307 let is_copilot_disabled = self
7308 .refresh_edit_prediction(false, true, window, cx)
7309 .is_none();
7310 if is_copilot_disabled {
7311 cx.propagate();
7312 }
7313 }
7314 }
7315
7316 pub fn previous_edit_prediction(
7317 &mut self,
7318 _: &PreviousEditPrediction,
7319 window: &mut Window,
7320 cx: &mut Context<Self>,
7321 ) {
7322 if self.has_active_edit_prediction() {
7323 self.cycle_edit_prediction(Direction::Prev, window, cx);
7324 } else {
7325 let is_copilot_disabled = self
7326 .refresh_edit_prediction(false, true, window, cx)
7327 .is_none();
7328 if is_copilot_disabled {
7329 cx.propagate();
7330 }
7331 }
7332 }
7333
7334 pub fn accept_edit_prediction(
7335 &mut self,
7336 _: &AcceptEditPrediction,
7337 window: &mut Window,
7338 cx: &mut Context<Self>,
7339 ) {
7340 if self.show_edit_predictions_in_menu() {
7341 self.hide_context_menu(window, cx);
7342 }
7343
7344 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7345 return;
7346 };
7347
7348 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7349
7350 match &active_edit_prediction.completion {
7351 EditPrediction::Move { target, .. } => {
7352 let target = *target;
7353
7354 if let Some(position_map) = &self.last_position_map {
7355 if position_map
7356 .visible_row_range
7357 .contains(&target.to_display_point(&position_map.snapshot).row())
7358 || !self.edit_prediction_requires_modifier()
7359 {
7360 self.unfold_ranges(&[target..target], true, false, cx);
7361 // Note that this is also done in vim's handler of the Tab action.
7362 self.change_selections(
7363 SelectionEffects::scroll(Autoscroll::newest()),
7364 window,
7365 cx,
7366 |selections| {
7367 selections.select_anchor_ranges([target..target]);
7368 },
7369 );
7370 self.clear_row_highlights::<EditPredictionPreview>();
7371
7372 self.edit_prediction_preview
7373 .set_previous_scroll_position(None);
7374 } else {
7375 self.edit_prediction_preview
7376 .set_previous_scroll_position(Some(
7377 position_map.snapshot.scroll_anchor,
7378 ));
7379
7380 self.highlight_rows::<EditPredictionPreview>(
7381 target..target,
7382 cx.theme().colors().editor_highlighted_line_background,
7383 RowHighlightOptions {
7384 autoscroll: true,
7385 ..Default::default()
7386 },
7387 cx,
7388 );
7389 self.request_autoscroll(Autoscroll::fit(), cx);
7390 }
7391 }
7392 }
7393 EditPrediction::Edit { edits, .. } => {
7394 if let Some(provider) = self.edit_prediction_provider() {
7395 provider.accept(cx);
7396 }
7397
7398 // Store the transaction ID and selections before applying the edit
7399 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7400
7401 let snapshot = self.buffer.read(cx).snapshot(cx);
7402 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7403
7404 self.buffer.update(cx, |buffer, cx| {
7405 buffer.edit(edits.iter().cloned(), None, cx)
7406 });
7407
7408 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7409 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7410 });
7411
7412 let selections = self.selections.disjoint_anchors();
7413 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7414 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7415 if has_new_transaction {
7416 self.selection_history
7417 .insert_transaction(transaction_id_now, selections);
7418 }
7419 }
7420
7421 self.update_visible_edit_prediction(window, cx);
7422 if self.active_edit_prediction.is_none() {
7423 self.refresh_edit_prediction(true, true, window, cx);
7424 }
7425
7426 cx.notify();
7427 }
7428 }
7429
7430 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7431 }
7432
7433 pub fn accept_partial_edit_prediction(
7434 &mut self,
7435 _: &AcceptPartialEditPrediction,
7436 window: &mut Window,
7437 cx: &mut Context<Self>,
7438 ) {
7439 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7440 return;
7441 };
7442 if self.selections.count() != 1 {
7443 return;
7444 }
7445
7446 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7447
7448 match &active_edit_prediction.completion {
7449 EditPrediction::Move { target, .. } => {
7450 let target = *target;
7451 self.change_selections(
7452 SelectionEffects::scroll(Autoscroll::newest()),
7453 window,
7454 cx,
7455 |selections| {
7456 selections.select_anchor_ranges([target..target]);
7457 },
7458 );
7459 }
7460 EditPrediction::Edit { edits, .. } => {
7461 // Find an insertion that starts at the cursor position.
7462 let snapshot = self.buffer.read(cx).snapshot(cx);
7463 let cursor_offset = self.selections.newest::<usize>(cx).head();
7464 let insertion = edits.iter().find_map(|(range, text)| {
7465 let range = range.to_offset(&snapshot);
7466 if range.is_empty() && range.start == cursor_offset {
7467 Some(text)
7468 } else {
7469 None
7470 }
7471 });
7472
7473 if let Some(text) = insertion {
7474 let mut partial_completion = text
7475 .chars()
7476 .by_ref()
7477 .take_while(|c| c.is_alphabetic())
7478 .collect::<String>();
7479 if partial_completion.is_empty() {
7480 partial_completion = text
7481 .chars()
7482 .by_ref()
7483 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7484 .collect::<String>();
7485 }
7486
7487 cx.emit(EditorEvent::InputHandled {
7488 utf16_range_to_replace: None,
7489 text: partial_completion.clone().into(),
7490 });
7491
7492 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7493
7494 self.refresh_edit_prediction(true, true, window, cx);
7495 cx.notify();
7496 } else {
7497 self.accept_edit_prediction(&Default::default(), window, cx);
7498 }
7499 }
7500 }
7501 }
7502
7503 fn discard_edit_prediction(
7504 &mut self,
7505 should_report_edit_prediction_event: bool,
7506 cx: &mut Context<Self>,
7507 ) -> bool {
7508 if should_report_edit_prediction_event {
7509 let completion_id = self
7510 .active_edit_prediction
7511 .as_ref()
7512 .and_then(|active_completion| active_completion.completion_id.clone());
7513
7514 self.report_edit_prediction_event(completion_id, false, cx);
7515 }
7516
7517 if let Some(provider) = self.edit_prediction_provider() {
7518 provider.discard(cx);
7519 }
7520
7521 self.take_active_edit_prediction(cx)
7522 }
7523
7524 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7525 let Some(provider) = self.edit_prediction_provider() else {
7526 return;
7527 };
7528
7529 let Some((_, buffer, _)) = self
7530 .buffer
7531 .read(cx)
7532 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7533 else {
7534 return;
7535 };
7536
7537 let extension = buffer
7538 .read(cx)
7539 .file()
7540 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7541
7542 let event_type = match accepted {
7543 true => "Edit Prediction Accepted",
7544 false => "Edit Prediction Discarded",
7545 };
7546 telemetry::event!(
7547 event_type,
7548 provider = provider.name(),
7549 prediction_id = id,
7550 suggestion_accepted = accepted,
7551 file_extension = extension,
7552 );
7553 }
7554
7555 pub fn has_active_edit_prediction(&self) -> bool {
7556 self.active_edit_prediction.is_some()
7557 }
7558
7559 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7560 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7561 return false;
7562 };
7563
7564 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7565 self.clear_highlights::<EditPredictionHighlight>(cx);
7566 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7567 true
7568 }
7569
7570 /// Returns true when we're displaying the edit prediction popover below the cursor
7571 /// like we are not previewing and the LSP autocomplete menu is visible
7572 /// or we are in `when_holding_modifier` mode.
7573 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7574 if self.edit_prediction_preview_is_active()
7575 || !self.show_edit_predictions_in_menu()
7576 || !self.edit_predictions_enabled()
7577 {
7578 return false;
7579 }
7580
7581 if self.has_visible_completions_menu() {
7582 return true;
7583 }
7584
7585 has_completion && self.edit_prediction_requires_modifier()
7586 }
7587
7588 fn handle_modifiers_changed(
7589 &mut self,
7590 modifiers: Modifiers,
7591 position_map: &PositionMap,
7592 window: &mut Window,
7593 cx: &mut Context<Self>,
7594 ) {
7595 if self.show_edit_predictions_in_menu() {
7596 self.update_edit_prediction_preview(&modifiers, window, cx);
7597 }
7598
7599 self.update_selection_mode(&modifiers, position_map, window, cx);
7600
7601 let mouse_position = window.mouse_position();
7602 if !position_map.text_hitbox.is_hovered(window) {
7603 return;
7604 }
7605
7606 self.update_hovered_link(
7607 position_map.point_for_position(mouse_position),
7608 &position_map.snapshot,
7609 modifiers,
7610 window,
7611 cx,
7612 )
7613 }
7614
7615 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7616 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7617 if invert {
7618 match multi_cursor_setting {
7619 MultiCursorModifier::Alt => modifiers.alt,
7620 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7621 }
7622 } else {
7623 match multi_cursor_setting {
7624 MultiCursorModifier::Alt => modifiers.secondary(),
7625 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7626 }
7627 }
7628 }
7629
7630 fn columnar_selection_mode(
7631 modifiers: &Modifiers,
7632 cx: &mut Context<Self>,
7633 ) -> Option<ColumnarMode> {
7634 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7635 if Self::multi_cursor_modifier(false, modifiers, cx) {
7636 Some(ColumnarMode::FromMouse)
7637 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7638 Some(ColumnarMode::FromSelection)
7639 } else {
7640 None
7641 }
7642 } else {
7643 None
7644 }
7645 }
7646
7647 fn update_selection_mode(
7648 &mut self,
7649 modifiers: &Modifiers,
7650 position_map: &PositionMap,
7651 window: &mut Window,
7652 cx: &mut Context<Self>,
7653 ) {
7654 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7655 return;
7656 };
7657 if self.selections.pending.is_none() {
7658 return;
7659 }
7660
7661 let mouse_position = window.mouse_position();
7662 let point_for_position = position_map.point_for_position(mouse_position);
7663 let position = point_for_position.previous_valid;
7664
7665 self.select(
7666 SelectPhase::BeginColumnar {
7667 position,
7668 reset: false,
7669 mode,
7670 goal_column: point_for_position.exact_unclipped.column(),
7671 },
7672 window,
7673 cx,
7674 );
7675 }
7676
7677 fn update_edit_prediction_preview(
7678 &mut self,
7679 modifiers: &Modifiers,
7680 window: &mut Window,
7681 cx: &mut Context<Self>,
7682 ) {
7683 let mut modifiers_held = false;
7684 if let Some(accept_keystroke) = self
7685 .accept_edit_prediction_keybind(false, window, cx)
7686 .keystroke()
7687 {
7688 modifiers_held = modifiers_held
7689 || (accept_keystroke.modifiers() == modifiers
7690 && accept_keystroke.modifiers().modified());
7691 };
7692 if let Some(accept_partial_keystroke) = self
7693 .accept_edit_prediction_keybind(true, window, cx)
7694 .keystroke()
7695 {
7696 modifiers_held = modifiers_held
7697 || (accept_partial_keystroke.modifiers() == modifiers
7698 && accept_partial_keystroke.modifiers().modified());
7699 }
7700
7701 if modifiers_held {
7702 if matches!(
7703 self.edit_prediction_preview,
7704 EditPredictionPreview::Inactive { .. }
7705 ) {
7706 self.edit_prediction_preview = EditPredictionPreview::Active {
7707 previous_scroll_position: None,
7708 since: Instant::now(),
7709 };
7710
7711 self.update_visible_edit_prediction(window, cx);
7712 cx.notify();
7713 }
7714 } else if let EditPredictionPreview::Active {
7715 previous_scroll_position,
7716 since,
7717 } = self.edit_prediction_preview
7718 {
7719 if let (Some(previous_scroll_position), Some(position_map)) =
7720 (previous_scroll_position, self.last_position_map.as_ref())
7721 {
7722 self.set_scroll_position(
7723 previous_scroll_position
7724 .scroll_position(&position_map.snapshot.display_snapshot),
7725 window,
7726 cx,
7727 );
7728 }
7729
7730 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7731 released_too_fast: since.elapsed() < Duration::from_millis(200),
7732 };
7733 self.clear_row_highlights::<EditPredictionPreview>();
7734 self.update_visible_edit_prediction(window, cx);
7735 cx.notify();
7736 }
7737 }
7738
7739 fn update_visible_edit_prediction(
7740 &mut self,
7741 _window: &mut Window,
7742 cx: &mut Context<Self>,
7743 ) -> Option<()> {
7744 if DisableAiSettings::get_global(cx).disable_ai {
7745 return None;
7746 }
7747
7748 let selection = self.selections.newest_anchor();
7749 let cursor = selection.head();
7750 let multibuffer = self.buffer.read(cx).snapshot(cx);
7751 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7752 let excerpt_id = cursor.excerpt_id;
7753
7754 let show_in_menu = self.show_edit_predictions_in_menu();
7755 let completions_menu_has_precedence = !show_in_menu
7756 && (self.context_menu.borrow().is_some()
7757 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7758
7759 if completions_menu_has_precedence
7760 || !offset_selection.is_empty()
7761 || self
7762 .active_edit_prediction
7763 .as_ref()
7764 .is_some_and(|completion| {
7765 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7766 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7767 !invalidation_range.contains(&offset_selection.head())
7768 })
7769 {
7770 self.discard_edit_prediction(false, cx);
7771 return None;
7772 }
7773
7774 self.take_active_edit_prediction(cx);
7775 let Some(provider) = self.edit_prediction_provider() else {
7776 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7777 return None;
7778 };
7779
7780 let (buffer, cursor_buffer_position) =
7781 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7782
7783 self.edit_prediction_settings =
7784 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7785
7786 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7787 self.discard_edit_prediction(false, cx);
7788 return None;
7789 };
7790
7791 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7792
7793 if self.edit_prediction_indent_conflict {
7794 let cursor_point = cursor.to_point(&multibuffer);
7795
7796 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7797
7798 if let Some((_, indent)) = indents.iter().next()
7799 && indent.len == cursor_point.column
7800 {
7801 self.edit_prediction_indent_conflict = false;
7802 }
7803 }
7804
7805 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7806 let edits = edit_prediction
7807 .edits
7808 .into_iter()
7809 .flat_map(|(range, new_text)| {
7810 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7811 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7812 Some((start..end, new_text))
7813 })
7814 .collect::<Vec<_>>();
7815 if edits.is_empty() {
7816 return None;
7817 }
7818
7819 let first_edit_start = edits.first().unwrap().0.start;
7820 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7821 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7822
7823 let last_edit_end = edits.last().unwrap().0.end;
7824 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7825 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7826
7827 let cursor_row = cursor.to_point(&multibuffer).row;
7828
7829 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7830
7831 let mut inlay_ids = Vec::new();
7832 let invalidation_row_range;
7833 let move_invalidation_row_range = if cursor_row < edit_start_row {
7834 Some(cursor_row..edit_end_row)
7835 } else if cursor_row > edit_end_row {
7836 Some(edit_start_row..cursor_row)
7837 } else {
7838 None
7839 };
7840 let supports_jump = self
7841 .edit_prediction_provider
7842 .as_ref()
7843 .map(|provider| provider.provider.supports_jump_to_edit())
7844 .unwrap_or(true);
7845
7846 let is_move = supports_jump
7847 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7848 let completion = if is_move {
7849 invalidation_row_range =
7850 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7851 let target = first_edit_start;
7852 EditPrediction::Move { target, snapshot }
7853 } else {
7854 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7855 && !self.edit_predictions_hidden_for_vim_mode;
7856
7857 if show_completions_in_buffer {
7858 if edits
7859 .iter()
7860 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7861 {
7862 let mut inlays = Vec::new();
7863 for (range, new_text) in &edits {
7864 let inlay = Inlay::edit_prediction(
7865 post_inc(&mut self.next_inlay_id),
7866 range.start,
7867 new_text.as_str(),
7868 );
7869 inlay_ids.push(inlay.id);
7870 inlays.push(inlay);
7871 }
7872
7873 self.splice_inlays(&[], inlays, cx);
7874 } else {
7875 let background_color = cx.theme().status().deleted_background;
7876 self.highlight_text::<EditPredictionHighlight>(
7877 edits.iter().map(|(range, _)| range.clone()).collect(),
7878 HighlightStyle {
7879 background_color: Some(background_color),
7880 ..Default::default()
7881 },
7882 cx,
7883 );
7884 }
7885 }
7886
7887 invalidation_row_range = edit_start_row..edit_end_row;
7888
7889 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7890 if provider.show_tab_accept_marker() {
7891 EditDisplayMode::TabAccept
7892 } else {
7893 EditDisplayMode::Inline
7894 }
7895 } else {
7896 EditDisplayMode::DiffPopover
7897 };
7898
7899 EditPrediction::Edit {
7900 edits,
7901 edit_preview: edit_prediction.edit_preview,
7902 display_mode,
7903 snapshot,
7904 }
7905 };
7906
7907 let invalidation_range = multibuffer
7908 .anchor_before(Point::new(invalidation_row_range.start, 0))
7909 ..multibuffer.anchor_after(Point::new(
7910 invalidation_row_range.end,
7911 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7912 ));
7913
7914 self.stale_edit_prediction_in_menu = None;
7915 self.active_edit_prediction = Some(EditPredictionState {
7916 inlay_ids,
7917 completion,
7918 completion_id: edit_prediction.id,
7919 invalidation_range,
7920 });
7921
7922 cx.notify();
7923
7924 Some(())
7925 }
7926
7927 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7928 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7929 }
7930
7931 fn clear_tasks(&mut self) {
7932 self.tasks.clear()
7933 }
7934
7935 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7936 if self.tasks.insert(key, value).is_some() {
7937 // This case should hopefully be rare, but just in case...
7938 log::error!(
7939 "multiple different run targets found on a single line, only the last target will be rendered"
7940 )
7941 }
7942 }
7943
7944 /// Get all display points of breakpoints that will be rendered within editor
7945 ///
7946 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7947 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7948 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7949 fn active_breakpoints(
7950 &self,
7951 range: Range<DisplayRow>,
7952 window: &mut Window,
7953 cx: &mut Context<Self>,
7954 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7955 let mut breakpoint_display_points = HashMap::default();
7956
7957 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7958 return breakpoint_display_points;
7959 };
7960
7961 let snapshot = self.snapshot(window, cx);
7962
7963 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7964 let Some(project) = self.project() else {
7965 return breakpoint_display_points;
7966 };
7967
7968 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7969 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7970
7971 for (buffer_snapshot, range, excerpt_id) in
7972 multi_buffer_snapshot.range_to_buffer_ranges(range)
7973 {
7974 let Some(buffer) = project
7975 .read(cx)
7976 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7977 else {
7978 continue;
7979 };
7980 let breakpoints = breakpoint_store.read(cx).breakpoints(
7981 &buffer,
7982 Some(
7983 buffer_snapshot.anchor_before(range.start)
7984 ..buffer_snapshot.anchor_after(range.end),
7985 ),
7986 buffer_snapshot,
7987 cx,
7988 );
7989 for (breakpoint, state) in breakpoints {
7990 let multi_buffer_anchor =
7991 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7992 let position = multi_buffer_anchor
7993 .to_point(multi_buffer_snapshot)
7994 .to_display_point(&snapshot);
7995
7996 breakpoint_display_points.insert(
7997 position.row(),
7998 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7999 );
8000 }
8001 }
8002
8003 breakpoint_display_points
8004 }
8005
8006 fn breakpoint_context_menu(
8007 &self,
8008 anchor: Anchor,
8009 window: &mut Window,
8010 cx: &mut Context<Self>,
8011 ) -> Entity<ui::ContextMenu> {
8012 let weak_editor = cx.weak_entity();
8013 let focus_handle = self.focus_handle(cx);
8014
8015 let row = self
8016 .buffer
8017 .read(cx)
8018 .snapshot(cx)
8019 .summary_for_anchor::<Point>(&anchor)
8020 .row;
8021
8022 let breakpoint = self
8023 .breakpoint_at_row(row, window, cx)
8024 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8025
8026 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8027 "Edit Log Breakpoint"
8028 } else {
8029 "Set Log Breakpoint"
8030 };
8031
8032 let condition_breakpoint_msg = if breakpoint
8033 .as_ref()
8034 .is_some_and(|bp| bp.1.condition.is_some())
8035 {
8036 "Edit Condition Breakpoint"
8037 } else {
8038 "Set Condition Breakpoint"
8039 };
8040
8041 let hit_condition_breakpoint_msg = if breakpoint
8042 .as_ref()
8043 .is_some_and(|bp| bp.1.hit_condition.is_some())
8044 {
8045 "Edit Hit Condition Breakpoint"
8046 } else {
8047 "Set Hit Condition Breakpoint"
8048 };
8049
8050 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8051 "Unset Breakpoint"
8052 } else {
8053 "Set Breakpoint"
8054 };
8055
8056 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8057
8058 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8059 BreakpointState::Enabled => Some("Disable"),
8060 BreakpointState::Disabled => Some("Enable"),
8061 });
8062
8063 let (anchor, breakpoint) =
8064 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8065
8066 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8067 menu.on_blur_subscription(Subscription::new(|| {}))
8068 .context(focus_handle)
8069 .when(run_to_cursor, |this| {
8070 let weak_editor = weak_editor.clone();
8071 this.entry("Run to cursor", None, move |window, cx| {
8072 weak_editor
8073 .update(cx, |editor, cx| {
8074 editor.change_selections(
8075 SelectionEffects::no_scroll(),
8076 window,
8077 cx,
8078 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8079 );
8080 })
8081 .ok();
8082
8083 window.dispatch_action(Box::new(RunToCursor), cx);
8084 })
8085 .separator()
8086 })
8087 .when_some(toggle_state_msg, |this, msg| {
8088 this.entry(msg, None, {
8089 let weak_editor = weak_editor.clone();
8090 let breakpoint = breakpoint.clone();
8091 move |_window, cx| {
8092 weak_editor
8093 .update(cx, |this, cx| {
8094 this.edit_breakpoint_at_anchor(
8095 anchor,
8096 breakpoint.as_ref().clone(),
8097 BreakpointEditAction::InvertState,
8098 cx,
8099 );
8100 })
8101 .log_err();
8102 }
8103 })
8104 })
8105 .entry(set_breakpoint_msg, None, {
8106 let weak_editor = weak_editor.clone();
8107 let breakpoint = breakpoint.clone();
8108 move |_window, cx| {
8109 weak_editor
8110 .update(cx, |this, cx| {
8111 this.edit_breakpoint_at_anchor(
8112 anchor,
8113 breakpoint.as_ref().clone(),
8114 BreakpointEditAction::Toggle,
8115 cx,
8116 );
8117 })
8118 .log_err();
8119 }
8120 })
8121 .entry(log_breakpoint_msg, None, {
8122 let breakpoint = breakpoint.clone();
8123 let weak_editor = weak_editor.clone();
8124 move |window, cx| {
8125 weak_editor
8126 .update(cx, |this, cx| {
8127 this.add_edit_breakpoint_block(
8128 anchor,
8129 breakpoint.as_ref(),
8130 BreakpointPromptEditAction::Log,
8131 window,
8132 cx,
8133 );
8134 })
8135 .log_err();
8136 }
8137 })
8138 .entry(condition_breakpoint_msg, None, {
8139 let breakpoint = breakpoint.clone();
8140 let weak_editor = weak_editor.clone();
8141 move |window, cx| {
8142 weak_editor
8143 .update(cx, |this, cx| {
8144 this.add_edit_breakpoint_block(
8145 anchor,
8146 breakpoint.as_ref(),
8147 BreakpointPromptEditAction::Condition,
8148 window,
8149 cx,
8150 );
8151 })
8152 .log_err();
8153 }
8154 })
8155 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8156 weak_editor
8157 .update(cx, |this, cx| {
8158 this.add_edit_breakpoint_block(
8159 anchor,
8160 breakpoint.as_ref(),
8161 BreakpointPromptEditAction::HitCondition,
8162 window,
8163 cx,
8164 );
8165 })
8166 .log_err();
8167 })
8168 })
8169 }
8170
8171 fn render_breakpoint(
8172 &self,
8173 position: Anchor,
8174 row: DisplayRow,
8175 breakpoint: &Breakpoint,
8176 state: Option<BreakpointSessionState>,
8177 cx: &mut Context<Self>,
8178 ) -> IconButton {
8179 let is_rejected = state.is_some_and(|s| !s.verified);
8180 // Is it a breakpoint that shows up when hovering over gutter?
8181 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8182 (false, false),
8183 |PhantomBreakpointIndicator {
8184 is_active,
8185 display_row,
8186 collides_with_existing_breakpoint,
8187 }| {
8188 (
8189 is_active && display_row == row,
8190 collides_with_existing_breakpoint,
8191 )
8192 },
8193 );
8194
8195 let (color, icon) = {
8196 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8197 (false, false) => ui::IconName::DebugBreakpoint,
8198 (true, false) => ui::IconName::DebugLogBreakpoint,
8199 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8200 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8201 };
8202
8203 let color = if is_phantom {
8204 Color::Hint
8205 } else if is_rejected {
8206 Color::Disabled
8207 } else {
8208 Color::Debugger
8209 };
8210
8211 (color, icon)
8212 };
8213
8214 let breakpoint = Arc::from(breakpoint.clone());
8215
8216 let alt_as_text = gpui::Keystroke {
8217 modifiers: Modifiers::secondary_key(),
8218 ..Default::default()
8219 };
8220 let primary_action_text = if breakpoint.is_disabled() {
8221 "Enable breakpoint"
8222 } else if is_phantom && !collides_with_existing {
8223 "Set breakpoint"
8224 } else {
8225 "Unset breakpoint"
8226 };
8227 let focus_handle = self.focus_handle.clone();
8228
8229 let meta = if is_rejected {
8230 SharedString::from("No executable code is associated with this line.")
8231 } else if collides_with_existing && !breakpoint.is_disabled() {
8232 SharedString::from(format!(
8233 "{alt_as_text}-click to disable,\nright-click for more options."
8234 ))
8235 } else {
8236 SharedString::from("Right-click for more options.")
8237 };
8238 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8239 .icon_size(IconSize::XSmall)
8240 .size(ui::ButtonSize::None)
8241 .when(is_rejected, |this| {
8242 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8243 })
8244 .icon_color(color)
8245 .style(ButtonStyle::Transparent)
8246 .on_click(cx.listener({
8247 move |editor, event: &ClickEvent, window, cx| {
8248 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8249 BreakpointEditAction::InvertState
8250 } else {
8251 BreakpointEditAction::Toggle
8252 };
8253
8254 window.focus(&editor.focus_handle(cx));
8255 editor.edit_breakpoint_at_anchor(
8256 position,
8257 breakpoint.as_ref().clone(),
8258 edit_action,
8259 cx,
8260 );
8261 }
8262 }))
8263 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8264 editor.set_breakpoint_context_menu(
8265 row,
8266 Some(position),
8267 event.position(),
8268 window,
8269 cx,
8270 );
8271 }))
8272 .tooltip(move |window, cx| {
8273 Tooltip::with_meta_in(
8274 primary_action_text,
8275 Some(&ToggleBreakpoint),
8276 meta.clone(),
8277 &focus_handle,
8278 window,
8279 cx,
8280 )
8281 })
8282 }
8283
8284 fn build_tasks_context(
8285 project: &Entity<Project>,
8286 buffer: &Entity<Buffer>,
8287 buffer_row: u32,
8288 tasks: &Arc<RunnableTasks>,
8289 cx: &mut Context<Self>,
8290 ) -> Task<Option<task::TaskContext>> {
8291 let position = Point::new(buffer_row, tasks.column);
8292 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8293 let location = Location {
8294 buffer: buffer.clone(),
8295 range: range_start..range_start,
8296 };
8297 // Fill in the environmental variables from the tree-sitter captures
8298 let mut captured_task_variables = TaskVariables::default();
8299 for (capture_name, value) in tasks.extra_variables.clone() {
8300 captured_task_variables.insert(
8301 task::VariableName::Custom(capture_name.into()),
8302 value.clone(),
8303 );
8304 }
8305 project.update(cx, |project, cx| {
8306 project.task_store().update(cx, |task_store, cx| {
8307 task_store.task_context_for_location(captured_task_variables, location, cx)
8308 })
8309 })
8310 }
8311
8312 pub fn spawn_nearest_task(
8313 &mut self,
8314 action: &SpawnNearestTask,
8315 window: &mut Window,
8316 cx: &mut Context<Self>,
8317 ) {
8318 let Some((workspace, _)) = self.workspace.clone() else {
8319 return;
8320 };
8321 let Some(project) = self.project.clone() else {
8322 return;
8323 };
8324
8325 // Try to find a closest, enclosing node using tree-sitter that has a task
8326 let Some((buffer, buffer_row, tasks)) = self
8327 .find_enclosing_node_task(cx)
8328 // Or find the task that's closest in row-distance.
8329 .or_else(|| self.find_closest_task(cx))
8330 else {
8331 return;
8332 };
8333
8334 let reveal_strategy = action.reveal;
8335 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8336 cx.spawn_in(window, async move |_, cx| {
8337 let context = task_context.await?;
8338 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8339
8340 let resolved = &mut resolved_task.resolved;
8341 resolved.reveal = reveal_strategy;
8342
8343 workspace
8344 .update_in(cx, |workspace, window, cx| {
8345 workspace.schedule_resolved_task(
8346 task_source_kind,
8347 resolved_task,
8348 false,
8349 window,
8350 cx,
8351 );
8352 })
8353 .ok()
8354 })
8355 .detach();
8356 }
8357
8358 fn find_closest_task(
8359 &mut self,
8360 cx: &mut Context<Self>,
8361 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8362 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8363
8364 let ((buffer_id, row), tasks) = self
8365 .tasks
8366 .iter()
8367 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8368
8369 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8370 let tasks = Arc::new(tasks.to_owned());
8371 Some((buffer, *row, tasks))
8372 }
8373
8374 fn find_enclosing_node_task(
8375 &mut self,
8376 cx: &mut Context<Self>,
8377 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8378 let snapshot = self.buffer.read(cx).snapshot(cx);
8379 let offset = self.selections.newest::<usize>(cx).head();
8380 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8381 let buffer_id = excerpt.buffer().remote_id();
8382
8383 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8384 let mut cursor = layer.node().walk();
8385
8386 while cursor.goto_first_child_for_byte(offset).is_some() {
8387 if cursor.node().end_byte() == offset {
8388 cursor.goto_next_sibling();
8389 }
8390 }
8391
8392 // Ascend to the smallest ancestor that contains the range and has a task.
8393 loop {
8394 let node = cursor.node();
8395 let node_range = node.byte_range();
8396 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8397
8398 // Check if this node contains our offset
8399 if node_range.start <= offset && node_range.end >= offset {
8400 // If it contains offset, check for task
8401 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8402 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8403 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8404 }
8405 }
8406
8407 if !cursor.goto_parent() {
8408 break;
8409 }
8410 }
8411 None
8412 }
8413
8414 fn render_run_indicator(
8415 &self,
8416 _style: &EditorStyle,
8417 is_active: bool,
8418 row: DisplayRow,
8419 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8420 cx: &mut Context<Self>,
8421 ) -> IconButton {
8422 let color = Color::Muted;
8423 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8424
8425 IconButton::new(
8426 ("run_indicator", row.0 as usize),
8427 ui::IconName::PlayOutlined,
8428 )
8429 .shape(ui::IconButtonShape::Square)
8430 .icon_size(IconSize::XSmall)
8431 .icon_color(color)
8432 .toggle_state(is_active)
8433 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8434 let quick_launch = match e {
8435 ClickEvent::Keyboard(_) => true,
8436 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8437 };
8438
8439 window.focus(&editor.focus_handle(cx));
8440 editor.toggle_code_actions(
8441 &ToggleCodeActions {
8442 deployed_from: Some(CodeActionSource::RunMenu(row)),
8443 quick_launch,
8444 },
8445 window,
8446 cx,
8447 );
8448 }))
8449 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8450 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8451 }))
8452 }
8453
8454 pub fn context_menu_visible(&self) -> bool {
8455 !self.edit_prediction_preview_is_active()
8456 && self
8457 .context_menu
8458 .borrow()
8459 .as_ref()
8460 .is_some_and(|menu| menu.visible())
8461 }
8462
8463 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8464 self.context_menu
8465 .borrow()
8466 .as_ref()
8467 .map(|menu| menu.origin())
8468 }
8469
8470 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8471 self.context_menu_options = Some(options);
8472 }
8473
8474 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8475 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8476
8477 fn render_edit_prediction_popover(
8478 &mut self,
8479 text_bounds: &Bounds<Pixels>,
8480 content_origin: gpui::Point<Pixels>,
8481 right_margin: Pixels,
8482 editor_snapshot: &EditorSnapshot,
8483 visible_row_range: Range<DisplayRow>,
8484 scroll_top: f32,
8485 scroll_bottom: f32,
8486 line_layouts: &[LineWithInvisibles],
8487 line_height: Pixels,
8488 scroll_pixel_position: gpui::Point<Pixels>,
8489 newest_selection_head: Option<DisplayPoint>,
8490 editor_width: Pixels,
8491 style: &EditorStyle,
8492 window: &mut Window,
8493 cx: &mut App,
8494 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8495 if self.mode().is_minimap() {
8496 return None;
8497 }
8498 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8499
8500 if self.edit_prediction_visible_in_cursor_popover(true) {
8501 return None;
8502 }
8503
8504 match &active_edit_prediction.completion {
8505 EditPrediction::Move { target, .. } => {
8506 let target_display_point = target.to_display_point(editor_snapshot);
8507
8508 if self.edit_prediction_requires_modifier() {
8509 if !self.edit_prediction_preview_is_active() {
8510 return None;
8511 }
8512
8513 self.render_edit_prediction_modifier_jump_popover(
8514 text_bounds,
8515 content_origin,
8516 visible_row_range,
8517 line_layouts,
8518 line_height,
8519 scroll_pixel_position,
8520 newest_selection_head,
8521 target_display_point,
8522 window,
8523 cx,
8524 )
8525 } else {
8526 self.render_edit_prediction_eager_jump_popover(
8527 text_bounds,
8528 content_origin,
8529 editor_snapshot,
8530 visible_row_range,
8531 scroll_top,
8532 scroll_bottom,
8533 line_height,
8534 scroll_pixel_position,
8535 target_display_point,
8536 editor_width,
8537 window,
8538 cx,
8539 )
8540 }
8541 }
8542 EditPrediction::Edit {
8543 display_mode: EditDisplayMode::Inline,
8544 ..
8545 } => None,
8546 EditPrediction::Edit {
8547 display_mode: EditDisplayMode::TabAccept,
8548 edits,
8549 ..
8550 } => {
8551 let range = &edits.first()?.0;
8552 let target_display_point = range.end.to_display_point(editor_snapshot);
8553
8554 self.render_edit_prediction_end_of_line_popover(
8555 "Accept",
8556 editor_snapshot,
8557 visible_row_range,
8558 target_display_point,
8559 line_height,
8560 scroll_pixel_position,
8561 content_origin,
8562 editor_width,
8563 window,
8564 cx,
8565 )
8566 }
8567 EditPrediction::Edit {
8568 edits,
8569 edit_preview,
8570 display_mode: EditDisplayMode::DiffPopover,
8571 snapshot,
8572 } => self.render_edit_prediction_diff_popover(
8573 text_bounds,
8574 content_origin,
8575 right_margin,
8576 editor_snapshot,
8577 visible_row_range,
8578 line_layouts,
8579 line_height,
8580 scroll_pixel_position,
8581 newest_selection_head,
8582 editor_width,
8583 style,
8584 edits,
8585 edit_preview,
8586 snapshot,
8587 window,
8588 cx,
8589 ),
8590 }
8591 }
8592
8593 fn render_edit_prediction_modifier_jump_popover(
8594 &mut self,
8595 text_bounds: &Bounds<Pixels>,
8596 content_origin: gpui::Point<Pixels>,
8597 visible_row_range: Range<DisplayRow>,
8598 line_layouts: &[LineWithInvisibles],
8599 line_height: Pixels,
8600 scroll_pixel_position: gpui::Point<Pixels>,
8601 newest_selection_head: Option<DisplayPoint>,
8602 target_display_point: DisplayPoint,
8603 window: &mut Window,
8604 cx: &mut App,
8605 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8606 let scrolled_content_origin =
8607 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8608
8609 const SCROLL_PADDING_Y: Pixels = px(12.);
8610
8611 if target_display_point.row() < visible_row_range.start {
8612 return self.render_edit_prediction_scroll_popover(
8613 |_| SCROLL_PADDING_Y,
8614 IconName::ArrowUp,
8615 visible_row_range,
8616 line_layouts,
8617 newest_selection_head,
8618 scrolled_content_origin,
8619 window,
8620 cx,
8621 );
8622 } else if target_display_point.row() >= visible_row_range.end {
8623 return self.render_edit_prediction_scroll_popover(
8624 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8625 IconName::ArrowDown,
8626 visible_row_range,
8627 line_layouts,
8628 newest_selection_head,
8629 scrolled_content_origin,
8630 window,
8631 cx,
8632 );
8633 }
8634
8635 const POLE_WIDTH: Pixels = px(2.);
8636
8637 let line_layout =
8638 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8639 let target_column = target_display_point.column() as usize;
8640
8641 let target_x = line_layout.x_for_index(target_column);
8642 let target_y =
8643 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8644
8645 let flag_on_right = target_x < text_bounds.size.width / 2.;
8646
8647 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8648 border_color.l += 0.001;
8649
8650 let mut element = v_flex()
8651 .items_end()
8652 .when(flag_on_right, |el| el.items_start())
8653 .child(if flag_on_right {
8654 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8655 .rounded_bl(px(0.))
8656 .rounded_tl(px(0.))
8657 .border_l_2()
8658 .border_color(border_color)
8659 } else {
8660 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8661 .rounded_br(px(0.))
8662 .rounded_tr(px(0.))
8663 .border_r_2()
8664 .border_color(border_color)
8665 })
8666 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8667 .into_any();
8668
8669 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8670
8671 let mut origin = scrolled_content_origin + point(target_x, target_y)
8672 - point(
8673 if flag_on_right {
8674 POLE_WIDTH
8675 } else {
8676 size.width - POLE_WIDTH
8677 },
8678 size.height - line_height,
8679 );
8680
8681 origin.x = origin.x.max(content_origin.x);
8682
8683 element.prepaint_at(origin, window, cx);
8684
8685 Some((element, origin))
8686 }
8687
8688 fn render_edit_prediction_scroll_popover(
8689 &mut self,
8690 to_y: impl Fn(Size<Pixels>) -> Pixels,
8691 scroll_icon: IconName,
8692 visible_row_range: Range<DisplayRow>,
8693 line_layouts: &[LineWithInvisibles],
8694 newest_selection_head: Option<DisplayPoint>,
8695 scrolled_content_origin: gpui::Point<Pixels>,
8696 window: &mut Window,
8697 cx: &mut App,
8698 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8699 let mut element = self
8700 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8701 .into_any();
8702
8703 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8704
8705 let cursor = newest_selection_head?;
8706 let cursor_row_layout =
8707 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8708 let cursor_column = cursor.column() as usize;
8709
8710 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8711
8712 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8713
8714 element.prepaint_at(origin, window, cx);
8715 Some((element, origin))
8716 }
8717
8718 fn render_edit_prediction_eager_jump_popover(
8719 &mut self,
8720 text_bounds: &Bounds<Pixels>,
8721 content_origin: gpui::Point<Pixels>,
8722 editor_snapshot: &EditorSnapshot,
8723 visible_row_range: Range<DisplayRow>,
8724 scroll_top: f32,
8725 scroll_bottom: f32,
8726 line_height: Pixels,
8727 scroll_pixel_position: gpui::Point<Pixels>,
8728 target_display_point: DisplayPoint,
8729 editor_width: Pixels,
8730 window: &mut Window,
8731 cx: &mut App,
8732 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8733 if target_display_point.row().as_f32() < scroll_top {
8734 let mut element = self
8735 .render_edit_prediction_line_popover(
8736 "Jump to Edit",
8737 Some(IconName::ArrowUp),
8738 window,
8739 cx,
8740 )?
8741 .into_any();
8742
8743 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8744 let offset = point(
8745 (text_bounds.size.width - size.width) / 2.,
8746 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8747 );
8748
8749 let origin = text_bounds.origin + offset;
8750 element.prepaint_at(origin, window, cx);
8751 Some((element, origin))
8752 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8753 let mut element = self
8754 .render_edit_prediction_line_popover(
8755 "Jump to Edit",
8756 Some(IconName::ArrowDown),
8757 window,
8758 cx,
8759 )?
8760 .into_any();
8761
8762 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8763 let offset = point(
8764 (text_bounds.size.width - size.width) / 2.,
8765 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8766 );
8767
8768 let origin = text_bounds.origin + offset;
8769 element.prepaint_at(origin, window, cx);
8770 Some((element, origin))
8771 } else {
8772 self.render_edit_prediction_end_of_line_popover(
8773 "Jump to Edit",
8774 editor_snapshot,
8775 visible_row_range,
8776 target_display_point,
8777 line_height,
8778 scroll_pixel_position,
8779 content_origin,
8780 editor_width,
8781 window,
8782 cx,
8783 )
8784 }
8785 }
8786
8787 fn render_edit_prediction_end_of_line_popover(
8788 self: &mut Editor,
8789 label: &'static str,
8790 editor_snapshot: &EditorSnapshot,
8791 visible_row_range: Range<DisplayRow>,
8792 target_display_point: DisplayPoint,
8793 line_height: Pixels,
8794 scroll_pixel_position: gpui::Point<Pixels>,
8795 content_origin: gpui::Point<Pixels>,
8796 editor_width: Pixels,
8797 window: &mut Window,
8798 cx: &mut App,
8799 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8800 let target_line_end = DisplayPoint::new(
8801 target_display_point.row(),
8802 editor_snapshot.line_len(target_display_point.row()),
8803 );
8804
8805 let mut element = self
8806 .render_edit_prediction_line_popover(label, None, window, cx)?
8807 .into_any();
8808
8809 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8810
8811 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8812
8813 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8814 let mut origin = start_point
8815 + line_origin
8816 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8817 origin.x = origin.x.max(content_origin.x);
8818
8819 let max_x = content_origin.x + editor_width - size.width;
8820
8821 if origin.x > max_x {
8822 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8823
8824 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8825 origin.y += offset;
8826 IconName::ArrowUp
8827 } else {
8828 origin.y -= offset;
8829 IconName::ArrowDown
8830 };
8831
8832 element = self
8833 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8834 .into_any();
8835
8836 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8837
8838 origin.x = content_origin.x + editor_width - size.width - px(2.);
8839 }
8840
8841 element.prepaint_at(origin, window, cx);
8842 Some((element, origin))
8843 }
8844
8845 fn render_edit_prediction_diff_popover(
8846 self: &Editor,
8847 text_bounds: &Bounds<Pixels>,
8848 content_origin: gpui::Point<Pixels>,
8849 right_margin: Pixels,
8850 editor_snapshot: &EditorSnapshot,
8851 visible_row_range: Range<DisplayRow>,
8852 line_layouts: &[LineWithInvisibles],
8853 line_height: Pixels,
8854 scroll_pixel_position: gpui::Point<Pixels>,
8855 newest_selection_head: Option<DisplayPoint>,
8856 editor_width: Pixels,
8857 style: &EditorStyle,
8858 edits: &Vec<(Range<Anchor>, String)>,
8859 edit_preview: &Option<language::EditPreview>,
8860 snapshot: &language::BufferSnapshot,
8861 window: &mut Window,
8862 cx: &mut App,
8863 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8864 let edit_start = edits
8865 .first()
8866 .unwrap()
8867 .0
8868 .start
8869 .to_display_point(editor_snapshot);
8870 let edit_end = edits
8871 .last()
8872 .unwrap()
8873 .0
8874 .end
8875 .to_display_point(editor_snapshot);
8876
8877 let is_visible = visible_row_range.contains(&edit_start.row())
8878 || visible_row_range.contains(&edit_end.row());
8879 if !is_visible {
8880 return None;
8881 }
8882
8883 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8884 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8885 } else {
8886 // Fallback for providers without edit_preview
8887 crate::edit_prediction_fallback_text(edits, cx)
8888 };
8889
8890 let styled_text = highlighted_edits.to_styled_text(&style.text);
8891 let line_count = highlighted_edits.text.lines().count();
8892
8893 const BORDER_WIDTH: Pixels = px(1.);
8894
8895 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8896 let has_keybind = keybind.is_some();
8897
8898 let mut element = h_flex()
8899 .items_start()
8900 .child(
8901 h_flex()
8902 .bg(cx.theme().colors().editor_background)
8903 .border(BORDER_WIDTH)
8904 .shadow_xs()
8905 .border_color(cx.theme().colors().border)
8906 .rounded_l_lg()
8907 .when(line_count > 1, |el| el.rounded_br_lg())
8908 .pr_1()
8909 .child(styled_text),
8910 )
8911 .child(
8912 h_flex()
8913 .h(line_height + BORDER_WIDTH * 2.)
8914 .px_1p5()
8915 .gap_1()
8916 // Workaround: For some reason, there's a gap if we don't do this
8917 .ml(-BORDER_WIDTH)
8918 .shadow(vec![gpui::BoxShadow {
8919 color: gpui::black().opacity(0.05),
8920 offset: point(px(1.), px(1.)),
8921 blur_radius: px(2.),
8922 spread_radius: px(0.),
8923 }])
8924 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8925 .border(BORDER_WIDTH)
8926 .border_color(cx.theme().colors().border)
8927 .rounded_r_lg()
8928 .id("edit_prediction_diff_popover_keybind")
8929 .when(!has_keybind, |el| {
8930 let status_colors = cx.theme().status();
8931
8932 el.bg(status_colors.error_background)
8933 .border_color(status_colors.error.opacity(0.6))
8934 .child(Icon::new(IconName::Info).color(Color::Error))
8935 .cursor_default()
8936 .hoverable_tooltip(move |_window, cx| {
8937 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8938 })
8939 })
8940 .children(keybind),
8941 )
8942 .into_any();
8943
8944 let longest_row =
8945 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8946 let longest_line_width = if visible_row_range.contains(&longest_row) {
8947 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8948 } else {
8949 layout_line(
8950 longest_row,
8951 editor_snapshot,
8952 style,
8953 editor_width,
8954 |_| false,
8955 window,
8956 cx,
8957 )
8958 .width
8959 };
8960
8961 let viewport_bounds =
8962 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8963 right: -right_margin,
8964 ..Default::default()
8965 });
8966
8967 let x_after_longest =
8968 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8969 - scroll_pixel_position.x;
8970
8971 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8972
8973 // Fully visible if it can be displayed within the window (allow overlapping other
8974 // panes). However, this is only allowed if the popover starts within text_bounds.
8975 let can_position_to_the_right = x_after_longest < text_bounds.right()
8976 && x_after_longest + element_bounds.width < viewport_bounds.right();
8977
8978 let mut origin = if can_position_to_the_right {
8979 point(
8980 x_after_longest,
8981 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8982 - scroll_pixel_position.y,
8983 )
8984 } else {
8985 let cursor_row = newest_selection_head.map(|head| head.row());
8986 let above_edit = edit_start
8987 .row()
8988 .0
8989 .checked_sub(line_count as u32)
8990 .map(DisplayRow);
8991 let below_edit = Some(edit_end.row() + 1);
8992 let above_cursor =
8993 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8994 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8995
8996 // Place the edit popover adjacent to the edit if there is a location
8997 // available that is onscreen and does not obscure the cursor. Otherwise,
8998 // place it adjacent to the cursor.
8999 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9000 .into_iter()
9001 .flatten()
9002 .find(|&start_row| {
9003 let end_row = start_row + line_count as u32;
9004 visible_row_range.contains(&start_row)
9005 && visible_row_range.contains(&end_row)
9006 && cursor_row
9007 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9008 })?;
9009
9010 content_origin
9011 + point(
9012 -scroll_pixel_position.x,
9013 row_target.as_f32() * line_height - scroll_pixel_position.y,
9014 )
9015 };
9016
9017 origin.x -= BORDER_WIDTH;
9018
9019 window.defer_draw(element, origin, 1);
9020
9021 // Do not return an element, since it will already be drawn due to defer_draw.
9022 None
9023 }
9024
9025 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9026 px(30.)
9027 }
9028
9029 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9030 if self.read_only(cx) {
9031 cx.theme().players().read_only()
9032 } else {
9033 self.style.as_ref().unwrap().local_player
9034 }
9035 }
9036
9037 fn render_edit_prediction_accept_keybind(
9038 &self,
9039 window: &mut Window,
9040 cx: &App,
9041 ) -> Option<AnyElement> {
9042 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9043 let accept_keystroke = accept_binding.keystroke()?;
9044
9045 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9046
9047 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9048 Color::Accent
9049 } else {
9050 Color::Muted
9051 };
9052
9053 h_flex()
9054 .px_0p5()
9055 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9056 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9057 .text_size(TextSize::XSmall.rems(cx))
9058 .child(h_flex().children(ui::render_modifiers(
9059 accept_keystroke.modifiers(),
9060 PlatformStyle::platform(),
9061 Some(modifiers_color),
9062 Some(IconSize::XSmall.rems().into()),
9063 true,
9064 )))
9065 .when(is_platform_style_mac, |parent| {
9066 parent.child(accept_keystroke.key().to_string())
9067 })
9068 .when(!is_platform_style_mac, |parent| {
9069 parent.child(
9070 Key::new(
9071 util::capitalize(accept_keystroke.key()),
9072 Some(Color::Default),
9073 )
9074 .size(Some(IconSize::XSmall.rems().into())),
9075 )
9076 })
9077 .into_any()
9078 .into()
9079 }
9080
9081 fn render_edit_prediction_line_popover(
9082 &self,
9083 label: impl Into<SharedString>,
9084 icon: Option<IconName>,
9085 window: &mut Window,
9086 cx: &App,
9087 ) -> Option<Stateful<Div>> {
9088 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9089
9090 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9091 let has_keybind = keybind.is_some();
9092
9093 let result = h_flex()
9094 .id("ep-line-popover")
9095 .py_0p5()
9096 .pl_1()
9097 .pr(padding_right)
9098 .gap_1()
9099 .rounded_md()
9100 .border_1()
9101 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9102 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9103 .shadow_xs()
9104 .when(!has_keybind, |el| {
9105 let status_colors = cx.theme().status();
9106
9107 el.bg(status_colors.error_background)
9108 .border_color(status_colors.error.opacity(0.6))
9109 .pl_2()
9110 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9111 .cursor_default()
9112 .hoverable_tooltip(move |_window, cx| {
9113 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9114 })
9115 })
9116 .children(keybind)
9117 .child(
9118 Label::new(label)
9119 .size(LabelSize::Small)
9120 .when(!has_keybind, |el| {
9121 el.color(cx.theme().status().error.into()).strikethrough()
9122 }),
9123 )
9124 .when(!has_keybind, |el| {
9125 el.child(
9126 h_flex().ml_1().child(
9127 Icon::new(IconName::Info)
9128 .size(IconSize::Small)
9129 .color(cx.theme().status().error.into()),
9130 ),
9131 )
9132 })
9133 .when_some(icon, |element, icon| {
9134 element.child(
9135 div()
9136 .mt(px(1.5))
9137 .child(Icon::new(icon).size(IconSize::Small)),
9138 )
9139 });
9140
9141 Some(result)
9142 }
9143
9144 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9145 let accent_color = cx.theme().colors().text_accent;
9146 let editor_bg_color = cx.theme().colors().editor_background;
9147 editor_bg_color.blend(accent_color.opacity(0.1))
9148 }
9149
9150 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9151 let accent_color = cx.theme().colors().text_accent;
9152 let editor_bg_color = cx.theme().colors().editor_background;
9153 editor_bg_color.blend(accent_color.opacity(0.6))
9154 }
9155 fn get_prediction_provider_icon_name(
9156 provider: &Option<RegisteredEditPredictionProvider>,
9157 ) -> IconName {
9158 match provider {
9159 Some(provider) => match provider.provider.name() {
9160 "copilot" => IconName::Copilot,
9161 "supermaven" => IconName::Supermaven,
9162 _ => IconName::ZedPredict,
9163 },
9164 None => IconName::ZedPredict,
9165 }
9166 }
9167
9168 fn render_edit_prediction_cursor_popover(
9169 &self,
9170 min_width: Pixels,
9171 max_width: Pixels,
9172 cursor_point: Point,
9173 style: &EditorStyle,
9174 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9175 _window: &Window,
9176 cx: &mut Context<Editor>,
9177 ) -> Option<AnyElement> {
9178 let provider = self.edit_prediction_provider.as_ref()?;
9179 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9180
9181 let is_refreshing = provider.provider.is_refreshing(cx);
9182
9183 fn pending_completion_container(icon: IconName) -> Div {
9184 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9185 }
9186
9187 let completion = match &self.active_edit_prediction {
9188 Some(prediction) => {
9189 if !self.has_visible_completions_menu() {
9190 const RADIUS: Pixels = px(6.);
9191 const BORDER_WIDTH: Pixels = px(1.);
9192
9193 return Some(
9194 h_flex()
9195 .elevation_2(cx)
9196 .border(BORDER_WIDTH)
9197 .border_color(cx.theme().colors().border)
9198 .when(accept_keystroke.is_none(), |el| {
9199 el.border_color(cx.theme().status().error)
9200 })
9201 .rounded(RADIUS)
9202 .rounded_tl(px(0.))
9203 .overflow_hidden()
9204 .child(div().px_1p5().child(match &prediction.completion {
9205 EditPrediction::Move { target, snapshot } => {
9206 use text::ToPoint as _;
9207 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9208 {
9209 Icon::new(IconName::ZedPredictDown)
9210 } else {
9211 Icon::new(IconName::ZedPredictUp)
9212 }
9213 }
9214 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9215 }))
9216 .child(
9217 h_flex()
9218 .gap_1()
9219 .py_1()
9220 .px_2()
9221 .rounded_r(RADIUS - BORDER_WIDTH)
9222 .border_l_1()
9223 .border_color(cx.theme().colors().border)
9224 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9225 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9226 el.child(
9227 Label::new("Hold")
9228 .size(LabelSize::Small)
9229 .when(accept_keystroke.is_none(), |el| {
9230 el.strikethrough()
9231 })
9232 .line_height_style(LineHeightStyle::UiLabel),
9233 )
9234 })
9235 .id("edit_prediction_cursor_popover_keybind")
9236 .when(accept_keystroke.is_none(), |el| {
9237 let status_colors = cx.theme().status();
9238
9239 el.bg(status_colors.error_background)
9240 .border_color(status_colors.error.opacity(0.6))
9241 .child(Icon::new(IconName::Info).color(Color::Error))
9242 .cursor_default()
9243 .hoverable_tooltip(move |_window, cx| {
9244 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9245 .into()
9246 })
9247 })
9248 .when_some(
9249 accept_keystroke.as_ref(),
9250 |el, accept_keystroke| {
9251 el.child(h_flex().children(ui::render_modifiers(
9252 accept_keystroke.modifiers(),
9253 PlatformStyle::platform(),
9254 Some(Color::Default),
9255 Some(IconSize::XSmall.rems().into()),
9256 false,
9257 )))
9258 },
9259 ),
9260 )
9261 .into_any(),
9262 );
9263 }
9264
9265 self.render_edit_prediction_cursor_popover_preview(
9266 prediction,
9267 cursor_point,
9268 style,
9269 cx,
9270 )?
9271 }
9272
9273 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9274 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9275 stale_completion,
9276 cursor_point,
9277 style,
9278 cx,
9279 )?,
9280
9281 None => pending_completion_container(provider_icon)
9282 .child(Label::new("...").size(LabelSize::Small)),
9283 },
9284
9285 None => pending_completion_container(provider_icon)
9286 .child(Label::new("...").size(LabelSize::Small)),
9287 };
9288
9289 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9290 completion
9291 .with_animation(
9292 "loading-completion",
9293 Animation::new(Duration::from_secs(2))
9294 .repeat()
9295 .with_easing(pulsating_between(0.4, 0.8)),
9296 |label, delta| label.opacity(delta),
9297 )
9298 .into_any_element()
9299 } else {
9300 completion.into_any_element()
9301 };
9302
9303 let has_completion = self.active_edit_prediction.is_some();
9304
9305 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9306 Some(
9307 h_flex()
9308 .min_w(min_width)
9309 .max_w(max_width)
9310 .flex_1()
9311 .elevation_2(cx)
9312 .border_color(cx.theme().colors().border)
9313 .child(
9314 div()
9315 .flex_1()
9316 .py_1()
9317 .px_2()
9318 .overflow_hidden()
9319 .child(completion),
9320 )
9321 .when_some(accept_keystroke, |el, accept_keystroke| {
9322 if !accept_keystroke.modifiers().modified() {
9323 return el;
9324 }
9325
9326 el.child(
9327 h_flex()
9328 .h_full()
9329 .border_l_1()
9330 .rounded_r_lg()
9331 .border_color(cx.theme().colors().border)
9332 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9333 .gap_1()
9334 .py_1()
9335 .px_2()
9336 .child(
9337 h_flex()
9338 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9339 .when(is_platform_style_mac, |parent| parent.gap_1())
9340 .child(h_flex().children(ui::render_modifiers(
9341 accept_keystroke.modifiers(),
9342 PlatformStyle::platform(),
9343 Some(if !has_completion {
9344 Color::Muted
9345 } else {
9346 Color::Default
9347 }),
9348 None,
9349 false,
9350 ))),
9351 )
9352 .child(Label::new("Preview").into_any_element())
9353 .opacity(if has_completion { 1.0 } else { 0.4 }),
9354 )
9355 })
9356 .into_any(),
9357 )
9358 }
9359
9360 fn render_edit_prediction_cursor_popover_preview(
9361 &self,
9362 completion: &EditPredictionState,
9363 cursor_point: Point,
9364 style: &EditorStyle,
9365 cx: &mut Context<Editor>,
9366 ) -> Option<Div> {
9367 use text::ToPoint as _;
9368
9369 fn render_relative_row_jump(
9370 prefix: impl Into<String>,
9371 current_row: u32,
9372 target_row: u32,
9373 ) -> Div {
9374 let (row_diff, arrow) = if target_row < current_row {
9375 (current_row - target_row, IconName::ArrowUp)
9376 } else {
9377 (target_row - current_row, IconName::ArrowDown)
9378 };
9379
9380 h_flex()
9381 .child(
9382 Label::new(format!("{}{}", prefix.into(), row_diff))
9383 .color(Color::Muted)
9384 .size(LabelSize::Small),
9385 )
9386 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9387 }
9388
9389 let supports_jump = self
9390 .edit_prediction_provider
9391 .as_ref()
9392 .map(|provider| provider.provider.supports_jump_to_edit())
9393 .unwrap_or(true);
9394
9395 match &completion.completion {
9396 EditPrediction::Move {
9397 target, snapshot, ..
9398 } => {
9399 if !supports_jump {
9400 return None;
9401 }
9402
9403 Some(
9404 h_flex()
9405 .px_2()
9406 .gap_2()
9407 .flex_1()
9408 .child(
9409 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9410 Icon::new(IconName::ZedPredictDown)
9411 } else {
9412 Icon::new(IconName::ZedPredictUp)
9413 },
9414 )
9415 .child(Label::new("Jump to Edit")),
9416 )
9417 }
9418
9419 EditPrediction::Edit {
9420 edits,
9421 edit_preview,
9422 snapshot,
9423 display_mode: _,
9424 } => {
9425 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9426
9427 let (highlighted_edits, has_more_lines) =
9428 if let Some(edit_preview) = edit_preview.as_ref() {
9429 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9430 .first_line_preview()
9431 } else {
9432 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9433 };
9434
9435 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9436 .with_default_highlights(&style.text, highlighted_edits.highlights);
9437
9438 let preview = h_flex()
9439 .gap_1()
9440 .min_w_16()
9441 .child(styled_text)
9442 .when(has_more_lines, |parent| parent.child("…"));
9443
9444 let left = if supports_jump && first_edit_row != cursor_point.row {
9445 render_relative_row_jump("", cursor_point.row, first_edit_row)
9446 .into_any_element()
9447 } else {
9448 let icon_name =
9449 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9450 Icon::new(icon_name).into_any_element()
9451 };
9452
9453 Some(
9454 h_flex()
9455 .h_full()
9456 .flex_1()
9457 .gap_2()
9458 .pr_1()
9459 .overflow_x_hidden()
9460 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9461 .child(left)
9462 .child(preview),
9463 )
9464 }
9465 }
9466 }
9467
9468 pub fn render_context_menu(
9469 &self,
9470 style: &EditorStyle,
9471 max_height_in_lines: u32,
9472 window: &mut Window,
9473 cx: &mut Context<Editor>,
9474 ) -> Option<AnyElement> {
9475 let menu = self.context_menu.borrow();
9476 let menu = menu.as_ref()?;
9477 if !menu.visible() {
9478 return None;
9479 };
9480 Some(menu.render(style, max_height_in_lines, window, cx))
9481 }
9482
9483 fn render_context_menu_aside(
9484 &mut self,
9485 max_size: Size<Pixels>,
9486 window: &mut Window,
9487 cx: &mut Context<Editor>,
9488 ) -> Option<AnyElement> {
9489 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9490 if menu.visible() {
9491 menu.render_aside(max_size, window, cx)
9492 } else {
9493 None
9494 }
9495 })
9496 }
9497
9498 fn hide_context_menu(
9499 &mut self,
9500 window: &mut Window,
9501 cx: &mut Context<Self>,
9502 ) -> Option<CodeContextMenu> {
9503 cx.notify();
9504 self.completion_tasks.clear();
9505 let context_menu = self.context_menu.borrow_mut().take();
9506 self.stale_edit_prediction_in_menu.take();
9507 self.update_visible_edit_prediction(window, cx);
9508 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9509 && let Some(completion_provider) = &self.completion_provider
9510 {
9511 completion_provider.selection_changed(None, window, cx);
9512 }
9513 context_menu
9514 }
9515
9516 fn show_snippet_choices(
9517 &mut self,
9518 choices: &Vec<String>,
9519 selection: Range<Anchor>,
9520 cx: &mut Context<Self>,
9521 ) {
9522 let Some((_, buffer, _)) = self
9523 .buffer()
9524 .read(cx)
9525 .excerpt_containing(selection.start, cx)
9526 else {
9527 return;
9528 };
9529 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9530 else {
9531 return;
9532 };
9533 if buffer != end_buffer {
9534 log::error!("expected anchor range to have matching buffer IDs");
9535 return;
9536 }
9537
9538 let id = post_inc(&mut self.next_completion_id);
9539 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9540 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9541 CompletionsMenu::new_snippet_choices(
9542 id,
9543 true,
9544 choices,
9545 selection,
9546 buffer,
9547 snippet_sort_order,
9548 ),
9549 ));
9550 }
9551
9552 pub fn insert_snippet(
9553 &mut self,
9554 insertion_ranges: &[Range<usize>],
9555 snippet: Snippet,
9556 window: &mut Window,
9557 cx: &mut Context<Self>,
9558 ) -> Result<()> {
9559 struct Tabstop<T> {
9560 is_end_tabstop: bool,
9561 ranges: Vec<Range<T>>,
9562 choices: Option<Vec<String>>,
9563 }
9564
9565 let tabstops = self.buffer.update(cx, |buffer, cx| {
9566 let snippet_text: Arc<str> = snippet.text.clone().into();
9567 let edits = insertion_ranges
9568 .iter()
9569 .cloned()
9570 .map(|range| (range, snippet_text.clone()));
9571 let autoindent_mode = AutoindentMode::Block {
9572 original_indent_columns: Vec::new(),
9573 };
9574 buffer.edit(edits, Some(autoindent_mode), cx);
9575
9576 let snapshot = &*buffer.read(cx);
9577 let snippet = &snippet;
9578 snippet
9579 .tabstops
9580 .iter()
9581 .map(|tabstop| {
9582 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9583 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9584 });
9585 let mut tabstop_ranges = tabstop
9586 .ranges
9587 .iter()
9588 .flat_map(|tabstop_range| {
9589 let mut delta = 0_isize;
9590 insertion_ranges.iter().map(move |insertion_range| {
9591 let insertion_start = insertion_range.start as isize + delta;
9592 delta +=
9593 snippet.text.len() as isize - insertion_range.len() as isize;
9594
9595 let start = ((insertion_start + tabstop_range.start) as usize)
9596 .min(snapshot.len());
9597 let end = ((insertion_start + tabstop_range.end) as usize)
9598 .min(snapshot.len());
9599 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9600 })
9601 })
9602 .collect::<Vec<_>>();
9603 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9604
9605 Tabstop {
9606 is_end_tabstop,
9607 ranges: tabstop_ranges,
9608 choices: tabstop.choices.clone(),
9609 }
9610 })
9611 .collect::<Vec<_>>()
9612 });
9613 if let Some(tabstop) = tabstops.first() {
9614 self.change_selections(Default::default(), window, cx, |s| {
9615 // Reverse order so that the first range is the newest created selection.
9616 // Completions will use it and autoscroll will prioritize it.
9617 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9618 });
9619
9620 if let Some(choices) = &tabstop.choices
9621 && let Some(selection) = tabstop.ranges.first()
9622 {
9623 self.show_snippet_choices(choices, selection.clone(), cx)
9624 }
9625
9626 // If we're already at the last tabstop and it's at the end of the snippet,
9627 // we're done, we don't need to keep the state around.
9628 if !tabstop.is_end_tabstop {
9629 let choices = tabstops
9630 .iter()
9631 .map(|tabstop| tabstop.choices.clone())
9632 .collect();
9633
9634 let ranges = tabstops
9635 .into_iter()
9636 .map(|tabstop| tabstop.ranges)
9637 .collect::<Vec<_>>();
9638
9639 self.snippet_stack.push(SnippetState {
9640 active_index: 0,
9641 ranges,
9642 choices,
9643 });
9644 }
9645
9646 // Check whether the just-entered snippet ends with an auto-closable bracket.
9647 if self.autoclose_regions.is_empty() {
9648 let snapshot = self.buffer.read(cx).snapshot(cx);
9649 let mut all_selections = self.selections.all::<Point>(cx);
9650 for selection in &mut all_selections {
9651 let selection_head = selection.head();
9652 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9653 continue;
9654 };
9655
9656 let mut bracket_pair = None;
9657 let max_lookup_length = scope
9658 .brackets()
9659 .map(|(pair, _)| {
9660 pair.start
9661 .as_str()
9662 .chars()
9663 .count()
9664 .max(pair.end.as_str().chars().count())
9665 })
9666 .max();
9667 if let Some(max_lookup_length) = max_lookup_length {
9668 let next_text = snapshot
9669 .chars_at(selection_head)
9670 .take(max_lookup_length)
9671 .collect::<String>();
9672 let prev_text = snapshot
9673 .reversed_chars_at(selection_head)
9674 .take(max_lookup_length)
9675 .collect::<String>();
9676
9677 for (pair, enabled) in scope.brackets() {
9678 if enabled
9679 && pair.close
9680 && prev_text.starts_with(pair.start.as_str())
9681 && next_text.starts_with(pair.end.as_str())
9682 {
9683 bracket_pair = Some(pair.clone());
9684 break;
9685 }
9686 }
9687 }
9688
9689 if let Some(pair) = bracket_pair {
9690 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9691 let autoclose_enabled =
9692 self.use_autoclose && snapshot_settings.use_autoclose;
9693 if autoclose_enabled {
9694 let start = snapshot.anchor_after(selection_head);
9695 let end = snapshot.anchor_after(selection_head);
9696 self.autoclose_regions.push(AutocloseRegion {
9697 selection_id: selection.id,
9698 range: start..end,
9699 pair,
9700 });
9701 }
9702 }
9703 }
9704 }
9705 }
9706 Ok(())
9707 }
9708
9709 pub fn move_to_next_snippet_tabstop(
9710 &mut self,
9711 window: &mut Window,
9712 cx: &mut Context<Self>,
9713 ) -> bool {
9714 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9715 }
9716
9717 pub fn move_to_prev_snippet_tabstop(
9718 &mut self,
9719 window: &mut Window,
9720 cx: &mut Context<Self>,
9721 ) -> bool {
9722 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9723 }
9724
9725 pub fn move_to_snippet_tabstop(
9726 &mut self,
9727 bias: Bias,
9728 window: &mut Window,
9729 cx: &mut Context<Self>,
9730 ) -> bool {
9731 if let Some(mut snippet) = self.snippet_stack.pop() {
9732 match bias {
9733 Bias::Left => {
9734 if snippet.active_index > 0 {
9735 snippet.active_index -= 1;
9736 } else {
9737 self.snippet_stack.push(snippet);
9738 return false;
9739 }
9740 }
9741 Bias::Right => {
9742 if snippet.active_index + 1 < snippet.ranges.len() {
9743 snippet.active_index += 1;
9744 } else {
9745 self.snippet_stack.push(snippet);
9746 return false;
9747 }
9748 }
9749 }
9750 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9751 self.change_selections(Default::default(), window, cx, |s| {
9752 // Reverse order so that the first range is the newest created selection.
9753 // Completions will use it and autoscroll will prioritize it.
9754 s.select_ranges(current_ranges.iter().rev().cloned())
9755 });
9756
9757 if let Some(choices) = &snippet.choices[snippet.active_index]
9758 && let Some(selection) = current_ranges.first()
9759 {
9760 self.show_snippet_choices(choices, selection.clone(), cx);
9761 }
9762
9763 // If snippet state is not at the last tabstop, push it back on the stack
9764 if snippet.active_index + 1 < snippet.ranges.len() {
9765 self.snippet_stack.push(snippet);
9766 }
9767 return true;
9768 }
9769 }
9770
9771 false
9772 }
9773
9774 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9775 self.transact(window, cx, |this, window, cx| {
9776 this.select_all(&SelectAll, window, cx);
9777 this.insert("", window, cx);
9778 });
9779 }
9780
9781 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9782 if self.read_only(cx) {
9783 return;
9784 }
9785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9786 self.transact(window, cx, |this, window, cx| {
9787 this.select_autoclose_pair(window, cx);
9788 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9789 if !this.linked_edit_ranges.is_empty() {
9790 let selections = this.selections.all::<MultiBufferPoint>(cx);
9791 let snapshot = this.buffer.read(cx).snapshot(cx);
9792
9793 for selection in selections.iter() {
9794 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9795 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9796 if selection_start.buffer_id != selection_end.buffer_id {
9797 continue;
9798 }
9799 if let Some(ranges) =
9800 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9801 {
9802 for (buffer, entries) in ranges {
9803 linked_ranges.entry(buffer).or_default().extend(entries);
9804 }
9805 }
9806 }
9807 }
9808
9809 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9810 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9811 for selection in &mut selections {
9812 if selection.is_empty() {
9813 let old_head = selection.head();
9814 let mut new_head =
9815 movement::left(&display_map, old_head.to_display_point(&display_map))
9816 .to_point(&display_map);
9817 if let Some((buffer, line_buffer_range)) = display_map
9818 .buffer_snapshot
9819 .buffer_line_for_row(MultiBufferRow(old_head.row))
9820 {
9821 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9822 let indent_len = match indent_size.kind {
9823 IndentKind::Space => {
9824 buffer.settings_at(line_buffer_range.start, cx).tab_size
9825 }
9826 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9827 };
9828 if old_head.column <= indent_size.len && old_head.column > 0 {
9829 let indent_len = indent_len.get();
9830 new_head = cmp::min(
9831 new_head,
9832 MultiBufferPoint::new(
9833 old_head.row,
9834 ((old_head.column - 1) / indent_len) * indent_len,
9835 ),
9836 );
9837 }
9838 }
9839
9840 selection.set_head(new_head, SelectionGoal::None);
9841 }
9842 }
9843
9844 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9845 this.insert("", window, cx);
9846 let empty_str: Arc<str> = Arc::from("");
9847 for (buffer, edits) in linked_ranges {
9848 let snapshot = buffer.read(cx).snapshot();
9849 use text::ToPoint as TP;
9850
9851 let edits = edits
9852 .into_iter()
9853 .map(|range| {
9854 let end_point = TP::to_point(&range.end, &snapshot);
9855 let mut start_point = TP::to_point(&range.start, &snapshot);
9856
9857 if end_point == start_point {
9858 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9859 .saturating_sub(1);
9860 start_point =
9861 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9862 };
9863
9864 (start_point..end_point, empty_str.clone())
9865 })
9866 .sorted_by_key(|(range, _)| range.start)
9867 .collect::<Vec<_>>();
9868 buffer.update(cx, |this, cx| {
9869 this.edit(edits, None, cx);
9870 })
9871 }
9872 this.refresh_edit_prediction(true, false, window, cx);
9873 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9874 });
9875 }
9876
9877 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9878 if self.read_only(cx) {
9879 return;
9880 }
9881 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9882 self.transact(window, cx, |this, window, cx| {
9883 this.change_selections(Default::default(), window, cx, |s| {
9884 s.move_with(|map, selection| {
9885 if selection.is_empty() {
9886 let cursor = movement::right(map, selection.head());
9887 selection.end = cursor;
9888 selection.reversed = true;
9889 selection.goal = SelectionGoal::None;
9890 }
9891 })
9892 });
9893 this.insert("", window, cx);
9894 this.refresh_edit_prediction(true, false, window, cx);
9895 });
9896 }
9897
9898 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9899 if self.mode.is_single_line() {
9900 cx.propagate();
9901 return;
9902 }
9903
9904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9905 if self.move_to_prev_snippet_tabstop(window, cx) {
9906 return;
9907 }
9908 self.outdent(&Outdent, window, cx);
9909 }
9910
9911 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9912 if self.mode.is_single_line() {
9913 cx.propagate();
9914 return;
9915 }
9916
9917 if self.move_to_next_snippet_tabstop(window, cx) {
9918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9919 return;
9920 }
9921 if self.read_only(cx) {
9922 return;
9923 }
9924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9925 let mut selections = self.selections.all_adjusted(cx);
9926 let buffer = self.buffer.read(cx);
9927 let snapshot = buffer.snapshot(cx);
9928 let rows_iter = selections.iter().map(|s| s.head().row);
9929 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9930
9931 let has_some_cursor_in_whitespace = selections
9932 .iter()
9933 .filter(|selection| selection.is_empty())
9934 .any(|selection| {
9935 let cursor = selection.head();
9936 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9937 cursor.column < current_indent.len
9938 });
9939
9940 let mut edits = Vec::new();
9941 let mut prev_edited_row = 0;
9942 let mut row_delta = 0;
9943 for selection in &mut selections {
9944 if selection.start.row != prev_edited_row {
9945 row_delta = 0;
9946 }
9947 prev_edited_row = selection.end.row;
9948
9949 // If the selection is non-empty, then increase the indentation of the selected lines.
9950 if !selection.is_empty() {
9951 row_delta =
9952 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9953 continue;
9954 }
9955
9956 let cursor = selection.head();
9957 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9958 if let Some(suggested_indent) =
9959 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9960 {
9961 // Don't do anything if already at suggested indent
9962 // and there is any other cursor which is not
9963 if has_some_cursor_in_whitespace
9964 && cursor.column == current_indent.len
9965 && current_indent.len == suggested_indent.len
9966 {
9967 continue;
9968 }
9969
9970 // Adjust line and move cursor to suggested indent
9971 // if cursor is not at suggested indent
9972 if cursor.column < suggested_indent.len
9973 && cursor.column <= current_indent.len
9974 && current_indent.len <= suggested_indent.len
9975 {
9976 selection.start = Point::new(cursor.row, suggested_indent.len);
9977 selection.end = selection.start;
9978 if row_delta == 0 {
9979 edits.extend(Buffer::edit_for_indent_size_adjustment(
9980 cursor.row,
9981 current_indent,
9982 suggested_indent,
9983 ));
9984 row_delta = suggested_indent.len - current_indent.len;
9985 }
9986 continue;
9987 }
9988
9989 // If current indent is more than suggested indent
9990 // only move cursor to current indent and skip indent
9991 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9992 selection.start = Point::new(cursor.row, current_indent.len);
9993 selection.end = selection.start;
9994 continue;
9995 }
9996 }
9997
9998 // Otherwise, insert a hard or soft tab.
9999 let settings = buffer.language_settings_at(cursor, cx);
10000 let tab_size = if settings.hard_tabs {
10001 IndentSize::tab()
10002 } else {
10003 let tab_size = settings.tab_size.get();
10004 let indent_remainder = snapshot
10005 .text_for_range(Point::new(cursor.row, 0)..cursor)
10006 .flat_map(str::chars)
10007 .fold(row_delta % tab_size, |counter: u32, c| {
10008 if c == '\t' {
10009 0
10010 } else {
10011 (counter + 1) % tab_size
10012 }
10013 });
10014
10015 let chars_to_next_tab_stop = tab_size - indent_remainder;
10016 IndentSize::spaces(chars_to_next_tab_stop)
10017 };
10018 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10019 selection.end = selection.start;
10020 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10021 row_delta += tab_size.len;
10022 }
10023
10024 self.transact(window, cx, |this, window, cx| {
10025 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10026 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10027 this.refresh_edit_prediction(true, false, window, cx);
10028 });
10029 }
10030
10031 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10032 if self.read_only(cx) {
10033 return;
10034 }
10035 if self.mode.is_single_line() {
10036 cx.propagate();
10037 return;
10038 }
10039
10040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10041 let mut selections = self.selections.all::<Point>(cx);
10042 let mut prev_edited_row = 0;
10043 let mut row_delta = 0;
10044 let mut edits = Vec::new();
10045 let buffer = self.buffer.read(cx);
10046 let snapshot = buffer.snapshot(cx);
10047 for selection in &mut selections {
10048 if selection.start.row != prev_edited_row {
10049 row_delta = 0;
10050 }
10051 prev_edited_row = selection.end.row;
10052
10053 row_delta =
10054 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10055 }
10056
10057 self.transact(window, cx, |this, window, cx| {
10058 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10059 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10060 });
10061 }
10062
10063 fn indent_selection(
10064 buffer: &MultiBuffer,
10065 snapshot: &MultiBufferSnapshot,
10066 selection: &mut Selection<Point>,
10067 edits: &mut Vec<(Range<Point>, String)>,
10068 delta_for_start_row: u32,
10069 cx: &App,
10070 ) -> u32 {
10071 let settings = buffer.language_settings_at(selection.start, cx);
10072 let tab_size = settings.tab_size.get();
10073 let indent_kind = if settings.hard_tabs {
10074 IndentKind::Tab
10075 } else {
10076 IndentKind::Space
10077 };
10078 let mut start_row = selection.start.row;
10079 let mut end_row = selection.end.row + 1;
10080
10081 // If a selection ends at the beginning of a line, don't indent
10082 // that last line.
10083 if selection.end.column == 0 && selection.end.row > selection.start.row {
10084 end_row -= 1;
10085 }
10086
10087 // Avoid re-indenting a row that has already been indented by a
10088 // previous selection, but still update this selection's column
10089 // to reflect that indentation.
10090 if delta_for_start_row > 0 {
10091 start_row += 1;
10092 selection.start.column += delta_for_start_row;
10093 if selection.end.row == selection.start.row {
10094 selection.end.column += delta_for_start_row;
10095 }
10096 }
10097
10098 let mut delta_for_end_row = 0;
10099 let has_multiple_rows = start_row + 1 != end_row;
10100 for row in start_row..end_row {
10101 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10102 let indent_delta = match (current_indent.kind, indent_kind) {
10103 (IndentKind::Space, IndentKind::Space) => {
10104 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10105 IndentSize::spaces(columns_to_next_tab_stop)
10106 }
10107 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10108 (_, IndentKind::Tab) => IndentSize::tab(),
10109 };
10110
10111 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10112 0
10113 } else {
10114 selection.start.column
10115 };
10116 let row_start = Point::new(row, start);
10117 edits.push((
10118 row_start..row_start,
10119 indent_delta.chars().collect::<String>(),
10120 ));
10121
10122 // Update this selection's endpoints to reflect the indentation.
10123 if row == selection.start.row {
10124 selection.start.column += indent_delta.len;
10125 }
10126 if row == selection.end.row {
10127 selection.end.column += indent_delta.len;
10128 delta_for_end_row = indent_delta.len;
10129 }
10130 }
10131
10132 if selection.start.row == selection.end.row {
10133 delta_for_start_row + delta_for_end_row
10134 } else {
10135 delta_for_end_row
10136 }
10137 }
10138
10139 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10140 if self.read_only(cx) {
10141 return;
10142 }
10143 if self.mode.is_single_line() {
10144 cx.propagate();
10145 return;
10146 }
10147
10148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10150 let selections = self.selections.all::<Point>(cx);
10151 let mut deletion_ranges = Vec::new();
10152 let mut last_outdent = None;
10153 {
10154 let buffer = self.buffer.read(cx);
10155 let snapshot = buffer.snapshot(cx);
10156 for selection in &selections {
10157 let settings = buffer.language_settings_at(selection.start, cx);
10158 let tab_size = settings.tab_size.get();
10159 let mut rows = selection.spanned_rows(false, &display_map);
10160
10161 // Avoid re-outdenting a row that has already been outdented by a
10162 // previous selection.
10163 if let Some(last_row) = last_outdent
10164 && last_row == rows.start
10165 {
10166 rows.start = rows.start.next_row();
10167 }
10168 let has_multiple_rows = rows.len() > 1;
10169 for row in rows.iter_rows() {
10170 let indent_size = snapshot.indent_size_for_line(row);
10171 if indent_size.len > 0 {
10172 let deletion_len = match indent_size.kind {
10173 IndentKind::Space => {
10174 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10175 if columns_to_prev_tab_stop == 0 {
10176 tab_size
10177 } else {
10178 columns_to_prev_tab_stop
10179 }
10180 }
10181 IndentKind::Tab => 1,
10182 };
10183 let start = if has_multiple_rows
10184 || deletion_len > selection.start.column
10185 || indent_size.len < selection.start.column
10186 {
10187 0
10188 } else {
10189 selection.start.column - deletion_len
10190 };
10191 deletion_ranges.push(
10192 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10193 );
10194 last_outdent = Some(row);
10195 }
10196 }
10197 }
10198 }
10199
10200 self.transact(window, cx, |this, window, cx| {
10201 this.buffer.update(cx, |buffer, cx| {
10202 let empty_str: Arc<str> = Arc::default();
10203 buffer.edit(
10204 deletion_ranges
10205 .into_iter()
10206 .map(|range| (range, empty_str.clone())),
10207 None,
10208 cx,
10209 );
10210 });
10211 let selections = this.selections.all::<usize>(cx);
10212 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10213 });
10214 }
10215
10216 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10217 if self.read_only(cx) {
10218 return;
10219 }
10220 if self.mode.is_single_line() {
10221 cx.propagate();
10222 return;
10223 }
10224
10225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10226 let selections = self
10227 .selections
10228 .all::<usize>(cx)
10229 .into_iter()
10230 .map(|s| s.range());
10231
10232 self.transact(window, cx, |this, window, cx| {
10233 this.buffer.update(cx, |buffer, cx| {
10234 buffer.autoindent_ranges(selections, cx);
10235 });
10236 let selections = this.selections.all::<usize>(cx);
10237 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10238 });
10239 }
10240
10241 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10244 let selections = self.selections.all::<Point>(cx);
10245
10246 let mut new_cursors = Vec::new();
10247 let mut edit_ranges = Vec::new();
10248 let mut selections = selections.iter().peekable();
10249 while let Some(selection) = selections.next() {
10250 let mut rows = selection.spanned_rows(false, &display_map);
10251 let goal_display_column = selection.head().to_display_point(&display_map).column();
10252
10253 // Accumulate contiguous regions of rows that we want to delete.
10254 while let Some(next_selection) = selections.peek() {
10255 let next_rows = next_selection.spanned_rows(false, &display_map);
10256 if next_rows.start <= rows.end {
10257 rows.end = next_rows.end;
10258 selections.next().unwrap();
10259 } else {
10260 break;
10261 }
10262 }
10263
10264 let buffer = &display_map.buffer_snapshot;
10265 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10266 let edit_end;
10267 let cursor_buffer_row;
10268 if buffer.max_point().row >= rows.end.0 {
10269 // If there's a line after the range, delete the \n from the end of the row range
10270 // and position the cursor on the next line.
10271 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10272 cursor_buffer_row = rows.end;
10273 } else {
10274 // If there isn't a line after the range, delete the \n from the line before the
10275 // start of the row range and position the cursor there.
10276 edit_start = edit_start.saturating_sub(1);
10277 edit_end = buffer.len();
10278 cursor_buffer_row = rows.start.previous_row();
10279 }
10280
10281 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10282 *cursor.column_mut() =
10283 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10284
10285 new_cursors.push((
10286 selection.id,
10287 buffer.anchor_after(cursor.to_point(&display_map)),
10288 ));
10289 edit_ranges.push(edit_start..edit_end);
10290 }
10291
10292 self.transact(window, cx, |this, window, cx| {
10293 let buffer = this.buffer.update(cx, |buffer, cx| {
10294 let empty_str: Arc<str> = Arc::default();
10295 buffer.edit(
10296 edit_ranges
10297 .into_iter()
10298 .map(|range| (range, empty_str.clone())),
10299 None,
10300 cx,
10301 );
10302 buffer.snapshot(cx)
10303 });
10304 let new_selections = new_cursors
10305 .into_iter()
10306 .map(|(id, cursor)| {
10307 let cursor = cursor.to_point(&buffer);
10308 Selection {
10309 id,
10310 start: cursor,
10311 end: cursor,
10312 reversed: false,
10313 goal: SelectionGoal::None,
10314 }
10315 })
10316 .collect();
10317
10318 this.change_selections(Default::default(), window, cx, |s| {
10319 s.select(new_selections);
10320 });
10321 });
10322 }
10323
10324 pub fn join_lines_impl(
10325 &mut self,
10326 insert_whitespace: bool,
10327 window: &mut Window,
10328 cx: &mut Context<Self>,
10329 ) {
10330 if self.read_only(cx) {
10331 return;
10332 }
10333 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10334 for selection in self.selections.all::<Point>(cx) {
10335 let start = MultiBufferRow(selection.start.row);
10336 // Treat single line selections as if they include the next line. Otherwise this action
10337 // would do nothing for single line selections individual cursors.
10338 let end = if selection.start.row == selection.end.row {
10339 MultiBufferRow(selection.start.row + 1)
10340 } else {
10341 MultiBufferRow(selection.end.row)
10342 };
10343
10344 if let Some(last_row_range) = row_ranges.last_mut()
10345 && start <= last_row_range.end
10346 {
10347 last_row_range.end = end;
10348 continue;
10349 }
10350 row_ranges.push(start..end);
10351 }
10352
10353 let snapshot = self.buffer.read(cx).snapshot(cx);
10354 let mut cursor_positions = Vec::new();
10355 for row_range in &row_ranges {
10356 let anchor = snapshot.anchor_before(Point::new(
10357 row_range.end.previous_row().0,
10358 snapshot.line_len(row_range.end.previous_row()),
10359 ));
10360 cursor_positions.push(anchor..anchor);
10361 }
10362
10363 self.transact(window, cx, |this, window, cx| {
10364 for row_range in row_ranges.into_iter().rev() {
10365 for row in row_range.iter_rows().rev() {
10366 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10367 let next_line_row = row.next_row();
10368 let indent = snapshot.indent_size_for_line(next_line_row);
10369 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10370
10371 let replace =
10372 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10373 " "
10374 } else {
10375 ""
10376 };
10377
10378 this.buffer.update(cx, |buffer, cx| {
10379 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10380 });
10381 }
10382 }
10383
10384 this.change_selections(Default::default(), window, cx, |s| {
10385 s.select_anchor_ranges(cursor_positions)
10386 });
10387 });
10388 }
10389
10390 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10392 self.join_lines_impl(true, window, cx);
10393 }
10394
10395 pub fn sort_lines_case_sensitive(
10396 &mut self,
10397 _: &SortLinesCaseSensitive,
10398 window: &mut Window,
10399 cx: &mut Context<Self>,
10400 ) {
10401 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10402 }
10403
10404 pub fn sort_lines_by_length(
10405 &mut self,
10406 _: &SortLinesByLength,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) {
10410 self.manipulate_immutable_lines(window, cx, |lines| {
10411 lines.sort_by_key(|&line| line.chars().count())
10412 })
10413 }
10414
10415 pub fn sort_lines_case_insensitive(
10416 &mut self,
10417 _: &SortLinesCaseInsensitive,
10418 window: &mut Window,
10419 cx: &mut Context<Self>,
10420 ) {
10421 self.manipulate_immutable_lines(window, cx, |lines| {
10422 lines.sort_by_key(|line| line.to_lowercase())
10423 })
10424 }
10425
10426 pub fn unique_lines_case_insensitive(
10427 &mut self,
10428 _: &UniqueLinesCaseInsensitive,
10429 window: &mut Window,
10430 cx: &mut Context<Self>,
10431 ) {
10432 self.manipulate_immutable_lines(window, cx, |lines| {
10433 let mut seen = HashSet::default();
10434 lines.retain(|line| seen.insert(line.to_lowercase()));
10435 })
10436 }
10437
10438 pub fn unique_lines_case_sensitive(
10439 &mut self,
10440 _: &UniqueLinesCaseSensitive,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 self.manipulate_immutable_lines(window, cx, |lines| {
10445 let mut seen = HashSet::default();
10446 lines.retain(|line| seen.insert(*line));
10447 })
10448 }
10449
10450 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10451 let snapshot = self.buffer.read(cx).snapshot(cx);
10452 for selection in self.selections.disjoint_anchors().iter() {
10453 if snapshot
10454 .language_at(selection.start)
10455 .and_then(|lang| lang.config().wrap_characters.as_ref())
10456 .is_some()
10457 {
10458 return true;
10459 }
10460 }
10461 false
10462 }
10463
10464 fn wrap_selections_in_tag(
10465 &mut self,
10466 _: &WrapSelectionsInTag,
10467 window: &mut Window,
10468 cx: &mut Context<Self>,
10469 ) {
10470 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10471
10472 let snapshot = self.buffer.read(cx).snapshot(cx);
10473
10474 let mut edits = Vec::new();
10475 let mut boundaries = Vec::new();
10476
10477 for selection in self.selections.all::<Point>(cx).iter() {
10478 let Some(wrap_config) = snapshot
10479 .language_at(selection.start)
10480 .and_then(|lang| lang.config().wrap_characters.clone())
10481 else {
10482 continue;
10483 };
10484
10485 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10486 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10487
10488 let start_before = snapshot.anchor_before(selection.start);
10489 let end_after = snapshot.anchor_after(selection.end);
10490
10491 edits.push((start_before..start_before, open_tag));
10492 edits.push((end_after..end_after, close_tag));
10493
10494 boundaries.push((
10495 start_before,
10496 end_after,
10497 wrap_config.start_prefix.len(),
10498 wrap_config.end_suffix.len(),
10499 ));
10500 }
10501
10502 if edits.is_empty() {
10503 return;
10504 }
10505
10506 self.transact(window, cx, |this, window, cx| {
10507 let buffer = this.buffer.update(cx, |buffer, cx| {
10508 buffer.edit(edits, None, cx);
10509 buffer.snapshot(cx)
10510 });
10511
10512 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10513 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10514 boundaries.into_iter()
10515 {
10516 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10517 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10518 new_selections.push(open_offset..open_offset);
10519 new_selections.push(close_offset..close_offset);
10520 }
10521
10522 this.change_selections(Default::default(), window, cx, |s| {
10523 s.select_ranges(new_selections);
10524 });
10525
10526 this.request_autoscroll(Autoscroll::fit(), cx);
10527 });
10528 }
10529
10530 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10531 let Some(project) = self.project.clone() else {
10532 return;
10533 };
10534 self.reload(project, window, cx)
10535 .detach_and_notify_err(window, cx);
10536 }
10537
10538 pub fn restore_file(
10539 &mut self,
10540 _: &::git::RestoreFile,
10541 window: &mut Window,
10542 cx: &mut Context<Self>,
10543 ) {
10544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10545 let mut buffer_ids = HashSet::default();
10546 let snapshot = self.buffer().read(cx).snapshot(cx);
10547 for selection in self.selections.all::<usize>(cx) {
10548 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10549 }
10550
10551 let buffer = self.buffer().read(cx);
10552 let ranges = buffer_ids
10553 .into_iter()
10554 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10555 .collect::<Vec<_>>();
10556
10557 self.restore_hunks_in_ranges(ranges, window, cx);
10558 }
10559
10560 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10561 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10562 let selections = self
10563 .selections
10564 .all(cx)
10565 .into_iter()
10566 .map(|s| s.range())
10567 .collect();
10568 self.restore_hunks_in_ranges(selections, window, cx);
10569 }
10570
10571 pub fn restore_hunks_in_ranges(
10572 &mut self,
10573 ranges: Vec<Range<Point>>,
10574 window: &mut Window,
10575 cx: &mut Context<Editor>,
10576 ) {
10577 let mut revert_changes = HashMap::default();
10578 let chunk_by = self
10579 .snapshot(window, cx)
10580 .hunks_for_ranges(ranges)
10581 .into_iter()
10582 .chunk_by(|hunk| hunk.buffer_id);
10583 for (buffer_id, hunks) in &chunk_by {
10584 let hunks = hunks.collect::<Vec<_>>();
10585 for hunk in &hunks {
10586 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10587 }
10588 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10589 }
10590 drop(chunk_by);
10591 if !revert_changes.is_empty() {
10592 self.transact(window, cx, |editor, window, cx| {
10593 editor.restore(revert_changes, window, cx);
10594 });
10595 }
10596 }
10597
10598 pub fn open_active_item_in_terminal(
10599 &mut self,
10600 _: &OpenInTerminal,
10601 window: &mut Window,
10602 cx: &mut Context<Self>,
10603 ) {
10604 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10605 let project_path = buffer.read(cx).project_path(cx)?;
10606 let project = self.project()?.read(cx);
10607 let entry = project.entry_for_path(&project_path, cx)?;
10608 let parent = match &entry.canonical_path {
10609 Some(canonical_path) => canonical_path.to_path_buf(),
10610 None => project.absolute_path(&project_path, cx)?,
10611 }
10612 .parent()?
10613 .to_path_buf();
10614 Some(parent)
10615 }) {
10616 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10617 }
10618 }
10619
10620 fn set_breakpoint_context_menu(
10621 &mut self,
10622 display_row: DisplayRow,
10623 position: Option<Anchor>,
10624 clicked_point: gpui::Point<Pixels>,
10625 window: &mut Window,
10626 cx: &mut Context<Self>,
10627 ) {
10628 let source = self
10629 .buffer
10630 .read(cx)
10631 .snapshot(cx)
10632 .anchor_before(Point::new(display_row.0, 0u32));
10633
10634 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10635
10636 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10637 self,
10638 source,
10639 clicked_point,
10640 context_menu,
10641 window,
10642 cx,
10643 );
10644 }
10645
10646 fn add_edit_breakpoint_block(
10647 &mut self,
10648 anchor: Anchor,
10649 breakpoint: &Breakpoint,
10650 edit_action: BreakpointPromptEditAction,
10651 window: &mut Window,
10652 cx: &mut Context<Self>,
10653 ) {
10654 let weak_editor = cx.weak_entity();
10655 let bp_prompt = cx.new(|cx| {
10656 BreakpointPromptEditor::new(
10657 weak_editor,
10658 anchor,
10659 breakpoint.clone(),
10660 edit_action,
10661 window,
10662 cx,
10663 )
10664 });
10665
10666 let height = bp_prompt.update(cx, |this, cx| {
10667 this.prompt
10668 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10669 });
10670 let cloned_prompt = bp_prompt.clone();
10671 let blocks = vec![BlockProperties {
10672 style: BlockStyle::Sticky,
10673 placement: BlockPlacement::Above(anchor),
10674 height: Some(height),
10675 render: Arc::new(move |cx| {
10676 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10677 cloned_prompt.clone().into_any_element()
10678 }),
10679 priority: 0,
10680 }];
10681
10682 let focus_handle = bp_prompt.focus_handle(cx);
10683 window.focus(&focus_handle);
10684
10685 let block_ids = self.insert_blocks(blocks, None, cx);
10686 bp_prompt.update(cx, |prompt, _| {
10687 prompt.add_block_ids(block_ids);
10688 });
10689 }
10690
10691 pub(crate) fn breakpoint_at_row(
10692 &self,
10693 row: u32,
10694 window: &mut Window,
10695 cx: &mut Context<Self>,
10696 ) -> Option<(Anchor, Breakpoint)> {
10697 let snapshot = self.snapshot(window, cx);
10698 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10699
10700 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10701 }
10702
10703 pub(crate) fn breakpoint_at_anchor(
10704 &self,
10705 breakpoint_position: Anchor,
10706 snapshot: &EditorSnapshot,
10707 cx: &mut Context<Self>,
10708 ) -> Option<(Anchor, Breakpoint)> {
10709 let buffer = self
10710 .buffer
10711 .read(cx)
10712 .buffer_for_anchor(breakpoint_position, cx)?;
10713
10714 let enclosing_excerpt = breakpoint_position.excerpt_id;
10715 let buffer_snapshot = buffer.read(cx).snapshot();
10716
10717 let row = buffer_snapshot
10718 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10719 .row;
10720
10721 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10722 let anchor_end = snapshot
10723 .buffer_snapshot
10724 .anchor_after(Point::new(row, line_len));
10725
10726 self.breakpoint_store
10727 .as_ref()?
10728 .read_with(cx, |breakpoint_store, cx| {
10729 breakpoint_store
10730 .breakpoints(
10731 &buffer,
10732 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10733 &buffer_snapshot,
10734 cx,
10735 )
10736 .next()
10737 .and_then(|(bp, _)| {
10738 let breakpoint_row = buffer_snapshot
10739 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10740 .row;
10741
10742 if breakpoint_row == row {
10743 snapshot
10744 .buffer_snapshot
10745 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10746 .map(|position| (position, bp.bp.clone()))
10747 } else {
10748 None
10749 }
10750 })
10751 })
10752 }
10753
10754 pub fn edit_log_breakpoint(
10755 &mut self,
10756 _: &EditLogBreakpoint,
10757 window: &mut Window,
10758 cx: &mut Context<Self>,
10759 ) {
10760 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10761 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10762 message: None,
10763 state: BreakpointState::Enabled,
10764 condition: None,
10765 hit_condition: None,
10766 });
10767
10768 self.add_edit_breakpoint_block(
10769 anchor,
10770 &breakpoint,
10771 BreakpointPromptEditAction::Log,
10772 window,
10773 cx,
10774 );
10775 }
10776 }
10777
10778 fn breakpoints_at_cursors(
10779 &self,
10780 window: &mut Window,
10781 cx: &mut Context<Self>,
10782 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10783 let snapshot = self.snapshot(window, cx);
10784 let cursors = self
10785 .selections
10786 .disjoint_anchors()
10787 .iter()
10788 .map(|selection| {
10789 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10790
10791 let breakpoint_position = self
10792 .breakpoint_at_row(cursor_position.row, window, cx)
10793 .map(|bp| bp.0)
10794 .unwrap_or_else(|| {
10795 snapshot
10796 .display_snapshot
10797 .buffer_snapshot
10798 .anchor_after(Point::new(cursor_position.row, 0))
10799 });
10800
10801 let breakpoint = self
10802 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10803 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10804
10805 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10806 })
10807 // 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.
10808 .collect::<HashMap<Anchor, _>>();
10809
10810 cursors.into_iter().collect()
10811 }
10812
10813 pub fn enable_breakpoint(
10814 &mut self,
10815 _: &crate::actions::EnableBreakpoint,
10816 window: &mut Window,
10817 cx: &mut Context<Self>,
10818 ) {
10819 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10820 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10821 continue;
10822 };
10823 self.edit_breakpoint_at_anchor(
10824 anchor,
10825 breakpoint,
10826 BreakpointEditAction::InvertState,
10827 cx,
10828 );
10829 }
10830 }
10831
10832 pub fn disable_breakpoint(
10833 &mut self,
10834 _: &crate::actions::DisableBreakpoint,
10835 window: &mut Window,
10836 cx: &mut Context<Self>,
10837 ) {
10838 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10839 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10840 continue;
10841 };
10842 self.edit_breakpoint_at_anchor(
10843 anchor,
10844 breakpoint,
10845 BreakpointEditAction::InvertState,
10846 cx,
10847 );
10848 }
10849 }
10850
10851 pub fn toggle_breakpoint(
10852 &mut self,
10853 _: &crate::actions::ToggleBreakpoint,
10854 window: &mut Window,
10855 cx: &mut Context<Self>,
10856 ) {
10857 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10858 if let Some(breakpoint) = breakpoint {
10859 self.edit_breakpoint_at_anchor(
10860 anchor,
10861 breakpoint,
10862 BreakpointEditAction::Toggle,
10863 cx,
10864 );
10865 } else {
10866 self.edit_breakpoint_at_anchor(
10867 anchor,
10868 Breakpoint::new_standard(),
10869 BreakpointEditAction::Toggle,
10870 cx,
10871 );
10872 }
10873 }
10874 }
10875
10876 pub fn edit_breakpoint_at_anchor(
10877 &mut self,
10878 breakpoint_position: Anchor,
10879 breakpoint: Breakpoint,
10880 edit_action: BreakpointEditAction,
10881 cx: &mut Context<Self>,
10882 ) {
10883 let Some(breakpoint_store) = &self.breakpoint_store else {
10884 return;
10885 };
10886
10887 let Some(buffer) = self
10888 .buffer
10889 .read(cx)
10890 .buffer_for_anchor(breakpoint_position, cx)
10891 else {
10892 return;
10893 };
10894
10895 breakpoint_store.update(cx, |breakpoint_store, cx| {
10896 breakpoint_store.toggle_breakpoint(
10897 buffer,
10898 BreakpointWithPosition {
10899 position: breakpoint_position.text_anchor,
10900 bp: breakpoint,
10901 },
10902 edit_action,
10903 cx,
10904 );
10905 });
10906
10907 cx.notify();
10908 }
10909
10910 #[cfg(any(test, feature = "test-support"))]
10911 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10912 self.breakpoint_store.clone()
10913 }
10914
10915 pub fn prepare_restore_change(
10916 &self,
10917 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10918 hunk: &MultiBufferDiffHunk,
10919 cx: &mut App,
10920 ) -> Option<()> {
10921 if hunk.is_created_file() {
10922 return None;
10923 }
10924 let buffer = self.buffer.read(cx);
10925 let diff = buffer.diff_for(hunk.buffer_id)?;
10926 let buffer = buffer.buffer(hunk.buffer_id)?;
10927 let buffer = buffer.read(cx);
10928 let original_text = diff
10929 .read(cx)
10930 .base_text()
10931 .as_rope()
10932 .slice(hunk.diff_base_byte_range.clone());
10933 let buffer_snapshot = buffer.snapshot();
10934 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10935 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10936 probe
10937 .0
10938 .start
10939 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10940 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10941 }) {
10942 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10943 Some(())
10944 } else {
10945 None
10946 }
10947 }
10948
10949 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10950 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10951 }
10952
10953 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10954 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10955 }
10956
10957 fn manipulate_lines<M>(
10958 &mut self,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 mut manipulate: M,
10962 ) where
10963 M: FnMut(&str) -> LineManipulationResult,
10964 {
10965 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10966
10967 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10968 let buffer = self.buffer.read(cx).snapshot(cx);
10969
10970 let mut edits = Vec::new();
10971
10972 let selections = self.selections.all::<Point>(cx);
10973 let mut selections = selections.iter().peekable();
10974 let mut contiguous_row_selections = Vec::new();
10975 let mut new_selections = Vec::new();
10976 let mut added_lines = 0;
10977 let mut removed_lines = 0;
10978
10979 while let Some(selection) = selections.next() {
10980 let (start_row, end_row) = consume_contiguous_rows(
10981 &mut contiguous_row_selections,
10982 selection,
10983 &display_map,
10984 &mut selections,
10985 );
10986
10987 let start_point = Point::new(start_row.0, 0);
10988 let end_point = Point::new(
10989 end_row.previous_row().0,
10990 buffer.line_len(end_row.previous_row()),
10991 );
10992 let text = buffer
10993 .text_for_range(start_point..end_point)
10994 .collect::<String>();
10995
10996 let LineManipulationResult {
10997 new_text,
10998 line_count_before,
10999 line_count_after,
11000 } = manipulate(&text);
11001
11002 edits.push((start_point..end_point, new_text));
11003
11004 // Selections must change based on added and removed line count
11005 let start_row =
11006 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11007 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11008 new_selections.push(Selection {
11009 id: selection.id,
11010 start: start_row,
11011 end: end_row,
11012 goal: SelectionGoal::None,
11013 reversed: selection.reversed,
11014 });
11015
11016 if line_count_after > line_count_before {
11017 added_lines += line_count_after - line_count_before;
11018 } else if line_count_before > line_count_after {
11019 removed_lines += line_count_before - line_count_after;
11020 }
11021 }
11022
11023 self.transact(window, cx, |this, window, cx| {
11024 let buffer = this.buffer.update(cx, |buffer, cx| {
11025 buffer.edit(edits, None, cx);
11026 buffer.snapshot(cx)
11027 });
11028
11029 // Recalculate offsets on newly edited buffer
11030 let new_selections = new_selections
11031 .iter()
11032 .map(|s| {
11033 let start_point = Point::new(s.start.0, 0);
11034 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11035 Selection {
11036 id: s.id,
11037 start: buffer.point_to_offset(start_point),
11038 end: buffer.point_to_offset(end_point),
11039 goal: s.goal,
11040 reversed: s.reversed,
11041 }
11042 })
11043 .collect();
11044
11045 this.change_selections(Default::default(), window, cx, |s| {
11046 s.select(new_selections);
11047 });
11048
11049 this.request_autoscroll(Autoscroll::fit(), cx);
11050 });
11051 }
11052
11053 fn manipulate_immutable_lines<Fn>(
11054 &mut self,
11055 window: &mut Window,
11056 cx: &mut Context<Self>,
11057 mut callback: Fn,
11058 ) where
11059 Fn: FnMut(&mut Vec<&str>),
11060 {
11061 self.manipulate_lines(window, cx, |text| {
11062 let mut lines: Vec<&str> = text.split('\n').collect();
11063 let line_count_before = lines.len();
11064
11065 callback(&mut lines);
11066
11067 LineManipulationResult {
11068 new_text: lines.join("\n"),
11069 line_count_before,
11070 line_count_after: lines.len(),
11071 }
11072 });
11073 }
11074
11075 fn manipulate_mutable_lines<Fn>(
11076 &mut self,
11077 window: &mut Window,
11078 cx: &mut Context<Self>,
11079 mut callback: Fn,
11080 ) where
11081 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11082 {
11083 self.manipulate_lines(window, cx, |text| {
11084 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11085 let line_count_before = lines.len();
11086
11087 callback(&mut lines);
11088
11089 LineManipulationResult {
11090 new_text: lines.join("\n"),
11091 line_count_before,
11092 line_count_after: lines.len(),
11093 }
11094 });
11095 }
11096
11097 pub fn convert_indentation_to_spaces(
11098 &mut self,
11099 _: &ConvertIndentationToSpaces,
11100 window: &mut Window,
11101 cx: &mut Context<Self>,
11102 ) {
11103 let settings = self.buffer.read(cx).language_settings(cx);
11104 let tab_size = settings.tab_size.get() as usize;
11105
11106 self.manipulate_mutable_lines(window, cx, |lines| {
11107 // Allocates a reasonably sized scratch buffer once for the whole loop
11108 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11109 // Avoids recomputing spaces that could be inserted many times
11110 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11111 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11112 .collect();
11113
11114 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11115 let mut chars = line.as_ref().chars();
11116 let mut col = 0;
11117 let mut changed = false;
11118
11119 for ch in chars.by_ref() {
11120 match ch {
11121 ' ' => {
11122 reindented_line.push(' ');
11123 col += 1;
11124 }
11125 '\t' => {
11126 // \t are converted to spaces depending on the current column
11127 let spaces_len = tab_size - (col % tab_size);
11128 reindented_line.extend(&space_cache[spaces_len - 1]);
11129 col += spaces_len;
11130 changed = true;
11131 }
11132 _ => {
11133 // If we dont append before break, the character is consumed
11134 reindented_line.push(ch);
11135 break;
11136 }
11137 }
11138 }
11139
11140 if !changed {
11141 reindented_line.clear();
11142 continue;
11143 }
11144 // Append the rest of the line and replace old reference with new one
11145 reindented_line.extend(chars);
11146 *line = Cow::Owned(reindented_line.clone());
11147 reindented_line.clear();
11148 }
11149 });
11150 }
11151
11152 pub fn convert_indentation_to_tabs(
11153 &mut self,
11154 _: &ConvertIndentationToTabs,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 ) {
11158 let settings = self.buffer.read(cx).language_settings(cx);
11159 let tab_size = settings.tab_size.get() as usize;
11160
11161 self.manipulate_mutable_lines(window, cx, |lines| {
11162 // Allocates a reasonably sized buffer once for the whole loop
11163 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11164 // Avoids recomputing spaces that could be inserted many times
11165 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11166 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11167 .collect();
11168
11169 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11170 let mut chars = line.chars();
11171 let mut spaces_count = 0;
11172 let mut first_non_indent_char = None;
11173 let mut changed = false;
11174
11175 for ch in chars.by_ref() {
11176 match ch {
11177 ' ' => {
11178 // Keep track of spaces. Append \t when we reach tab_size
11179 spaces_count += 1;
11180 changed = true;
11181 if spaces_count == tab_size {
11182 reindented_line.push('\t');
11183 spaces_count = 0;
11184 }
11185 }
11186 '\t' => {
11187 reindented_line.push('\t');
11188 spaces_count = 0;
11189 }
11190 _ => {
11191 // Dont append it yet, we might have remaining spaces
11192 first_non_indent_char = Some(ch);
11193 break;
11194 }
11195 }
11196 }
11197
11198 if !changed {
11199 reindented_line.clear();
11200 continue;
11201 }
11202 // Remaining spaces that didn't make a full tab stop
11203 if spaces_count > 0 {
11204 reindented_line.extend(&space_cache[spaces_count - 1]);
11205 }
11206 // If we consume an extra character that was not indentation, add it back
11207 if let Some(extra_char) = first_non_indent_char {
11208 reindented_line.push(extra_char);
11209 }
11210 // Append the rest of the line and replace old reference with new one
11211 reindented_line.extend(chars);
11212 *line = Cow::Owned(reindented_line.clone());
11213 reindented_line.clear();
11214 }
11215 });
11216 }
11217
11218 pub fn convert_to_upper_case(
11219 &mut self,
11220 _: &ConvertToUpperCase,
11221 window: &mut Window,
11222 cx: &mut Context<Self>,
11223 ) {
11224 self.manipulate_text(window, cx, |text| text.to_uppercase())
11225 }
11226
11227 pub fn convert_to_lower_case(
11228 &mut self,
11229 _: &ConvertToLowerCase,
11230 window: &mut Window,
11231 cx: &mut Context<Self>,
11232 ) {
11233 self.manipulate_text(window, cx, |text| text.to_lowercase())
11234 }
11235
11236 pub fn convert_to_title_case(
11237 &mut self,
11238 _: &ConvertToTitleCase,
11239 window: &mut Window,
11240 cx: &mut Context<Self>,
11241 ) {
11242 self.manipulate_text(window, cx, |text| {
11243 text.split('\n')
11244 .map(|line| line.to_case(Case::Title))
11245 .join("\n")
11246 })
11247 }
11248
11249 pub fn convert_to_snake_case(
11250 &mut self,
11251 _: &ConvertToSnakeCase,
11252 window: &mut Window,
11253 cx: &mut Context<Self>,
11254 ) {
11255 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11256 }
11257
11258 pub fn convert_to_kebab_case(
11259 &mut self,
11260 _: &ConvertToKebabCase,
11261 window: &mut Window,
11262 cx: &mut Context<Self>,
11263 ) {
11264 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11265 }
11266
11267 pub fn convert_to_upper_camel_case(
11268 &mut self,
11269 _: &ConvertToUpperCamelCase,
11270 window: &mut Window,
11271 cx: &mut Context<Self>,
11272 ) {
11273 self.manipulate_text(window, cx, |text| {
11274 text.split('\n')
11275 .map(|line| line.to_case(Case::UpperCamel))
11276 .join("\n")
11277 })
11278 }
11279
11280 pub fn convert_to_lower_camel_case(
11281 &mut self,
11282 _: &ConvertToLowerCamelCase,
11283 window: &mut Window,
11284 cx: &mut Context<Self>,
11285 ) {
11286 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11287 }
11288
11289 pub fn convert_to_opposite_case(
11290 &mut self,
11291 _: &ConvertToOppositeCase,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 self.manipulate_text(window, cx, |text| {
11296 text.chars()
11297 .fold(String::with_capacity(text.len()), |mut t, c| {
11298 if c.is_uppercase() {
11299 t.extend(c.to_lowercase());
11300 } else {
11301 t.extend(c.to_uppercase());
11302 }
11303 t
11304 })
11305 })
11306 }
11307
11308 pub fn convert_to_sentence_case(
11309 &mut self,
11310 _: &ConvertToSentenceCase,
11311 window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) {
11314 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11315 }
11316
11317 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11318 self.manipulate_text(window, cx, |text| {
11319 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11320 if has_upper_case_characters {
11321 text.to_lowercase()
11322 } else {
11323 text.to_uppercase()
11324 }
11325 })
11326 }
11327
11328 pub fn convert_to_rot13(
11329 &mut self,
11330 _: &ConvertToRot13,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) {
11334 self.manipulate_text(window, cx, |text| {
11335 text.chars()
11336 .map(|c| match c {
11337 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11338 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11339 _ => c,
11340 })
11341 .collect()
11342 })
11343 }
11344
11345 pub fn convert_to_rot47(
11346 &mut self,
11347 _: &ConvertToRot47,
11348 window: &mut Window,
11349 cx: &mut Context<Self>,
11350 ) {
11351 self.manipulate_text(window, cx, |text| {
11352 text.chars()
11353 .map(|c| {
11354 let code_point = c as u32;
11355 if code_point >= 33 && code_point <= 126 {
11356 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11357 }
11358 c
11359 })
11360 .collect()
11361 })
11362 }
11363
11364 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11365 where
11366 Fn: FnMut(&str) -> String,
11367 {
11368 let buffer = self.buffer.read(cx).snapshot(cx);
11369
11370 let mut new_selections = Vec::new();
11371 let mut edits = Vec::new();
11372 let mut selection_adjustment = 0i32;
11373
11374 for selection in self.selections.all::<usize>(cx) {
11375 let selection_is_empty = selection.is_empty();
11376
11377 let (start, end) = if selection_is_empty {
11378 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11379 (word_range.start, word_range.end)
11380 } else {
11381 (selection.start, selection.end)
11382 };
11383
11384 let text = buffer.text_for_range(start..end).collect::<String>();
11385 let old_length = text.len() as i32;
11386 let text = callback(&text);
11387
11388 new_selections.push(Selection {
11389 start: (start as i32 - selection_adjustment) as usize,
11390 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11391 goal: SelectionGoal::None,
11392 ..selection
11393 });
11394
11395 selection_adjustment += old_length - text.len() as i32;
11396
11397 edits.push((start..end, text));
11398 }
11399
11400 self.transact(window, cx, |this, window, cx| {
11401 this.buffer.update(cx, |buffer, cx| {
11402 buffer.edit(edits, None, cx);
11403 });
11404
11405 this.change_selections(Default::default(), window, cx, |s| {
11406 s.select(new_selections);
11407 });
11408
11409 this.request_autoscroll(Autoscroll::fit(), cx);
11410 });
11411 }
11412
11413 pub fn move_selection_on_drop(
11414 &mut self,
11415 selection: &Selection<Anchor>,
11416 target: DisplayPoint,
11417 is_cut: bool,
11418 window: &mut Window,
11419 cx: &mut Context<Self>,
11420 ) {
11421 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11422 let buffer = &display_map.buffer_snapshot;
11423 let mut edits = Vec::new();
11424 let insert_point = display_map
11425 .clip_point(target, Bias::Left)
11426 .to_point(&display_map);
11427 let text = buffer
11428 .text_for_range(selection.start..selection.end)
11429 .collect::<String>();
11430 if is_cut {
11431 edits.push(((selection.start..selection.end), String::new()));
11432 }
11433 let insert_anchor = buffer.anchor_before(insert_point);
11434 edits.push(((insert_anchor..insert_anchor), text));
11435 let last_edit_start = insert_anchor.bias_left(buffer);
11436 let last_edit_end = insert_anchor.bias_right(buffer);
11437 self.transact(window, cx, |this, window, cx| {
11438 this.buffer.update(cx, |buffer, cx| {
11439 buffer.edit(edits, None, cx);
11440 });
11441 this.change_selections(Default::default(), window, cx, |s| {
11442 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11443 });
11444 });
11445 }
11446
11447 pub fn clear_selection_drag_state(&mut self) {
11448 self.selection_drag_state = SelectionDragState::None;
11449 }
11450
11451 pub fn duplicate(
11452 &mut self,
11453 upwards: bool,
11454 whole_lines: bool,
11455 window: &mut Window,
11456 cx: &mut Context<Self>,
11457 ) {
11458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11459
11460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11461 let buffer = &display_map.buffer_snapshot;
11462 let selections = self.selections.all::<Point>(cx);
11463
11464 let mut edits = Vec::new();
11465 let mut selections_iter = selections.iter().peekable();
11466 while let Some(selection) = selections_iter.next() {
11467 let mut rows = selection.spanned_rows(false, &display_map);
11468 // duplicate line-wise
11469 if whole_lines || selection.start == selection.end {
11470 // Avoid duplicating the same lines twice.
11471 while let Some(next_selection) = selections_iter.peek() {
11472 let next_rows = next_selection.spanned_rows(false, &display_map);
11473 if next_rows.start < rows.end {
11474 rows.end = next_rows.end;
11475 selections_iter.next().unwrap();
11476 } else {
11477 break;
11478 }
11479 }
11480
11481 // Copy the text from the selected row region and splice it either at the start
11482 // or end of the region.
11483 let start = Point::new(rows.start.0, 0);
11484 let end = Point::new(
11485 rows.end.previous_row().0,
11486 buffer.line_len(rows.end.previous_row()),
11487 );
11488 let text = buffer
11489 .text_for_range(start..end)
11490 .chain(Some("\n"))
11491 .collect::<String>();
11492 let insert_location = if upwards {
11493 Point::new(rows.end.0, 0)
11494 } else {
11495 start
11496 };
11497 edits.push((insert_location..insert_location, text));
11498 } else {
11499 // duplicate character-wise
11500 let start = selection.start;
11501 let end = selection.end;
11502 let text = buffer.text_for_range(start..end).collect::<String>();
11503 edits.push((selection.end..selection.end, text));
11504 }
11505 }
11506
11507 self.transact(window, cx, |this, _, cx| {
11508 this.buffer.update(cx, |buffer, cx| {
11509 buffer.edit(edits, None, cx);
11510 });
11511
11512 this.request_autoscroll(Autoscroll::fit(), cx);
11513 });
11514 }
11515
11516 pub fn duplicate_line_up(
11517 &mut self,
11518 _: &DuplicateLineUp,
11519 window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 self.duplicate(true, true, window, cx);
11523 }
11524
11525 pub fn duplicate_line_down(
11526 &mut self,
11527 _: &DuplicateLineDown,
11528 window: &mut Window,
11529 cx: &mut Context<Self>,
11530 ) {
11531 self.duplicate(false, true, window, cx);
11532 }
11533
11534 pub fn duplicate_selection(
11535 &mut self,
11536 _: &DuplicateSelection,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 self.duplicate(false, false, window, cx);
11541 }
11542
11543 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11545 if self.mode.is_single_line() {
11546 cx.propagate();
11547 return;
11548 }
11549
11550 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11551 let buffer = self.buffer.read(cx).snapshot(cx);
11552
11553 let mut edits = Vec::new();
11554 let mut unfold_ranges = Vec::new();
11555 let mut refold_creases = Vec::new();
11556
11557 let selections = self.selections.all::<Point>(cx);
11558 let mut selections = selections.iter().peekable();
11559 let mut contiguous_row_selections = Vec::new();
11560 let mut new_selections = Vec::new();
11561
11562 while let Some(selection) = selections.next() {
11563 // Find all the selections that span a contiguous row range
11564 let (start_row, end_row) = consume_contiguous_rows(
11565 &mut contiguous_row_selections,
11566 selection,
11567 &display_map,
11568 &mut selections,
11569 );
11570
11571 // Move the text spanned by the row range to be before the line preceding the row range
11572 if start_row.0 > 0 {
11573 let range_to_move = Point::new(
11574 start_row.previous_row().0,
11575 buffer.line_len(start_row.previous_row()),
11576 )
11577 ..Point::new(
11578 end_row.previous_row().0,
11579 buffer.line_len(end_row.previous_row()),
11580 );
11581 let insertion_point = display_map
11582 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11583 .0;
11584
11585 // Don't move lines across excerpts
11586 if buffer
11587 .excerpt_containing(insertion_point..range_to_move.end)
11588 .is_some()
11589 {
11590 let text = buffer
11591 .text_for_range(range_to_move.clone())
11592 .flat_map(|s| s.chars())
11593 .skip(1)
11594 .chain(['\n'])
11595 .collect::<String>();
11596
11597 edits.push((
11598 buffer.anchor_after(range_to_move.start)
11599 ..buffer.anchor_before(range_to_move.end),
11600 String::new(),
11601 ));
11602 let insertion_anchor = buffer.anchor_after(insertion_point);
11603 edits.push((insertion_anchor..insertion_anchor, text));
11604
11605 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11606
11607 // Move selections up
11608 new_selections.extend(contiguous_row_selections.drain(..).map(
11609 |mut selection| {
11610 selection.start.row -= row_delta;
11611 selection.end.row -= row_delta;
11612 selection
11613 },
11614 ));
11615
11616 // Move folds up
11617 unfold_ranges.push(range_to_move.clone());
11618 for fold in display_map.folds_in_range(
11619 buffer.anchor_before(range_to_move.start)
11620 ..buffer.anchor_after(range_to_move.end),
11621 ) {
11622 let mut start = fold.range.start.to_point(&buffer);
11623 let mut end = fold.range.end.to_point(&buffer);
11624 start.row -= row_delta;
11625 end.row -= row_delta;
11626 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11627 }
11628 }
11629 }
11630
11631 // If we didn't move line(s), preserve the existing selections
11632 new_selections.append(&mut contiguous_row_selections);
11633 }
11634
11635 self.transact(window, cx, |this, window, cx| {
11636 this.unfold_ranges(&unfold_ranges, true, true, cx);
11637 this.buffer.update(cx, |buffer, cx| {
11638 for (range, text) in edits {
11639 buffer.edit([(range, text)], None, cx);
11640 }
11641 });
11642 this.fold_creases(refold_creases, true, window, cx);
11643 this.change_selections(Default::default(), window, cx, |s| {
11644 s.select(new_selections);
11645 })
11646 });
11647 }
11648
11649 pub fn move_line_down(
11650 &mut self,
11651 _: &MoveLineDown,
11652 window: &mut Window,
11653 cx: &mut Context<Self>,
11654 ) {
11655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11656 if self.mode.is_single_line() {
11657 cx.propagate();
11658 return;
11659 }
11660
11661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11662 let buffer = self.buffer.read(cx).snapshot(cx);
11663
11664 let mut edits = Vec::new();
11665 let mut unfold_ranges = Vec::new();
11666 let mut refold_creases = Vec::new();
11667
11668 let selections = self.selections.all::<Point>(cx);
11669 let mut selections = selections.iter().peekable();
11670 let mut contiguous_row_selections = Vec::new();
11671 let mut new_selections = Vec::new();
11672
11673 while let Some(selection) = selections.next() {
11674 // Find all the selections that span a contiguous row range
11675 let (start_row, end_row) = consume_contiguous_rows(
11676 &mut contiguous_row_selections,
11677 selection,
11678 &display_map,
11679 &mut selections,
11680 );
11681
11682 // Move the text spanned by the row range to be after the last line of the row range
11683 if end_row.0 <= buffer.max_point().row {
11684 let range_to_move =
11685 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11686 let insertion_point = display_map
11687 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11688 .0;
11689
11690 // Don't move lines across excerpt boundaries
11691 if buffer
11692 .excerpt_containing(range_to_move.start..insertion_point)
11693 .is_some()
11694 {
11695 let mut text = String::from("\n");
11696 text.extend(buffer.text_for_range(range_to_move.clone()));
11697 text.pop(); // Drop trailing newline
11698 edits.push((
11699 buffer.anchor_after(range_to_move.start)
11700 ..buffer.anchor_before(range_to_move.end),
11701 String::new(),
11702 ));
11703 let insertion_anchor = buffer.anchor_after(insertion_point);
11704 edits.push((insertion_anchor..insertion_anchor, text));
11705
11706 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11707
11708 // Move selections down
11709 new_selections.extend(contiguous_row_selections.drain(..).map(
11710 |mut selection| {
11711 selection.start.row += row_delta;
11712 selection.end.row += row_delta;
11713 selection
11714 },
11715 ));
11716
11717 // Move folds down
11718 unfold_ranges.push(range_to_move.clone());
11719 for fold in display_map.folds_in_range(
11720 buffer.anchor_before(range_to_move.start)
11721 ..buffer.anchor_after(range_to_move.end),
11722 ) {
11723 let mut start = fold.range.start.to_point(&buffer);
11724 let mut end = fold.range.end.to_point(&buffer);
11725 start.row += row_delta;
11726 end.row += row_delta;
11727 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11728 }
11729 }
11730 }
11731
11732 // If we didn't move line(s), preserve the existing selections
11733 new_selections.append(&mut contiguous_row_selections);
11734 }
11735
11736 self.transact(window, cx, |this, window, cx| {
11737 this.unfold_ranges(&unfold_ranges, true, true, cx);
11738 this.buffer.update(cx, |buffer, cx| {
11739 for (range, text) in edits {
11740 buffer.edit([(range, text)], None, cx);
11741 }
11742 });
11743 this.fold_creases(refold_creases, true, window, cx);
11744 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11745 });
11746 }
11747
11748 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11750 let text_layout_details = &self.text_layout_details(window);
11751 self.transact(window, cx, |this, window, cx| {
11752 let edits = this.change_selections(Default::default(), window, cx, |s| {
11753 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11754 s.move_with(|display_map, selection| {
11755 if !selection.is_empty() {
11756 return;
11757 }
11758
11759 let mut head = selection.head();
11760 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11761 if head.column() == display_map.line_len(head.row()) {
11762 transpose_offset = display_map
11763 .buffer_snapshot
11764 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11765 }
11766
11767 if transpose_offset == 0 {
11768 return;
11769 }
11770
11771 *head.column_mut() += 1;
11772 head = display_map.clip_point(head, Bias::Right);
11773 let goal = SelectionGoal::HorizontalPosition(
11774 display_map
11775 .x_for_display_point(head, text_layout_details)
11776 .into(),
11777 );
11778 selection.collapse_to(head, goal);
11779
11780 let transpose_start = display_map
11781 .buffer_snapshot
11782 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11783 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11784 let transpose_end = display_map
11785 .buffer_snapshot
11786 .clip_offset(transpose_offset + 1, Bias::Right);
11787 if let Some(ch) =
11788 display_map.buffer_snapshot.chars_at(transpose_start).next()
11789 {
11790 edits.push((transpose_start..transpose_offset, String::new()));
11791 edits.push((transpose_end..transpose_end, ch.to_string()));
11792 }
11793 }
11794 });
11795 edits
11796 });
11797 this.buffer
11798 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11799 let selections = this.selections.all::<usize>(cx);
11800 this.change_selections(Default::default(), window, cx, |s| {
11801 s.select(selections);
11802 });
11803 });
11804 }
11805
11806 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11807 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11808 if self.mode.is_single_line() {
11809 cx.propagate();
11810 return;
11811 }
11812
11813 self.rewrap_impl(RewrapOptions::default(), cx)
11814 }
11815
11816 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11817 let buffer = self.buffer.read(cx).snapshot(cx);
11818 let selections = self.selections.all::<Point>(cx);
11819
11820 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11821 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11822 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11823 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11824 .peekable();
11825
11826 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11827 row
11828 } else {
11829 return Vec::new();
11830 };
11831
11832 let language_settings = buffer.language_settings_at(selection.head(), cx);
11833 let language_scope = buffer.language_scope_at(selection.head());
11834
11835 let indent_and_prefix_for_row =
11836 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11837 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11838 let (comment_prefix, rewrap_prefix) =
11839 if let Some(language_scope) = &language_scope {
11840 let indent_end = Point::new(row, indent.len);
11841 let comment_prefix = language_scope
11842 .line_comment_prefixes()
11843 .iter()
11844 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11845 .map(|prefix| prefix.to_string());
11846 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11847 let line_text_after_indent = buffer
11848 .text_for_range(indent_end..line_end)
11849 .collect::<String>();
11850 let rewrap_prefix = language_scope
11851 .rewrap_prefixes()
11852 .iter()
11853 .find_map(|prefix_regex| {
11854 prefix_regex.find(&line_text_after_indent).map(|mat| {
11855 if mat.start() == 0 {
11856 Some(mat.as_str().to_string())
11857 } else {
11858 None
11859 }
11860 })
11861 })
11862 .flatten();
11863 (comment_prefix, rewrap_prefix)
11864 } else {
11865 (None, None)
11866 };
11867 (indent, comment_prefix, rewrap_prefix)
11868 };
11869
11870 let mut ranges = Vec::new();
11871 let from_empty_selection = selection.is_empty();
11872
11873 let mut current_range_start = first_row;
11874 let mut prev_row = first_row;
11875 let (
11876 mut current_range_indent,
11877 mut current_range_comment_prefix,
11878 mut current_range_rewrap_prefix,
11879 ) = indent_and_prefix_for_row(first_row);
11880
11881 for row in non_blank_rows_iter.skip(1) {
11882 let has_paragraph_break = row > prev_row + 1;
11883
11884 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11885 indent_and_prefix_for_row(row);
11886
11887 let has_indent_change = row_indent != current_range_indent;
11888 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11889
11890 let has_boundary_change = has_comment_change
11891 || row_rewrap_prefix.is_some()
11892 || (has_indent_change && current_range_comment_prefix.is_some());
11893
11894 if has_paragraph_break || has_boundary_change {
11895 ranges.push((
11896 language_settings.clone(),
11897 Point::new(current_range_start, 0)
11898 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11899 current_range_indent,
11900 current_range_comment_prefix.clone(),
11901 current_range_rewrap_prefix.clone(),
11902 from_empty_selection,
11903 ));
11904 current_range_start = row;
11905 current_range_indent = row_indent;
11906 current_range_comment_prefix = row_comment_prefix;
11907 current_range_rewrap_prefix = row_rewrap_prefix;
11908 }
11909 prev_row = row;
11910 }
11911
11912 ranges.push((
11913 language_settings.clone(),
11914 Point::new(current_range_start, 0)
11915 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11916 current_range_indent,
11917 current_range_comment_prefix,
11918 current_range_rewrap_prefix,
11919 from_empty_selection,
11920 ));
11921
11922 ranges
11923 });
11924
11925 let mut edits = Vec::new();
11926 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11927
11928 for (
11929 language_settings,
11930 wrap_range,
11931 indent_size,
11932 comment_prefix,
11933 rewrap_prefix,
11934 from_empty_selection,
11935 ) in wrap_ranges
11936 {
11937 let mut start_row = wrap_range.start.row;
11938 let mut end_row = wrap_range.end.row;
11939
11940 // Skip selections that overlap with a range that has already been rewrapped.
11941 let selection_range = start_row..end_row;
11942 if rewrapped_row_ranges
11943 .iter()
11944 .any(|range| range.overlaps(&selection_range))
11945 {
11946 continue;
11947 }
11948
11949 let tab_size = language_settings.tab_size;
11950
11951 let indent_prefix = indent_size.chars().collect::<String>();
11952 let mut line_prefix = indent_prefix.clone();
11953 let mut inside_comment = false;
11954 if let Some(prefix) = &comment_prefix {
11955 line_prefix.push_str(prefix);
11956 inside_comment = true;
11957 }
11958 if let Some(prefix) = &rewrap_prefix {
11959 line_prefix.push_str(prefix);
11960 }
11961
11962 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11963 RewrapBehavior::InComments => inside_comment,
11964 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11965 RewrapBehavior::Anywhere => true,
11966 };
11967
11968 let should_rewrap = options.override_language_settings
11969 || allow_rewrap_based_on_language
11970 || self.hard_wrap.is_some();
11971 if !should_rewrap {
11972 continue;
11973 }
11974
11975 if from_empty_selection {
11976 'expand_upwards: while start_row > 0 {
11977 let prev_row = start_row - 1;
11978 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11979 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11980 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11981 {
11982 start_row = prev_row;
11983 } else {
11984 break 'expand_upwards;
11985 }
11986 }
11987
11988 'expand_downwards: while end_row < buffer.max_point().row {
11989 let next_row = end_row + 1;
11990 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11991 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11992 && !buffer.is_line_blank(MultiBufferRow(next_row))
11993 {
11994 end_row = next_row;
11995 } else {
11996 break 'expand_downwards;
11997 }
11998 }
11999 }
12000
12001 let start = Point::new(start_row, 0);
12002 let start_offset = start.to_offset(&buffer);
12003 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12004 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12005 let Some(lines_without_prefixes) = selection_text
12006 .lines()
12007 .enumerate()
12008 .map(|(ix, line)| {
12009 let line_trimmed = line.trim_start();
12010 if rewrap_prefix.is_some() && ix > 0 {
12011 Ok(line_trimmed)
12012 } else {
12013 line_trimmed
12014 .strip_prefix(&line_prefix.trim_start())
12015 .with_context(|| {
12016 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12017 })
12018 }
12019 })
12020 .collect::<Result<Vec<_>, _>>()
12021 .log_err()
12022 else {
12023 continue;
12024 };
12025
12026 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12027 buffer
12028 .language_settings_at(Point::new(start_row, 0), cx)
12029 .preferred_line_length as usize
12030 });
12031
12032 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12033 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12034 } else {
12035 line_prefix.clone()
12036 };
12037
12038 let wrapped_text = wrap_with_prefix(
12039 line_prefix,
12040 subsequent_lines_prefix,
12041 lines_without_prefixes.join("\n"),
12042 wrap_column,
12043 tab_size,
12044 options.preserve_existing_whitespace,
12045 );
12046
12047 // TODO: should always use char-based diff while still supporting cursor behavior that
12048 // matches vim.
12049 let mut diff_options = DiffOptions::default();
12050 if options.override_language_settings {
12051 diff_options.max_word_diff_len = 0;
12052 diff_options.max_word_diff_line_count = 0;
12053 } else {
12054 diff_options.max_word_diff_len = usize::MAX;
12055 diff_options.max_word_diff_line_count = usize::MAX;
12056 }
12057
12058 for (old_range, new_text) in
12059 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12060 {
12061 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12062 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12063 edits.push((edit_start..edit_end, new_text));
12064 }
12065
12066 rewrapped_row_ranges.push(start_row..=end_row);
12067 }
12068
12069 self.buffer
12070 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12071 }
12072
12073 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12074 let mut text = String::new();
12075 let buffer = self.buffer.read(cx).snapshot(cx);
12076 let mut selections = self.selections.all::<Point>(cx);
12077 let mut clipboard_selections = Vec::with_capacity(selections.len());
12078 {
12079 let max_point = buffer.max_point();
12080 let mut is_first = true;
12081 for selection in &mut selections {
12082 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12083 if is_entire_line {
12084 selection.start = Point::new(selection.start.row, 0);
12085 if !selection.is_empty() && selection.end.column == 0 {
12086 selection.end = cmp::min(max_point, selection.end);
12087 } else {
12088 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12089 }
12090 selection.goal = SelectionGoal::None;
12091 }
12092 if is_first {
12093 is_first = false;
12094 } else {
12095 text += "\n";
12096 }
12097 let mut len = 0;
12098 for chunk in buffer.text_for_range(selection.start..selection.end) {
12099 text.push_str(chunk);
12100 len += chunk.len();
12101 }
12102 clipboard_selections.push(ClipboardSelection {
12103 len,
12104 is_entire_line,
12105 first_line_indent: buffer
12106 .indent_size_for_line(MultiBufferRow(selection.start.row))
12107 .len,
12108 });
12109 }
12110 }
12111
12112 self.transact(window, cx, |this, window, cx| {
12113 this.change_selections(Default::default(), window, cx, |s| {
12114 s.select(selections);
12115 });
12116 this.insert("", window, cx);
12117 });
12118 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12119 }
12120
12121 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12122 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12123 let item = self.cut_common(window, cx);
12124 cx.write_to_clipboard(item);
12125 }
12126
12127 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12129 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12130 s.move_with(|snapshot, sel| {
12131 if sel.is_empty() {
12132 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12133 }
12134 });
12135 });
12136 let item = self.cut_common(window, cx);
12137 cx.set_global(KillRing(item))
12138 }
12139
12140 pub fn kill_ring_yank(
12141 &mut self,
12142 _: &KillRingYank,
12143 window: &mut Window,
12144 cx: &mut Context<Self>,
12145 ) {
12146 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12147 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12148 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12149 (kill_ring.text().to_string(), kill_ring.metadata_json())
12150 } else {
12151 return;
12152 }
12153 } else {
12154 return;
12155 };
12156 self.do_paste(&text, metadata, false, window, cx);
12157 }
12158
12159 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12160 self.do_copy(true, cx);
12161 }
12162
12163 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12164 self.do_copy(false, cx);
12165 }
12166
12167 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12168 let selections = self.selections.all::<Point>(cx);
12169 let buffer = self.buffer.read(cx).read(cx);
12170 let mut text = String::new();
12171
12172 let mut clipboard_selections = Vec::with_capacity(selections.len());
12173 {
12174 let max_point = buffer.max_point();
12175 let mut is_first = true;
12176 for selection in &selections {
12177 let mut start = selection.start;
12178 let mut end = selection.end;
12179 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12180 if is_entire_line {
12181 start = Point::new(start.row, 0);
12182 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12183 }
12184
12185 let mut trimmed_selections = Vec::new();
12186 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12187 let row = MultiBufferRow(start.row);
12188 let first_indent = buffer.indent_size_for_line(row);
12189 if first_indent.len == 0 || start.column > first_indent.len {
12190 trimmed_selections.push(start..end);
12191 } else {
12192 trimmed_selections.push(
12193 Point::new(row.0, first_indent.len)
12194 ..Point::new(row.0, buffer.line_len(row)),
12195 );
12196 for row in start.row + 1..=end.row {
12197 let mut line_len = buffer.line_len(MultiBufferRow(row));
12198 if row == end.row {
12199 line_len = end.column;
12200 }
12201 if line_len == 0 {
12202 trimmed_selections
12203 .push(Point::new(row, 0)..Point::new(row, line_len));
12204 continue;
12205 }
12206 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12207 if row_indent_size.len >= first_indent.len {
12208 trimmed_selections.push(
12209 Point::new(row, first_indent.len)..Point::new(row, line_len),
12210 );
12211 } else {
12212 trimmed_selections.clear();
12213 trimmed_selections.push(start..end);
12214 break;
12215 }
12216 }
12217 }
12218 } else {
12219 trimmed_selections.push(start..end);
12220 }
12221
12222 for trimmed_range in trimmed_selections {
12223 if is_first {
12224 is_first = false;
12225 } else {
12226 text += "\n";
12227 }
12228 let mut len = 0;
12229 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12230 text.push_str(chunk);
12231 len += chunk.len();
12232 }
12233 clipboard_selections.push(ClipboardSelection {
12234 len,
12235 is_entire_line,
12236 first_line_indent: buffer
12237 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12238 .len,
12239 });
12240 }
12241 }
12242 }
12243
12244 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12245 text,
12246 clipboard_selections,
12247 ));
12248 }
12249
12250 pub fn do_paste(
12251 &mut self,
12252 text: &String,
12253 clipboard_selections: Option<Vec<ClipboardSelection>>,
12254 handle_entire_lines: bool,
12255 window: &mut Window,
12256 cx: &mut Context<Self>,
12257 ) {
12258 if self.read_only(cx) {
12259 return;
12260 }
12261
12262 let clipboard_text = Cow::Borrowed(text);
12263
12264 self.transact(window, cx, |this, window, cx| {
12265 let had_active_edit_prediction = this.has_active_edit_prediction();
12266
12267 if let Some(mut clipboard_selections) = clipboard_selections {
12268 let old_selections = this.selections.all::<usize>(cx);
12269 let all_selections_were_entire_line =
12270 clipboard_selections.iter().all(|s| s.is_entire_line);
12271 let first_selection_indent_column =
12272 clipboard_selections.first().map(|s| s.first_line_indent);
12273 if clipboard_selections.len() != old_selections.len() {
12274 clipboard_selections.drain(..);
12275 }
12276 let cursor_offset = this.selections.last::<usize>(cx).head();
12277 let mut auto_indent_on_paste = true;
12278
12279 this.buffer.update(cx, |buffer, cx| {
12280 let snapshot = buffer.read(cx);
12281 auto_indent_on_paste = snapshot
12282 .language_settings_at(cursor_offset, cx)
12283 .auto_indent_on_paste;
12284
12285 let mut start_offset = 0;
12286 let mut edits = Vec::new();
12287 let mut original_indent_columns = Vec::new();
12288 for (ix, selection) in old_selections.iter().enumerate() {
12289 let to_insert;
12290 let entire_line;
12291 let original_indent_column;
12292 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12293 let end_offset = start_offset + clipboard_selection.len;
12294 to_insert = &clipboard_text[start_offset..end_offset];
12295 entire_line = clipboard_selection.is_entire_line;
12296 start_offset = end_offset + 1;
12297 original_indent_column = Some(clipboard_selection.first_line_indent);
12298 } else {
12299 to_insert = clipboard_text.as_str();
12300 entire_line = all_selections_were_entire_line;
12301 original_indent_column = first_selection_indent_column
12302 }
12303
12304 // If the corresponding selection was empty when this slice of the
12305 // clipboard text was written, then the entire line containing the
12306 // selection was copied. If this selection is also currently empty,
12307 // then paste the line before the current line of the buffer.
12308 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12309 let column = selection.start.to_point(&snapshot).column as usize;
12310 let line_start = selection.start - column;
12311 line_start..line_start
12312 } else {
12313 selection.range()
12314 };
12315
12316 edits.push((range, to_insert));
12317 original_indent_columns.push(original_indent_column);
12318 }
12319 drop(snapshot);
12320
12321 buffer.edit(
12322 edits,
12323 if auto_indent_on_paste {
12324 Some(AutoindentMode::Block {
12325 original_indent_columns,
12326 })
12327 } else {
12328 None
12329 },
12330 cx,
12331 );
12332 });
12333
12334 let selections = this.selections.all::<usize>(cx);
12335 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12336 } else {
12337 this.insert(&clipboard_text, window, cx);
12338 }
12339
12340 let trigger_in_words =
12341 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12342
12343 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12344 });
12345 }
12346
12347 pub fn diff_clipboard_with_selection(
12348 &mut self,
12349 _: &DiffClipboardWithSelection,
12350 window: &mut Window,
12351 cx: &mut Context<Self>,
12352 ) {
12353 let selections = self.selections.all::<usize>(cx);
12354
12355 if selections.is_empty() {
12356 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12357 return;
12358 };
12359
12360 let clipboard_text = match cx.read_from_clipboard() {
12361 Some(item) => match item.entries().first() {
12362 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12363 _ => None,
12364 },
12365 None => None,
12366 };
12367
12368 let Some(clipboard_text) = clipboard_text else {
12369 log::warn!("Clipboard doesn't contain text.");
12370 return;
12371 };
12372
12373 window.dispatch_action(
12374 Box::new(DiffClipboardWithSelectionData {
12375 clipboard_text,
12376 editor: cx.entity(),
12377 }),
12378 cx,
12379 );
12380 }
12381
12382 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12384 if let Some(item) = cx.read_from_clipboard() {
12385 let entries = item.entries();
12386
12387 match entries.first() {
12388 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12389 // of all the pasted entries.
12390 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12391 .do_paste(
12392 clipboard_string.text(),
12393 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12394 true,
12395 window,
12396 cx,
12397 ),
12398 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12399 }
12400 }
12401 }
12402
12403 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12404 if self.read_only(cx) {
12405 return;
12406 }
12407
12408 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12409
12410 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12411 if let Some((selections, _)) =
12412 self.selection_history.transaction(transaction_id).cloned()
12413 {
12414 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12415 s.select_anchors(selections.to_vec());
12416 });
12417 } else {
12418 log::error!(
12419 "No entry in selection_history found for undo. \
12420 This may correspond to a bug where undo does not update the selection. \
12421 If this is occurring, please add details to \
12422 https://github.com/zed-industries/zed/issues/22692"
12423 );
12424 }
12425 self.request_autoscroll(Autoscroll::fit(), cx);
12426 self.unmark_text(window, cx);
12427 self.refresh_edit_prediction(true, false, window, cx);
12428 cx.emit(EditorEvent::Edited { transaction_id });
12429 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12430 }
12431 }
12432
12433 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12434 if self.read_only(cx) {
12435 return;
12436 }
12437
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12439
12440 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12441 if let Some((_, Some(selections))) =
12442 self.selection_history.transaction(transaction_id).cloned()
12443 {
12444 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12445 s.select_anchors(selections.to_vec());
12446 });
12447 } else {
12448 log::error!(
12449 "No entry in selection_history found for redo. \
12450 This may correspond to a bug where undo does not update the selection. \
12451 If this is occurring, please add details to \
12452 https://github.com/zed-industries/zed/issues/22692"
12453 );
12454 }
12455 self.request_autoscroll(Autoscroll::fit(), cx);
12456 self.unmark_text(window, cx);
12457 self.refresh_edit_prediction(true, false, window, cx);
12458 cx.emit(EditorEvent::Edited { transaction_id });
12459 }
12460 }
12461
12462 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12463 self.buffer
12464 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12465 }
12466
12467 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12468 self.buffer
12469 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12470 }
12471
12472 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12473 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12474 self.change_selections(Default::default(), window, cx, |s| {
12475 s.move_with(|map, selection| {
12476 let cursor = if selection.is_empty() {
12477 movement::left(map, selection.start)
12478 } else {
12479 selection.start
12480 };
12481 selection.collapse_to(cursor, SelectionGoal::None);
12482 });
12483 })
12484 }
12485
12486 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12488 self.change_selections(Default::default(), window, cx, |s| {
12489 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12490 })
12491 }
12492
12493 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12494 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12495 self.change_selections(Default::default(), window, cx, |s| {
12496 s.move_with(|map, selection| {
12497 let cursor = if selection.is_empty() {
12498 movement::right(map, selection.end)
12499 } else {
12500 selection.end
12501 };
12502 selection.collapse_to(cursor, SelectionGoal::None)
12503 });
12504 })
12505 }
12506
12507 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12509 self.change_selections(Default::default(), window, cx, |s| {
12510 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12511 })
12512 }
12513
12514 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12515 if self.take_rename(true, window, cx).is_some() {
12516 return;
12517 }
12518
12519 if self.mode.is_single_line() {
12520 cx.propagate();
12521 return;
12522 }
12523
12524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12525
12526 let text_layout_details = &self.text_layout_details(window);
12527 let selection_count = self.selections.count();
12528 let first_selection = self.selections.first_anchor();
12529
12530 self.change_selections(Default::default(), window, cx, |s| {
12531 s.move_with(|map, selection| {
12532 if !selection.is_empty() {
12533 selection.goal = SelectionGoal::None;
12534 }
12535 let (cursor, goal) = movement::up(
12536 map,
12537 selection.start,
12538 selection.goal,
12539 false,
12540 text_layout_details,
12541 );
12542 selection.collapse_to(cursor, goal);
12543 });
12544 });
12545
12546 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12547 {
12548 cx.propagate();
12549 }
12550 }
12551
12552 pub fn move_up_by_lines(
12553 &mut self,
12554 action: &MoveUpByLines,
12555 window: &mut Window,
12556 cx: &mut Context<Self>,
12557 ) {
12558 if self.take_rename(true, window, cx).is_some() {
12559 return;
12560 }
12561
12562 if self.mode.is_single_line() {
12563 cx.propagate();
12564 return;
12565 }
12566
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12568
12569 let text_layout_details = &self.text_layout_details(window);
12570
12571 self.change_selections(Default::default(), window, cx, |s| {
12572 s.move_with(|map, selection| {
12573 if !selection.is_empty() {
12574 selection.goal = SelectionGoal::None;
12575 }
12576 let (cursor, goal) = movement::up_by_rows(
12577 map,
12578 selection.start,
12579 action.lines,
12580 selection.goal,
12581 false,
12582 text_layout_details,
12583 );
12584 selection.collapse_to(cursor, goal);
12585 });
12586 })
12587 }
12588
12589 pub fn move_down_by_lines(
12590 &mut self,
12591 action: &MoveDownByLines,
12592 window: &mut Window,
12593 cx: &mut Context<Self>,
12594 ) {
12595 if self.take_rename(true, window, cx).is_some() {
12596 return;
12597 }
12598
12599 if self.mode.is_single_line() {
12600 cx.propagate();
12601 return;
12602 }
12603
12604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12605
12606 let text_layout_details = &self.text_layout_details(window);
12607
12608 self.change_selections(Default::default(), window, cx, |s| {
12609 s.move_with(|map, selection| {
12610 if !selection.is_empty() {
12611 selection.goal = SelectionGoal::None;
12612 }
12613 let (cursor, goal) = movement::down_by_rows(
12614 map,
12615 selection.start,
12616 action.lines,
12617 selection.goal,
12618 false,
12619 text_layout_details,
12620 );
12621 selection.collapse_to(cursor, goal);
12622 });
12623 })
12624 }
12625
12626 pub fn select_down_by_lines(
12627 &mut self,
12628 action: &SelectDownByLines,
12629 window: &mut Window,
12630 cx: &mut Context<Self>,
12631 ) {
12632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12633 let text_layout_details = &self.text_layout_details(window);
12634 self.change_selections(Default::default(), window, cx, |s| {
12635 s.move_heads_with(|map, head, goal| {
12636 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12637 })
12638 })
12639 }
12640
12641 pub fn select_up_by_lines(
12642 &mut self,
12643 action: &SelectUpByLines,
12644 window: &mut Window,
12645 cx: &mut Context<Self>,
12646 ) {
12647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12648 let text_layout_details = &self.text_layout_details(window);
12649 self.change_selections(Default::default(), window, cx, |s| {
12650 s.move_heads_with(|map, head, goal| {
12651 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12652 })
12653 })
12654 }
12655
12656 pub fn select_page_up(
12657 &mut self,
12658 _: &SelectPageUp,
12659 window: &mut Window,
12660 cx: &mut Context<Self>,
12661 ) {
12662 let Some(row_count) = self.visible_row_count() else {
12663 return;
12664 };
12665
12666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12667
12668 let text_layout_details = &self.text_layout_details(window);
12669
12670 self.change_selections(Default::default(), window, cx, |s| {
12671 s.move_heads_with(|map, head, goal| {
12672 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12673 })
12674 })
12675 }
12676
12677 pub fn move_page_up(
12678 &mut self,
12679 action: &MovePageUp,
12680 window: &mut Window,
12681 cx: &mut Context<Self>,
12682 ) {
12683 if self.take_rename(true, window, cx).is_some() {
12684 return;
12685 }
12686
12687 if self
12688 .context_menu
12689 .borrow_mut()
12690 .as_mut()
12691 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12692 .unwrap_or(false)
12693 {
12694 return;
12695 }
12696
12697 if matches!(self.mode, EditorMode::SingleLine) {
12698 cx.propagate();
12699 return;
12700 }
12701
12702 let Some(row_count) = self.visible_row_count() else {
12703 return;
12704 };
12705
12706 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12707
12708 let effects = if action.center_cursor {
12709 SelectionEffects::scroll(Autoscroll::center())
12710 } else {
12711 SelectionEffects::default()
12712 };
12713
12714 let text_layout_details = &self.text_layout_details(window);
12715
12716 self.change_selections(effects, window, cx, |s| {
12717 s.move_with(|map, selection| {
12718 if !selection.is_empty() {
12719 selection.goal = SelectionGoal::None;
12720 }
12721 let (cursor, goal) = movement::up_by_rows(
12722 map,
12723 selection.end,
12724 row_count,
12725 selection.goal,
12726 false,
12727 text_layout_details,
12728 );
12729 selection.collapse_to(cursor, goal);
12730 });
12731 });
12732 }
12733
12734 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12736 let text_layout_details = &self.text_layout_details(window);
12737 self.change_selections(Default::default(), window, cx, |s| {
12738 s.move_heads_with(|map, head, goal| {
12739 movement::up(map, head, goal, false, text_layout_details)
12740 })
12741 })
12742 }
12743
12744 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12745 self.take_rename(true, window, cx);
12746
12747 if self.mode.is_single_line() {
12748 cx.propagate();
12749 return;
12750 }
12751
12752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12753
12754 let text_layout_details = &self.text_layout_details(window);
12755 let selection_count = self.selections.count();
12756 let first_selection = self.selections.first_anchor();
12757
12758 self.change_selections(Default::default(), window, cx, |s| {
12759 s.move_with(|map, selection| {
12760 if !selection.is_empty() {
12761 selection.goal = SelectionGoal::None;
12762 }
12763 let (cursor, goal) = movement::down(
12764 map,
12765 selection.end,
12766 selection.goal,
12767 false,
12768 text_layout_details,
12769 );
12770 selection.collapse_to(cursor, goal);
12771 });
12772 });
12773
12774 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12775 {
12776 cx.propagate();
12777 }
12778 }
12779
12780 pub fn select_page_down(
12781 &mut self,
12782 _: &SelectPageDown,
12783 window: &mut Window,
12784 cx: &mut Context<Self>,
12785 ) {
12786 let Some(row_count) = self.visible_row_count() else {
12787 return;
12788 };
12789
12790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12791
12792 let text_layout_details = &self.text_layout_details(window);
12793
12794 self.change_selections(Default::default(), window, cx, |s| {
12795 s.move_heads_with(|map, head, goal| {
12796 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12797 })
12798 })
12799 }
12800
12801 pub fn move_page_down(
12802 &mut self,
12803 action: &MovePageDown,
12804 window: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 if self.take_rename(true, window, cx).is_some() {
12808 return;
12809 }
12810
12811 if self
12812 .context_menu
12813 .borrow_mut()
12814 .as_mut()
12815 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12816 .unwrap_or(false)
12817 {
12818 return;
12819 }
12820
12821 if matches!(self.mode, EditorMode::SingleLine) {
12822 cx.propagate();
12823 return;
12824 }
12825
12826 let Some(row_count) = self.visible_row_count() else {
12827 return;
12828 };
12829
12830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12831
12832 let effects = if action.center_cursor {
12833 SelectionEffects::scroll(Autoscroll::center())
12834 } else {
12835 SelectionEffects::default()
12836 };
12837
12838 let text_layout_details = &self.text_layout_details(window);
12839 self.change_selections(effects, window, cx, |s| {
12840 s.move_with(|map, selection| {
12841 if !selection.is_empty() {
12842 selection.goal = SelectionGoal::None;
12843 }
12844 let (cursor, goal) = movement::down_by_rows(
12845 map,
12846 selection.end,
12847 row_count,
12848 selection.goal,
12849 false,
12850 text_layout_details,
12851 );
12852 selection.collapse_to(cursor, goal);
12853 });
12854 });
12855 }
12856
12857 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12859 let text_layout_details = &self.text_layout_details(window);
12860 self.change_selections(Default::default(), window, cx, |s| {
12861 s.move_heads_with(|map, head, goal| {
12862 movement::down(map, head, goal, false, text_layout_details)
12863 })
12864 });
12865 }
12866
12867 pub fn context_menu_first(
12868 &mut self,
12869 _: &ContextMenuFirst,
12870 window: &mut Window,
12871 cx: &mut Context<Self>,
12872 ) {
12873 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12874 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12875 }
12876 }
12877
12878 pub fn context_menu_prev(
12879 &mut self,
12880 _: &ContextMenuPrevious,
12881 window: &mut Window,
12882 cx: &mut Context<Self>,
12883 ) {
12884 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12885 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12886 }
12887 }
12888
12889 pub fn context_menu_next(
12890 &mut self,
12891 _: &ContextMenuNext,
12892 window: &mut Window,
12893 cx: &mut Context<Self>,
12894 ) {
12895 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12896 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12897 }
12898 }
12899
12900 pub fn context_menu_last(
12901 &mut self,
12902 _: &ContextMenuLast,
12903 window: &mut Window,
12904 cx: &mut Context<Self>,
12905 ) {
12906 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12907 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12908 }
12909 }
12910
12911 pub fn signature_help_prev(
12912 &mut self,
12913 _: &SignatureHelpPrevious,
12914 _: &mut Window,
12915 cx: &mut Context<Self>,
12916 ) {
12917 if let Some(popover) = self.signature_help_state.popover_mut() {
12918 if popover.current_signature == 0 {
12919 popover.current_signature = popover.signatures.len() - 1;
12920 } else {
12921 popover.current_signature -= 1;
12922 }
12923 cx.notify();
12924 }
12925 }
12926
12927 pub fn signature_help_next(
12928 &mut self,
12929 _: &SignatureHelpNext,
12930 _: &mut Window,
12931 cx: &mut Context<Self>,
12932 ) {
12933 if let Some(popover) = self.signature_help_state.popover_mut() {
12934 if popover.current_signature + 1 == popover.signatures.len() {
12935 popover.current_signature = 0;
12936 } else {
12937 popover.current_signature += 1;
12938 }
12939 cx.notify();
12940 }
12941 }
12942
12943 pub fn move_to_previous_word_start(
12944 &mut self,
12945 _: &MoveToPreviousWordStart,
12946 window: &mut Window,
12947 cx: &mut Context<Self>,
12948 ) {
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950 self.change_selections(Default::default(), window, cx, |s| {
12951 s.move_cursors_with(|map, head, _| {
12952 (
12953 movement::previous_word_start(map, head),
12954 SelectionGoal::None,
12955 )
12956 });
12957 })
12958 }
12959
12960 pub fn move_to_previous_subword_start(
12961 &mut self,
12962 _: &MoveToPreviousSubwordStart,
12963 window: &mut Window,
12964 cx: &mut Context<Self>,
12965 ) {
12966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12967 self.change_selections(Default::default(), window, cx, |s| {
12968 s.move_cursors_with(|map, head, _| {
12969 (
12970 movement::previous_subword_start(map, head),
12971 SelectionGoal::None,
12972 )
12973 });
12974 })
12975 }
12976
12977 pub fn select_to_previous_word_start(
12978 &mut self,
12979 _: &SelectToPreviousWordStart,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12984 self.change_selections(Default::default(), window, cx, |s| {
12985 s.move_heads_with(|map, head, _| {
12986 (
12987 movement::previous_word_start(map, head),
12988 SelectionGoal::None,
12989 )
12990 });
12991 })
12992 }
12993
12994 pub fn select_to_previous_subword_start(
12995 &mut self,
12996 _: &SelectToPreviousSubwordStart,
12997 window: &mut Window,
12998 cx: &mut Context<Self>,
12999 ) {
13000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13001 self.change_selections(Default::default(), window, cx, |s| {
13002 s.move_heads_with(|map, head, _| {
13003 (
13004 movement::previous_subword_start(map, head),
13005 SelectionGoal::None,
13006 )
13007 });
13008 })
13009 }
13010
13011 pub fn delete_to_previous_word_start(
13012 &mut self,
13013 action: &DeleteToPreviousWordStart,
13014 window: &mut Window,
13015 cx: &mut Context<Self>,
13016 ) {
13017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13018 self.transact(window, cx, |this, window, cx| {
13019 this.select_autoclose_pair(window, cx);
13020 this.change_selections(Default::default(), window, cx, |s| {
13021 s.move_with(|map, selection| {
13022 if selection.is_empty() {
13023 let cursor = if action.ignore_newlines {
13024 movement::previous_word_start(map, selection.head())
13025 } else {
13026 movement::previous_word_start_or_newline(map, selection.head())
13027 };
13028 selection.set_head(cursor, SelectionGoal::None);
13029 }
13030 });
13031 });
13032 this.insert("", window, cx);
13033 });
13034 }
13035
13036 pub fn delete_to_previous_subword_start(
13037 &mut self,
13038 _: &DeleteToPreviousSubwordStart,
13039 window: &mut Window,
13040 cx: &mut Context<Self>,
13041 ) {
13042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13043 self.transact(window, cx, |this, window, cx| {
13044 this.select_autoclose_pair(window, cx);
13045 this.change_selections(Default::default(), window, cx, |s| {
13046 s.move_with(|map, selection| {
13047 if selection.is_empty() {
13048 let cursor = movement::previous_subword_start(map, selection.head());
13049 selection.set_head(cursor, SelectionGoal::None);
13050 }
13051 });
13052 });
13053 this.insert("", window, cx);
13054 });
13055 }
13056
13057 pub fn move_to_next_word_end(
13058 &mut self,
13059 _: &MoveToNextWordEnd,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13064 self.change_selections(Default::default(), window, cx, |s| {
13065 s.move_cursors_with(|map, head, _| {
13066 (movement::next_word_end(map, head), SelectionGoal::None)
13067 });
13068 })
13069 }
13070
13071 pub fn move_to_next_subword_end(
13072 &mut self,
13073 _: &MoveToNextSubwordEnd,
13074 window: &mut Window,
13075 cx: &mut Context<Self>,
13076 ) {
13077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13078 self.change_selections(Default::default(), window, cx, |s| {
13079 s.move_cursors_with(|map, head, _| {
13080 (movement::next_subword_end(map, head), SelectionGoal::None)
13081 });
13082 })
13083 }
13084
13085 pub fn select_to_next_word_end(
13086 &mut self,
13087 _: &SelectToNextWordEnd,
13088 window: &mut Window,
13089 cx: &mut Context<Self>,
13090 ) {
13091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13092 self.change_selections(Default::default(), window, cx, |s| {
13093 s.move_heads_with(|map, head, _| {
13094 (movement::next_word_end(map, head), SelectionGoal::None)
13095 });
13096 })
13097 }
13098
13099 pub fn select_to_next_subword_end(
13100 &mut self,
13101 _: &SelectToNextSubwordEnd,
13102 window: &mut Window,
13103 cx: &mut Context<Self>,
13104 ) {
13105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13106 self.change_selections(Default::default(), window, cx, |s| {
13107 s.move_heads_with(|map, head, _| {
13108 (movement::next_subword_end(map, head), SelectionGoal::None)
13109 });
13110 })
13111 }
13112
13113 pub fn delete_to_next_word_end(
13114 &mut self,
13115 action: &DeleteToNextWordEnd,
13116 window: &mut Window,
13117 cx: &mut Context<Self>,
13118 ) {
13119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13120 self.transact(window, cx, |this, window, cx| {
13121 this.change_selections(Default::default(), window, cx, |s| {
13122 s.move_with(|map, selection| {
13123 if selection.is_empty() {
13124 let cursor = if action.ignore_newlines {
13125 movement::next_word_end(map, selection.head())
13126 } else {
13127 movement::next_word_end_or_newline(map, selection.head())
13128 };
13129 selection.set_head(cursor, SelectionGoal::None);
13130 }
13131 });
13132 });
13133 this.insert("", window, cx);
13134 });
13135 }
13136
13137 pub fn delete_to_next_subword_end(
13138 &mut self,
13139 _: &DeleteToNextSubwordEnd,
13140 window: &mut Window,
13141 cx: &mut Context<Self>,
13142 ) {
13143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13144 self.transact(window, cx, |this, window, cx| {
13145 this.change_selections(Default::default(), window, cx, |s| {
13146 s.move_with(|map, selection| {
13147 if selection.is_empty() {
13148 let cursor = movement::next_subword_end(map, selection.head());
13149 selection.set_head(cursor, SelectionGoal::None);
13150 }
13151 });
13152 });
13153 this.insert("", window, cx);
13154 });
13155 }
13156
13157 pub fn move_to_beginning_of_line(
13158 &mut self,
13159 action: &MoveToBeginningOfLine,
13160 window: &mut Window,
13161 cx: &mut Context<Self>,
13162 ) {
13163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13164 self.change_selections(Default::default(), window, cx, |s| {
13165 s.move_cursors_with(|map, head, _| {
13166 (
13167 movement::indented_line_beginning(
13168 map,
13169 head,
13170 action.stop_at_soft_wraps,
13171 action.stop_at_indent,
13172 ),
13173 SelectionGoal::None,
13174 )
13175 });
13176 })
13177 }
13178
13179 pub fn select_to_beginning_of_line(
13180 &mut self,
13181 action: &SelectToBeginningOfLine,
13182 window: &mut Window,
13183 cx: &mut Context<Self>,
13184 ) {
13185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13186 self.change_selections(Default::default(), window, cx, |s| {
13187 s.move_heads_with(|map, head, _| {
13188 (
13189 movement::indented_line_beginning(
13190 map,
13191 head,
13192 action.stop_at_soft_wraps,
13193 action.stop_at_indent,
13194 ),
13195 SelectionGoal::None,
13196 )
13197 });
13198 });
13199 }
13200
13201 pub fn delete_to_beginning_of_line(
13202 &mut self,
13203 action: &DeleteToBeginningOfLine,
13204 window: &mut Window,
13205 cx: &mut Context<Self>,
13206 ) {
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13208 self.transact(window, cx, |this, window, cx| {
13209 this.change_selections(Default::default(), window, cx, |s| {
13210 s.move_with(|_, selection| {
13211 selection.reversed = true;
13212 });
13213 });
13214
13215 this.select_to_beginning_of_line(
13216 &SelectToBeginningOfLine {
13217 stop_at_soft_wraps: false,
13218 stop_at_indent: action.stop_at_indent,
13219 },
13220 window,
13221 cx,
13222 );
13223 this.backspace(&Backspace, window, cx);
13224 });
13225 }
13226
13227 pub fn move_to_end_of_line(
13228 &mut self,
13229 action: &MoveToEndOfLine,
13230 window: &mut Window,
13231 cx: &mut Context<Self>,
13232 ) {
13233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13234 self.change_selections(Default::default(), window, cx, |s| {
13235 s.move_cursors_with(|map, head, _| {
13236 (
13237 movement::line_end(map, head, action.stop_at_soft_wraps),
13238 SelectionGoal::None,
13239 )
13240 });
13241 })
13242 }
13243
13244 pub fn select_to_end_of_line(
13245 &mut self,
13246 action: &SelectToEndOfLine,
13247 window: &mut Window,
13248 cx: &mut Context<Self>,
13249 ) {
13250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13251 self.change_selections(Default::default(), window, cx, |s| {
13252 s.move_heads_with(|map, head, _| {
13253 (
13254 movement::line_end(map, head, action.stop_at_soft_wraps),
13255 SelectionGoal::None,
13256 )
13257 });
13258 })
13259 }
13260
13261 pub fn delete_to_end_of_line(
13262 &mut self,
13263 _: &DeleteToEndOfLine,
13264 window: &mut Window,
13265 cx: &mut Context<Self>,
13266 ) {
13267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13268 self.transact(window, cx, |this, window, cx| {
13269 this.select_to_end_of_line(
13270 &SelectToEndOfLine {
13271 stop_at_soft_wraps: false,
13272 },
13273 window,
13274 cx,
13275 );
13276 this.delete(&Delete, window, cx);
13277 });
13278 }
13279
13280 pub fn cut_to_end_of_line(
13281 &mut self,
13282 _: &CutToEndOfLine,
13283 window: &mut Window,
13284 cx: &mut Context<Self>,
13285 ) {
13286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13287 self.transact(window, cx, |this, window, cx| {
13288 this.select_to_end_of_line(
13289 &SelectToEndOfLine {
13290 stop_at_soft_wraps: false,
13291 },
13292 window,
13293 cx,
13294 );
13295 this.cut(&Cut, window, cx);
13296 });
13297 }
13298
13299 pub fn move_to_start_of_paragraph(
13300 &mut self,
13301 _: &MoveToStartOfParagraph,
13302 window: &mut Window,
13303 cx: &mut Context<Self>,
13304 ) {
13305 if matches!(self.mode, EditorMode::SingleLine) {
13306 cx.propagate();
13307 return;
13308 }
13309 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13310 self.change_selections(Default::default(), window, cx, |s| {
13311 s.move_with(|map, selection| {
13312 selection.collapse_to(
13313 movement::start_of_paragraph(map, selection.head(), 1),
13314 SelectionGoal::None,
13315 )
13316 });
13317 })
13318 }
13319
13320 pub fn move_to_end_of_paragraph(
13321 &mut self,
13322 _: &MoveToEndOfParagraph,
13323 window: &mut Window,
13324 cx: &mut Context<Self>,
13325 ) {
13326 if matches!(self.mode, EditorMode::SingleLine) {
13327 cx.propagate();
13328 return;
13329 }
13330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13331 self.change_selections(Default::default(), window, cx, |s| {
13332 s.move_with(|map, selection| {
13333 selection.collapse_to(
13334 movement::end_of_paragraph(map, selection.head(), 1),
13335 SelectionGoal::None,
13336 )
13337 });
13338 })
13339 }
13340
13341 pub fn select_to_start_of_paragraph(
13342 &mut self,
13343 _: &SelectToStartOfParagraph,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 if matches!(self.mode, EditorMode::SingleLine) {
13348 cx.propagate();
13349 return;
13350 }
13351 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13352 self.change_selections(Default::default(), window, cx, |s| {
13353 s.move_heads_with(|map, head, _| {
13354 (
13355 movement::start_of_paragraph(map, head, 1),
13356 SelectionGoal::None,
13357 )
13358 });
13359 })
13360 }
13361
13362 pub fn select_to_end_of_paragraph(
13363 &mut self,
13364 _: &SelectToEndOfParagraph,
13365 window: &mut Window,
13366 cx: &mut Context<Self>,
13367 ) {
13368 if matches!(self.mode, EditorMode::SingleLine) {
13369 cx.propagate();
13370 return;
13371 }
13372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13373 self.change_selections(Default::default(), window, cx, |s| {
13374 s.move_heads_with(|map, head, _| {
13375 (
13376 movement::end_of_paragraph(map, head, 1),
13377 SelectionGoal::None,
13378 )
13379 });
13380 })
13381 }
13382
13383 pub fn move_to_start_of_excerpt(
13384 &mut self,
13385 _: &MoveToStartOfExcerpt,
13386 window: &mut Window,
13387 cx: &mut Context<Self>,
13388 ) {
13389 if matches!(self.mode, EditorMode::SingleLine) {
13390 cx.propagate();
13391 return;
13392 }
13393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13394 self.change_selections(Default::default(), window, cx, |s| {
13395 s.move_with(|map, selection| {
13396 selection.collapse_to(
13397 movement::start_of_excerpt(
13398 map,
13399 selection.head(),
13400 workspace::searchable::Direction::Prev,
13401 ),
13402 SelectionGoal::None,
13403 )
13404 });
13405 })
13406 }
13407
13408 pub fn move_to_start_of_next_excerpt(
13409 &mut self,
13410 _: &MoveToStartOfNextExcerpt,
13411 window: &mut Window,
13412 cx: &mut Context<Self>,
13413 ) {
13414 if matches!(self.mode, EditorMode::SingleLine) {
13415 cx.propagate();
13416 return;
13417 }
13418
13419 self.change_selections(Default::default(), window, cx, |s| {
13420 s.move_with(|map, selection| {
13421 selection.collapse_to(
13422 movement::start_of_excerpt(
13423 map,
13424 selection.head(),
13425 workspace::searchable::Direction::Next,
13426 ),
13427 SelectionGoal::None,
13428 )
13429 });
13430 })
13431 }
13432
13433 pub fn move_to_end_of_excerpt(
13434 &mut self,
13435 _: &MoveToEndOfExcerpt,
13436 window: &mut Window,
13437 cx: &mut Context<Self>,
13438 ) {
13439 if matches!(self.mode, EditorMode::SingleLine) {
13440 cx.propagate();
13441 return;
13442 }
13443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.move_with(|map, selection| {
13446 selection.collapse_to(
13447 movement::end_of_excerpt(
13448 map,
13449 selection.head(),
13450 workspace::searchable::Direction::Next,
13451 ),
13452 SelectionGoal::None,
13453 )
13454 });
13455 })
13456 }
13457
13458 pub fn move_to_end_of_previous_excerpt(
13459 &mut self,
13460 _: &MoveToEndOfPreviousExcerpt,
13461 window: &mut Window,
13462 cx: &mut Context<Self>,
13463 ) {
13464 if matches!(self.mode, EditorMode::SingleLine) {
13465 cx.propagate();
13466 return;
13467 }
13468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13469 self.change_selections(Default::default(), window, cx, |s| {
13470 s.move_with(|map, selection| {
13471 selection.collapse_to(
13472 movement::end_of_excerpt(
13473 map,
13474 selection.head(),
13475 workspace::searchable::Direction::Prev,
13476 ),
13477 SelectionGoal::None,
13478 )
13479 });
13480 })
13481 }
13482
13483 pub fn select_to_start_of_excerpt(
13484 &mut self,
13485 _: &SelectToStartOfExcerpt,
13486 window: &mut Window,
13487 cx: &mut Context<Self>,
13488 ) {
13489 if matches!(self.mode, EditorMode::SingleLine) {
13490 cx.propagate();
13491 return;
13492 }
13493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13494 self.change_selections(Default::default(), window, cx, |s| {
13495 s.move_heads_with(|map, head, _| {
13496 (
13497 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13498 SelectionGoal::None,
13499 )
13500 });
13501 })
13502 }
13503
13504 pub fn select_to_start_of_next_excerpt(
13505 &mut self,
13506 _: &SelectToStartOfNextExcerpt,
13507 window: &mut Window,
13508 cx: &mut Context<Self>,
13509 ) {
13510 if matches!(self.mode, EditorMode::SingleLine) {
13511 cx.propagate();
13512 return;
13513 }
13514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13515 self.change_selections(Default::default(), window, cx, |s| {
13516 s.move_heads_with(|map, head, _| {
13517 (
13518 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13519 SelectionGoal::None,
13520 )
13521 });
13522 })
13523 }
13524
13525 pub fn select_to_end_of_excerpt(
13526 &mut self,
13527 _: &SelectToEndOfExcerpt,
13528 window: &mut Window,
13529 cx: &mut Context<Self>,
13530 ) {
13531 if matches!(self.mode, EditorMode::SingleLine) {
13532 cx.propagate();
13533 return;
13534 }
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.move_heads_with(|map, head, _| {
13538 (
13539 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13540 SelectionGoal::None,
13541 )
13542 });
13543 })
13544 }
13545
13546 pub fn select_to_end_of_previous_excerpt(
13547 &mut self,
13548 _: &SelectToEndOfPreviousExcerpt,
13549 window: &mut Window,
13550 cx: &mut Context<Self>,
13551 ) {
13552 if matches!(self.mode, EditorMode::SingleLine) {
13553 cx.propagate();
13554 return;
13555 }
13556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13557 self.change_selections(Default::default(), window, cx, |s| {
13558 s.move_heads_with(|map, head, _| {
13559 (
13560 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13561 SelectionGoal::None,
13562 )
13563 });
13564 })
13565 }
13566
13567 pub fn move_to_beginning(
13568 &mut self,
13569 _: &MoveToBeginning,
13570 window: &mut Window,
13571 cx: &mut Context<Self>,
13572 ) {
13573 if matches!(self.mode, EditorMode::SingleLine) {
13574 cx.propagate();
13575 return;
13576 }
13577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13578 self.change_selections(Default::default(), window, cx, |s| {
13579 s.select_ranges(vec![0..0]);
13580 });
13581 }
13582
13583 pub fn select_to_beginning(
13584 &mut self,
13585 _: &SelectToBeginning,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 let mut selection = self.selections.last::<Point>(cx);
13590 selection.set_head(Point::zero(), SelectionGoal::None);
13591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13592 self.change_selections(Default::default(), window, cx, |s| {
13593 s.select(vec![selection]);
13594 });
13595 }
13596
13597 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13598 if matches!(self.mode, EditorMode::SingleLine) {
13599 cx.propagate();
13600 return;
13601 }
13602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13603 let cursor = self.buffer.read(cx).read(cx).len();
13604 self.change_selections(Default::default(), window, cx, |s| {
13605 s.select_ranges(vec![cursor..cursor])
13606 });
13607 }
13608
13609 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13610 self.nav_history = nav_history;
13611 }
13612
13613 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13614 self.nav_history.as_ref()
13615 }
13616
13617 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13618 self.push_to_nav_history(
13619 self.selections.newest_anchor().head(),
13620 None,
13621 false,
13622 true,
13623 cx,
13624 );
13625 }
13626
13627 fn push_to_nav_history(
13628 &mut self,
13629 cursor_anchor: Anchor,
13630 new_position: Option<Point>,
13631 is_deactivate: bool,
13632 always: bool,
13633 cx: &mut Context<Self>,
13634 ) {
13635 if let Some(nav_history) = self.nav_history.as_mut() {
13636 let buffer = self.buffer.read(cx).read(cx);
13637 let cursor_position = cursor_anchor.to_point(&buffer);
13638 let scroll_state = self.scroll_manager.anchor();
13639 let scroll_top_row = scroll_state.top_row(&buffer);
13640 drop(buffer);
13641
13642 if let Some(new_position) = new_position {
13643 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13644 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13645 return;
13646 }
13647 }
13648
13649 nav_history.push(
13650 Some(NavigationData {
13651 cursor_anchor,
13652 cursor_position,
13653 scroll_anchor: scroll_state,
13654 scroll_top_row,
13655 }),
13656 cx,
13657 );
13658 cx.emit(EditorEvent::PushedToNavHistory {
13659 anchor: cursor_anchor,
13660 is_deactivate,
13661 })
13662 }
13663 }
13664
13665 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13667 let buffer = self.buffer.read(cx).snapshot(cx);
13668 let mut selection = self.selections.first::<usize>(cx);
13669 selection.set_head(buffer.len(), SelectionGoal::None);
13670 self.change_selections(Default::default(), window, cx, |s| {
13671 s.select(vec![selection]);
13672 });
13673 }
13674
13675 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13676 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13677 let end = self.buffer.read(cx).read(cx).len();
13678 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13679 s.select_ranges(vec![0..end]);
13680 });
13681 }
13682
13683 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13685 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13686 let mut selections = self.selections.all::<Point>(cx);
13687 let max_point = display_map.buffer_snapshot.max_point();
13688 for selection in &mut selections {
13689 let rows = selection.spanned_rows(true, &display_map);
13690 selection.start = Point::new(rows.start.0, 0);
13691 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13692 selection.reversed = false;
13693 }
13694 self.change_selections(Default::default(), window, cx, |s| {
13695 s.select(selections);
13696 });
13697 }
13698
13699 pub fn split_selection_into_lines(
13700 &mut self,
13701 action: &SplitSelectionIntoLines,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 let selections = self
13706 .selections
13707 .all::<Point>(cx)
13708 .into_iter()
13709 .map(|selection| selection.start..selection.end)
13710 .collect::<Vec<_>>();
13711 self.unfold_ranges(&selections, true, true, cx);
13712
13713 let mut new_selection_ranges = Vec::new();
13714 {
13715 let buffer = self.buffer.read(cx).read(cx);
13716 for selection in selections {
13717 for row in selection.start.row..selection.end.row {
13718 let line_start = Point::new(row, 0);
13719 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13720
13721 if action.keep_selections {
13722 // Keep the selection range for each line
13723 let selection_start = if row == selection.start.row {
13724 selection.start
13725 } else {
13726 line_start
13727 };
13728 new_selection_ranges.push(selection_start..line_end);
13729 } else {
13730 // Collapse to cursor at end of line
13731 new_selection_ranges.push(line_end..line_end);
13732 }
13733 }
13734
13735 let is_multiline_selection = selection.start.row != selection.end.row;
13736 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13737 // so this action feels more ergonomic when paired with other selection operations
13738 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13739 if !should_skip_last {
13740 if action.keep_selections {
13741 if is_multiline_selection {
13742 let line_start = Point::new(selection.end.row, 0);
13743 new_selection_ranges.push(line_start..selection.end);
13744 } else {
13745 new_selection_ranges.push(selection.start..selection.end);
13746 }
13747 } else {
13748 new_selection_ranges.push(selection.end..selection.end);
13749 }
13750 }
13751 }
13752 }
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.select_ranges(new_selection_ranges);
13755 });
13756 }
13757
13758 pub fn add_selection_above(
13759 &mut self,
13760 _: &AddSelectionAbove,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 self.add_selection(true, window, cx);
13765 }
13766
13767 pub fn add_selection_below(
13768 &mut self,
13769 _: &AddSelectionBelow,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 self.add_selection(false, window, cx);
13774 }
13775
13776 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778
13779 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13780 let all_selections = self.selections.all::<Point>(cx);
13781 let text_layout_details = self.text_layout_details(window);
13782
13783 let (mut columnar_selections, new_selections_to_columnarize) = {
13784 if let Some(state) = self.add_selections_state.as_ref() {
13785 let columnar_selection_ids: HashSet<_> = state
13786 .groups
13787 .iter()
13788 .flat_map(|group| group.stack.iter())
13789 .copied()
13790 .collect();
13791
13792 all_selections
13793 .into_iter()
13794 .partition(|s| columnar_selection_ids.contains(&s.id))
13795 } else {
13796 (Vec::new(), all_selections)
13797 }
13798 };
13799
13800 let mut state = self
13801 .add_selections_state
13802 .take()
13803 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13804
13805 for selection in new_selections_to_columnarize {
13806 let range = selection.display_range(&display_map).sorted();
13807 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13808 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13809 let positions = start_x.min(end_x)..start_x.max(end_x);
13810 let mut stack = Vec::new();
13811 for row in range.start.row().0..=range.end.row().0 {
13812 if let Some(selection) = self.selections.build_columnar_selection(
13813 &display_map,
13814 DisplayRow(row),
13815 &positions,
13816 selection.reversed,
13817 &text_layout_details,
13818 ) {
13819 stack.push(selection.id);
13820 columnar_selections.push(selection);
13821 }
13822 }
13823 if !stack.is_empty() {
13824 if above {
13825 stack.reverse();
13826 }
13827 state.groups.push(AddSelectionsGroup { above, stack });
13828 }
13829 }
13830
13831 let mut final_selections = Vec::new();
13832 let end_row = if above {
13833 DisplayRow(0)
13834 } else {
13835 display_map.max_point().row()
13836 };
13837
13838 let mut last_added_item_per_group = HashMap::default();
13839 for group in state.groups.iter_mut() {
13840 if let Some(last_id) = group.stack.last() {
13841 last_added_item_per_group.insert(*last_id, group);
13842 }
13843 }
13844
13845 for selection in columnar_selections {
13846 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13847 if above == group.above {
13848 let range = selection.display_range(&display_map).sorted();
13849 debug_assert_eq!(range.start.row(), range.end.row());
13850 let mut row = range.start.row();
13851 let positions =
13852 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13853 px(start)..px(end)
13854 } else {
13855 let start_x =
13856 display_map.x_for_display_point(range.start, &text_layout_details);
13857 let end_x =
13858 display_map.x_for_display_point(range.end, &text_layout_details);
13859 start_x.min(end_x)..start_x.max(end_x)
13860 };
13861
13862 let mut maybe_new_selection = None;
13863 while row != end_row {
13864 if above {
13865 row.0 -= 1;
13866 } else {
13867 row.0 += 1;
13868 }
13869 if let Some(new_selection) = self.selections.build_columnar_selection(
13870 &display_map,
13871 row,
13872 &positions,
13873 selection.reversed,
13874 &text_layout_details,
13875 ) {
13876 maybe_new_selection = Some(new_selection);
13877 break;
13878 }
13879 }
13880
13881 if let Some(new_selection) = maybe_new_selection {
13882 group.stack.push(new_selection.id);
13883 if above {
13884 final_selections.push(new_selection);
13885 final_selections.push(selection);
13886 } else {
13887 final_selections.push(selection);
13888 final_selections.push(new_selection);
13889 }
13890 } else {
13891 final_selections.push(selection);
13892 }
13893 } else {
13894 group.stack.pop();
13895 }
13896 } else {
13897 final_selections.push(selection);
13898 }
13899 }
13900
13901 self.change_selections(Default::default(), window, cx, |s| {
13902 s.select(final_selections);
13903 });
13904
13905 let final_selection_ids: HashSet<_> = self
13906 .selections
13907 .all::<Point>(cx)
13908 .iter()
13909 .map(|s| s.id)
13910 .collect();
13911 state.groups.retain_mut(|group| {
13912 // selections might get merged above so we remove invalid items from stacks
13913 group.stack.retain(|id| final_selection_ids.contains(id));
13914
13915 // single selection in stack can be treated as initial state
13916 group.stack.len() > 1
13917 });
13918
13919 if !state.groups.is_empty() {
13920 self.add_selections_state = Some(state);
13921 }
13922 }
13923
13924 fn select_match_ranges(
13925 &mut self,
13926 range: Range<usize>,
13927 reversed: bool,
13928 replace_newest: bool,
13929 auto_scroll: Option<Autoscroll>,
13930 window: &mut Window,
13931 cx: &mut Context<Editor>,
13932 ) {
13933 self.unfold_ranges(
13934 std::slice::from_ref(&range),
13935 false,
13936 auto_scroll.is_some(),
13937 cx,
13938 );
13939 let effects = if let Some(scroll) = auto_scroll {
13940 SelectionEffects::scroll(scroll)
13941 } else {
13942 SelectionEffects::no_scroll()
13943 };
13944 self.change_selections(effects, window, cx, |s| {
13945 if replace_newest {
13946 s.delete(s.newest_anchor().id);
13947 }
13948 if reversed {
13949 s.insert_range(range.end..range.start);
13950 } else {
13951 s.insert_range(range);
13952 }
13953 });
13954 }
13955
13956 pub fn select_next_match_internal(
13957 &mut self,
13958 display_map: &DisplaySnapshot,
13959 replace_newest: bool,
13960 autoscroll: Option<Autoscroll>,
13961 window: &mut Window,
13962 cx: &mut Context<Self>,
13963 ) -> Result<()> {
13964 let buffer = &display_map.buffer_snapshot;
13965 let mut selections = self.selections.all::<usize>(cx);
13966 if let Some(mut select_next_state) = self.select_next_state.take() {
13967 let query = &select_next_state.query;
13968 if !select_next_state.done {
13969 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13970 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13971 let mut next_selected_range = None;
13972
13973 let bytes_after_last_selection =
13974 buffer.bytes_in_range(last_selection.end..buffer.len());
13975 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13976 let query_matches = query
13977 .stream_find_iter(bytes_after_last_selection)
13978 .map(|result| (last_selection.end, result))
13979 .chain(
13980 query
13981 .stream_find_iter(bytes_before_first_selection)
13982 .map(|result| (0, result)),
13983 );
13984
13985 for (start_offset, query_match) in query_matches {
13986 let query_match = query_match.unwrap(); // can only fail due to I/O
13987 let offset_range =
13988 start_offset + query_match.start()..start_offset + query_match.end();
13989
13990 if !select_next_state.wordwise
13991 || (!buffer.is_inside_word(offset_range.start, false)
13992 && !buffer.is_inside_word(offset_range.end, false))
13993 {
13994 // TODO: This is n^2, because we might check all the selections
13995 if !selections
13996 .iter()
13997 .any(|selection| selection.range().overlaps(&offset_range))
13998 {
13999 next_selected_range = Some(offset_range);
14000 break;
14001 }
14002 }
14003 }
14004
14005 if let Some(next_selected_range) = next_selected_range {
14006 self.select_match_ranges(
14007 next_selected_range,
14008 last_selection.reversed,
14009 replace_newest,
14010 autoscroll,
14011 window,
14012 cx,
14013 );
14014 } else {
14015 select_next_state.done = true;
14016 }
14017 }
14018
14019 self.select_next_state = Some(select_next_state);
14020 } else {
14021 let mut only_carets = true;
14022 let mut same_text_selected = true;
14023 let mut selected_text = None;
14024
14025 let mut selections_iter = selections.iter().peekable();
14026 while let Some(selection) = selections_iter.next() {
14027 if selection.start != selection.end {
14028 only_carets = false;
14029 }
14030
14031 if same_text_selected {
14032 if selected_text.is_none() {
14033 selected_text =
14034 Some(buffer.text_for_range(selection.range()).collect::<String>());
14035 }
14036
14037 if let Some(next_selection) = selections_iter.peek() {
14038 if next_selection.range().len() == selection.range().len() {
14039 let next_selected_text = buffer
14040 .text_for_range(next_selection.range())
14041 .collect::<String>();
14042 if Some(next_selected_text) != selected_text {
14043 same_text_selected = false;
14044 selected_text = None;
14045 }
14046 } else {
14047 same_text_selected = false;
14048 selected_text = None;
14049 }
14050 }
14051 }
14052 }
14053
14054 if only_carets {
14055 for selection in &mut selections {
14056 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14057 selection.start = word_range.start;
14058 selection.end = word_range.end;
14059 selection.goal = SelectionGoal::None;
14060 selection.reversed = false;
14061 self.select_match_ranges(
14062 selection.start..selection.end,
14063 selection.reversed,
14064 replace_newest,
14065 autoscroll,
14066 window,
14067 cx,
14068 );
14069 }
14070
14071 if selections.len() == 1 {
14072 let selection = selections
14073 .last()
14074 .expect("ensured that there's only one selection");
14075 let query = buffer
14076 .text_for_range(selection.start..selection.end)
14077 .collect::<String>();
14078 let is_empty = query.is_empty();
14079 let select_state = SelectNextState {
14080 query: AhoCorasick::new(&[query])?,
14081 wordwise: true,
14082 done: is_empty,
14083 };
14084 self.select_next_state = Some(select_state);
14085 } else {
14086 self.select_next_state = None;
14087 }
14088 } else if let Some(selected_text) = selected_text {
14089 self.select_next_state = Some(SelectNextState {
14090 query: AhoCorasick::new(&[selected_text])?,
14091 wordwise: false,
14092 done: false,
14093 });
14094 self.select_next_match_internal(
14095 display_map,
14096 replace_newest,
14097 autoscroll,
14098 window,
14099 cx,
14100 )?;
14101 }
14102 }
14103 Ok(())
14104 }
14105
14106 pub fn select_all_matches(
14107 &mut self,
14108 _action: &SelectAllMatches,
14109 window: &mut Window,
14110 cx: &mut Context<Self>,
14111 ) -> Result<()> {
14112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14113
14114 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14115
14116 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14117 let Some(select_next_state) = self.select_next_state.as_mut() else {
14118 return Ok(());
14119 };
14120 if select_next_state.done {
14121 return Ok(());
14122 }
14123
14124 let mut new_selections = Vec::new();
14125
14126 let reversed = self.selections.oldest::<usize>(cx).reversed;
14127 let buffer = &display_map.buffer_snapshot;
14128 let query_matches = select_next_state
14129 .query
14130 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14131
14132 for query_match in query_matches.into_iter() {
14133 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14134 let offset_range = if reversed {
14135 query_match.end()..query_match.start()
14136 } else {
14137 query_match.start()..query_match.end()
14138 };
14139
14140 if !select_next_state.wordwise
14141 || (!buffer.is_inside_word(offset_range.start, false)
14142 && !buffer.is_inside_word(offset_range.end, false))
14143 {
14144 new_selections.push(offset_range.start..offset_range.end);
14145 }
14146 }
14147
14148 select_next_state.done = true;
14149
14150 if new_selections.is_empty() {
14151 log::error!("bug: new_selections is empty in select_all_matches");
14152 return Ok(());
14153 }
14154
14155 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14156 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14157 selections.select_ranges(new_selections)
14158 });
14159
14160 Ok(())
14161 }
14162
14163 pub fn select_next(
14164 &mut self,
14165 action: &SelectNext,
14166 window: &mut Window,
14167 cx: &mut Context<Self>,
14168 ) -> Result<()> {
14169 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14171 self.select_next_match_internal(
14172 &display_map,
14173 action.replace_newest,
14174 Some(Autoscroll::newest()),
14175 window,
14176 cx,
14177 )?;
14178 Ok(())
14179 }
14180
14181 pub fn select_previous(
14182 &mut self,
14183 action: &SelectPrevious,
14184 window: &mut Window,
14185 cx: &mut Context<Self>,
14186 ) -> Result<()> {
14187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14189 let buffer = &display_map.buffer_snapshot;
14190 let mut selections = self.selections.all::<usize>(cx);
14191 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14192 let query = &select_prev_state.query;
14193 if !select_prev_state.done {
14194 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14195 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14196 let mut next_selected_range = None;
14197 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14198 let bytes_before_last_selection =
14199 buffer.reversed_bytes_in_range(0..last_selection.start);
14200 let bytes_after_first_selection =
14201 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14202 let query_matches = query
14203 .stream_find_iter(bytes_before_last_selection)
14204 .map(|result| (last_selection.start, result))
14205 .chain(
14206 query
14207 .stream_find_iter(bytes_after_first_selection)
14208 .map(|result| (buffer.len(), result)),
14209 );
14210 for (end_offset, query_match) in query_matches {
14211 let query_match = query_match.unwrap(); // can only fail due to I/O
14212 let offset_range =
14213 end_offset - query_match.end()..end_offset - query_match.start();
14214
14215 if !select_prev_state.wordwise
14216 || (!buffer.is_inside_word(offset_range.start, false)
14217 && !buffer.is_inside_word(offset_range.end, false))
14218 {
14219 next_selected_range = Some(offset_range);
14220 break;
14221 }
14222 }
14223
14224 if let Some(next_selected_range) = next_selected_range {
14225 self.select_match_ranges(
14226 next_selected_range,
14227 last_selection.reversed,
14228 action.replace_newest,
14229 Some(Autoscroll::newest()),
14230 window,
14231 cx,
14232 );
14233 } else {
14234 select_prev_state.done = true;
14235 }
14236 }
14237
14238 self.select_prev_state = Some(select_prev_state);
14239 } else {
14240 let mut only_carets = true;
14241 let mut same_text_selected = true;
14242 let mut selected_text = None;
14243
14244 let mut selections_iter = selections.iter().peekable();
14245 while let Some(selection) = selections_iter.next() {
14246 if selection.start != selection.end {
14247 only_carets = false;
14248 }
14249
14250 if same_text_selected {
14251 if selected_text.is_none() {
14252 selected_text =
14253 Some(buffer.text_for_range(selection.range()).collect::<String>());
14254 }
14255
14256 if let Some(next_selection) = selections_iter.peek() {
14257 if next_selection.range().len() == selection.range().len() {
14258 let next_selected_text = buffer
14259 .text_for_range(next_selection.range())
14260 .collect::<String>();
14261 if Some(next_selected_text) != selected_text {
14262 same_text_selected = false;
14263 selected_text = None;
14264 }
14265 } else {
14266 same_text_selected = false;
14267 selected_text = None;
14268 }
14269 }
14270 }
14271 }
14272
14273 if only_carets {
14274 for selection in &mut selections {
14275 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14276 selection.start = word_range.start;
14277 selection.end = word_range.end;
14278 selection.goal = SelectionGoal::None;
14279 selection.reversed = false;
14280 self.select_match_ranges(
14281 selection.start..selection.end,
14282 selection.reversed,
14283 action.replace_newest,
14284 Some(Autoscroll::newest()),
14285 window,
14286 cx,
14287 );
14288 }
14289 if selections.len() == 1 {
14290 let selection = selections
14291 .last()
14292 .expect("ensured that there's only one selection");
14293 let query = buffer
14294 .text_for_range(selection.start..selection.end)
14295 .collect::<String>();
14296 let is_empty = query.is_empty();
14297 let select_state = SelectNextState {
14298 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14299 wordwise: true,
14300 done: is_empty,
14301 };
14302 self.select_prev_state = Some(select_state);
14303 } else {
14304 self.select_prev_state = None;
14305 }
14306 } else if let Some(selected_text) = selected_text {
14307 self.select_prev_state = Some(SelectNextState {
14308 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14309 wordwise: false,
14310 done: false,
14311 });
14312 self.select_previous(action, window, cx)?;
14313 }
14314 }
14315 Ok(())
14316 }
14317
14318 pub fn find_next_match(
14319 &mut self,
14320 _: &FindNextMatch,
14321 window: &mut Window,
14322 cx: &mut Context<Self>,
14323 ) -> Result<()> {
14324 let selections = self.selections.disjoint_anchors();
14325 match selections.first() {
14326 Some(first) if selections.len() >= 2 => {
14327 self.change_selections(Default::default(), window, cx, |s| {
14328 s.select_ranges([first.range()]);
14329 });
14330 }
14331 _ => self.select_next(
14332 &SelectNext {
14333 replace_newest: true,
14334 },
14335 window,
14336 cx,
14337 )?,
14338 }
14339 Ok(())
14340 }
14341
14342 pub fn find_previous_match(
14343 &mut self,
14344 _: &FindPreviousMatch,
14345 window: &mut Window,
14346 cx: &mut Context<Self>,
14347 ) -> Result<()> {
14348 let selections = self.selections.disjoint_anchors();
14349 match selections.last() {
14350 Some(last) if selections.len() >= 2 => {
14351 self.change_selections(Default::default(), window, cx, |s| {
14352 s.select_ranges([last.range()]);
14353 });
14354 }
14355 _ => self.select_previous(
14356 &SelectPrevious {
14357 replace_newest: true,
14358 },
14359 window,
14360 cx,
14361 )?,
14362 }
14363 Ok(())
14364 }
14365
14366 pub fn toggle_comments(
14367 &mut self,
14368 action: &ToggleComments,
14369 window: &mut Window,
14370 cx: &mut Context<Self>,
14371 ) {
14372 if self.read_only(cx) {
14373 return;
14374 }
14375 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14376 let text_layout_details = &self.text_layout_details(window);
14377 self.transact(window, cx, |this, window, cx| {
14378 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14379 let mut edits = Vec::new();
14380 let mut selection_edit_ranges = Vec::new();
14381 let mut last_toggled_row = None;
14382 let snapshot = this.buffer.read(cx).read(cx);
14383 let empty_str: Arc<str> = Arc::default();
14384 let mut suffixes_inserted = Vec::new();
14385 let ignore_indent = action.ignore_indent;
14386
14387 fn comment_prefix_range(
14388 snapshot: &MultiBufferSnapshot,
14389 row: MultiBufferRow,
14390 comment_prefix: &str,
14391 comment_prefix_whitespace: &str,
14392 ignore_indent: bool,
14393 ) -> Range<Point> {
14394 let indent_size = if ignore_indent {
14395 0
14396 } else {
14397 snapshot.indent_size_for_line(row).len
14398 };
14399
14400 let start = Point::new(row.0, indent_size);
14401
14402 let mut line_bytes = snapshot
14403 .bytes_in_range(start..snapshot.max_point())
14404 .flatten()
14405 .copied();
14406
14407 // If this line currently begins with the line comment prefix, then record
14408 // the range containing the prefix.
14409 if line_bytes
14410 .by_ref()
14411 .take(comment_prefix.len())
14412 .eq(comment_prefix.bytes())
14413 {
14414 // Include any whitespace that matches the comment prefix.
14415 let matching_whitespace_len = line_bytes
14416 .zip(comment_prefix_whitespace.bytes())
14417 .take_while(|(a, b)| a == b)
14418 .count() as u32;
14419 let end = Point::new(
14420 start.row,
14421 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14422 );
14423 start..end
14424 } else {
14425 start..start
14426 }
14427 }
14428
14429 fn comment_suffix_range(
14430 snapshot: &MultiBufferSnapshot,
14431 row: MultiBufferRow,
14432 comment_suffix: &str,
14433 comment_suffix_has_leading_space: bool,
14434 ) -> Range<Point> {
14435 let end = Point::new(row.0, snapshot.line_len(row));
14436 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14437
14438 let mut line_end_bytes = snapshot
14439 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14440 .flatten()
14441 .copied();
14442
14443 let leading_space_len = if suffix_start_column > 0
14444 && line_end_bytes.next() == Some(b' ')
14445 && comment_suffix_has_leading_space
14446 {
14447 1
14448 } else {
14449 0
14450 };
14451
14452 // If this line currently begins with the line comment prefix, then record
14453 // the range containing the prefix.
14454 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14455 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14456 start..end
14457 } else {
14458 end..end
14459 }
14460 }
14461
14462 // TODO: Handle selections that cross excerpts
14463 for selection in &mut selections {
14464 let start_column = snapshot
14465 .indent_size_for_line(MultiBufferRow(selection.start.row))
14466 .len;
14467 let language = if let Some(language) =
14468 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14469 {
14470 language
14471 } else {
14472 continue;
14473 };
14474
14475 selection_edit_ranges.clear();
14476
14477 // If multiple selections contain a given row, avoid processing that
14478 // row more than once.
14479 let mut start_row = MultiBufferRow(selection.start.row);
14480 if last_toggled_row == Some(start_row) {
14481 start_row = start_row.next_row();
14482 }
14483 let end_row =
14484 if selection.end.row > selection.start.row && selection.end.column == 0 {
14485 MultiBufferRow(selection.end.row - 1)
14486 } else {
14487 MultiBufferRow(selection.end.row)
14488 };
14489 last_toggled_row = Some(end_row);
14490
14491 if start_row > end_row {
14492 continue;
14493 }
14494
14495 // If the language has line comments, toggle those.
14496 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14497
14498 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14499 if ignore_indent {
14500 full_comment_prefixes = full_comment_prefixes
14501 .into_iter()
14502 .map(|s| Arc::from(s.trim_end()))
14503 .collect();
14504 }
14505
14506 if !full_comment_prefixes.is_empty() {
14507 let first_prefix = full_comment_prefixes
14508 .first()
14509 .expect("prefixes is non-empty");
14510 let prefix_trimmed_lengths = full_comment_prefixes
14511 .iter()
14512 .map(|p| p.trim_end_matches(' ').len())
14513 .collect::<SmallVec<[usize; 4]>>();
14514
14515 let mut all_selection_lines_are_comments = true;
14516
14517 for row in start_row.0..=end_row.0 {
14518 let row = MultiBufferRow(row);
14519 if start_row < end_row && snapshot.is_line_blank(row) {
14520 continue;
14521 }
14522
14523 let prefix_range = full_comment_prefixes
14524 .iter()
14525 .zip(prefix_trimmed_lengths.iter().copied())
14526 .map(|(prefix, trimmed_prefix_len)| {
14527 comment_prefix_range(
14528 snapshot.deref(),
14529 row,
14530 &prefix[..trimmed_prefix_len],
14531 &prefix[trimmed_prefix_len..],
14532 ignore_indent,
14533 )
14534 })
14535 .max_by_key(|range| range.end.column - range.start.column)
14536 .expect("prefixes is non-empty");
14537
14538 if prefix_range.is_empty() {
14539 all_selection_lines_are_comments = false;
14540 }
14541
14542 selection_edit_ranges.push(prefix_range);
14543 }
14544
14545 if all_selection_lines_are_comments {
14546 edits.extend(
14547 selection_edit_ranges
14548 .iter()
14549 .cloned()
14550 .map(|range| (range, empty_str.clone())),
14551 );
14552 } else {
14553 let min_column = selection_edit_ranges
14554 .iter()
14555 .map(|range| range.start.column)
14556 .min()
14557 .unwrap_or(0);
14558 edits.extend(selection_edit_ranges.iter().map(|range| {
14559 let position = Point::new(range.start.row, min_column);
14560 (position..position, first_prefix.clone())
14561 }));
14562 }
14563 } else if let Some(BlockCommentConfig {
14564 start: full_comment_prefix,
14565 end: comment_suffix,
14566 ..
14567 }) = language.block_comment()
14568 {
14569 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14570 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14571 let prefix_range = comment_prefix_range(
14572 snapshot.deref(),
14573 start_row,
14574 comment_prefix,
14575 comment_prefix_whitespace,
14576 ignore_indent,
14577 );
14578 let suffix_range = comment_suffix_range(
14579 snapshot.deref(),
14580 end_row,
14581 comment_suffix.trim_start_matches(' '),
14582 comment_suffix.starts_with(' '),
14583 );
14584
14585 if prefix_range.is_empty() || suffix_range.is_empty() {
14586 edits.push((
14587 prefix_range.start..prefix_range.start,
14588 full_comment_prefix.clone(),
14589 ));
14590 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14591 suffixes_inserted.push((end_row, comment_suffix.len()));
14592 } else {
14593 edits.push((prefix_range, empty_str.clone()));
14594 edits.push((suffix_range, empty_str.clone()));
14595 }
14596 } else {
14597 continue;
14598 }
14599 }
14600
14601 drop(snapshot);
14602 this.buffer.update(cx, |buffer, cx| {
14603 buffer.edit(edits, None, cx);
14604 });
14605
14606 // Adjust selections so that they end before any comment suffixes that
14607 // were inserted.
14608 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14609 let mut selections = this.selections.all::<Point>(cx);
14610 let snapshot = this.buffer.read(cx).read(cx);
14611 for selection in &mut selections {
14612 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14613 match row.cmp(&MultiBufferRow(selection.end.row)) {
14614 Ordering::Less => {
14615 suffixes_inserted.next();
14616 continue;
14617 }
14618 Ordering::Greater => break,
14619 Ordering::Equal => {
14620 if selection.end.column == snapshot.line_len(row) {
14621 if selection.is_empty() {
14622 selection.start.column -= suffix_len as u32;
14623 }
14624 selection.end.column -= suffix_len as u32;
14625 }
14626 break;
14627 }
14628 }
14629 }
14630 }
14631
14632 drop(snapshot);
14633 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14634
14635 let selections = this.selections.all::<Point>(cx);
14636 let selections_on_single_row = selections.windows(2).all(|selections| {
14637 selections[0].start.row == selections[1].start.row
14638 && selections[0].end.row == selections[1].end.row
14639 && selections[0].start.row == selections[0].end.row
14640 });
14641 let selections_selecting = selections
14642 .iter()
14643 .any(|selection| selection.start != selection.end);
14644 let advance_downwards = action.advance_downwards
14645 && selections_on_single_row
14646 && !selections_selecting
14647 && !matches!(this.mode, EditorMode::SingleLine);
14648
14649 if advance_downwards {
14650 let snapshot = this.buffer.read(cx).snapshot(cx);
14651
14652 this.change_selections(Default::default(), window, cx, |s| {
14653 s.move_cursors_with(|display_snapshot, display_point, _| {
14654 let mut point = display_point.to_point(display_snapshot);
14655 point.row += 1;
14656 point = snapshot.clip_point(point, Bias::Left);
14657 let display_point = point.to_display_point(display_snapshot);
14658 let goal = SelectionGoal::HorizontalPosition(
14659 display_snapshot
14660 .x_for_display_point(display_point, text_layout_details)
14661 .into(),
14662 );
14663 (display_point, goal)
14664 })
14665 });
14666 }
14667 });
14668 }
14669
14670 pub fn select_enclosing_symbol(
14671 &mut self,
14672 _: &SelectEnclosingSymbol,
14673 window: &mut Window,
14674 cx: &mut Context<Self>,
14675 ) {
14676 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14677
14678 let buffer = self.buffer.read(cx).snapshot(cx);
14679 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14680
14681 fn update_selection(
14682 selection: &Selection<usize>,
14683 buffer_snap: &MultiBufferSnapshot,
14684 ) -> Option<Selection<usize>> {
14685 let cursor = selection.head();
14686 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14687 for symbol in symbols.iter().rev() {
14688 let start = symbol.range.start.to_offset(buffer_snap);
14689 let end = symbol.range.end.to_offset(buffer_snap);
14690 let new_range = start..end;
14691 if start < selection.start || end > selection.end {
14692 return Some(Selection {
14693 id: selection.id,
14694 start: new_range.start,
14695 end: new_range.end,
14696 goal: SelectionGoal::None,
14697 reversed: selection.reversed,
14698 });
14699 }
14700 }
14701 None
14702 }
14703
14704 let mut selected_larger_symbol = false;
14705 let new_selections = old_selections
14706 .iter()
14707 .map(|selection| match update_selection(selection, &buffer) {
14708 Some(new_selection) => {
14709 if new_selection.range() != selection.range() {
14710 selected_larger_symbol = true;
14711 }
14712 new_selection
14713 }
14714 None => selection.clone(),
14715 })
14716 .collect::<Vec<_>>();
14717
14718 if selected_larger_symbol {
14719 self.change_selections(Default::default(), window, cx, |s| {
14720 s.select(new_selections);
14721 });
14722 }
14723 }
14724
14725 pub fn select_larger_syntax_node(
14726 &mut self,
14727 _: &SelectLargerSyntaxNode,
14728 window: &mut Window,
14729 cx: &mut Context<Self>,
14730 ) {
14731 let Some(visible_row_count) = self.visible_row_count() else {
14732 return;
14733 };
14734 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14735 if old_selections.is_empty() {
14736 return;
14737 }
14738
14739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14740
14741 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14742 let buffer = self.buffer.read(cx).snapshot(cx);
14743
14744 let mut selected_larger_node = false;
14745 let mut new_selections = old_selections
14746 .iter()
14747 .map(|selection| {
14748 let old_range = selection.start..selection.end;
14749
14750 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14751 // manually select word at selection
14752 if ["string_content", "inline"].contains(&node.kind()) {
14753 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14754 // ignore if word is already selected
14755 if !word_range.is_empty() && old_range != word_range {
14756 let (last_word_range, _) =
14757 buffer.surrounding_word(old_range.end, false);
14758 // only select word if start and end point belongs to same word
14759 if word_range == last_word_range {
14760 selected_larger_node = true;
14761 return Selection {
14762 id: selection.id,
14763 start: word_range.start,
14764 end: word_range.end,
14765 goal: SelectionGoal::None,
14766 reversed: selection.reversed,
14767 };
14768 }
14769 }
14770 }
14771 }
14772
14773 let mut new_range = old_range.clone();
14774 while let Some((_node, containing_range)) =
14775 buffer.syntax_ancestor(new_range.clone())
14776 {
14777 new_range = match containing_range {
14778 MultiOrSingleBufferOffsetRange::Single(_) => break,
14779 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14780 };
14781 if !display_map.intersects_fold(new_range.start)
14782 && !display_map.intersects_fold(new_range.end)
14783 {
14784 break;
14785 }
14786 }
14787
14788 selected_larger_node |= new_range != old_range;
14789 Selection {
14790 id: selection.id,
14791 start: new_range.start,
14792 end: new_range.end,
14793 goal: SelectionGoal::None,
14794 reversed: selection.reversed,
14795 }
14796 })
14797 .collect::<Vec<_>>();
14798
14799 if !selected_larger_node {
14800 return; // don't put this call in the history
14801 }
14802
14803 // scroll based on transformation done to the last selection created by the user
14804 let (last_old, last_new) = old_selections
14805 .last()
14806 .zip(new_selections.last().cloned())
14807 .expect("old_selections isn't empty");
14808
14809 // revert selection
14810 let is_selection_reversed = {
14811 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14812 new_selections.last_mut().expect("checked above").reversed =
14813 should_newest_selection_be_reversed;
14814 should_newest_selection_be_reversed
14815 };
14816
14817 if selected_larger_node {
14818 self.select_syntax_node_history.disable_clearing = true;
14819 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14820 s.select(new_selections.clone());
14821 });
14822 self.select_syntax_node_history.disable_clearing = false;
14823 }
14824
14825 let start_row = last_new.start.to_display_point(&display_map).row().0;
14826 let end_row = last_new.end.to_display_point(&display_map).row().0;
14827 let selection_height = end_row - start_row + 1;
14828 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14829
14830 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14831 let scroll_behavior = if fits_on_the_screen {
14832 self.request_autoscroll(Autoscroll::fit(), cx);
14833 SelectSyntaxNodeScrollBehavior::FitSelection
14834 } else if is_selection_reversed {
14835 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14836 SelectSyntaxNodeScrollBehavior::CursorTop
14837 } else {
14838 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14839 SelectSyntaxNodeScrollBehavior::CursorBottom
14840 };
14841
14842 self.select_syntax_node_history.push((
14843 old_selections,
14844 scroll_behavior,
14845 is_selection_reversed,
14846 ));
14847 }
14848
14849 pub fn select_smaller_syntax_node(
14850 &mut self,
14851 _: &SelectSmallerSyntaxNode,
14852 window: &mut Window,
14853 cx: &mut Context<Self>,
14854 ) {
14855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14856
14857 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14858 self.select_syntax_node_history.pop()
14859 {
14860 if let Some(selection) = selections.last_mut() {
14861 selection.reversed = is_selection_reversed;
14862 }
14863
14864 self.select_syntax_node_history.disable_clearing = true;
14865 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14866 s.select(selections.to_vec());
14867 });
14868 self.select_syntax_node_history.disable_clearing = false;
14869
14870 match scroll_behavior {
14871 SelectSyntaxNodeScrollBehavior::CursorTop => {
14872 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14873 }
14874 SelectSyntaxNodeScrollBehavior::FitSelection => {
14875 self.request_autoscroll(Autoscroll::fit(), cx);
14876 }
14877 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14878 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14879 }
14880 }
14881 }
14882 }
14883
14884 pub fn unwrap_syntax_node(
14885 &mut self,
14886 _: &UnwrapSyntaxNode,
14887 window: &mut Window,
14888 cx: &mut Context<Self>,
14889 ) {
14890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14891
14892 let buffer = self.buffer.read(cx).snapshot(cx);
14893 let selections = self
14894 .selections
14895 .all::<usize>(cx)
14896 .into_iter()
14897 // subtracting the offset requires sorting
14898 .sorted_by_key(|i| i.start);
14899
14900 let full_edits = selections
14901 .into_iter()
14902 .filter_map(|selection| {
14903 // Only requires two branches once if-let-chains stabilize (#53667)
14904 let child = if !selection.is_empty() {
14905 selection.range()
14906 } else if let Some((_, ancestor_range)) =
14907 buffer.syntax_ancestor(selection.start..selection.end)
14908 {
14909 match ancestor_range {
14910 MultiOrSingleBufferOffsetRange::Single(range) => range,
14911 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14912 }
14913 } else {
14914 selection.range()
14915 };
14916
14917 let mut parent = child.clone();
14918 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14919 parent = match ancestor_range {
14920 MultiOrSingleBufferOffsetRange::Single(range) => range,
14921 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14922 };
14923 if parent.start < child.start || parent.end > child.end {
14924 break;
14925 }
14926 }
14927
14928 if parent == child {
14929 return None;
14930 }
14931 let text = buffer.text_for_range(child).collect::<String>();
14932 Some((selection.id, parent, text))
14933 })
14934 .collect::<Vec<_>>();
14935
14936 self.transact(window, cx, |this, window, cx| {
14937 this.buffer.update(cx, |buffer, cx| {
14938 buffer.edit(
14939 full_edits
14940 .iter()
14941 .map(|(_, p, t)| (p.clone(), t.clone()))
14942 .collect::<Vec<_>>(),
14943 None,
14944 cx,
14945 );
14946 });
14947 this.change_selections(Default::default(), window, cx, |s| {
14948 let mut offset = 0;
14949 let mut selections = vec![];
14950 for (id, parent, text) in full_edits {
14951 let start = parent.start - offset;
14952 offset += parent.len() - text.len();
14953 selections.push(Selection {
14954 id,
14955 start,
14956 end: start + text.len(),
14957 reversed: false,
14958 goal: Default::default(),
14959 });
14960 }
14961 s.select(selections);
14962 });
14963 });
14964 }
14965
14966 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14967 if !EditorSettings::get_global(cx).gutter.runnables {
14968 self.clear_tasks();
14969 return Task::ready(());
14970 }
14971 let project = self.project().map(Entity::downgrade);
14972 let task_sources = self.lsp_task_sources(cx);
14973 let multi_buffer = self.buffer.downgrade();
14974 cx.spawn_in(window, async move |editor, cx| {
14975 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14976 let Some(project) = project.and_then(|p| p.upgrade()) else {
14977 return;
14978 };
14979 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14980 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14981 }) else {
14982 return;
14983 };
14984
14985 let hide_runnables = project
14986 .update(cx, |project, _| project.is_via_collab())
14987 .unwrap_or(true);
14988 if hide_runnables {
14989 return;
14990 }
14991 let new_rows =
14992 cx.background_spawn({
14993 let snapshot = display_snapshot.clone();
14994 async move {
14995 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14996 }
14997 })
14998 .await;
14999 let Ok(lsp_tasks) =
15000 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15001 else {
15002 return;
15003 };
15004 let lsp_tasks = lsp_tasks.await;
15005
15006 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15007 lsp_tasks
15008 .into_iter()
15009 .flat_map(|(kind, tasks)| {
15010 tasks.into_iter().filter_map(move |(location, task)| {
15011 Some((kind.clone(), location?, task))
15012 })
15013 })
15014 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15015 let buffer = location.target.buffer;
15016 let buffer_snapshot = buffer.read(cx).snapshot();
15017 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15018 |(excerpt_id, snapshot, _)| {
15019 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15020 display_snapshot
15021 .buffer_snapshot
15022 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15023 } else {
15024 None
15025 }
15026 },
15027 );
15028 if let Some(offset) = offset {
15029 let task_buffer_range =
15030 location.target.range.to_point(&buffer_snapshot);
15031 let context_buffer_range =
15032 task_buffer_range.to_offset(&buffer_snapshot);
15033 let context_range = BufferOffset(context_buffer_range.start)
15034 ..BufferOffset(context_buffer_range.end);
15035
15036 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15037 .or_insert_with(|| RunnableTasks {
15038 templates: Vec::new(),
15039 offset,
15040 column: task_buffer_range.start.column,
15041 extra_variables: HashMap::default(),
15042 context_range,
15043 })
15044 .templates
15045 .push((kind, task.original_task().clone()));
15046 }
15047
15048 acc
15049 })
15050 }) else {
15051 return;
15052 };
15053
15054 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15055 buffer.language_settings(cx).tasks.prefer_lsp
15056 }) else {
15057 return;
15058 };
15059
15060 let rows = Self::runnable_rows(
15061 project,
15062 display_snapshot,
15063 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15064 new_rows,
15065 cx.clone(),
15066 )
15067 .await;
15068 editor
15069 .update(cx, |editor, _| {
15070 editor.clear_tasks();
15071 for (key, mut value) in rows {
15072 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15073 value.templates.extend(lsp_tasks.templates);
15074 }
15075
15076 editor.insert_tasks(key, value);
15077 }
15078 for (key, value) in lsp_tasks_by_rows {
15079 editor.insert_tasks(key, value);
15080 }
15081 })
15082 .ok();
15083 })
15084 }
15085 fn fetch_runnable_ranges(
15086 snapshot: &DisplaySnapshot,
15087 range: Range<Anchor>,
15088 ) -> Vec<language::RunnableRange> {
15089 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15090 }
15091
15092 fn runnable_rows(
15093 project: Entity<Project>,
15094 snapshot: DisplaySnapshot,
15095 prefer_lsp: bool,
15096 runnable_ranges: Vec<RunnableRange>,
15097 cx: AsyncWindowContext,
15098 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15099 cx.spawn(async move |cx| {
15100 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15101 for mut runnable in runnable_ranges {
15102 let Some(tasks) = cx
15103 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15104 .ok()
15105 else {
15106 continue;
15107 };
15108 let mut tasks = tasks.await;
15109
15110 if prefer_lsp {
15111 tasks.retain(|(task_kind, _)| {
15112 !matches!(task_kind, TaskSourceKind::Language { .. })
15113 });
15114 }
15115 if tasks.is_empty() {
15116 continue;
15117 }
15118
15119 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15120 let Some(row) = snapshot
15121 .buffer_snapshot
15122 .buffer_line_for_row(MultiBufferRow(point.row))
15123 .map(|(_, range)| range.start.row)
15124 else {
15125 continue;
15126 };
15127
15128 let context_range =
15129 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15130 runnable_rows.push((
15131 (runnable.buffer_id, row),
15132 RunnableTasks {
15133 templates: tasks,
15134 offset: snapshot
15135 .buffer_snapshot
15136 .anchor_before(runnable.run_range.start),
15137 context_range,
15138 column: point.column,
15139 extra_variables: runnable.extra_captures,
15140 },
15141 ));
15142 }
15143 runnable_rows
15144 })
15145 }
15146
15147 fn templates_with_tags(
15148 project: &Entity<Project>,
15149 runnable: &mut Runnable,
15150 cx: &mut App,
15151 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15152 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15153 let (worktree_id, file) = project
15154 .buffer_for_id(runnable.buffer, cx)
15155 .and_then(|buffer| buffer.read(cx).file())
15156 .map(|file| (file.worktree_id(cx), file.clone()))
15157 .unzip();
15158
15159 (
15160 project.task_store().read(cx).task_inventory().cloned(),
15161 worktree_id,
15162 file,
15163 )
15164 });
15165
15166 let tags = mem::take(&mut runnable.tags);
15167 let language = runnable.language.clone();
15168 cx.spawn(async move |cx| {
15169 let mut templates_with_tags = Vec::new();
15170 if let Some(inventory) = inventory {
15171 for RunnableTag(tag) in tags {
15172 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15173 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15174 }) else {
15175 return templates_with_tags;
15176 };
15177 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15178 move |(_, template)| {
15179 template.tags.iter().any(|source_tag| source_tag == &tag)
15180 },
15181 ));
15182 }
15183 }
15184 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15185
15186 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15187 // Strongest source wins; if we have worktree tag binding, prefer that to
15188 // global and language bindings;
15189 // if we have a global binding, prefer that to language binding.
15190 let first_mismatch = templates_with_tags
15191 .iter()
15192 .position(|(tag_source, _)| tag_source != leading_tag_source);
15193 if let Some(index) = first_mismatch {
15194 templates_with_tags.truncate(index);
15195 }
15196 }
15197
15198 templates_with_tags
15199 })
15200 }
15201
15202 pub fn move_to_enclosing_bracket(
15203 &mut self,
15204 _: &MoveToEnclosingBracket,
15205 window: &mut Window,
15206 cx: &mut Context<Self>,
15207 ) {
15208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15209 self.change_selections(Default::default(), window, cx, |s| {
15210 s.move_offsets_with(|snapshot, selection| {
15211 let Some(enclosing_bracket_ranges) =
15212 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15213 else {
15214 return;
15215 };
15216
15217 let mut best_length = usize::MAX;
15218 let mut best_inside = false;
15219 let mut best_in_bracket_range = false;
15220 let mut best_destination = None;
15221 for (open, close) in enclosing_bracket_ranges {
15222 let close = close.to_inclusive();
15223 let length = close.end() - open.start;
15224 let inside = selection.start >= open.end && selection.end <= *close.start();
15225 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15226 || close.contains(&selection.head());
15227
15228 // If best is next to a bracket and current isn't, skip
15229 if !in_bracket_range && best_in_bracket_range {
15230 continue;
15231 }
15232
15233 // Prefer smaller lengths unless best is inside and current isn't
15234 if length > best_length && (best_inside || !inside) {
15235 continue;
15236 }
15237
15238 best_length = length;
15239 best_inside = inside;
15240 best_in_bracket_range = in_bracket_range;
15241 best_destination = Some(
15242 if close.contains(&selection.start) && close.contains(&selection.end) {
15243 if inside { open.end } else { open.start }
15244 } else if inside {
15245 *close.start()
15246 } else {
15247 *close.end()
15248 },
15249 );
15250 }
15251
15252 if let Some(destination) = best_destination {
15253 selection.collapse_to(destination, SelectionGoal::None);
15254 }
15255 })
15256 });
15257 }
15258
15259 pub fn undo_selection(
15260 &mut self,
15261 _: &UndoSelection,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15266 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15267 self.selection_history.mode = SelectionHistoryMode::Undoing;
15268 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15269 this.end_selection(window, cx);
15270 this.change_selections(
15271 SelectionEffects::scroll(Autoscroll::newest()),
15272 window,
15273 cx,
15274 |s| s.select_anchors(entry.selections.to_vec()),
15275 );
15276 });
15277 self.selection_history.mode = SelectionHistoryMode::Normal;
15278
15279 self.select_next_state = entry.select_next_state;
15280 self.select_prev_state = entry.select_prev_state;
15281 self.add_selections_state = entry.add_selections_state;
15282 }
15283 }
15284
15285 pub fn redo_selection(
15286 &mut self,
15287 _: &RedoSelection,
15288 window: &mut Window,
15289 cx: &mut Context<Self>,
15290 ) {
15291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15292 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15293 self.selection_history.mode = SelectionHistoryMode::Redoing;
15294 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15295 this.end_selection(window, cx);
15296 this.change_selections(
15297 SelectionEffects::scroll(Autoscroll::newest()),
15298 window,
15299 cx,
15300 |s| s.select_anchors(entry.selections.to_vec()),
15301 );
15302 });
15303 self.selection_history.mode = SelectionHistoryMode::Normal;
15304
15305 self.select_next_state = entry.select_next_state;
15306 self.select_prev_state = entry.select_prev_state;
15307 self.add_selections_state = entry.add_selections_state;
15308 }
15309 }
15310
15311 pub fn expand_excerpts(
15312 &mut self,
15313 action: &ExpandExcerpts,
15314 _: &mut Window,
15315 cx: &mut Context<Self>,
15316 ) {
15317 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15318 }
15319
15320 pub fn expand_excerpts_down(
15321 &mut self,
15322 action: &ExpandExcerptsDown,
15323 _: &mut Window,
15324 cx: &mut Context<Self>,
15325 ) {
15326 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15327 }
15328
15329 pub fn expand_excerpts_up(
15330 &mut self,
15331 action: &ExpandExcerptsUp,
15332 _: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) {
15335 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15336 }
15337
15338 pub fn expand_excerpts_for_direction(
15339 &mut self,
15340 lines: u32,
15341 direction: ExpandExcerptDirection,
15342
15343 cx: &mut Context<Self>,
15344 ) {
15345 let selections = self.selections.disjoint_anchors();
15346
15347 let lines = if lines == 0 {
15348 EditorSettings::get_global(cx).expand_excerpt_lines
15349 } else {
15350 lines
15351 };
15352
15353 self.buffer.update(cx, |buffer, cx| {
15354 let snapshot = buffer.snapshot(cx);
15355 let mut excerpt_ids = selections
15356 .iter()
15357 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15358 .collect::<Vec<_>>();
15359 excerpt_ids.sort();
15360 excerpt_ids.dedup();
15361 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15362 })
15363 }
15364
15365 pub fn expand_excerpt(
15366 &mut self,
15367 excerpt: ExcerptId,
15368 direction: ExpandExcerptDirection,
15369 window: &mut Window,
15370 cx: &mut Context<Self>,
15371 ) {
15372 let current_scroll_position = self.scroll_position(cx);
15373 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15374 let mut should_scroll_up = false;
15375
15376 if direction == ExpandExcerptDirection::Down {
15377 let multi_buffer = self.buffer.read(cx);
15378 let snapshot = multi_buffer.snapshot(cx);
15379 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15380 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15381 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15382 {
15383 let buffer_snapshot = buffer.read(cx).snapshot();
15384 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15385 let last_row = buffer_snapshot.max_point().row;
15386 let lines_below = last_row.saturating_sub(excerpt_end_row);
15387 should_scroll_up = lines_below >= lines_to_expand;
15388 }
15389 }
15390
15391 self.buffer.update(cx, |buffer, cx| {
15392 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15393 });
15394
15395 if should_scroll_up {
15396 let new_scroll_position =
15397 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15398 self.set_scroll_position(new_scroll_position, window, cx);
15399 }
15400 }
15401
15402 pub fn go_to_singleton_buffer_point(
15403 &mut self,
15404 point: Point,
15405 window: &mut Window,
15406 cx: &mut Context<Self>,
15407 ) {
15408 self.go_to_singleton_buffer_range(point..point, window, cx);
15409 }
15410
15411 pub fn go_to_singleton_buffer_range(
15412 &mut self,
15413 range: Range<Point>,
15414 window: &mut Window,
15415 cx: &mut Context<Self>,
15416 ) {
15417 let multibuffer = self.buffer().read(cx);
15418 let Some(buffer) = multibuffer.as_singleton() else {
15419 return;
15420 };
15421 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15422 return;
15423 };
15424 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15425 return;
15426 };
15427 self.change_selections(
15428 SelectionEffects::default().nav_history(true),
15429 window,
15430 cx,
15431 |s| s.select_anchor_ranges([start..end]),
15432 );
15433 }
15434
15435 pub fn go_to_diagnostic(
15436 &mut self,
15437 action: &GoToDiagnostic,
15438 window: &mut Window,
15439 cx: &mut Context<Self>,
15440 ) {
15441 if !self.diagnostics_enabled() {
15442 return;
15443 }
15444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15445 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15446 }
15447
15448 pub fn go_to_prev_diagnostic(
15449 &mut self,
15450 action: &GoToPreviousDiagnostic,
15451 window: &mut Window,
15452 cx: &mut Context<Self>,
15453 ) {
15454 if !self.diagnostics_enabled() {
15455 return;
15456 }
15457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15458 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15459 }
15460
15461 pub fn go_to_diagnostic_impl(
15462 &mut self,
15463 direction: Direction,
15464 severity: GoToDiagnosticSeverityFilter,
15465 window: &mut Window,
15466 cx: &mut Context<Self>,
15467 ) {
15468 let buffer = self.buffer.read(cx).snapshot(cx);
15469 let selection = self.selections.newest::<usize>(cx);
15470
15471 let mut active_group_id = None;
15472 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15473 && active_group.active_range.start.to_offset(&buffer) == selection.start
15474 {
15475 active_group_id = Some(active_group.group_id);
15476 }
15477
15478 fn filtered(
15479 snapshot: EditorSnapshot,
15480 severity: GoToDiagnosticSeverityFilter,
15481 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15482 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15483 diagnostics
15484 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15485 .filter(|entry| entry.range.start != entry.range.end)
15486 .filter(|entry| !entry.diagnostic.is_unnecessary)
15487 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15488 }
15489
15490 let snapshot = self.snapshot(window, cx);
15491 let before = filtered(
15492 snapshot.clone(),
15493 severity,
15494 buffer
15495 .diagnostics_in_range(0..selection.start)
15496 .filter(|entry| entry.range.start <= selection.start),
15497 );
15498 let after = filtered(
15499 snapshot,
15500 severity,
15501 buffer
15502 .diagnostics_in_range(selection.start..buffer.len())
15503 .filter(|entry| entry.range.start >= selection.start),
15504 );
15505
15506 let mut found: Option<DiagnosticEntry<usize>> = None;
15507 if direction == Direction::Prev {
15508 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15509 {
15510 for diagnostic in prev_diagnostics.into_iter().rev() {
15511 if diagnostic.range.start != selection.start
15512 || active_group_id
15513 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15514 {
15515 found = Some(diagnostic);
15516 break 'outer;
15517 }
15518 }
15519 }
15520 } else {
15521 for diagnostic in after.chain(before) {
15522 if diagnostic.range.start != selection.start
15523 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15524 {
15525 found = Some(diagnostic);
15526 break;
15527 }
15528 }
15529 }
15530 let Some(next_diagnostic) = found else {
15531 return;
15532 };
15533
15534 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15535 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15536 return;
15537 };
15538 self.change_selections(Default::default(), window, cx, |s| {
15539 s.select_ranges(vec![
15540 next_diagnostic.range.start..next_diagnostic.range.start,
15541 ])
15542 });
15543 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15544 self.refresh_edit_prediction(false, true, window, cx);
15545 }
15546
15547 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15549 let snapshot = self.snapshot(window, cx);
15550 let selection = self.selections.newest::<Point>(cx);
15551 self.go_to_hunk_before_or_after_position(
15552 &snapshot,
15553 selection.head(),
15554 Direction::Next,
15555 window,
15556 cx,
15557 );
15558 }
15559
15560 pub fn go_to_hunk_before_or_after_position(
15561 &mut self,
15562 snapshot: &EditorSnapshot,
15563 position: Point,
15564 direction: Direction,
15565 window: &mut Window,
15566 cx: &mut Context<Editor>,
15567 ) {
15568 let row = if direction == Direction::Next {
15569 self.hunk_after_position(snapshot, position)
15570 .map(|hunk| hunk.row_range.start)
15571 } else {
15572 self.hunk_before_position(snapshot, position)
15573 };
15574
15575 if let Some(row) = row {
15576 let destination = Point::new(row.0, 0);
15577 let autoscroll = Autoscroll::center();
15578
15579 self.unfold_ranges(&[destination..destination], false, false, cx);
15580 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15581 s.select_ranges([destination..destination]);
15582 });
15583 }
15584 }
15585
15586 fn hunk_after_position(
15587 &mut self,
15588 snapshot: &EditorSnapshot,
15589 position: Point,
15590 ) -> Option<MultiBufferDiffHunk> {
15591 snapshot
15592 .buffer_snapshot
15593 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15594 .find(|hunk| hunk.row_range.start.0 > position.row)
15595 .or_else(|| {
15596 snapshot
15597 .buffer_snapshot
15598 .diff_hunks_in_range(Point::zero()..position)
15599 .find(|hunk| hunk.row_range.end.0 < position.row)
15600 })
15601 }
15602
15603 fn go_to_prev_hunk(
15604 &mut self,
15605 _: &GoToPreviousHunk,
15606 window: &mut Window,
15607 cx: &mut Context<Self>,
15608 ) {
15609 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15610 let snapshot = self.snapshot(window, cx);
15611 let selection = self.selections.newest::<Point>(cx);
15612 self.go_to_hunk_before_or_after_position(
15613 &snapshot,
15614 selection.head(),
15615 Direction::Prev,
15616 window,
15617 cx,
15618 );
15619 }
15620
15621 fn hunk_before_position(
15622 &mut self,
15623 snapshot: &EditorSnapshot,
15624 position: Point,
15625 ) -> Option<MultiBufferRow> {
15626 snapshot
15627 .buffer_snapshot
15628 .diff_hunk_before(position)
15629 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15630 }
15631
15632 fn go_to_next_change(
15633 &mut self,
15634 _: &GoToNextChange,
15635 window: &mut Window,
15636 cx: &mut Context<Self>,
15637 ) {
15638 if let Some(selections) = self
15639 .change_list
15640 .next_change(1, Direction::Next)
15641 .map(|s| s.to_vec())
15642 {
15643 self.change_selections(Default::default(), window, cx, |s| {
15644 let map = s.display_map();
15645 s.select_display_ranges(selections.iter().map(|a| {
15646 let point = a.to_display_point(&map);
15647 point..point
15648 }))
15649 })
15650 }
15651 }
15652
15653 fn go_to_previous_change(
15654 &mut self,
15655 _: &GoToPreviousChange,
15656 window: &mut Window,
15657 cx: &mut Context<Self>,
15658 ) {
15659 if let Some(selections) = self
15660 .change_list
15661 .next_change(1, Direction::Prev)
15662 .map(|s| s.to_vec())
15663 {
15664 self.change_selections(Default::default(), window, cx, |s| {
15665 let map = s.display_map();
15666 s.select_display_ranges(selections.iter().map(|a| {
15667 let point = a.to_display_point(&map);
15668 point..point
15669 }))
15670 })
15671 }
15672 }
15673
15674 fn go_to_line<T: 'static>(
15675 &mut self,
15676 position: Anchor,
15677 highlight_color: Option<Hsla>,
15678 window: &mut Window,
15679 cx: &mut Context<Self>,
15680 ) {
15681 let snapshot = self.snapshot(window, cx).display_snapshot;
15682 let position = position.to_point(&snapshot.buffer_snapshot);
15683 let start = snapshot
15684 .buffer_snapshot
15685 .clip_point(Point::new(position.row, 0), Bias::Left);
15686 let end = start + Point::new(1, 0);
15687 let start = snapshot.buffer_snapshot.anchor_before(start);
15688 let end = snapshot.buffer_snapshot.anchor_before(end);
15689
15690 self.highlight_rows::<T>(
15691 start..end,
15692 highlight_color
15693 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15694 Default::default(),
15695 cx,
15696 );
15697
15698 if self.buffer.read(cx).is_singleton() {
15699 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15700 }
15701 }
15702
15703 pub fn go_to_definition(
15704 &mut self,
15705 _: &GoToDefinition,
15706 window: &mut Window,
15707 cx: &mut Context<Self>,
15708 ) -> Task<Result<Navigated>> {
15709 let definition =
15710 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15711 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15712 cx.spawn_in(window, async move |editor, cx| {
15713 if definition.await? == Navigated::Yes {
15714 return Ok(Navigated::Yes);
15715 }
15716 match fallback_strategy {
15717 GoToDefinitionFallback::None => Ok(Navigated::No),
15718 GoToDefinitionFallback::FindAllReferences => {
15719 match editor.update_in(cx, |editor, window, cx| {
15720 editor.find_all_references(&FindAllReferences, window, cx)
15721 })? {
15722 Some(references) => references.await,
15723 None => Ok(Navigated::No),
15724 }
15725 }
15726 }
15727 })
15728 }
15729
15730 pub fn go_to_declaration(
15731 &mut self,
15732 _: &GoToDeclaration,
15733 window: &mut Window,
15734 cx: &mut Context<Self>,
15735 ) -> Task<Result<Navigated>> {
15736 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15737 }
15738
15739 pub fn go_to_declaration_split(
15740 &mut self,
15741 _: &GoToDeclaration,
15742 window: &mut Window,
15743 cx: &mut Context<Self>,
15744 ) -> Task<Result<Navigated>> {
15745 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15746 }
15747
15748 pub fn go_to_implementation(
15749 &mut self,
15750 _: &GoToImplementation,
15751 window: &mut Window,
15752 cx: &mut Context<Self>,
15753 ) -> Task<Result<Navigated>> {
15754 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15755 }
15756
15757 pub fn go_to_implementation_split(
15758 &mut self,
15759 _: &GoToImplementationSplit,
15760 window: &mut Window,
15761 cx: &mut Context<Self>,
15762 ) -> Task<Result<Navigated>> {
15763 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15764 }
15765
15766 pub fn go_to_type_definition(
15767 &mut self,
15768 _: &GoToTypeDefinition,
15769 window: &mut Window,
15770 cx: &mut Context<Self>,
15771 ) -> Task<Result<Navigated>> {
15772 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15773 }
15774
15775 pub fn go_to_definition_split(
15776 &mut self,
15777 _: &GoToDefinitionSplit,
15778 window: &mut Window,
15779 cx: &mut Context<Self>,
15780 ) -> Task<Result<Navigated>> {
15781 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15782 }
15783
15784 pub fn go_to_type_definition_split(
15785 &mut self,
15786 _: &GoToTypeDefinitionSplit,
15787 window: &mut Window,
15788 cx: &mut Context<Self>,
15789 ) -> Task<Result<Navigated>> {
15790 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15791 }
15792
15793 fn go_to_definition_of_kind(
15794 &mut self,
15795 kind: GotoDefinitionKind,
15796 split: bool,
15797 window: &mut Window,
15798 cx: &mut Context<Self>,
15799 ) -> Task<Result<Navigated>> {
15800 let Some(provider) = self.semantics_provider.clone() else {
15801 return Task::ready(Ok(Navigated::No));
15802 };
15803 let head = self.selections.newest::<usize>(cx).head();
15804 let buffer = self.buffer.read(cx);
15805 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15806 return Task::ready(Ok(Navigated::No));
15807 };
15808 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15809 return Task::ready(Ok(Navigated::No));
15810 };
15811
15812 cx.spawn_in(window, async move |editor, cx| {
15813 let Some(definitions) = definitions.await? else {
15814 return Ok(Navigated::No);
15815 };
15816 let navigated = editor
15817 .update_in(cx, |editor, window, cx| {
15818 editor.navigate_to_hover_links(
15819 Some(kind),
15820 definitions
15821 .into_iter()
15822 .filter(|location| {
15823 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15824 })
15825 .map(HoverLink::Text)
15826 .collect::<Vec<_>>(),
15827 split,
15828 window,
15829 cx,
15830 )
15831 })?
15832 .await?;
15833 anyhow::Ok(navigated)
15834 })
15835 }
15836
15837 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15838 let selection = self.selections.newest_anchor();
15839 let head = selection.head();
15840 let tail = selection.tail();
15841
15842 let Some((buffer, start_position)) =
15843 self.buffer.read(cx).text_anchor_for_position(head, cx)
15844 else {
15845 return;
15846 };
15847
15848 let end_position = if head != tail {
15849 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15850 return;
15851 };
15852 Some(pos)
15853 } else {
15854 None
15855 };
15856
15857 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15858 let url = if let Some(end_pos) = end_position {
15859 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15860 } else {
15861 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15862 };
15863
15864 if let Some(url) = url {
15865 editor.update(cx, |_, cx| {
15866 cx.open_url(&url);
15867 })
15868 } else {
15869 Ok(())
15870 }
15871 });
15872
15873 url_finder.detach();
15874 }
15875
15876 pub fn open_selected_filename(
15877 &mut self,
15878 _: &OpenSelectedFilename,
15879 window: &mut Window,
15880 cx: &mut Context<Self>,
15881 ) {
15882 let Some(workspace) = self.workspace() else {
15883 return;
15884 };
15885
15886 let position = self.selections.newest_anchor().head();
15887
15888 let Some((buffer, buffer_position)) =
15889 self.buffer.read(cx).text_anchor_for_position(position, cx)
15890 else {
15891 return;
15892 };
15893
15894 let project = self.project.clone();
15895
15896 cx.spawn_in(window, async move |_, cx| {
15897 let result = find_file(&buffer, project, buffer_position, cx).await;
15898
15899 if let Some((_, path)) = result {
15900 workspace
15901 .update_in(cx, |workspace, window, cx| {
15902 workspace.open_resolved_path(path, window, cx)
15903 })?
15904 .await?;
15905 }
15906 anyhow::Ok(())
15907 })
15908 .detach();
15909 }
15910
15911 pub(crate) fn navigate_to_hover_links(
15912 &mut self,
15913 kind: Option<GotoDefinitionKind>,
15914 definitions: Vec<HoverLink>,
15915 split: bool,
15916 window: &mut Window,
15917 cx: &mut Context<Editor>,
15918 ) -> Task<Result<Navigated>> {
15919 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15920 let mut first_url_or_file = None;
15921 let definitions: Vec<_> = definitions
15922 .into_iter()
15923 .filter_map(|def| match def {
15924 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15925 HoverLink::InlayHint(lsp_location, server_id) => {
15926 let computation =
15927 self.compute_target_location(lsp_location, server_id, window, cx);
15928 Some(cx.background_spawn(computation))
15929 }
15930 HoverLink::Url(url) => {
15931 first_url_or_file = Some(Either::Left(url));
15932 None
15933 }
15934 HoverLink::File(path) => {
15935 first_url_or_file = Some(Either::Right(path));
15936 None
15937 }
15938 })
15939 .collect();
15940
15941 let workspace = self.workspace();
15942
15943 cx.spawn_in(window, async move |editor, acx| {
15944 let mut locations: Vec<Location> = future::join_all(definitions)
15945 .await
15946 .into_iter()
15947 .filter_map(|location| location.transpose())
15948 .collect::<Result<_>>()
15949 .context("location tasks")?;
15950
15951 if locations.len() > 1 {
15952 let Some(workspace) = workspace else {
15953 return Ok(Navigated::No);
15954 };
15955
15956 let tab_kind = match kind {
15957 Some(GotoDefinitionKind::Implementation) => "Implementations",
15958 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15959 Some(GotoDefinitionKind::Declaration) => "Declarations",
15960 Some(GotoDefinitionKind::Type) => "Types",
15961 };
15962 let title = editor
15963 .update_in(acx, |_, _, cx| {
15964 let target = locations
15965 .iter()
15966 .map(|location| {
15967 location
15968 .buffer
15969 .read(cx)
15970 .text_for_range(location.range.clone())
15971 .collect::<String>()
15972 })
15973 .filter(|text| !text.contains('\n'))
15974 .unique()
15975 .take(3)
15976 .join(", ");
15977 if target.is_empty() {
15978 tab_kind.to_owned()
15979 } else {
15980 format!("{tab_kind} for {target}")
15981 }
15982 })
15983 .context("buffer title")?;
15984
15985 let opened = workspace
15986 .update_in(acx, |workspace, window, cx| {
15987 Self::open_locations_in_multibuffer(
15988 workspace,
15989 locations,
15990 title,
15991 split,
15992 MultibufferSelectionMode::First,
15993 window,
15994 cx,
15995 )
15996 })
15997 .is_ok();
15998
15999 anyhow::Ok(Navigated::from_bool(opened))
16000 } else if locations.is_empty() {
16001 // If there is one definition, just open it directly
16002 match first_url_or_file {
16003 Some(Either::Left(url)) => {
16004 acx.update(|_, cx| cx.open_url(&url))?;
16005 Ok(Navigated::Yes)
16006 }
16007 Some(Either::Right(path)) => {
16008 let Some(workspace) = workspace else {
16009 return Ok(Navigated::No);
16010 };
16011
16012 workspace
16013 .update_in(acx, |workspace, window, cx| {
16014 workspace.open_resolved_path(path, window, cx)
16015 })?
16016 .await?;
16017 Ok(Navigated::Yes)
16018 }
16019 None => Ok(Navigated::No),
16020 }
16021 } else {
16022 let Some(workspace) = workspace else {
16023 return Ok(Navigated::No);
16024 };
16025
16026 let target = locations.pop().unwrap();
16027 editor.update_in(acx, |editor, window, cx| {
16028 let pane = workspace.read(cx).active_pane().clone();
16029
16030 let range = target.range.to_point(target.buffer.read(cx));
16031 let range = editor.range_for_match(&range);
16032 let range = collapse_multiline_range(range);
16033
16034 if !split
16035 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16036 {
16037 editor.go_to_singleton_buffer_range(range, window, cx);
16038 } else {
16039 window.defer(cx, move |window, cx| {
16040 let target_editor: Entity<Self> =
16041 workspace.update(cx, |workspace, cx| {
16042 let pane = if split {
16043 workspace.adjacent_pane(window, cx)
16044 } else {
16045 workspace.active_pane().clone()
16046 };
16047
16048 workspace.open_project_item(
16049 pane,
16050 target.buffer.clone(),
16051 true,
16052 true,
16053 window,
16054 cx,
16055 )
16056 });
16057 target_editor.update(cx, |target_editor, cx| {
16058 // When selecting a definition in a different buffer, disable the nav history
16059 // to avoid creating a history entry at the previous cursor location.
16060 pane.update(cx, |pane, _| pane.disable_history());
16061 target_editor.go_to_singleton_buffer_range(range, window, cx);
16062 pane.update(cx, |pane, _| pane.enable_history());
16063 });
16064 });
16065 }
16066 Navigated::Yes
16067 })
16068 }
16069 })
16070 }
16071
16072 fn compute_target_location(
16073 &self,
16074 lsp_location: lsp::Location,
16075 server_id: LanguageServerId,
16076 window: &mut Window,
16077 cx: &mut Context<Self>,
16078 ) -> Task<anyhow::Result<Option<Location>>> {
16079 let Some(project) = self.project.clone() else {
16080 return Task::ready(Ok(None));
16081 };
16082
16083 cx.spawn_in(window, async move |editor, cx| {
16084 let location_task = editor.update(cx, |_, cx| {
16085 project.update(cx, |project, cx| {
16086 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16087 })
16088 })?;
16089 let location = Some({
16090 let target_buffer_handle = location_task.await.context("open local buffer")?;
16091 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16092 let target_start = target_buffer
16093 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16094 let target_end = target_buffer
16095 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16096 target_buffer.anchor_after(target_start)
16097 ..target_buffer.anchor_before(target_end)
16098 })?;
16099 Location {
16100 buffer: target_buffer_handle,
16101 range,
16102 }
16103 });
16104 Ok(location)
16105 })
16106 }
16107
16108 pub fn find_all_references(
16109 &mut self,
16110 _: &FindAllReferences,
16111 window: &mut Window,
16112 cx: &mut Context<Self>,
16113 ) -> Option<Task<Result<Navigated>>> {
16114 let selection = self.selections.newest::<usize>(cx);
16115 let multi_buffer = self.buffer.read(cx);
16116 let head = selection.head();
16117
16118 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16119 let head_anchor = multi_buffer_snapshot.anchor_at(
16120 head,
16121 if head < selection.tail() {
16122 Bias::Right
16123 } else {
16124 Bias::Left
16125 },
16126 );
16127
16128 match self
16129 .find_all_references_task_sources
16130 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16131 {
16132 Ok(_) => {
16133 log::info!(
16134 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16135 );
16136 return None;
16137 }
16138 Err(i) => {
16139 self.find_all_references_task_sources.insert(i, head_anchor);
16140 }
16141 }
16142
16143 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16144 let workspace = self.workspace()?;
16145 let project = workspace.read(cx).project().clone();
16146 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16147 Some(cx.spawn_in(window, async move |editor, cx| {
16148 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16149 if let Ok(i) = editor
16150 .find_all_references_task_sources
16151 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16152 {
16153 editor.find_all_references_task_sources.remove(i);
16154 }
16155 });
16156
16157 let Some(locations) = references.await? else {
16158 return anyhow::Ok(Navigated::No);
16159 };
16160 if locations.is_empty() {
16161 return anyhow::Ok(Navigated::No);
16162 }
16163
16164 workspace.update_in(cx, |workspace, window, cx| {
16165 let target = locations
16166 .iter()
16167 .map(|location| {
16168 location
16169 .buffer
16170 .read(cx)
16171 .text_for_range(location.range.clone())
16172 .collect::<String>()
16173 })
16174 .filter(|text| !text.contains('\n'))
16175 .unique()
16176 .take(3)
16177 .join(", ");
16178 let title = if target.is_empty() {
16179 "References".to_owned()
16180 } else {
16181 format!("References to {target}")
16182 };
16183 Self::open_locations_in_multibuffer(
16184 workspace,
16185 locations,
16186 title,
16187 false,
16188 MultibufferSelectionMode::First,
16189 window,
16190 cx,
16191 );
16192 Navigated::Yes
16193 })
16194 }))
16195 }
16196
16197 /// Opens a multibuffer with the given project locations in it
16198 pub fn open_locations_in_multibuffer(
16199 workspace: &mut Workspace,
16200 mut locations: Vec<Location>,
16201 title: String,
16202 split: bool,
16203 multibuffer_selection_mode: MultibufferSelectionMode,
16204 window: &mut Window,
16205 cx: &mut Context<Workspace>,
16206 ) {
16207 if locations.is_empty() {
16208 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16209 return;
16210 }
16211
16212 // If there are multiple definitions, open them in a multibuffer
16213 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16214 let mut locations = locations.into_iter().peekable();
16215 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16216 let capability = workspace.project().read(cx).capability();
16217
16218 let excerpt_buffer = cx.new(|cx| {
16219 let mut multibuffer = MultiBuffer::new(capability);
16220 while let Some(location) = locations.next() {
16221 let buffer = location.buffer.read(cx);
16222 let mut ranges_for_buffer = Vec::new();
16223 let range = location.range.to_point(buffer);
16224 ranges_for_buffer.push(range.clone());
16225
16226 while let Some(next_location) = locations.peek() {
16227 if next_location.buffer == location.buffer {
16228 ranges_for_buffer.push(next_location.range.to_point(buffer));
16229 locations.next();
16230 } else {
16231 break;
16232 }
16233 }
16234
16235 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16236 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16237 PathKey::for_buffer(&location.buffer, cx),
16238 location.buffer.clone(),
16239 ranges_for_buffer,
16240 DEFAULT_MULTIBUFFER_CONTEXT,
16241 cx,
16242 );
16243 ranges.extend(new_ranges)
16244 }
16245
16246 multibuffer.with_title(title)
16247 });
16248
16249 let editor = cx.new(|cx| {
16250 Editor::for_multibuffer(
16251 excerpt_buffer,
16252 Some(workspace.project().clone()),
16253 window,
16254 cx,
16255 )
16256 });
16257 editor.update(cx, |editor, cx| {
16258 match multibuffer_selection_mode {
16259 MultibufferSelectionMode::First => {
16260 if let Some(first_range) = ranges.first() {
16261 editor.change_selections(
16262 SelectionEffects::no_scroll(),
16263 window,
16264 cx,
16265 |selections| {
16266 selections.clear_disjoint();
16267 selections
16268 .select_anchor_ranges(std::iter::once(first_range.clone()));
16269 },
16270 );
16271 }
16272 editor.highlight_background::<Self>(
16273 &ranges,
16274 |theme| theme.colors().editor_highlighted_line_background,
16275 cx,
16276 );
16277 }
16278 MultibufferSelectionMode::All => {
16279 editor.change_selections(
16280 SelectionEffects::no_scroll(),
16281 window,
16282 cx,
16283 |selections| {
16284 selections.clear_disjoint();
16285 selections.select_anchor_ranges(ranges);
16286 },
16287 );
16288 }
16289 }
16290 editor.register_buffers_with_language_servers(cx);
16291 });
16292
16293 let item = Box::new(editor);
16294 let item_id = item.item_id();
16295
16296 if split {
16297 workspace.split_item(SplitDirection::Right, item, window, cx);
16298 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16299 let (preview_item_id, preview_item_idx) =
16300 workspace.active_pane().read_with(cx, |pane, _| {
16301 (pane.preview_item_id(), pane.preview_item_idx())
16302 });
16303
16304 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16305
16306 if let Some(preview_item_id) = preview_item_id {
16307 workspace.active_pane().update(cx, |pane, cx| {
16308 pane.remove_item(preview_item_id, false, false, window, cx);
16309 });
16310 }
16311 } else {
16312 workspace.add_item_to_active_pane(item, None, true, window, cx);
16313 }
16314 workspace.active_pane().update(cx, |pane, cx| {
16315 pane.set_preview_item_id(Some(item_id), cx);
16316 });
16317 }
16318
16319 pub fn rename(
16320 &mut self,
16321 _: &Rename,
16322 window: &mut Window,
16323 cx: &mut Context<Self>,
16324 ) -> Option<Task<Result<()>>> {
16325 use language::ToOffset as _;
16326
16327 let provider = self.semantics_provider.clone()?;
16328 let selection = self.selections.newest_anchor().clone();
16329 let (cursor_buffer, cursor_buffer_position) = self
16330 .buffer
16331 .read(cx)
16332 .text_anchor_for_position(selection.head(), cx)?;
16333 let (tail_buffer, cursor_buffer_position_end) = self
16334 .buffer
16335 .read(cx)
16336 .text_anchor_for_position(selection.tail(), cx)?;
16337 if tail_buffer != cursor_buffer {
16338 return None;
16339 }
16340
16341 let snapshot = cursor_buffer.read(cx).snapshot();
16342 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16343 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16344 let prepare_rename = provider
16345 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16346 .unwrap_or_else(|| Task::ready(Ok(None)));
16347 drop(snapshot);
16348
16349 Some(cx.spawn_in(window, async move |this, cx| {
16350 let rename_range = if let Some(range) = prepare_rename.await? {
16351 Some(range)
16352 } else {
16353 this.update(cx, |this, cx| {
16354 let buffer = this.buffer.read(cx).snapshot(cx);
16355 let mut buffer_highlights = this
16356 .document_highlights_for_position(selection.head(), &buffer)
16357 .filter(|highlight| {
16358 highlight.start.excerpt_id == selection.head().excerpt_id
16359 && highlight.end.excerpt_id == selection.head().excerpt_id
16360 });
16361 buffer_highlights
16362 .next()
16363 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16364 })?
16365 };
16366 if let Some(rename_range) = rename_range {
16367 this.update_in(cx, |this, window, cx| {
16368 let snapshot = cursor_buffer.read(cx).snapshot();
16369 let rename_buffer_range = rename_range.to_offset(&snapshot);
16370 let cursor_offset_in_rename_range =
16371 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16372 let cursor_offset_in_rename_range_end =
16373 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16374
16375 this.take_rename(false, window, cx);
16376 let buffer = this.buffer.read(cx).read(cx);
16377 let cursor_offset = selection.head().to_offset(&buffer);
16378 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16379 let rename_end = rename_start + rename_buffer_range.len();
16380 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16381 let mut old_highlight_id = None;
16382 let old_name: Arc<str> = buffer
16383 .chunks(rename_start..rename_end, true)
16384 .map(|chunk| {
16385 if old_highlight_id.is_none() {
16386 old_highlight_id = chunk.syntax_highlight_id;
16387 }
16388 chunk.text
16389 })
16390 .collect::<String>()
16391 .into();
16392
16393 drop(buffer);
16394
16395 // Position the selection in the rename editor so that it matches the current selection.
16396 this.show_local_selections = false;
16397 let rename_editor = cx.new(|cx| {
16398 let mut editor = Editor::single_line(window, cx);
16399 editor.buffer.update(cx, |buffer, cx| {
16400 buffer.edit([(0..0, old_name.clone())], None, cx)
16401 });
16402 let rename_selection_range = match cursor_offset_in_rename_range
16403 .cmp(&cursor_offset_in_rename_range_end)
16404 {
16405 Ordering::Equal => {
16406 editor.select_all(&SelectAll, window, cx);
16407 return editor;
16408 }
16409 Ordering::Less => {
16410 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16411 }
16412 Ordering::Greater => {
16413 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16414 }
16415 };
16416 if rename_selection_range.end > old_name.len() {
16417 editor.select_all(&SelectAll, window, cx);
16418 } else {
16419 editor.change_selections(Default::default(), window, cx, |s| {
16420 s.select_ranges([rename_selection_range]);
16421 });
16422 }
16423 editor
16424 });
16425 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16426 if e == &EditorEvent::Focused {
16427 cx.emit(EditorEvent::FocusedIn)
16428 }
16429 })
16430 .detach();
16431
16432 let write_highlights =
16433 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16434 let read_highlights =
16435 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16436 let ranges = write_highlights
16437 .iter()
16438 .flat_map(|(_, ranges)| ranges.iter())
16439 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16440 .cloned()
16441 .collect();
16442
16443 this.highlight_text::<Rename>(
16444 ranges,
16445 HighlightStyle {
16446 fade_out: Some(0.6),
16447 ..Default::default()
16448 },
16449 cx,
16450 );
16451 let rename_focus_handle = rename_editor.focus_handle(cx);
16452 window.focus(&rename_focus_handle);
16453 let block_id = this.insert_blocks(
16454 [BlockProperties {
16455 style: BlockStyle::Flex,
16456 placement: BlockPlacement::Below(range.start),
16457 height: Some(1),
16458 render: Arc::new({
16459 let rename_editor = rename_editor.clone();
16460 move |cx: &mut BlockContext| {
16461 let mut text_style = cx.editor_style.text.clone();
16462 if let Some(highlight_style) = old_highlight_id
16463 .and_then(|h| h.style(&cx.editor_style.syntax))
16464 {
16465 text_style = text_style.highlight(highlight_style);
16466 }
16467 div()
16468 .block_mouse_except_scroll()
16469 .pl(cx.anchor_x)
16470 .child(EditorElement::new(
16471 &rename_editor,
16472 EditorStyle {
16473 background: cx.theme().system().transparent,
16474 local_player: cx.editor_style.local_player,
16475 text: text_style,
16476 scrollbar_width: cx.editor_style.scrollbar_width,
16477 syntax: cx.editor_style.syntax.clone(),
16478 status: cx.editor_style.status.clone(),
16479 inlay_hints_style: HighlightStyle {
16480 font_weight: Some(FontWeight::BOLD),
16481 ..make_inlay_hints_style(cx.app)
16482 },
16483 edit_prediction_styles: make_suggestion_styles(
16484 cx.app,
16485 ),
16486 ..EditorStyle::default()
16487 },
16488 ))
16489 .into_any_element()
16490 }
16491 }),
16492 priority: 0,
16493 }],
16494 Some(Autoscroll::fit()),
16495 cx,
16496 )[0];
16497 this.pending_rename = Some(RenameState {
16498 range,
16499 old_name,
16500 editor: rename_editor,
16501 block_id,
16502 });
16503 })?;
16504 }
16505
16506 Ok(())
16507 }))
16508 }
16509
16510 pub fn confirm_rename(
16511 &mut self,
16512 _: &ConfirmRename,
16513 window: &mut Window,
16514 cx: &mut Context<Self>,
16515 ) -> Option<Task<Result<()>>> {
16516 let rename = self.take_rename(false, window, cx)?;
16517 let workspace = self.workspace()?.downgrade();
16518 let (buffer, start) = self
16519 .buffer
16520 .read(cx)
16521 .text_anchor_for_position(rename.range.start, cx)?;
16522 let (end_buffer, _) = self
16523 .buffer
16524 .read(cx)
16525 .text_anchor_for_position(rename.range.end, cx)?;
16526 if buffer != end_buffer {
16527 return None;
16528 }
16529
16530 let old_name = rename.old_name;
16531 let new_name = rename.editor.read(cx).text(cx);
16532
16533 let rename = self.semantics_provider.as_ref()?.perform_rename(
16534 &buffer,
16535 start,
16536 new_name.clone(),
16537 cx,
16538 )?;
16539
16540 Some(cx.spawn_in(window, async move |editor, cx| {
16541 let project_transaction = rename.await?;
16542 Self::open_project_transaction(
16543 &editor,
16544 workspace,
16545 project_transaction,
16546 format!("Rename: {} → {}", old_name, new_name),
16547 cx,
16548 )
16549 .await?;
16550
16551 editor.update(cx, |editor, cx| {
16552 editor.refresh_document_highlights(cx);
16553 })?;
16554 Ok(())
16555 }))
16556 }
16557
16558 fn take_rename(
16559 &mut self,
16560 moving_cursor: bool,
16561 window: &mut Window,
16562 cx: &mut Context<Self>,
16563 ) -> Option<RenameState> {
16564 let rename = self.pending_rename.take()?;
16565 if rename.editor.focus_handle(cx).is_focused(window) {
16566 window.focus(&self.focus_handle);
16567 }
16568
16569 self.remove_blocks(
16570 [rename.block_id].into_iter().collect(),
16571 Some(Autoscroll::fit()),
16572 cx,
16573 );
16574 self.clear_highlights::<Rename>(cx);
16575 self.show_local_selections = true;
16576
16577 if moving_cursor {
16578 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16579 editor.selections.newest::<usize>(cx).head()
16580 });
16581
16582 // Update the selection to match the position of the selection inside
16583 // the rename editor.
16584 let snapshot = self.buffer.read(cx).read(cx);
16585 let rename_range = rename.range.to_offset(&snapshot);
16586 let cursor_in_editor = snapshot
16587 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16588 .min(rename_range.end);
16589 drop(snapshot);
16590
16591 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16592 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16593 });
16594 } else {
16595 self.refresh_document_highlights(cx);
16596 }
16597
16598 Some(rename)
16599 }
16600
16601 pub fn pending_rename(&self) -> Option<&RenameState> {
16602 self.pending_rename.as_ref()
16603 }
16604
16605 fn format(
16606 &mut self,
16607 _: &Format,
16608 window: &mut Window,
16609 cx: &mut Context<Self>,
16610 ) -> Option<Task<Result<()>>> {
16611 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16612
16613 let project = match &self.project {
16614 Some(project) => project.clone(),
16615 None => return None,
16616 };
16617
16618 Some(self.perform_format(
16619 project,
16620 FormatTrigger::Manual,
16621 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16622 window,
16623 cx,
16624 ))
16625 }
16626
16627 fn format_selections(
16628 &mut self,
16629 _: &FormatSelections,
16630 window: &mut Window,
16631 cx: &mut Context<Self>,
16632 ) -> Option<Task<Result<()>>> {
16633 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16634
16635 let project = match &self.project {
16636 Some(project) => project.clone(),
16637 None => return None,
16638 };
16639
16640 let ranges = self
16641 .selections
16642 .all_adjusted(cx)
16643 .into_iter()
16644 .map(|selection| selection.range())
16645 .collect_vec();
16646
16647 Some(self.perform_format(
16648 project,
16649 FormatTrigger::Manual,
16650 FormatTarget::Ranges(ranges),
16651 window,
16652 cx,
16653 ))
16654 }
16655
16656 fn perform_format(
16657 &mut self,
16658 project: Entity<Project>,
16659 trigger: FormatTrigger,
16660 target: FormatTarget,
16661 window: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) -> Task<Result<()>> {
16664 let buffer = self.buffer.clone();
16665 let (buffers, target) = match target {
16666 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16667 FormatTarget::Ranges(selection_ranges) => {
16668 let multi_buffer = buffer.read(cx);
16669 let snapshot = multi_buffer.read(cx);
16670 let mut buffers = HashSet::default();
16671 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16672 BTreeMap::new();
16673 for selection_range in selection_ranges {
16674 for (buffer, buffer_range, _) in
16675 snapshot.range_to_buffer_ranges(selection_range)
16676 {
16677 let buffer_id = buffer.remote_id();
16678 let start = buffer.anchor_before(buffer_range.start);
16679 let end = buffer.anchor_after(buffer_range.end);
16680 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16681 buffer_id_to_ranges
16682 .entry(buffer_id)
16683 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16684 .or_insert_with(|| vec![start..end]);
16685 }
16686 }
16687 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16688 }
16689 };
16690
16691 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16692 let selections_prev = transaction_id_prev
16693 .and_then(|transaction_id_prev| {
16694 // default to selections as they were after the last edit, if we have them,
16695 // instead of how they are now.
16696 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16697 // will take you back to where you made the last edit, instead of staying where you scrolled
16698 self.selection_history
16699 .transaction(transaction_id_prev)
16700 .map(|t| t.0.clone())
16701 })
16702 .unwrap_or_else(|| self.selections.disjoint_anchors());
16703
16704 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16705 let format = project.update(cx, |project, cx| {
16706 project.format(buffers, target, true, trigger, cx)
16707 });
16708
16709 cx.spawn_in(window, async move |editor, cx| {
16710 let transaction = futures::select_biased! {
16711 transaction = format.log_err().fuse() => transaction,
16712 () = timeout => {
16713 log::warn!("timed out waiting for formatting");
16714 None
16715 }
16716 };
16717
16718 buffer
16719 .update(cx, |buffer, cx| {
16720 if let Some(transaction) = transaction
16721 && !buffer.is_singleton()
16722 {
16723 buffer.push_transaction(&transaction.0, cx);
16724 }
16725 cx.notify();
16726 })
16727 .ok();
16728
16729 if let Some(transaction_id_now) =
16730 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16731 {
16732 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16733 if has_new_transaction {
16734 _ = editor.update(cx, |editor, _| {
16735 editor
16736 .selection_history
16737 .insert_transaction(transaction_id_now, selections_prev);
16738 });
16739 }
16740 }
16741
16742 Ok(())
16743 })
16744 }
16745
16746 fn organize_imports(
16747 &mut self,
16748 _: &OrganizeImports,
16749 window: &mut Window,
16750 cx: &mut Context<Self>,
16751 ) -> Option<Task<Result<()>>> {
16752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16753 let project = match &self.project {
16754 Some(project) => project.clone(),
16755 None => return None,
16756 };
16757 Some(self.perform_code_action_kind(
16758 project,
16759 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16760 window,
16761 cx,
16762 ))
16763 }
16764
16765 fn perform_code_action_kind(
16766 &mut self,
16767 project: Entity<Project>,
16768 kind: CodeActionKind,
16769 window: &mut Window,
16770 cx: &mut Context<Self>,
16771 ) -> Task<Result<()>> {
16772 let buffer = self.buffer.clone();
16773 let buffers = buffer.read(cx).all_buffers();
16774 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16775 let apply_action = project.update(cx, |project, cx| {
16776 project.apply_code_action_kind(buffers, kind, true, cx)
16777 });
16778 cx.spawn_in(window, async move |_, cx| {
16779 let transaction = futures::select_biased! {
16780 () = timeout => {
16781 log::warn!("timed out waiting for executing code action");
16782 None
16783 }
16784 transaction = apply_action.log_err().fuse() => transaction,
16785 };
16786 buffer
16787 .update(cx, |buffer, cx| {
16788 // check if we need this
16789 if let Some(transaction) = transaction
16790 && !buffer.is_singleton()
16791 {
16792 buffer.push_transaction(&transaction.0, cx);
16793 }
16794 cx.notify();
16795 })
16796 .ok();
16797 Ok(())
16798 })
16799 }
16800
16801 pub fn restart_language_server(
16802 &mut self,
16803 _: &RestartLanguageServer,
16804 _: &mut Window,
16805 cx: &mut Context<Self>,
16806 ) {
16807 if let Some(project) = self.project.clone() {
16808 self.buffer.update(cx, |multi_buffer, cx| {
16809 project.update(cx, |project, cx| {
16810 project.restart_language_servers_for_buffers(
16811 multi_buffer.all_buffers().into_iter().collect(),
16812 HashSet::default(),
16813 cx,
16814 );
16815 });
16816 })
16817 }
16818 }
16819
16820 pub fn stop_language_server(
16821 &mut self,
16822 _: &StopLanguageServer,
16823 _: &mut Window,
16824 cx: &mut Context<Self>,
16825 ) {
16826 if let Some(project) = self.project.clone() {
16827 self.buffer.update(cx, |multi_buffer, cx| {
16828 project.update(cx, |project, cx| {
16829 project.stop_language_servers_for_buffers(
16830 multi_buffer.all_buffers().into_iter().collect(),
16831 HashSet::default(),
16832 cx,
16833 );
16834 cx.emit(project::Event::RefreshInlayHints);
16835 });
16836 });
16837 }
16838 }
16839
16840 fn cancel_language_server_work(
16841 workspace: &mut Workspace,
16842 _: &actions::CancelLanguageServerWork,
16843 _: &mut Window,
16844 cx: &mut Context<Workspace>,
16845 ) {
16846 let project = workspace.project();
16847 let buffers = workspace
16848 .active_item(cx)
16849 .and_then(|item| item.act_as::<Editor>(cx))
16850 .map_or(HashSet::default(), |editor| {
16851 editor.read(cx).buffer.read(cx).all_buffers()
16852 });
16853 project.update(cx, |project, cx| {
16854 project.cancel_language_server_work_for_buffers(buffers, cx);
16855 });
16856 }
16857
16858 fn show_character_palette(
16859 &mut self,
16860 _: &ShowCharacterPalette,
16861 window: &mut Window,
16862 _: &mut Context<Self>,
16863 ) {
16864 window.show_character_palette();
16865 }
16866
16867 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16868 if !self.diagnostics_enabled() {
16869 return;
16870 }
16871
16872 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16873 let buffer = self.buffer.read(cx).snapshot(cx);
16874 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16875 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16876 let is_valid = buffer
16877 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16878 .any(|entry| {
16879 entry.diagnostic.is_primary
16880 && !entry.range.is_empty()
16881 && entry.range.start == primary_range_start
16882 && entry.diagnostic.message == active_diagnostics.active_message
16883 });
16884
16885 if !is_valid {
16886 self.dismiss_diagnostics(cx);
16887 }
16888 }
16889 }
16890
16891 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16892 match &self.active_diagnostics {
16893 ActiveDiagnostic::Group(group) => Some(group),
16894 _ => None,
16895 }
16896 }
16897
16898 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16899 if !self.diagnostics_enabled() {
16900 return;
16901 }
16902 self.dismiss_diagnostics(cx);
16903 self.active_diagnostics = ActiveDiagnostic::All;
16904 }
16905
16906 fn activate_diagnostics(
16907 &mut self,
16908 buffer_id: BufferId,
16909 diagnostic: DiagnosticEntry<usize>,
16910 window: &mut Window,
16911 cx: &mut Context<Self>,
16912 ) {
16913 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16914 return;
16915 }
16916 self.dismiss_diagnostics(cx);
16917 let snapshot = self.snapshot(window, cx);
16918 let buffer = self.buffer.read(cx).snapshot(cx);
16919 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16920 return;
16921 };
16922
16923 let diagnostic_group = buffer
16924 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16925 .collect::<Vec<_>>();
16926
16927 let blocks =
16928 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16929
16930 let blocks = self.display_map.update(cx, |display_map, cx| {
16931 display_map.insert_blocks(blocks, cx).into_iter().collect()
16932 });
16933 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16934 active_range: buffer.anchor_before(diagnostic.range.start)
16935 ..buffer.anchor_after(diagnostic.range.end),
16936 active_message: diagnostic.diagnostic.message.clone(),
16937 group_id: diagnostic.diagnostic.group_id,
16938 blocks,
16939 });
16940 cx.notify();
16941 }
16942
16943 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16944 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16945 return;
16946 };
16947
16948 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16949 if let ActiveDiagnostic::Group(group) = prev {
16950 self.display_map.update(cx, |display_map, cx| {
16951 display_map.remove_blocks(group.blocks, cx);
16952 });
16953 cx.notify();
16954 }
16955 }
16956
16957 /// Disable inline diagnostics rendering for this editor.
16958 pub fn disable_inline_diagnostics(&mut self) {
16959 self.inline_diagnostics_enabled = false;
16960 self.inline_diagnostics_update = Task::ready(());
16961 self.inline_diagnostics.clear();
16962 }
16963
16964 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16965 self.diagnostics_enabled = false;
16966 self.dismiss_diagnostics(cx);
16967 self.inline_diagnostics_update = Task::ready(());
16968 self.inline_diagnostics.clear();
16969 }
16970
16971 pub fn diagnostics_enabled(&self) -> bool {
16972 self.diagnostics_enabled && self.mode.is_full()
16973 }
16974
16975 pub fn inline_diagnostics_enabled(&self) -> bool {
16976 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16977 }
16978
16979 pub fn show_inline_diagnostics(&self) -> bool {
16980 self.show_inline_diagnostics
16981 }
16982
16983 pub fn toggle_inline_diagnostics(
16984 &mut self,
16985 _: &ToggleInlineDiagnostics,
16986 window: &mut Window,
16987 cx: &mut Context<Editor>,
16988 ) {
16989 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16990 self.refresh_inline_diagnostics(false, window, cx);
16991 }
16992
16993 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16994 self.diagnostics_max_severity = severity;
16995 self.display_map.update(cx, |display_map, _| {
16996 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16997 });
16998 }
16999
17000 pub fn toggle_diagnostics(
17001 &mut self,
17002 _: &ToggleDiagnostics,
17003 window: &mut Window,
17004 cx: &mut Context<Editor>,
17005 ) {
17006 if !self.diagnostics_enabled() {
17007 return;
17008 }
17009
17010 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17011 EditorSettings::get_global(cx)
17012 .diagnostics_max_severity
17013 .filter(|severity| severity != &DiagnosticSeverity::Off)
17014 .unwrap_or(DiagnosticSeverity::Hint)
17015 } else {
17016 DiagnosticSeverity::Off
17017 };
17018 self.set_max_diagnostics_severity(new_severity, cx);
17019 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17020 self.active_diagnostics = ActiveDiagnostic::None;
17021 self.inline_diagnostics_update = Task::ready(());
17022 self.inline_diagnostics.clear();
17023 } else {
17024 self.refresh_inline_diagnostics(false, window, cx);
17025 }
17026
17027 cx.notify();
17028 }
17029
17030 pub fn toggle_minimap(
17031 &mut self,
17032 _: &ToggleMinimap,
17033 window: &mut Window,
17034 cx: &mut Context<Editor>,
17035 ) {
17036 if self.supports_minimap(cx) {
17037 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17038 }
17039 }
17040
17041 fn refresh_inline_diagnostics(
17042 &mut self,
17043 debounce: bool,
17044 window: &mut Window,
17045 cx: &mut Context<Self>,
17046 ) {
17047 let max_severity = ProjectSettings::get_global(cx)
17048 .diagnostics
17049 .inline
17050 .max_severity
17051 .unwrap_or(self.diagnostics_max_severity);
17052
17053 if !self.inline_diagnostics_enabled()
17054 || !self.show_inline_diagnostics
17055 || max_severity == DiagnosticSeverity::Off
17056 {
17057 self.inline_diagnostics_update = Task::ready(());
17058 self.inline_diagnostics.clear();
17059 return;
17060 }
17061
17062 let debounce_ms = ProjectSettings::get_global(cx)
17063 .diagnostics
17064 .inline
17065 .update_debounce_ms;
17066 let debounce = if debounce && debounce_ms > 0 {
17067 Some(Duration::from_millis(debounce_ms))
17068 } else {
17069 None
17070 };
17071 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17072 if let Some(debounce) = debounce {
17073 cx.background_executor().timer(debounce).await;
17074 }
17075 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17076 editor
17077 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17078 .ok()
17079 }) else {
17080 return;
17081 };
17082
17083 let new_inline_diagnostics = cx
17084 .background_spawn(async move {
17085 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17086 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17087 let message = diagnostic_entry
17088 .diagnostic
17089 .message
17090 .split_once('\n')
17091 .map(|(line, _)| line)
17092 .map(SharedString::new)
17093 .unwrap_or_else(|| {
17094 SharedString::from(diagnostic_entry.diagnostic.message)
17095 });
17096 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17097 let (Ok(i) | Err(i)) = inline_diagnostics
17098 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17099 inline_diagnostics.insert(
17100 i,
17101 (
17102 start_anchor,
17103 InlineDiagnostic {
17104 message,
17105 group_id: diagnostic_entry.diagnostic.group_id,
17106 start: diagnostic_entry.range.start.to_point(&snapshot),
17107 is_primary: diagnostic_entry.diagnostic.is_primary,
17108 severity: diagnostic_entry.diagnostic.severity,
17109 },
17110 ),
17111 );
17112 }
17113 inline_diagnostics
17114 })
17115 .await;
17116
17117 editor
17118 .update(cx, |editor, cx| {
17119 editor.inline_diagnostics = new_inline_diagnostics;
17120 cx.notify();
17121 })
17122 .ok();
17123 });
17124 }
17125
17126 fn pull_diagnostics(
17127 &mut self,
17128 buffer_id: Option<BufferId>,
17129 window: &Window,
17130 cx: &mut Context<Self>,
17131 ) -> Option<()> {
17132 if !self.mode().is_full() {
17133 return None;
17134 }
17135 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17136 .diagnostics
17137 .lsp_pull_diagnostics;
17138 if !pull_diagnostics_settings.enabled {
17139 return None;
17140 }
17141 let project = self.project()?.downgrade();
17142 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17143 let mut buffers = self.buffer.read(cx).all_buffers();
17144 if let Some(buffer_id) = buffer_id {
17145 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17146 }
17147
17148 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17149 cx.background_executor().timer(debounce).await;
17150
17151 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17152 buffers
17153 .into_iter()
17154 .filter_map(|buffer| {
17155 project
17156 .update(cx, |project, cx| {
17157 project.lsp_store().update(cx, |lsp_store, cx| {
17158 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17159 })
17160 })
17161 .ok()
17162 })
17163 .collect::<FuturesUnordered<_>>()
17164 }) else {
17165 return;
17166 };
17167
17168 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17169 match pull_task {
17170 Ok(()) => {
17171 if editor
17172 .update_in(cx, |editor, window, cx| {
17173 editor.update_diagnostics_state(window, cx);
17174 })
17175 .is_err()
17176 {
17177 return;
17178 }
17179 }
17180 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17181 }
17182 }
17183 });
17184
17185 Some(())
17186 }
17187
17188 pub fn set_selections_from_remote(
17189 &mut self,
17190 selections: Vec<Selection<Anchor>>,
17191 pending_selection: Option<Selection<Anchor>>,
17192 window: &mut Window,
17193 cx: &mut Context<Self>,
17194 ) {
17195 let old_cursor_position = self.selections.newest_anchor().head();
17196 self.selections.change_with(cx, |s| {
17197 s.select_anchors(selections);
17198 if let Some(pending_selection) = pending_selection {
17199 s.set_pending(pending_selection, SelectMode::Character);
17200 } else {
17201 s.clear_pending();
17202 }
17203 });
17204 self.selections_did_change(
17205 false,
17206 &old_cursor_position,
17207 SelectionEffects::default(),
17208 window,
17209 cx,
17210 );
17211 }
17212
17213 pub fn transact(
17214 &mut self,
17215 window: &mut Window,
17216 cx: &mut Context<Self>,
17217 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17218 ) -> Option<TransactionId> {
17219 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17220 this.start_transaction_at(Instant::now(), window, cx);
17221 update(this, window, cx);
17222 this.end_transaction_at(Instant::now(), cx)
17223 })
17224 }
17225
17226 pub fn start_transaction_at(
17227 &mut self,
17228 now: Instant,
17229 window: &mut Window,
17230 cx: &mut Context<Self>,
17231 ) -> Option<TransactionId> {
17232 self.end_selection(window, cx);
17233 if let Some(tx_id) = self
17234 .buffer
17235 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17236 {
17237 self.selection_history
17238 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17239 cx.emit(EditorEvent::TransactionBegun {
17240 transaction_id: tx_id,
17241 });
17242 Some(tx_id)
17243 } else {
17244 None
17245 }
17246 }
17247
17248 pub fn end_transaction_at(
17249 &mut self,
17250 now: Instant,
17251 cx: &mut Context<Self>,
17252 ) -> Option<TransactionId> {
17253 if let Some(transaction_id) = self
17254 .buffer
17255 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17256 {
17257 if let Some((_, end_selections)) =
17258 self.selection_history.transaction_mut(transaction_id)
17259 {
17260 *end_selections = Some(self.selections.disjoint_anchors());
17261 } else {
17262 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17263 }
17264
17265 cx.emit(EditorEvent::Edited { transaction_id });
17266 Some(transaction_id)
17267 } else {
17268 None
17269 }
17270 }
17271
17272 pub fn modify_transaction_selection_history(
17273 &mut self,
17274 transaction_id: TransactionId,
17275 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17276 ) -> bool {
17277 self.selection_history
17278 .transaction_mut(transaction_id)
17279 .map(modify)
17280 .is_some()
17281 }
17282
17283 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17284 if self.selection_mark_mode {
17285 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17286 s.move_with(|_, sel| {
17287 sel.collapse_to(sel.head(), SelectionGoal::None);
17288 });
17289 })
17290 }
17291 self.selection_mark_mode = true;
17292 cx.notify();
17293 }
17294
17295 pub fn swap_selection_ends(
17296 &mut self,
17297 _: &actions::SwapSelectionEnds,
17298 window: &mut Window,
17299 cx: &mut Context<Self>,
17300 ) {
17301 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17302 s.move_with(|_, sel| {
17303 if sel.start != sel.end {
17304 sel.reversed = !sel.reversed
17305 }
17306 });
17307 });
17308 self.request_autoscroll(Autoscroll::newest(), cx);
17309 cx.notify();
17310 }
17311
17312 pub fn toggle_focus(
17313 workspace: &mut Workspace,
17314 _: &actions::ToggleFocus,
17315 window: &mut Window,
17316 cx: &mut Context<Workspace>,
17317 ) {
17318 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17319 return;
17320 };
17321 workspace.activate_item(&item, true, true, window, cx);
17322 }
17323
17324 pub fn toggle_fold(
17325 &mut self,
17326 _: &actions::ToggleFold,
17327 window: &mut Window,
17328 cx: &mut Context<Self>,
17329 ) {
17330 if self.is_singleton(cx) {
17331 let selection = self.selections.newest::<Point>(cx);
17332
17333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17334 let range = if selection.is_empty() {
17335 let point = selection.head().to_display_point(&display_map);
17336 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17337 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17338 .to_point(&display_map);
17339 start..end
17340 } else {
17341 selection.range()
17342 };
17343 if display_map.folds_in_range(range).next().is_some() {
17344 self.unfold_lines(&Default::default(), window, cx)
17345 } else {
17346 self.fold(&Default::default(), window, cx)
17347 }
17348 } else {
17349 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17350 let buffer_ids: HashSet<_> = self
17351 .selections
17352 .disjoint_anchor_ranges()
17353 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17354 .collect();
17355
17356 let should_unfold = buffer_ids
17357 .iter()
17358 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17359
17360 for buffer_id in buffer_ids {
17361 if should_unfold {
17362 self.unfold_buffer(buffer_id, cx);
17363 } else {
17364 self.fold_buffer(buffer_id, cx);
17365 }
17366 }
17367 }
17368 }
17369
17370 pub fn toggle_fold_recursive(
17371 &mut self,
17372 _: &actions::ToggleFoldRecursive,
17373 window: &mut Window,
17374 cx: &mut Context<Self>,
17375 ) {
17376 let selection = self.selections.newest::<Point>(cx);
17377
17378 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17379 let range = if selection.is_empty() {
17380 let point = selection.head().to_display_point(&display_map);
17381 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17382 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17383 .to_point(&display_map);
17384 start..end
17385 } else {
17386 selection.range()
17387 };
17388 if display_map.folds_in_range(range).next().is_some() {
17389 self.unfold_recursive(&Default::default(), window, cx)
17390 } else {
17391 self.fold_recursive(&Default::default(), window, cx)
17392 }
17393 }
17394
17395 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17396 if self.is_singleton(cx) {
17397 let mut to_fold = Vec::new();
17398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17399 let selections = self.selections.all_adjusted(cx);
17400
17401 for selection in selections {
17402 let range = selection.range().sorted();
17403 let buffer_start_row = range.start.row;
17404
17405 if range.start.row != range.end.row {
17406 let mut found = false;
17407 let mut row = range.start.row;
17408 while row <= range.end.row {
17409 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17410 {
17411 found = true;
17412 row = crease.range().end.row + 1;
17413 to_fold.push(crease);
17414 } else {
17415 row += 1
17416 }
17417 }
17418 if found {
17419 continue;
17420 }
17421 }
17422
17423 for row in (0..=range.start.row).rev() {
17424 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17425 && crease.range().end.row >= buffer_start_row
17426 {
17427 to_fold.push(crease);
17428 if row <= range.start.row {
17429 break;
17430 }
17431 }
17432 }
17433 }
17434
17435 self.fold_creases(to_fold, true, window, cx);
17436 } else {
17437 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17438 let buffer_ids = self
17439 .selections
17440 .disjoint_anchor_ranges()
17441 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17442 .collect::<HashSet<_>>();
17443 for buffer_id in buffer_ids {
17444 self.fold_buffer(buffer_id, cx);
17445 }
17446 }
17447 }
17448
17449 pub fn toggle_fold_all(
17450 &mut self,
17451 _: &actions::ToggleFoldAll,
17452 window: &mut Window,
17453 cx: &mut Context<Self>,
17454 ) {
17455 if self.buffer.read(cx).is_singleton() {
17456 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17457 let has_folds = display_map
17458 .folds_in_range(0..display_map.buffer_snapshot.len())
17459 .next()
17460 .is_some();
17461
17462 if has_folds {
17463 self.unfold_all(&actions::UnfoldAll, window, cx);
17464 } else {
17465 self.fold_all(&actions::FoldAll, window, cx);
17466 }
17467 } else {
17468 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17469 let should_unfold = buffer_ids
17470 .iter()
17471 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17472
17473 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17474 editor
17475 .update_in(cx, |editor, _, cx| {
17476 for buffer_id in buffer_ids {
17477 if should_unfold {
17478 editor.unfold_buffer(buffer_id, cx);
17479 } else {
17480 editor.fold_buffer(buffer_id, cx);
17481 }
17482 }
17483 })
17484 .ok();
17485 });
17486 }
17487 }
17488
17489 fn fold_at_level(
17490 &mut self,
17491 fold_at: &FoldAtLevel,
17492 window: &mut Window,
17493 cx: &mut Context<Self>,
17494 ) {
17495 if !self.buffer.read(cx).is_singleton() {
17496 return;
17497 }
17498
17499 let fold_at_level = fold_at.0;
17500 let snapshot = self.buffer.read(cx).snapshot(cx);
17501 let mut to_fold = Vec::new();
17502 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17503
17504 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17505 while start_row < end_row {
17506 match self
17507 .snapshot(window, cx)
17508 .crease_for_buffer_row(MultiBufferRow(start_row))
17509 {
17510 Some(crease) => {
17511 let nested_start_row = crease.range().start.row + 1;
17512 let nested_end_row = crease.range().end.row;
17513
17514 if current_level < fold_at_level {
17515 stack.push((nested_start_row, nested_end_row, current_level + 1));
17516 } else if current_level == fold_at_level {
17517 to_fold.push(crease);
17518 }
17519
17520 start_row = nested_end_row + 1;
17521 }
17522 None => start_row += 1,
17523 }
17524 }
17525 }
17526
17527 self.fold_creases(to_fold, true, window, cx);
17528 }
17529
17530 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17531 if self.buffer.read(cx).is_singleton() {
17532 let mut fold_ranges = Vec::new();
17533 let snapshot = self.buffer.read(cx).snapshot(cx);
17534
17535 for row in 0..snapshot.max_row().0 {
17536 if let Some(foldable_range) = self
17537 .snapshot(window, cx)
17538 .crease_for_buffer_row(MultiBufferRow(row))
17539 {
17540 fold_ranges.push(foldable_range);
17541 }
17542 }
17543
17544 self.fold_creases(fold_ranges, true, window, cx);
17545 } else {
17546 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17547 editor
17548 .update_in(cx, |editor, _, cx| {
17549 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17550 editor.fold_buffer(buffer_id, cx);
17551 }
17552 })
17553 .ok();
17554 });
17555 }
17556 }
17557
17558 pub fn fold_function_bodies(
17559 &mut self,
17560 _: &actions::FoldFunctionBodies,
17561 window: &mut Window,
17562 cx: &mut Context<Self>,
17563 ) {
17564 let snapshot = self.buffer.read(cx).snapshot(cx);
17565
17566 let ranges = snapshot
17567 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17568 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17569 .collect::<Vec<_>>();
17570
17571 let creases = ranges
17572 .into_iter()
17573 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17574 .collect();
17575
17576 self.fold_creases(creases, true, window, cx);
17577 }
17578
17579 pub fn fold_recursive(
17580 &mut self,
17581 _: &actions::FoldRecursive,
17582 window: &mut Window,
17583 cx: &mut Context<Self>,
17584 ) {
17585 let mut to_fold = Vec::new();
17586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17587 let selections = self.selections.all_adjusted(cx);
17588
17589 for selection in selections {
17590 let range = selection.range().sorted();
17591 let buffer_start_row = range.start.row;
17592
17593 if range.start.row != range.end.row {
17594 let mut found = false;
17595 for row in range.start.row..=range.end.row {
17596 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17597 found = true;
17598 to_fold.push(crease);
17599 }
17600 }
17601 if found {
17602 continue;
17603 }
17604 }
17605
17606 for row in (0..=range.start.row).rev() {
17607 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17608 if crease.range().end.row >= buffer_start_row {
17609 to_fold.push(crease);
17610 } else {
17611 break;
17612 }
17613 }
17614 }
17615 }
17616
17617 self.fold_creases(to_fold, true, window, cx);
17618 }
17619
17620 pub fn fold_at(
17621 &mut self,
17622 buffer_row: MultiBufferRow,
17623 window: &mut Window,
17624 cx: &mut Context<Self>,
17625 ) {
17626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17627
17628 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17629 let autoscroll = self
17630 .selections
17631 .all::<Point>(cx)
17632 .iter()
17633 .any(|selection| crease.range().overlaps(&selection.range()));
17634
17635 self.fold_creases(vec![crease], autoscroll, window, cx);
17636 }
17637 }
17638
17639 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17640 if self.is_singleton(cx) {
17641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17642 let buffer = &display_map.buffer_snapshot;
17643 let selections = self.selections.all::<Point>(cx);
17644 let ranges = selections
17645 .iter()
17646 .map(|s| {
17647 let range = s.display_range(&display_map).sorted();
17648 let mut start = range.start.to_point(&display_map);
17649 let mut end = range.end.to_point(&display_map);
17650 start.column = 0;
17651 end.column = buffer.line_len(MultiBufferRow(end.row));
17652 start..end
17653 })
17654 .collect::<Vec<_>>();
17655
17656 self.unfold_ranges(&ranges, true, true, cx);
17657 } else {
17658 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17659 let buffer_ids = self
17660 .selections
17661 .disjoint_anchor_ranges()
17662 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17663 .collect::<HashSet<_>>();
17664 for buffer_id in buffer_ids {
17665 self.unfold_buffer(buffer_id, cx);
17666 }
17667 }
17668 }
17669
17670 pub fn unfold_recursive(
17671 &mut self,
17672 _: &UnfoldRecursive,
17673 _window: &mut Window,
17674 cx: &mut Context<Self>,
17675 ) {
17676 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17677 let selections = self.selections.all::<Point>(cx);
17678 let ranges = selections
17679 .iter()
17680 .map(|s| {
17681 let mut range = s.display_range(&display_map).sorted();
17682 *range.start.column_mut() = 0;
17683 *range.end.column_mut() = display_map.line_len(range.end.row());
17684 let start = range.start.to_point(&display_map);
17685 let end = range.end.to_point(&display_map);
17686 start..end
17687 })
17688 .collect::<Vec<_>>();
17689
17690 self.unfold_ranges(&ranges, true, true, cx);
17691 }
17692
17693 pub fn unfold_at(
17694 &mut self,
17695 buffer_row: MultiBufferRow,
17696 _window: &mut Window,
17697 cx: &mut Context<Self>,
17698 ) {
17699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17700
17701 let intersection_range = Point::new(buffer_row.0, 0)
17702 ..Point::new(
17703 buffer_row.0,
17704 display_map.buffer_snapshot.line_len(buffer_row),
17705 );
17706
17707 let autoscroll = self
17708 .selections
17709 .all::<Point>(cx)
17710 .iter()
17711 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17712
17713 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17714 }
17715
17716 pub fn unfold_all(
17717 &mut self,
17718 _: &actions::UnfoldAll,
17719 _window: &mut Window,
17720 cx: &mut Context<Self>,
17721 ) {
17722 if self.buffer.read(cx).is_singleton() {
17723 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17724 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17725 } else {
17726 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17727 editor
17728 .update(cx, |editor, cx| {
17729 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17730 editor.unfold_buffer(buffer_id, cx);
17731 }
17732 })
17733 .ok();
17734 });
17735 }
17736 }
17737
17738 pub fn fold_selected_ranges(
17739 &mut self,
17740 _: &FoldSelectedRanges,
17741 window: &mut Window,
17742 cx: &mut Context<Self>,
17743 ) {
17744 let selections = self.selections.all_adjusted(cx);
17745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17746 let ranges = selections
17747 .into_iter()
17748 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17749 .collect::<Vec<_>>();
17750 self.fold_creases(ranges, true, window, cx);
17751 }
17752
17753 pub fn fold_ranges<T: ToOffset + Clone>(
17754 &mut self,
17755 ranges: Vec<Range<T>>,
17756 auto_scroll: bool,
17757 window: &mut Window,
17758 cx: &mut Context<Self>,
17759 ) {
17760 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17761 let ranges = ranges
17762 .into_iter()
17763 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17764 .collect::<Vec<_>>();
17765 self.fold_creases(ranges, auto_scroll, window, cx);
17766 }
17767
17768 pub fn fold_creases<T: ToOffset + Clone>(
17769 &mut self,
17770 creases: Vec<Crease<T>>,
17771 auto_scroll: bool,
17772 _window: &mut Window,
17773 cx: &mut Context<Self>,
17774 ) {
17775 if creases.is_empty() {
17776 return;
17777 }
17778
17779 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17780
17781 if auto_scroll {
17782 self.request_autoscroll(Autoscroll::fit(), cx);
17783 }
17784
17785 cx.notify();
17786
17787 self.scrollbar_marker_state.dirty = true;
17788 self.folds_did_change(cx);
17789 }
17790
17791 /// Removes any folds whose ranges intersect any of the given ranges.
17792 pub fn unfold_ranges<T: ToOffset + Clone>(
17793 &mut self,
17794 ranges: &[Range<T>],
17795 inclusive: bool,
17796 auto_scroll: bool,
17797 cx: &mut Context<Self>,
17798 ) {
17799 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17800 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17801 });
17802 self.folds_did_change(cx);
17803 }
17804
17805 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17806 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17807 return;
17808 }
17809 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17810 self.display_map.update(cx, |display_map, cx| {
17811 display_map.fold_buffers([buffer_id], cx)
17812 });
17813 cx.emit(EditorEvent::BufferFoldToggled {
17814 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17815 folded: true,
17816 });
17817 cx.notify();
17818 }
17819
17820 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17821 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17822 return;
17823 }
17824 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17825 self.display_map.update(cx, |display_map, cx| {
17826 display_map.unfold_buffers([buffer_id], cx);
17827 });
17828 cx.emit(EditorEvent::BufferFoldToggled {
17829 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17830 folded: false,
17831 });
17832 cx.notify();
17833 }
17834
17835 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17836 self.display_map.read(cx).is_buffer_folded(buffer)
17837 }
17838
17839 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17840 self.display_map.read(cx).folded_buffers()
17841 }
17842
17843 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17844 self.display_map.update(cx, |display_map, cx| {
17845 display_map.disable_header_for_buffer(buffer_id, cx);
17846 });
17847 cx.notify();
17848 }
17849
17850 /// Removes any folds with the given ranges.
17851 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17852 &mut self,
17853 ranges: &[Range<T>],
17854 type_id: TypeId,
17855 auto_scroll: bool,
17856 cx: &mut Context<Self>,
17857 ) {
17858 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17859 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17860 });
17861 self.folds_did_change(cx);
17862 }
17863
17864 fn remove_folds_with<T: ToOffset + Clone>(
17865 &mut self,
17866 ranges: &[Range<T>],
17867 auto_scroll: bool,
17868 cx: &mut Context<Self>,
17869 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17870 ) {
17871 if ranges.is_empty() {
17872 return;
17873 }
17874
17875 let mut buffers_affected = HashSet::default();
17876 let multi_buffer = self.buffer().read(cx);
17877 for range in ranges {
17878 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17879 buffers_affected.insert(buffer.read(cx).remote_id());
17880 };
17881 }
17882
17883 self.display_map.update(cx, update);
17884
17885 if auto_scroll {
17886 self.request_autoscroll(Autoscroll::fit(), cx);
17887 }
17888
17889 cx.notify();
17890 self.scrollbar_marker_state.dirty = true;
17891 self.active_indent_guides_state.dirty = true;
17892 }
17893
17894 pub fn update_renderer_widths(
17895 &mut self,
17896 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17897 cx: &mut Context<Self>,
17898 ) -> bool {
17899 self.display_map
17900 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17901 }
17902
17903 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17904 self.display_map.read(cx).fold_placeholder.clone()
17905 }
17906
17907 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17908 self.buffer.update(cx, |buffer, cx| {
17909 buffer.set_all_diff_hunks_expanded(cx);
17910 });
17911 }
17912
17913 pub fn expand_all_diff_hunks(
17914 &mut self,
17915 _: &ExpandAllDiffHunks,
17916 _window: &mut Window,
17917 cx: &mut Context<Self>,
17918 ) {
17919 self.buffer.update(cx, |buffer, cx| {
17920 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17921 });
17922 }
17923
17924 pub fn toggle_selected_diff_hunks(
17925 &mut self,
17926 _: &ToggleSelectedDiffHunks,
17927 _window: &mut Window,
17928 cx: &mut Context<Self>,
17929 ) {
17930 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17931 self.toggle_diff_hunks_in_ranges(ranges, cx);
17932 }
17933
17934 pub fn diff_hunks_in_ranges<'a>(
17935 &'a self,
17936 ranges: &'a [Range<Anchor>],
17937 buffer: &'a MultiBufferSnapshot,
17938 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17939 ranges.iter().flat_map(move |range| {
17940 let end_excerpt_id = range.end.excerpt_id;
17941 let range = range.to_point(buffer);
17942 let mut peek_end = range.end;
17943 if range.end.row < buffer.max_row().0 {
17944 peek_end = Point::new(range.end.row + 1, 0);
17945 }
17946 buffer
17947 .diff_hunks_in_range(range.start..peek_end)
17948 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17949 })
17950 }
17951
17952 pub fn has_stageable_diff_hunks_in_ranges(
17953 &self,
17954 ranges: &[Range<Anchor>],
17955 snapshot: &MultiBufferSnapshot,
17956 ) -> bool {
17957 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17958 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17959 }
17960
17961 pub fn toggle_staged_selected_diff_hunks(
17962 &mut self,
17963 _: &::git::ToggleStaged,
17964 _: &mut Window,
17965 cx: &mut Context<Self>,
17966 ) {
17967 let snapshot = self.buffer.read(cx).snapshot(cx);
17968 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17969 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17970 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17971 }
17972
17973 pub fn set_render_diff_hunk_controls(
17974 &mut self,
17975 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17976 cx: &mut Context<Self>,
17977 ) {
17978 self.render_diff_hunk_controls = render_diff_hunk_controls;
17979 cx.notify();
17980 }
17981
17982 pub fn stage_and_next(
17983 &mut self,
17984 _: &::git::StageAndNext,
17985 window: &mut Window,
17986 cx: &mut Context<Self>,
17987 ) {
17988 self.do_stage_or_unstage_and_next(true, window, cx);
17989 }
17990
17991 pub fn unstage_and_next(
17992 &mut self,
17993 _: &::git::UnstageAndNext,
17994 window: &mut Window,
17995 cx: &mut Context<Self>,
17996 ) {
17997 self.do_stage_or_unstage_and_next(false, window, cx);
17998 }
17999
18000 pub fn stage_or_unstage_diff_hunks(
18001 &mut self,
18002 stage: bool,
18003 ranges: Vec<Range<Anchor>>,
18004 cx: &mut Context<Self>,
18005 ) {
18006 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18007 cx.spawn(async move |this, cx| {
18008 task.await?;
18009 this.update(cx, |this, cx| {
18010 let snapshot = this.buffer.read(cx).snapshot(cx);
18011 let chunk_by = this
18012 .diff_hunks_in_ranges(&ranges, &snapshot)
18013 .chunk_by(|hunk| hunk.buffer_id);
18014 for (buffer_id, hunks) in &chunk_by {
18015 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18016 }
18017 })
18018 })
18019 .detach_and_log_err(cx);
18020 }
18021
18022 fn save_buffers_for_ranges_if_needed(
18023 &mut self,
18024 ranges: &[Range<Anchor>],
18025 cx: &mut Context<Editor>,
18026 ) -> Task<Result<()>> {
18027 let multibuffer = self.buffer.read(cx);
18028 let snapshot = multibuffer.read(cx);
18029 let buffer_ids: HashSet<_> = ranges
18030 .iter()
18031 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18032 .collect();
18033 drop(snapshot);
18034
18035 let mut buffers = HashSet::default();
18036 for buffer_id in buffer_ids {
18037 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18038 let buffer = buffer_entity.read(cx);
18039 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18040 {
18041 buffers.insert(buffer_entity);
18042 }
18043 }
18044 }
18045
18046 if let Some(project) = &self.project {
18047 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18048 } else {
18049 Task::ready(Ok(()))
18050 }
18051 }
18052
18053 fn do_stage_or_unstage_and_next(
18054 &mut self,
18055 stage: bool,
18056 window: &mut Window,
18057 cx: &mut Context<Self>,
18058 ) {
18059 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18060
18061 if ranges.iter().any(|range| range.start != range.end) {
18062 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18063 return;
18064 }
18065
18066 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18067 let snapshot = self.snapshot(window, cx);
18068 let position = self.selections.newest::<Point>(cx).head();
18069 let mut row = snapshot
18070 .buffer_snapshot
18071 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18072 .find(|hunk| hunk.row_range.start.0 > position.row)
18073 .map(|hunk| hunk.row_range.start);
18074
18075 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18076 // Outside of the project diff editor, wrap around to the beginning.
18077 if !all_diff_hunks_expanded {
18078 row = row.or_else(|| {
18079 snapshot
18080 .buffer_snapshot
18081 .diff_hunks_in_range(Point::zero()..position)
18082 .find(|hunk| hunk.row_range.end.0 < position.row)
18083 .map(|hunk| hunk.row_range.start)
18084 });
18085 }
18086
18087 if let Some(row) = row {
18088 let destination = Point::new(row.0, 0);
18089 let autoscroll = Autoscroll::center();
18090
18091 self.unfold_ranges(&[destination..destination], false, false, cx);
18092 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18093 s.select_ranges([destination..destination]);
18094 });
18095 }
18096 }
18097
18098 fn do_stage_or_unstage(
18099 &self,
18100 stage: bool,
18101 buffer_id: BufferId,
18102 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18103 cx: &mut App,
18104 ) -> Option<()> {
18105 let project = self.project()?;
18106 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18107 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18108 let buffer_snapshot = buffer.read(cx).snapshot();
18109 let file_exists = buffer_snapshot
18110 .file()
18111 .is_some_and(|file| file.disk_state().exists());
18112 diff.update(cx, |diff, cx| {
18113 diff.stage_or_unstage_hunks(
18114 stage,
18115 &hunks
18116 .map(|hunk| buffer_diff::DiffHunk {
18117 buffer_range: hunk.buffer_range,
18118 diff_base_byte_range: hunk.diff_base_byte_range,
18119 secondary_status: hunk.secondary_status,
18120 range: Point::zero()..Point::zero(), // unused
18121 })
18122 .collect::<Vec<_>>(),
18123 &buffer_snapshot,
18124 file_exists,
18125 cx,
18126 )
18127 });
18128 None
18129 }
18130
18131 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18132 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18133 self.buffer
18134 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18135 }
18136
18137 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18138 self.buffer.update(cx, |buffer, cx| {
18139 let ranges = vec![Anchor::min()..Anchor::max()];
18140 if !buffer.all_diff_hunks_expanded()
18141 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18142 {
18143 buffer.collapse_diff_hunks(ranges, cx);
18144 true
18145 } else {
18146 false
18147 }
18148 })
18149 }
18150
18151 fn toggle_diff_hunks_in_ranges(
18152 &mut self,
18153 ranges: Vec<Range<Anchor>>,
18154 cx: &mut Context<Editor>,
18155 ) {
18156 self.buffer.update(cx, |buffer, cx| {
18157 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18158 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18159 })
18160 }
18161
18162 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18163 self.buffer.update(cx, |buffer, cx| {
18164 let snapshot = buffer.snapshot(cx);
18165 let excerpt_id = range.end.excerpt_id;
18166 let point_range = range.to_point(&snapshot);
18167 let expand = !buffer.single_hunk_is_expanded(range, cx);
18168 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18169 })
18170 }
18171
18172 pub(crate) fn apply_all_diff_hunks(
18173 &mut self,
18174 _: &ApplyAllDiffHunks,
18175 window: &mut Window,
18176 cx: &mut Context<Self>,
18177 ) {
18178 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18179
18180 let buffers = self.buffer.read(cx).all_buffers();
18181 for branch_buffer in buffers {
18182 branch_buffer.update(cx, |branch_buffer, cx| {
18183 branch_buffer.merge_into_base(Vec::new(), cx);
18184 });
18185 }
18186
18187 if let Some(project) = self.project.clone() {
18188 self.save(
18189 SaveOptions {
18190 format: true,
18191 autosave: false,
18192 },
18193 project,
18194 window,
18195 cx,
18196 )
18197 .detach_and_log_err(cx);
18198 }
18199 }
18200
18201 pub(crate) fn apply_selected_diff_hunks(
18202 &mut self,
18203 _: &ApplyDiffHunk,
18204 window: &mut Window,
18205 cx: &mut Context<Self>,
18206 ) {
18207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18208 let snapshot = self.snapshot(window, cx);
18209 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18210 let mut ranges_by_buffer = HashMap::default();
18211 self.transact(window, cx, |editor, _window, cx| {
18212 for hunk in hunks {
18213 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18214 ranges_by_buffer
18215 .entry(buffer.clone())
18216 .or_insert_with(Vec::new)
18217 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18218 }
18219 }
18220
18221 for (buffer, ranges) in ranges_by_buffer {
18222 buffer.update(cx, |buffer, cx| {
18223 buffer.merge_into_base(ranges, cx);
18224 });
18225 }
18226 });
18227
18228 if let Some(project) = self.project.clone() {
18229 self.save(
18230 SaveOptions {
18231 format: true,
18232 autosave: false,
18233 },
18234 project,
18235 window,
18236 cx,
18237 )
18238 .detach_and_log_err(cx);
18239 }
18240 }
18241
18242 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18243 if hovered != self.gutter_hovered {
18244 self.gutter_hovered = hovered;
18245 cx.notify();
18246 }
18247 }
18248
18249 pub fn insert_blocks(
18250 &mut self,
18251 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18252 autoscroll: Option<Autoscroll>,
18253 cx: &mut Context<Self>,
18254 ) -> Vec<CustomBlockId> {
18255 let blocks = self
18256 .display_map
18257 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18258 if let Some(autoscroll) = autoscroll {
18259 self.request_autoscroll(autoscroll, cx);
18260 }
18261 cx.notify();
18262 blocks
18263 }
18264
18265 pub fn resize_blocks(
18266 &mut self,
18267 heights: HashMap<CustomBlockId, u32>,
18268 autoscroll: Option<Autoscroll>,
18269 cx: &mut Context<Self>,
18270 ) {
18271 self.display_map
18272 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18273 if let Some(autoscroll) = autoscroll {
18274 self.request_autoscroll(autoscroll, cx);
18275 }
18276 cx.notify();
18277 }
18278
18279 pub fn replace_blocks(
18280 &mut self,
18281 renderers: HashMap<CustomBlockId, RenderBlock>,
18282 autoscroll: Option<Autoscroll>,
18283 cx: &mut Context<Self>,
18284 ) {
18285 self.display_map
18286 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18287 if let Some(autoscroll) = autoscroll {
18288 self.request_autoscroll(autoscroll, cx);
18289 }
18290 cx.notify();
18291 }
18292
18293 pub fn remove_blocks(
18294 &mut self,
18295 block_ids: HashSet<CustomBlockId>,
18296 autoscroll: Option<Autoscroll>,
18297 cx: &mut Context<Self>,
18298 ) {
18299 self.display_map.update(cx, |display_map, cx| {
18300 display_map.remove_blocks(block_ids, cx)
18301 });
18302 if let Some(autoscroll) = autoscroll {
18303 self.request_autoscroll(autoscroll, cx);
18304 }
18305 cx.notify();
18306 }
18307
18308 pub fn row_for_block(
18309 &self,
18310 block_id: CustomBlockId,
18311 cx: &mut Context<Self>,
18312 ) -> Option<DisplayRow> {
18313 self.display_map
18314 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18315 }
18316
18317 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18318 self.focused_block = Some(focused_block);
18319 }
18320
18321 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18322 self.focused_block.take()
18323 }
18324
18325 pub fn insert_creases(
18326 &mut self,
18327 creases: impl IntoIterator<Item = Crease<Anchor>>,
18328 cx: &mut Context<Self>,
18329 ) -> Vec<CreaseId> {
18330 self.display_map
18331 .update(cx, |map, cx| map.insert_creases(creases, cx))
18332 }
18333
18334 pub fn remove_creases(
18335 &mut self,
18336 ids: impl IntoIterator<Item = CreaseId>,
18337 cx: &mut Context<Self>,
18338 ) -> Vec<(CreaseId, Range<Anchor>)> {
18339 self.display_map
18340 .update(cx, |map, cx| map.remove_creases(ids, cx))
18341 }
18342
18343 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18344 self.display_map
18345 .update(cx, |map, cx| map.snapshot(cx))
18346 .longest_row()
18347 }
18348
18349 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18350 self.display_map
18351 .update(cx, |map, cx| map.snapshot(cx))
18352 .max_point()
18353 }
18354
18355 pub fn text(&self, cx: &App) -> String {
18356 self.buffer.read(cx).read(cx).text()
18357 }
18358
18359 pub fn is_empty(&self, cx: &App) -> bool {
18360 self.buffer.read(cx).read(cx).is_empty()
18361 }
18362
18363 pub fn text_option(&self, cx: &App) -> Option<String> {
18364 let text = self.text(cx);
18365 let text = text.trim();
18366
18367 if text.is_empty() {
18368 return None;
18369 }
18370
18371 Some(text.to_string())
18372 }
18373
18374 pub fn set_text(
18375 &mut self,
18376 text: impl Into<Arc<str>>,
18377 window: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 self.transact(window, cx, |this, _, cx| {
18381 this.buffer
18382 .read(cx)
18383 .as_singleton()
18384 .expect("you can only call set_text on editors for singleton buffers")
18385 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18386 });
18387 }
18388
18389 pub fn display_text(&self, cx: &mut App) -> String {
18390 self.display_map
18391 .update(cx, |map, cx| map.snapshot(cx))
18392 .text()
18393 }
18394
18395 fn create_minimap(
18396 &self,
18397 minimap_settings: MinimapSettings,
18398 window: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) -> Option<Entity<Self>> {
18401 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18402 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18403 }
18404
18405 fn initialize_new_minimap(
18406 &self,
18407 minimap_settings: MinimapSettings,
18408 window: &mut Window,
18409 cx: &mut Context<Self>,
18410 ) -> Entity<Self> {
18411 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18412
18413 let mut minimap = Editor::new_internal(
18414 EditorMode::Minimap {
18415 parent: cx.weak_entity(),
18416 },
18417 self.buffer.clone(),
18418 None,
18419 Some(self.display_map.clone()),
18420 window,
18421 cx,
18422 );
18423 minimap.scroll_manager.clone_state(&self.scroll_manager);
18424 minimap.set_text_style_refinement(TextStyleRefinement {
18425 font_size: Some(MINIMAP_FONT_SIZE),
18426 font_weight: Some(MINIMAP_FONT_WEIGHT),
18427 ..Default::default()
18428 });
18429 minimap.update_minimap_configuration(minimap_settings, cx);
18430 cx.new(|_| minimap)
18431 }
18432
18433 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18434 let current_line_highlight = minimap_settings
18435 .current_line_highlight
18436 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18437 self.set_current_line_highlight(Some(current_line_highlight));
18438 }
18439
18440 pub fn minimap(&self) -> Option<&Entity<Self>> {
18441 self.minimap
18442 .as_ref()
18443 .filter(|_| self.minimap_visibility.visible())
18444 }
18445
18446 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18447 let mut wrap_guides = smallvec![];
18448
18449 if self.show_wrap_guides == Some(false) {
18450 return wrap_guides;
18451 }
18452
18453 let settings = self.buffer.read(cx).language_settings(cx);
18454 if settings.show_wrap_guides {
18455 match self.soft_wrap_mode(cx) {
18456 SoftWrap::Column(soft_wrap) => {
18457 wrap_guides.push((soft_wrap as usize, true));
18458 }
18459 SoftWrap::Bounded(soft_wrap) => {
18460 wrap_guides.push((soft_wrap as usize, true));
18461 }
18462 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18463 }
18464 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18465 }
18466
18467 wrap_guides
18468 }
18469
18470 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18471 let settings = self.buffer.read(cx).language_settings(cx);
18472 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18473 match mode {
18474 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18475 SoftWrap::None
18476 }
18477 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18478 language_settings::SoftWrap::PreferredLineLength => {
18479 SoftWrap::Column(settings.preferred_line_length)
18480 }
18481 language_settings::SoftWrap::Bounded => {
18482 SoftWrap::Bounded(settings.preferred_line_length)
18483 }
18484 }
18485 }
18486
18487 pub fn set_soft_wrap_mode(
18488 &mut self,
18489 mode: language_settings::SoftWrap,
18490
18491 cx: &mut Context<Self>,
18492 ) {
18493 self.soft_wrap_mode_override = Some(mode);
18494 cx.notify();
18495 }
18496
18497 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18498 self.hard_wrap = hard_wrap;
18499 cx.notify();
18500 }
18501
18502 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18503 self.text_style_refinement = Some(style);
18504 }
18505
18506 /// called by the Element so we know what style we were most recently rendered with.
18507 pub(crate) fn set_style(
18508 &mut self,
18509 style: EditorStyle,
18510 window: &mut Window,
18511 cx: &mut Context<Self>,
18512 ) {
18513 // We intentionally do not inform the display map about the minimap style
18514 // so that wrapping is not recalculated and stays consistent for the editor
18515 // and its linked minimap.
18516 if !self.mode.is_minimap() {
18517 let rem_size = window.rem_size();
18518 self.display_map.update(cx, |map, cx| {
18519 map.set_font(
18520 style.text.font(),
18521 style.text.font_size.to_pixels(rem_size),
18522 cx,
18523 )
18524 });
18525 }
18526 self.style = Some(style);
18527 }
18528
18529 pub fn style(&self) -> Option<&EditorStyle> {
18530 self.style.as_ref()
18531 }
18532
18533 // Called by the element. This method is not designed to be called outside of the editor
18534 // element's layout code because it does not notify when rewrapping is computed synchronously.
18535 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18536 self.display_map
18537 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18538 }
18539
18540 pub fn set_soft_wrap(&mut self) {
18541 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18542 }
18543
18544 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18545 if self.soft_wrap_mode_override.is_some() {
18546 self.soft_wrap_mode_override.take();
18547 } else {
18548 let soft_wrap = match self.soft_wrap_mode(cx) {
18549 SoftWrap::GitDiff => return,
18550 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18551 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18552 language_settings::SoftWrap::None
18553 }
18554 };
18555 self.soft_wrap_mode_override = Some(soft_wrap);
18556 }
18557 cx.notify();
18558 }
18559
18560 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18561 let Some(workspace) = self.workspace() else {
18562 return;
18563 };
18564 let fs = workspace.read(cx).app_state().fs.clone();
18565 let current_show = TabBarSettings::get_global(cx).show;
18566 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18567 setting.show = Some(!current_show);
18568 });
18569 }
18570
18571 pub fn toggle_indent_guides(
18572 &mut self,
18573 _: &ToggleIndentGuides,
18574 _: &mut Window,
18575 cx: &mut Context<Self>,
18576 ) {
18577 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18578 self.buffer
18579 .read(cx)
18580 .language_settings(cx)
18581 .indent_guides
18582 .enabled
18583 });
18584 self.show_indent_guides = Some(!currently_enabled);
18585 cx.notify();
18586 }
18587
18588 fn should_show_indent_guides(&self) -> Option<bool> {
18589 self.show_indent_guides
18590 }
18591
18592 pub fn toggle_line_numbers(
18593 &mut self,
18594 _: &ToggleLineNumbers,
18595 _: &mut Window,
18596 cx: &mut Context<Self>,
18597 ) {
18598 let mut editor_settings = EditorSettings::get_global(cx).clone();
18599 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18600 EditorSettings::override_global(editor_settings, cx);
18601 }
18602
18603 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18604 if let Some(show_line_numbers) = self.show_line_numbers {
18605 return show_line_numbers;
18606 }
18607 EditorSettings::get_global(cx).gutter.line_numbers
18608 }
18609
18610 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18611 self.use_relative_line_numbers
18612 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18613 }
18614
18615 pub fn toggle_relative_line_numbers(
18616 &mut self,
18617 _: &ToggleRelativeLineNumbers,
18618 _: &mut Window,
18619 cx: &mut Context<Self>,
18620 ) {
18621 let is_relative = self.should_use_relative_line_numbers(cx);
18622 self.set_relative_line_number(Some(!is_relative), cx)
18623 }
18624
18625 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18626 self.use_relative_line_numbers = is_relative;
18627 cx.notify();
18628 }
18629
18630 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18631 self.show_gutter = show_gutter;
18632 cx.notify();
18633 }
18634
18635 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18636 self.show_scrollbars = ScrollbarAxes {
18637 horizontal: show,
18638 vertical: show,
18639 };
18640 cx.notify();
18641 }
18642
18643 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18644 self.show_scrollbars.vertical = show;
18645 cx.notify();
18646 }
18647
18648 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18649 self.show_scrollbars.horizontal = show;
18650 cx.notify();
18651 }
18652
18653 pub fn set_minimap_visibility(
18654 &mut self,
18655 minimap_visibility: MinimapVisibility,
18656 window: &mut Window,
18657 cx: &mut Context<Self>,
18658 ) {
18659 if self.minimap_visibility != minimap_visibility {
18660 if minimap_visibility.visible() && self.minimap.is_none() {
18661 let minimap_settings = EditorSettings::get_global(cx).minimap;
18662 self.minimap =
18663 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18664 }
18665 self.minimap_visibility = minimap_visibility;
18666 cx.notify();
18667 }
18668 }
18669
18670 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18671 self.set_show_scrollbars(false, cx);
18672 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18673 }
18674
18675 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18676 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18677 }
18678
18679 /// Normally the text in full mode and auto height editors is padded on the
18680 /// left side by roughly half a character width for improved hit testing.
18681 ///
18682 /// Use this method to disable this for cases where this is not wanted (e.g.
18683 /// if you want to align the editor text with some other text above or below)
18684 /// or if you want to add this padding to single-line editors.
18685 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18686 self.offset_content = offset_content;
18687 cx.notify();
18688 }
18689
18690 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18691 self.show_line_numbers = Some(show_line_numbers);
18692 cx.notify();
18693 }
18694
18695 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18696 self.disable_expand_excerpt_buttons = true;
18697 cx.notify();
18698 }
18699
18700 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18701 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18702 cx.notify();
18703 }
18704
18705 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18706 self.show_code_actions = Some(show_code_actions);
18707 cx.notify();
18708 }
18709
18710 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18711 self.show_runnables = Some(show_runnables);
18712 cx.notify();
18713 }
18714
18715 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18716 self.show_breakpoints = Some(show_breakpoints);
18717 cx.notify();
18718 }
18719
18720 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18721 if self.display_map.read(cx).masked != masked {
18722 self.display_map.update(cx, |map, _| map.masked = masked);
18723 }
18724 cx.notify()
18725 }
18726
18727 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18728 self.show_wrap_guides = Some(show_wrap_guides);
18729 cx.notify();
18730 }
18731
18732 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18733 self.show_indent_guides = Some(show_indent_guides);
18734 cx.notify();
18735 }
18736
18737 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18738 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18739 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18740 && let Some(dir) = file.abs_path(cx).parent()
18741 {
18742 return Some(dir.to_owned());
18743 }
18744
18745 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18746 return Some(project_path.path.to_path_buf());
18747 }
18748 }
18749
18750 None
18751 }
18752
18753 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18754 self.active_excerpt(cx)?
18755 .1
18756 .read(cx)
18757 .file()
18758 .and_then(|f| f.as_local())
18759 }
18760
18761 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18762 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18763 let buffer = buffer.read(cx);
18764 if let Some(project_path) = buffer.project_path(cx) {
18765 let project = self.project()?.read(cx);
18766 project.absolute_path(&project_path, cx)
18767 } else {
18768 buffer
18769 .file()
18770 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18771 }
18772 })
18773 }
18774
18775 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18776 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18777 let project_path = buffer.read(cx).project_path(cx)?;
18778 let project = self.project()?.read(cx);
18779 let entry = project.entry_for_path(&project_path, cx)?;
18780 let path = entry.path.to_path_buf();
18781 Some(path)
18782 })
18783 }
18784
18785 pub fn reveal_in_finder(
18786 &mut self,
18787 _: &RevealInFileManager,
18788 _window: &mut Window,
18789 cx: &mut Context<Self>,
18790 ) {
18791 if let Some(target) = self.target_file(cx) {
18792 cx.reveal_path(&target.abs_path(cx));
18793 }
18794 }
18795
18796 pub fn copy_path(
18797 &mut self,
18798 _: &zed_actions::workspace::CopyPath,
18799 _window: &mut Window,
18800 cx: &mut Context<Self>,
18801 ) {
18802 if let Some(path) = self.target_file_abs_path(cx)
18803 && let Some(path) = path.to_str()
18804 {
18805 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18806 }
18807 }
18808
18809 pub fn copy_relative_path(
18810 &mut self,
18811 _: &zed_actions::workspace::CopyRelativePath,
18812 _window: &mut Window,
18813 cx: &mut Context<Self>,
18814 ) {
18815 if let Some(path) = self.target_file_path(cx)
18816 && let Some(path) = path.to_str()
18817 {
18818 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18819 }
18820 }
18821
18822 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18823 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18824 buffer.read(cx).project_path(cx)
18825 } else {
18826 None
18827 }
18828 }
18829
18830 // Returns true if the editor handled a go-to-line request
18831 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18832 maybe!({
18833 let breakpoint_store = self.breakpoint_store.as_ref()?;
18834
18835 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18836 else {
18837 self.clear_row_highlights::<ActiveDebugLine>();
18838 return None;
18839 };
18840
18841 let position = active_stack_frame.position;
18842 let buffer_id = position.buffer_id?;
18843 let snapshot = self
18844 .project
18845 .as_ref()?
18846 .read(cx)
18847 .buffer_for_id(buffer_id, cx)?
18848 .read(cx)
18849 .snapshot();
18850
18851 let mut handled = false;
18852 for (id, ExcerptRange { context, .. }) in
18853 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18854 {
18855 if context.start.cmp(&position, &snapshot).is_ge()
18856 || context.end.cmp(&position, &snapshot).is_lt()
18857 {
18858 continue;
18859 }
18860 let snapshot = self.buffer.read(cx).snapshot(cx);
18861 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18862
18863 handled = true;
18864 self.clear_row_highlights::<ActiveDebugLine>();
18865
18866 self.go_to_line::<ActiveDebugLine>(
18867 multibuffer_anchor,
18868 Some(cx.theme().colors().editor_debugger_active_line_background),
18869 window,
18870 cx,
18871 );
18872
18873 cx.notify();
18874 }
18875
18876 handled.then_some(())
18877 })
18878 .is_some()
18879 }
18880
18881 pub fn copy_file_name_without_extension(
18882 &mut self,
18883 _: &CopyFileNameWithoutExtension,
18884 _: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) {
18887 if let Some(file) = self.target_file(cx)
18888 && let Some(file_stem) = file.path().file_stem()
18889 && let Some(name) = file_stem.to_str()
18890 {
18891 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18892 }
18893 }
18894
18895 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18896 if let Some(file) = self.target_file(cx)
18897 && let Some(file_name) = file.path().file_name()
18898 && let Some(name) = file_name.to_str()
18899 {
18900 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18901 }
18902 }
18903
18904 pub fn toggle_git_blame(
18905 &mut self,
18906 _: &::git::Blame,
18907 window: &mut Window,
18908 cx: &mut Context<Self>,
18909 ) {
18910 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18911
18912 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18913 self.start_git_blame(true, window, cx);
18914 }
18915
18916 cx.notify();
18917 }
18918
18919 pub fn toggle_git_blame_inline(
18920 &mut self,
18921 _: &ToggleGitBlameInline,
18922 window: &mut Window,
18923 cx: &mut Context<Self>,
18924 ) {
18925 self.toggle_git_blame_inline_internal(true, window, cx);
18926 cx.notify();
18927 }
18928
18929 pub fn open_git_blame_commit(
18930 &mut self,
18931 _: &OpenGitBlameCommit,
18932 window: &mut Window,
18933 cx: &mut Context<Self>,
18934 ) {
18935 self.open_git_blame_commit_internal(window, cx);
18936 }
18937
18938 fn open_git_blame_commit_internal(
18939 &mut self,
18940 window: &mut Window,
18941 cx: &mut Context<Self>,
18942 ) -> Option<()> {
18943 let blame = self.blame.as_ref()?;
18944 let snapshot = self.snapshot(window, cx);
18945 let cursor = self.selections.newest::<Point>(cx).head();
18946 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18947 let blame_entry = blame
18948 .update(cx, |blame, cx| {
18949 blame
18950 .blame_for_rows(
18951 &[RowInfo {
18952 buffer_id: Some(buffer.remote_id()),
18953 buffer_row: Some(point.row),
18954 ..Default::default()
18955 }],
18956 cx,
18957 )
18958 .next()
18959 })
18960 .flatten()?;
18961 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18962 let repo = blame.read(cx).repository(cx)?;
18963 let workspace = self.workspace()?.downgrade();
18964 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18965 None
18966 }
18967
18968 pub fn git_blame_inline_enabled(&self) -> bool {
18969 self.git_blame_inline_enabled
18970 }
18971
18972 pub fn toggle_selection_menu(
18973 &mut self,
18974 _: &ToggleSelectionMenu,
18975 _: &mut Window,
18976 cx: &mut Context<Self>,
18977 ) {
18978 self.show_selection_menu = self
18979 .show_selection_menu
18980 .map(|show_selections_menu| !show_selections_menu)
18981 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18982
18983 cx.notify();
18984 }
18985
18986 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18987 self.show_selection_menu
18988 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18989 }
18990
18991 fn start_git_blame(
18992 &mut self,
18993 user_triggered: bool,
18994 window: &mut Window,
18995 cx: &mut Context<Self>,
18996 ) {
18997 if let Some(project) = self.project() {
18998 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18999 return;
19000 };
19001
19002 if buffer.read(cx).file().is_none() {
19003 return;
19004 }
19005
19006 let focused = self.focus_handle(cx).contains_focused(window, cx);
19007
19008 let project = project.clone();
19009 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
19010 self.blame_subscription =
19011 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19012 self.blame = Some(blame);
19013 }
19014 }
19015
19016 fn toggle_git_blame_inline_internal(
19017 &mut self,
19018 user_triggered: bool,
19019 window: &mut Window,
19020 cx: &mut Context<Self>,
19021 ) {
19022 if self.git_blame_inline_enabled {
19023 self.git_blame_inline_enabled = false;
19024 self.show_git_blame_inline = false;
19025 self.show_git_blame_inline_delay_task.take();
19026 } else {
19027 self.git_blame_inline_enabled = true;
19028 self.start_git_blame_inline(user_triggered, window, cx);
19029 }
19030
19031 cx.notify();
19032 }
19033
19034 fn start_git_blame_inline(
19035 &mut self,
19036 user_triggered: bool,
19037 window: &mut Window,
19038 cx: &mut Context<Self>,
19039 ) {
19040 self.start_git_blame(user_triggered, window, cx);
19041
19042 if ProjectSettings::get_global(cx)
19043 .git
19044 .inline_blame_delay()
19045 .is_some()
19046 {
19047 self.start_inline_blame_timer(window, cx);
19048 } else {
19049 self.show_git_blame_inline = true
19050 }
19051 }
19052
19053 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19054 self.blame.as_ref()
19055 }
19056
19057 pub fn show_git_blame_gutter(&self) -> bool {
19058 self.show_git_blame_gutter
19059 }
19060
19061 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19062 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19063 }
19064
19065 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19066 self.show_git_blame_inline
19067 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19068 && !self.newest_selection_head_on_empty_line(cx)
19069 && self.has_blame_entries(cx)
19070 }
19071
19072 fn has_blame_entries(&self, cx: &App) -> bool {
19073 self.blame()
19074 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19075 }
19076
19077 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19078 let cursor_anchor = self.selections.newest_anchor().head();
19079
19080 let snapshot = self.buffer.read(cx).snapshot(cx);
19081 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19082
19083 snapshot.line_len(buffer_row) == 0
19084 }
19085
19086 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19087 let buffer_and_selection = maybe!({
19088 let selection = self.selections.newest::<Point>(cx);
19089 let selection_range = selection.range();
19090
19091 let multi_buffer = self.buffer().read(cx);
19092 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19093 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19094
19095 let (buffer, range, _) = if selection.reversed {
19096 buffer_ranges.first()
19097 } else {
19098 buffer_ranges.last()
19099 }?;
19100
19101 let selection = text::ToPoint::to_point(&range.start, buffer).row
19102 ..text::ToPoint::to_point(&range.end, buffer).row;
19103 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19104 });
19105
19106 let Some((buffer, selection)) = buffer_and_selection else {
19107 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19108 };
19109
19110 let Some(project) = self.project() else {
19111 return Task::ready(Err(anyhow!("editor does not have project")));
19112 };
19113
19114 project.update(cx, |project, cx| {
19115 project.get_permalink_to_line(&buffer, selection, cx)
19116 })
19117 }
19118
19119 pub fn copy_permalink_to_line(
19120 &mut self,
19121 _: &CopyPermalinkToLine,
19122 window: &mut Window,
19123 cx: &mut Context<Self>,
19124 ) {
19125 let permalink_task = self.get_permalink_to_line(cx);
19126 let workspace = self.workspace();
19127
19128 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19129 Ok(permalink) => {
19130 cx.update(|_, cx| {
19131 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19132 })
19133 .ok();
19134 }
19135 Err(err) => {
19136 let message = format!("Failed to copy permalink: {err}");
19137
19138 anyhow::Result::<()>::Err(err).log_err();
19139
19140 if let Some(workspace) = workspace {
19141 workspace
19142 .update_in(cx, |workspace, _, cx| {
19143 struct CopyPermalinkToLine;
19144
19145 workspace.show_toast(
19146 Toast::new(
19147 NotificationId::unique::<CopyPermalinkToLine>(),
19148 message,
19149 ),
19150 cx,
19151 )
19152 })
19153 .ok();
19154 }
19155 }
19156 })
19157 .detach();
19158 }
19159
19160 pub fn copy_file_location(
19161 &mut self,
19162 _: &CopyFileLocation,
19163 _: &mut Window,
19164 cx: &mut Context<Self>,
19165 ) {
19166 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19167 if let Some(file) = self.target_file(cx)
19168 && let Some(path) = file.path().to_str()
19169 {
19170 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19171 }
19172 }
19173
19174 pub fn open_permalink_to_line(
19175 &mut self,
19176 _: &OpenPermalinkToLine,
19177 window: &mut Window,
19178 cx: &mut Context<Self>,
19179 ) {
19180 let permalink_task = self.get_permalink_to_line(cx);
19181 let workspace = self.workspace();
19182
19183 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19184 Ok(permalink) => {
19185 cx.update(|_, cx| {
19186 cx.open_url(permalink.as_ref());
19187 })
19188 .ok();
19189 }
19190 Err(err) => {
19191 let message = format!("Failed to open permalink: {err}");
19192
19193 anyhow::Result::<()>::Err(err).log_err();
19194
19195 if let Some(workspace) = workspace {
19196 workspace
19197 .update(cx, |workspace, cx| {
19198 struct OpenPermalinkToLine;
19199
19200 workspace.show_toast(
19201 Toast::new(
19202 NotificationId::unique::<OpenPermalinkToLine>(),
19203 message,
19204 ),
19205 cx,
19206 )
19207 })
19208 .ok();
19209 }
19210 }
19211 })
19212 .detach();
19213 }
19214
19215 pub fn insert_uuid_v4(
19216 &mut self,
19217 _: &InsertUuidV4,
19218 window: &mut Window,
19219 cx: &mut Context<Self>,
19220 ) {
19221 self.insert_uuid(UuidVersion::V4, window, cx);
19222 }
19223
19224 pub fn insert_uuid_v7(
19225 &mut self,
19226 _: &InsertUuidV7,
19227 window: &mut Window,
19228 cx: &mut Context<Self>,
19229 ) {
19230 self.insert_uuid(UuidVersion::V7, window, cx);
19231 }
19232
19233 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19234 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19235 self.transact(window, cx, |this, window, cx| {
19236 let edits = this
19237 .selections
19238 .all::<Point>(cx)
19239 .into_iter()
19240 .map(|selection| {
19241 let uuid = match version {
19242 UuidVersion::V4 => uuid::Uuid::new_v4(),
19243 UuidVersion::V7 => uuid::Uuid::now_v7(),
19244 };
19245
19246 (selection.range(), uuid.to_string())
19247 });
19248 this.edit(edits, cx);
19249 this.refresh_edit_prediction(true, false, window, cx);
19250 });
19251 }
19252
19253 pub fn open_selections_in_multibuffer(
19254 &mut self,
19255 _: &OpenSelectionsInMultibuffer,
19256 window: &mut Window,
19257 cx: &mut Context<Self>,
19258 ) {
19259 let multibuffer = self.buffer.read(cx);
19260
19261 let Some(buffer) = multibuffer.as_singleton() else {
19262 return;
19263 };
19264
19265 let Some(workspace) = self.workspace() else {
19266 return;
19267 };
19268
19269 let title = multibuffer.title(cx).to_string();
19270
19271 let locations = self
19272 .selections
19273 .all_anchors(cx)
19274 .iter()
19275 .map(|selection| Location {
19276 buffer: buffer.clone(),
19277 range: selection.start.text_anchor..selection.end.text_anchor,
19278 })
19279 .collect::<Vec<_>>();
19280
19281 cx.spawn_in(window, async move |_, cx| {
19282 workspace.update_in(cx, |workspace, window, cx| {
19283 Self::open_locations_in_multibuffer(
19284 workspace,
19285 locations,
19286 format!("Selections for '{title}'"),
19287 false,
19288 MultibufferSelectionMode::All,
19289 window,
19290 cx,
19291 );
19292 })
19293 })
19294 .detach();
19295 }
19296
19297 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19298 /// last highlight added will be used.
19299 ///
19300 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19301 pub fn highlight_rows<T: 'static>(
19302 &mut self,
19303 range: Range<Anchor>,
19304 color: Hsla,
19305 options: RowHighlightOptions,
19306 cx: &mut Context<Self>,
19307 ) {
19308 let snapshot = self.buffer().read(cx).snapshot(cx);
19309 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19310 let ix = row_highlights.binary_search_by(|highlight| {
19311 Ordering::Equal
19312 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19313 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19314 });
19315
19316 if let Err(mut ix) = ix {
19317 let index = post_inc(&mut self.highlight_order);
19318
19319 // If this range intersects with the preceding highlight, then merge it with
19320 // the preceding highlight. Otherwise insert a new highlight.
19321 let mut merged = false;
19322 if ix > 0 {
19323 let prev_highlight = &mut row_highlights[ix - 1];
19324 if prev_highlight
19325 .range
19326 .end
19327 .cmp(&range.start, &snapshot)
19328 .is_ge()
19329 {
19330 ix -= 1;
19331 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19332 prev_highlight.range.end = range.end;
19333 }
19334 merged = true;
19335 prev_highlight.index = index;
19336 prev_highlight.color = color;
19337 prev_highlight.options = options;
19338 }
19339 }
19340
19341 if !merged {
19342 row_highlights.insert(
19343 ix,
19344 RowHighlight {
19345 range,
19346 index,
19347 color,
19348 options,
19349 type_id: TypeId::of::<T>(),
19350 },
19351 );
19352 }
19353
19354 // If any of the following highlights intersect with this one, merge them.
19355 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19356 let highlight = &row_highlights[ix];
19357 if next_highlight
19358 .range
19359 .start
19360 .cmp(&highlight.range.end, &snapshot)
19361 .is_le()
19362 {
19363 if next_highlight
19364 .range
19365 .end
19366 .cmp(&highlight.range.end, &snapshot)
19367 .is_gt()
19368 {
19369 row_highlights[ix].range.end = next_highlight.range.end;
19370 }
19371 row_highlights.remove(ix + 1);
19372 } else {
19373 break;
19374 }
19375 }
19376 }
19377 }
19378
19379 /// Remove any highlighted row ranges of the given type that intersect the
19380 /// given ranges.
19381 pub fn remove_highlighted_rows<T: 'static>(
19382 &mut self,
19383 ranges_to_remove: Vec<Range<Anchor>>,
19384 cx: &mut Context<Self>,
19385 ) {
19386 let snapshot = self.buffer().read(cx).snapshot(cx);
19387 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19388 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19389 row_highlights.retain(|highlight| {
19390 while let Some(range_to_remove) = ranges_to_remove.peek() {
19391 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19392 Ordering::Less | Ordering::Equal => {
19393 ranges_to_remove.next();
19394 }
19395 Ordering::Greater => {
19396 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19397 Ordering::Less | Ordering::Equal => {
19398 return false;
19399 }
19400 Ordering::Greater => break,
19401 }
19402 }
19403 }
19404 }
19405
19406 true
19407 })
19408 }
19409
19410 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19411 pub fn clear_row_highlights<T: 'static>(&mut self) {
19412 self.highlighted_rows.remove(&TypeId::of::<T>());
19413 }
19414
19415 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19416 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19417 self.highlighted_rows
19418 .get(&TypeId::of::<T>())
19419 .map_or(&[] as &[_], |vec| vec.as_slice())
19420 .iter()
19421 .map(|highlight| (highlight.range.clone(), highlight.color))
19422 }
19423
19424 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19425 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19426 /// Allows to ignore certain kinds of highlights.
19427 pub fn highlighted_display_rows(
19428 &self,
19429 window: &mut Window,
19430 cx: &mut App,
19431 ) -> BTreeMap<DisplayRow, LineHighlight> {
19432 let snapshot = self.snapshot(window, cx);
19433 let mut used_highlight_orders = HashMap::default();
19434 self.highlighted_rows
19435 .iter()
19436 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19437 .fold(
19438 BTreeMap::<DisplayRow, LineHighlight>::new(),
19439 |mut unique_rows, highlight| {
19440 let start = highlight.range.start.to_display_point(&snapshot);
19441 let end = highlight.range.end.to_display_point(&snapshot);
19442 let start_row = start.row().0;
19443 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19444 && end.column() == 0
19445 {
19446 end.row().0.saturating_sub(1)
19447 } else {
19448 end.row().0
19449 };
19450 for row in start_row..=end_row {
19451 let used_index =
19452 used_highlight_orders.entry(row).or_insert(highlight.index);
19453 if highlight.index >= *used_index {
19454 *used_index = highlight.index;
19455 unique_rows.insert(
19456 DisplayRow(row),
19457 LineHighlight {
19458 include_gutter: highlight.options.include_gutter,
19459 border: None,
19460 background: highlight.color.into(),
19461 type_id: Some(highlight.type_id),
19462 },
19463 );
19464 }
19465 }
19466 unique_rows
19467 },
19468 )
19469 }
19470
19471 pub fn highlighted_display_row_for_autoscroll(
19472 &self,
19473 snapshot: &DisplaySnapshot,
19474 ) -> Option<DisplayRow> {
19475 self.highlighted_rows
19476 .values()
19477 .flat_map(|highlighted_rows| highlighted_rows.iter())
19478 .filter_map(|highlight| {
19479 if highlight.options.autoscroll {
19480 Some(highlight.range.start.to_display_point(snapshot).row())
19481 } else {
19482 None
19483 }
19484 })
19485 .min()
19486 }
19487
19488 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19489 self.highlight_background::<SearchWithinRange>(
19490 ranges,
19491 |colors| colors.colors().editor_document_highlight_read_background,
19492 cx,
19493 )
19494 }
19495
19496 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19497 self.breadcrumb_header = Some(new_header);
19498 }
19499
19500 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19501 self.clear_background_highlights::<SearchWithinRange>(cx);
19502 }
19503
19504 pub fn highlight_background<T: 'static>(
19505 &mut self,
19506 ranges: &[Range<Anchor>],
19507 color_fetcher: fn(&Theme) -> Hsla,
19508 cx: &mut Context<Self>,
19509 ) {
19510 self.background_highlights.insert(
19511 HighlightKey::Type(TypeId::of::<T>()),
19512 (color_fetcher, Arc::from(ranges)),
19513 );
19514 self.scrollbar_marker_state.dirty = true;
19515 cx.notify();
19516 }
19517
19518 pub fn highlight_background_key<T: 'static>(
19519 &mut self,
19520 key: usize,
19521 ranges: &[Range<Anchor>],
19522 color_fetcher: fn(&Theme) -> Hsla,
19523 cx: &mut Context<Self>,
19524 ) {
19525 self.background_highlights.insert(
19526 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19527 (color_fetcher, Arc::from(ranges)),
19528 );
19529 self.scrollbar_marker_state.dirty = true;
19530 cx.notify();
19531 }
19532
19533 pub fn clear_background_highlights<T: 'static>(
19534 &mut self,
19535 cx: &mut Context<Self>,
19536 ) -> Option<BackgroundHighlight> {
19537 let text_highlights = self
19538 .background_highlights
19539 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19540 if !text_highlights.1.is_empty() {
19541 self.scrollbar_marker_state.dirty = true;
19542 cx.notify();
19543 }
19544 Some(text_highlights)
19545 }
19546
19547 pub fn highlight_gutter<T: 'static>(
19548 &mut self,
19549 ranges: impl Into<Vec<Range<Anchor>>>,
19550 color_fetcher: fn(&App) -> Hsla,
19551 cx: &mut Context<Self>,
19552 ) {
19553 self.gutter_highlights
19554 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19555 cx.notify();
19556 }
19557
19558 pub fn clear_gutter_highlights<T: 'static>(
19559 &mut self,
19560 cx: &mut Context<Self>,
19561 ) -> Option<GutterHighlight> {
19562 cx.notify();
19563 self.gutter_highlights.remove(&TypeId::of::<T>())
19564 }
19565
19566 pub fn insert_gutter_highlight<T: 'static>(
19567 &mut self,
19568 range: Range<Anchor>,
19569 color_fetcher: fn(&App) -> Hsla,
19570 cx: &mut Context<Self>,
19571 ) {
19572 let snapshot = self.buffer().read(cx).snapshot(cx);
19573 let mut highlights = self
19574 .gutter_highlights
19575 .remove(&TypeId::of::<T>())
19576 .map(|(_, highlights)| highlights)
19577 .unwrap_or_default();
19578 let ix = highlights.binary_search_by(|highlight| {
19579 Ordering::Equal
19580 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19581 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19582 });
19583 if let Err(ix) = ix {
19584 highlights.insert(ix, range);
19585 }
19586 self.gutter_highlights
19587 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19588 }
19589
19590 pub fn remove_gutter_highlights<T: 'static>(
19591 &mut self,
19592 ranges_to_remove: Vec<Range<Anchor>>,
19593 cx: &mut Context<Self>,
19594 ) {
19595 let snapshot = self.buffer().read(cx).snapshot(cx);
19596 let Some((color_fetcher, mut gutter_highlights)) =
19597 self.gutter_highlights.remove(&TypeId::of::<T>())
19598 else {
19599 return;
19600 };
19601 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19602 gutter_highlights.retain(|highlight| {
19603 while let Some(range_to_remove) = ranges_to_remove.peek() {
19604 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19605 Ordering::Less | Ordering::Equal => {
19606 ranges_to_remove.next();
19607 }
19608 Ordering::Greater => {
19609 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19610 Ordering::Less | Ordering::Equal => {
19611 return false;
19612 }
19613 Ordering::Greater => break,
19614 }
19615 }
19616 }
19617 }
19618
19619 true
19620 });
19621 self.gutter_highlights
19622 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19623 }
19624
19625 #[cfg(feature = "test-support")]
19626 pub fn all_text_highlights(
19627 &self,
19628 window: &mut Window,
19629 cx: &mut Context<Self>,
19630 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19631 let snapshot = self.snapshot(window, cx);
19632 self.display_map.update(cx, |display_map, _| {
19633 display_map
19634 .all_text_highlights()
19635 .map(|highlight| {
19636 let (style, ranges) = highlight.as_ref();
19637 (
19638 *style,
19639 ranges
19640 .iter()
19641 .map(|range| range.clone().to_display_points(&snapshot))
19642 .collect(),
19643 )
19644 })
19645 .collect()
19646 })
19647 }
19648
19649 #[cfg(feature = "test-support")]
19650 pub fn all_text_background_highlights(
19651 &self,
19652 window: &mut Window,
19653 cx: &mut Context<Self>,
19654 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19655 let snapshot = self.snapshot(window, cx);
19656 let buffer = &snapshot.buffer_snapshot;
19657 let start = buffer.anchor_before(0);
19658 let end = buffer.anchor_after(buffer.len());
19659 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19660 }
19661
19662 #[cfg(feature = "test-support")]
19663 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19664 let snapshot = self.buffer().read(cx).snapshot(cx);
19665
19666 let highlights = self
19667 .background_highlights
19668 .get(&HighlightKey::Type(TypeId::of::<
19669 items::BufferSearchHighlights,
19670 >()));
19671
19672 if let Some((_color, ranges)) = highlights {
19673 ranges
19674 .iter()
19675 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19676 .collect_vec()
19677 } else {
19678 vec![]
19679 }
19680 }
19681
19682 fn document_highlights_for_position<'a>(
19683 &'a self,
19684 position: Anchor,
19685 buffer: &'a MultiBufferSnapshot,
19686 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19687 let read_highlights = self
19688 .background_highlights
19689 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19690 .map(|h| &h.1);
19691 let write_highlights = self
19692 .background_highlights
19693 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19694 .map(|h| &h.1);
19695 let left_position = position.bias_left(buffer);
19696 let right_position = position.bias_right(buffer);
19697 read_highlights
19698 .into_iter()
19699 .chain(write_highlights)
19700 .flat_map(move |ranges| {
19701 let start_ix = match ranges.binary_search_by(|probe| {
19702 let cmp = probe.end.cmp(&left_position, buffer);
19703 if cmp.is_ge() {
19704 Ordering::Greater
19705 } else {
19706 Ordering::Less
19707 }
19708 }) {
19709 Ok(i) | Err(i) => i,
19710 };
19711
19712 ranges[start_ix..]
19713 .iter()
19714 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19715 })
19716 }
19717
19718 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19719 self.background_highlights
19720 .get(&HighlightKey::Type(TypeId::of::<T>()))
19721 .is_some_and(|(_, highlights)| !highlights.is_empty())
19722 }
19723
19724 pub fn background_highlights_in_range(
19725 &self,
19726 search_range: Range<Anchor>,
19727 display_snapshot: &DisplaySnapshot,
19728 theme: &Theme,
19729 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19730 let mut results = Vec::new();
19731 for (color_fetcher, ranges) in self.background_highlights.values() {
19732 let color = color_fetcher(theme);
19733 let start_ix = match ranges.binary_search_by(|probe| {
19734 let cmp = probe
19735 .end
19736 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19737 if cmp.is_gt() {
19738 Ordering::Greater
19739 } else {
19740 Ordering::Less
19741 }
19742 }) {
19743 Ok(i) | Err(i) => i,
19744 };
19745 for range in &ranges[start_ix..] {
19746 if range
19747 .start
19748 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19749 .is_ge()
19750 {
19751 break;
19752 }
19753
19754 let start = range.start.to_display_point(display_snapshot);
19755 let end = range.end.to_display_point(display_snapshot);
19756 results.push((start..end, color))
19757 }
19758 }
19759 results
19760 }
19761
19762 pub fn background_highlight_row_ranges<T: 'static>(
19763 &self,
19764 search_range: Range<Anchor>,
19765 display_snapshot: &DisplaySnapshot,
19766 count: usize,
19767 ) -> Vec<RangeInclusive<DisplayPoint>> {
19768 let mut results = Vec::new();
19769 let Some((_, ranges)) = self
19770 .background_highlights
19771 .get(&HighlightKey::Type(TypeId::of::<T>()))
19772 else {
19773 return vec![];
19774 };
19775
19776 let start_ix = match ranges.binary_search_by(|probe| {
19777 let cmp = probe
19778 .end
19779 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19780 if cmp.is_gt() {
19781 Ordering::Greater
19782 } else {
19783 Ordering::Less
19784 }
19785 }) {
19786 Ok(i) | Err(i) => i,
19787 };
19788 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19789 if let (Some(start_display), Some(end_display)) = (start, end) {
19790 results.push(
19791 start_display.to_display_point(display_snapshot)
19792 ..=end_display.to_display_point(display_snapshot),
19793 );
19794 }
19795 };
19796 let mut start_row: Option<Point> = None;
19797 let mut end_row: Option<Point> = None;
19798 if ranges.len() > count {
19799 return Vec::new();
19800 }
19801 for range in &ranges[start_ix..] {
19802 if range
19803 .start
19804 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19805 .is_ge()
19806 {
19807 break;
19808 }
19809 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19810 if let Some(current_row) = &end_row
19811 && end.row == current_row.row
19812 {
19813 continue;
19814 }
19815 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19816 if start_row.is_none() {
19817 assert_eq!(end_row, None);
19818 start_row = Some(start);
19819 end_row = Some(end);
19820 continue;
19821 }
19822 if let Some(current_end) = end_row.as_mut() {
19823 if start.row > current_end.row + 1 {
19824 push_region(start_row, end_row);
19825 start_row = Some(start);
19826 end_row = Some(end);
19827 } else {
19828 // Merge two hunks.
19829 *current_end = end;
19830 }
19831 } else {
19832 unreachable!();
19833 }
19834 }
19835 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19836 push_region(start_row, end_row);
19837 results
19838 }
19839
19840 pub fn gutter_highlights_in_range(
19841 &self,
19842 search_range: Range<Anchor>,
19843 display_snapshot: &DisplaySnapshot,
19844 cx: &App,
19845 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19846 let mut results = Vec::new();
19847 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19848 let color = color_fetcher(cx);
19849 let start_ix = match ranges.binary_search_by(|probe| {
19850 let cmp = probe
19851 .end
19852 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19853 if cmp.is_gt() {
19854 Ordering::Greater
19855 } else {
19856 Ordering::Less
19857 }
19858 }) {
19859 Ok(i) | Err(i) => i,
19860 };
19861 for range in &ranges[start_ix..] {
19862 if range
19863 .start
19864 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19865 .is_ge()
19866 {
19867 break;
19868 }
19869
19870 let start = range.start.to_display_point(display_snapshot);
19871 let end = range.end.to_display_point(display_snapshot);
19872 results.push((start..end, color))
19873 }
19874 }
19875 results
19876 }
19877
19878 /// Get the text ranges corresponding to the redaction query
19879 pub fn redacted_ranges(
19880 &self,
19881 search_range: Range<Anchor>,
19882 display_snapshot: &DisplaySnapshot,
19883 cx: &App,
19884 ) -> Vec<Range<DisplayPoint>> {
19885 display_snapshot
19886 .buffer_snapshot
19887 .redacted_ranges(search_range, |file| {
19888 if let Some(file) = file {
19889 file.is_private()
19890 && EditorSettings::get(
19891 Some(SettingsLocation {
19892 worktree_id: file.worktree_id(cx),
19893 path: file.path().as_ref(),
19894 }),
19895 cx,
19896 )
19897 .redact_private_values
19898 } else {
19899 false
19900 }
19901 })
19902 .map(|range| {
19903 range.start.to_display_point(display_snapshot)
19904 ..range.end.to_display_point(display_snapshot)
19905 })
19906 .collect()
19907 }
19908
19909 pub fn highlight_text_key<T: 'static>(
19910 &mut self,
19911 key: usize,
19912 ranges: Vec<Range<Anchor>>,
19913 style: HighlightStyle,
19914 cx: &mut Context<Self>,
19915 ) {
19916 self.display_map.update(cx, |map, _| {
19917 map.highlight_text(
19918 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19919 ranges,
19920 style,
19921 );
19922 });
19923 cx.notify();
19924 }
19925
19926 pub fn highlight_text<T: 'static>(
19927 &mut self,
19928 ranges: Vec<Range<Anchor>>,
19929 style: HighlightStyle,
19930 cx: &mut Context<Self>,
19931 ) {
19932 self.display_map.update(cx, |map, _| {
19933 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19934 });
19935 cx.notify();
19936 }
19937
19938 pub(crate) fn highlight_inlays<T: 'static>(
19939 &mut self,
19940 highlights: Vec<InlayHighlight>,
19941 style: HighlightStyle,
19942 cx: &mut Context<Self>,
19943 ) {
19944 self.display_map.update(cx, |map, _| {
19945 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19946 });
19947 cx.notify();
19948 }
19949
19950 pub fn text_highlights<'a, T: 'static>(
19951 &'a self,
19952 cx: &'a App,
19953 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19954 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19955 }
19956
19957 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19958 let cleared = self
19959 .display_map
19960 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19961 if cleared {
19962 cx.notify();
19963 }
19964 }
19965
19966 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19967 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19968 && self.focus_handle.is_focused(window)
19969 }
19970
19971 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19972 self.show_cursor_when_unfocused = is_enabled;
19973 cx.notify();
19974 }
19975
19976 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19977 cx.notify();
19978 }
19979
19980 fn on_debug_session_event(
19981 &mut self,
19982 _session: Entity<Session>,
19983 event: &SessionEvent,
19984 cx: &mut Context<Self>,
19985 ) {
19986 if let SessionEvent::InvalidateInlineValue = event {
19987 self.refresh_inline_values(cx);
19988 }
19989 }
19990
19991 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19992 let Some(project) = self.project.clone() else {
19993 return;
19994 };
19995
19996 if !self.inline_value_cache.enabled {
19997 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19998 self.splice_inlays(&inlays, Vec::new(), cx);
19999 return;
20000 }
20001
20002 let current_execution_position = self
20003 .highlighted_rows
20004 .get(&TypeId::of::<ActiveDebugLine>())
20005 .and_then(|lines| lines.last().map(|line| line.range.end));
20006
20007 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20008 let inline_values = editor
20009 .update(cx, |editor, cx| {
20010 let Some(current_execution_position) = current_execution_position else {
20011 return Some(Task::ready(Ok(Vec::new())));
20012 };
20013
20014 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20015 let snapshot = buffer.snapshot(cx);
20016
20017 let excerpt = snapshot.excerpt_containing(
20018 current_execution_position..current_execution_position,
20019 )?;
20020
20021 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20022 })?;
20023
20024 let range =
20025 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20026
20027 project.inline_values(buffer, range, cx)
20028 })
20029 .ok()
20030 .flatten()?
20031 .await
20032 .context("refreshing debugger inlays")
20033 .log_err()?;
20034
20035 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20036
20037 for (buffer_id, inline_value) in inline_values
20038 .into_iter()
20039 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20040 {
20041 buffer_inline_values
20042 .entry(buffer_id)
20043 .or_default()
20044 .push(inline_value);
20045 }
20046
20047 editor
20048 .update(cx, |editor, cx| {
20049 let snapshot = editor.buffer.read(cx).snapshot(cx);
20050 let mut new_inlays = Vec::default();
20051
20052 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20053 let buffer_id = buffer_snapshot.remote_id();
20054 buffer_inline_values
20055 .get(&buffer_id)
20056 .into_iter()
20057 .flatten()
20058 .for_each(|hint| {
20059 let inlay = Inlay::debugger(
20060 post_inc(&mut editor.next_inlay_id),
20061 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20062 hint.text(),
20063 );
20064 if !inlay.text.chars().contains(&'\n') {
20065 new_inlays.push(inlay);
20066 }
20067 });
20068 }
20069
20070 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20071 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20072
20073 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20074 })
20075 .ok()?;
20076 Some(())
20077 });
20078 }
20079
20080 fn on_buffer_event(
20081 &mut self,
20082 multibuffer: &Entity<MultiBuffer>,
20083 event: &multi_buffer::Event,
20084 window: &mut Window,
20085 cx: &mut Context<Self>,
20086 ) {
20087 match event {
20088 multi_buffer::Event::Edited {
20089 singleton_buffer_edited,
20090 edited_buffer,
20091 } => {
20092 self.scrollbar_marker_state.dirty = true;
20093 self.active_indent_guides_state.dirty = true;
20094 self.refresh_active_diagnostics(cx);
20095 self.refresh_code_actions(window, cx);
20096 self.refresh_selected_text_highlights(true, window, cx);
20097 self.refresh_single_line_folds(window, cx);
20098 refresh_matching_bracket_highlights(self, window, cx);
20099 if self.has_active_edit_prediction() {
20100 self.update_visible_edit_prediction(window, cx);
20101 }
20102 if let Some(project) = self.project.as_ref()
20103 && let Some(edited_buffer) = edited_buffer
20104 {
20105 project.update(cx, |project, cx| {
20106 self.registered_buffers
20107 .entry(edited_buffer.read(cx).remote_id())
20108 .or_insert_with(|| {
20109 project.register_buffer_with_language_servers(edited_buffer, cx)
20110 });
20111 });
20112 }
20113 cx.emit(EditorEvent::BufferEdited);
20114 cx.emit(SearchEvent::MatchesInvalidated);
20115
20116 if let Some(buffer) = edited_buffer {
20117 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20118 }
20119
20120 if *singleton_buffer_edited {
20121 if let Some(buffer) = edited_buffer
20122 && buffer.read(cx).file().is_none()
20123 {
20124 cx.emit(EditorEvent::TitleChanged);
20125 }
20126 if let Some(project) = &self.project {
20127 #[allow(clippy::mutable_key_type)]
20128 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20129 multibuffer
20130 .all_buffers()
20131 .into_iter()
20132 .filter_map(|buffer| {
20133 buffer.update(cx, |buffer, cx| {
20134 let language = buffer.language()?;
20135 let should_discard = project.update(cx, |project, cx| {
20136 project.is_local()
20137 && !project.has_language_servers_for(buffer, cx)
20138 });
20139 should_discard.not().then_some(language.clone())
20140 })
20141 })
20142 .collect::<HashSet<_>>()
20143 });
20144 if !languages_affected.is_empty() {
20145 self.refresh_inlay_hints(
20146 InlayHintRefreshReason::BufferEdited(languages_affected),
20147 cx,
20148 );
20149 }
20150 }
20151 }
20152
20153 let Some(project) = &self.project else { return };
20154 let (telemetry, is_via_ssh) = {
20155 let project = project.read(cx);
20156 let telemetry = project.client().telemetry().clone();
20157 let is_via_ssh = project.is_via_remote_server();
20158 (telemetry, is_via_ssh)
20159 };
20160 refresh_linked_ranges(self, window, cx);
20161 telemetry.log_edit_event("editor", is_via_ssh);
20162 }
20163 multi_buffer::Event::ExcerptsAdded {
20164 buffer,
20165 predecessor,
20166 excerpts,
20167 } => {
20168 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20169 let buffer_id = buffer.read(cx).remote_id();
20170 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20171 && let Some(project) = &self.project
20172 {
20173 update_uncommitted_diff_for_buffer(
20174 cx.entity(),
20175 project,
20176 [buffer.clone()],
20177 self.buffer.clone(),
20178 cx,
20179 )
20180 .detach();
20181 }
20182 self.update_lsp_data(false, Some(buffer_id), window, cx);
20183 cx.emit(EditorEvent::ExcerptsAdded {
20184 buffer: buffer.clone(),
20185 predecessor: *predecessor,
20186 excerpts: excerpts.clone(),
20187 });
20188 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20189 }
20190 multi_buffer::Event::ExcerptsRemoved {
20191 ids,
20192 removed_buffer_ids,
20193 } => {
20194 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20195 let buffer = self.buffer.read(cx);
20196 self.registered_buffers
20197 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20198 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20199 cx.emit(EditorEvent::ExcerptsRemoved {
20200 ids: ids.clone(),
20201 removed_buffer_ids: removed_buffer_ids.clone(),
20202 });
20203 }
20204 multi_buffer::Event::ExcerptsEdited {
20205 excerpt_ids,
20206 buffer_ids,
20207 } => {
20208 self.display_map.update(cx, |map, cx| {
20209 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20210 });
20211 cx.emit(EditorEvent::ExcerptsEdited {
20212 ids: excerpt_ids.clone(),
20213 });
20214 }
20215 multi_buffer::Event::ExcerptsExpanded { ids } => {
20216 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20217 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20218 }
20219 multi_buffer::Event::Reparsed(buffer_id) => {
20220 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20221 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20222
20223 cx.emit(EditorEvent::Reparsed(*buffer_id));
20224 }
20225 multi_buffer::Event::DiffHunksToggled => {
20226 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20227 }
20228 multi_buffer::Event::LanguageChanged(buffer_id) => {
20229 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20230 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20231 cx.emit(EditorEvent::Reparsed(*buffer_id));
20232 cx.notify();
20233 }
20234 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20235 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20236 multi_buffer::Event::FileHandleChanged
20237 | multi_buffer::Event::Reloaded
20238 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20239 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20240 multi_buffer::Event::DiagnosticsUpdated => {
20241 self.update_diagnostics_state(window, cx);
20242 }
20243 _ => {}
20244 };
20245 }
20246
20247 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20248 if !self.diagnostics_enabled() {
20249 return;
20250 }
20251 self.refresh_active_diagnostics(cx);
20252 self.refresh_inline_diagnostics(true, window, cx);
20253 self.scrollbar_marker_state.dirty = true;
20254 cx.notify();
20255 }
20256
20257 pub fn start_temporary_diff_override(&mut self) {
20258 self.load_diff_task.take();
20259 self.temporary_diff_override = true;
20260 }
20261
20262 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20263 self.temporary_diff_override = false;
20264 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20265 self.buffer.update(cx, |buffer, cx| {
20266 buffer.set_all_diff_hunks_collapsed(cx);
20267 });
20268
20269 if let Some(project) = self.project.clone() {
20270 self.load_diff_task = Some(
20271 update_uncommitted_diff_for_buffer(
20272 cx.entity(),
20273 &project,
20274 self.buffer.read(cx).all_buffers(),
20275 self.buffer.clone(),
20276 cx,
20277 )
20278 .shared(),
20279 );
20280 }
20281 }
20282
20283 fn on_display_map_changed(
20284 &mut self,
20285 _: Entity<DisplayMap>,
20286 _: &mut Window,
20287 cx: &mut Context<Self>,
20288 ) {
20289 cx.notify();
20290 }
20291
20292 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20293 if self.diagnostics_enabled() {
20294 let new_severity = EditorSettings::get_global(cx)
20295 .diagnostics_max_severity
20296 .unwrap_or(DiagnosticSeverity::Hint);
20297 self.set_max_diagnostics_severity(new_severity, cx);
20298 }
20299 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20300 self.update_edit_prediction_settings(cx);
20301 self.refresh_edit_prediction(true, false, window, cx);
20302 self.refresh_inline_values(cx);
20303 self.refresh_inlay_hints(
20304 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20305 self.selections.newest_anchor().head(),
20306 &self.buffer.read(cx).snapshot(cx),
20307 cx,
20308 )),
20309 cx,
20310 );
20311
20312 let old_cursor_shape = self.cursor_shape;
20313 let old_show_breadcrumbs = self.show_breadcrumbs;
20314
20315 {
20316 let editor_settings = EditorSettings::get_global(cx);
20317 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20318 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20319 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20320 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20321 }
20322
20323 if old_cursor_shape != self.cursor_shape {
20324 cx.emit(EditorEvent::CursorShapeChanged);
20325 }
20326
20327 if old_show_breadcrumbs != self.show_breadcrumbs {
20328 cx.emit(EditorEvent::BreadcrumbsChanged);
20329 }
20330
20331 let project_settings = ProjectSettings::get_global(cx);
20332 self.serialize_dirty_buffers =
20333 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20334
20335 if self.mode.is_full() {
20336 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20337 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20338 if self.show_inline_diagnostics != show_inline_diagnostics {
20339 self.show_inline_diagnostics = show_inline_diagnostics;
20340 self.refresh_inline_diagnostics(false, window, cx);
20341 }
20342
20343 if self.git_blame_inline_enabled != inline_blame_enabled {
20344 self.toggle_git_blame_inline_internal(false, window, cx);
20345 }
20346
20347 let minimap_settings = EditorSettings::get_global(cx).minimap;
20348 if self.minimap_visibility != MinimapVisibility::Disabled {
20349 if self.minimap_visibility.settings_visibility()
20350 != minimap_settings.minimap_enabled()
20351 {
20352 self.set_minimap_visibility(
20353 MinimapVisibility::for_mode(self.mode(), cx),
20354 window,
20355 cx,
20356 );
20357 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20358 minimap_entity.update(cx, |minimap_editor, cx| {
20359 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20360 })
20361 }
20362 }
20363 }
20364
20365 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20366 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20367 }) {
20368 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20369 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20370 }
20371 self.refresh_colors(false, None, window, cx);
20372 }
20373
20374 cx.notify();
20375 }
20376
20377 pub fn set_searchable(&mut self, searchable: bool) {
20378 self.searchable = searchable;
20379 }
20380
20381 pub fn searchable(&self) -> bool {
20382 self.searchable
20383 }
20384
20385 fn open_proposed_changes_editor(
20386 &mut self,
20387 _: &OpenProposedChangesEditor,
20388 window: &mut Window,
20389 cx: &mut Context<Self>,
20390 ) {
20391 let Some(workspace) = self.workspace() else {
20392 cx.propagate();
20393 return;
20394 };
20395
20396 let selections = self.selections.all::<usize>(cx);
20397 let multi_buffer = self.buffer.read(cx);
20398 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20399 let mut new_selections_by_buffer = HashMap::default();
20400 for selection in selections {
20401 for (buffer, range, _) in
20402 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20403 {
20404 let mut range = range.to_point(buffer);
20405 range.start.column = 0;
20406 range.end.column = buffer.line_len(range.end.row);
20407 new_selections_by_buffer
20408 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20409 .or_insert(Vec::new())
20410 .push(range)
20411 }
20412 }
20413
20414 let proposed_changes_buffers = new_selections_by_buffer
20415 .into_iter()
20416 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20417 .collect::<Vec<_>>();
20418 let proposed_changes_editor = cx.new(|cx| {
20419 ProposedChangesEditor::new(
20420 "Proposed changes",
20421 proposed_changes_buffers,
20422 self.project.clone(),
20423 window,
20424 cx,
20425 )
20426 });
20427
20428 window.defer(cx, move |window, cx| {
20429 workspace.update(cx, |workspace, cx| {
20430 workspace.active_pane().update(cx, |pane, cx| {
20431 pane.add_item(
20432 Box::new(proposed_changes_editor),
20433 true,
20434 true,
20435 None,
20436 window,
20437 cx,
20438 );
20439 });
20440 });
20441 });
20442 }
20443
20444 pub fn open_excerpts_in_split(
20445 &mut self,
20446 _: &OpenExcerptsSplit,
20447 window: &mut Window,
20448 cx: &mut Context<Self>,
20449 ) {
20450 self.open_excerpts_common(None, true, window, cx)
20451 }
20452
20453 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20454 self.open_excerpts_common(None, false, window, cx)
20455 }
20456
20457 fn open_excerpts_common(
20458 &mut self,
20459 jump_data: Option<JumpData>,
20460 split: bool,
20461 window: &mut Window,
20462 cx: &mut Context<Self>,
20463 ) {
20464 let Some(workspace) = self.workspace() else {
20465 cx.propagate();
20466 return;
20467 };
20468
20469 if self.buffer.read(cx).is_singleton() {
20470 cx.propagate();
20471 return;
20472 }
20473
20474 let mut new_selections_by_buffer = HashMap::default();
20475 match &jump_data {
20476 Some(JumpData::MultiBufferPoint {
20477 excerpt_id,
20478 position,
20479 anchor,
20480 line_offset_from_top,
20481 }) => {
20482 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20483 if let Some(buffer) = multi_buffer_snapshot
20484 .buffer_id_for_excerpt(*excerpt_id)
20485 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20486 {
20487 let buffer_snapshot = buffer.read(cx).snapshot();
20488 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20489 language::ToPoint::to_point(anchor, &buffer_snapshot)
20490 } else {
20491 buffer_snapshot.clip_point(*position, Bias::Left)
20492 };
20493 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20494 new_selections_by_buffer.insert(
20495 buffer,
20496 (
20497 vec![jump_to_offset..jump_to_offset],
20498 Some(*line_offset_from_top),
20499 ),
20500 );
20501 }
20502 }
20503 Some(JumpData::MultiBufferRow {
20504 row,
20505 line_offset_from_top,
20506 }) => {
20507 let point = MultiBufferPoint::new(row.0, 0);
20508 if let Some((buffer, buffer_point, _)) =
20509 self.buffer.read(cx).point_to_buffer_point(point, cx)
20510 {
20511 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20512 new_selections_by_buffer
20513 .entry(buffer)
20514 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20515 .0
20516 .push(buffer_offset..buffer_offset)
20517 }
20518 }
20519 None => {
20520 let selections = self.selections.all::<usize>(cx);
20521 let multi_buffer = self.buffer.read(cx);
20522 for selection in selections {
20523 for (snapshot, range, _, anchor) in multi_buffer
20524 .snapshot(cx)
20525 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20526 {
20527 if let Some(anchor) = anchor {
20528 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20529 else {
20530 continue;
20531 };
20532 let offset = text::ToOffset::to_offset(
20533 &anchor.text_anchor,
20534 &buffer_handle.read(cx).snapshot(),
20535 );
20536 let range = offset..offset;
20537 new_selections_by_buffer
20538 .entry(buffer_handle)
20539 .or_insert((Vec::new(), None))
20540 .0
20541 .push(range)
20542 } else {
20543 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20544 else {
20545 continue;
20546 };
20547 new_selections_by_buffer
20548 .entry(buffer_handle)
20549 .or_insert((Vec::new(), None))
20550 .0
20551 .push(range)
20552 }
20553 }
20554 }
20555 }
20556 }
20557
20558 new_selections_by_buffer
20559 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20560
20561 if new_selections_by_buffer.is_empty() {
20562 return;
20563 }
20564
20565 // We defer the pane interaction because we ourselves are a workspace item
20566 // and activating a new item causes the pane to call a method on us reentrantly,
20567 // which panics if we're on the stack.
20568 window.defer(cx, move |window, cx| {
20569 workspace.update(cx, |workspace, cx| {
20570 let pane = if split {
20571 workspace.adjacent_pane(window, cx)
20572 } else {
20573 workspace.active_pane().clone()
20574 };
20575
20576 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20577 let editor = buffer
20578 .read(cx)
20579 .file()
20580 .is_none()
20581 .then(|| {
20582 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20583 // so `workspace.open_project_item` will never find them, always opening a new editor.
20584 // Instead, we try to activate the existing editor in the pane first.
20585 let (editor, pane_item_index) =
20586 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20587 let editor = item.downcast::<Editor>()?;
20588 let singleton_buffer =
20589 editor.read(cx).buffer().read(cx).as_singleton()?;
20590 if singleton_buffer == buffer {
20591 Some((editor, i))
20592 } else {
20593 None
20594 }
20595 })?;
20596 pane.update(cx, |pane, cx| {
20597 pane.activate_item(pane_item_index, true, true, window, cx)
20598 });
20599 Some(editor)
20600 })
20601 .flatten()
20602 .unwrap_or_else(|| {
20603 workspace.open_project_item::<Self>(
20604 pane.clone(),
20605 buffer,
20606 true,
20607 true,
20608 window,
20609 cx,
20610 )
20611 });
20612
20613 editor.update(cx, |editor, cx| {
20614 let autoscroll = match scroll_offset {
20615 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20616 None => Autoscroll::newest(),
20617 };
20618 let nav_history = editor.nav_history.take();
20619 editor.change_selections(
20620 SelectionEffects::scroll(autoscroll),
20621 window,
20622 cx,
20623 |s| {
20624 s.select_ranges(ranges);
20625 },
20626 );
20627 editor.nav_history = nav_history;
20628 });
20629 }
20630 })
20631 });
20632 }
20633
20634 // For now, don't allow opening excerpts in buffers that aren't backed by
20635 // regular project files.
20636 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20637 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20638 }
20639
20640 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20641 let snapshot = self.buffer.read(cx).read(cx);
20642 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20643 Some(
20644 ranges
20645 .iter()
20646 .map(move |range| {
20647 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20648 })
20649 .collect(),
20650 )
20651 }
20652
20653 fn selection_replacement_ranges(
20654 &self,
20655 range: Range<OffsetUtf16>,
20656 cx: &mut App,
20657 ) -> Vec<Range<OffsetUtf16>> {
20658 let selections = self.selections.all::<OffsetUtf16>(cx);
20659 let newest_selection = selections
20660 .iter()
20661 .max_by_key(|selection| selection.id)
20662 .unwrap();
20663 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20664 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20665 let snapshot = self.buffer.read(cx).read(cx);
20666 selections
20667 .into_iter()
20668 .map(|mut selection| {
20669 selection.start.0 =
20670 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20671 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20672 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20673 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20674 })
20675 .collect()
20676 }
20677
20678 fn report_editor_event(
20679 &self,
20680 reported_event: ReportEditorEvent,
20681 file_extension: Option<String>,
20682 cx: &App,
20683 ) {
20684 if cfg!(any(test, feature = "test-support")) {
20685 return;
20686 }
20687
20688 let Some(project) = &self.project else { return };
20689
20690 // If None, we are in a file without an extension
20691 let file = self
20692 .buffer
20693 .read(cx)
20694 .as_singleton()
20695 .and_then(|b| b.read(cx).file());
20696 let file_extension = file_extension.or(file
20697 .as_ref()
20698 .and_then(|file| Path::new(file.file_name(cx)).extension())
20699 .and_then(|e| e.to_str())
20700 .map(|a| a.to_string()));
20701
20702 let vim_mode = vim_enabled(cx);
20703
20704 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20705 let copilot_enabled = edit_predictions_provider
20706 == language::language_settings::EditPredictionProvider::Copilot;
20707 let copilot_enabled_for_language = self
20708 .buffer
20709 .read(cx)
20710 .language_settings(cx)
20711 .show_edit_predictions;
20712
20713 let project = project.read(cx);
20714 let event_type = reported_event.event_type();
20715
20716 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20717 telemetry::event!(
20718 event_type,
20719 type = if auto_saved {"autosave"} else {"manual"},
20720 file_extension,
20721 vim_mode,
20722 copilot_enabled,
20723 copilot_enabled_for_language,
20724 edit_predictions_provider,
20725 is_via_ssh = project.is_via_remote_server(),
20726 );
20727 } else {
20728 telemetry::event!(
20729 event_type,
20730 file_extension,
20731 vim_mode,
20732 copilot_enabled,
20733 copilot_enabled_for_language,
20734 edit_predictions_provider,
20735 is_via_ssh = project.is_via_remote_server(),
20736 );
20737 };
20738 }
20739
20740 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20741 /// with each line being an array of {text, highlight} objects.
20742 fn copy_highlight_json(
20743 &mut self,
20744 _: &CopyHighlightJson,
20745 window: &mut Window,
20746 cx: &mut Context<Self>,
20747 ) {
20748 #[derive(Serialize)]
20749 struct Chunk<'a> {
20750 text: String,
20751 highlight: Option<&'a str>,
20752 }
20753
20754 let snapshot = self.buffer.read(cx).snapshot(cx);
20755 let range = self
20756 .selected_text_range(false, window, cx)
20757 .and_then(|selection| {
20758 if selection.range.is_empty() {
20759 None
20760 } else {
20761 Some(selection.range)
20762 }
20763 })
20764 .unwrap_or_else(|| 0..snapshot.len());
20765
20766 let chunks = snapshot.chunks(range, true);
20767 let mut lines = Vec::new();
20768 let mut line: VecDeque<Chunk> = VecDeque::new();
20769
20770 let Some(style) = self.style.as_ref() else {
20771 return;
20772 };
20773
20774 for chunk in chunks {
20775 let highlight = chunk
20776 .syntax_highlight_id
20777 .and_then(|id| id.name(&style.syntax));
20778 let mut chunk_lines = chunk.text.split('\n').peekable();
20779 while let Some(text) = chunk_lines.next() {
20780 let mut merged_with_last_token = false;
20781 if let Some(last_token) = line.back_mut()
20782 && last_token.highlight == highlight
20783 {
20784 last_token.text.push_str(text);
20785 merged_with_last_token = true;
20786 }
20787
20788 if !merged_with_last_token {
20789 line.push_back(Chunk {
20790 text: text.into(),
20791 highlight,
20792 });
20793 }
20794
20795 if chunk_lines.peek().is_some() {
20796 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20797 line.pop_front();
20798 }
20799 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20800 line.pop_back();
20801 }
20802
20803 lines.push(mem::take(&mut line));
20804 }
20805 }
20806 }
20807
20808 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20809 return;
20810 };
20811 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20812 }
20813
20814 pub fn open_context_menu(
20815 &mut self,
20816 _: &OpenContextMenu,
20817 window: &mut Window,
20818 cx: &mut Context<Self>,
20819 ) {
20820 self.request_autoscroll(Autoscroll::newest(), cx);
20821 let position = self.selections.newest_display(cx).start;
20822 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20823 }
20824
20825 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20826 &self.inlay_hint_cache
20827 }
20828
20829 pub fn replay_insert_event(
20830 &mut self,
20831 text: &str,
20832 relative_utf16_range: Option<Range<isize>>,
20833 window: &mut Window,
20834 cx: &mut Context<Self>,
20835 ) {
20836 if !self.input_enabled {
20837 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20838 return;
20839 }
20840 if let Some(relative_utf16_range) = relative_utf16_range {
20841 let selections = self.selections.all::<OffsetUtf16>(cx);
20842 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20843 let new_ranges = selections.into_iter().map(|range| {
20844 let start = OffsetUtf16(
20845 range
20846 .head()
20847 .0
20848 .saturating_add_signed(relative_utf16_range.start),
20849 );
20850 let end = OffsetUtf16(
20851 range
20852 .head()
20853 .0
20854 .saturating_add_signed(relative_utf16_range.end),
20855 );
20856 start..end
20857 });
20858 s.select_ranges(new_ranges);
20859 });
20860 }
20861
20862 self.handle_input(text, window, cx);
20863 }
20864
20865 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20866 let Some(provider) = self.semantics_provider.as_ref() else {
20867 return false;
20868 };
20869
20870 let mut supports = false;
20871 self.buffer().update(cx, |this, cx| {
20872 this.for_each_buffer(|buffer| {
20873 supports |= provider.supports_inlay_hints(buffer, cx);
20874 });
20875 });
20876
20877 supports
20878 }
20879
20880 pub fn is_focused(&self, window: &Window) -> bool {
20881 self.focus_handle.is_focused(window)
20882 }
20883
20884 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20885 cx.emit(EditorEvent::Focused);
20886
20887 if let Some(descendant) = self
20888 .last_focused_descendant
20889 .take()
20890 .and_then(|descendant| descendant.upgrade())
20891 {
20892 window.focus(&descendant);
20893 } else {
20894 if let Some(blame) = self.blame.as_ref() {
20895 blame.update(cx, GitBlame::focus)
20896 }
20897
20898 self.blink_manager.update(cx, BlinkManager::enable);
20899 self.show_cursor_names(window, cx);
20900 self.buffer.update(cx, |buffer, cx| {
20901 buffer.finalize_last_transaction(cx);
20902 if self.leader_id.is_none() {
20903 buffer.set_active_selections(
20904 &self.selections.disjoint_anchors(),
20905 self.selections.line_mode,
20906 self.cursor_shape,
20907 cx,
20908 );
20909 }
20910 });
20911 }
20912 }
20913
20914 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20915 cx.emit(EditorEvent::FocusedIn)
20916 }
20917
20918 fn handle_focus_out(
20919 &mut self,
20920 event: FocusOutEvent,
20921 _window: &mut Window,
20922 cx: &mut Context<Self>,
20923 ) {
20924 if event.blurred != self.focus_handle {
20925 self.last_focused_descendant = Some(event.blurred);
20926 }
20927 self.selection_drag_state = SelectionDragState::None;
20928 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20929 }
20930
20931 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20932 self.blink_manager.update(cx, BlinkManager::disable);
20933 self.buffer
20934 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20935
20936 if let Some(blame) = self.blame.as_ref() {
20937 blame.update(cx, GitBlame::blur)
20938 }
20939 if !self.hover_state.focused(window, cx) {
20940 hide_hover(self, cx);
20941 }
20942 if !self
20943 .context_menu
20944 .borrow()
20945 .as_ref()
20946 .is_some_and(|context_menu| context_menu.focused(window, cx))
20947 {
20948 self.hide_context_menu(window, cx);
20949 }
20950 self.discard_edit_prediction(false, cx);
20951 cx.emit(EditorEvent::Blurred);
20952 cx.notify();
20953 }
20954
20955 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20956 let mut pending: String = window
20957 .pending_input_keystrokes()
20958 .into_iter()
20959 .flatten()
20960 .filter_map(|keystroke| {
20961 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20962 keystroke.key_char.clone()
20963 } else {
20964 None
20965 }
20966 })
20967 .collect();
20968
20969 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20970 pending = "".to_string();
20971 }
20972
20973 let existing_pending = self
20974 .text_highlights::<PendingInput>(cx)
20975 .map(|(_, ranges)| ranges.to_vec());
20976 if existing_pending.is_none() && pending.is_empty() {
20977 return;
20978 }
20979 let transaction =
20980 self.transact(window, cx, |this, window, cx| {
20981 let selections = this.selections.all::<usize>(cx);
20982 let edits = selections
20983 .iter()
20984 .map(|selection| (selection.end..selection.end, pending.clone()));
20985 this.edit(edits, cx);
20986 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20987 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20988 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20989 }));
20990 });
20991 if let Some(existing_ranges) = existing_pending {
20992 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20993 this.edit(edits, cx);
20994 }
20995 });
20996
20997 let snapshot = self.snapshot(window, cx);
20998 let ranges = self
20999 .selections
21000 .all::<usize>(cx)
21001 .into_iter()
21002 .map(|selection| {
21003 snapshot.buffer_snapshot.anchor_after(selection.end)
21004 ..snapshot
21005 .buffer_snapshot
21006 .anchor_before(selection.end + pending.len())
21007 })
21008 .collect();
21009
21010 if pending.is_empty() {
21011 self.clear_highlights::<PendingInput>(cx);
21012 } else {
21013 self.highlight_text::<PendingInput>(
21014 ranges,
21015 HighlightStyle {
21016 underline: Some(UnderlineStyle {
21017 thickness: px(1.),
21018 color: None,
21019 wavy: false,
21020 }),
21021 ..Default::default()
21022 },
21023 cx,
21024 );
21025 }
21026
21027 self.ime_transaction = self.ime_transaction.or(transaction);
21028 if let Some(transaction) = self.ime_transaction {
21029 self.buffer.update(cx, |buffer, cx| {
21030 buffer.group_until_transaction(transaction, cx);
21031 });
21032 }
21033
21034 if self.text_highlights::<PendingInput>(cx).is_none() {
21035 self.ime_transaction.take();
21036 }
21037 }
21038
21039 pub fn register_action_renderer(
21040 &mut self,
21041 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21042 ) -> Subscription {
21043 let id = self.next_editor_action_id.post_inc();
21044 self.editor_actions
21045 .borrow_mut()
21046 .insert(id, Box::new(listener));
21047
21048 let editor_actions = self.editor_actions.clone();
21049 Subscription::new(move || {
21050 editor_actions.borrow_mut().remove(&id);
21051 })
21052 }
21053
21054 pub fn register_action<A: Action>(
21055 &mut self,
21056 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21057 ) -> Subscription {
21058 let id = self.next_editor_action_id.post_inc();
21059 let listener = Arc::new(listener);
21060 self.editor_actions.borrow_mut().insert(
21061 id,
21062 Box::new(move |_, window, _| {
21063 let listener = listener.clone();
21064 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21065 let action = action.downcast_ref().unwrap();
21066 if phase == DispatchPhase::Bubble {
21067 listener(action, window, cx)
21068 }
21069 })
21070 }),
21071 );
21072
21073 let editor_actions = self.editor_actions.clone();
21074 Subscription::new(move || {
21075 editor_actions.borrow_mut().remove(&id);
21076 })
21077 }
21078
21079 pub fn file_header_size(&self) -> u32 {
21080 FILE_HEADER_HEIGHT
21081 }
21082
21083 pub fn restore(
21084 &mut self,
21085 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21086 window: &mut Window,
21087 cx: &mut Context<Self>,
21088 ) {
21089 let workspace = self.workspace();
21090 let project = self.project();
21091 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21092 let mut tasks = Vec::new();
21093 for (buffer_id, changes) in revert_changes {
21094 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21095 buffer.update(cx, |buffer, cx| {
21096 buffer.edit(
21097 changes
21098 .into_iter()
21099 .map(|(range, text)| (range, text.to_string())),
21100 None,
21101 cx,
21102 );
21103 });
21104
21105 if let Some(project) =
21106 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21107 {
21108 project.update(cx, |project, cx| {
21109 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21110 })
21111 }
21112 }
21113 }
21114 tasks
21115 });
21116 cx.spawn_in(window, async move |_, cx| {
21117 for (buffer, task) in save_tasks {
21118 let result = task.await;
21119 if result.is_err() {
21120 let Some(path) = buffer
21121 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21122 .ok()
21123 else {
21124 continue;
21125 };
21126 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21127 let Some(task) = cx
21128 .update_window_entity(workspace, |workspace, window, cx| {
21129 workspace
21130 .open_path_preview(path, None, false, false, false, window, cx)
21131 })
21132 .ok()
21133 else {
21134 continue;
21135 };
21136 task.await.log_err();
21137 }
21138 }
21139 }
21140 })
21141 .detach();
21142 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21143 selections.refresh()
21144 });
21145 }
21146
21147 pub fn to_pixel_point(
21148 &self,
21149 source: multi_buffer::Anchor,
21150 editor_snapshot: &EditorSnapshot,
21151 window: &mut Window,
21152 ) -> Option<gpui::Point<Pixels>> {
21153 let source_point = source.to_display_point(editor_snapshot);
21154 self.display_to_pixel_point(source_point, editor_snapshot, window)
21155 }
21156
21157 pub fn display_to_pixel_point(
21158 &self,
21159 source: DisplayPoint,
21160 editor_snapshot: &EditorSnapshot,
21161 window: &mut Window,
21162 ) -> Option<gpui::Point<Pixels>> {
21163 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21164 let text_layout_details = self.text_layout_details(window);
21165 let scroll_top = text_layout_details
21166 .scroll_anchor
21167 .scroll_position(editor_snapshot)
21168 .y;
21169
21170 if source.row().as_f32() < scroll_top.floor() {
21171 return None;
21172 }
21173 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21174 let source_y = line_height * (source.row().as_f32() - scroll_top);
21175 Some(gpui::Point::new(source_x, source_y))
21176 }
21177
21178 pub fn has_visible_completions_menu(&self) -> bool {
21179 !self.edit_prediction_preview_is_active()
21180 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21181 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21182 })
21183 }
21184
21185 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21186 if self.mode.is_minimap() {
21187 return;
21188 }
21189 self.addons
21190 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21191 }
21192
21193 pub fn unregister_addon<T: Addon>(&mut self) {
21194 self.addons.remove(&std::any::TypeId::of::<T>());
21195 }
21196
21197 pub fn addon<T: Addon>(&self) -> Option<&T> {
21198 let type_id = std::any::TypeId::of::<T>();
21199 self.addons
21200 .get(&type_id)
21201 .and_then(|item| item.to_any().downcast_ref::<T>())
21202 }
21203
21204 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21205 let type_id = std::any::TypeId::of::<T>();
21206 self.addons
21207 .get_mut(&type_id)
21208 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21209 }
21210
21211 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21212 let text_layout_details = self.text_layout_details(window);
21213 let style = &text_layout_details.editor_style;
21214 let font_id = window.text_system().resolve_font(&style.text.font());
21215 let font_size = style.text.font_size.to_pixels(window.rem_size());
21216 let line_height = style.text.line_height_in_pixels(window.rem_size());
21217 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21218 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21219
21220 CharacterDimensions {
21221 em_width,
21222 em_advance,
21223 line_height,
21224 }
21225 }
21226
21227 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21228 self.load_diff_task.clone()
21229 }
21230
21231 fn read_metadata_from_db(
21232 &mut self,
21233 item_id: u64,
21234 workspace_id: WorkspaceId,
21235 window: &mut Window,
21236 cx: &mut Context<Editor>,
21237 ) {
21238 if self.is_singleton(cx)
21239 && !self.mode.is_minimap()
21240 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21241 {
21242 let buffer_snapshot = OnceCell::new();
21243
21244 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21245 && !folds.is_empty()
21246 {
21247 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21248 self.fold_ranges(
21249 folds
21250 .into_iter()
21251 .map(|(start, end)| {
21252 snapshot.clip_offset(start, Bias::Left)
21253 ..snapshot.clip_offset(end, Bias::Right)
21254 })
21255 .collect(),
21256 false,
21257 window,
21258 cx,
21259 );
21260 }
21261
21262 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21263 && !selections.is_empty()
21264 {
21265 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21266 // skip adding the initial selection to selection history
21267 self.selection_history.mode = SelectionHistoryMode::Skipping;
21268 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21269 s.select_ranges(selections.into_iter().map(|(start, end)| {
21270 snapshot.clip_offset(start, Bias::Left)
21271 ..snapshot.clip_offset(end, Bias::Right)
21272 }));
21273 });
21274 self.selection_history.mode = SelectionHistoryMode::Normal;
21275 };
21276 }
21277
21278 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21279 }
21280
21281 fn update_lsp_data(
21282 &mut self,
21283 ignore_cache: bool,
21284 for_buffer: Option<BufferId>,
21285 window: &mut Window,
21286 cx: &mut Context<'_, Self>,
21287 ) {
21288 self.pull_diagnostics(for_buffer, window, cx);
21289 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21290 }
21291}
21292
21293fn vim_enabled(cx: &App) -> bool {
21294 cx.global::<SettingsStore>()
21295 .raw_user_settings()
21296 .get("vim_mode")
21297 == Some(&serde_json::Value::Bool(true))
21298}
21299
21300fn process_completion_for_edit(
21301 completion: &Completion,
21302 intent: CompletionIntent,
21303 buffer: &Entity<Buffer>,
21304 cursor_position: &text::Anchor,
21305 cx: &mut Context<Editor>,
21306) -> CompletionEdit {
21307 let buffer = buffer.read(cx);
21308 let buffer_snapshot = buffer.snapshot();
21309 let (snippet, new_text) = if completion.is_snippet() {
21310 // Workaround for typescript language server issues so that methods don't expand within
21311 // strings and functions with type expressions. The previous point is used because the query
21312 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21313 let mut snippet_source = completion.new_text.clone();
21314 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21315 previous_point.column = previous_point.column.saturating_sub(1);
21316 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21317 && scope.prefers_label_for_snippet_in_completion()
21318 && let Some(label) = completion.label()
21319 && matches!(
21320 completion.kind(),
21321 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21322 )
21323 {
21324 snippet_source = label;
21325 }
21326 match Snippet::parse(&snippet_source).log_err() {
21327 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21328 None => (None, completion.new_text.clone()),
21329 }
21330 } else {
21331 (None, completion.new_text.clone())
21332 };
21333
21334 let mut range_to_replace = {
21335 let replace_range = &completion.replace_range;
21336 if let CompletionSource::Lsp {
21337 insert_range: Some(insert_range),
21338 ..
21339 } = &completion.source
21340 {
21341 debug_assert_eq!(
21342 insert_range.start, replace_range.start,
21343 "insert_range and replace_range should start at the same position"
21344 );
21345 debug_assert!(
21346 insert_range
21347 .start
21348 .cmp(cursor_position, &buffer_snapshot)
21349 .is_le(),
21350 "insert_range should start before or at cursor position"
21351 );
21352 debug_assert!(
21353 replace_range
21354 .start
21355 .cmp(cursor_position, &buffer_snapshot)
21356 .is_le(),
21357 "replace_range should start before or at cursor position"
21358 );
21359
21360 let should_replace = match intent {
21361 CompletionIntent::CompleteWithInsert => false,
21362 CompletionIntent::CompleteWithReplace => true,
21363 CompletionIntent::Complete | CompletionIntent::Compose => {
21364 let insert_mode =
21365 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21366 .completions
21367 .lsp_insert_mode;
21368 match insert_mode {
21369 LspInsertMode::Insert => false,
21370 LspInsertMode::Replace => true,
21371 LspInsertMode::ReplaceSubsequence => {
21372 let mut text_to_replace = buffer.chars_for_range(
21373 buffer.anchor_before(replace_range.start)
21374 ..buffer.anchor_after(replace_range.end),
21375 );
21376 let mut current_needle = text_to_replace.next();
21377 for haystack_ch in completion.label.text.chars() {
21378 if let Some(needle_ch) = current_needle
21379 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21380 {
21381 current_needle = text_to_replace.next();
21382 }
21383 }
21384 current_needle.is_none()
21385 }
21386 LspInsertMode::ReplaceSuffix => {
21387 if replace_range
21388 .end
21389 .cmp(cursor_position, &buffer_snapshot)
21390 .is_gt()
21391 {
21392 let range_after_cursor = *cursor_position..replace_range.end;
21393 let text_after_cursor = buffer
21394 .text_for_range(
21395 buffer.anchor_before(range_after_cursor.start)
21396 ..buffer.anchor_after(range_after_cursor.end),
21397 )
21398 .collect::<String>()
21399 .to_ascii_lowercase();
21400 completion
21401 .label
21402 .text
21403 .to_ascii_lowercase()
21404 .ends_with(&text_after_cursor)
21405 } else {
21406 true
21407 }
21408 }
21409 }
21410 }
21411 };
21412
21413 if should_replace {
21414 replace_range.clone()
21415 } else {
21416 insert_range.clone()
21417 }
21418 } else {
21419 replace_range.clone()
21420 }
21421 };
21422
21423 if range_to_replace
21424 .end
21425 .cmp(cursor_position, &buffer_snapshot)
21426 .is_lt()
21427 {
21428 range_to_replace.end = *cursor_position;
21429 }
21430
21431 CompletionEdit {
21432 new_text,
21433 replace_range: range_to_replace.to_offset(buffer),
21434 snippet,
21435 }
21436}
21437
21438struct CompletionEdit {
21439 new_text: String,
21440 replace_range: Range<usize>,
21441 snippet: Option<Snippet>,
21442}
21443
21444fn insert_extra_newline_brackets(
21445 buffer: &MultiBufferSnapshot,
21446 range: Range<usize>,
21447 language: &language::LanguageScope,
21448) -> bool {
21449 let leading_whitespace_len = buffer
21450 .reversed_chars_at(range.start)
21451 .take_while(|c| c.is_whitespace() && *c != '\n')
21452 .map(|c| c.len_utf8())
21453 .sum::<usize>();
21454 let trailing_whitespace_len = buffer
21455 .chars_at(range.end)
21456 .take_while(|c| c.is_whitespace() && *c != '\n')
21457 .map(|c| c.len_utf8())
21458 .sum::<usize>();
21459 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21460
21461 language.brackets().any(|(pair, enabled)| {
21462 let pair_start = pair.start.trim_end();
21463 let pair_end = pair.end.trim_start();
21464
21465 enabled
21466 && pair.newline
21467 && buffer.contains_str_at(range.end, pair_end)
21468 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21469 })
21470}
21471
21472fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21473 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21474 [(buffer, range, _)] => (*buffer, range.clone()),
21475 _ => return false,
21476 };
21477 let pair = {
21478 let mut result: Option<BracketMatch> = None;
21479
21480 for pair in buffer
21481 .all_bracket_ranges(range.clone())
21482 .filter(move |pair| {
21483 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21484 })
21485 {
21486 let len = pair.close_range.end - pair.open_range.start;
21487
21488 if let Some(existing) = &result {
21489 let existing_len = existing.close_range.end - existing.open_range.start;
21490 if len > existing_len {
21491 continue;
21492 }
21493 }
21494
21495 result = Some(pair);
21496 }
21497
21498 result
21499 };
21500 let Some(pair) = pair else {
21501 return false;
21502 };
21503 pair.newline_only
21504 && buffer
21505 .chars_for_range(pair.open_range.end..range.start)
21506 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21507 .all(|c| c.is_whitespace() && c != '\n')
21508}
21509
21510fn update_uncommitted_diff_for_buffer(
21511 editor: Entity<Editor>,
21512 project: &Entity<Project>,
21513 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21514 buffer: Entity<MultiBuffer>,
21515 cx: &mut App,
21516) -> Task<()> {
21517 let mut tasks = Vec::new();
21518 project.update(cx, |project, cx| {
21519 for buffer in buffers {
21520 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21521 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21522 }
21523 }
21524 });
21525 cx.spawn(async move |cx| {
21526 let diffs = future::join_all(tasks).await;
21527 if editor
21528 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21529 .unwrap_or(false)
21530 {
21531 return;
21532 }
21533
21534 buffer
21535 .update(cx, |buffer, cx| {
21536 for diff in diffs.into_iter().flatten() {
21537 buffer.add_diff(diff, cx);
21538 }
21539 })
21540 .ok();
21541 })
21542}
21543
21544fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21545 let tab_size = tab_size.get() as usize;
21546 let mut width = offset;
21547
21548 for ch in text.chars() {
21549 width += if ch == '\t' {
21550 tab_size - (width % tab_size)
21551 } else {
21552 1
21553 };
21554 }
21555
21556 width - offset
21557}
21558
21559#[cfg(test)]
21560mod tests {
21561 use super::*;
21562
21563 #[test]
21564 fn test_string_size_with_expanded_tabs() {
21565 let nz = |val| NonZeroU32::new(val).unwrap();
21566 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21567 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21568 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21569 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21570 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21571 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21572 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21573 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21574 }
21575}
21576
21577/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21578struct WordBreakingTokenizer<'a> {
21579 input: &'a str,
21580}
21581
21582impl<'a> WordBreakingTokenizer<'a> {
21583 fn new(input: &'a str) -> Self {
21584 Self { input }
21585 }
21586}
21587
21588fn is_char_ideographic(ch: char) -> bool {
21589 use unicode_script::Script::*;
21590 use unicode_script::UnicodeScript;
21591 matches!(ch.script(), Han | Tangut | Yi)
21592}
21593
21594fn is_grapheme_ideographic(text: &str) -> bool {
21595 text.chars().any(is_char_ideographic)
21596}
21597
21598fn is_grapheme_whitespace(text: &str) -> bool {
21599 text.chars().any(|x| x.is_whitespace())
21600}
21601
21602fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21603 text.chars()
21604 .next()
21605 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21606}
21607
21608#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21609enum WordBreakToken<'a> {
21610 Word { token: &'a str, grapheme_len: usize },
21611 InlineWhitespace { token: &'a str, grapheme_len: usize },
21612 Newline,
21613}
21614
21615impl<'a> Iterator for WordBreakingTokenizer<'a> {
21616 /// Yields a span, the count of graphemes in the token, and whether it was
21617 /// whitespace. Note that it also breaks at word boundaries.
21618 type Item = WordBreakToken<'a>;
21619
21620 fn next(&mut self) -> Option<Self::Item> {
21621 use unicode_segmentation::UnicodeSegmentation;
21622 if self.input.is_empty() {
21623 return None;
21624 }
21625
21626 let mut iter = self.input.graphemes(true).peekable();
21627 let mut offset = 0;
21628 let mut grapheme_len = 0;
21629 if let Some(first_grapheme) = iter.next() {
21630 let is_newline = first_grapheme == "\n";
21631 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21632 offset += first_grapheme.len();
21633 grapheme_len += 1;
21634 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21635 if let Some(grapheme) = iter.peek().copied()
21636 && should_stay_with_preceding_ideograph(grapheme)
21637 {
21638 offset += grapheme.len();
21639 grapheme_len += 1;
21640 }
21641 } else {
21642 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21643 let mut next_word_bound = words.peek().copied();
21644 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21645 next_word_bound = words.next();
21646 }
21647 while let Some(grapheme) = iter.peek().copied() {
21648 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21649 break;
21650 };
21651 if is_grapheme_whitespace(grapheme) != is_whitespace
21652 || (grapheme == "\n") != is_newline
21653 {
21654 break;
21655 };
21656 offset += grapheme.len();
21657 grapheme_len += 1;
21658 iter.next();
21659 }
21660 }
21661 let token = &self.input[..offset];
21662 self.input = &self.input[offset..];
21663 if token == "\n" {
21664 Some(WordBreakToken::Newline)
21665 } else if is_whitespace {
21666 Some(WordBreakToken::InlineWhitespace {
21667 token,
21668 grapheme_len,
21669 })
21670 } else {
21671 Some(WordBreakToken::Word {
21672 token,
21673 grapheme_len,
21674 })
21675 }
21676 } else {
21677 None
21678 }
21679 }
21680}
21681
21682#[test]
21683fn test_word_breaking_tokenizer() {
21684 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21685 ("", &[]),
21686 (" ", &[whitespace(" ", 2)]),
21687 ("Ʒ", &[word("Ʒ", 1)]),
21688 ("Ǽ", &[word("Ǽ", 1)]),
21689 ("⋑", &[word("⋑", 1)]),
21690 ("⋑⋑", &[word("⋑⋑", 2)]),
21691 (
21692 "原理,进而",
21693 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21694 ),
21695 (
21696 "hello world",
21697 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21698 ),
21699 (
21700 "hello, world",
21701 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21702 ),
21703 (
21704 " hello world",
21705 &[
21706 whitespace(" ", 2),
21707 word("hello", 5),
21708 whitespace(" ", 1),
21709 word("world", 5),
21710 ],
21711 ),
21712 (
21713 "这是什么 \n 钢笔",
21714 &[
21715 word("这", 1),
21716 word("是", 1),
21717 word("什", 1),
21718 word("么", 1),
21719 whitespace(" ", 1),
21720 newline(),
21721 whitespace(" ", 1),
21722 word("钢", 1),
21723 word("笔", 1),
21724 ],
21725 ),
21726 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21727 ];
21728
21729 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21730 WordBreakToken::Word {
21731 token,
21732 grapheme_len,
21733 }
21734 }
21735
21736 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21737 WordBreakToken::InlineWhitespace {
21738 token,
21739 grapheme_len,
21740 }
21741 }
21742
21743 fn newline() -> WordBreakToken<'static> {
21744 WordBreakToken::Newline
21745 }
21746
21747 for (input, result) in tests {
21748 assert_eq!(
21749 WordBreakingTokenizer::new(input)
21750 .collect::<Vec<_>>()
21751 .as_slice(),
21752 *result,
21753 );
21754 }
21755}
21756
21757fn wrap_with_prefix(
21758 first_line_prefix: String,
21759 subsequent_lines_prefix: String,
21760 unwrapped_text: String,
21761 wrap_column: usize,
21762 tab_size: NonZeroU32,
21763 preserve_existing_whitespace: bool,
21764) -> String {
21765 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21766 let subsequent_lines_prefix_len =
21767 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21768 let mut wrapped_text = String::new();
21769 let mut current_line = first_line_prefix;
21770 let mut is_first_line = true;
21771
21772 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21773 let mut current_line_len = first_line_prefix_len;
21774 let mut in_whitespace = false;
21775 for token in tokenizer {
21776 let have_preceding_whitespace = in_whitespace;
21777 match token {
21778 WordBreakToken::Word {
21779 token,
21780 grapheme_len,
21781 } => {
21782 in_whitespace = false;
21783 let current_prefix_len = if is_first_line {
21784 first_line_prefix_len
21785 } else {
21786 subsequent_lines_prefix_len
21787 };
21788 if current_line_len + grapheme_len > wrap_column
21789 && current_line_len != current_prefix_len
21790 {
21791 wrapped_text.push_str(current_line.trim_end());
21792 wrapped_text.push('\n');
21793 is_first_line = false;
21794 current_line = subsequent_lines_prefix.clone();
21795 current_line_len = subsequent_lines_prefix_len;
21796 }
21797 current_line.push_str(token);
21798 current_line_len += grapheme_len;
21799 }
21800 WordBreakToken::InlineWhitespace {
21801 mut token,
21802 mut grapheme_len,
21803 } => {
21804 in_whitespace = true;
21805 if have_preceding_whitespace && !preserve_existing_whitespace {
21806 continue;
21807 }
21808 if !preserve_existing_whitespace {
21809 token = " ";
21810 grapheme_len = 1;
21811 }
21812 let current_prefix_len = if is_first_line {
21813 first_line_prefix_len
21814 } else {
21815 subsequent_lines_prefix_len
21816 };
21817 if current_line_len + grapheme_len > wrap_column {
21818 wrapped_text.push_str(current_line.trim_end());
21819 wrapped_text.push('\n');
21820 is_first_line = false;
21821 current_line = subsequent_lines_prefix.clone();
21822 current_line_len = subsequent_lines_prefix_len;
21823 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21824 current_line.push_str(token);
21825 current_line_len += grapheme_len;
21826 }
21827 }
21828 WordBreakToken::Newline => {
21829 in_whitespace = true;
21830 let current_prefix_len = if is_first_line {
21831 first_line_prefix_len
21832 } else {
21833 subsequent_lines_prefix_len
21834 };
21835 if preserve_existing_whitespace {
21836 wrapped_text.push_str(current_line.trim_end());
21837 wrapped_text.push('\n');
21838 is_first_line = false;
21839 current_line = subsequent_lines_prefix.clone();
21840 current_line_len = subsequent_lines_prefix_len;
21841 } else if have_preceding_whitespace {
21842 continue;
21843 } else if current_line_len + 1 > wrap_column
21844 && current_line_len != current_prefix_len
21845 {
21846 wrapped_text.push_str(current_line.trim_end());
21847 wrapped_text.push('\n');
21848 is_first_line = false;
21849 current_line = subsequent_lines_prefix.clone();
21850 current_line_len = subsequent_lines_prefix_len;
21851 } else if current_line_len != current_prefix_len {
21852 current_line.push(' ');
21853 current_line_len += 1;
21854 }
21855 }
21856 }
21857 }
21858
21859 if !current_line.is_empty() {
21860 wrapped_text.push_str(¤t_line);
21861 }
21862 wrapped_text
21863}
21864
21865#[test]
21866fn test_wrap_with_prefix() {
21867 assert_eq!(
21868 wrap_with_prefix(
21869 "# ".to_string(),
21870 "# ".to_string(),
21871 "abcdefg".to_string(),
21872 4,
21873 NonZeroU32::new(4).unwrap(),
21874 false,
21875 ),
21876 "# abcdefg"
21877 );
21878 assert_eq!(
21879 wrap_with_prefix(
21880 "".to_string(),
21881 "".to_string(),
21882 "\thello world".to_string(),
21883 8,
21884 NonZeroU32::new(4).unwrap(),
21885 false,
21886 ),
21887 "hello\nworld"
21888 );
21889 assert_eq!(
21890 wrap_with_prefix(
21891 "// ".to_string(),
21892 "// ".to_string(),
21893 "xx \nyy zz aa bb cc".to_string(),
21894 12,
21895 NonZeroU32::new(4).unwrap(),
21896 false,
21897 ),
21898 "// xx yy zz\n// aa bb cc"
21899 );
21900 assert_eq!(
21901 wrap_with_prefix(
21902 String::new(),
21903 String::new(),
21904 "这是什么 \n 钢笔".to_string(),
21905 3,
21906 NonZeroU32::new(4).unwrap(),
21907 false,
21908 ),
21909 "这是什\n么 钢\n笔"
21910 );
21911}
21912
21913pub trait CollaborationHub {
21914 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21915 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21916 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21917}
21918
21919impl CollaborationHub for Entity<Project> {
21920 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21921 self.read(cx).collaborators()
21922 }
21923
21924 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21925 self.read(cx).user_store().read(cx).participant_indices()
21926 }
21927
21928 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21929 let this = self.read(cx);
21930 let user_ids = this.collaborators().values().map(|c| c.user_id);
21931 this.user_store().read(cx).participant_names(user_ids, cx)
21932 }
21933}
21934
21935pub trait SemanticsProvider {
21936 fn hover(
21937 &self,
21938 buffer: &Entity<Buffer>,
21939 position: text::Anchor,
21940 cx: &mut App,
21941 ) -> Option<Task<Option<Vec<project::Hover>>>>;
21942
21943 fn inline_values(
21944 &self,
21945 buffer_handle: Entity<Buffer>,
21946 range: Range<text::Anchor>,
21947 cx: &mut App,
21948 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21949
21950 fn inlay_hints(
21951 &self,
21952 buffer_handle: Entity<Buffer>,
21953 range: Range<text::Anchor>,
21954 cx: &mut App,
21955 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21956
21957 fn resolve_inlay_hint(
21958 &self,
21959 hint: InlayHint,
21960 buffer_handle: Entity<Buffer>,
21961 server_id: LanguageServerId,
21962 cx: &mut App,
21963 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21964
21965 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21966
21967 fn document_highlights(
21968 &self,
21969 buffer: &Entity<Buffer>,
21970 position: text::Anchor,
21971 cx: &mut App,
21972 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21973
21974 fn definitions(
21975 &self,
21976 buffer: &Entity<Buffer>,
21977 position: text::Anchor,
21978 kind: GotoDefinitionKind,
21979 cx: &mut App,
21980 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
21981
21982 fn range_for_rename(
21983 &self,
21984 buffer: &Entity<Buffer>,
21985 position: text::Anchor,
21986 cx: &mut App,
21987 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21988
21989 fn perform_rename(
21990 &self,
21991 buffer: &Entity<Buffer>,
21992 position: text::Anchor,
21993 new_name: String,
21994 cx: &mut App,
21995 ) -> Option<Task<Result<ProjectTransaction>>>;
21996}
21997
21998pub trait CompletionProvider {
21999 fn completions(
22000 &self,
22001 excerpt_id: ExcerptId,
22002 buffer: &Entity<Buffer>,
22003 buffer_position: text::Anchor,
22004 trigger: CompletionContext,
22005 window: &mut Window,
22006 cx: &mut Context<Editor>,
22007 ) -> Task<Result<Vec<CompletionResponse>>>;
22008
22009 fn resolve_completions(
22010 &self,
22011 _buffer: Entity<Buffer>,
22012 _completion_indices: Vec<usize>,
22013 _completions: Rc<RefCell<Box<[Completion]>>>,
22014 _cx: &mut Context<Editor>,
22015 ) -> Task<Result<bool>> {
22016 Task::ready(Ok(false))
22017 }
22018
22019 fn apply_additional_edits_for_completion(
22020 &self,
22021 _buffer: Entity<Buffer>,
22022 _completions: Rc<RefCell<Box<[Completion]>>>,
22023 _completion_index: usize,
22024 _push_to_history: bool,
22025 _cx: &mut Context<Editor>,
22026 ) -> Task<Result<Option<language::Transaction>>> {
22027 Task::ready(Ok(None))
22028 }
22029
22030 fn is_completion_trigger(
22031 &self,
22032 buffer: &Entity<Buffer>,
22033 position: language::Anchor,
22034 text: &str,
22035 trigger_in_words: bool,
22036 menu_is_open: bool,
22037 cx: &mut Context<Editor>,
22038 ) -> bool;
22039
22040 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22041
22042 fn sort_completions(&self) -> bool {
22043 true
22044 }
22045
22046 fn filter_completions(&self) -> bool {
22047 true
22048 }
22049}
22050
22051pub trait CodeActionProvider {
22052 fn id(&self) -> Arc<str>;
22053
22054 fn code_actions(
22055 &self,
22056 buffer: &Entity<Buffer>,
22057 range: Range<text::Anchor>,
22058 window: &mut Window,
22059 cx: &mut App,
22060 ) -> Task<Result<Vec<CodeAction>>>;
22061
22062 fn apply_code_action(
22063 &self,
22064 buffer_handle: Entity<Buffer>,
22065 action: CodeAction,
22066 excerpt_id: ExcerptId,
22067 push_to_history: bool,
22068 window: &mut Window,
22069 cx: &mut App,
22070 ) -> Task<Result<ProjectTransaction>>;
22071}
22072
22073impl CodeActionProvider for Entity<Project> {
22074 fn id(&self) -> Arc<str> {
22075 "project".into()
22076 }
22077
22078 fn code_actions(
22079 &self,
22080 buffer: &Entity<Buffer>,
22081 range: Range<text::Anchor>,
22082 _window: &mut Window,
22083 cx: &mut App,
22084 ) -> Task<Result<Vec<CodeAction>>> {
22085 self.update(cx, |project, cx| {
22086 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22087 let code_actions = project.code_actions(buffer, range, None, cx);
22088 cx.background_spawn(async move {
22089 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22090 Ok(code_lens_actions
22091 .context("code lens fetch")?
22092 .into_iter()
22093 .flatten()
22094 .chain(
22095 code_actions
22096 .context("code action fetch")?
22097 .into_iter()
22098 .flatten(),
22099 )
22100 .collect())
22101 })
22102 })
22103 }
22104
22105 fn apply_code_action(
22106 &self,
22107 buffer_handle: Entity<Buffer>,
22108 action: CodeAction,
22109 _excerpt_id: ExcerptId,
22110 push_to_history: bool,
22111 _window: &mut Window,
22112 cx: &mut App,
22113 ) -> Task<Result<ProjectTransaction>> {
22114 self.update(cx, |project, cx| {
22115 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22116 })
22117 }
22118}
22119
22120fn snippet_completions(
22121 project: &Project,
22122 buffer: &Entity<Buffer>,
22123 buffer_position: text::Anchor,
22124 cx: &mut App,
22125) -> Task<Result<CompletionResponse>> {
22126 let languages = buffer.read(cx).languages_at(buffer_position);
22127 let snippet_store = project.snippets().read(cx);
22128
22129 let scopes: Vec<_> = languages
22130 .iter()
22131 .filter_map(|language| {
22132 let language_name = language.lsp_id();
22133 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22134
22135 if snippets.is_empty() {
22136 None
22137 } else {
22138 Some((language.default_scope(), snippets))
22139 }
22140 })
22141 .collect();
22142
22143 if scopes.is_empty() {
22144 return Task::ready(Ok(CompletionResponse {
22145 completions: vec![],
22146 is_incomplete: false,
22147 }));
22148 }
22149
22150 let snapshot = buffer.read(cx).text_snapshot();
22151 let chars: String = snapshot
22152 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22153 .collect();
22154 let executor = cx.background_executor().clone();
22155
22156 cx.background_spawn(async move {
22157 let mut is_incomplete = false;
22158 let mut completions: Vec<Completion> = Vec::new();
22159 for (scope, snippets) in scopes.into_iter() {
22160 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22161 let mut last_word = chars
22162 .chars()
22163 .take_while(|c| classifier.is_word(*c))
22164 .collect::<String>();
22165 last_word = last_word.chars().rev().collect();
22166
22167 if last_word.is_empty() {
22168 return Ok(CompletionResponse {
22169 completions: vec![],
22170 is_incomplete: true,
22171 });
22172 }
22173
22174 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22175 let to_lsp = |point: &text::Anchor| {
22176 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22177 point_to_lsp(end)
22178 };
22179 let lsp_end = to_lsp(&buffer_position);
22180
22181 let candidates = snippets
22182 .iter()
22183 .enumerate()
22184 .flat_map(|(ix, snippet)| {
22185 snippet
22186 .prefix
22187 .iter()
22188 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22189 })
22190 .collect::<Vec<StringMatchCandidate>>();
22191
22192 const MAX_RESULTS: usize = 100;
22193 let mut matches = fuzzy::match_strings(
22194 &candidates,
22195 &last_word,
22196 last_word.chars().any(|c| c.is_uppercase()),
22197 true,
22198 MAX_RESULTS,
22199 &Default::default(),
22200 executor.clone(),
22201 )
22202 .await;
22203
22204 if matches.len() >= MAX_RESULTS {
22205 is_incomplete = true;
22206 }
22207
22208 // Remove all candidates where the query's start does not match the start of any word in the candidate
22209 if let Some(query_start) = last_word.chars().next() {
22210 matches.retain(|string_match| {
22211 split_words(&string_match.string).any(|word| {
22212 // Check that the first codepoint of the word as lowercase matches the first
22213 // codepoint of the query as lowercase
22214 word.chars()
22215 .flat_map(|codepoint| codepoint.to_lowercase())
22216 .zip(query_start.to_lowercase())
22217 .all(|(word_cp, query_cp)| word_cp == query_cp)
22218 })
22219 });
22220 }
22221
22222 let matched_strings = matches
22223 .into_iter()
22224 .map(|m| m.string)
22225 .collect::<HashSet<_>>();
22226
22227 completions.extend(snippets.iter().filter_map(|snippet| {
22228 let matching_prefix = snippet
22229 .prefix
22230 .iter()
22231 .find(|prefix| matched_strings.contains(*prefix))?;
22232 let start = as_offset - last_word.len();
22233 let start = snapshot.anchor_before(start);
22234 let range = start..buffer_position;
22235 let lsp_start = to_lsp(&start);
22236 let lsp_range = lsp::Range {
22237 start: lsp_start,
22238 end: lsp_end,
22239 };
22240 Some(Completion {
22241 replace_range: range,
22242 new_text: snippet.body.clone(),
22243 source: CompletionSource::Lsp {
22244 insert_range: None,
22245 server_id: LanguageServerId(usize::MAX),
22246 resolved: true,
22247 lsp_completion: Box::new(lsp::CompletionItem {
22248 label: snippet.prefix.first().unwrap().clone(),
22249 kind: Some(CompletionItemKind::SNIPPET),
22250 label_details: snippet.description.as_ref().map(|description| {
22251 lsp::CompletionItemLabelDetails {
22252 detail: Some(description.clone()),
22253 description: None,
22254 }
22255 }),
22256 insert_text_format: Some(InsertTextFormat::SNIPPET),
22257 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22258 lsp::InsertReplaceEdit {
22259 new_text: snippet.body.clone(),
22260 insert: lsp_range,
22261 replace: lsp_range,
22262 },
22263 )),
22264 filter_text: Some(snippet.body.clone()),
22265 sort_text: Some(char::MAX.to_string()),
22266 ..lsp::CompletionItem::default()
22267 }),
22268 lsp_defaults: None,
22269 },
22270 label: CodeLabel {
22271 text: matching_prefix.clone(),
22272 runs: Vec::new(),
22273 filter_range: 0..matching_prefix.len(),
22274 },
22275 icon_path: None,
22276 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22277 single_line: snippet.name.clone().into(),
22278 plain_text: snippet
22279 .description
22280 .clone()
22281 .map(|description| description.into()),
22282 }),
22283 insert_text_mode: None,
22284 confirm: None,
22285 })
22286 }))
22287 }
22288
22289 Ok(CompletionResponse {
22290 completions,
22291 is_incomplete,
22292 })
22293 })
22294}
22295
22296impl CompletionProvider for Entity<Project> {
22297 fn completions(
22298 &self,
22299 _excerpt_id: ExcerptId,
22300 buffer: &Entity<Buffer>,
22301 buffer_position: text::Anchor,
22302 options: CompletionContext,
22303 _window: &mut Window,
22304 cx: &mut Context<Editor>,
22305 ) -> Task<Result<Vec<CompletionResponse>>> {
22306 self.update(cx, |project, cx| {
22307 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22308 let project_completions = project.completions(buffer, buffer_position, options, cx);
22309 cx.background_spawn(async move {
22310 let mut responses = project_completions.await?;
22311 let snippets = snippets.await?;
22312 if !snippets.completions.is_empty() {
22313 responses.push(snippets);
22314 }
22315 Ok(responses)
22316 })
22317 })
22318 }
22319
22320 fn resolve_completions(
22321 &self,
22322 buffer: Entity<Buffer>,
22323 completion_indices: Vec<usize>,
22324 completions: Rc<RefCell<Box<[Completion]>>>,
22325 cx: &mut Context<Editor>,
22326 ) -> Task<Result<bool>> {
22327 self.update(cx, |project, cx| {
22328 project.lsp_store().update(cx, |lsp_store, cx| {
22329 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22330 })
22331 })
22332 }
22333
22334 fn apply_additional_edits_for_completion(
22335 &self,
22336 buffer: Entity<Buffer>,
22337 completions: Rc<RefCell<Box<[Completion]>>>,
22338 completion_index: usize,
22339 push_to_history: bool,
22340 cx: &mut Context<Editor>,
22341 ) -> Task<Result<Option<language::Transaction>>> {
22342 self.update(cx, |project, cx| {
22343 project.lsp_store().update(cx, |lsp_store, cx| {
22344 lsp_store.apply_additional_edits_for_completion(
22345 buffer,
22346 completions,
22347 completion_index,
22348 push_to_history,
22349 cx,
22350 )
22351 })
22352 })
22353 }
22354
22355 fn is_completion_trigger(
22356 &self,
22357 buffer: &Entity<Buffer>,
22358 position: language::Anchor,
22359 text: &str,
22360 trigger_in_words: bool,
22361 menu_is_open: bool,
22362 cx: &mut Context<Editor>,
22363 ) -> bool {
22364 let mut chars = text.chars();
22365 let char = if let Some(char) = chars.next() {
22366 char
22367 } else {
22368 return false;
22369 };
22370 if chars.next().is_some() {
22371 return false;
22372 }
22373
22374 let buffer = buffer.read(cx);
22375 let snapshot = buffer.snapshot();
22376 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22377 return false;
22378 }
22379 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22380 if trigger_in_words && classifier.is_word(char) {
22381 return true;
22382 }
22383
22384 buffer.completion_triggers().contains(text)
22385 }
22386}
22387
22388impl SemanticsProvider for Entity<Project> {
22389 fn hover(
22390 &self,
22391 buffer: &Entity<Buffer>,
22392 position: text::Anchor,
22393 cx: &mut App,
22394 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22395 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22396 }
22397
22398 fn document_highlights(
22399 &self,
22400 buffer: &Entity<Buffer>,
22401 position: text::Anchor,
22402 cx: &mut App,
22403 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22404 Some(self.update(cx, |project, cx| {
22405 project.document_highlights(buffer, position, cx)
22406 }))
22407 }
22408
22409 fn definitions(
22410 &self,
22411 buffer: &Entity<Buffer>,
22412 position: text::Anchor,
22413 kind: GotoDefinitionKind,
22414 cx: &mut App,
22415 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22416 Some(self.update(cx, |project, cx| match kind {
22417 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22418 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22419 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22420 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22421 }))
22422 }
22423
22424 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22425 self.update(cx, |project, cx| {
22426 if project
22427 .active_debug_session(cx)
22428 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22429 {
22430 return true;
22431 }
22432
22433 buffer.update(cx, |buffer, cx| {
22434 project.any_language_server_supports_inlay_hints(buffer, cx)
22435 })
22436 })
22437 }
22438
22439 fn inline_values(
22440 &self,
22441 buffer_handle: Entity<Buffer>,
22442 range: Range<text::Anchor>,
22443 cx: &mut App,
22444 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22445 self.update(cx, |project, cx| {
22446 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22447
22448 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22449 })
22450 }
22451
22452 fn inlay_hints(
22453 &self,
22454 buffer_handle: Entity<Buffer>,
22455 range: Range<text::Anchor>,
22456 cx: &mut App,
22457 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22458 Some(self.update(cx, |project, cx| {
22459 project.inlay_hints(buffer_handle, range, cx)
22460 }))
22461 }
22462
22463 fn resolve_inlay_hint(
22464 &self,
22465 hint: InlayHint,
22466 buffer_handle: Entity<Buffer>,
22467 server_id: LanguageServerId,
22468 cx: &mut App,
22469 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22470 Some(self.update(cx, |project, cx| {
22471 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22472 }))
22473 }
22474
22475 fn range_for_rename(
22476 &self,
22477 buffer: &Entity<Buffer>,
22478 position: text::Anchor,
22479 cx: &mut App,
22480 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22481 Some(self.update(cx, |project, cx| {
22482 let buffer = buffer.clone();
22483 let task = project.prepare_rename(buffer.clone(), position, cx);
22484 cx.spawn(async move |_, cx| {
22485 Ok(match task.await? {
22486 PrepareRenameResponse::Success(range) => Some(range),
22487 PrepareRenameResponse::InvalidPosition => None,
22488 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22489 // Fallback on using TreeSitter info to determine identifier range
22490 buffer.read_with(cx, |buffer, _| {
22491 let snapshot = buffer.snapshot();
22492 let (range, kind) = snapshot.surrounding_word(position, false);
22493 if kind != Some(CharKind::Word) {
22494 return None;
22495 }
22496 Some(
22497 snapshot.anchor_before(range.start)
22498 ..snapshot.anchor_after(range.end),
22499 )
22500 })?
22501 }
22502 })
22503 })
22504 }))
22505 }
22506
22507 fn perform_rename(
22508 &self,
22509 buffer: &Entity<Buffer>,
22510 position: text::Anchor,
22511 new_name: String,
22512 cx: &mut App,
22513 ) -> Option<Task<Result<ProjectTransaction>>> {
22514 Some(self.update(cx, |project, cx| {
22515 project.perform_rename(buffer.clone(), position, new_name, cx)
22516 }))
22517 }
22518}
22519
22520fn inlay_hint_settings(
22521 location: Anchor,
22522 snapshot: &MultiBufferSnapshot,
22523 cx: &mut Context<Editor>,
22524) -> InlayHintSettings {
22525 let file = snapshot.file_at(location);
22526 let language = snapshot.language_at(location).map(|l| l.name());
22527 language_settings(language, file, cx).inlay_hints
22528}
22529
22530fn consume_contiguous_rows(
22531 contiguous_row_selections: &mut Vec<Selection<Point>>,
22532 selection: &Selection<Point>,
22533 display_map: &DisplaySnapshot,
22534 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22535) -> (MultiBufferRow, MultiBufferRow) {
22536 contiguous_row_selections.push(selection.clone());
22537 let start_row = starting_row(selection, display_map);
22538 let mut end_row = ending_row(selection, display_map);
22539
22540 while let Some(next_selection) = selections.peek() {
22541 if next_selection.start.row <= end_row.0 {
22542 end_row = ending_row(next_selection, display_map);
22543 contiguous_row_selections.push(selections.next().unwrap().clone());
22544 } else {
22545 break;
22546 }
22547 }
22548 (start_row, end_row)
22549}
22550
22551fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22552 if selection.start.column > 0 {
22553 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22554 } else {
22555 MultiBufferRow(selection.start.row)
22556 }
22557}
22558
22559fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22560 if next_selection.end.column > 0 || next_selection.is_empty() {
22561 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22562 } else {
22563 MultiBufferRow(next_selection.end.row)
22564 }
22565}
22566
22567impl EditorSnapshot {
22568 pub fn remote_selections_in_range<'a>(
22569 &'a self,
22570 range: &'a Range<Anchor>,
22571 collaboration_hub: &dyn CollaborationHub,
22572 cx: &'a App,
22573 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22574 let participant_names = collaboration_hub.user_names(cx);
22575 let participant_indices = collaboration_hub.user_participant_indices(cx);
22576 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22577 let collaborators_by_replica_id = collaborators_by_peer_id
22578 .values()
22579 .map(|collaborator| (collaborator.replica_id, collaborator))
22580 .collect::<HashMap<_, _>>();
22581 self.buffer_snapshot
22582 .selections_in_range(range, false)
22583 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22584 if replica_id == AGENT_REPLICA_ID {
22585 Some(RemoteSelection {
22586 replica_id,
22587 selection,
22588 cursor_shape,
22589 line_mode,
22590 collaborator_id: CollaboratorId::Agent,
22591 user_name: Some("Agent".into()),
22592 color: cx.theme().players().agent(),
22593 })
22594 } else {
22595 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22596 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22597 let user_name = participant_names.get(&collaborator.user_id).cloned();
22598 Some(RemoteSelection {
22599 replica_id,
22600 selection,
22601 cursor_shape,
22602 line_mode,
22603 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22604 user_name,
22605 color: if let Some(index) = participant_index {
22606 cx.theme().players().color_for_participant(index.0)
22607 } else {
22608 cx.theme().players().absent()
22609 },
22610 })
22611 }
22612 })
22613 }
22614
22615 pub fn hunks_for_ranges(
22616 &self,
22617 ranges: impl IntoIterator<Item = Range<Point>>,
22618 ) -> Vec<MultiBufferDiffHunk> {
22619 let mut hunks = Vec::new();
22620 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22621 HashMap::default();
22622 for query_range in ranges {
22623 let query_rows =
22624 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22625 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22626 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22627 ) {
22628 // Include deleted hunks that are adjacent to the query range, because
22629 // otherwise they would be missed.
22630 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22631 if hunk.status().is_deleted() {
22632 intersects_range |= hunk.row_range.start == query_rows.end;
22633 intersects_range |= hunk.row_range.end == query_rows.start;
22634 }
22635 if intersects_range {
22636 if !processed_buffer_rows
22637 .entry(hunk.buffer_id)
22638 .or_default()
22639 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22640 {
22641 continue;
22642 }
22643 hunks.push(hunk);
22644 }
22645 }
22646 }
22647
22648 hunks
22649 }
22650
22651 fn display_diff_hunks_for_rows<'a>(
22652 &'a self,
22653 display_rows: Range<DisplayRow>,
22654 folded_buffers: &'a HashSet<BufferId>,
22655 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22656 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22657 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22658
22659 self.buffer_snapshot
22660 .diff_hunks_in_range(buffer_start..buffer_end)
22661 .filter_map(|hunk| {
22662 if folded_buffers.contains(&hunk.buffer_id) {
22663 return None;
22664 }
22665
22666 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22667 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22668
22669 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22670 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22671
22672 let display_hunk = if hunk_display_start.column() != 0 {
22673 DisplayDiffHunk::Folded {
22674 display_row: hunk_display_start.row(),
22675 }
22676 } else {
22677 let mut end_row = hunk_display_end.row();
22678 if hunk_display_end.column() > 0 {
22679 end_row.0 += 1;
22680 }
22681 let is_created_file = hunk.is_created_file();
22682 DisplayDiffHunk::Unfolded {
22683 status: hunk.status(),
22684 diff_base_byte_range: hunk.diff_base_byte_range,
22685 display_row_range: hunk_display_start.row()..end_row,
22686 multi_buffer_range: Anchor::range_in_buffer(
22687 hunk.excerpt_id,
22688 hunk.buffer_id,
22689 hunk.buffer_range,
22690 ),
22691 is_created_file,
22692 }
22693 };
22694
22695 Some(display_hunk)
22696 })
22697 }
22698
22699 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22700 self.display_snapshot.buffer_snapshot.language_at(position)
22701 }
22702
22703 pub fn is_focused(&self) -> bool {
22704 self.is_focused
22705 }
22706
22707 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22708 self.placeholder_text.as_ref()
22709 }
22710
22711 pub fn scroll_position(&self) -> gpui::Point<f32> {
22712 self.scroll_anchor.scroll_position(&self.display_snapshot)
22713 }
22714
22715 fn gutter_dimensions(
22716 &self,
22717 font_id: FontId,
22718 font_size: Pixels,
22719 max_line_number_width: Pixels,
22720 cx: &App,
22721 ) -> Option<GutterDimensions> {
22722 if !self.show_gutter {
22723 return None;
22724 }
22725
22726 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22727 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22728
22729 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22730 matches!(
22731 ProjectSettings::get_global(cx).git.git_gutter,
22732 Some(GitGutterSetting::TrackedFiles)
22733 )
22734 });
22735 let gutter_settings = EditorSettings::get_global(cx).gutter;
22736 let show_line_numbers = self
22737 .show_line_numbers
22738 .unwrap_or(gutter_settings.line_numbers);
22739 let line_gutter_width = if show_line_numbers {
22740 // Avoid flicker-like gutter resizes when the line number gains another digit by
22741 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22742 let min_width_for_number_on_gutter =
22743 ch_advance * gutter_settings.min_line_number_digits as f32;
22744 max_line_number_width.max(min_width_for_number_on_gutter)
22745 } else {
22746 0.0.into()
22747 };
22748
22749 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22750 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22751
22752 let git_blame_entries_width =
22753 self.git_blame_gutter_max_author_length
22754 .map(|max_author_length| {
22755 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22756 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22757
22758 /// The number of characters to dedicate to gaps and margins.
22759 const SPACING_WIDTH: usize = 4;
22760
22761 let max_char_count = max_author_length.min(renderer.max_author_length())
22762 + ::git::SHORT_SHA_LENGTH
22763 + MAX_RELATIVE_TIMESTAMP.len()
22764 + SPACING_WIDTH;
22765
22766 ch_advance * max_char_count
22767 });
22768
22769 let is_singleton = self.buffer_snapshot.is_singleton();
22770
22771 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22772 left_padding += if !is_singleton {
22773 ch_width * 4.0
22774 } else if show_runnables || show_breakpoints {
22775 ch_width * 3.0
22776 } else if show_git_gutter && show_line_numbers {
22777 ch_width * 2.0
22778 } else if show_git_gutter || show_line_numbers {
22779 ch_width
22780 } else {
22781 px(0.)
22782 };
22783
22784 let shows_folds = is_singleton && gutter_settings.folds;
22785
22786 let right_padding = if shows_folds && show_line_numbers {
22787 ch_width * 4.0
22788 } else if shows_folds || (!is_singleton && show_line_numbers) {
22789 ch_width * 3.0
22790 } else if show_line_numbers {
22791 ch_width
22792 } else {
22793 px(0.)
22794 };
22795
22796 Some(GutterDimensions {
22797 left_padding,
22798 right_padding,
22799 width: line_gutter_width + left_padding + right_padding,
22800 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22801 git_blame_entries_width,
22802 })
22803 }
22804
22805 pub fn render_crease_toggle(
22806 &self,
22807 buffer_row: MultiBufferRow,
22808 row_contains_cursor: bool,
22809 editor: Entity<Editor>,
22810 window: &mut Window,
22811 cx: &mut App,
22812 ) -> Option<AnyElement> {
22813 let folded = self.is_line_folded(buffer_row);
22814 let mut is_foldable = false;
22815
22816 if let Some(crease) = self
22817 .crease_snapshot
22818 .query_row(buffer_row, &self.buffer_snapshot)
22819 {
22820 is_foldable = true;
22821 match crease {
22822 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22823 if let Some(render_toggle) = render_toggle {
22824 let toggle_callback =
22825 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22826 if folded {
22827 editor.update(cx, |editor, cx| {
22828 editor.fold_at(buffer_row, window, cx)
22829 });
22830 } else {
22831 editor.update(cx, |editor, cx| {
22832 editor.unfold_at(buffer_row, window, cx)
22833 });
22834 }
22835 });
22836 return Some((render_toggle)(
22837 buffer_row,
22838 folded,
22839 toggle_callback,
22840 window,
22841 cx,
22842 ));
22843 }
22844 }
22845 }
22846 }
22847
22848 is_foldable |= self.starts_indent(buffer_row);
22849
22850 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22851 Some(
22852 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22853 .toggle_state(folded)
22854 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22855 if folded {
22856 this.unfold_at(buffer_row, window, cx);
22857 } else {
22858 this.fold_at(buffer_row, window, cx);
22859 }
22860 }))
22861 .into_any_element(),
22862 )
22863 } else {
22864 None
22865 }
22866 }
22867
22868 pub fn render_crease_trailer(
22869 &self,
22870 buffer_row: MultiBufferRow,
22871 window: &mut Window,
22872 cx: &mut App,
22873 ) -> Option<AnyElement> {
22874 let folded = self.is_line_folded(buffer_row);
22875 if let Crease::Inline { render_trailer, .. } = self
22876 .crease_snapshot
22877 .query_row(buffer_row, &self.buffer_snapshot)?
22878 {
22879 let render_trailer = render_trailer.as_ref()?;
22880 Some(render_trailer(buffer_row, folded, window, cx))
22881 } else {
22882 None
22883 }
22884 }
22885}
22886
22887impl Deref for EditorSnapshot {
22888 type Target = DisplaySnapshot;
22889
22890 fn deref(&self) -> &Self::Target {
22891 &self.display_snapshot
22892 }
22893}
22894
22895#[derive(Clone, Debug, PartialEq, Eq)]
22896pub enum EditorEvent {
22897 InputIgnored {
22898 text: Arc<str>,
22899 },
22900 InputHandled {
22901 utf16_range_to_replace: Option<Range<isize>>,
22902 text: Arc<str>,
22903 },
22904 ExcerptsAdded {
22905 buffer: Entity<Buffer>,
22906 predecessor: ExcerptId,
22907 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22908 },
22909 ExcerptsRemoved {
22910 ids: Vec<ExcerptId>,
22911 removed_buffer_ids: Vec<BufferId>,
22912 },
22913 BufferFoldToggled {
22914 ids: Vec<ExcerptId>,
22915 folded: bool,
22916 },
22917 ExcerptsEdited {
22918 ids: Vec<ExcerptId>,
22919 },
22920 ExcerptsExpanded {
22921 ids: Vec<ExcerptId>,
22922 },
22923 BufferEdited,
22924 Edited {
22925 transaction_id: clock::Lamport,
22926 },
22927 Reparsed(BufferId),
22928 Focused,
22929 FocusedIn,
22930 Blurred,
22931 DirtyChanged,
22932 Saved,
22933 TitleChanged,
22934 DiffBaseChanged,
22935 SelectionsChanged {
22936 local: bool,
22937 },
22938 ScrollPositionChanged {
22939 local: bool,
22940 autoscroll: bool,
22941 },
22942 Closed,
22943 TransactionUndone {
22944 transaction_id: clock::Lamport,
22945 },
22946 TransactionBegun {
22947 transaction_id: clock::Lamport,
22948 },
22949 Reloaded,
22950 CursorShapeChanged,
22951 BreadcrumbsChanged,
22952 PushedToNavHistory {
22953 anchor: Anchor,
22954 is_deactivate: bool,
22955 },
22956}
22957
22958impl EventEmitter<EditorEvent> for Editor {}
22959
22960impl Focusable for Editor {
22961 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22962 self.focus_handle.clone()
22963 }
22964}
22965
22966impl Render for Editor {
22967 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22968 let settings = ThemeSettings::get_global(cx);
22969
22970 let mut text_style = match self.mode {
22971 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
22972 color: cx.theme().colors().editor_foreground,
22973 font_family: settings.ui_font.family.clone(),
22974 font_features: settings.ui_font.features.clone(),
22975 font_fallbacks: settings.ui_font.fallbacks.clone(),
22976 font_size: rems(0.875).into(),
22977 font_weight: settings.ui_font.weight,
22978 line_height: relative(settings.buffer_line_height.value()),
22979 ..Default::default()
22980 },
22981 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22982 color: cx.theme().colors().editor_foreground,
22983 font_family: settings.buffer_font.family.clone(),
22984 font_features: settings.buffer_font.features.clone(),
22985 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22986 font_size: settings.buffer_font_size(cx).into(),
22987 font_weight: settings.buffer_font.weight,
22988 line_height: relative(settings.buffer_line_height.value()),
22989 ..Default::default()
22990 },
22991 };
22992 if let Some(text_style_refinement) = &self.text_style_refinement {
22993 text_style.refine(text_style_refinement)
22994 }
22995
22996 let background = match self.mode {
22997 EditorMode::SingleLine => cx.theme().system().transparent,
22998 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22999 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23000 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23001 };
23002
23003 EditorElement::new(
23004 &cx.entity(),
23005 EditorStyle {
23006 background,
23007 border: cx.theme().colors().border,
23008 local_player: cx.theme().players().local(),
23009 text: text_style,
23010 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23011 syntax: cx.theme().syntax().clone(),
23012 status: cx.theme().status().clone(),
23013 inlay_hints_style: make_inlay_hints_style(cx),
23014 edit_prediction_styles: make_suggestion_styles(cx),
23015 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23016 show_underlines: self.diagnostics_enabled(),
23017 },
23018 )
23019 }
23020}
23021
23022impl EntityInputHandler for Editor {
23023 fn text_for_range(
23024 &mut self,
23025 range_utf16: Range<usize>,
23026 adjusted_range: &mut Option<Range<usize>>,
23027 _: &mut Window,
23028 cx: &mut Context<Self>,
23029 ) -> Option<String> {
23030 let snapshot = self.buffer.read(cx).read(cx);
23031 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23032 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23033 if (start.0..end.0) != range_utf16 {
23034 adjusted_range.replace(start.0..end.0);
23035 }
23036 Some(snapshot.text_for_range(start..end).collect())
23037 }
23038
23039 fn selected_text_range(
23040 &mut self,
23041 ignore_disabled_input: bool,
23042 _: &mut Window,
23043 cx: &mut Context<Self>,
23044 ) -> Option<UTF16Selection> {
23045 // Prevent the IME menu from appearing when holding down an alphabetic key
23046 // while input is disabled.
23047 if !ignore_disabled_input && !self.input_enabled {
23048 return None;
23049 }
23050
23051 let selection = self.selections.newest::<OffsetUtf16>(cx);
23052 let range = selection.range();
23053
23054 Some(UTF16Selection {
23055 range: range.start.0..range.end.0,
23056 reversed: selection.reversed,
23057 })
23058 }
23059
23060 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23061 let snapshot = self.buffer.read(cx).read(cx);
23062 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23063 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23064 }
23065
23066 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23067 self.clear_highlights::<InputComposition>(cx);
23068 self.ime_transaction.take();
23069 }
23070
23071 fn replace_text_in_range(
23072 &mut self,
23073 range_utf16: Option<Range<usize>>,
23074 text: &str,
23075 window: &mut Window,
23076 cx: &mut Context<Self>,
23077 ) {
23078 if !self.input_enabled {
23079 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23080 return;
23081 }
23082
23083 self.transact(window, cx, |this, window, cx| {
23084 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23085 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23086 Some(this.selection_replacement_ranges(range_utf16, cx))
23087 } else {
23088 this.marked_text_ranges(cx)
23089 };
23090
23091 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23092 let newest_selection_id = this.selections.newest_anchor().id;
23093 this.selections
23094 .all::<OffsetUtf16>(cx)
23095 .iter()
23096 .zip(ranges_to_replace.iter())
23097 .find_map(|(selection, range)| {
23098 if selection.id == newest_selection_id {
23099 Some(
23100 (range.start.0 as isize - selection.head().0 as isize)
23101 ..(range.end.0 as isize - selection.head().0 as isize),
23102 )
23103 } else {
23104 None
23105 }
23106 })
23107 });
23108
23109 cx.emit(EditorEvent::InputHandled {
23110 utf16_range_to_replace: range_to_replace,
23111 text: text.into(),
23112 });
23113
23114 if let Some(new_selected_ranges) = new_selected_ranges {
23115 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23116 selections.select_ranges(new_selected_ranges)
23117 });
23118 this.backspace(&Default::default(), window, cx);
23119 }
23120
23121 this.handle_input(text, window, cx);
23122 });
23123
23124 if let Some(transaction) = self.ime_transaction {
23125 self.buffer.update(cx, |buffer, cx| {
23126 buffer.group_until_transaction(transaction, cx);
23127 });
23128 }
23129
23130 self.unmark_text(window, cx);
23131 }
23132
23133 fn replace_and_mark_text_in_range(
23134 &mut self,
23135 range_utf16: Option<Range<usize>>,
23136 text: &str,
23137 new_selected_range_utf16: Option<Range<usize>>,
23138 window: &mut Window,
23139 cx: &mut Context<Self>,
23140 ) {
23141 if !self.input_enabled {
23142 return;
23143 }
23144
23145 let transaction = self.transact(window, cx, |this, window, cx| {
23146 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23147 let snapshot = this.buffer.read(cx).read(cx);
23148 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23149 for marked_range in &mut marked_ranges {
23150 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23151 marked_range.start.0 += relative_range_utf16.start;
23152 marked_range.start =
23153 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23154 marked_range.end =
23155 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23156 }
23157 }
23158 Some(marked_ranges)
23159 } else if let Some(range_utf16) = range_utf16 {
23160 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23161 Some(this.selection_replacement_ranges(range_utf16, cx))
23162 } else {
23163 None
23164 };
23165
23166 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23167 let newest_selection_id = this.selections.newest_anchor().id;
23168 this.selections
23169 .all::<OffsetUtf16>(cx)
23170 .iter()
23171 .zip(ranges_to_replace.iter())
23172 .find_map(|(selection, range)| {
23173 if selection.id == newest_selection_id {
23174 Some(
23175 (range.start.0 as isize - selection.head().0 as isize)
23176 ..(range.end.0 as isize - selection.head().0 as isize),
23177 )
23178 } else {
23179 None
23180 }
23181 })
23182 });
23183
23184 cx.emit(EditorEvent::InputHandled {
23185 utf16_range_to_replace: range_to_replace,
23186 text: text.into(),
23187 });
23188
23189 if let Some(ranges) = ranges_to_replace {
23190 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23191 s.select_ranges(ranges)
23192 });
23193 }
23194
23195 let marked_ranges = {
23196 let snapshot = this.buffer.read(cx).read(cx);
23197 this.selections
23198 .disjoint_anchors()
23199 .iter()
23200 .map(|selection| {
23201 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23202 })
23203 .collect::<Vec<_>>()
23204 };
23205
23206 if text.is_empty() {
23207 this.unmark_text(window, cx);
23208 } else {
23209 this.highlight_text::<InputComposition>(
23210 marked_ranges.clone(),
23211 HighlightStyle {
23212 underline: Some(UnderlineStyle {
23213 thickness: px(1.),
23214 color: None,
23215 wavy: false,
23216 }),
23217 ..Default::default()
23218 },
23219 cx,
23220 );
23221 }
23222
23223 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23224 let use_autoclose = this.use_autoclose;
23225 let use_auto_surround = this.use_auto_surround;
23226 this.set_use_autoclose(false);
23227 this.set_use_auto_surround(false);
23228 this.handle_input(text, window, cx);
23229 this.set_use_autoclose(use_autoclose);
23230 this.set_use_auto_surround(use_auto_surround);
23231
23232 if let Some(new_selected_range) = new_selected_range_utf16 {
23233 let snapshot = this.buffer.read(cx).read(cx);
23234 let new_selected_ranges = marked_ranges
23235 .into_iter()
23236 .map(|marked_range| {
23237 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23238 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23239 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23240 snapshot.clip_offset_utf16(new_start, Bias::Left)
23241 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23242 })
23243 .collect::<Vec<_>>();
23244
23245 drop(snapshot);
23246 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23247 selections.select_ranges(new_selected_ranges)
23248 });
23249 }
23250 });
23251
23252 self.ime_transaction = self.ime_transaction.or(transaction);
23253 if let Some(transaction) = self.ime_transaction {
23254 self.buffer.update(cx, |buffer, cx| {
23255 buffer.group_until_transaction(transaction, cx);
23256 });
23257 }
23258
23259 if self.text_highlights::<InputComposition>(cx).is_none() {
23260 self.ime_transaction.take();
23261 }
23262 }
23263
23264 fn bounds_for_range(
23265 &mut self,
23266 range_utf16: Range<usize>,
23267 element_bounds: gpui::Bounds<Pixels>,
23268 window: &mut Window,
23269 cx: &mut Context<Self>,
23270 ) -> Option<gpui::Bounds<Pixels>> {
23271 let text_layout_details = self.text_layout_details(window);
23272 let CharacterDimensions {
23273 em_width,
23274 em_advance,
23275 line_height,
23276 } = self.character_dimensions(window);
23277
23278 let snapshot = self.snapshot(window, cx);
23279 let scroll_position = snapshot.scroll_position();
23280 let scroll_left = scroll_position.x * em_advance;
23281
23282 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23283 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23284 + self.gutter_dimensions.full_width();
23285 let y = line_height * (start.row().as_f32() - scroll_position.y);
23286
23287 Some(Bounds {
23288 origin: element_bounds.origin + point(x, y),
23289 size: size(em_width, line_height),
23290 })
23291 }
23292
23293 fn character_index_for_point(
23294 &mut self,
23295 point: gpui::Point<Pixels>,
23296 _window: &mut Window,
23297 _cx: &mut Context<Self>,
23298 ) -> Option<usize> {
23299 let position_map = self.last_position_map.as_ref()?;
23300 if !position_map.text_hitbox.contains(&point) {
23301 return None;
23302 }
23303 let display_point = position_map.point_for_position(point).previous_valid;
23304 let anchor = position_map
23305 .snapshot
23306 .display_point_to_anchor(display_point, Bias::Left);
23307 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23308 Some(utf16_offset.0)
23309 }
23310}
23311
23312trait SelectionExt {
23313 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23314 fn spanned_rows(
23315 &self,
23316 include_end_if_at_line_start: bool,
23317 map: &DisplaySnapshot,
23318 ) -> Range<MultiBufferRow>;
23319}
23320
23321impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23322 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23323 let start = self
23324 .start
23325 .to_point(&map.buffer_snapshot)
23326 .to_display_point(map);
23327 let end = self
23328 .end
23329 .to_point(&map.buffer_snapshot)
23330 .to_display_point(map);
23331 if self.reversed {
23332 end..start
23333 } else {
23334 start..end
23335 }
23336 }
23337
23338 fn spanned_rows(
23339 &self,
23340 include_end_if_at_line_start: bool,
23341 map: &DisplaySnapshot,
23342 ) -> Range<MultiBufferRow> {
23343 let start = self.start.to_point(&map.buffer_snapshot);
23344 let mut end = self.end.to_point(&map.buffer_snapshot);
23345 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23346 end.row -= 1;
23347 }
23348
23349 let buffer_start = map.prev_line_boundary(start).0;
23350 let buffer_end = map.next_line_boundary(end).0;
23351 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23352 }
23353}
23354
23355impl<T: InvalidationRegion> InvalidationStack<T> {
23356 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23357 where
23358 S: Clone + ToOffset,
23359 {
23360 while let Some(region) = self.last() {
23361 let all_selections_inside_invalidation_ranges =
23362 if selections.len() == region.ranges().len() {
23363 selections
23364 .iter()
23365 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23366 .all(|(selection, invalidation_range)| {
23367 let head = selection.head().to_offset(buffer);
23368 invalidation_range.start <= head && invalidation_range.end >= head
23369 })
23370 } else {
23371 false
23372 };
23373
23374 if all_selections_inside_invalidation_ranges {
23375 break;
23376 } else {
23377 self.pop();
23378 }
23379 }
23380 }
23381}
23382
23383impl<T> Default for InvalidationStack<T> {
23384 fn default() -> Self {
23385 Self(Default::default())
23386 }
23387}
23388
23389impl<T> Deref for InvalidationStack<T> {
23390 type Target = Vec<T>;
23391
23392 fn deref(&self) -> &Self::Target {
23393 &self.0
23394 }
23395}
23396
23397impl<T> DerefMut for InvalidationStack<T> {
23398 fn deref_mut(&mut self) -> &mut Self::Target {
23399 &mut self.0
23400 }
23401}
23402
23403impl InvalidationRegion for SnippetState {
23404 fn ranges(&self) -> &[Range<Anchor>] {
23405 &self.ranges[self.active_index]
23406 }
23407}
23408
23409fn edit_prediction_edit_text(
23410 current_snapshot: &BufferSnapshot,
23411 edits: &[(Range<Anchor>, String)],
23412 edit_preview: &EditPreview,
23413 include_deletions: bool,
23414 cx: &App,
23415) -> HighlightedText {
23416 let edits = edits
23417 .iter()
23418 .map(|(anchor, text)| {
23419 (
23420 anchor.start.text_anchor..anchor.end.text_anchor,
23421 text.clone(),
23422 )
23423 })
23424 .collect::<Vec<_>>();
23425
23426 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23427}
23428
23429fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23430 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23431 // Just show the raw edit text with basic styling
23432 let mut text = String::new();
23433 let mut highlights = Vec::new();
23434
23435 let insertion_highlight_style = HighlightStyle {
23436 color: Some(cx.theme().colors().text),
23437 ..Default::default()
23438 };
23439
23440 for (_, edit_text) in edits {
23441 let start_offset = text.len();
23442 text.push_str(edit_text);
23443 let end_offset = text.len();
23444
23445 if start_offset < end_offset {
23446 highlights.push((start_offset..end_offset, insertion_highlight_style));
23447 }
23448 }
23449
23450 HighlightedText {
23451 text: text.into(),
23452 highlights,
23453 }
23454}
23455
23456pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23457 match severity {
23458 lsp::DiagnosticSeverity::ERROR => colors.error,
23459 lsp::DiagnosticSeverity::WARNING => colors.warning,
23460 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23461 lsp::DiagnosticSeverity::HINT => colors.info,
23462 _ => colors.ignored,
23463 }
23464}
23465
23466pub fn styled_runs_for_code_label<'a>(
23467 label: &'a CodeLabel,
23468 syntax_theme: &'a theme::SyntaxTheme,
23469) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23470 let fade_out = HighlightStyle {
23471 fade_out: Some(0.35),
23472 ..Default::default()
23473 };
23474
23475 let mut prev_end = label.filter_range.end;
23476 label
23477 .runs
23478 .iter()
23479 .enumerate()
23480 .flat_map(move |(ix, (range, highlight_id))| {
23481 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23482 style
23483 } else {
23484 return Default::default();
23485 };
23486 let mut muted_style = style;
23487 muted_style.highlight(fade_out);
23488
23489 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23490 if range.start >= label.filter_range.end {
23491 if range.start > prev_end {
23492 runs.push((prev_end..range.start, fade_out));
23493 }
23494 runs.push((range.clone(), muted_style));
23495 } else if range.end <= label.filter_range.end {
23496 runs.push((range.clone(), style));
23497 } else {
23498 runs.push((range.start..label.filter_range.end, style));
23499 runs.push((label.filter_range.end..range.end, muted_style));
23500 }
23501 prev_end = cmp::max(prev_end, range.end);
23502
23503 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23504 runs.push((prev_end..label.text.len(), fade_out));
23505 }
23506
23507 runs
23508 })
23509}
23510
23511pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23512 let mut prev_index = 0;
23513 let mut prev_codepoint: Option<char> = None;
23514 text.char_indices()
23515 .chain([(text.len(), '\0')])
23516 .filter_map(move |(index, codepoint)| {
23517 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23518 let is_boundary = index == text.len()
23519 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23520 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23521 if is_boundary {
23522 let chunk = &text[prev_index..index];
23523 prev_index = index;
23524 Some(chunk)
23525 } else {
23526 None
23527 }
23528 })
23529}
23530
23531pub trait RangeToAnchorExt: Sized {
23532 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23533
23534 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23535 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23536 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23537 }
23538}
23539
23540impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23541 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23542 let start_offset = self.start.to_offset(snapshot);
23543 let end_offset = self.end.to_offset(snapshot);
23544 if start_offset == end_offset {
23545 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23546 } else {
23547 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23548 }
23549 }
23550}
23551
23552pub trait RowExt {
23553 fn as_f32(&self) -> f32;
23554
23555 fn next_row(&self) -> Self;
23556
23557 fn previous_row(&self) -> Self;
23558
23559 fn minus(&self, other: Self) -> u32;
23560}
23561
23562impl RowExt for DisplayRow {
23563 fn as_f32(&self) -> f32 {
23564 self.0 as f32
23565 }
23566
23567 fn next_row(&self) -> Self {
23568 Self(self.0 + 1)
23569 }
23570
23571 fn previous_row(&self) -> Self {
23572 Self(self.0.saturating_sub(1))
23573 }
23574
23575 fn minus(&self, other: Self) -> u32 {
23576 self.0 - other.0
23577 }
23578}
23579
23580impl RowExt for MultiBufferRow {
23581 fn as_f32(&self) -> f32 {
23582 self.0 as f32
23583 }
23584
23585 fn next_row(&self) -> Self {
23586 Self(self.0 + 1)
23587 }
23588
23589 fn previous_row(&self) -> Self {
23590 Self(self.0.saturating_sub(1))
23591 }
23592
23593 fn minus(&self, other: Self) -> u32 {
23594 self.0 - other.0
23595 }
23596}
23597
23598trait RowRangeExt {
23599 type Row;
23600
23601 fn len(&self) -> usize;
23602
23603 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23604}
23605
23606impl RowRangeExt for Range<MultiBufferRow> {
23607 type Row = MultiBufferRow;
23608
23609 fn len(&self) -> usize {
23610 (self.end.0 - self.start.0) as usize
23611 }
23612
23613 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23614 (self.start.0..self.end.0).map(MultiBufferRow)
23615 }
23616}
23617
23618impl RowRangeExt for Range<DisplayRow> {
23619 type Row = DisplayRow;
23620
23621 fn len(&self) -> usize {
23622 (self.end.0 - self.start.0) as usize
23623 }
23624
23625 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23626 (self.start.0..self.end.0).map(DisplayRow)
23627 }
23628}
23629
23630/// If select range has more than one line, we
23631/// just point the cursor to range.start.
23632fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23633 if range.start.row == range.end.row {
23634 range
23635 } else {
23636 range.start..range.start
23637 }
23638}
23639pub struct KillRing(ClipboardItem);
23640impl Global for KillRing {}
23641
23642const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23643
23644enum BreakpointPromptEditAction {
23645 Log,
23646 Condition,
23647 HitCondition,
23648}
23649
23650struct BreakpointPromptEditor {
23651 pub(crate) prompt: Entity<Editor>,
23652 editor: WeakEntity<Editor>,
23653 breakpoint_anchor: Anchor,
23654 breakpoint: Breakpoint,
23655 edit_action: BreakpointPromptEditAction,
23656 block_ids: HashSet<CustomBlockId>,
23657 editor_margins: Arc<Mutex<EditorMargins>>,
23658 _subscriptions: Vec<Subscription>,
23659}
23660
23661impl BreakpointPromptEditor {
23662 const MAX_LINES: u8 = 4;
23663
23664 fn new(
23665 editor: WeakEntity<Editor>,
23666 breakpoint_anchor: Anchor,
23667 breakpoint: Breakpoint,
23668 edit_action: BreakpointPromptEditAction,
23669 window: &mut Window,
23670 cx: &mut Context<Self>,
23671 ) -> Self {
23672 let base_text = match edit_action {
23673 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23674 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23675 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23676 }
23677 .map(|msg| msg.to_string())
23678 .unwrap_or_default();
23679
23680 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23681 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23682
23683 let prompt = cx.new(|cx| {
23684 let mut prompt = Editor::new(
23685 EditorMode::AutoHeight {
23686 min_lines: 1,
23687 max_lines: Some(Self::MAX_LINES as usize),
23688 },
23689 buffer,
23690 None,
23691 window,
23692 cx,
23693 );
23694 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23695 prompt.set_show_cursor_when_unfocused(false, cx);
23696 prompt.set_placeholder_text(
23697 match edit_action {
23698 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23699 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23700 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23701 },
23702 cx,
23703 );
23704
23705 prompt
23706 });
23707
23708 Self {
23709 prompt,
23710 editor,
23711 breakpoint_anchor,
23712 breakpoint,
23713 edit_action,
23714 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23715 block_ids: Default::default(),
23716 _subscriptions: vec![],
23717 }
23718 }
23719
23720 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23721 self.block_ids.extend(block_ids)
23722 }
23723
23724 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23725 if let Some(editor) = self.editor.upgrade() {
23726 let message = self
23727 .prompt
23728 .read(cx)
23729 .buffer
23730 .read(cx)
23731 .as_singleton()
23732 .expect("A multi buffer in breakpoint prompt isn't possible")
23733 .read(cx)
23734 .as_rope()
23735 .to_string();
23736
23737 editor.update(cx, |editor, cx| {
23738 editor.edit_breakpoint_at_anchor(
23739 self.breakpoint_anchor,
23740 self.breakpoint.clone(),
23741 match self.edit_action {
23742 BreakpointPromptEditAction::Log => {
23743 BreakpointEditAction::EditLogMessage(message.into())
23744 }
23745 BreakpointPromptEditAction::Condition => {
23746 BreakpointEditAction::EditCondition(message.into())
23747 }
23748 BreakpointPromptEditAction::HitCondition => {
23749 BreakpointEditAction::EditHitCondition(message.into())
23750 }
23751 },
23752 cx,
23753 );
23754
23755 editor.remove_blocks(self.block_ids.clone(), None, cx);
23756 cx.focus_self(window);
23757 });
23758 }
23759 }
23760
23761 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23762 self.editor
23763 .update(cx, |editor, cx| {
23764 editor.remove_blocks(self.block_ids.clone(), None, cx);
23765 window.focus(&editor.focus_handle);
23766 })
23767 .log_err();
23768 }
23769
23770 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23771 let settings = ThemeSettings::get_global(cx);
23772 let text_style = TextStyle {
23773 color: if self.prompt.read(cx).read_only(cx) {
23774 cx.theme().colors().text_disabled
23775 } else {
23776 cx.theme().colors().text
23777 },
23778 font_family: settings.buffer_font.family.clone(),
23779 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23780 font_size: settings.buffer_font_size(cx).into(),
23781 font_weight: settings.buffer_font.weight,
23782 line_height: relative(settings.buffer_line_height.value()),
23783 ..Default::default()
23784 };
23785 EditorElement::new(
23786 &self.prompt,
23787 EditorStyle {
23788 background: cx.theme().colors().editor_background,
23789 local_player: cx.theme().players().local(),
23790 text: text_style,
23791 ..Default::default()
23792 },
23793 )
23794 }
23795}
23796
23797impl Render for BreakpointPromptEditor {
23798 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23799 let editor_margins = *self.editor_margins.lock();
23800 let gutter_dimensions = editor_margins.gutter;
23801 h_flex()
23802 .key_context("Editor")
23803 .bg(cx.theme().colors().editor_background)
23804 .border_y_1()
23805 .border_color(cx.theme().status().info_border)
23806 .size_full()
23807 .py(window.line_height() / 2.5)
23808 .on_action(cx.listener(Self::confirm))
23809 .on_action(cx.listener(Self::cancel))
23810 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23811 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23812 }
23813}
23814
23815impl Focusable for BreakpointPromptEditor {
23816 fn focus_handle(&self, cx: &App) -> FocusHandle {
23817 self.prompt.focus_handle(cx)
23818 }
23819}
23820
23821fn all_edits_insertions_or_deletions(
23822 edits: &Vec<(Range<Anchor>, String)>,
23823 snapshot: &MultiBufferSnapshot,
23824) -> bool {
23825 let mut all_insertions = true;
23826 let mut all_deletions = true;
23827
23828 for (range, new_text) in edits.iter() {
23829 let range_is_empty = range.to_offset(snapshot).is_empty();
23830 let text_is_empty = new_text.is_empty();
23831
23832 if range_is_empty != text_is_empty {
23833 if range_is_empty {
23834 all_deletions = false;
23835 } else {
23836 all_insertions = false;
23837 }
23838 } else {
23839 return false;
23840 }
23841
23842 if !all_insertions && !all_deletions {
23843 return false;
23844 }
23845 }
23846 all_insertions || all_deletions
23847}
23848
23849struct MissingEditPredictionKeybindingTooltip;
23850
23851impl Render for MissingEditPredictionKeybindingTooltip {
23852 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23853 ui::tooltip_container(window, cx, |container, _, cx| {
23854 container
23855 .flex_shrink_0()
23856 .max_w_80()
23857 .min_h(rems_from_px(124.))
23858 .justify_between()
23859 .child(
23860 v_flex()
23861 .flex_1()
23862 .text_ui_sm(cx)
23863 .child(Label::new("Conflict with Accept Keybinding"))
23864 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23865 )
23866 .child(
23867 h_flex()
23868 .pb_1()
23869 .gap_1()
23870 .items_end()
23871 .w_full()
23872 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23873 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23874 }))
23875 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23876 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23877 })),
23878 )
23879 })
23880 }
23881}
23882
23883#[derive(Debug, Clone, Copy, PartialEq)]
23884pub struct LineHighlight {
23885 pub background: Background,
23886 pub border: Option<gpui::Hsla>,
23887 pub include_gutter: bool,
23888 pub type_id: Option<TypeId>,
23889}
23890
23891struct LineManipulationResult {
23892 pub new_text: String,
23893 pub line_count_before: usize,
23894 pub line_count_after: usize,
23895}
23896
23897fn render_diff_hunk_controls(
23898 row: u32,
23899 status: &DiffHunkStatus,
23900 hunk_range: Range<Anchor>,
23901 is_created_file: bool,
23902 line_height: Pixels,
23903 editor: &Entity<Editor>,
23904 _window: &mut Window,
23905 cx: &mut App,
23906) -> AnyElement {
23907 h_flex()
23908 .h(line_height)
23909 .mr_1()
23910 .gap_1()
23911 .px_0p5()
23912 .pb_1()
23913 .border_x_1()
23914 .border_b_1()
23915 .border_color(cx.theme().colors().border_variant)
23916 .rounded_b_lg()
23917 .bg(cx.theme().colors().editor_background)
23918 .gap_1()
23919 .block_mouse_except_scroll()
23920 .shadow_md()
23921 .child(if status.has_secondary_hunk() {
23922 Button::new(("stage", row as u64), "Stage")
23923 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23924 .tooltip({
23925 let focus_handle = editor.focus_handle(cx);
23926 move |window, cx| {
23927 Tooltip::for_action_in(
23928 "Stage Hunk",
23929 &::git::ToggleStaged,
23930 &focus_handle,
23931 window,
23932 cx,
23933 )
23934 }
23935 })
23936 .on_click({
23937 let editor = editor.clone();
23938 move |_event, _window, cx| {
23939 editor.update(cx, |editor, cx| {
23940 editor.stage_or_unstage_diff_hunks(
23941 true,
23942 vec![hunk_range.start..hunk_range.start],
23943 cx,
23944 );
23945 });
23946 }
23947 })
23948 } else {
23949 Button::new(("unstage", row as u64), "Unstage")
23950 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23951 .tooltip({
23952 let focus_handle = editor.focus_handle(cx);
23953 move |window, cx| {
23954 Tooltip::for_action_in(
23955 "Unstage Hunk",
23956 &::git::ToggleStaged,
23957 &focus_handle,
23958 window,
23959 cx,
23960 )
23961 }
23962 })
23963 .on_click({
23964 let editor = editor.clone();
23965 move |_event, _window, cx| {
23966 editor.update(cx, |editor, cx| {
23967 editor.stage_or_unstage_diff_hunks(
23968 false,
23969 vec![hunk_range.start..hunk_range.start],
23970 cx,
23971 );
23972 });
23973 }
23974 })
23975 })
23976 .child(
23977 Button::new(("restore", row as u64), "Restore")
23978 .tooltip({
23979 let focus_handle = editor.focus_handle(cx);
23980 move |window, cx| {
23981 Tooltip::for_action_in(
23982 "Restore Hunk",
23983 &::git::Restore,
23984 &focus_handle,
23985 window,
23986 cx,
23987 )
23988 }
23989 })
23990 .on_click({
23991 let editor = editor.clone();
23992 move |_event, window, cx| {
23993 editor.update(cx, |editor, cx| {
23994 let snapshot = editor.snapshot(window, cx);
23995 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23996 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23997 });
23998 }
23999 })
24000 .disabled(is_created_file),
24001 )
24002 .when(
24003 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24004 |el| {
24005 el.child(
24006 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24007 .shape(IconButtonShape::Square)
24008 .icon_size(IconSize::Small)
24009 // .disabled(!has_multiple_hunks)
24010 .tooltip({
24011 let focus_handle = editor.focus_handle(cx);
24012 move |window, cx| {
24013 Tooltip::for_action_in(
24014 "Next Hunk",
24015 &GoToHunk,
24016 &focus_handle,
24017 window,
24018 cx,
24019 )
24020 }
24021 })
24022 .on_click({
24023 let editor = editor.clone();
24024 move |_event, window, cx| {
24025 editor.update(cx, |editor, cx| {
24026 let snapshot = editor.snapshot(window, cx);
24027 let position =
24028 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24029 editor.go_to_hunk_before_or_after_position(
24030 &snapshot,
24031 position,
24032 Direction::Next,
24033 window,
24034 cx,
24035 );
24036 editor.expand_selected_diff_hunks(cx);
24037 });
24038 }
24039 }),
24040 )
24041 .child(
24042 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24043 .shape(IconButtonShape::Square)
24044 .icon_size(IconSize::Small)
24045 // .disabled(!has_multiple_hunks)
24046 .tooltip({
24047 let focus_handle = editor.focus_handle(cx);
24048 move |window, cx| {
24049 Tooltip::for_action_in(
24050 "Previous Hunk",
24051 &GoToPreviousHunk,
24052 &focus_handle,
24053 window,
24054 cx,
24055 )
24056 }
24057 })
24058 .on_click({
24059 let editor = editor.clone();
24060 move |_event, window, cx| {
24061 editor.update(cx, |editor, cx| {
24062 let snapshot = editor.snapshot(window, cx);
24063 let point =
24064 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24065 editor.go_to_hunk_before_or_after_position(
24066 &snapshot,
24067 point,
24068 Direction::Prev,
24069 window,
24070 cx,
24071 );
24072 editor.expand_selected_diff_hunks(cx);
24073 });
24074 }
24075 }),
24076 )
24077 },
24078 )
24079 .into_any_element()
24080}