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 ZetaTosClicked,
257 Closed,
258}
259
260impl ReportEditorEvent {
261 pub fn event_type(&self) -> &'static str {
262 match self {
263 Self::Saved { .. } => "Editor Saved",
264 Self::EditorOpened => "Editor Opened",
265 Self::ZetaTosClicked => "Edit Prediction Provider ToS Clicked",
266 Self::Closed => "Editor Closed",
267 }
268 }
269}
270
271struct InlineValueCache {
272 enabled: bool,
273 inlays: Vec<InlayId>,
274 refresh_task: Task<Option<()>>,
275}
276
277impl InlineValueCache {
278 fn new(enabled: bool) -> Self {
279 Self {
280 enabled,
281 inlays: Vec::new(),
282 refresh_task: Task::ready(None),
283 }
284 }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
288pub enum InlayId {
289 EditPrediction(usize),
290 DebuggerValue(usize),
291 // LSP
292 Hint(usize),
293 Color(usize),
294}
295
296impl InlayId {
297 fn id(&self) -> usize {
298 match self {
299 Self::EditPrediction(id) => *id,
300 Self::DebuggerValue(id) => *id,
301 Self::Hint(id) => *id,
302 Self::Color(id) => *id,
303 }
304 }
305}
306
307pub enum ActiveDebugLine {}
308pub enum DebugStackFrameLine {}
309enum DocumentHighlightRead {}
310enum DocumentHighlightWrite {}
311enum InputComposition {}
312pub enum PendingInput {}
313enum SelectedTextHighlight {}
314
315pub enum ConflictsOuter {}
316pub enum ConflictsOurs {}
317pub enum ConflictsTheirs {}
318pub enum ConflictsOursMarker {}
319pub enum ConflictsTheirsMarker {}
320
321#[derive(Debug, Copy, Clone, PartialEq, Eq)]
322pub enum Navigated {
323 Yes,
324 No,
325}
326
327impl Navigated {
328 pub fn from_bool(yes: bool) -> Navigated {
329 if yes { Navigated::Yes } else { Navigated::No }
330 }
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
334enum DisplayDiffHunk {
335 Folded {
336 display_row: DisplayRow,
337 },
338 Unfolded {
339 is_created_file: bool,
340 diff_base_byte_range: Range<usize>,
341 display_row_range: Range<DisplayRow>,
342 multi_buffer_range: Range<Anchor>,
343 status: DiffHunkStatus,
344 },
345}
346
347pub enum HideMouseCursorOrigin {
348 TypingAction,
349 MovementAction,
350}
351
352pub fn init_settings(cx: &mut App) {
353 EditorSettings::register(cx);
354}
355
356pub fn init(cx: &mut App) {
357 init_settings(cx);
358
359 cx.set_global(GlobalBlameRenderer(Arc::new(())));
360
361 workspace::register_project_item::<Editor>(cx);
362 workspace::FollowableViewRegistry::register::<Editor>(cx);
363 workspace::register_serializable_item::<Editor>(cx);
364
365 cx.observe_new(
366 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
367 workspace.register_action(Editor::new_file);
368 workspace.register_action(Editor::new_file_vertical);
369 workspace.register_action(Editor::new_file_horizontal);
370 workspace.register_action(Editor::cancel_language_server_work);
371 workspace.register_action(Editor::toggle_focus);
372 },
373 )
374 .detach();
375
376 cx.on_action(move |_: &workspace::NewFile, cx| {
377 let app_state = workspace::AppState::global(cx);
378 if let Some(app_state) = app_state.upgrade() {
379 workspace::open_new(
380 Default::default(),
381 app_state,
382 cx,
383 |workspace, window, cx| {
384 Editor::new_file(workspace, &Default::default(), window, cx)
385 },
386 )
387 .detach();
388 }
389 });
390 cx.on_action(move |_: &workspace::NewWindow, cx| {
391 let app_state = workspace::AppState::global(cx);
392 if let Some(app_state) = app_state.upgrade() {
393 workspace::open_new(
394 Default::default(),
395 app_state,
396 cx,
397 |workspace, window, cx| {
398 cx.activate(true);
399 Editor::new_file(workspace, &Default::default(), window, cx)
400 },
401 )
402 .detach();
403 }
404 });
405}
406
407pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
408 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
409}
410
411pub trait DiagnosticRenderer {
412 fn render_group(
413 &self,
414 diagnostic_group: Vec<DiagnosticEntry<Point>>,
415 buffer_id: BufferId,
416 snapshot: EditorSnapshot,
417 editor: WeakEntity<Editor>,
418 cx: &mut App,
419 ) -> Vec<BlockProperties<Anchor>>;
420
421 fn render_hover(
422 &self,
423 diagnostic_group: Vec<DiagnosticEntry<Point>>,
424 range: Range<Point>,
425 buffer_id: BufferId,
426 cx: &mut App,
427 ) -> Option<Entity<markdown::Markdown>>;
428
429 fn open_link(
430 &self,
431 editor: &mut Editor,
432 link: SharedString,
433 window: &mut Window,
434 cx: &mut Context<Editor>,
435 );
436}
437
438pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
439
440impl GlobalDiagnosticRenderer {
441 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
442 cx.try_global::<Self>().map(|g| g.0.clone())
443 }
444}
445
446impl gpui::Global for GlobalDiagnosticRenderer {}
447pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
448 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
449}
450
451pub struct SearchWithinRange;
452
453trait InvalidationRegion {
454 fn ranges(&self) -> &[Range<Anchor>];
455}
456
457#[derive(Clone, Debug, PartialEq)]
458pub enum SelectPhase {
459 Begin {
460 position: DisplayPoint,
461 add: bool,
462 click_count: usize,
463 },
464 BeginColumnar {
465 position: DisplayPoint,
466 reset: bool,
467 mode: ColumnarMode,
468 goal_column: u32,
469 },
470 Extend {
471 position: DisplayPoint,
472 click_count: usize,
473 },
474 Update {
475 position: DisplayPoint,
476 goal_column: u32,
477 scroll_delta: gpui::Point<f32>,
478 },
479 End,
480}
481
482#[derive(Clone, Debug, PartialEq)]
483pub enum ColumnarMode {
484 FromMouse,
485 FromSelection,
486}
487
488#[derive(Clone, Debug)]
489pub enum SelectMode {
490 Character,
491 Word(Range<Anchor>),
492 Line(Range<Anchor>),
493 All,
494}
495
496#[derive(Clone, PartialEq, Eq, Debug)]
497pub enum EditorMode {
498 SingleLine,
499 AutoHeight {
500 min_lines: usize,
501 max_lines: Option<usize>,
502 },
503 Full {
504 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
505 scale_ui_elements_with_buffer_font_size: bool,
506 /// When set to `true`, the editor will render a background for the active line.
507 show_active_line_background: bool,
508 /// When set to `true`, the editor's height will be determined by its content.
509 sized_by_content: bool,
510 },
511 Minimap {
512 parent: WeakEntity<Editor>,
513 },
514}
515
516impl EditorMode {
517 pub fn full() -> Self {
518 Self::Full {
519 scale_ui_elements_with_buffer_font_size: true,
520 show_active_line_background: true,
521 sized_by_content: false,
522 }
523 }
524
525 #[inline]
526 pub fn is_full(&self) -> bool {
527 matches!(self, Self::Full { .. })
528 }
529
530 #[inline]
531 pub fn is_single_line(&self) -> bool {
532 matches!(self, Self::SingleLine { .. })
533 }
534
535 #[inline]
536 fn is_minimap(&self) -> bool {
537 matches!(self, Self::Minimap { .. })
538 }
539}
540
541#[derive(Copy, Clone, Debug)]
542pub enum SoftWrap {
543 /// Prefer not to wrap at all.
544 ///
545 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
546 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
547 GitDiff,
548 /// Prefer a single line generally, unless an overly long line is encountered.
549 None,
550 /// Soft wrap lines that exceed the editor width.
551 EditorWidth,
552 /// Soft wrap lines at the preferred line length.
553 Column(u32),
554 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
555 Bounded(u32),
556}
557
558#[derive(Clone)]
559pub struct EditorStyle {
560 pub background: Hsla,
561 pub border: Hsla,
562 pub local_player: PlayerColor,
563 pub text: TextStyle,
564 pub scrollbar_width: Pixels,
565 pub syntax: Arc<SyntaxTheme>,
566 pub status: StatusColors,
567 pub inlay_hints_style: HighlightStyle,
568 pub edit_prediction_styles: EditPredictionStyles,
569 pub unnecessary_code_fade: f32,
570 pub show_underlines: bool,
571}
572
573impl Default for EditorStyle {
574 fn default() -> Self {
575 Self {
576 background: Hsla::default(),
577 border: Hsla::default(),
578 local_player: PlayerColor::default(),
579 text: TextStyle::default(),
580 scrollbar_width: Pixels::default(),
581 syntax: Default::default(),
582 // HACK: Status colors don't have a real default.
583 // We should look into removing the status colors from the editor
584 // style and retrieve them directly from the theme.
585 status: StatusColors::dark(),
586 inlay_hints_style: HighlightStyle::default(),
587 edit_prediction_styles: EditPredictionStyles {
588 insertion: HighlightStyle::default(),
589 whitespace: HighlightStyle::default(),
590 },
591 unnecessary_code_fade: Default::default(),
592 show_underlines: true,
593 }
594 }
595}
596
597pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
598 let show_background = language_settings::language_settings(None, None, cx)
599 .inlay_hints
600 .show_background;
601
602 HighlightStyle {
603 color: Some(cx.theme().status().hint),
604 background_color: show_background.then(|| cx.theme().status().hint_background),
605 ..HighlightStyle::default()
606 }
607}
608
609pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
610 EditPredictionStyles {
611 insertion: HighlightStyle {
612 color: Some(cx.theme().status().predictive),
613 ..HighlightStyle::default()
614 },
615 whitespace: HighlightStyle {
616 background_color: Some(cx.theme().status().created_background),
617 ..HighlightStyle::default()
618 },
619 }
620}
621
622type CompletionId = usize;
623
624pub(crate) enum EditDisplayMode {
625 TabAccept,
626 DiffPopover,
627 Inline,
628}
629
630enum EditPrediction {
631 Edit {
632 edits: Vec<(Range<Anchor>, String)>,
633 edit_preview: Option<EditPreview>,
634 display_mode: EditDisplayMode,
635 snapshot: BufferSnapshot,
636 },
637 Move {
638 target: Anchor,
639 snapshot: BufferSnapshot,
640 },
641}
642
643struct EditPredictionState {
644 inlay_ids: Vec<InlayId>,
645 completion: EditPrediction,
646 completion_id: Option<SharedString>,
647 invalidation_range: Range<Anchor>,
648}
649
650enum EditPredictionSettings {
651 Disabled,
652 Enabled {
653 show_in_menu: bool,
654 preview_requires_modifier: bool,
655 },
656}
657
658enum EditPredictionHighlight {}
659
660#[derive(Debug, Clone)]
661struct InlineDiagnostic {
662 message: SharedString,
663 group_id: usize,
664 is_primary: bool,
665 start: Point,
666 severity: lsp::DiagnosticSeverity,
667}
668
669pub enum MenuEditPredictionsPolicy {
670 Never,
671 ByProvider,
672}
673
674pub enum EditPredictionPreview {
675 /// Modifier is not pressed
676 Inactive { released_too_fast: bool },
677 /// Modifier pressed
678 Active {
679 since: Instant,
680 previous_scroll_position: Option<ScrollAnchor>,
681 },
682}
683
684impl EditPredictionPreview {
685 pub fn released_too_fast(&self) -> bool {
686 match self {
687 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
688 EditPredictionPreview::Active { .. } => false,
689 }
690 }
691
692 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
693 if let EditPredictionPreview::Active {
694 previous_scroll_position,
695 ..
696 } = self
697 {
698 *previous_scroll_position = scroll_position;
699 }
700 }
701}
702
703pub struct ContextMenuOptions {
704 pub min_entries_visible: usize,
705 pub max_entries_visible: usize,
706 pub placement: Option<ContextMenuPlacement>,
707}
708
709#[derive(Debug, Clone, PartialEq, Eq)]
710pub enum ContextMenuPlacement {
711 Above,
712 Below,
713}
714
715#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
716struct EditorActionId(usize);
717
718impl EditorActionId {
719 pub fn post_inc(&mut self) -> Self {
720 let answer = self.0;
721
722 *self = Self(answer + 1);
723
724 Self(answer)
725 }
726}
727
728// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
729// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
730
731type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
732type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
733
734#[derive(Default)]
735struct ScrollbarMarkerState {
736 scrollbar_size: Size<Pixels>,
737 dirty: bool,
738 markers: Arc<[PaintQuad]>,
739 pending_refresh: Option<Task<Result<()>>>,
740}
741
742impl ScrollbarMarkerState {
743 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
744 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
745 }
746}
747
748#[derive(Clone, Copy, PartialEq, Eq)]
749pub enum MinimapVisibility {
750 Disabled,
751 Enabled {
752 /// The configuration currently present in the users settings.
753 setting_configuration: bool,
754 /// Whether to override the currently set visibility from the users setting.
755 toggle_override: bool,
756 },
757}
758
759impl MinimapVisibility {
760 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
761 if mode.is_full() {
762 Self::Enabled {
763 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
764 toggle_override: false,
765 }
766 } else {
767 Self::Disabled
768 }
769 }
770
771 fn hidden(&self) -> Self {
772 match *self {
773 Self::Enabled {
774 setting_configuration,
775 ..
776 } => Self::Enabled {
777 setting_configuration,
778 toggle_override: setting_configuration,
779 },
780 Self::Disabled => Self::Disabled,
781 }
782 }
783
784 fn disabled(&self) -> bool {
785 matches!(*self, Self::Disabled)
786 }
787
788 fn settings_visibility(&self) -> bool {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 ..
793 } => setting_configuration,
794 _ => false,
795 }
796 }
797
798 fn visible(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 toggle_override,
803 } => setting_configuration ^ toggle_override,
804 _ => false,
805 }
806 }
807
808 fn toggle_visibility(&self) -> Self {
809 match *self {
810 Self::Enabled {
811 toggle_override,
812 setting_configuration,
813 } => Self::Enabled {
814 setting_configuration,
815 toggle_override: !toggle_override,
816 },
817 Self::Disabled => Self::Disabled,
818 }
819 }
820}
821
822#[derive(Clone, Debug)]
823struct RunnableTasks {
824 templates: Vec<(TaskSourceKind, TaskTemplate)>,
825 offset: multi_buffer::Anchor,
826 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
827 column: u32,
828 // Values of all named captures, including those starting with '_'
829 extra_variables: HashMap<String, String>,
830 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
831 context_range: Range<BufferOffset>,
832}
833
834impl RunnableTasks {
835 fn resolve<'a>(
836 &'a self,
837 cx: &'a task::TaskContext,
838 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
839 self.templates.iter().filter_map(|(kind, template)| {
840 template
841 .resolve_task(&kind.to_id_base(), cx)
842 .map(|task| (kind.clone(), task))
843 })
844 }
845}
846
847#[derive(Clone)]
848pub struct ResolvedTasks {
849 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
850 position: Anchor,
851}
852
853#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
854struct BufferOffset(usize);
855
856// Addons allow storing per-editor state in other crates (e.g. Vim)
857pub trait Addon: 'static {
858 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
859
860 fn render_buffer_header_controls(
861 &self,
862 _: &ExcerptInfo,
863 _: &Window,
864 _: &App,
865 ) -> Option<AnyElement> {
866 None
867 }
868
869 fn to_any(&self) -> &dyn std::any::Any;
870
871 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
872 None
873 }
874}
875
876struct ChangeLocation {
877 current: Option<Vec<Anchor>>,
878 original: Vec<Anchor>,
879}
880impl ChangeLocation {
881 fn locations(&self) -> &[Anchor] {
882 self.current.as_ref().unwrap_or(&self.original)
883 }
884}
885
886/// A set of caret positions, registered when the editor was edited.
887pub struct ChangeList {
888 changes: Vec<ChangeLocation>,
889 /// Currently "selected" change.
890 position: Option<usize>,
891}
892
893impl ChangeList {
894 pub fn new() -> Self {
895 Self {
896 changes: Vec::new(),
897 position: None,
898 }
899 }
900
901 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
902 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
903 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
904 if self.changes.is_empty() {
905 return None;
906 }
907
908 let prev = self.position.unwrap_or(self.changes.len());
909 let next = if direction == Direction::Prev {
910 prev.saturating_sub(count)
911 } else {
912 (prev + count).min(self.changes.len() - 1)
913 };
914 self.position = Some(next);
915 self.changes.get(next).map(|change| change.locations())
916 }
917
918 /// Adds a new change to the list, resetting the change list position.
919 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
920 self.position.take();
921 if let Some(last) = self.changes.last_mut()
922 && group
923 {
924 last.current = Some(new_positions)
925 } else {
926 self.changes.push(ChangeLocation {
927 original: new_positions,
928 current: None,
929 });
930 }
931 }
932
933 pub fn last(&self) -> Option<&[Anchor]> {
934 self.changes.last().map(|change| change.locations())
935 }
936
937 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
938 self.changes.last().map(|change| change.original.as_slice())
939 }
940
941 pub fn invert_last_group(&mut self) {
942 if let Some(last) = self.changes.last_mut()
943 && let Some(current) = last.current.as_mut()
944 {
945 mem::swap(&mut last.original, current);
946 }
947 }
948}
949
950#[derive(Clone)]
951struct InlineBlamePopoverState {
952 scroll_handle: ScrollHandle,
953 commit_message: Option<ParsedCommitMessage>,
954 markdown: Entity<Markdown>,
955}
956
957struct InlineBlamePopover {
958 position: gpui::Point<Pixels>,
959 hide_task: Option<Task<()>>,
960 popover_bounds: Option<Bounds<Pixels>>,
961 popover_state: InlineBlamePopoverState,
962 keyboard_grace: bool,
963}
964
965enum SelectionDragState {
966 /// State when no drag related activity is detected.
967 None,
968 /// State when the mouse is down on a selection that is about to be dragged.
969 ReadyToDrag {
970 selection: Selection<Anchor>,
971 click_position: gpui::Point<Pixels>,
972 mouse_down_time: Instant,
973 },
974 /// State when the mouse is dragging the selection in the editor.
975 Dragging {
976 selection: Selection<Anchor>,
977 drop_cursor: Selection<Anchor>,
978 hide_drop_cursor: bool,
979 },
980}
981
982enum ColumnarSelectionState {
983 FromMouse {
984 selection_tail: Anchor,
985 display_point: Option<DisplayPoint>,
986 },
987 FromSelection {
988 selection_tail: Anchor,
989 },
990}
991
992/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
993/// a breakpoint on them.
994#[derive(Clone, Copy, Debug, PartialEq, Eq)]
995struct PhantomBreakpointIndicator {
996 display_row: DisplayRow,
997 /// There's a small debounce between hovering over the line and showing the indicator.
998 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
999 is_active: bool,
1000 collides_with_existing_breakpoint: bool,
1001}
1002
1003/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1004///
1005/// See the [module level documentation](self) for more information.
1006pub struct Editor {
1007 focus_handle: FocusHandle,
1008 last_focused_descendant: Option<WeakFocusHandle>,
1009 /// The text buffer being edited
1010 buffer: Entity<MultiBuffer>,
1011 /// Map of how text in the buffer should be displayed.
1012 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1013 pub display_map: Entity<DisplayMap>,
1014 pub selections: SelectionsCollection,
1015 pub scroll_manager: ScrollManager,
1016 /// When inline assist editors are linked, they all render cursors because
1017 /// typing enters text into each of them, even the ones that aren't focused.
1018 pub(crate) show_cursor_when_unfocused: bool,
1019 columnar_selection_state: Option<ColumnarSelectionState>,
1020 add_selections_state: Option<AddSelectionsState>,
1021 select_next_state: Option<SelectNextState>,
1022 select_prev_state: Option<SelectNextState>,
1023 selection_history: SelectionHistory,
1024 defer_selection_effects: bool,
1025 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1026 autoclose_regions: Vec<AutocloseRegion>,
1027 snippet_stack: InvalidationStack<SnippetState>,
1028 select_syntax_node_history: SelectSyntaxNodeHistory,
1029 ime_transaction: Option<TransactionId>,
1030 pub diagnostics_max_severity: DiagnosticSeverity,
1031 active_diagnostics: ActiveDiagnostic,
1032 show_inline_diagnostics: bool,
1033 inline_diagnostics_update: Task<()>,
1034 inline_diagnostics_enabled: bool,
1035 diagnostics_enabled: bool,
1036 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1037 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1038 hard_wrap: Option<usize>,
1039 project: Option<Entity<Project>>,
1040 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1041 completion_provider: Option<Rc<dyn CompletionProvider>>,
1042 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1043 blink_manager: Entity<BlinkManager>,
1044 show_cursor_names: bool,
1045 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1046 pub show_local_selections: bool,
1047 mode: EditorMode,
1048 show_breadcrumbs: bool,
1049 show_gutter: bool,
1050 show_scrollbars: ScrollbarAxes,
1051 minimap_visibility: MinimapVisibility,
1052 offset_content: bool,
1053 disable_expand_excerpt_buttons: bool,
1054 show_line_numbers: Option<bool>,
1055 use_relative_line_numbers: Option<bool>,
1056 show_git_diff_gutter: Option<bool>,
1057 show_code_actions: Option<bool>,
1058 show_runnables: Option<bool>,
1059 show_breakpoints: Option<bool>,
1060 show_wrap_guides: Option<bool>,
1061 show_indent_guides: Option<bool>,
1062 placeholder_text: Option<Arc<str>>,
1063 highlight_order: usize,
1064 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1065 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1066 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1067 scrollbar_marker_state: ScrollbarMarkerState,
1068 active_indent_guides_state: ActiveIndentGuidesState,
1069 nav_history: Option<ItemNavHistory>,
1070 context_menu: RefCell<Option<CodeContextMenu>>,
1071 context_menu_options: Option<ContextMenuOptions>,
1072 mouse_context_menu: Option<MouseContextMenu>,
1073 completion_tasks: Vec<(CompletionId, Task<()>)>,
1074 inline_blame_popover: Option<InlineBlamePopover>,
1075 inline_blame_popover_show_task: Option<Task<()>>,
1076 signature_help_state: SignatureHelpState,
1077 auto_signature_help: Option<bool>,
1078 find_all_references_task_sources: Vec<Anchor>,
1079 next_completion_id: CompletionId,
1080 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1081 code_actions_task: Option<Task<Result<()>>>,
1082 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1083 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1084 document_highlights_task: Option<Task<()>>,
1085 linked_editing_range_task: Option<Task<Option<()>>>,
1086 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1087 pending_rename: Option<RenameState>,
1088 searchable: bool,
1089 cursor_shape: CursorShape,
1090 current_line_highlight: Option<CurrentLineHighlight>,
1091 collapse_matches: bool,
1092 autoindent_mode: Option<AutoindentMode>,
1093 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1094 input_enabled: bool,
1095 use_modal_editing: bool,
1096 read_only: bool,
1097 leader_id: Option<CollaboratorId>,
1098 remote_id: Option<ViewId>,
1099 pub hover_state: HoverState,
1100 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1101 gutter_hovered: bool,
1102 hovered_link_state: Option<HoveredLinkState>,
1103 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1104 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1105 active_edit_prediction: Option<EditPredictionState>,
1106 /// Used to prevent flickering as the user types while the menu is open
1107 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1108 edit_prediction_settings: EditPredictionSettings,
1109 edit_predictions_hidden_for_vim_mode: bool,
1110 show_edit_predictions_override: Option<bool>,
1111 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1112 edit_prediction_preview: EditPredictionPreview,
1113 edit_prediction_indent_conflict: bool,
1114 edit_prediction_requires_modifier_in_indent_conflict: bool,
1115 inlay_hint_cache: InlayHintCache,
1116 next_inlay_id: usize,
1117 _subscriptions: Vec<Subscription>,
1118 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1119 gutter_dimensions: GutterDimensions,
1120 style: Option<EditorStyle>,
1121 text_style_refinement: Option<TextStyleRefinement>,
1122 next_editor_action_id: EditorActionId,
1123 editor_actions: Rc<
1124 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1125 >,
1126 use_autoclose: bool,
1127 use_auto_surround: bool,
1128 auto_replace_emoji_shortcode: bool,
1129 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1130 show_git_blame_gutter: bool,
1131 show_git_blame_inline: bool,
1132 show_git_blame_inline_delay_task: Option<Task<()>>,
1133 git_blame_inline_enabled: bool,
1134 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1135 serialize_dirty_buffers: bool,
1136 show_selection_menu: Option<bool>,
1137 blame: Option<Entity<GitBlame>>,
1138 blame_subscription: Option<Subscription>,
1139 custom_context_menu: Option<
1140 Box<
1141 dyn 'static
1142 + Fn(
1143 &mut Self,
1144 DisplayPoint,
1145 &mut Window,
1146 &mut Context<Self>,
1147 ) -> Option<Entity<ui::ContextMenu>>,
1148 >,
1149 >,
1150 last_bounds: Option<Bounds<Pixels>>,
1151 last_position_map: Option<Rc<PositionMap>>,
1152 expect_bounds_change: Option<Bounds<Pixels>>,
1153 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1154 tasks_update_task: Option<Task<()>>,
1155 breakpoint_store: Option<Entity<BreakpointStore>>,
1156 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1157 hovered_diff_hunk_row: Option<DisplayRow>,
1158 pull_diagnostics_task: Task<()>,
1159 in_project_search: bool,
1160 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1161 breadcrumb_header: Option<String>,
1162 focused_block: Option<FocusedBlock>,
1163 next_scroll_position: NextScrollCursorCenterTopBottom,
1164 addons: HashMap<TypeId, Box<dyn Addon>>,
1165 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1166 load_diff_task: Option<Shared<Task<()>>>,
1167 /// Whether we are temporarily displaying a diff other than git's
1168 temporary_diff_override: bool,
1169 selection_mark_mode: bool,
1170 toggle_fold_multiple_buffers: Task<()>,
1171 _scroll_cursor_center_top_bottom_task: Task<()>,
1172 serialize_selections: Task<()>,
1173 serialize_folds: Task<()>,
1174 mouse_cursor_hidden: bool,
1175 minimap: Option<Entity<Self>>,
1176 hide_mouse_mode: HideMouseMode,
1177 pub change_list: ChangeList,
1178 inline_value_cache: InlineValueCache,
1179 selection_drag_state: SelectionDragState,
1180 next_color_inlay_id: usize,
1181 colors: Option<LspColorData>,
1182 folding_newlines: Task<()>,
1183}
1184
1185#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1186enum NextScrollCursorCenterTopBottom {
1187 #[default]
1188 Center,
1189 Top,
1190 Bottom,
1191}
1192
1193impl NextScrollCursorCenterTopBottom {
1194 fn next(&self) -> Self {
1195 match self {
1196 Self::Center => Self::Top,
1197 Self::Top => Self::Bottom,
1198 Self::Bottom => Self::Center,
1199 }
1200 }
1201}
1202
1203#[derive(Clone)]
1204pub struct EditorSnapshot {
1205 pub mode: EditorMode,
1206 show_gutter: bool,
1207 show_line_numbers: Option<bool>,
1208 show_git_diff_gutter: Option<bool>,
1209 show_code_actions: Option<bool>,
1210 show_runnables: Option<bool>,
1211 show_breakpoints: Option<bool>,
1212 git_blame_gutter_max_author_length: Option<usize>,
1213 pub display_snapshot: DisplaySnapshot,
1214 pub placeholder_text: Option<Arc<str>>,
1215 is_focused: bool,
1216 scroll_anchor: ScrollAnchor,
1217 ongoing_scroll: OngoingScroll,
1218 current_line_highlight: CurrentLineHighlight,
1219 gutter_hovered: bool,
1220}
1221
1222#[derive(Default, Debug, Clone, Copy)]
1223pub struct GutterDimensions {
1224 pub left_padding: Pixels,
1225 pub right_padding: Pixels,
1226 pub width: Pixels,
1227 pub margin: Pixels,
1228 pub git_blame_entries_width: Option<Pixels>,
1229}
1230
1231impl GutterDimensions {
1232 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1233 Self {
1234 margin: Self::default_gutter_margin(font_id, font_size, cx),
1235 ..Default::default()
1236 }
1237 }
1238
1239 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1240 -cx.text_system().descent(font_id, font_size)
1241 }
1242 /// The full width of the space taken up by the gutter.
1243 pub fn full_width(&self) -> Pixels {
1244 self.margin + self.width
1245 }
1246
1247 /// The width of the space reserved for the fold indicators,
1248 /// use alongside 'justify_end' and `gutter_width` to
1249 /// right align content with the line numbers
1250 pub fn fold_area_width(&self) -> Pixels {
1251 self.margin + self.right_padding
1252 }
1253}
1254
1255struct CharacterDimensions {
1256 em_width: Pixels,
1257 em_advance: Pixels,
1258 line_height: Pixels,
1259}
1260
1261#[derive(Debug)]
1262pub struct RemoteSelection {
1263 pub replica_id: ReplicaId,
1264 pub selection: Selection<Anchor>,
1265 pub cursor_shape: CursorShape,
1266 pub collaborator_id: CollaboratorId,
1267 pub line_mode: bool,
1268 pub user_name: Option<SharedString>,
1269 pub color: PlayerColor,
1270}
1271
1272#[derive(Clone, Debug)]
1273struct SelectionHistoryEntry {
1274 selections: Arc<[Selection<Anchor>]>,
1275 select_next_state: Option<SelectNextState>,
1276 select_prev_state: Option<SelectNextState>,
1277 add_selections_state: Option<AddSelectionsState>,
1278}
1279
1280#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1281enum SelectionHistoryMode {
1282 Normal,
1283 Undoing,
1284 Redoing,
1285 Skipping,
1286}
1287
1288#[derive(Clone, PartialEq, Eq, Hash)]
1289struct HoveredCursor {
1290 replica_id: u16,
1291 selection_id: usize,
1292}
1293
1294impl Default for SelectionHistoryMode {
1295 fn default() -> Self {
1296 Self::Normal
1297 }
1298}
1299
1300#[derive(Debug)]
1301/// SelectionEffects controls the side-effects of updating the selection.
1302///
1303/// The default behaviour does "what you mostly want":
1304/// - it pushes to the nav history if the cursor moved by >10 lines
1305/// - it re-triggers completion requests
1306/// - it scrolls to fit
1307///
1308/// You might want to modify these behaviours. For example when doing a "jump"
1309/// like go to definition, we always want to add to nav history; but when scrolling
1310/// in vim mode we never do.
1311///
1312/// Similarly, you might want to disable scrolling if you don't want the viewport to
1313/// move.
1314#[derive(Clone)]
1315pub struct SelectionEffects {
1316 nav_history: Option<bool>,
1317 completions: bool,
1318 scroll: Option<Autoscroll>,
1319}
1320
1321impl Default for SelectionEffects {
1322 fn default() -> Self {
1323 Self {
1324 nav_history: None,
1325 completions: true,
1326 scroll: Some(Autoscroll::fit()),
1327 }
1328 }
1329}
1330impl SelectionEffects {
1331 pub fn scroll(scroll: Autoscroll) -> Self {
1332 Self {
1333 scroll: Some(scroll),
1334 ..Default::default()
1335 }
1336 }
1337
1338 pub fn no_scroll() -> Self {
1339 Self {
1340 scroll: None,
1341 ..Default::default()
1342 }
1343 }
1344
1345 pub fn completions(self, completions: bool) -> Self {
1346 Self {
1347 completions,
1348 ..self
1349 }
1350 }
1351
1352 pub fn nav_history(self, nav_history: bool) -> Self {
1353 Self {
1354 nav_history: Some(nav_history),
1355 ..self
1356 }
1357 }
1358}
1359
1360struct DeferredSelectionEffectsState {
1361 changed: bool,
1362 effects: SelectionEffects,
1363 old_cursor_position: Anchor,
1364 history_entry: SelectionHistoryEntry,
1365}
1366
1367#[derive(Default)]
1368struct SelectionHistory {
1369 #[allow(clippy::type_complexity)]
1370 selections_by_transaction:
1371 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1372 mode: SelectionHistoryMode,
1373 undo_stack: VecDeque<SelectionHistoryEntry>,
1374 redo_stack: VecDeque<SelectionHistoryEntry>,
1375}
1376
1377impl SelectionHistory {
1378 #[track_caller]
1379 fn insert_transaction(
1380 &mut self,
1381 transaction_id: TransactionId,
1382 selections: Arc<[Selection<Anchor>]>,
1383 ) {
1384 if selections.is_empty() {
1385 log::error!(
1386 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1387 std::panic::Location::caller()
1388 );
1389 return;
1390 }
1391 self.selections_by_transaction
1392 .insert(transaction_id, (selections, None));
1393 }
1394
1395 #[allow(clippy::type_complexity)]
1396 fn transaction(
1397 &self,
1398 transaction_id: TransactionId,
1399 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1400 self.selections_by_transaction.get(&transaction_id)
1401 }
1402
1403 #[allow(clippy::type_complexity)]
1404 fn transaction_mut(
1405 &mut self,
1406 transaction_id: TransactionId,
1407 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1408 self.selections_by_transaction.get_mut(&transaction_id)
1409 }
1410
1411 fn push(&mut self, entry: SelectionHistoryEntry) {
1412 if !entry.selections.is_empty() {
1413 match self.mode {
1414 SelectionHistoryMode::Normal => {
1415 self.push_undo(entry);
1416 self.redo_stack.clear();
1417 }
1418 SelectionHistoryMode::Undoing => self.push_redo(entry),
1419 SelectionHistoryMode::Redoing => self.push_undo(entry),
1420 SelectionHistoryMode::Skipping => {}
1421 }
1422 }
1423 }
1424
1425 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1426 if self
1427 .undo_stack
1428 .back()
1429 .is_none_or(|e| e.selections != entry.selections)
1430 {
1431 self.undo_stack.push_back(entry);
1432 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1433 self.undo_stack.pop_front();
1434 }
1435 }
1436 }
1437
1438 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .redo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.redo_stack.push_back(entry);
1445 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.redo_stack.pop_front();
1447 }
1448 }
1449 }
1450}
1451
1452#[derive(Clone, Copy)]
1453pub struct RowHighlightOptions {
1454 pub autoscroll: bool,
1455 pub include_gutter: bool,
1456}
1457
1458impl Default for RowHighlightOptions {
1459 fn default() -> Self {
1460 Self {
1461 autoscroll: Default::default(),
1462 include_gutter: true,
1463 }
1464 }
1465}
1466
1467struct RowHighlight {
1468 index: usize,
1469 range: Range<Anchor>,
1470 color: Hsla,
1471 options: RowHighlightOptions,
1472 type_id: TypeId,
1473}
1474
1475#[derive(Clone, Debug)]
1476struct AddSelectionsState {
1477 groups: Vec<AddSelectionsGroup>,
1478}
1479
1480#[derive(Clone, Debug)]
1481struct AddSelectionsGroup {
1482 above: bool,
1483 stack: Vec<usize>,
1484}
1485
1486#[derive(Clone)]
1487struct SelectNextState {
1488 query: AhoCorasick,
1489 wordwise: bool,
1490 done: bool,
1491}
1492
1493impl std::fmt::Debug for SelectNextState {
1494 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1495 f.debug_struct(std::any::type_name::<Self>())
1496 .field("wordwise", &self.wordwise)
1497 .field("done", &self.done)
1498 .finish()
1499 }
1500}
1501
1502#[derive(Debug)]
1503struct AutocloseRegion {
1504 selection_id: usize,
1505 range: Range<Anchor>,
1506 pair: BracketPair,
1507}
1508
1509#[derive(Debug)]
1510struct SnippetState {
1511 ranges: Vec<Vec<Range<Anchor>>>,
1512 active_index: usize,
1513 choices: Vec<Option<Vec<String>>>,
1514}
1515
1516#[doc(hidden)]
1517pub struct RenameState {
1518 pub range: Range<Anchor>,
1519 pub old_name: Arc<str>,
1520 pub editor: Entity<Editor>,
1521 block_id: CustomBlockId,
1522}
1523
1524struct InvalidationStack<T>(Vec<T>);
1525
1526struct RegisteredEditPredictionProvider {
1527 provider: Arc<dyn EditPredictionProviderHandle>,
1528 _subscription: Subscription,
1529}
1530
1531#[derive(Debug, PartialEq, Eq)]
1532pub struct ActiveDiagnosticGroup {
1533 pub active_range: Range<Anchor>,
1534 pub active_message: String,
1535 pub group_id: usize,
1536 pub blocks: HashSet<CustomBlockId>,
1537}
1538
1539#[derive(Debug, PartialEq, Eq)]
1540
1541pub(crate) enum ActiveDiagnostic {
1542 None,
1543 All,
1544 Group(ActiveDiagnosticGroup),
1545}
1546
1547#[derive(Serialize, Deserialize, Clone, Debug)]
1548pub struct ClipboardSelection {
1549 /// The number of bytes in this selection.
1550 pub len: usize,
1551 /// Whether this was a full-line selection.
1552 pub is_entire_line: bool,
1553 /// The indentation of the first line when this content was originally copied.
1554 pub first_line_indent: u32,
1555}
1556
1557// selections, scroll behavior, was newest selection reversed
1558type SelectSyntaxNodeHistoryState = (
1559 Box<[Selection<usize>]>,
1560 SelectSyntaxNodeScrollBehavior,
1561 bool,
1562);
1563
1564#[derive(Default)]
1565struct SelectSyntaxNodeHistory {
1566 stack: Vec<SelectSyntaxNodeHistoryState>,
1567 // disable temporarily to allow changing selections without losing the stack
1568 pub disable_clearing: bool,
1569}
1570
1571impl SelectSyntaxNodeHistory {
1572 pub fn try_clear(&mut self) {
1573 if !self.disable_clearing {
1574 self.stack.clear();
1575 }
1576 }
1577
1578 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1579 self.stack.push(selection);
1580 }
1581
1582 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1583 self.stack.pop()
1584 }
1585}
1586
1587enum SelectSyntaxNodeScrollBehavior {
1588 CursorTop,
1589 FitSelection,
1590 CursorBottom,
1591}
1592
1593#[derive(Debug)]
1594pub(crate) struct NavigationData {
1595 cursor_anchor: Anchor,
1596 cursor_position: Point,
1597 scroll_anchor: ScrollAnchor,
1598 scroll_top_row: u32,
1599}
1600
1601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1602pub enum GotoDefinitionKind {
1603 Symbol,
1604 Declaration,
1605 Type,
1606 Implementation,
1607}
1608
1609#[derive(Debug, Clone)]
1610enum InlayHintRefreshReason {
1611 ModifiersChanged(bool),
1612 Toggle(bool),
1613 SettingsChange(InlayHintSettings),
1614 NewLinesShown,
1615 BufferEdited(HashSet<Arc<Language>>),
1616 RefreshRequested,
1617 ExcerptsRemoved(Vec<ExcerptId>),
1618}
1619
1620impl InlayHintRefreshReason {
1621 fn description(&self) -> &'static str {
1622 match self {
1623 Self::ModifiersChanged(_) => "modifiers changed",
1624 Self::Toggle(_) => "toggle",
1625 Self::SettingsChange(_) => "settings change",
1626 Self::NewLinesShown => "new lines shown",
1627 Self::BufferEdited(_) => "buffer edited",
1628 Self::RefreshRequested => "refresh requested",
1629 Self::ExcerptsRemoved(_) => "excerpts removed",
1630 }
1631 }
1632}
1633
1634pub enum FormatTarget {
1635 Buffers(HashSet<Entity<Buffer>>),
1636 Ranges(Vec<Range<MultiBufferPoint>>),
1637}
1638
1639pub(crate) struct FocusedBlock {
1640 id: BlockId,
1641 focus_handle: WeakFocusHandle,
1642}
1643
1644#[derive(Clone)]
1645enum JumpData {
1646 MultiBufferRow {
1647 row: MultiBufferRow,
1648 line_offset_from_top: u32,
1649 },
1650 MultiBufferPoint {
1651 excerpt_id: ExcerptId,
1652 position: Point,
1653 anchor: text::Anchor,
1654 line_offset_from_top: u32,
1655 },
1656}
1657
1658pub enum MultibufferSelectionMode {
1659 First,
1660 All,
1661}
1662
1663#[derive(Clone, Copy, Debug, Default)]
1664pub struct RewrapOptions {
1665 pub override_language_settings: bool,
1666 pub preserve_existing_whitespace: bool,
1667}
1668
1669impl Editor {
1670 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1674 }
1675
1676 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1677 let buffer = cx.new(|cx| Buffer::local("", cx));
1678 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1679 Self::new(EditorMode::full(), buffer, None, window, cx)
1680 }
1681
1682 pub fn auto_height(
1683 min_lines: usize,
1684 max_lines: usize,
1685 window: &mut Window,
1686 cx: &mut Context<Self>,
1687 ) -> Self {
1688 let buffer = cx.new(|cx| Buffer::local("", cx));
1689 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1690 Self::new(
1691 EditorMode::AutoHeight {
1692 min_lines,
1693 max_lines: Some(max_lines),
1694 },
1695 buffer,
1696 None,
1697 window,
1698 cx,
1699 )
1700 }
1701
1702 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1703 /// The editor grows as tall as needed to fit its content.
1704 pub fn auto_height_unbounded(
1705 min_lines: usize,
1706 window: &mut Window,
1707 cx: &mut Context<Self>,
1708 ) -> Self {
1709 let buffer = cx.new(|cx| Buffer::local("", cx));
1710 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1711 Self::new(
1712 EditorMode::AutoHeight {
1713 min_lines,
1714 max_lines: None,
1715 },
1716 buffer,
1717 None,
1718 window,
1719 cx,
1720 )
1721 }
1722
1723 pub fn for_buffer(
1724 buffer: Entity<Buffer>,
1725 project: Option<Entity<Project>>,
1726 window: &mut Window,
1727 cx: &mut Context<Self>,
1728 ) -> Self {
1729 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1730 Self::new(EditorMode::full(), buffer, project, window, cx)
1731 }
1732
1733 pub fn for_multibuffer(
1734 buffer: Entity<MultiBuffer>,
1735 project: Option<Entity<Project>>,
1736 window: &mut Window,
1737 cx: &mut Context<Self>,
1738 ) -> Self {
1739 Self::new(EditorMode::full(), buffer, project, window, cx)
1740 }
1741
1742 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1743 let mut clone = Self::new(
1744 self.mode.clone(),
1745 self.buffer.clone(),
1746 self.project.clone(),
1747 window,
1748 cx,
1749 );
1750 self.display_map.update(cx, |display_map, cx| {
1751 let snapshot = display_map.snapshot(cx);
1752 clone.display_map.update(cx, |display_map, cx| {
1753 display_map.set_state(&snapshot, cx);
1754 });
1755 });
1756 clone.folds_did_change(cx);
1757 clone.selections.clone_state(&self.selections);
1758 clone.scroll_manager.clone_state(&self.scroll_manager);
1759 clone.searchable = self.searchable;
1760 clone.read_only = self.read_only;
1761 clone
1762 }
1763
1764 pub fn new(
1765 mode: EditorMode,
1766 buffer: Entity<MultiBuffer>,
1767 project: Option<Entity<Project>>,
1768 window: &mut Window,
1769 cx: &mut Context<Self>,
1770 ) -> Self {
1771 Editor::new_internal(mode, buffer, project, None, window, cx)
1772 }
1773
1774 fn new_internal(
1775 mode: EditorMode,
1776 buffer: Entity<MultiBuffer>,
1777 project: Option<Entity<Project>>,
1778 display_map: Option<Entity<DisplayMap>>,
1779 window: &mut Window,
1780 cx: &mut Context<Self>,
1781 ) -> Self {
1782 debug_assert!(
1783 display_map.is_none() || mode.is_minimap(),
1784 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1785 );
1786
1787 let full_mode = mode.is_full();
1788 let is_minimap = mode.is_minimap();
1789 let diagnostics_max_severity = if full_mode {
1790 EditorSettings::get_global(cx)
1791 .diagnostics_max_severity
1792 .unwrap_or(DiagnosticSeverity::Hint)
1793 } else {
1794 DiagnosticSeverity::Off
1795 };
1796 let style = window.text_style();
1797 let font_size = style.font_size.to_pixels(window.rem_size());
1798 let editor = cx.entity().downgrade();
1799 let fold_placeholder = FoldPlaceholder {
1800 constrain_width: true,
1801 render: Arc::new(move |fold_id, fold_range, cx| {
1802 let editor = editor.clone();
1803 div()
1804 .id(fold_id)
1805 .bg(cx.theme().colors().ghost_element_background)
1806 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1807 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1808 .rounded_xs()
1809 .size_full()
1810 .cursor_pointer()
1811 .child("⋯")
1812 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1813 .on_click(move |_, _window, cx| {
1814 editor
1815 .update(cx, |editor, cx| {
1816 editor.unfold_ranges(
1817 &[fold_range.start..fold_range.end],
1818 true,
1819 false,
1820 cx,
1821 );
1822 cx.stop_propagation();
1823 })
1824 .ok();
1825 })
1826 .into_any()
1827 }),
1828 merge_adjacent: true,
1829 ..FoldPlaceholder::default()
1830 };
1831 let display_map = display_map.unwrap_or_else(|| {
1832 cx.new(|cx| {
1833 DisplayMap::new(
1834 buffer.clone(),
1835 style.font(),
1836 font_size,
1837 None,
1838 FILE_HEADER_HEIGHT,
1839 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1840 fold_placeholder,
1841 diagnostics_max_severity,
1842 cx,
1843 )
1844 })
1845 });
1846
1847 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1848
1849 let blink_manager = cx.new(|cx| {
1850 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1851 if is_minimap {
1852 blink_manager.disable(cx);
1853 }
1854 blink_manager
1855 });
1856
1857 let soft_wrap_mode_override =
1858 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1859
1860 let mut project_subscriptions = Vec::new();
1861 if full_mode && let Some(project) = project.as_ref() {
1862 project_subscriptions.push(cx.subscribe_in(
1863 project,
1864 window,
1865 |editor, _, event, window, cx| match event {
1866 project::Event::RefreshCodeLens => {
1867 // we always query lens with actions, without storing them, always refreshing them
1868 }
1869 project::Event::RefreshInlayHints => {
1870 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1871 }
1872 project::Event::LanguageServerAdded(..)
1873 | project::Event::LanguageServerRemoved(..) => {
1874 if editor.tasks_update_task.is_none() {
1875 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1876 }
1877 }
1878 project::Event::SnippetEdit(id, snippet_edits) => {
1879 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1880 let focus_handle = editor.focus_handle(cx);
1881 if focus_handle.is_focused(window) {
1882 let snapshot = buffer.read(cx).snapshot();
1883 for (range, snippet) in snippet_edits {
1884 let editor_range =
1885 language::range_from_lsp(*range).to_offset(&snapshot);
1886 editor
1887 .insert_snippet(
1888 &[editor_range],
1889 snippet.clone(),
1890 window,
1891 cx,
1892 )
1893 .ok();
1894 }
1895 }
1896 }
1897 }
1898 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1899 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1900 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1901 }
1902 }
1903 _ => {}
1904 },
1905 ));
1906 if let Some(task_inventory) = project
1907 .read(cx)
1908 .task_store()
1909 .read(cx)
1910 .task_inventory()
1911 .cloned()
1912 {
1913 project_subscriptions.push(cx.observe_in(
1914 &task_inventory,
1915 window,
1916 |editor, _, window, cx| {
1917 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1918 },
1919 ));
1920 };
1921
1922 project_subscriptions.push(cx.subscribe_in(
1923 &project.read(cx).breakpoint_store(),
1924 window,
1925 |editor, _, event, window, cx| match event {
1926 BreakpointStoreEvent::ClearDebugLines => {
1927 editor.clear_row_highlights::<ActiveDebugLine>();
1928 editor.refresh_inline_values(cx);
1929 }
1930 BreakpointStoreEvent::SetDebugLine => {
1931 if editor.go_to_active_debug_line(window, cx) {
1932 cx.stop_propagation();
1933 }
1934
1935 editor.refresh_inline_values(cx);
1936 }
1937 _ => {}
1938 },
1939 ));
1940 let git_store = project.read(cx).git_store().clone();
1941 let project = project.clone();
1942 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1943 if let GitStoreEvent::RepositoryUpdated(
1944 _,
1945 RepositoryEvent::Updated {
1946 new_instance: true, ..
1947 },
1948 _,
1949 ) = event
1950 {
1951 this.load_diff_task = Some(
1952 update_uncommitted_diff_for_buffer(
1953 cx.entity(),
1954 &project,
1955 this.buffer.read(cx).all_buffers(),
1956 this.buffer.clone(),
1957 cx,
1958 )
1959 .shared(),
1960 );
1961 }
1962 }));
1963 }
1964
1965 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1966
1967 let inlay_hint_settings =
1968 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1969 let focus_handle = cx.focus_handle();
1970 if !is_minimap {
1971 cx.on_focus(&focus_handle, window, Self::handle_focus)
1972 .detach();
1973 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1974 .detach();
1975 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1976 .detach();
1977 cx.on_blur(&focus_handle, window, Self::handle_blur)
1978 .detach();
1979 cx.observe_pending_input(window, Self::observe_pending_input)
1980 .detach();
1981 }
1982
1983 let show_indent_guides =
1984 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
1985 Some(false)
1986 } else {
1987 None
1988 };
1989
1990 let breakpoint_store = match (&mode, project.as_ref()) {
1991 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1992 _ => None,
1993 };
1994
1995 let mut code_action_providers = Vec::new();
1996 let mut load_uncommitted_diff = None;
1997 if let Some(project) = project.clone() {
1998 load_uncommitted_diff = Some(
1999 update_uncommitted_diff_for_buffer(
2000 cx.entity(),
2001 &project,
2002 buffer.read(cx).all_buffers(),
2003 buffer.clone(),
2004 cx,
2005 )
2006 .shared(),
2007 );
2008 code_action_providers.push(Rc::new(project) as Rc<_>);
2009 }
2010
2011 let mut editor = Self {
2012 focus_handle,
2013 show_cursor_when_unfocused: false,
2014 last_focused_descendant: None,
2015 buffer: buffer.clone(),
2016 display_map: display_map.clone(),
2017 selections,
2018 scroll_manager: ScrollManager::new(cx),
2019 columnar_selection_state: None,
2020 add_selections_state: None,
2021 select_next_state: None,
2022 select_prev_state: None,
2023 selection_history: SelectionHistory::default(),
2024 defer_selection_effects: false,
2025 deferred_selection_effects_state: None,
2026 autoclose_regions: Vec::new(),
2027 snippet_stack: InvalidationStack::default(),
2028 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2029 ime_transaction: None,
2030 active_diagnostics: ActiveDiagnostic::None,
2031 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2032 inline_diagnostics_update: Task::ready(()),
2033 inline_diagnostics: Vec::new(),
2034 soft_wrap_mode_override,
2035 diagnostics_max_severity,
2036 hard_wrap: None,
2037 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2038 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2039 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2040 project,
2041 blink_manager: blink_manager.clone(),
2042 show_local_selections: true,
2043 show_scrollbars: ScrollbarAxes {
2044 horizontal: full_mode,
2045 vertical: full_mode,
2046 },
2047 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2048 offset_content: !matches!(mode, EditorMode::SingleLine),
2049 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2050 show_gutter: full_mode,
2051 show_line_numbers: (!full_mode).then_some(false),
2052 use_relative_line_numbers: None,
2053 disable_expand_excerpt_buttons: !full_mode,
2054 show_git_diff_gutter: None,
2055 show_code_actions: None,
2056 show_runnables: None,
2057 show_breakpoints: None,
2058 show_wrap_guides: None,
2059 show_indent_guides,
2060 placeholder_text: None,
2061 highlight_order: 0,
2062 highlighted_rows: HashMap::default(),
2063 background_highlights: TreeMap::default(),
2064 gutter_highlights: TreeMap::default(),
2065 scrollbar_marker_state: ScrollbarMarkerState::default(),
2066 active_indent_guides_state: ActiveIndentGuidesState::default(),
2067 nav_history: None,
2068 context_menu: RefCell::new(None),
2069 context_menu_options: None,
2070 mouse_context_menu: None,
2071 completion_tasks: Vec::new(),
2072 inline_blame_popover: None,
2073 inline_blame_popover_show_task: None,
2074 signature_help_state: SignatureHelpState::default(),
2075 auto_signature_help: None,
2076 find_all_references_task_sources: Vec::new(),
2077 next_completion_id: 0,
2078 next_inlay_id: 0,
2079 code_action_providers,
2080 available_code_actions: None,
2081 code_actions_task: None,
2082 quick_selection_highlight_task: None,
2083 debounced_selection_highlight_task: None,
2084 document_highlights_task: None,
2085 linked_editing_range_task: None,
2086 pending_rename: None,
2087 searchable: !is_minimap,
2088 cursor_shape: EditorSettings::get_global(cx)
2089 .cursor_shape
2090 .unwrap_or_default(),
2091 current_line_highlight: None,
2092 autoindent_mode: Some(AutoindentMode::EachLine),
2093 collapse_matches: false,
2094 workspace: None,
2095 input_enabled: !is_minimap,
2096 use_modal_editing: full_mode,
2097 read_only: is_minimap,
2098 use_autoclose: true,
2099 use_auto_surround: true,
2100 auto_replace_emoji_shortcode: false,
2101 jsx_tag_auto_close_enabled_in_any_buffer: false,
2102 leader_id: None,
2103 remote_id: None,
2104 hover_state: HoverState::default(),
2105 pending_mouse_down: None,
2106 hovered_link_state: None,
2107 edit_prediction_provider: None,
2108 active_edit_prediction: None,
2109 stale_edit_prediction_in_menu: None,
2110 edit_prediction_preview: EditPredictionPreview::Inactive {
2111 released_too_fast: false,
2112 },
2113 inline_diagnostics_enabled: full_mode,
2114 diagnostics_enabled: full_mode,
2115 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2116 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2117 gutter_hovered: false,
2118 pixel_position_of_newest_cursor: None,
2119 last_bounds: None,
2120 last_position_map: None,
2121 expect_bounds_change: None,
2122 gutter_dimensions: GutterDimensions::default(),
2123 style: None,
2124 show_cursor_names: false,
2125 hovered_cursors: HashMap::default(),
2126 next_editor_action_id: EditorActionId::default(),
2127 editor_actions: Rc::default(),
2128 edit_predictions_hidden_for_vim_mode: false,
2129 show_edit_predictions_override: None,
2130 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2131 edit_prediction_settings: EditPredictionSettings::Disabled,
2132 edit_prediction_indent_conflict: false,
2133 edit_prediction_requires_modifier_in_indent_conflict: true,
2134 custom_context_menu: None,
2135 show_git_blame_gutter: false,
2136 show_git_blame_inline: false,
2137 show_selection_menu: None,
2138 show_git_blame_inline_delay_task: None,
2139 git_blame_inline_enabled: full_mode
2140 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2141 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2142 serialize_dirty_buffers: !is_minimap
2143 && ProjectSettings::get_global(cx)
2144 .session
2145 .restore_unsaved_buffers,
2146 blame: None,
2147 blame_subscription: None,
2148 tasks: BTreeMap::default(),
2149
2150 breakpoint_store,
2151 gutter_breakpoint_indicator: (None, None),
2152 hovered_diff_hunk_row: None,
2153 _subscriptions: (!is_minimap)
2154 .then(|| {
2155 vec![
2156 cx.observe(&buffer, Self::on_buffer_changed),
2157 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2158 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2159 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2160 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2161 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2162 cx.observe_window_activation(window, |editor, window, cx| {
2163 let active = window.is_window_active();
2164 editor.blink_manager.update(cx, |blink_manager, cx| {
2165 if active {
2166 blink_manager.enable(cx);
2167 } else {
2168 blink_manager.disable(cx);
2169 }
2170 });
2171 if active {
2172 editor.show_mouse_cursor(cx);
2173 }
2174 }),
2175 ]
2176 })
2177 .unwrap_or_default(),
2178 tasks_update_task: None,
2179 pull_diagnostics_task: Task::ready(()),
2180 colors: None,
2181 next_color_inlay_id: 0,
2182 linked_edit_ranges: Default::default(),
2183 in_project_search: false,
2184 previous_search_ranges: None,
2185 breadcrumb_header: None,
2186 focused_block: None,
2187 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2188 addons: HashMap::default(),
2189 registered_buffers: HashMap::default(),
2190 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2191 selection_mark_mode: false,
2192 toggle_fold_multiple_buffers: Task::ready(()),
2193 serialize_selections: Task::ready(()),
2194 serialize_folds: Task::ready(()),
2195 text_style_refinement: None,
2196 load_diff_task: load_uncommitted_diff,
2197 temporary_diff_override: false,
2198 mouse_cursor_hidden: false,
2199 minimap: None,
2200 hide_mouse_mode: EditorSettings::get_global(cx)
2201 .hide_mouse
2202 .unwrap_or_default(),
2203 change_list: ChangeList::new(),
2204 mode,
2205 selection_drag_state: SelectionDragState::None,
2206 folding_newlines: Task::ready(()),
2207 };
2208
2209 if is_minimap {
2210 return editor;
2211 }
2212
2213 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2214 editor
2215 ._subscriptions
2216 .push(cx.observe(breakpoints, |_, _, cx| {
2217 cx.notify();
2218 }));
2219 }
2220 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2221 editor._subscriptions.extend(project_subscriptions);
2222
2223 editor._subscriptions.push(cx.subscribe_in(
2224 &cx.entity(),
2225 window,
2226 |editor, _, e: &EditorEvent, window, cx| match e {
2227 EditorEvent::ScrollPositionChanged { local, .. } => {
2228 if *local {
2229 let new_anchor = editor.scroll_manager.anchor();
2230 let snapshot = editor.snapshot(window, cx);
2231 editor.update_restoration_data(cx, move |data| {
2232 data.scroll_position = (
2233 new_anchor.top_row(&snapshot.buffer_snapshot),
2234 new_anchor.offset,
2235 );
2236 });
2237 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2238 editor.inline_blame_popover.take();
2239 }
2240 }
2241 EditorEvent::Edited { .. } => {
2242 if !vim_enabled(cx) {
2243 let (map, selections) = editor.selections.all_adjusted_display(cx);
2244 let pop_state = editor
2245 .change_list
2246 .last()
2247 .map(|previous| {
2248 previous.len() == selections.len()
2249 && previous.iter().enumerate().all(|(ix, p)| {
2250 p.to_display_point(&map).row()
2251 == selections[ix].head().row()
2252 })
2253 })
2254 .unwrap_or(false);
2255 let new_positions = selections
2256 .into_iter()
2257 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2258 .collect();
2259 editor
2260 .change_list
2261 .push_to_change_list(pop_state, new_positions);
2262 }
2263 }
2264 _ => (),
2265 },
2266 ));
2267
2268 if let Some(dap_store) = editor
2269 .project
2270 .as_ref()
2271 .map(|project| project.read(cx).dap_store())
2272 {
2273 let weak_editor = cx.weak_entity();
2274
2275 editor
2276 ._subscriptions
2277 .push(
2278 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2279 let session_entity = cx.entity();
2280 weak_editor
2281 .update(cx, |editor, cx| {
2282 editor._subscriptions.push(
2283 cx.subscribe(&session_entity, Self::on_debug_session_event),
2284 );
2285 })
2286 .ok();
2287 }),
2288 );
2289
2290 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2291 editor
2292 ._subscriptions
2293 .push(cx.subscribe(&session, Self::on_debug_session_event));
2294 }
2295 }
2296
2297 // skip adding the initial selection to selection history
2298 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2299 editor.end_selection(window, cx);
2300 editor.selection_history.mode = SelectionHistoryMode::Normal;
2301
2302 editor.scroll_manager.show_scrollbars(window, cx);
2303 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2304
2305 if full_mode {
2306 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2307 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2308
2309 if editor.git_blame_inline_enabled {
2310 editor.start_git_blame_inline(false, window, cx);
2311 }
2312
2313 editor.go_to_active_debug_line(window, cx);
2314
2315 if let Some(buffer) = buffer.read(cx).as_singleton()
2316 && let Some(project) = editor.project()
2317 {
2318 let handle = project.update(cx, |project, cx| {
2319 project.register_buffer_with_language_servers(&buffer, cx)
2320 });
2321 editor
2322 .registered_buffers
2323 .insert(buffer.read(cx).remote_id(), handle);
2324 }
2325
2326 editor.minimap =
2327 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2328 editor.colors = Some(LspColorData::new(cx));
2329 editor.update_lsp_data(false, None, window, cx);
2330 }
2331
2332 if editor.mode.is_full() {
2333 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2334 }
2335
2336 editor
2337 }
2338
2339 pub fn deploy_mouse_context_menu(
2340 &mut self,
2341 position: gpui::Point<Pixels>,
2342 context_menu: Entity<ContextMenu>,
2343 window: &mut Window,
2344 cx: &mut Context<Self>,
2345 ) {
2346 self.mouse_context_menu = Some(MouseContextMenu::new(
2347 self,
2348 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2349 context_menu,
2350 window,
2351 cx,
2352 ));
2353 }
2354
2355 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2356 self.mouse_context_menu
2357 .as_ref()
2358 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2359 }
2360
2361 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2362 if self
2363 .selections
2364 .pending
2365 .as_ref()
2366 .is_some_and(|pending_selection| {
2367 let snapshot = self.buffer().read(cx).snapshot(cx);
2368 pending_selection
2369 .selection
2370 .range()
2371 .includes(range, &snapshot)
2372 })
2373 {
2374 return true;
2375 }
2376
2377 self.selections
2378 .disjoint_in_range::<usize>(range.clone(), cx)
2379 .into_iter()
2380 .any(|selection| {
2381 // This is needed to cover a corner case, if we just check for an existing
2382 // selection in the fold range, having a cursor at the start of the fold
2383 // marks it as selected. Non-empty selections don't cause this.
2384 let length = selection.end - selection.start;
2385 length > 0
2386 })
2387 }
2388
2389 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2390 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2391 }
2392
2393 fn key_context_internal(
2394 &self,
2395 has_active_edit_prediction: bool,
2396 window: &Window,
2397 cx: &App,
2398 ) -> KeyContext {
2399 let mut key_context = KeyContext::new_with_defaults();
2400 key_context.add("Editor");
2401 let mode = match self.mode {
2402 EditorMode::SingleLine => "single_line",
2403 EditorMode::AutoHeight { .. } => "auto_height",
2404 EditorMode::Minimap { .. } => "minimap",
2405 EditorMode::Full { .. } => "full",
2406 };
2407
2408 if EditorSettings::jupyter_enabled(cx) {
2409 key_context.add("jupyter");
2410 }
2411
2412 key_context.set("mode", mode);
2413 if self.pending_rename.is_some() {
2414 key_context.add("renaming");
2415 }
2416
2417 match self.context_menu.borrow().as_ref() {
2418 Some(CodeContextMenu::Completions(menu)) => {
2419 if menu.visible() {
2420 key_context.add("menu");
2421 key_context.add("showing_completions");
2422 }
2423 }
2424 Some(CodeContextMenu::CodeActions(menu)) => {
2425 if menu.visible() {
2426 key_context.add("menu");
2427 key_context.add("showing_code_actions")
2428 }
2429 }
2430 None => {}
2431 }
2432
2433 if self.signature_help_state.has_multiple_signatures() {
2434 key_context.add("showing_signature_help");
2435 }
2436
2437 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2438 if !self.focus_handle(cx).contains_focused(window, cx)
2439 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2440 {
2441 for addon in self.addons.values() {
2442 addon.extend_key_context(&mut key_context, cx)
2443 }
2444 }
2445
2446 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2447 if let Some(extension) = singleton_buffer
2448 .read(cx)
2449 .file()
2450 .and_then(|file| file.path().extension()?.to_str())
2451 {
2452 key_context.set("extension", extension.to_string());
2453 }
2454 } else {
2455 key_context.add("multibuffer");
2456 }
2457
2458 if has_active_edit_prediction {
2459 if self.edit_prediction_in_conflict() {
2460 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2461 } else {
2462 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2463 key_context.add("copilot_suggestion");
2464 }
2465 }
2466
2467 if self.selection_mark_mode {
2468 key_context.add("selection_mode");
2469 }
2470
2471 key_context
2472 }
2473
2474 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2475 if self.mouse_cursor_hidden {
2476 self.mouse_cursor_hidden = false;
2477 cx.notify();
2478 }
2479 }
2480
2481 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2482 let hide_mouse_cursor = match origin {
2483 HideMouseCursorOrigin::TypingAction => {
2484 matches!(
2485 self.hide_mouse_mode,
2486 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2487 )
2488 }
2489 HideMouseCursorOrigin::MovementAction => {
2490 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2491 }
2492 };
2493 if self.mouse_cursor_hidden != hide_mouse_cursor {
2494 self.mouse_cursor_hidden = hide_mouse_cursor;
2495 cx.notify();
2496 }
2497 }
2498
2499 pub fn edit_prediction_in_conflict(&self) -> bool {
2500 if !self.show_edit_predictions_in_menu() {
2501 return false;
2502 }
2503
2504 let showing_completions = self
2505 .context_menu
2506 .borrow()
2507 .as_ref()
2508 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2509
2510 showing_completions
2511 || self.edit_prediction_requires_modifier()
2512 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2513 // bindings to insert tab characters.
2514 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2515 }
2516
2517 pub fn accept_edit_prediction_keybind(
2518 &self,
2519 accept_partial: bool,
2520 window: &Window,
2521 cx: &App,
2522 ) -> AcceptEditPredictionBinding {
2523 let key_context = self.key_context_internal(true, window, cx);
2524 let in_conflict = self.edit_prediction_in_conflict();
2525
2526 let bindings = if accept_partial {
2527 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2528 } else {
2529 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2530 };
2531
2532 // TODO: if the binding contains multiple keystrokes, display all of them, not
2533 // just the first one.
2534 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2535 !in_conflict
2536 || binding
2537 .keystrokes()
2538 .first()
2539 .is_some_and(|keystroke| keystroke.modifiers.modified())
2540 }))
2541 }
2542
2543 pub fn new_file(
2544 workspace: &mut Workspace,
2545 _: &workspace::NewFile,
2546 window: &mut Window,
2547 cx: &mut Context<Workspace>,
2548 ) {
2549 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2550 "Failed to create buffer",
2551 window,
2552 cx,
2553 |e, _, _| match e.error_code() {
2554 ErrorCode::RemoteUpgradeRequired => Some(format!(
2555 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2556 e.error_tag("required").unwrap_or("the latest version")
2557 )),
2558 _ => None,
2559 },
2560 );
2561 }
2562
2563 pub fn new_in_workspace(
2564 workspace: &mut Workspace,
2565 window: &mut Window,
2566 cx: &mut Context<Workspace>,
2567 ) -> Task<Result<Entity<Editor>>> {
2568 let project = workspace.project().clone();
2569 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2570
2571 cx.spawn_in(window, async move |workspace, cx| {
2572 let buffer = create.await?;
2573 workspace.update_in(cx, |workspace, window, cx| {
2574 let editor =
2575 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2576 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2577 editor
2578 })
2579 })
2580 }
2581
2582 fn new_file_vertical(
2583 workspace: &mut Workspace,
2584 _: &workspace::NewFileSplitVertical,
2585 window: &mut Window,
2586 cx: &mut Context<Workspace>,
2587 ) {
2588 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2589 }
2590
2591 fn new_file_horizontal(
2592 workspace: &mut Workspace,
2593 _: &workspace::NewFileSplitHorizontal,
2594 window: &mut Window,
2595 cx: &mut Context<Workspace>,
2596 ) {
2597 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2598 }
2599
2600 fn new_file_in_direction(
2601 workspace: &mut Workspace,
2602 direction: SplitDirection,
2603 window: &mut Window,
2604 cx: &mut Context<Workspace>,
2605 ) {
2606 let project = workspace.project().clone();
2607 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2608
2609 cx.spawn_in(window, async move |workspace, cx| {
2610 let buffer = create.await?;
2611 workspace.update_in(cx, move |workspace, window, cx| {
2612 workspace.split_item(
2613 direction,
2614 Box::new(
2615 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2616 ),
2617 window,
2618 cx,
2619 )
2620 })?;
2621 anyhow::Ok(())
2622 })
2623 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2624 match e.error_code() {
2625 ErrorCode::RemoteUpgradeRequired => Some(format!(
2626 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2627 e.error_tag("required").unwrap_or("the latest version")
2628 )),
2629 _ => None,
2630 }
2631 });
2632 }
2633
2634 pub fn leader_id(&self) -> Option<CollaboratorId> {
2635 self.leader_id
2636 }
2637
2638 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2639 &self.buffer
2640 }
2641
2642 pub fn project(&self) -> Option<&Entity<Project>> {
2643 self.project.as_ref()
2644 }
2645
2646 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2647 self.workspace.as_ref()?.0.upgrade()
2648 }
2649
2650 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2651 self.buffer().read(cx).title(cx)
2652 }
2653
2654 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2655 let git_blame_gutter_max_author_length = self
2656 .render_git_blame_gutter(cx)
2657 .then(|| {
2658 if let Some(blame) = self.blame.as_ref() {
2659 let max_author_length =
2660 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2661 Some(max_author_length)
2662 } else {
2663 None
2664 }
2665 })
2666 .flatten();
2667
2668 EditorSnapshot {
2669 mode: self.mode.clone(),
2670 show_gutter: self.show_gutter,
2671 show_line_numbers: self.show_line_numbers,
2672 show_git_diff_gutter: self.show_git_diff_gutter,
2673 show_code_actions: self.show_code_actions,
2674 show_runnables: self.show_runnables,
2675 show_breakpoints: self.show_breakpoints,
2676 git_blame_gutter_max_author_length,
2677 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2678 scroll_anchor: self.scroll_manager.anchor(),
2679 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2680 placeholder_text: self.placeholder_text.clone(),
2681 is_focused: self.focus_handle.is_focused(window),
2682 current_line_highlight: self
2683 .current_line_highlight
2684 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2685 gutter_hovered: self.gutter_hovered,
2686 }
2687 }
2688
2689 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2690 self.buffer.read(cx).language_at(point, cx)
2691 }
2692
2693 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2694 self.buffer.read(cx).read(cx).file_at(point).cloned()
2695 }
2696
2697 pub fn active_excerpt(
2698 &self,
2699 cx: &App,
2700 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2701 self.buffer
2702 .read(cx)
2703 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2704 }
2705
2706 pub fn mode(&self) -> &EditorMode {
2707 &self.mode
2708 }
2709
2710 pub fn set_mode(&mut self, mode: EditorMode) {
2711 self.mode = mode;
2712 }
2713
2714 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2715 self.collaboration_hub.as_deref()
2716 }
2717
2718 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2719 self.collaboration_hub = Some(hub);
2720 }
2721
2722 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2723 self.in_project_search = in_project_search;
2724 }
2725
2726 pub fn set_custom_context_menu(
2727 &mut self,
2728 f: impl 'static
2729 + Fn(
2730 &mut Self,
2731 DisplayPoint,
2732 &mut Window,
2733 &mut Context<Self>,
2734 ) -> Option<Entity<ui::ContextMenu>>,
2735 ) {
2736 self.custom_context_menu = Some(Box::new(f))
2737 }
2738
2739 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2740 self.completion_provider = provider;
2741 }
2742
2743 #[cfg(any(test, feature = "test-support"))]
2744 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2745 self.completion_provider.clone()
2746 }
2747
2748 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2749 self.semantics_provider.clone()
2750 }
2751
2752 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2753 self.semantics_provider = provider;
2754 }
2755
2756 pub fn set_edit_prediction_provider<T>(
2757 &mut self,
2758 provider: Option<Entity<T>>,
2759 window: &mut Window,
2760 cx: &mut Context<Self>,
2761 ) where
2762 T: EditPredictionProvider,
2763 {
2764 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2765 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2766 if this.focus_handle.is_focused(window) {
2767 this.update_visible_edit_prediction(window, cx);
2768 }
2769 }),
2770 provider: Arc::new(provider),
2771 });
2772 self.update_edit_prediction_settings(cx);
2773 self.refresh_edit_prediction(false, false, window, cx);
2774 }
2775
2776 pub fn placeholder_text(&self) -> Option<&str> {
2777 self.placeholder_text.as_deref()
2778 }
2779
2780 pub fn set_placeholder_text(
2781 &mut self,
2782 placeholder_text: impl Into<Arc<str>>,
2783 cx: &mut Context<Self>,
2784 ) {
2785 let placeholder_text = Some(placeholder_text.into());
2786 if self.placeholder_text != placeholder_text {
2787 self.placeholder_text = placeholder_text;
2788 cx.notify();
2789 }
2790 }
2791
2792 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2793 self.cursor_shape = cursor_shape;
2794
2795 // Disrupt blink for immediate user feedback that the cursor shape has changed
2796 self.blink_manager.update(cx, BlinkManager::show_cursor);
2797
2798 cx.notify();
2799 }
2800
2801 pub fn set_current_line_highlight(
2802 &mut self,
2803 current_line_highlight: Option<CurrentLineHighlight>,
2804 ) {
2805 self.current_line_highlight = current_line_highlight;
2806 }
2807
2808 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2809 self.collapse_matches = collapse_matches;
2810 }
2811
2812 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2813 let buffers = self.buffer.read(cx).all_buffers();
2814 let Some(project) = self.project.as_ref() else {
2815 return;
2816 };
2817 project.update(cx, |project, cx| {
2818 for buffer in buffers {
2819 self.registered_buffers
2820 .entry(buffer.read(cx).remote_id())
2821 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2822 }
2823 })
2824 }
2825
2826 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2827 if self.collapse_matches {
2828 return range.start..range.start;
2829 }
2830 range.clone()
2831 }
2832
2833 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2834 if self.display_map.read(cx).clip_at_line_ends != clip {
2835 self.display_map
2836 .update(cx, |map, _| map.clip_at_line_ends = clip);
2837 }
2838 }
2839
2840 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2841 self.input_enabled = input_enabled;
2842 }
2843
2844 pub fn set_edit_predictions_hidden_for_vim_mode(
2845 &mut self,
2846 hidden: bool,
2847 window: &mut Window,
2848 cx: &mut Context<Self>,
2849 ) {
2850 if hidden != self.edit_predictions_hidden_for_vim_mode {
2851 self.edit_predictions_hidden_for_vim_mode = hidden;
2852 if hidden {
2853 self.update_visible_edit_prediction(window, cx);
2854 } else {
2855 self.refresh_edit_prediction(true, false, window, cx);
2856 }
2857 }
2858 }
2859
2860 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2861 self.menu_edit_predictions_policy = value;
2862 }
2863
2864 pub fn set_autoindent(&mut self, autoindent: bool) {
2865 if autoindent {
2866 self.autoindent_mode = Some(AutoindentMode::EachLine);
2867 } else {
2868 self.autoindent_mode = None;
2869 }
2870 }
2871
2872 pub fn read_only(&self, cx: &App) -> bool {
2873 self.read_only || self.buffer.read(cx).read_only()
2874 }
2875
2876 pub fn set_read_only(&mut self, read_only: bool) {
2877 self.read_only = read_only;
2878 }
2879
2880 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2881 self.use_autoclose = autoclose;
2882 }
2883
2884 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2885 self.use_auto_surround = auto_surround;
2886 }
2887
2888 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2889 self.auto_replace_emoji_shortcode = auto_replace;
2890 }
2891
2892 pub fn toggle_edit_predictions(
2893 &mut self,
2894 _: &ToggleEditPrediction,
2895 window: &mut Window,
2896 cx: &mut Context<Self>,
2897 ) {
2898 if self.show_edit_predictions_override.is_some() {
2899 self.set_show_edit_predictions(None, window, cx);
2900 } else {
2901 let show_edit_predictions = !self.edit_predictions_enabled();
2902 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2903 }
2904 }
2905
2906 pub fn set_show_edit_predictions(
2907 &mut self,
2908 show_edit_predictions: Option<bool>,
2909 window: &mut Window,
2910 cx: &mut Context<Self>,
2911 ) {
2912 self.show_edit_predictions_override = show_edit_predictions;
2913 self.update_edit_prediction_settings(cx);
2914
2915 if let Some(false) = show_edit_predictions {
2916 self.discard_edit_prediction(false, cx);
2917 } else {
2918 self.refresh_edit_prediction(false, true, window, cx);
2919 }
2920 }
2921
2922 fn edit_predictions_disabled_in_scope(
2923 &self,
2924 buffer: &Entity<Buffer>,
2925 buffer_position: language::Anchor,
2926 cx: &App,
2927 ) -> bool {
2928 let snapshot = buffer.read(cx).snapshot();
2929 let settings = snapshot.settings_at(buffer_position, cx);
2930
2931 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2932 return false;
2933 };
2934
2935 scope.override_name().is_some_and(|scope_name| {
2936 settings
2937 .edit_predictions_disabled_in
2938 .iter()
2939 .any(|s| s == scope_name)
2940 })
2941 }
2942
2943 pub fn set_use_modal_editing(&mut self, to: bool) {
2944 self.use_modal_editing = to;
2945 }
2946
2947 pub fn use_modal_editing(&self) -> bool {
2948 self.use_modal_editing
2949 }
2950
2951 fn selections_did_change(
2952 &mut self,
2953 local: bool,
2954 old_cursor_position: &Anchor,
2955 effects: SelectionEffects,
2956 window: &mut Window,
2957 cx: &mut Context<Self>,
2958 ) {
2959 window.invalidate_character_coordinates();
2960
2961 // Copy selections to primary selection buffer
2962 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2963 if local {
2964 let selections = self.selections.all::<usize>(cx);
2965 let buffer_handle = self.buffer.read(cx).read(cx);
2966
2967 let mut text = String::new();
2968 for (index, selection) in selections.iter().enumerate() {
2969 let text_for_selection = buffer_handle
2970 .text_for_range(selection.start..selection.end)
2971 .collect::<String>();
2972
2973 text.push_str(&text_for_selection);
2974 if index != selections.len() - 1 {
2975 text.push('\n');
2976 }
2977 }
2978
2979 if !text.is_empty() {
2980 cx.write_to_primary(ClipboardItem::new_string(text));
2981 }
2982 }
2983
2984 let selection_anchors = self.selections.disjoint_anchors();
2985
2986 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2987 self.buffer.update(cx, |buffer, cx| {
2988 buffer.set_active_selections(
2989 &selection_anchors,
2990 self.selections.line_mode,
2991 self.cursor_shape,
2992 cx,
2993 )
2994 });
2995 }
2996 let display_map = self
2997 .display_map
2998 .update(cx, |display_map, cx| display_map.snapshot(cx));
2999 let buffer = &display_map.buffer_snapshot;
3000 if self.selections.count() == 1 {
3001 self.add_selections_state = None;
3002 }
3003 self.select_next_state = None;
3004 self.select_prev_state = None;
3005 self.select_syntax_node_history.try_clear();
3006 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3007 self.snippet_stack.invalidate(&selection_anchors, buffer);
3008 self.take_rename(false, window, cx);
3009
3010 let newest_selection = self.selections.newest_anchor();
3011 let new_cursor_position = newest_selection.head();
3012 let selection_start = newest_selection.start;
3013
3014 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3015 self.push_to_nav_history(
3016 *old_cursor_position,
3017 Some(new_cursor_position.to_point(buffer)),
3018 false,
3019 effects.nav_history == Some(true),
3020 cx,
3021 );
3022 }
3023
3024 if local {
3025 if let Some(buffer_id) = new_cursor_position.buffer_id
3026 && !self.registered_buffers.contains_key(&buffer_id)
3027 && let Some(project) = self.project.as_ref()
3028 {
3029 project.update(cx, |project, cx| {
3030 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3031 return;
3032 };
3033 self.registered_buffers.insert(
3034 buffer_id,
3035 project.register_buffer_with_language_servers(&buffer, cx),
3036 );
3037 })
3038 }
3039
3040 let mut context_menu = self.context_menu.borrow_mut();
3041 let completion_menu = match context_menu.as_ref() {
3042 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3043 Some(CodeContextMenu::CodeActions(_)) => {
3044 *context_menu = None;
3045 None
3046 }
3047 None => None,
3048 };
3049 let completion_position = completion_menu.map(|menu| menu.initial_position);
3050 drop(context_menu);
3051
3052 if effects.completions
3053 && let Some(completion_position) = completion_position
3054 {
3055 let start_offset = selection_start.to_offset(buffer);
3056 let position_matches = start_offset == completion_position.to_offset(buffer);
3057 let continue_showing = if position_matches {
3058 if self.snippet_stack.is_empty() {
3059 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3060 } else {
3061 // Snippet choices can be shown even when the cursor is in whitespace.
3062 // Dismissing the menu with actions like backspace is handled by
3063 // invalidation regions.
3064 true
3065 }
3066 } else {
3067 false
3068 };
3069
3070 if continue_showing {
3071 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3072 } else {
3073 self.hide_context_menu(window, cx);
3074 }
3075 }
3076
3077 hide_hover(self, cx);
3078
3079 if old_cursor_position.to_display_point(&display_map).row()
3080 != new_cursor_position.to_display_point(&display_map).row()
3081 {
3082 self.available_code_actions.take();
3083 }
3084 self.refresh_code_actions(window, cx);
3085 self.refresh_document_highlights(cx);
3086 self.refresh_selected_text_highlights(false, window, cx);
3087 refresh_matching_bracket_highlights(self, window, cx);
3088 self.update_visible_edit_prediction(window, cx);
3089 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3090 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3091 self.inline_blame_popover.take();
3092 if self.git_blame_inline_enabled {
3093 self.start_inline_blame_timer(window, cx);
3094 }
3095 }
3096
3097 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3098 cx.emit(EditorEvent::SelectionsChanged { local });
3099
3100 let selections = &self.selections.disjoint;
3101 if selections.len() == 1 {
3102 cx.emit(SearchEvent::ActiveMatchChanged)
3103 }
3104 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3105 let inmemory_selections = selections
3106 .iter()
3107 .map(|s| {
3108 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3109 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3110 })
3111 .collect();
3112 self.update_restoration_data(cx, |data| {
3113 data.selections = inmemory_selections;
3114 });
3115
3116 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3117 && let Some(workspace_id) =
3118 self.workspace.as_ref().and_then(|workspace| workspace.1)
3119 {
3120 let snapshot = self.buffer().read(cx).snapshot(cx);
3121 let selections = selections.clone();
3122 let background_executor = cx.background_executor().clone();
3123 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3124 self.serialize_selections = cx.background_spawn(async move {
3125 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3126 let db_selections = selections
3127 .iter()
3128 .map(|selection| {
3129 (
3130 selection.start.to_offset(&snapshot),
3131 selection.end.to_offset(&snapshot),
3132 )
3133 })
3134 .collect();
3135
3136 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3137 .await
3138 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3139 .log_err();
3140 });
3141 }
3142 }
3143
3144 cx.notify();
3145 }
3146
3147 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3148 use text::ToOffset as _;
3149 use text::ToPoint as _;
3150
3151 if self.mode.is_minimap()
3152 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3153 {
3154 return;
3155 }
3156
3157 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3158 return;
3159 };
3160
3161 let snapshot = singleton.read(cx).snapshot();
3162 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3163 let display_snapshot = display_map.snapshot(cx);
3164
3165 display_snapshot
3166 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3167 .map(|fold| {
3168 fold.range.start.text_anchor.to_point(&snapshot)
3169 ..fold.range.end.text_anchor.to_point(&snapshot)
3170 })
3171 .collect()
3172 });
3173 self.update_restoration_data(cx, |data| {
3174 data.folds = inmemory_folds;
3175 });
3176
3177 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3178 return;
3179 };
3180 let background_executor = cx.background_executor().clone();
3181 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3182 let db_folds = self.display_map.update(cx, |display_map, cx| {
3183 display_map
3184 .snapshot(cx)
3185 .folds_in_range(0..snapshot.len())
3186 .map(|fold| {
3187 (
3188 fold.range.start.text_anchor.to_offset(&snapshot),
3189 fold.range.end.text_anchor.to_offset(&snapshot),
3190 )
3191 })
3192 .collect()
3193 });
3194 self.serialize_folds = cx.background_spawn(async move {
3195 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3196 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3197 .await
3198 .with_context(|| {
3199 format!(
3200 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3201 )
3202 })
3203 .log_err();
3204 });
3205 }
3206
3207 pub fn sync_selections(
3208 &mut self,
3209 other: Entity<Editor>,
3210 cx: &mut Context<Self>,
3211 ) -> gpui::Subscription {
3212 let other_selections = other.read(cx).selections.disjoint.to_vec();
3213 self.selections.change_with(cx, |selections| {
3214 selections.select_anchors(other_selections);
3215 });
3216
3217 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3218 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3219 let other_selections = other.read(cx).selections.disjoint.to_vec();
3220 if other_selections.is_empty() {
3221 return;
3222 }
3223 this.selections.change_with(cx, |selections| {
3224 selections.select_anchors(other_selections);
3225 });
3226 }
3227 });
3228
3229 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3230 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3231 let these_selections = this.selections.disjoint.to_vec();
3232 if these_selections.is_empty() {
3233 return;
3234 }
3235 other.update(cx, |other_editor, cx| {
3236 other_editor.selections.change_with(cx, |selections| {
3237 selections.select_anchors(these_selections);
3238 })
3239 });
3240 }
3241 });
3242
3243 Subscription::join(other_subscription, this_subscription)
3244 }
3245
3246 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3247 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3248 /// effects of selection change occur at the end of the transaction.
3249 pub fn change_selections<R>(
3250 &mut self,
3251 effects: SelectionEffects,
3252 window: &mut Window,
3253 cx: &mut Context<Self>,
3254 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3255 ) -> R {
3256 if let Some(state) = &mut self.deferred_selection_effects_state {
3257 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3258 state.effects.completions = effects.completions;
3259 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3260 let (changed, result) = self.selections.change_with(cx, change);
3261 state.changed |= changed;
3262 return result;
3263 }
3264 let mut state = DeferredSelectionEffectsState {
3265 changed: false,
3266 effects,
3267 old_cursor_position: self.selections.newest_anchor().head(),
3268 history_entry: SelectionHistoryEntry {
3269 selections: self.selections.disjoint_anchors(),
3270 select_next_state: self.select_next_state.clone(),
3271 select_prev_state: self.select_prev_state.clone(),
3272 add_selections_state: self.add_selections_state.clone(),
3273 },
3274 };
3275 let (changed, result) = self.selections.change_with(cx, change);
3276 state.changed = state.changed || changed;
3277 if self.defer_selection_effects {
3278 self.deferred_selection_effects_state = Some(state);
3279 } else {
3280 self.apply_selection_effects(state, window, cx);
3281 }
3282 result
3283 }
3284
3285 /// Defers the effects of selection change, so that the effects of multiple calls to
3286 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3287 /// to selection history and the state of popovers based on selection position aren't
3288 /// erroneously updated.
3289 pub fn with_selection_effects_deferred<R>(
3290 &mut self,
3291 window: &mut Window,
3292 cx: &mut Context<Self>,
3293 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3294 ) -> R {
3295 let already_deferred = self.defer_selection_effects;
3296 self.defer_selection_effects = true;
3297 let result = update(self, window, cx);
3298 if !already_deferred {
3299 self.defer_selection_effects = false;
3300 if let Some(state) = self.deferred_selection_effects_state.take() {
3301 self.apply_selection_effects(state, window, cx);
3302 }
3303 }
3304 result
3305 }
3306
3307 fn apply_selection_effects(
3308 &mut self,
3309 state: DeferredSelectionEffectsState,
3310 window: &mut Window,
3311 cx: &mut Context<Self>,
3312 ) {
3313 if state.changed {
3314 self.selection_history.push(state.history_entry);
3315
3316 if let Some(autoscroll) = state.effects.scroll {
3317 self.request_autoscroll(autoscroll, cx);
3318 }
3319
3320 let old_cursor_position = &state.old_cursor_position;
3321
3322 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3323
3324 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3325 self.show_signature_help(&ShowSignatureHelp, window, cx);
3326 }
3327 }
3328 }
3329
3330 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3331 where
3332 I: IntoIterator<Item = (Range<S>, T)>,
3333 S: ToOffset,
3334 T: Into<Arc<str>>,
3335 {
3336 if self.read_only(cx) {
3337 return;
3338 }
3339
3340 self.buffer
3341 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3342 }
3343
3344 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3345 where
3346 I: IntoIterator<Item = (Range<S>, T)>,
3347 S: ToOffset,
3348 T: Into<Arc<str>>,
3349 {
3350 if self.read_only(cx) {
3351 return;
3352 }
3353
3354 self.buffer.update(cx, |buffer, cx| {
3355 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3356 });
3357 }
3358
3359 pub fn edit_with_block_indent<I, S, T>(
3360 &mut self,
3361 edits: I,
3362 original_indent_columns: Vec<Option<u32>>,
3363 cx: &mut Context<Self>,
3364 ) where
3365 I: IntoIterator<Item = (Range<S>, T)>,
3366 S: ToOffset,
3367 T: Into<Arc<str>>,
3368 {
3369 if self.read_only(cx) {
3370 return;
3371 }
3372
3373 self.buffer.update(cx, |buffer, cx| {
3374 buffer.edit(
3375 edits,
3376 Some(AutoindentMode::Block {
3377 original_indent_columns,
3378 }),
3379 cx,
3380 )
3381 });
3382 }
3383
3384 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3385 self.hide_context_menu(window, cx);
3386
3387 match phase {
3388 SelectPhase::Begin {
3389 position,
3390 add,
3391 click_count,
3392 } => self.begin_selection(position, add, click_count, window, cx),
3393 SelectPhase::BeginColumnar {
3394 position,
3395 goal_column,
3396 reset,
3397 mode,
3398 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3399 SelectPhase::Extend {
3400 position,
3401 click_count,
3402 } => self.extend_selection(position, click_count, window, cx),
3403 SelectPhase::Update {
3404 position,
3405 goal_column,
3406 scroll_delta,
3407 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3408 SelectPhase::End => self.end_selection(window, cx),
3409 }
3410 }
3411
3412 fn extend_selection(
3413 &mut self,
3414 position: DisplayPoint,
3415 click_count: usize,
3416 window: &mut Window,
3417 cx: &mut Context<Self>,
3418 ) {
3419 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3420 let tail = self.selections.newest::<usize>(cx).tail();
3421 self.begin_selection(position, false, click_count, window, cx);
3422
3423 let position = position.to_offset(&display_map, Bias::Left);
3424 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3425
3426 let mut pending_selection = self
3427 .selections
3428 .pending_anchor()
3429 .expect("extend_selection not called with pending selection");
3430 if position >= tail {
3431 pending_selection.start = tail_anchor;
3432 } else {
3433 pending_selection.end = tail_anchor;
3434 pending_selection.reversed = true;
3435 }
3436
3437 let mut pending_mode = self.selections.pending_mode().unwrap();
3438 match &mut pending_mode {
3439 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3440 _ => {}
3441 }
3442
3443 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3444 SelectionEffects::scroll(Autoscroll::fit())
3445 } else {
3446 SelectionEffects::no_scroll()
3447 };
3448
3449 self.change_selections(effects, window, cx, |s| {
3450 s.set_pending(pending_selection, pending_mode)
3451 });
3452 }
3453
3454 fn begin_selection(
3455 &mut self,
3456 position: DisplayPoint,
3457 add: bool,
3458 click_count: usize,
3459 window: &mut Window,
3460 cx: &mut Context<Self>,
3461 ) {
3462 if !self.focus_handle.is_focused(window) {
3463 self.last_focused_descendant = None;
3464 window.focus(&self.focus_handle);
3465 }
3466
3467 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3468 let buffer = &display_map.buffer_snapshot;
3469 let position = display_map.clip_point(position, Bias::Left);
3470
3471 let start;
3472 let end;
3473 let mode;
3474 let mut auto_scroll;
3475 match click_count {
3476 1 => {
3477 start = buffer.anchor_before(position.to_point(&display_map));
3478 end = start;
3479 mode = SelectMode::Character;
3480 auto_scroll = true;
3481 }
3482 2 => {
3483 let position = display_map
3484 .clip_point(position, Bias::Left)
3485 .to_offset(&display_map, Bias::Left);
3486 let (range, _) = buffer.surrounding_word(position, false);
3487 start = buffer.anchor_before(range.start);
3488 end = buffer.anchor_before(range.end);
3489 mode = SelectMode::Word(start..end);
3490 auto_scroll = true;
3491 }
3492 3 => {
3493 let position = display_map
3494 .clip_point(position, Bias::Left)
3495 .to_point(&display_map);
3496 let line_start = display_map.prev_line_boundary(position).0;
3497 let next_line_start = buffer.clip_point(
3498 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3499 Bias::Left,
3500 );
3501 start = buffer.anchor_before(line_start);
3502 end = buffer.anchor_before(next_line_start);
3503 mode = SelectMode::Line(start..end);
3504 auto_scroll = true;
3505 }
3506 _ => {
3507 start = buffer.anchor_before(0);
3508 end = buffer.anchor_before(buffer.len());
3509 mode = SelectMode::All;
3510 auto_scroll = false;
3511 }
3512 }
3513 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3514
3515 let point_to_delete: Option<usize> = {
3516 let selected_points: Vec<Selection<Point>> =
3517 self.selections.disjoint_in_range(start..end, cx);
3518
3519 if !add || click_count > 1 {
3520 None
3521 } else if !selected_points.is_empty() {
3522 Some(selected_points[0].id)
3523 } else {
3524 let clicked_point_already_selected =
3525 self.selections.disjoint.iter().find(|selection| {
3526 selection.start.to_point(buffer) == start.to_point(buffer)
3527 || selection.end.to_point(buffer) == end.to_point(buffer)
3528 });
3529
3530 clicked_point_already_selected.map(|selection| selection.id)
3531 }
3532 };
3533
3534 let selections_count = self.selections.count();
3535 let effects = if auto_scroll {
3536 SelectionEffects::default()
3537 } else {
3538 SelectionEffects::no_scroll()
3539 };
3540
3541 self.change_selections(effects, window, cx, |s| {
3542 if let Some(point_to_delete) = point_to_delete {
3543 s.delete(point_to_delete);
3544
3545 if selections_count == 1 {
3546 s.set_pending_anchor_range(start..end, mode);
3547 }
3548 } else {
3549 if !add {
3550 s.clear_disjoint();
3551 }
3552
3553 s.set_pending_anchor_range(start..end, mode);
3554 }
3555 });
3556 }
3557
3558 fn begin_columnar_selection(
3559 &mut self,
3560 position: DisplayPoint,
3561 goal_column: u32,
3562 reset: bool,
3563 mode: ColumnarMode,
3564 window: &mut Window,
3565 cx: &mut Context<Self>,
3566 ) {
3567 if !self.focus_handle.is_focused(window) {
3568 self.last_focused_descendant = None;
3569 window.focus(&self.focus_handle);
3570 }
3571
3572 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3573
3574 if reset {
3575 let pointer_position = display_map
3576 .buffer_snapshot
3577 .anchor_before(position.to_point(&display_map));
3578
3579 self.change_selections(
3580 SelectionEffects::scroll(Autoscroll::newest()),
3581 window,
3582 cx,
3583 |s| {
3584 s.clear_disjoint();
3585 s.set_pending_anchor_range(
3586 pointer_position..pointer_position,
3587 SelectMode::Character,
3588 );
3589 },
3590 );
3591 };
3592
3593 let tail = self.selections.newest::<Point>(cx).tail();
3594 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3595 self.columnar_selection_state = match mode {
3596 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3597 selection_tail: selection_anchor,
3598 display_point: if reset {
3599 if position.column() != goal_column {
3600 Some(DisplayPoint::new(position.row(), goal_column))
3601 } else {
3602 None
3603 }
3604 } else {
3605 None
3606 },
3607 }),
3608 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3609 selection_tail: selection_anchor,
3610 }),
3611 };
3612
3613 if !reset {
3614 self.select_columns(position, goal_column, &display_map, window, cx);
3615 }
3616 }
3617
3618 fn update_selection(
3619 &mut self,
3620 position: DisplayPoint,
3621 goal_column: u32,
3622 scroll_delta: gpui::Point<f32>,
3623 window: &mut Window,
3624 cx: &mut Context<Self>,
3625 ) {
3626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3627
3628 if self.columnar_selection_state.is_some() {
3629 self.select_columns(position, goal_column, &display_map, window, cx);
3630 } else if let Some(mut pending) = self.selections.pending_anchor() {
3631 let buffer = &display_map.buffer_snapshot;
3632 let head;
3633 let tail;
3634 let mode = self.selections.pending_mode().unwrap();
3635 match &mode {
3636 SelectMode::Character => {
3637 head = position.to_point(&display_map);
3638 tail = pending.tail().to_point(buffer);
3639 }
3640 SelectMode::Word(original_range) => {
3641 let offset = display_map
3642 .clip_point(position, Bias::Left)
3643 .to_offset(&display_map, Bias::Left);
3644 let original_range = original_range.to_offset(buffer);
3645
3646 let head_offset = if buffer.is_inside_word(offset, false)
3647 || original_range.contains(&offset)
3648 {
3649 let (word_range, _) = buffer.surrounding_word(offset, false);
3650 if word_range.start < original_range.start {
3651 word_range.start
3652 } else {
3653 word_range.end
3654 }
3655 } else {
3656 offset
3657 };
3658
3659 head = head_offset.to_point(buffer);
3660 if head_offset <= original_range.start {
3661 tail = original_range.end.to_point(buffer);
3662 } else {
3663 tail = original_range.start.to_point(buffer);
3664 }
3665 }
3666 SelectMode::Line(original_range) => {
3667 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3668
3669 let position = display_map
3670 .clip_point(position, Bias::Left)
3671 .to_point(&display_map);
3672 let line_start = display_map.prev_line_boundary(position).0;
3673 let next_line_start = buffer.clip_point(
3674 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3675 Bias::Left,
3676 );
3677
3678 if line_start < original_range.start {
3679 head = line_start
3680 } else {
3681 head = next_line_start
3682 }
3683
3684 if head <= original_range.start {
3685 tail = original_range.end;
3686 } else {
3687 tail = original_range.start;
3688 }
3689 }
3690 SelectMode::All => {
3691 return;
3692 }
3693 };
3694
3695 if head < tail {
3696 pending.start = buffer.anchor_before(head);
3697 pending.end = buffer.anchor_before(tail);
3698 pending.reversed = true;
3699 } else {
3700 pending.start = buffer.anchor_before(tail);
3701 pending.end = buffer.anchor_before(head);
3702 pending.reversed = false;
3703 }
3704
3705 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3706 s.set_pending(pending, mode);
3707 });
3708 } else {
3709 log::error!("update_selection dispatched with no pending selection");
3710 return;
3711 }
3712
3713 self.apply_scroll_delta(scroll_delta, window, cx);
3714 cx.notify();
3715 }
3716
3717 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3718 self.columnar_selection_state.take();
3719 if self.selections.pending_anchor().is_some() {
3720 let selections = self.selections.all::<usize>(cx);
3721 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3722 s.select(selections);
3723 s.clear_pending();
3724 });
3725 }
3726 }
3727
3728 fn select_columns(
3729 &mut self,
3730 head: DisplayPoint,
3731 goal_column: u32,
3732 display_map: &DisplaySnapshot,
3733 window: &mut Window,
3734 cx: &mut Context<Self>,
3735 ) {
3736 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3737 return;
3738 };
3739
3740 let tail = match columnar_state {
3741 ColumnarSelectionState::FromMouse {
3742 selection_tail,
3743 display_point,
3744 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3745 ColumnarSelectionState::FromSelection { selection_tail } => {
3746 selection_tail.to_display_point(display_map)
3747 }
3748 };
3749
3750 let start_row = cmp::min(tail.row(), head.row());
3751 let end_row = cmp::max(tail.row(), head.row());
3752 let start_column = cmp::min(tail.column(), goal_column);
3753 let end_column = cmp::max(tail.column(), goal_column);
3754 let reversed = start_column < tail.column();
3755
3756 let selection_ranges = (start_row.0..=end_row.0)
3757 .map(DisplayRow)
3758 .filter_map(|row| {
3759 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3760 || start_column <= display_map.line_len(row))
3761 && !display_map.is_block_line(row)
3762 {
3763 let start = display_map
3764 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3765 .to_point(display_map);
3766 let end = display_map
3767 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3768 .to_point(display_map);
3769 if reversed {
3770 Some(end..start)
3771 } else {
3772 Some(start..end)
3773 }
3774 } else {
3775 None
3776 }
3777 })
3778 .collect::<Vec<_>>();
3779
3780 let ranges = match columnar_state {
3781 ColumnarSelectionState::FromMouse { .. } => {
3782 let mut non_empty_ranges = selection_ranges
3783 .iter()
3784 .filter(|selection_range| selection_range.start != selection_range.end)
3785 .peekable();
3786 if non_empty_ranges.peek().is_some() {
3787 non_empty_ranges.cloned().collect()
3788 } else {
3789 selection_ranges
3790 }
3791 }
3792 _ => selection_ranges,
3793 };
3794
3795 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3796 s.select_ranges(ranges);
3797 });
3798 cx.notify();
3799 }
3800
3801 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3802 self.selections
3803 .all_adjusted(cx)
3804 .iter()
3805 .any(|selection| !selection.is_empty())
3806 }
3807
3808 pub fn has_pending_nonempty_selection(&self) -> bool {
3809 let pending_nonempty_selection = match self.selections.pending_anchor() {
3810 Some(Selection { start, end, .. }) => start != end,
3811 None => false,
3812 };
3813
3814 pending_nonempty_selection
3815 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3816 }
3817
3818 pub fn has_pending_selection(&self) -> bool {
3819 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3820 }
3821
3822 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3823 self.selection_mark_mode = false;
3824 self.selection_drag_state = SelectionDragState::None;
3825
3826 if self.clear_expanded_diff_hunks(cx) {
3827 cx.notify();
3828 return;
3829 }
3830 if self.dismiss_menus_and_popups(true, window, cx) {
3831 return;
3832 }
3833
3834 if self.mode.is_full()
3835 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3836 {
3837 return;
3838 }
3839
3840 cx.propagate();
3841 }
3842
3843 pub fn dismiss_menus_and_popups(
3844 &mut self,
3845 is_user_requested: bool,
3846 window: &mut Window,
3847 cx: &mut Context<Self>,
3848 ) -> bool {
3849 if self.take_rename(false, window, cx).is_some() {
3850 return true;
3851 }
3852
3853 if hide_hover(self, cx) {
3854 return true;
3855 }
3856
3857 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3858 return true;
3859 }
3860
3861 if self.hide_context_menu(window, cx).is_some() {
3862 return true;
3863 }
3864
3865 if self.mouse_context_menu.take().is_some() {
3866 return true;
3867 }
3868
3869 if is_user_requested && self.discard_edit_prediction(true, cx) {
3870 return true;
3871 }
3872
3873 if self.snippet_stack.pop().is_some() {
3874 return true;
3875 }
3876
3877 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3878 self.dismiss_diagnostics(cx);
3879 return true;
3880 }
3881
3882 false
3883 }
3884
3885 fn linked_editing_ranges_for(
3886 &self,
3887 selection: Range<text::Anchor>,
3888 cx: &App,
3889 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3890 if self.linked_edit_ranges.is_empty() {
3891 return None;
3892 }
3893 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3894 selection.end.buffer_id.and_then(|end_buffer_id| {
3895 if selection.start.buffer_id != Some(end_buffer_id) {
3896 return None;
3897 }
3898 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3899 let snapshot = buffer.read(cx).snapshot();
3900 self.linked_edit_ranges
3901 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3902 .map(|ranges| (ranges, snapshot, buffer))
3903 })?;
3904 use text::ToOffset as TO;
3905 // find offset from the start of current range to current cursor position
3906 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3907
3908 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3909 let start_difference = start_offset - start_byte_offset;
3910 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3911 let end_difference = end_offset - start_byte_offset;
3912 // Current range has associated linked ranges.
3913 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3914 for range in linked_ranges.iter() {
3915 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3916 let end_offset = start_offset + end_difference;
3917 let start_offset = start_offset + start_difference;
3918 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3919 continue;
3920 }
3921 if self.selections.disjoint_anchor_ranges().any(|s| {
3922 if s.start.buffer_id != selection.start.buffer_id
3923 || s.end.buffer_id != selection.end.buffer_id
3924 {
3925 return false;
3926 }
3927 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3928 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3929 }) {
3930 continue;
3931 }
3932 let start = buffer_snapshot.anchor_after(start_offset);
3933 let end = buffer_snapshot.anchor_after(end_offset);
3934 linked_edits
3935 .entry(buffer.clone())
3936 .or_default()
3937 .push(start..end);
3938 }
3939 Some(linked_edits)
3940 }
3941
3942 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3943 let text: Arc<str> = text.into();
3944
3945 if self.read_only(cx) {
3946 return;
3947 }
3948
3949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3950
3951 let selections = self.selections.all_adjusted(cx);
3952 let mut bracket_inserted = false;
3953 let mut edits = Vec::new();
3954 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3955 let mut new_selections = Vec::with_capacity(selections.len());
3956 let mut new_autoclose_regions = Vec::new();
3957 let snapshot = self.buffer.read(cx).read(cx);
3958 let mut clear_linked_edit_ranges = false;
3959
3960 for (selection, autoclose_region) in
3961 self.selections_with_autoclose_regions(selections, &snapshot)
3962 {
3963 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3964 // Determine if the inserted text matches the opening or closing
3965 // bracket of any of this language's bracket pairs.
3966 let mut bracket_pair = None;
3967 let mut is_bracket_pair_start = false;
3968 let mut is_bracket_pair_end = false;
3969 if !text.is_empty() {
3970 let mut bracket_pair_matching_end = None;
3971 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3972 // and they are removing the character that triggered IME popup.
3973 for (pair, enabled) in scope.brackets() {
3974 if !pair.close && !pair.surround {
3975 continue;
3976 }
3977
3978 if enabled && pair.start.ends_with(text.as_ref()) {
3979 let prefix_len = pair.start.len() - text.len();
3980 let preceding_text_matches_prefix = prefix_len == 0
3981 || (selection.start.column >= (prefix_len as u32)
3982 && snapshot.contains_str_at(
3983 Point::new(
3984 selection.start.row,
3985 selection.start.column - (prefix_len as u32),
3986 ),
3987 &pair.start[..prefix_len],
3988 ));
3989 if preceding_text_matches_prefix {
3990 bracket_pair = Some(pair.clone());
3991 is_bracket_pair_start = true;
3992 break;
3993 }
3994 }
3995 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3996 {
3997 // take first bracket pair matching end, but don't break in case a later bracket
3998 // pair matches start
3999 bracket_pair_matching_end = Some(pair.clone());
4000 }
4001 }
4002 if let Some(end) = bracket_pair_matching_end
4003 && bracket_pair.is_none()
4004 {
4005 bracket_pair = Some(end);
4006 is_bracket_pair_end = true;
4007 }
4008 }
4009
4010 if let Some(bracket_pair) = bracket_pair {
4011 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4012 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4013 let auto_surround =
4014 self.use_auto_surround && snapshot_settings.use_auto_surround;
4015 if selection.is_empty() {
4016 if is_bracket_pair_start {
4017 // If the inserted text is a suffix of an opening bracket and the
4018 // selection is preceded by the rest of the opening bracket, then
4019 // insert the closing bracket.
4020 let following_text_allows_autoclose = snapshot
4021 .chars_at(selection.start)
4022 .next()
4023 .is_none_or(|c| scope.should_autoclose_before(c));
4024
4025 let preceding_text_allows_autoclose = selection.start.column == 0
4026 || snapshot
4027 .reversed_chars_at(selection.start)
4028 .next()
4029 .is_none_or(|c| {
4030 bracket_pair.start != bracket_pair.end
4031 || !snapshot
4032 .char_classifier_at(selection.start)
4033 .is_word(c)
4034 });
4035
4036 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4037 && bracket_pair.start.len() == 1
4038 {
4039 let target = bracket_pair.start.chars().next().unwrap();
4040 let current_line_count = snapshot
4041 .reversed_chars_at(selection.start)
4042 .take_while(|&c| c != '\n')
4043 .filter(|&c| c == target)
4044 .count();
4045 current_line_count % 2 == 1
4046 } else {
4047 false
4048 };
4049
4050 if autoclose
4051 && bracket_pair.close
4052 && following_text_allows_autoclose
4053 && preceding_text_allows_autoclose
4054 && !is_closing_quote
4055 {
4056 let anchor = snapshot.anchor_before(selection.end);
4057 new_selections.push((selection.map(|_| anchor), text.len()));
4058 new_autoclose_regions.push((
4059 anchor,
4060 text.len(),
4061 selection.id,
4062 bracket_pair.clone(),
4063 ));
4064 edits.push((
4065 selection.range(),
4066 format!("{}{}", text, bracket_pair.end).into(),
4067 ));
4068 bracket_inserted = true;
4069 continue;
4070 }
4071 }
4072
4073 if let Some(region) = autoclose_region {
4074 // If the selection is followed by an auto-inserted closing bracket,
4075 // then don't insert that closing bracket again; just move the selection
4076 // past the closing bracket.
4077 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4078 && text.as_ref() == region.pair.end.as_str()
4079 && snapshot.contains_str_at(region.range.end, text.as_ref());
4080 if should_skip {
4081 let anchor = snapshot.anchor_after(selection.end);
4082 new_selections
4083 .push((selection.map(|_| anchor), region.pair.end.len()));
4084 continue;
4085 }
4086 }
4087
4088 let always_treat_brackets_as_autoclosed = snapshot
4089 .language_settings_at(selection.start, cx)
4090 .always_treat_brackets_as_autoclosed;
4091 if always_treat_brackets_as_autoclosed
4092 && is_bracket_pair_end
4093 && snapshot.contains_str_at(selection.end, text.as_ref())
4094 {
4095 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4096 // and the inserted text is a closing bracket and the selection is followed
4097 // by the closing bracket then move the selection past the closing bracket.
4098 let anchor = snapshot.anchor_after(selection.end);
4099 new_selections.push((selection.map(|_| anchor), text.len()));
4100 continue;
4101 }
4102 }
4103 // If an opening bracket is 1 character long and is typed while
4104 // text is selected, then surround that text with the bracket pair.
4105 else if auto_surround
4106 && bracket_pair.surround
4107 && is_bracket_pair_start
4108 && bracket_pair.start.chars().count() == 1
4109 {
4110 edits.push((selection.start..selection.start, text.clone()));
4111 edits.push((
4112 selection.end..selection.end,
4113 bracket_pair.end.as_str().into(),
4114 ));
4115 bracket_inserted = true;
4116 new_selections.push((
4117 Selection {
4118 id: selection.id,
4119 start: snapshot.anchor_after(selection.start),
4120 end: snapshot.anchor_before(selection.end),
4121 reversed: selection.reversed,
4122 goal: selection.goal,
4123 },
4124 0,
4125 ));
4126 continue;
4127 }
4128 }
4129 }
4130
4131 if self.auto_replace_emoji_shortcode
4132 && selection.is_empty()
4133 && text.as_ref().ends_with(':')
4134 && let Some(possible_emoji_short_code) =
4135 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4136 && !possible_emoji_short_code.is_empty()
4137 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4138 {
4139 let emoji_shortcode_start = Point::new(
4140 selection.start.row,
4141 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4142 );
4143
4144 // Remove shortcode from buffer
4145 edits.push((
4146 emoji_shortcode_start..selection.start,
4147 "".to_string().into(),
4148 ));
4149 new_selections.push((
4150 Selection {
4151 id: selection.id,
4152 start: snapshot.anchor_after(emoji_shortcode_start),
4153 end: snapshot.anchor_before(selection.start),
4154 reversed: selection.reversed,
4155 goal: selection.goal,
4156 },
4157 0,
4158 ));
4159
4160 // Insert emoji
4161 let selection_start_anchor = snapshot.anchor_after(selection.start);
4162 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4163 edits.push((selection.start..selection.end, emoji.to_string().into()));
4164
4165 continue;
4166 }
4167
4168 // If not handling any auto-close operation, then just replace the selected
4169 // text with the given input and move the selection to the end of the
4170 // newly inserted text.
4171 let anchor = snapshot.anchor_after(selection.end);
4172 if !self.linked_edit_ranges.is_empty() {
4173 let start_anchor = snapshot.anchor_before(selection.start);
4174
4175 let is_word_char = text.chars().next().is_none_or(|char| {
4176 let classifier = snapshot
4177 .char_classifier_at(start_anchor.to_offset(&snapshot))
4178 .ignore_punctuation(true);
4179 classifier.is_word(char)
4180 });
4181
4182 if is_word_char {
4183 if let Some(ranges) = self
4184 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4185 {
4186 for (buffer, edits) in ranges {
4187 linked_edits
4188 .entry(buffer.clone())
4189 .or_default()
4190 .extend(edits.into_iter().map(|range| (range, text.clone())));
4191 }
4192 }
4193 } else {
4194 clear_linked_edit_ranges = true;
4195 }
4196 }
4197
4198 new_selections.push((selection.map(|_| anchor), 0));
4199 edits.push((selection.start..selection.end, text.clone()));
4200 }
4201
4202 drop(snapshot);
4203
4204 self.transact(window, cx, |this, window, cx| {
4205 if clear_linked_edit_ranges {
4206 this.linked_edit_ranges.clear();
4207 }
4208 let initial_buffer_versions =
4209 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4210
4211 this.buffer.update(cx, |buffer, cx| {
4212 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4213 });
4214 for (buffer, edits) in linked_edits {
4215 buffer.update(cx, |buffer, cx| {
4216 let snapshot = buffer.snapshot();
4217 let edits = edits
4218 .into_iter()
4219 .map(|(range, text)| {
4220 use text::ToPoint as TP;
4221 let end_point = TP::to_point(&range.end, &snapshot);
4222 let start_point = TP::to_point(&range.start, &snapshot);
4223 (start_point..end_point, text)
4224 })
4225 .sorted_by_key(|(range, _)| range.start);
4226 buffer.edit(edits, None, cx);
4227 })
4228 }
4229 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4230 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4231 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4232 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4233 .zip(new_selection_deltas)
4234 .map(|(selection, delta)| Selection {
4235 id: selection.id,
4236 start: selection.start + delta,
4237 end: selection.end + delta,
4238 reversed: selection.reversed,
4239 goal: SelectionGoal::None,
4240 })
4241 .collect::<Vec<_>>();
4242
4243 let mut i = 0;
4244 for (position, delta, selection_id, pair) in new_autoclose_regions {
4245 let position = position.to_offset(&map.buffer_snapshot) + delta;
4246 let start = map.buffer_snapshot.anchor_before(position);
4247 let end = map.buffer_snapshot.anchor_after(position);
4248 while let Some(existing_state) = this.autoclose_regions.get(i) {
4249 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4250 Ordering::Less => i += 1,
4251 Ordering::Greater => break,
4252 Ordering::Equal => {
4253 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4254 Ordering::Less => i += 1,
4255 Ordering::Equal => break,
4256 Ordering::Greater => break,
4257 }
4258 }
4259 }
4260 }
4261 this.autoclose_regions.insert(
4262 i,
4263 AutocloseRegion {
4264 selection_id,
4265 range: start..end,
4266 pair,
4267 },
4268 );
4269 }
4270
4271 let had_active_edit_prediction = this.has_active_edit_prediction();
4272 this.change_selections(
4273 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4274 window,
4275 cx,
4276 |s| s.select(new_selections),
4277 );
4278
4279 if !bracket_inserted
4280 && let Some(on_type_format_task) =
4281 this.trigger_on_type_formatting(text.to_string(), window, cx)
4282 {
4283 on_type_format_task.detach_and_log_err(cx);
4284 }
4285
4286 let editor_settings = EditorSettings::get_global(cx);
4287 if bracket_inserted
4288 && (editor_settings.auto_signature_help
4289 || editor_settings.show_signature_help_after_edits)
4290 {
4291 this.show_signature_help(&ShowSignatureHelp, window, cx);
4292 }
4293
4294 let trigger_in_words =
4295 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4296 if this.hard_wrap.is_some() {
4297 let latest: Range<Point> = this.selections.newest(cx).range();
4298 if latest.is_empty()
4299 && this
4300 .buffer()
4301 .read(cx)
4302 .snapshot(cx)
4303 .line_len(MultiBufferRow(latest.start.row))
4304 == latest.start.column
4305 {
4306 this.rewrap_impl(
4307 RewrapOptions {
4308 override_language_settings: true,
4309 preserve_existing_whitespace: true,
4310 },
4311 cx,
4312 )
4313 }
4314 }
4315 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4316 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4317 this.refresh_edit_prediction(true, false, window, cx);
4318 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4319 });
4320 }
4321
4322 fn find_possible_emoji_shortcode_at_position(
4323 snapshot: &MultiBufferSnapshot,
4324 position: Point,
4325 ) -> Option<String> {
4326 let mut chars = Vec::new();
4327 let mut found_colon = false;
4328 for char in snapshot.reversed_chars_at(position).take(100) {
4329 // Found a possible emoji shortcode in the middle of the buffer
4330 if found_colon {
4331 if char.is_whitespace() {
4332 chars.reverse();
4333 return Some(chars.iter().collect());
4334 }
4335 // If the previous character is not a whitespace, we are in the middle of a word
4336 // and we only want to complete the shortcode if the word is made up of other emojis
4337 let mut containing_word = String::new();
4338 for ch in snapshot
4339 .reversed_chars_at(position)
4340 .skip(chars.len() + 1)
4341 .take(100)
4342 {
4343 if ch.is_whitespace() {
4344 break;
4345 }
4346 containing_word.push(ch);
4347 }
4348 let containing_word = containing_word.chars().rev().collect::<String>();
4349 if util::word_consists_of_emojis(containing_word.as_str()) {
4350 chars.reverse();
4351 return Some(chars.iter().collect());
4352 }
4353 }
4354
4355 if char.is_whitespace() || !char.is_ascii() {
4356 return None;
4357 }
4358 if char == ':' {
4359 found_colon = true;
4360 } else {
4361 chars.push(char);
4362 }
4363 }
4364 // Found a possible emoji shortcode at the beginning of the buffer
4365 chars.reverse();
4366 Some(chars.iter().collect())
4367 }
4368
4369 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4371 self.transact(window, cx, |this, window, cx| {
4372 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4373 let selections = this.selections.all::<usize>(cx);
4374 let multi_buffer = this.buffer.read(cx);
4375 let buffer = multi_buffer.snapshot(cx);
4376 selections
4377 .iter()
4378 .map(|selection| {
4379 let start_point = selection.start.to_point(&buffer);
4380 let mut existing_indent =
4381 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4382 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4383 let start = selection.start;
4384 let end = selection.end;
4385 let selection_is_empty = start == end;
4386 let language_scope = buffer.language_scope_at(start);
4387 let (
4388 comment_delimiter,
4389 doc_delimiter,
4390 insert_extra_newline,
4391 indent_on_newline,
4392 indent_on_extra_newline,
4393 ) = if let Some(language) = &language_scope {
4394 let mut insert_extra_newline =
4395 insert_extra_newline_brackets(&buffer, start..end, language)
4396 || insert_extra_newline_tree_sitter(&buffer, start..end);
4397
4398 // Comment extension on newline is allowed only for cursor selections
4399 let comment_delimiter = maybe!({
4400 if !selection_is_empty {
4401 return None;
4402 }
4403
4404 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4405 return None;
4406 }
4407
4408 let delimiters = language.line_comment_prefixes();
4409 let max_len_of_delimiter =
4410 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4411 let (snapshot, range) =
4412 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4413
4414 let num_of_whitespaces = snapshot
4415 .chars_for_range(range.clone())
4416 .take_while(|c| c.is_whitespace())
4417 .count();
4418 let comment_candidate = snapshot
4419 .chars_for_range(range.clone())
4420 .skip(num_of_whitespaces)
4421 .take(max_len_of_delimiter)
4422 .collect::<String>();
4423 let (delimiter, trimmed_len) = delimiters
4424 .iter()
4425 .filter_map(|delimiter| {
4426 let prefix = delimiter.trim_end();
4427 if comment_candidate.starts_with(prefix) {
4428 Some((delimiter, prefix.len()))
4429 } else {
4430 None
4431 }
4432 })
4433 .max_by_key(|(_, len)| *len)?;
4434
4435 if let Some(BlockCommentConfig {
4436 start: block_start, ..
4437 }) = language.block_comment()
4438 {
4439 let block_start_trimmed = block_start.trim_end();
4440 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4441 let line_content = snapshot
4442 .chars_for_range(range)
4443 .skip(num_of_whitespaces)
4444 .take(block_start_trimmed.len())
4445 .collect::<String>();
4446
4447 if line_content.starts_with(block_start_trimmed) {
4448 return None;
4449 }
4450 }
4451 }
4452
4453 let cursor_is_placed_after_comment_marker =
4454 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4455 if cursor_is_placed_after_comment_marker {
4456 Some(delimiter.clone())
4457 } else {
4458 None
4459 }
4460 });
4461
4462 let mut indent_on_newline = IndentSize::spaces(0);
4463 let mut indent_on_extra_newline = IndentSize::spaces(0);
4464
4465 let doc_delimiter = maybe!({
4466 if !selection_is_empty {
4467 return None;
4468 }
4469
4470 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4471 return None;
4472 }
4473
4474 let BlockCommentConfig {
4475 start: start_tag,
4476 end: end_tag,
4477 prefix: delimiter,
4478 tab_size: len,
4479 } = language.documentation_comment()?;
4480 let is_within_block_comment = buffer
4481 .language_scope_at(start_point)
4482 .is_some_and(|scope| scope.override_name() == Some("comment"));
4483 if !is_within_block_comment {
4484 return None;
4485 }
4486
4487 let (snapshot, range) =
4488 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4489
4490 let num_of_whitespaces = snapshot
4491 .chars_for_range(range.clone())
4492 .take_while(|c| c.is_whitespace())
4493 .count();
4494
4495 // 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.
4496 let column = start_point.column;
4497 let cursor_is_after_start_tag = {
4498 let start_tag_len = start_tag.len();
4499 let start_tag_line = snapshot
4500 .chars_for_range(range.clone())
4501 .skip(num_of_whitespaces)
4502 .take(start_tag_len)
4503 .collect::<String>();
4504 if start_tag_line.starts_with(start_tag.as_ref()) {
4505 num_of_whitespaces + start_tag_len <= column as usize
4506 } else {
4507 false
4508 }
4509 };
4510
4511 let cursor_is_after_delimiter = {
4512 let delimiter_trim = delimiter.trim_end();
4513 let delimiter_line = snapshot
4514 .chars_for_range(range.clone())
4515 .skip(num_of_whitespaces)
4516 .take(delimiter_trim.len())
4517 .collect::<String>();
4518 if delimiter_line.starts_with(delimiter_trim) {
4519 num_of_whitespaces + delimiter_trim.len() <= column as usize
4520 } else {
4521 false
4522 }
4523 };
4524
4525 let cursor_is_before_end_tag_if_exists = {
4526 let mut char_position = 0u32;
4527 let mut end_tag_offset = None;
4528
4529 'outer: for chunk in snapshot.text_for_range(range) {
4530 if let Some(byte_pos) = chunk.find(&**end_tag) {
4531 let chars_before_match =
4532 chunk[..byte_pos].chars().count() as u32;
4533 end_tag_offset =
4534 Some(char_position + chars_before_match);
4535 break 'outer;
4536 }
4537 char_position += chunk.chars().count() as u32;
4538 }
4539
4540 if let Some(end_tag_offset) = end_tag_offset {
4541 let cursor_is_before_end_tag = column <= end_tag_offset;
4542 if cursor_is_after_start_tag {
4543 if cursor_is_before_end_tag {
4544 insert_extra_newline = true;
4545 }
4546 let cursor_is_at_start_of_end_tag =
4547 column == end_tag_offset;
4548 if cursor_is_at_start_of_end_tag {
4549 indent_on_extra_newline.len = *len;
4550 }
4551 }
4552 cursor_is_before_end_tag
4553 } else {
4554 true
4555 }
4556 };
4557
4558 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4559 && cursor_is_before_end_tag_if_exists
4560 {
4561 if cursor_is_after_start_tag {
4562 indent_on_newline.len = *len;
4563 }
4564 Some(delimiter.clone())
4565 } else {
4566 None
4567 }
4568 });
4569
4570 (
4571 comment_delimiter,
4572 doc_delimiter,
4573 insert_extra_newline,
4574 indent_on_newline,
4575 indent_on_extra_newline,
4576 )
4577 } else {
4578 (
4579 None,
4580 None,
4581 false,
4582 IndentSize::default(),
4583 IndentSize::default(),
4584 )
4585 };
4586
4587 let prevent_auto_indent = doc_delimiter.is_some();
4588 let delimiter = comment_delimiter.or(doc_delimiter);
4589
4590 let capacity_for_delimiter =
4591 delimiter.as_deref().map(str::len).unwrap_or_default();
4592 let mut new_text = String::with_capacity(
4593 1 + capacity_for_delimiter
4594 + existing_indent.len as usize
4595 + indent_on_newline.len as usize
4596 + indent_on_extra_newline.len as usize,
4597 );
4598 new_text.push('\n');
4599 new_text.extend(existing_indent.chars());
4600 new_text.extend(indent_on_newline.chars());
4601
4602 if let Some(delimiter) = &delimiter {
4603 new_text.push_str(delimiter);
4604 }
4605
4606 if insert_extra_newline {
4607 new_text.push('\n');
4608 new_text.extend(existing_indent.chars());
4609 new_text.extend(indent_on_extra_newline.chars());
4610 }
4611
4612 let anchor = buffer.anchor_after(end);
4613 let new_selection = selection.map(|_| anchor);
4614 (
4615 ((start..end, new_text), prevent_auto_indent),
4616 (insert_extra_newline, new_selection),
4617 )
4618 })
4619 .unzip()
4620 };
4621
4622 let mut auto_indent_edits = Vec::new();
4623 let mut edits = Vec::new();
4624 for (edit, prevent_auto_indent) in edits_with_flags {
4625 if prevent_auto_indent {
4626 edits.push(edit);
4627 } else {
4628 auto_indent_edits.push(edit);
4629 }
4630 }
4631 if !edits.is_empty() {
4632 this.edit(edits, cx);
4633 }
4634 if !auto_indent_edits.is_empty() {
4635 this.edit_with_autoindent(auto_indent_edits, cx);
4636 }
4637
4638 let buffer = this.buffer.read(cx).snapshot(cx);
4639 let new_selections = selection_info
4640 .into_iter()
4641 .map(|(extra_newline_inserted, new_selection)| {
4642 let mut cursor = new_selection.end.to_point(&buffer);
4643 if extra_newline_inserted {
4644 cursor.row -= 1;
4645 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4646 }
4647 new_selection.map(|_| cursor)
4648 })
4649 .collect();
4650
4651 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4652 this.refresh_edit_prediction(true, false, window, cx);
4653 });
4654 }
4655
4656 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4657 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4658
4659 let buffer = self.buffer.read(cx);
4660 let snapshot = buffer.snapshot(cx);
4661
4662 let mut edits = Vec::new();
4663 let mut rows = Vec::new();
4664
4665 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4666 let cursor = selection.head();
4667 let row = cursor.row;
4668
4669 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4670
4671 let newline = "\n".to_string();
4672 edits.push((start_of_line..start_of_line, newline));
4673
4674 rows.push(row + rows_inserted as u32);
4675 }
4676
4677 self.transact(window, cx, |editor, window, cx| {
4678 editor.edit(edits, cx);
4679
4680 editor.change_selections(Default::default(), window, cx, |s| {
4681 let mut index = 0;
4682 s.move_cursors_with(|map, _, _| {
4683 let row = rows[index];
4684 index += 1;
4685
4686 let point = Point::new(row, 0);
4687 let boundary = map.next_line_boundary(point).1;
4688 let clipped = map.clip_point(boundary, Bias::Left);
4689
4690 (clipped, SelectionGoal::None)
4691 });
4692 });
4693
4694 let mut indent_edits = Vec::new();
4695 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4696 for row in rows {
4697 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4698 for (row, indent) in indents {
4699 if indent.len == 0 {
4700 continue;
4701 }
4702
4703 let text = match indent.kind {
4704 IndentKind::Space => " ".repeat(indent.len as usize),
4705 IndentKind::Tab => "\t".repeat(indent.len as usize),
4706 };
4707 let point = Point::new(row.0, 0);
4708 indent_edits.push((point..point, text));
4709 }
4710 }
4711 editor.edit(indent_edits, cx);
4712 });
4713 }
4714
4715 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4717
4718 let buffer = self.buffer.read(cx);
4719 let snapshot = buffer.snapshot(cx);
4720
4721 let mut edits = Vec::new();
4722 let mut rows = Vec::new();
4723 let mut rows_inserted = 0;
4724
4725 for selection in self.selections.all_adjusted(cx) {
4726 let cursor = selection.head();
4727 let row = cursor.row;
4728
4729 let point = Point::new(row + 1, 0);
4730 let start_of_line = snapshot.clip_point(point, Bias::Left);
4731
4732 let newline = "\n".to_string();
4733 edits.push((start_of_line..start_of_line, newline));
4734
4735 rows_inserted += 1;
4736 rows.push(row + rows_inserted);
4737 }
4738
4739 self.transact(window, cx, |editor, window, cx| {
4740 editor.edit(edits, cx);
4741
4742 editor.change_selections(Default::default(), window, cx, |s| {
4743 let mut index = 0;
4744 s.move_cursors_with(|map, _, _| {
4745 let row = rows[index];
4746 index += 1;
4747
4748 let point = Point::new(row, 0);
4749 let boundary = map.next_line_boundary(point).1;
4750 let clipped = map.clip_point(boundary, Bias::Left);
4751
4752 (clipped, SelectionGoal::None)
4753 });
4754 });
4755
4756 let mut indent_edits = Vec::new();
4757 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4758 for row in rows {
4759 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4760 for (row, indent) in indents {
4761 if indent.len == 0 {
4762 continue;
4763 }
4764
4765 let text = match indent.kind {
4766 IndentKind::Space => " ".repeat(indent.len as usize),
4767 IndentKind::Tab => "\t".repeat(indent.len as usize),
4768 };
4769 let point = Point::new(row.0, 0);
4770 indent_edits.push((point..point, text));
4771 }
4772 }
4773 editor.edit(indent_edits, cx);
4774 });
4775 }
4776
4777 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4778 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4779 original_indent_columns: Vec::new(),
4780 });
4781 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4782 }
4783
4784 fn insert_with_autoindent_mode(
4785 &mut self,
4786 text: &str,
4787 autoindent_mode: Option<AutoindentMode>,
4788 window: &mut Window,
4789 cx: &mut Context<Self>,
4790 ) {
4791 if self.read_only(cx) {
4792 return;
4793 }
4794
4795 let text: Arc<str> = text.into();
4796 self.transact(window, cx, |this, window, cx| {
4797 let old_selections = this.selections.all_adjusted(cx);
4798 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4799 let anchors = {
4800 let snapshot = buffer.read(cx);
4801 old_selections
4802 .iter()
4803 .map(|s| {
4804 let anchor = snapshot.anchor_after(s.head());
4805 s.map(|_| anchor)
4806 })
4807 .collect::<Vec<_>>()
4808 };
4809 buffer.edit(
4810 old_selections
4811 .iter()
4812 .map(|s| (s.start..s.end, text.clone())),
4813 autoindent_mode,
4814 cx,
4815 );
4816 anchors
4817 });
4818
4819 this.change_selections(Default::default(), window, cx, |s| {
4820 s.select_anchors(selection_anchors);
4821 });
4822
4823 cx.notify();
4824 });
4825 }
4826
4827 fn trigger_completion_on_input(
4828 &mut self,
4829 text: &str,
4830 trigger_in_words: bool,
4831 window: &mut Window,
4832 cx: &mut Context<Self>,
4833 ) {
4834 let completions_source = self
4835 .context_menu
4836 .borrow()
4837 .as_ref()
4838 .and_then(|menu| match menu {
4839 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4840 CodeContextMenu::CodeActions(_) => None,
4841 });
4842
4843 match completions_source {
4844 Some(CompletionsMenuSource::Words) => {
4845 self.show_word_completions(&ShowWordCompletions, window, cx)
4846 }
4847 Some(CompletionsMenuSource::Normal)
4848 | Some(CompletionsMenuSource::SnippetChoices)
4849 | None
4850 if self.is_completion_trigger(
4851 text,
4852 trigger_in_words,
4853 completions_source.is_some(),
4854 cx,
4855 ) =>
4856 {
4857 self.show_completions(
4858 &ShowCompletions {
4859 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4860 },
4861 window,
4862 cx,
4863 )
4864 }
4865 _ => {
4866 self.hide_context_menu(window, cx);
4867 }
4868 }
4869 }
4870
4871 fn is_completion_trigger(
4872 &self,
4873 text: &str,
4874 trigger_in_words: bool,
4875 menu_is_open: bool,
4876 cx: &mut Context<Self>,
4877 ) -> bool {
4878 let position = self.selections.newest_anchor().head();
4879 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4880 return false;
4881 };
4882
4883 if let Some(completion_provider) = &self.completion_provider {
4884 completion_provider.is_completion_trigger(
4885 &buffer,
4886 position.text_anchor,
4887 text,
4888 trigger_in_words,
4889 menu_is_open,
4890 cx,
4891 )
4892 } else {
4893 false
4894 }
4895 }
4896
4897 /// If any empty selections is touching the start of its innermost containing autoclose
4898 /// region, expand it to select the brackets.
4899 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4900 let selections = self.selections.all::<usize>(cx);
4901 let buffer = self.buffer.read(cx).read(cx);
4902 let new_selections = self
4903 .selections_with_autoclose_regions(selections, &buffer)
4904 .map(|(mut selection, region)| {
4905 if !selection.is_empty() {
4906 return selection;
4907 }
4908
4909 if let Some(region) = region {
4910 let mut range = region.range.to_offset(&buffer);
4911 if selection.start == range.start && range.start >= region.pair.start.len() {
4912 range.start -= region.pair.start.len();
4913 if buffer.contains_str_at(range.start, ®ion.pair.start)
4914 && buffer.contains_str_at(range.end, ®ion.pair.end)
4915 {
4916 range.end += region.pair.end.len();
4917 selection.start = range.start;
4918 selection.end = range.end;
4919
4920 return selection;
4921 }
4922 }
4923 }
4924
4925 let always_treat_brackets_as_autoclosed = buffer
4926 .language_settings_at(selection.start, cx)
4927 .always_treat_brackets_as_autoclosed;
4928
4929 if !always_treat_brackets_as_autoclosed {
4930 return selection;
4931 }
4932
4933 if let Some(scope) = buffer.language_scope_at(selection.start) {
4934 for (pair, enabled) in scope.brackets() {
4935 if !enabled || !pair.close {
4936 continue;
4937 }
4938
4939 if buffer.contains_str_at(selection.start, &pair.end) {
4940 let pair_start_len = pair.start.len();
4941 if buffer.contains_str_at(
4942 selection.start.saturating_sub(pair_start_len),
4943 &pair.start,
4944 ) {
4945 selection.start -= pair_start_len;
4946 selection.end += pair.end.len();
4947
4948 return selection;
4949 }
4950 }
4951 }
4952 }
4953
4954 selection
4955 })
4956 .collect();
4957
4958 drop(buffer);
4959 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4960 selections.select(new_selections)
4961 });
4962 }
4963
4964 /// Iterate the given selections, and for each one, find the smallest surrounding
4965 /// autoclose region. This uses the ordering of the selections and the autoclose
4966 /// regions to avoid repeated comparisons.
4967 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4968 &'a self,
4969 selections: impl IntoIterator<Item = Selection<D>>,
4970 buffer: &'a MultiBufferSnapshot,
4971 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4972 let mut i = 0;
4973 let mut regions = self.autoclose_regions.as_slice();
4974 selections.into_iter().map(move |selection| {
4975 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4976
4977 let mut enclosing = None;
4978 while let Some(pair_state) = regions.get(i) {
4979 if pair_state.range.end.to_offset(buffer) < range.start {
4980 regions = ®ions[i + 1..];
4981 i = 0;
4982 } else if pair_state.range.start.to_offset(buffer) > range.end {
4983 break;
4984 } else {
4985 if pair_state.selection_id == selection.id {
4986 enclosing = Some(pair_state);
4987 }
4988 i += 1;
4989 }
4990 }
4991
4992 (selection, enclosing)
4993 })
4994 }
4995
4996 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4997 fn invalidate_autoclose_regions(
4998 &mut self,
4999 mut selections: &[Selection<Anchor>],
5000 buffer: &MultiBufferSnapshot,
5001 ) {
5002 self.autoclose_regions.retain(|state| {
5003 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5004 return false;
5005 }
5006
5007 let mut i = 0;
5008 while let Some(selection) = selections.get(i) {
5009 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5010 selections = &selections[1..];
5011 continue;
5012 }
5013 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5014 break;
5015 }
5016 if selection.id == state.selection_id {
5017 return true;
5018 } else {
5019 i += 1;
5020 }
5021 }
5022 false
5023 });
5024 }
5025
5026 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5027 let offset = position.to_offset(buffer);
5028 let (word_range, kind) = buffer.surrounding_word(offset, true);
5029 if offset > word_range.start && kind == Some(CharKind::Word) {
5030 Some(
5031 buffer
5032 .text_for_range(word_range.start..offset)
5033 .collect::<String>(),
5034 )
5035 } else {
5036 None
5037 }
5038 }
5039
5040 pub fn toggle_inline_values(
5041 &mut self,
5042 _: &ToggleInlineValues,
5043 _: &mut Window,
5044 cx: &mut Context<Self>,
5045 ) {
5046 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5047
5048 self.refresh_inline_values(cx);
5049 }
5050
5051 pub fn toggle_inlay_hints(
5052 &mut self,
5053 _: &ToggleInlayHints,
5054 _: &mut Window,
5055 cx: &mut Context<Self>,
5056 ) {
5057 self.refresh_inlay_hints(
5058 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5059 cx,
5060 );
5061 }
5062
5063 pub fn inlay_hints_enabled(&self) -> bool {
5064 self.inlay_hint_cache.enabled
5065 }
5066
5067 pub fn inline_values_enabled(&self) -> bool {
5068 self.inline_value_cache.enabled
5069 }
5070
5071 #[cfg(any(test, feature = "test-support"))]
5072 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5073 self.display_map
5074 .read(cx)
5075 .current_inlays()
5076 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5077 .cloned()
5078 .collect()
5079 }
5080
5081 #[cfg(any(test, feature = "test-support"))]
5082 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5083 self.display_map
5084 .read(cx)
5085 .current_inlays()
5086 .cloned()
5087 .collect()
5088 }
5089
5090 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5091 if self.semantics_provider.is_none() || !self.mode.is_full() {
5092 return;
5093 }
5094
5095 let reason_description = reason.description();
5096 let ignore_debounce = matches!(
5097 reason,
5098 InlayHintRefreshReason::SettingsChange(_)
5099 | InlayHintRefreshReason::Toggle(_)
5100 | InlayHintRefreshReason::ExcerptsRemoved(_)
5101 | InlayHintRefreshReason::ModifiersChanged(_)
5102 );
5103 let (invalidate_cache, required_languages) = match reason {
5104 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5105 match self.inlay_hint_cache.modifiers_override(enabled) {
5106 Some(enabled) => {
5107 if enabled {
5108 (InvalidationStrategy::RefreshRequested, None)
5109 } else {
5110 self.splice_inlays(
5111 &self
5112 .visible_inlay_hints(cx)
5113 .iter()
5114 .map(|inlay| inlay.id)
5115 .collect::<Vec<InlayId>>(),
5116 Vec::new(),
5117 cx,
5118 );
5119 return;
5120 }
5121 }
5122 None => return,
5123 }
5124 }
5125 InlayHintRefreshReason::Toggle(enabled) => {
5126 if self.inlay_hint_cache.toggle(enabled) {
5127 if enabled {
5128 (InvalidationStrategy::RefreshRequested, None)
5129 } else {
5130 self.splice_inlays(
5131 &self
5132 .visible_inlay_hints(cx)
5133 .iter()
5134 .map(|inlay| inlay.id)
5135 .collect::<Vec<InlayId>>(),
5136 Vec::new(),
5137 cx,
5138 );
5139 return;
5140 }
5141 } else {
5142 return;
5143 }
5144 }
5145 InlayHintRefreshReason::SettingsChange(new_settings) => {
5146 match self.inlay_hint_cache.update_settings(
5147 &self.buffer,
5148 new_settings,
5149 self.visible_inlay_hints(cx),
5150 cx,
5151 ) {
5152 ControlFlow::Break(Some(InlaySplice {
5153 to_remove,
5154 to_insert,
5155 })) => {
5156 self.splice_inlays(&to_remove, to_insert, cx);
5157 return;
5158 }
5159 ControlFlow::Break(None) => return,
5160 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5161 }
5162 }
5163 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5164 if let Some(InlaySplice {
5165 to_remove,
5166 to_insert,
5167 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5168 {
5169 self.splice_inlays(&to_remove, to_insert, cx);
5170 }
5171 self.display_map.update(cx, |display_map, _| {
5172 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5173 });
5174 return;
5175 }
5176 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5177 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5178 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5179 }
5180 InlayHintRefreshReason::RefreshRequested => {
5181 (InvalidationStrategy::RefreshRequested, None)
5182 }
5183 };
5184
5185 if let Some(InlaySplice {
5186 to_remove,
5187 to_insert,
5188 }) = self.inlay_hint_cache.spawn_hint_refresh(
5189 reason_description,
5190 self.visible_excerpts(required_languages.as_ref(), cx),
5191 invalidate_cache,
5192 ignore_debounce,
5193 cx,
5194 ) {
5195 self.splice_inlays(&to_remove, to_insert, cx);
5196 }
5197 }
5198
5199 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5200 self.display_map
5201 .read(cx)
5202 .current_inlays()
5203 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5204 .cloned()
5205 .collect()
5206 }
5207
5208 pub fn visible_excerpts(
5209 &self,
5210 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5211 cx: &mut Context<Editor>,
5212 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5213 let Some(project) = self.project() else {
5214 return HashMap::default();
5215 };
5216 let project = project.read(cx);
5217 let multi_buffer = self.buffer().read(cx);
5218 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5219 let multi_buffer_visible_start = self
5220 .scroll_manager
5221 .anchor()
5222 .anchor
5223 .to_point(&multi_buffer_snapshot);
5224 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5225 multi_buffer_visible_start
5226 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5227 Bias::Left,
5228 );
5229 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5230 multi_buffer_snapshot
5231 .range_to_buffer_ranges(multi_buffer_visible_range)
5232 .into_iter()
5233 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5234 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5235 let buffer_file = project::File::from_dyn(buffer.file())?;
5236 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5237 let worktree_entry = buffer_worktree
5238 .read(cx)
5239 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5240 if worktree_entry.is_ignored {
5241 return None;
5242 }
5243
5244 let language = buffer.language()?;
5245 if let Some(restrict_to_languages) = restrict_to_languages
5246 && !restrict_to_languages.contains(language)
5247 {
5248 return None;
5249 }
5250 Some((
5251 excerpt_id,
5252 (
5253 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5254 buffer.version().clone(),
5255 excerpt_visible_range,
5256 ),
5257 ))
5258 })
5259 .collect()
5260 }
5261
5262 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5263 TextLayoutDetails {
5264 text_system: window.text_system().clone(),
5265 editor_style: self.style.clone().unwrap(),
5266 rem_size: window.rem_size(),
5267 scroll_anchor: self.scroll_manager.anchor(),
5268 visible_rows: self.visible_line_count(),
5269 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5270 }
5271 }
5272
5273 pub fn splice_inlays(
5274 &self,
5275 to_remove: &[InlayId],
5276 to_insert: Vec<Inlay>,
5277 cx: &mut Context<Self>,
5278 ) {
5279 self.display_map.update(cx, |display_map, cx| {
5280 display_map.splice_inlays(to_remove, to_insert, cx)
5281 });
5282 cx.notify();
5283 }
5284
5285 fn trigger_on_type_formatting(
5286 &self,
5287 input: String,
5288 window: &mut Window,
5289 cx: &mut Context<Self>,
5290 ) -> Option<Task<Result<()>>> {
5291 if input.len() != 1 {
5292 return None;
5293 }
5294
5295 let project = self.project()?;
5296 let position = self.selections.newest_anchor().head();
5297 let (buffer, buffer_position) = self
5298 .buffer
5299 .read(cx)
5300 .text_anchor_for_position(position, cx)?;
5301
5302 let settings = language_settings::language_settings(
5303 buffer
5304 .read(cx)
5305 .language_at(buffer_position)
5306 .map(|l| l.name()),
5307 buffer.read(cx).file(),
5308 cx,
5309 );
5310 if !settings.use_on_type_format {
5311 return None;
5312 }
5313
5314 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5315 // hence we do LSP request & edit on host side only — add formats to host's history.
5316 let push_to_lsp_host_history = true;
5317 // If this is not the host, append its history with new edits.
5318 let push_to_client_history = project.read(cx).is_via_collab();
5319
5320 let on_type_formatting = project.update(cx, |project, cx| {
5321 project.on_type_format(
5322 buffer.clone(),
5323 buffer_position,
5324 input,
5325 push_to_lsp_host_history,
5326 cx,
5327 )
5328 });
5329 Some(cx.spawn_in(window, async move |editor, cx| {
5330 if let Some(transaction) = on_type_formatting.await? {
5331 if push_to_client_history {
5332 buffer
5333 .update(cx, |buffer, _| {
5334 buffer.push_transaction(transaction, Instant::now());
5335 buffer.finalize_last_transaction();
5336 })
5337 .ok();
5338 }
5339 editor.update(cx, |editor, cx| {
5340 editor.refresh_document_highlights(cx);
5341 })?;
5342 }
5343 Ok(())
5344 }))
5345 }
5346
5347 pub fn show_word_completions(
5348 &mut self,
5349 _: &ShowWordCompletions,
5350 window: &mut Window,
5351 cx: &mut Context<Self>,
5352 ) {
5353 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5354 }
5355
5356 pub fn show_completions(
5357 &mut self,
5358 options: &ShowCompletions,
5359 window: &mut Window,
5360 cx: &mut Context<Self>,
5361 ) {
5362 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5363 }
5364
5365 fn open_or_update_completions_menu(
5366 &mut self,
5367 requested_source: Option<CompletionsMenuSource>,
5368 trigger: Option<&str>,
5369 window: &mut Window,
5370 cx: &mut Context<Self>,
5371 ) {
5372 if self.pending_rename.is_some() {
5373 return;
5374 }
5375
5376 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5377
5378 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5379 // inserted and selected. To handle that case, the start of the selection is used so that
5380 // the menu starts with all choices.
5381 let position = self
5382 .selections
5383 .newest_anchor()
5384 .start
5385 .bias_right(&multibuffer_snapshot);
5386 if position.diff_base_anchor.is_some() {
5387 return;
5388 }
5389 let (buffer, buffer_position) =
5390 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5391 output
5392 } else {
5393 return;
5394 };
5395 let buffer_snapshot = buffer.read(cx).snapshot();
5396
5397 let query: Option<Arc<String>> =
5398 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5399
5400 drop(multibuffer_snapshot);
5401
5402 let provider = match requested_source {
5403 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5404 Some(CompletionsMenuSource::Words) => None,
5405 Some(CompletionsMenuSource::SnippetChoices) => {
5406 log::error!("bug: SnippetChoices requested_source is not handled");
5407 None
5408 }
5409 };
5410
5411 let sort_completions = provider
5412 .as_ref()
5413 .is_some_and(|provider| provider.sort_completions());
5414
5415 let filter_completions = provider
5416 .as_ref()
5417 .is_none_or(|provider| provider.filter_completions());
5418
5419 let trigger_kind = match trigger {
5420 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5421 CompletionTriggerKind::TRIGGER_CHARACTER
5422 }
5423 _ => CompletionTriggerKind::INVOKED,
5424 };
5425 let completion_context = CompletionContext {
5426 trigger_character: trigger.and_then(|trigger| {
5427 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5428 Some(String::from(trigger))
5429 } else {
5430 None
5431 }
5432 }),
5433 trigger_kind,
5434 };
5435
5436 // Hide the current completions menu when a trigger char is typed. Without this, cached
5437 // completions from before the trigger char may be reused (#32774). Snippet choices could
5438 // involve trigger chars, so this is skipped in that case.
5439 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5440 {
5441 let menu_is_open = matches!(
5442 self.context_menu.borrow().as_ref(),
5443 Some(CodeContextMenu::Completions(_))
5444 );
5445 if menu_is_open {
5446 self.hide_context_menu(window, cx);
5447 }
5448 }
5449
5450 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5451 if filter_completions {
5452 menu.filter(query.clone(), provider.clone(), window, cx);
5453 }
5454 // When `is_incomplete` is false, no need to re-query completions when the current query
5455 // is a suffix of the initial query.
5456 if !menu.is_incomplete {
5457 // If the new query is a suffix of the old query (typing more characters) and
5458 // the previous result was complete, the existing completions can be filtered.
5459 //
5460 // Note that this is always true for snippet completions.
5461 let query_matches = match (&menu.initial_query, &query) {
5462 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5463 (None, _) => true,
5464 _ => false,
5465 };
5466 if query_matches {
5467 let position_matches = if menu.initial_position == position {
5468 true
5469 } else {
5470 let snapshot = self.buffer.read(cx).read(cx);
5471 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5472 };
5473 if position_matches {
5474 return;
5475 }
5476 }
5477 }
5478 };
5479
5480 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5481 buffer_snapshot.surrounding_word(buffer_position, false)
5482 {
5483 let word_to_exclude = buffer_snapshot
5484 .text_for_range(word_range.clone())
5485 .collect::<String>();
5486 (
5487 buffer_snapshot.anchor_before(word_range.start)
5488 ..buffer_snapshot.anchor_after(buffer_position),
5489 Some(word_to_exclude),
5490 )
5491 } else {
5492 (buffer_position..buffer_position, None)
5493 };
5494
5495 let language = buffer_snapshot
5496 .language_at(buffer_position)
5497 .map(|language| language.name());
5498
5499 let completion_settings =
5500 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5501
5502 let show_completion_documentation = buffer_snapshot
5503 .settings_at(buffer_position, cx)
5504 .show_completion_documentation;
5505
5506 // The document can be large, so stay in reasonable bounds when searching for words,
5507 // otherwise completion pop-up might be slow to appear.
5508 const WORD_LOOKUP_ROWS: u32 = 5_000;
5509 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5510 let min_word_search = buffer_snapshot.clip_point(
5511 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5512 Bias::Left,
5513 );
5514 let max_word_search = buffer_snapshot.clip_point(
5515 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5516 Bias::Right,
5517 );
5518 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5519 ..buffer_snapshot.point_to_offset(max_word_search);
5520
5521 let skip_digits = query
5522 .as_ref()
5523 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5524
5525 let (mut words, provider_responses) = match &provider {
5526 Some(provider) => {
5527 let provider_responses = provider.completions(
5528 position.excerpt_id,
5529 &buffer,
5530 buffer_position,
5531 completion_context,
5532 window,
5533 cx,
5534 );
5535
5536 let words = match completion_settings.words {
5537 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5538 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5539 .background_spawn(async move {
5540 buffer_snapshot.words_in_range(WordsQuery {
5541 fuzzy_contents: None,
5542 range: word_search_range,
5543 skip_digits,
5544 })
5545 }),
5546 };
5547
5548 (words, provider_responses)
5549 }
5550 None => (
5551 cx.background_spawn(async move {
5552 buffer_snapshot.words_in_range(WordsQuery {
5553 fuzzy_contents: None,
5554 range: word_search_range,
5555 skip_digits,
5556 })
5557 }),
5558 Task::ready(Ok(Vec::new())),
5559 ),
5560 };
5561
5562 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5563
5564 let id = post_inc(&mut self.next_completion_id);
5565 let task = cx.spawn_in(window, async move |editor, cx| {
5566 let Ok(()) = editor.update(cx, |this, _| {
5567 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5568 }) else {
5569 return;
5570 };
5571
5572 // TODO: Ideally completions from different sources would be selectively re-queried, so
5573 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5574 let mut completions = Vec::new();
5575 let mut is_incomplete = false;
5576 if let Some(provider_responses) = provider_responses.await.log_err()
5577 && !provider_responses.is_empty()
5578 {
5579 for response in provider_responses {
5580 completions.extend(response.completions);
5581 is_incomplete = is_incomplete || response.is_incomplete;
5582 }
5583 if completion_settings.words == WordsCompletionMode::Fallback {
5584 words = Task::ready(BTreeMap::default());
5585 }
5586 }
5587
5588 let mut words = words.await;
5589 if let Some(word_to_exclude) = &word_to_exclude {
5590 words.remove(word_to_exclude);
5591 }
5592 for lsp_completion in &completions {
5593 words.remove(&lsp_completion.new_text);
5594 }
5595 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5596 replace_range: word_replace_range.clone(),
5597 new_text: word.clone(),
5598 label: CodeLabel::plain(word, None),
5599 icon_path: None,
5600 documentation: None,
5601 source: CompletionSource::BufferWord {
5602 word_range,
5603 resolved: false,
5604 },
5605 insert_text_mode: Some(InsertTextMode::AS_IS),
5606 confirm: None,
5607 }));
5608
5609 let menu = if completions.is_empty() {
5610 None
5611 } else {
5612 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5613 let languages = editor
5614 .workspace
5615 .as_ref()
5616 .and_then(|(workspace, _)| workspace.upgrade())
5617 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5618 let menu = CompletionsMenu::new(
5619 id,
5620 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5621 sort_completions,
5622 show_completion_documentation,
5623 position,
5624 query.clone(),
5625 is_incomplete,
5626 buffer.clone(),
5627 completions.into(),
5628 snippet_sort_order,
5629 languages,
5630 language,
5631 cx,
5632 );
5633
5634 let query = if filter_completions { query } else { None };
5635 let matches_task = if let Some(query) = query {
5636 menu.do_async_filtering(query, cx)
5637 } else {
5638 Task::ready(menu.unfiltered_matches())
5639 };
5640 (menu, matches_task)
5641 }) else {
5642 return;
5643 };
5644
5645 let matches = matches_task.await;
5646
5647 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5648 // Newer menu already set, so exit.
5649 if let Some(CodeContextMenu::Completions(prev_menu)) =
5650 editor.context_menu.borrow().as_ref()
5651 && prev_menu.id > id
5652 {
5653 return;
5654 };
5655
5656 // Only valid to take prev_menu because it the new menu is immediately set
5657 // below, or the menu is hidden.
5658 if let Some(CodeContextMenu::Completions(prev_menu)) =
5659 editor.context_menu.borrow_mut().take()
5660 {
5661 let position_matches =
5662 if prev_menu.initial_position == menu.initial_position {
5663 true
5664 } else {
5665 let snapshot = editor.buffer.read(cx).read(cx);
5666 prev_menu.initial_position.to_offset(&snapshot)
5667 == menu.initial_position.to_offset(&snapshot)
5668 };
5669 if position_matches {
5670 // Preserve markdown cache before `set_filter_results` because it will
5671 // try to populate the documentation cache.
5672 menu.preserve_markdown_cache(prev_menu);
5673 }
5674 };
5675
5676 menu.set_filter_results(matches, provider, window, cx);
5677 }) else {
5678 return;
5679 };
5680
5681 menu.visible().then_some(menu)
5682 };
5683
5684 editor
5685 .update_in(cx, |editor, window, cx| {
5686 if editor.focus_handle.is_focused(window)
5687 && let Some(menu) = menu
5688 {
5689 *editor.context_menu.borrow_mut() =
5690 Some(CodeContextMenu::Completions(menu));
5691
5692 crate::hover_popover::hide_hover(editor, cx);
5693 if editor.show_edit_predictions_in_menu() {
5694 editor.update_visible_edit_prediction(window, cx);
5695 } else {
5696 editor.discard_edit_prediction(false, cx);
5697 }
5698
5699 cx.notify();
5700 return;
5701 }
5702
5703 if editor.completion_tasks.len() <= 1 {
5704 // If there are no more completion tasks and the last menu was empty, we should hide it.
5705 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5706 // If it was already hidden and we don't show edit predictions in the menu,
5707 // we should also show the edit prediction when available.
5708 if was_hidden && editor.show_edit_predictions_in_menu() {
5709 editor.update_visible_edit_prediction(window, cx);
5710 }
5711 }
5712 })
5713 .ok();
5714 });
5715
5716 self.completion_tasks.push((id, task));
5717 }
5718
5719 #[cfg(feature = "test-support")]
5720 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5721 let menu = self.context_menu.borrow();
5722 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5723 let completions = menu.completions.borrow();
5724 Some(completions.to_vec())
5725 } else {
5726 None
5727 }
5728 }
5729
5730 pub fn with_completions_menu_matching_id<R>(
5731 &self,
5732 id: CompletionId,
5733 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5734 ) -> R {
5735 let mut context_menu = self.context_menu.borrow_mut();
5736 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5737 return f(None);
5738 };
5739 if completions_menu.id != id {
5740 return f(None);
5741 }
5742 f(Some(completions_menu))
5743 }
5744
5745 pub fn confirm_completion(
5746 &mut self,
5747 action: &ConfirmCompletion,
5748 window: &mut Window,
5749 cx: &mut Context<Self>,
5750 ) -> Option<Task<Result<()>>> {
5751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5752 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5753 }
5754
5755 pub fn confirm_completion_insert(
5756 &mut self,
5757 _: &ConfirmCompletionInsert,
5758 window: &mut Window,
5759 cx: &mut Context<Self>,
5760 ) -> Option<Task<Result<()>>> {
5761 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5762 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5763 }
5764
5765 pub fn confirm_completion_replace(
5766 &mut self,
5767 _: &ConfirmCompletionReplace,
5768 window: &mut Window,
5769 cx: &mut Context<Self>,
5770 ) -> Option<Task<Result<()>>> {
5771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5772 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5773 }
5774
5775 pub fn compose_completion(
5776 &mut self,
5777 action: &ComposeCompletion,
5778 window: &mut Window,
5779 cx: &mut Context<Self>,
5780 ) -> Option<Task<Result<()>>> {
5781 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5782 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5783 }
5784
5785 fn do_completion(
5786 &mut self,
5787 item_ix: Option<usize>,
5788 intent: CompletionIntent,
5789 window: &mut Window,
5790 cx: &mut Context<Editor>,
5791 ) -> Option<Task<Result<()>>> {
5792 use language::ToOffset as _;
5793
5794 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5795 else {
5796 return None;
5797 };
5798
5799 let candidate_id = {
5800 let entries = completions_menu.entries.borrow();
5801 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5802 if self.show_edit_predictions_in_menu() {
5803 self.discard_edit_prediction(true, cx);
5804 }
5805 mat.candidate_id
5806 };
5807
5808 let completion = completions_menu
5809 .completions
5810 .borrow()
5811 .get(candidate_id)?
5812 .clone();
5813 cx.stop_propagation();
5814
5815 let buffer_handle = completions_menu.buffer.clone();
5816
5817 let CompletionEdit {
5818 new_text,
5819 snippet,
5820 replace_range,
5821 } = process_completion_for_edit(
5822 &completion,
5823 intent,
5824 &buffer_handle,
5825 &completions_menu.initial_position.text_anchor,
5826 cx,
5827 );
5828
5829 let buffer = buffer_handle.read(cx);
5830 let snapshot = self.buffer.read(cx).snapshot(cx);
5831 let newest_anchor = self.selections.newest_anchor();
5832 let replace_range_multibuffer = {
5833 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5834 let multibuffer_anchor = snapshot
5835 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5836 .unwrap()
5837 ..snapshot
5838 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5839 .unwrap();
5840 multibuffer_anchor.start.to_offset(&snapshot)
5841 ..multibuffer_anchor.end.to_offset(&snapshot)
5842 };
5843 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5844 return None;
5845 }
5846
5847 let old_text = buffer
5848 .text_for_range(replace_range.clone())
5849 .collect::<String>();
5850 let lookbehind = newest_anchor
5851 .start
5852 .text_anchor
5853 .to_offset(buffer)
5854 .saturating_sub(replace_range.start);
5855 let lookahead = replace_range
5856 .end
5857 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5858 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5859 let suffix = &old_text[lookbehind.min(old_text.len())..];
5860
5861 let selections = self.selections.all::<usize>(cx);
5862 let mut ranges = Vec::new();
5863 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5864
5865 for selection in &selections {
5866 let range = if selection.id == newest_anchor.id {
5867 replace_range_multibuffer.clone()
5868 } else {
5869 let mut range = selection.range();
5870
5871 // if prefix is present, don't duplicate it
5872 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5873 range.start = range.start.saturating_sub(lookbehind);
5874
5875 // if suffix is also present, mimic the newest cursor and replace it
5876 if selection.id != newest_anchor.id
5877 && snapshot.contains_str_at(range.end, suffix)
5878 {
5879 range.end += lookahead;
5880 }
5881 }
5882 range
5883 };
5884
5885 ranges.push(range.clone());
5886
5887 if !self.linked_edit_ranges.is_empty() {
5888 let start_anchor = snapshot.anchor_before(range.start);
5889 let end_anchor = snapshot.anchor_after(range.end);
5890 if let Some(ranges) = self
5891 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5892 {
5893 for (buffer, edits) in ranges {
5894 linked_edits
5895 .entry(buffer.clone())
5896 .or_default()
5897 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5898 }
5899 }
5900 }
5901 }
5902
5903 let common_prefix_len = old_text
5904 .chars()
5905 .zip(new_text.chars())
5906 .take_while(|(a, b)| a == b)
5907 .map(|(a, _)| a.len_utf8())
5908 .sum::<usize>();
5909
5910 cx.emit(EditorEvent::InputHandled {
5911 utf16_range_to_replace: None,
5912 text: new_text[common_prefix_len..].into(),
5913 });
5914
5915 self.transact(window, cx, |editor, window, cx| {
5916 if let Some(mut snippet) = snippet {
5917 snippet.text = new_text.to_string();
5918 editor
5919 .insert_snippet(&ranges, snippet, window, cx)
5920 .log_err();
5921 } else {
5922 editor.buffer.update(cx, |multi_buffer, cx| {
5923 let auto_indent = match completion.insert_text_mode {
5924 Some(InsertTextMode::AS_IS) => None,
5925 _ => editor.autoindent_mode.clone(),
5926 };
5927 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5928 multi_buffer.edit(edits, auto_indent, cx);
5929 });
5930 }
5931 for (buffer, edits) in linked_edits {
5932 buffer.update(cx, |buffer, cx| {
5933 let snapshot = buffer.snapshot();
5934 let edits = edits
5935 .into_iter()
5936 .map(|(range, text)| {
5937 use text::ToPoint as TP;
5938 let end_point = TP::to_point(&range.end, &snapshot);
5939 let start_point = TP::to_point(&range.start, &snapshot);
5940 (start_point..end_point, text)
5941 })
5942 .sorted_by_key(|(range, _)| range.start);
5943 buffer.edit(edits, None, cx);
5944 })
5945 }
5946
5947 editor.refresh_edit_prediction(true, false, window, cx);
5948 });
5949 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5950
5951 let show_new_completions_on_confirm = completion
5952 .confirm
5953 .as_ref()
5954 .is_some_and(|confirm| confirm(intent, window, cx));
5955 if show_new_completions_on_confirm {
5956 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5957 }
5958
5959 let provider = self.completion_provider.as_ref()?;
5960 drop(completion);
5961 let apply_edits = provider.apply_additional_edits_for_completion(
5962 buffer_handle,
5963 completions_menu.completions.clone(),
5964 candidate_id,
5965 true,
5966 cx,
5967 );
5968
5969 let editor_settings = EditorSettings::get_global(cx);
5970 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5971 // After the code completion is finished, users often want to know what signatures are needed.
5972 // so we should automatically call signature_help
5973 self.show_signature_help(&ShowSignatureHelp, window, cx);
5974 }
5975
5976 Some(cx.foreground_executor().spawn(async move {
5977 apply_edits.await?;
5978 Ok(())
5979 }))
5980 }
5981
5982 pub fn toggle_code_actions(
5983 &mut self,
5984 action: &ToggleCodeActions,
5985 window: &mut Window,
5986 cx: &mut Context<Self>,
5987 ) {
5988 let quick_launch = action.quick_launch;
5989 let mut context_menu = self.context_menu.borrow_mut();
5990 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5991 if code_actions.deployed_from == action.deployed_from {
5992 // Toggle if we're selecting the same one
5993 *context_menu = None;
5994 cx.notify();
5995 return;
5996 } else {
5997 // Otherwise, clear it and start a new one
5998 *context_menu = None;
5999 cx.notify();
6000 }
6001 }
6002 drop(context_menu);
6003 let snapshot = self.snapshot(window, cx);
6004 let deployed_from = action.deployed_from.clone();
6005 let action = action.clone();
6006 self.completion_tasks.clear();
6007 self.discard_edit_prediction(false, cx);
6008
6009 let multibuffer_point = match &action.deployed_from {
6010 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6011 DisplayPoint::new(*row, 0).to_point(&snapshot)
6012 }
6013 _ => self.selections.newest::<Point>(cx).head(),
6014 };
6015 let Some((buffer, buffer_row)) = snapshot
6016 .buffer_snapshot
6017 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6018 .and_then(|(buffer_snapshot, range)| {
6019 self.buffer()
6020 .read(cx)
6021 .buffer(buffer_snapshot.remote_id())
6022 .map(|buffer| (buffer, range.start.row))
6023 })
6024 else {
6025 return;
6026 };
6027 let buffer_id = buffer.read(cx).remote_id();
6028 let tasks = self
6029 .tasks
6030 .get(&(buffer_id, buffer_row))
6031 .map(|t| Arc::new(t.to_owned()));
6032
6033 if !self.focus_handle.is_focused(window) {
6034 return;
6035 }
6036 let project = self.project.clone();
6037
6038 let code_actions_task = match deployed_from {
6039 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6040 _ => self.code_actions(buffer_row, window, cx),
6041 };
6042
6043 let runnable_task = match deployed_from {
6044 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6045 _ => {
6046 let mut task_context_task = Task::ready(None);
6047 if let Some(tasks) = &tasks
6048 && let Some(project) = project
6049 {
6050 task_context_task =
6051 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6052 }
6053
6054 cx.spawn_in(window, {
6055 let buffer = buffer.clone();
6056 async move |editor, cx| {
6057 let task_context = task_context_task.await;
6058
6059 let resolved_tasks =
6060 tasks
6061 .zip(task_context.clone())
6062 .map(|(tasks, task_context)| ResolvedTasks {
6063 templates: tasks.resolve(&task_context).collect(),
6064 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6065 multibuffer_point.row,
6066 tasks.column,
6067 )),
6068 });
6069 let debug_scenarios = editor
6070 .update(cx, |editor, cx| {
6071 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6072 })?
6073 .await;
6074 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6075 }
6076 })
6077 }
6078 };
6079
6080 cx.spawn_in(window, async move |editor, cx| {
6081 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6082 let code_actions = code_actions_task.await;
6083 let spawn_straight_away = quick_launch
6084 && resolved_tasks
6085 .as_ref()
6086 .is_some_and(|tasks| tasks.templates.len() == 1)
6087 && code_actions
6088 .as_ref()
6089 .is_none_or(|actions| actions.is_empty())
6090 && debug_scenarios.is_empty();
6091
6092 editor.update_in(cx, |editor, window, cx| {
6093 crate::hover_popover::hide_hover(editor, cx);
6094 let actions = CodeActionContents::new(
6095 resolved_tasks,
6096 code_actions,
6097 debug_scenarios,
6098 task_context.unwrap_or_default(),
6099 );
6100
6101 // Don't show the menu if there are no actions available
6102 if actions.is_empty() {
6103 cx.notify();
6104 return Task::ready(Ok(()));
6105 }
6106
6107 *editor.context_menu.borrow_mut() =
6108 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6109 buffer,
6110 actions,
6111 selected_item: Default::default(),
6112 scroll_handle: UniformListScrollHandle::default(),
6113 deployed_from,
6114 }));
6115 cx.notify();
6116 if spawn_straight_away
6117 && let Some(task) = editor.confirm_code_action(
6118 &ConfirmCodeAction { item_ix: Some(0) },
6119 window,
6120 cx,
6121 )
6122 {
6123 return task;
6124 }
6125
6126 Task::ready(Ok(()))
6127 })
6128 })
6129 .detach_and_log_err(cx);
6130 }
6131
6132 fn debug_scenarios(
6133 &mut self,
6134 resolved_tasks: &Option<ResolvedTasks>,
6135 buffer: &Entity<Buffer>,
6136 cx: &mut App,
6137 ) -> Task<Vec<task::DebugScenario>> {
6138 maybe!({
6139 let project = self.project()?;
6140 let dap_store = project.read(cx).dap_store();
6141 let mut scenarios = vec![];
6142 let resolved_tasks = resolved_tasks.as_ref()?;
6143 let buffer = buffer.read(cx);
6144 let language = buffer.language()?;
6145 let file = buffer.file();
6146 let debug_adapter = language_settings(language.name().into(), file, cx)
6147 .debuggers
6148 .first()
6149 .map(SharedString::from)
6150 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6151
6152 dap_store.update(cx, |dap_store, cx| {
6153 for (_, task) in &resolved_tasks.templates {
6154 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6155 task.original_task().clone(),
6156 debug_adapter.clone().into(),
6157 task.display_label().to_owned().into(),
6158 cx,
6159 );
6160 scenarios.push(maybe_scenario);
6161 }
6162 });
6163 Some(cx.background_spawn(async move {
6164 futures::future::join_all(scenarios)
6165 .await
6166 .into_iter()
6167 .flatten()
6168 .collect::<Vec<_>>()
6169 }))
6170 })
6171 .unwrap_or_else(|| Task::ready(vec![]))
6172 }
6173
6174 fn code_actions(
6175 &mut self,
6176 buffer_row: u32,
6177 window: &mut Window,
6178 cx: &mut Context<Self>,
6179 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6180 let mut task = self.code_actions_task.take();
6181 cx.spawn_in(window, async move |editor, cx| {
6182 while let Some(prev_task) = task {
6183 prev_task.await.log_err();
6184 task = editor
6185 .update(cx, |this, _| this.code_actions_task.take())
6186 .ok()?;
6187 }
6188
6189 editor
6190 .update(cx, |editor, cx| {
6191 editor
6192 .available_code_actions
6193 .clone()
6194 .and_then(|(location, code_actions)| {
6195 let snapshot = location.buffer.read(cx).snapshot();
6196 let point_range = location.range.to_point(&snapshot);
6197 let point_range = point_range.start.row..=point_range.end.row;
6198 if point_range.contains(&buffer_row) {
6199 Some(code_actions)
6200 } else {
6201 None
6202 }
6203 })
6204 })
6205 .ok()
6206 .flatten()
6207 })
6208 }
6209
6210 pub fn confirm_code_action(
6211 &mut self,
6212 action: &ConfirmCodeAction,
6213 window: &mut Window,
6214 cx: &mut Context<Self>,
6215 ) -> Option<Task<Result<()>>> {
6216 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6217
6218 let actions_menu =
6219 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6220 menu
6221 } else {
6222 return None;
6223 };
6224
6225 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6226 let action = actions_menu.actions.get(action_ix)?;
6227 let title = action.label();
6228 let buffer = actions_menu.buffer;
6229 let workspace = self.workspace()?;
6230
6231 match action {
6232 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6233 workspace.update(cx, |workspace, cx| {
6234 workspace.schedule_resolved_task(
6235 task_source_kind,
6236 resolved_task,
6237 false,
6238 window,
6239 cx,
6240 );
6241
6242 Some(Task::ready(Ok(())))
6243 })
6244 }
6245 CodeActionsItem::CodeAction {
6246 excerpt_id,
6247 action,
6248 provider,
6249 } => {
6250 let apply_code_action =
6251 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6252 let workspace = workspace.downgrade();
6253 Some(cx.spawn_in(window, async move |editor, cx| {
6254 let project_transaction = apply_code_action.await?;
6255 Self::open_project_transaction(
6256 &editor,
6257 workspace,
6258 project_transaction,
6259 title,
6260 cx,
6261 )
6262 .await
6263 }))
6264 }
6265 CodeActionsItem::DebugScenario(scenario) => {
6266 let context = actions_menu.actions.context;
6267
6268 workspace.update(cx, |workspace, cx| {
6269 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6270 workspace.start_debug_session(
6271 scenario,
6272 context,
6273 Some(buffer),
6274 None,
6275 window,
6276 cx,
6277 );
6278 });
6279 Some(Task::ready(Ok(())))
6280 }
6281 }
6282 }
6283
6284 pub async fn open_project_transaction(
6285 this: &WeakEntity<Editor>,
6286 workspace: WeakEntity<Workspace>,
6287 transaction: ProjectTransaction,
6288 title: String,
6289 cx: &mut AsyncWindowContext,
6290 ) -> Result<()> {
6291 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6292 cx.update(|_, cx| {
6293 entries.sort_unstable_by_key(|(buffer, _)| {
6294 buffer.read(cx).file().map(|f| f.path().clone())
6295 });
6296 })?;
6297
6298 // If the project transaction's edits are all contained within this editor, then
6299 // avoid opening a new editor to display them.
6300
6301 if let Some((buffer, transaction)) = entries.first() {
6302 if entries.len() == 1 {
6303 let excerpt = this.update(cx, |editor, cx| {
6304 editor
6305 .buffer()
6306 .read(cx)
6307 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6308 })?;
6309 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6310 && excerpted_buffer == *buffer
6311 {
6312 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6313 let excerpt_range = excerpt_range.to_offset(buffer);
6314 buffer
6315 .edited_ranges_for_transaction::<usize>(transaction)
6316 .all(|range| {
6317 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6318 })
6319 })?;
6320
6321 if all_edits_within_excerpt {
6322 return Ok(());
6323 }
6324 }
6325 }
6326 } else {
6327 return Ok(());
6328 }
6329
6330 let mut ranges_to_highlight = Vec::new();
6331 let excerpt_buffer = cx.new(|cx| {
6332 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6333 for (buffer_handle, transaction) in &entries {
6334 let edited_ranges = buffer_handle
6335 .read(cx)
6336 .edited_ranges_for_transaction::<Point>(transaction)
6337 .collect::<Vec<_>>();
6338 let (ranges, _) = multibuffer.set_excerpts_for_path(
6339 PathKey::for_buffer(buffer_handle, cx),
6340 buffer_handle.clone(),
6341 edited_ranges,
6342 DEFAULT_MULTIBUFFER_CONTEXT,
6343 cx,
6344 );
6345
6346 ranges_to_highlight.extend(ranges);
6347 }
6348 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6349 multibuffer
6350 })?;
6351
6352 workspace.update_in(cx, |workspace, window, cx| {
6353 let project = workspace.project().clone();
6354 let editor =
6355 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6356 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6357 editor.update(cx, |editor, cx| {
6358 editor.highlight_background::<Self>(
6359 &ranges_to_highlight,
6360 |theme| theme.colors().editor_highlighted_line_background,
6361 cx,
6362 );
6363 });
6364 })?;
6365
6366 Ok(())
6367 }
6368
6369 pub fn clear_code_action_providers(&mut self) {
6370 self.code_action_providers.clear();
6371 self.available_code_actions.take();
6372 }
6373
6374 pub fn add_code_action_provider(
6375 &mut self,
6376 provider: Rc<dyn CodeActionProvider>,
6377 window: &mut Window,
6378 cx: &mut Context<Self>,
6379 ) {
6380 if self
6381 .code_action_providers
6382 .iter()
6383 .any(|existing_provider| existing_provider.id() == provider.id())
6384 {
6385 return;
6386 }
6387
6388 self.code_action_providers.push(provider);
6389 self.refresh_code_actions(window, cx);
6390 }
6391
6392 pub fn remove_code_action_provider(
6393 &mut self,
6394 id: Arc<str>,
6395 window: &mut Window,
6396 cx: &mut Context<Self>,
6397 ) {
6398 self.code_action_providers
6399 .retain(|provider| provider.id() != id);
6400 self.refresh_code_actions(window, cx);
6401 }
6402
6403 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6404 !self.code_action_providers.is_empty()
6405 && EditorSettings::get_global(cx).toolbar.code_actions
6406 }
6407
6408 pub fn has_available_code_actions(&self) -> bool {
6409 self.available_code_actions
6410 .as_ref()
6411 .is_some_and(|(_, actions)| !actions.is_empty())
6412 }
6413
6414 fn render_inline_code_actions(
6415 &self,
6416 icon_size: ui::IconSize,
6417 display_row: DisplayRow,
6418 is_active: bool,
6419 cx: &mut Context<Self>,
6420 ) -> AnyElement {
6421 let show_tooltip = !self.context_menu_visible();
6422 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6423 .icon_size(icon_size)
6424 .shape(ui::IconButtonShape::Square)
6425 .icon_color(ui::Color::Hidden)
6426 .toggle_state(is_active)
6427 .when(show_tooltip, |this| {
6428 this.tooltip({
6429 let focus_handle = self.focus_handle.clone();
6430 move |window, cx| {
6431 Tooltip::for_action_in(
6432 "Toggle Code Actions",
6433 &ToggleCodeActions {
6434 deployed_from: None,
6435 quick_launch: false,
6436 },
6437 &focus_handle,
6438 window,
6439 cx,
6440 )
6441 }
6442 })
6443 })
6444 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6445 window.focus(&editor.focus_handle(cx));
6446 editor.toggle_code_actions(
6447 &crate::actions::ToggleCodeActions {
6448 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6449 display_row,
6450 )),
6451 quick_launch: false,
6452 },
6453 window,
6454 cx,
6455 );
6456 }))
6457 .into_any_element()
6458 }
6459
6460 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6461 &self.context_menu
6462 }
6463
6464 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6465 let newest_selection = self.selections.newest_anchor().clone();
6466 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6467 let buffer = self.buffer.read(cx);
6468 if newest_selection.head().diff_base_anchor.is_some() {
6469 return None;
6470 }
6471 let (start_buffer, start) =
6472 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6473 let (end_buffer, end) =
6474 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6475 if start_buffer != end_buffer {
6476 return None;
6477 }
6478
6479 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6480 cx.background_executor()
6481 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6482 .await;
6483
6484 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6485 let providers = this.code_action_providers.clone();
6486 let tasks = this
6487 .code_action_providers
6488 .iter()
6489 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6490 .collect::<Vec<_>>();
6491 (providers, tasks)
6492 })?;
6493
6494 let mut actions = Vec::new();
6495 for (provider, provider_actions) in
6496 providers.into_iter().zip(future::join_all(tasks).await)
6497 {
6498 if let Some(provider_actions) = provider_actions.log_err() {
6499 actions.extend(provider_actions.into_iter().map(|action| {
6500 AvailableCodeAction {
6501 excerpt_id: newest_selection.start.excerpt_id,
6502 action,
6503 provider: provider.clone(),
6504 }
6505 }));
6506 }
6507 }
6508
6509 this.update(cx, |this, cx| {
6510 this.available_code_actions = if actions.is_empty() {
6511 None
6512 } else {
6513 Some((
6514 Location {
6515 buffer: start_buffer,
6516 range: start..end,
6517 },
6518 actions.into(),
6519 ))
6520 };
6521 cx.notify();
6522 })
6523 }));
6524 None
6525 }
6526
6527 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6528 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6529 self.show_git_blame_inline = false;
6530
6531 self.show_git_blame_inline_delay_task =
6532 Some(cx.spawn_in(window, async move |this, cx| {
6533 cx.background_executor().timer(delay).await;
6534
6535 this.update(cx, |this, cx| {
6536 this.show_git_blame_inline = true;
6537 cx.notify();
6538 })
6539 .log_err();
6540 }));
6541 }
6542 }
6543
6544 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6545 let snapshot = self.snapshot(window, cx);
6546 let cursor = self.selections.newest::<Point>(cx).head();
6547 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6548 else {
6549 return;
6550 };
6551
6552 let Some(blame) = self.blame.as_ref() else {
6553 return;
6554 };
6555
6556 let row_info = RowInfo {
6557 buffer_id: Some(buffer.remote_id()),
6558 buffer_row: Some(point.row),
6559 ..Default::default()
6560 };
6561 let Some(blame_entry) = blame
6562 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6563 .flatten()
6564 else {
6565 return;
6566 };
6567
6568 let anchor = self.selections.newest_anchor().head();
6569 let position = self.to_pixel_point(anchor, &snapshot, window);
6570 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6571 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6572 };
6573 }
6574
6575 fn show_blame_popover(
6576 &mut self,
6577 blame_entry: &BlameEntry,
6578 position: gpui::Point<Pixels>,
6579 ignore_timeout: bool,
6580 cx: &mut Context<Self>,
6581 ) {
6582 if let Some(state) = &mut self.inline_blame_popover {
6583 state.hide_task.take();
6584 } else {
6585 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6586 let blame_entry = blame_entry.clone();
6587 let show_task = cx.spawn(async move |editor, cx| {
6588 if !ignore_timeout {
6589 cx.background_executor()
6590 .timer(std::time::Duration::from_millis(blame_popover_delay))
6591 .await;
6592 }
6593 editor
6594 .update(cx, |editor, cx| {
6595 editor.inline_blame_popover_show_task.take();
6596 let Some(blame) = editor.blame.as_ref() else {
6597 return;
6598 };
6599 let blame = blame.read(cx);
6600 let details = blame.details_for_entry(&blame_entry);
6601 let markdown = cx.new(|cx| {
6602 Markdown::new(
6603 details
6604 .as_ref()
6605 .map(|message| message.message.clone())
6606 .unwrap_or_default(),
6607 None,
6608 None,
6609 cx,
6610 )
6611 });
6612 editor.inline_blame_popover = Some(InlineBlamePopover {
6613 position,
6614 hide_task: None,
6615 popover_bounds: None,
6616 popover_state: InlineBlamePopoverState {
6617 scroll_handle: ScrollHandle::new(),
6618 commit_message: details,
6619 markdown,
6620 },
6621 keyboard_grace: ignore_timeout,
6622 });
6623 cx.notify();
6624 })
6625 .ok();
6626 });
6627 self.inline_blame_popover_show_task = Some(show_task);
6628 }
6629 }
6630
6631 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6632 self.inline_blame_popover_show_task.take();
6633 if let Some(state) = &mut self.inline_blame_popover {
6634 let hide_task = cx.spawn(async move |editor, cx| {
6635 cx.background_executor()
6636 .timer(std::time::Duration::from_millis(100))
6637 .await;
6638 editor
6639 .update(cx, |editor, cx| {
6640 editor.inline_blame_popover.take();
6641 cx.notify();
6642 })
6643 .ok();
6644 });
6645 state.hide_task = Some(hide_task);
6646 }
6647 }
6648
6649 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6650 if self.pending_rename.is_some() {
6651 return None;
6652 }
6653
6654 let provider = self.semantics_provider.clone()?;
6655 let buffer = self.buffer.read(cx);
6656 let newest_selection = self.selections.newest_anchor().clone();
6657 let cursor_position = newest_selection.head();
6658 let (cursor_buffer, cursor_buffer_position) =
6659 buffer.text_anchor_for_position(cursor_position, cx)?;
6660 let (tail_buffer, tail_buffer_position) =
6661 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6662 if cursor_buffer != tail_buffer {
6663 return None;
6664 }
6665
6666 let snapshot = cursor_buffer.read(cx).snapshot();
6667 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6668 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6669 if start_word_range != end_word_range {
6670 self.document_highlights_task.take();
6671 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6672 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6673 return None;
6674 }
6675
6676 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6677 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6678 cx.background_executor()
6679 .timer(Duration::from_millis(debounce))
6680 .await;
6681
6682 let highlights = if let Some(highlights) = cx
6683 .update(|cx| {
6684 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6685 })
6686 .ok()
6687 .flatten()
6688 {
6689 highlights.await.log_err()
6690 } else {
6691 None
6692 };
6693
6694 if let Some(highlights) = highlights {
6695 this.update(cx, |this, cx| {
6696 if this.pending_rename.is_some() {
6697 return;
6698 }
6699
6700 let buffer = this.buffer.read(cx);
6701 if buffer
6702 .text_anchor_for_position(cursor_position, cx)
6703 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6704 {
6705 return;
6706 }
6707
6708 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6709 let mut write_ranges = Vec::new();
6710 let mut read_ranges = Vec::new();
6711 for highlight in highlights {
6712 let buffer_id = cursor_buffer.read(cx).remote_id();
6713 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6714 {
6715 let start = highlight
6716 .range
6717 .start
6718 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6719 let end = highlight
6720 .range
6721 .end
6722 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6723 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6724 continue;
6725 }
6726
6727 let range = Anchor {
6728 buffer_id: Some(buffer_id),
6729 excerpt_id,
6730 text_anchor: start,
6731 diff_base_anchor: None,
6732 }..Anchor {
6733 buffer_id: Some(buffer_id),
6734 excerpt_id,
6735 text_anchor: end,
6736 diff_base_anchor: None,
6737 };
6738 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6739 write_ranges.push(range);
6740 } else {
6741 read_ranges.push(range);
6742 }
6743 }
6744 }
6745
6746 this.highlight_background::<DocumentHighlightRead>(
6747 &read_ranges,
6748 |theme| theme.colors().editor_document_highlight_read_background,
6749 cx,
6750 );
6751 this.highlight_background::<DocumentHighlightWrite>(
6752 &write_ranges,
6753 |theme| theme.colors().editor_document_highlight_write_background,
6754 cx,
6755 );
6756 cx.notify();
6757 })
6758 .log_err();
6759 }
6760 }));
6761 None
6762 }
6763
6764 fn prepare_highlight_query_from_selection(
6765 &mut self,
6766 cx: &mut Context<Editor>,
6767 ) -> Option<(String, Range<Anchor>)> {
6768 if matches!(self.mode, EditorMode::SingleLine) {
6769 return None;
6770 }
6771 if !EditorSettings::get_global(cx).selection_highlight {
6772 return None;
6773 }
6774 if self.selections.count() != 1 || self.selections.line_mode {
6775 return None;
6776 }
6777 let selection = self.selections.newest::<Point>(cx);
6778 if selection.is_empty() || selection.start.row != selection.end.row {
6779 return None;
6780 }
6781 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6782 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6783 let query = multi_buffer_snapshot
6784 .text_for_range(selection_anchor_range.clone())
6785 .collect::<String>();
6786 if query.trim().is_empty() {
6787 return None;
6788 }
6789 Some((query, selection_anchor_range))
6790 }
6791
6792 fn update_selection_occurrence_highlights(
6793 &mut self,
6794 query_text: String,
6795 query_range: Range<Anchor>,
6796 multi_buffer_range_to_query: Range<Point>,
6797 use_debounce: bool,
6798 window: &mut Window,
6799 cx: &mut Context<Editor>,
6800 ) -> Task<()> {
6801 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6802 cx.spawn_in(window, async move |editor, cx| {
6803 if use_debounce {
6804 cx.background_executor()
6805 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6806 .await;
6807 }
6808 let match_task = cx.background_spawn(async move {
6809 let buffer_ranges = multi_buffer_snapshot
6810 .range_to_buffer_ranges(multi_buffer_range_to_query)
6811 .into_iter()
6812 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6813 let mut match_ranges = Vec::new();
6814 let Ok(regex) = project::search::SearchQuery::text(
6815 query_text.clone(),
6816 false,
6817 false,
6818 false,
6819 Default::default(),
6820 Default::default(),
6821 false,
6822 None,
6823 ) else {
6824 return Vec::default();
6825 };
6826 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6827 match_ranges.extend(
6828 regex
6829 .search(buffer_snapshot, Some(search_range.clone()))
6830 .await
6831 .into_iter()
6832 .filter_map(|match_range| {
6833 let match_start = buffer_snapshot
6834 .anchor_after(search_range.start + match_range.start);
6835 let match_end = buffer_snapshot
6836 .anchor_before(search_range.start + match_range.end);
6837 let match_anchor_range = Anchor::range_in_buffer(
6838 excerpt_id,
6839 buffer_snapshot.remote_id(),
6840 match_start..match_end,
6841 );
6842 (match_anchor_range != query_range).then_some(match_anchor_range)
6843 }),
6844 );
6845 }
6846 match_ranges
6847 });
6848 let match_ranges = match_task.await;
6849 editor
6850 .update_in(cx, |editor, _, cx| {
6851 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6852 if !match_ranges.is_empty() {
6853 editor.highlight_background::<SelectedTextHighlight>(
6854 &match_ranges,
6855 |theme| theme.colors().editor_document_highlight_bracket_background,
6856 cx,
6857 )
6858 }
6859 })
6860 .log_err();
6861 })
6862 }
6863
6864 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6865 struct NewlineFold;
6866 let type_id = std::any::TypeId::of::<NewlineFold>();
6867 if !self.mode.is_single_line() {
6868 return;
6869 }
6870 let snapshot = self.snapshot(window, cx);
6871 if snapshot.buffer_snapshot.max_point().row == 0 {
6872 return;
6873 }
6874 let task = cx.background_spawn(async move {
6875 let new_newlines = snapshot
6876 .buffer_chars_at(0)
6877 .filter_map(|(c, i)| {
6878 if c == '\n' {
6879 Some(
6880 snapshot.buffer_snapshot.anchor_after(i)
6881 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6882 )
6883 } else {
6884 None
6885 }
6886 })
6887 .collect::<Vec<_>>();
6888 let existing_newlines = snapshot
6889 .folds_in_range(0..snapshot.buffer_snapshot.len())
6890 .filter_map(|fold| {
6891 if fold.placeholder.type_tag == Some(type_id) {
6892 Some(fold.range.start..fold.range.end)
6893 } else {
6894 None
6895 }
6896 })
6897 .collect::<Vec<_>>();
6898
6899 (new_newlines, existing_newlines)
6900 });
6901 self.folding_newlines = cx.spawn(async move |this, cx| {
6902 let (new_newlines, existing_newlines) = task.await;
6903 if new_newlines == existing_newlines {
6904 return;
6905 }
6906 let placeholder = FoldPlaceholder {
6907 render: Arc::new(move |_, _, cx| {
6908 div()
6909 .bg(cx.theme().status().hint_background)
6910 .border_b_1()
6911 .size_full()
6912 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6913 .border_color(cx.theme().status().hint)
6914 .child("\\n")
6915 .into_any()
6916 }),
6917 constrain_width: false,
6918 merge_adjacent: false,
6919 type_tag: Some(type_id),
6920 };
6921 let creases = new_newlines
6922 .into_iter()
6923 .map(|range| Crease::simple(range, placeholder.clone()))
6924 .collect();
6925 this.update(cx, |this, cx| {
6926 this.display_map.update(cx, |display_map, cx| {
6927 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6928 display_map.fold(creases, cx);
6929 });
6930 })
6931 .ok();
6932 });
6933 }
6934
6935 fn refresh_selected_text_highlights(
6936 &mut self,
6937 on_buffer_edit: bool,
6938 window: &mut Window,
6939 cx: &mut Context<Editor>,
6940 ) {
6941 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6942 else {
6943 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6944 self.quick_selection_highlight_task.take();
6945 self.debounced_selection_highlight_task.take();
6946 return;
6947 };
6948 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6949 if on_buffer_edit
6950 || self
6951 .quick_selection_highlight_task
6952 .as_ref()
6953 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6954 {
6955 let multi_buffer_visible_start = self
6956 .scroll_manager
6957 .anchor()
6958 .anchor
6959 .to_point(&multi_buffer_snapshot);
6960 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6961 multi_buffer_visible_start
6962 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6963 Bias::Left,
6964 );
6965 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6966 self.quick_selection_highlight_task = Some((
6967 query_range.clone(),
6968 self.update_selection_occurrence_highlights(
6969 query_text.clone(),
6970 query_range.clone(),
6971 multi_buffer_visible_range,
6972 false,
6973 window,
6974 cx,
6975 ),
6976 ));
6977 }
6978 if on_buffer_edit
6979 || self
6980 .debounced_selection_highlight_task
6981 .as_ref()
6982 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6983 {
6984 let multi_buffer_start = multi_buffer_snapshot
6985 .anchor_before(0)
6986 .to_point(&multi_buffer_snapshot);
6987 let multi_buffer_end = multi_buffer_snapshot
6988 .anchor_after(multi_buffer_snapshot.len())
6989 .to_point(&multi_buffer_snapshot);
6990 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6991 self.debounced_selection_highlight_task = Some((
6992 query_range.clone(),
6993 self.update_selection_occurrence_highlights(
6994 query_text,
6995 query_range,
6996 multi_buffer_full_range,
6997 true,
6998 window,
6999 cx,
7000 ),
7001 ));
7002 }
7003 }
7004
7005 pub fn refresh_edit_prediction(
7006 &mut self,
7007 debounce: bool,
7008 user_requested: bool,
7009 window: &mut Window,
7010 cx: &mut Context<Self>,
7011 ) -> Option<()> {
7012 if DisableAiSettings::get_global(cx).disable_ai {
7013 return None;
7014 }
7015
7016 let provider = self.edit_prediction_provider()?;
7017 let cursor = self.selections.newest_anchor().head();
7018 let (buffer, cursor_buffer_position) =
7019 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7020
7021 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7022 self.discard_edit_prediction(false, cx);
7023 return None;
7024 }
7025
7026 if !user_requested
7027 && (!self.should_show_edit_predictions()
7028 || !self.is_focused(window)
7029 || buffer.read(cx).is_empty())
7030 {
7031 self.discard_edit_prediction(false, cx);
7032 return None;
7033 }
7034
7035 self.update_visible_edit_prediction(window, cx);
7036 provider.refresh(
7037 self.project.clone(),
7038 buffer,
7039 cursor_buffer_position,
7040 debounce,
7041 cx,
7042 );
7043 Some(())
7044 }
7045
7046 fn show_edit_predictions_in_menu(&self) -> bool {
7047 match self.edit_prediction_settings {
7048 EditPredictionSettings::Disabled => false,
7049 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7050 }
7051 }
7052
7053 pub fn edit_predictions_enabled(&self) -> bool {
7054 match self.edit_prediction_settings {
7055 EditPredictionSettings::Disabled => false,
7056 EditPredictionSettings::Enabled { .. } => true,
7057 }
7058 }
7059
7060 fn edit_prediction_requires_modifier(&self) -> bool {
7061 match self.edit_prediction_settings {
7062 EditPredictionSettings::Disabled => false,
7063 EditPredictionSettings::Enabled {
7064 preview_requires_modifier,
7065 ..
7066 } => preview_requires_modifier,
7067 }
7068 }
7069
7070 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7071 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7072 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7073 self.discard_edit_prediction(false, cx);
7074 } else {
7075 let selection = self.selections.newest_anchor();
7076 let cursor = selection.head();
7077
7078 if let Some((buffer, cursor_buffer_position)) =
7079 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7080 {
7081 self.edit_prediction_settings =
7082 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7083 }
7084 }
7085 }
7086
7087 fn edit_prediction_settings_at_position(
7088 &self,
7089 buffer: &Entity<Buffer>,
7090 buffer_position: language::Anchor,
7091 cx: &App,
7092 ) -> EditPredictionSettings {
7093 if !self.mode.is_full()
7094 || !self.show_edit_predictions_override.unwrap_or(true)
7095 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7096 {
7097 return EditPredictionSettings::Disabled;
7098 }
7099
7100 let buffer = buffer.read(cx);
7101
7102 let file = buffer.file();
7103
7104 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7105 return EditPredictionSettings::Disabled;
7106 };
7107
7108 let by_provider = matches!(
7109 self.menu_edit_predictions_policy,
7110 MenuEditPredictionsPolicy::ByProvider
7111 );
7112
7113 let show_in_menu = by_provider
7114 && self
7115 .edit_prediction_provider
7116 .as_ref()
7117 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7118
7119 let preview_requires_modifier =
7120 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7121
7122 EditPredictionSettings::Enabled {
7123 show_in_menu,
7124 preview_requires_modifier,
7125 }
7126 }
7127
7128 fn should_show_edit_predictions(&self) -> bool {
7129 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7130 }
7131
7132 pub fn edit_prediction_preview_is_active(&self) -> bool {
7133 matches!(
7134 self.edit_prediction_preview,
7135 EditPredictionPreview::Active { .. }
7136 )
7137 }
7138
7139 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7140 let cursor = self.selections.newest_anchor().head();
7141 if let Some((buffer, cursor_position)) =
7142 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7143 {
7144 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7145 } else {
7146 false
7147 }
7148 }
7149
7150 pub fn supports_minimap(&self, cx: &App) -> bool {
7151 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7152 }
7153
7154 fn edit_predictions_enabled_in_buffer(
7155 &self,
7156 buffer: &Entity<Buffer>,
7157 buffer_position: language::Anchor,
7158 cx: &App,
7159 ) -> bool {
7160 maybe!({
7161 if self.read_only(cx) {
7162 return Some(false);
7163 }
7164 let provider = self.edit_prediction_provider()?;
7165 if !provider.is_enabled(buffer, buffer_position, cx) {
7166 return Some(false);
7167 }
7168 let buffer = buffer.read(cx);
7169 let Some(file) = buffer.file() else {
7170 return Some(true);
7171 };
7172 let settings = all_language_settings(Some(file), cx);
7173 Some(settings.edit_predictions_enabled_for_file(file, cx))
7174 })
7175 .unwrap_or(false)
7176 }
7177
7178 fn cycle_edit_prediction(
7179 &mut self,
7180 direction: Direction,
7181 window: &mut Window,
7182 cx: &mut Context<Self>,
7183 ) -> Option<()> {
7184 let provider = self.edit_prediction_provider()?;
7185 let cursor = self.selections.newest_anchor().head();
7186 let (buffer, cursor_buffer_position) =
7187 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7188 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7189 return None;
7190 }
7191
7192 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7193 self.update_visible_edit_prediction(window, cx);
7194
7195 Some(())
7196 }
7197
7198 pub fn show_edit_prediction(
7199 &mut self,
7200 _: &ShowEditPrediction,
7201 window: &mut Window,
7202 cx: &mut Context<Self>,
7203 ) {
7204 if !self.has_active_edit_prediction() {
7205 self.refresh_edit_prediction(false, true, window, cx);
7206 return;
7207 }
7208
7209 self.update_visible_edit_prediction(window, cx);
7210 }
7211
7212 pub fn display_cursor_names(
7213 &mut self,
7214 _: &DisplayCursorNames,
7215 window: &mut Window,
7216 cx: &mut Context<Self>,
7217 ) {
7218 self.show_cursor_names(window, cx);
7219 }
7220
7221 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7222 self.show_cursor_names = true;
7223 cx.notify();
7224 cx.spawn_in(window, async move |this, cx| {
7225 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7226 this.update(cx, |this, cx| {
7227 this.show_cursor_names = false;
7228 cx.notify()
7229 })
7230 .ok()
7231 })
7232 .detach();
7233 }
7234
7235 pub fn next_edit_prediction(
7236 &mut self,
7237 _: &NextEditPrediction,
7238 window: &mut Window,
7239 cx: &mut Context<Self>,
7240 ) {
7241 if self.has_active_edit_prediction() {
7242 self.cycle_edit_prediction(Direction::Next, window, cx);
7243 } else {
7244 let is_copilot_disabled = self
7245 .refresh_edit_prediction(false, true, window, cx)
7246 .is_none();
7247 if is_copilot_disabled {
7248 cx.propagate();
7249 }
7250 }
7251 }
7252
7253 pub fn previous_edit_prediction(
7254 &mut self,
7255 _: &PreviousEditPrediction,
7256 window: &mut Window,
7257 cx: &mut Context<Self>,
7258 ) {
7259 if self.has_active_edit_prediction() {
7260 self.cycle_edit_prediction(Direction::Prev, window, cx);
7261 } else {
7262 let is_copilot_disabled = self
7263 .refresh_edit_prediction(false, true, window, cx)
7264 .is_none();
7265 if is_copilot_disabled {
7266 cx.propagate();
7267 }
7268 }
7269 }
7270
7271 pub fn accept_edit_prediction(
7272 &mut self,
7273 _: &AcceptEditPrediction,
7274 window: &mut Window,
7275 cx: &mut Context<Self>,
7276 ) {
7277 if self.show_edit_predictions_in_menu() {
7278 self.hide_context_menu(window, cx);
7279 }
7280
7281 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7282 return;
7283 };
7284
7285 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7286
7287 match &active_edit_prediction.completion {
7288 EditPrediction::Move { target, .. } => {
7289 let target = *target;
7290
7291 if let Some(position_map) = &self.last_position_map {
7292 if position_map
7293 .visible_row_range
7294 .contains(&target.to_display_point(&position_map.snapshot).row())
7295 || !self.edit_prediction_requires_modifier()
7296 {
7297 self.unfold_ranges(&[target..target], true, false, cx);
7298 // Note that this is also done in vim's handler of the Tab action.
7299 self.change_selections(
7300 SelectionEffects::scroll(Autoscroll::newest()),
7301 window,
7302 cx,
7303 |selections| {
7304 selections.select_anchor_ranges([target..target]);
7305 },
7306 );
7307 self.clear_row_highlights::<EditPredictionPreview>();
7308
7309 self.edit_prediction_preview
7310 .set_previous_scroll_position(None);
7311 } else {
7312 self.edit_prediction_preview
7313 .set_previous_scroll_position(Some(
7314 position_map.snapshot.scroll_anchor,
7315 ));
7316
7317 self.highlight_rows::<EditPredictionPreview>(
7318 target..target,
7319 cx.theme().colors().editor_highlighted_line_background,
7320 RowHighlightOptions {
7321 autoscroll: true,
7322 ..Default::default()
7323 },
7324 cx,
7325 );
7326 self.request_autoscroll(Autoscroll::fit(), cx);
7327 }
7328 }
7329 }
7330 EditPrediction::Edit { edits, .. } => {
7331 if let Some(provider) = self.edit_prediction_provider() {
7332 provider.accept(cx);
7333 }
7334
7335 // Store the transaction ID and selections before applying the edit
7336 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7337
7338 let snapshot = self.buffer.read(cx).snapshot(cx);
7339 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7340
7341 self.buffer.update(cx, |buffer, cx| {
7342 buffer.edit(edits.iter().cloned(), None, cx)
7343 });
7344
7345 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7346 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7347 });
7348
7349 let selections = self.selections.disjoint_anchors();
7350 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7351 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7352 if has_new_transaction {
7353 self.selection_history
7354 .insert_transaction(transaction_id_now, selections);
7355 }
7356 }
7357
7358 self.update_visible_edit_prediction(window, cx);
7359 if self.active_edit_prediction.is_none() {
7360 self.refresh_edit_prediction(true, true, window, cx);
7361 }
7362
7363 cx.notify();
7364 }
7365 }
7366
7367 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7368 }
7369
7370 pub fn accept_partial_edit_prediction(
7371 &mut self,
7372 _: &AcceptPartialEditPrediction,
7373 window: &mut Window,
7374 cx: &mut Context<Self>,
7375 ) {
7376 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7377 return;
7378 };
7379 if self.selections.count() != 1 {
7380 return;
7381 }
7382
7383 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7384
7385 match &active_edit_prediction.completion {
7386 EditPrediction::Move { target, .. } => {
7387 let target = *target;
7388 self.change_selections(
7389 SelectionEffects::scroll(Autoscroll::newest()),
7390 window,
7391 cx,
7392 |selections| {
7393 selections.select_anchor_ranges([target..target]);
7394 },
7395 );
7396 }
7397 EditPrediction::Edit { edits, .. } => {
7398 // Find an insertion that starts at the cursor position.
7399 let snapshot = self.buffer.read(cx).snapshot(cx);
7400 let cursor_offset = self.selections.newest::<usize>(cx).head();
7401 let insertion = edits.iter().find_map(|(range, text)| {
7402 let range = range.to_offset(&snapshot);
7403 if range.is_empty() && range.start == cursor_offset {
7404 Some(text)
7405 } else {
7406 None
7407 }
7408 });
7409
7410 if let Some(text) = insertion {
7411 let mut partial_completion = text
7412 .chars()
7413 .by_ref()
7414 .take_while(|c| c.is_alphabetic())
7415 .collect::<String>();
7416 if partial_completion.is_empty() {
7417 partial_completion = text
7418 .chars()
7419 .by_ref()
7420 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7421 .collect::<String>();
7422 }
7423
7424 cx.emit(EditorEvent::InputHandled {
7425 utf16_range_to_replace: None,
7426 text: partial_completion.clone().into(),
7427 });
7428
7429 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7430
7431 self.refresh_edit_prediction(true, true, window, cx);
7432 cx.notify();
7433 } else {
7434 self.accept_edit_prediction(&Default::default(), window, cx);
7435 }
7436 }
7437 }
7438 }
7439
7440 fn discard_edit_prediction(
7441 &mut self,
7442 should_report_edit_prediction_event: bool,
7443 cx: &mut Context<Self>,
7444 ) -> bool {
7445 if should_report_edit_prediction_event {
7446 let completion_id = self
7447 .active_edit_prediction
7448 .as_ref()
7449 .and_then(|active_completion| active_completion.completion_id.clone());
7450
7451 self.report_edit_prediction_event(completion_id, false, cx);
7452 }
7453
7454 if let Some(provider) = self.edit_prediction_provider() {
7455 provider.discard(cx);
7456 }
7457
7458 self.take_active_edit_prediction(cx)
7459 }
7460
7461 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7462 let Some(provider) = self.edit_prediction_provider() else {
7463 return;
7464 };
7465
7466 let Some((_, buffer, _)) = self
7467 .buffer
7468 .read(cx)
7469 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7470 else {
7471 return;
7472 };
7473
7474 let extension = buffer
7475 .read(cx)
7476 .file()
7477 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7478
7479 let event_type = match accepted {
7480 true => "Edit Prediction Accepted",
7481 false => "Edit Prediction Discarded",
7482 };
7483 telemetry::event!(
7484 event_type,
7485 provider = provider.name(),
7486 prediction_id = id,
7487 suggestion_accepted = accepted,
7488 file_extension = extension,
7489 );
7490 }
7491
7492 pub fn has_active_edit_prediction(&self) -> bool {
7493 self.active_edit_prediction.is_some()
7494 }
7495
7496 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7497 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7498 return false;
7499 };
7500
7501 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7502 self.clear_highlights::<EditPredictionHighlight>(cx);
7503 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7504 true
7505 }
7506
7507 /// Returns true when we're displaying the edit prediction popover below the cursor
7508 /// like we are not previewing and the LSP autocomplete menu is visible
7509 /// or we are in `when_holding_modifier` mode.
7510 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7511 if self.edit_prediction_preview_is_active()
7512 || !self.show_edit_predictions_in_menu()
7513 || !self.edit_predictions_enabled()
7514 {
7515 return false;
7516 }
7517
7518 if self.has_visible_completions_menu() {
7519 return true;
7520 }
7521
7522 has_completion && self.edit_prediction_requires_modifier()
7523 }
7524
7525 fn handle_modifiers_changed(
7526 &mut self,
7527 modifiers: Modifiers,
7528 position_map: &PositionMap,
7529 window: &mut Window,
7530 cx: &mut Context<Self>,
7531 ) {
7532 if self.show_edit_predictions_in_menu() {
7533 self.update_edit_prediction_preview(&modifiers, window, cx);
7534 }
7535
7536 self.update_selection_mode(&modifiers, position_map, window, cx);
7537
7538 let mouse_position = window.mouse_position();
7539 if !position_map.text_hitbox.is_hovered(window) {
7540 return;
7541 }
7542
7543 self.update_hovered_link(
7544 position_map.point_for_position(mouse_position),
7545 &position_map.snapshot,
7546 modifiers,
7547 window,
7548 cx,
7549 )
7550 }
7551
7552 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7553 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7554 if invert {
7555 match multi_cursor_setting {
7556 MultiCursorModifier::Alt => modifiers.alt,
7557 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7558 }
7559 } else {
7560 match multi_cursor_setting {
7561 MultiCursorModifier::Alt => modifiers.secondary(),
7562 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7563 }
7564 }
7565 }
7566
7567 fn columnar_selection_mode(
7568 modifiers: &Modifiers,
7569 cx: &mut Context<Self>,
7570 ) -> Option<ColumnarMode> {
7571 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7572 if Self::multi_cursor_modifier(false, modifiers, cx) {
7573 Some(ColumnarMode::FromMouse)
7574 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7575 Some(ColumnarMode::FromSelection)
7576 } else {
7577 None
7578 }
7579 } else {
7580 None
7581 }
7582 }
7583
7584 fn update_selection_mode(
7585 &mut self,
7586 modifiers: &Modifiers,
7587 position_map: &PositionMap,
7588 window: &mut Window,
7589 cx: &mut Context<Self>,
7590 ) {
7591 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7592 return;
7593 };
7594 if self.selections.pending.is_none() {
7595 return;
7596 }
7597
7598 let mouse_position = window.mouse_position();
7599 let point_for_position = position_map.point_for_position(mouse_position);
7600 let position = point_for_position.previous_valid;
7601
7602 self.select(
7603 SelectPhase::BeginColumnar {
7604 position,
7605 reset: false,
7606 mode,
7607 goal_column: point_for_position.exact_unclipped.column(),
7608 },
7609 window,
7610 cx,
7611 );
7612 }
7613
7614 fn update_edit_prediction_preview(
7615 &mut self,
7616 modifiers: &Modifiers,
7617 window: &mut Window,
7618 cx: &mut Context<Self>,
7619 ) {
7620 let mut modifiers_held = false;
7621 if let Some(accept_keystroke) = self
7622 .accept_edit_prediction_keybind(false, window, cx)
7623 .keystroke()
7624 {
7625 modifiers_held = modifiers_held
7626 || (&accept_keystroke.modifiers == modifiers
7627 && accept_keystroke.modifiers.modified());
7628 };
7629 if let Some(accept_partial_keystroke) = self
7630 .accept_edit_prediction_keybind(true, window, cx)
7631 .keystroke()
7632 {
7633 modifiers_held = modifiers_held
7634 || (&accept_partial_keystroke.modifiers == modifiers
7635 && accept_partial_keystroke.modifiers.modified());
7636 }
7637
7638 if modifiers_held {
7639 if matches!(
7640 self.edit_prediction_preview,
7641 EditPredictionPreview::Inactive { .. }
7642 ) {
7643 self.edit_prediction_preview = EditPredictionPreview::Active {
7644 previous_scroll_position: None,
7645 since: Instant::now(),
7646 };
7647
7648 self.update_visible_edit_prediction(window, cx);
7649 cx.notify();
7650 }
7651 } else if let EditPredictionPreview::Active {
7652 previous_scroll_position,
7653 since,
7654 } = self.edit_prediction_preview
7655 {
7656 if let (Some(previous_scroll_position), Some(position_map)) =
7657 (previous_scroll_position, self.last_position_map.as_ref())
7658 {
7659 self.set_scroll_position(
7660 previous_scroll_position
7661 .scroll_position(&position_map.snapshot.display_snapshot),
7662 window,
7663 cx,
7664 );
7665 }
7666
7667 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7668 released_too_fast: since.elapsed() < Duration::from_millis(200),
7669 };
7670 self.clear_row_highlights::<EditPredictionPreview>();
7671 self.update_visible_edit_prediction(window, cx);
7672 cx.notify();
7673 }
7674 }
7675
7676 fn update_visible_edit_prediction(
7677 &mut self,
7678 _window: &mut Window,
7679 cx: &mut Context<Self>,
7680 ) -> Option<()> {
7681 if DisableAiSettings::get_global(cx).disable_ai {
7682 return None;
7683 }
7684
7685 let selection = self.selections.newest_anchor();
7686 let cursor = selection.head();
7687 let multibuffer = self.buffer.read(cx).snapshot(cx);
7688 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7689 let excerpt_id = cursor.excerpt_id;
7690
7691 let show_in_menu = self.show_edit_predictions_in_menu();
7692 let completions_menu_has_precedence = !show_in_menu
7693 && (self.context_menu.borrow().is_some()
7694 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7695
7696 if completions_menu_has_precedence
7697 || !offset_selection.is_empty()
7698 || self
7699 .active_edit_prediction
7700 .as_ref()
7701 .is_some_and(|completion| {
7702 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7703 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7704 !invalidation_range.contains(&offset_selection.head())
7705 })
7706 {
7707 self.discard_edit_prediction(false, cx);
7708 return None;
7709 }
7710
7711 self.take_active_edit_prediction(cx);
7712 let Some(provider) = self.edit_prediction_provider() else {
7713 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7714 return None;
7715 };
7716
7717 let (buffer, cursor_buffer_position) =
7718 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7719
7720 self.edit_prediction_settings =
7721 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7722
7723 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7724 self.discard_edit_prediction(false, cx);
7725 return None;
7726 };
7727
7728 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7729
7730 if self.edit_prediction_indent_conflict {
7731 let cursor_point = cursor.to_point(&multibuffer);
7732
7733 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7734
7735 if let Some((_, indent)) = indents.iter().next()
7736 && indent.len == cursor_point.column
7737 {
7738 self.edit_prediction_indent_conflict = false;
7739 }
7740 }
7741
7742 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7743 let edits = edit_prediction
7744 .edits
7745 .into_iter()
7746 .flat_map(|(range, new_text)| {
7747 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7748 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7749 Some((start..end, new_text))
7750 })
7751 .collect::<Vec<_>>();
7752 if edits.is_empty() {
7753 return None;
7754 }
7755
7756 let first_edit_start = edits.first().unwrap().0.start;
7757 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7758 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7759
7760 let last_edit_end = edits.last().unwrap().0.end;
7761 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7762 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7763
7764 let cursor_row = cursor.to_point(&multibuffer).row;
7765
7766 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7767
7768 let mut inlay_ids = Vec::new();
7769 let invalidation_row_range;
7770 let move_invalidation_row_range = if cursor_row < edit_start_row {
7771 Some(cursor_row..edit_end_row)
7772 } else if cursor_row > edit_end_row {
7773 Some(edit_start_row..cursor_row)
7774 } else {
7775 None
7776 };
7777 let supports_jump = self
7778 .edit_prediction_provider
7779 .as_ref()
7780 .map(|provider| provider.provider.supports_jump_to_edit())
7781 .unwrap_or(true);
7782
7783 let is_move = supports_jump
7784 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7785 let completion = if is_move {
7786 invalidation_row_range =
7787 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7788 let target = first_edit_start;
7789 EditPrediction::Move { target, snapshot }
7790 } else {
7791 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7792 && !self.edit_predictions_hidden_for_vim_mode;
7793
7794 if show_completions_in_buffer {
7795 if edits
7796 .iter()
7797 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7798 {
7799 let mut inlays = Vec::new();
7800 for (range, new_text) in &edits {
7801 let inlay = Inlay::edit_prediction(
7802 post_inc(&mut self.next_inlay_id),
7803 range.start,
7804 new_text.as_str(),
7805 );
7806 inlay_ids.push(inlay.id);
7807 inlays.push(inlay);
7808 }
7809
7810 self.splice_inlays(&[], inlays, cx);
7811 } else {
7812 let background_color = cx.theme().status().deleted_background;
7813 self.highlight_text::<EditPredictionHighlight>(
7814 edits.iter().map(|(range, _)| range.clone()).collect(),
7815 HighlightStyle {
7816 background_color: Some(background_color),
7817 ..Default::default()
7818 },
7819 cx,
7820 );
7821 }
7822 }
7823
7824 invalidation_row_range = edit_start_row..edit_end_row;
7825
7826 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7827 if provider.show_tab_accept_marker() {
7828 EditDisplayMode::TabAccept
7829 } else {
7830 EditDisplayMode::Inline
7831 }
7832 } else {
7833 EditDisplayMode::DiffPopover
7834 };
7835
7836 EditPrediction::Edit {
7837 edits,
7838 edit_preview: edit_prediction.edit_preview,
7839 display_mode,
7840 snapshot,
7841 }
7842 };
7843
7844 let invalidation_range = multibuffer
7845 .anchor_before(Point::new(invalidation_row_range.start, 0))
7846 ..multibuffer.anchor_after(Point::new(
7847 invalidation_row_range.end,
7848 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7849 ));
7850
7851 self.stale_edit_prediction_in_menu = None;
7852 self.active_edit_prediction = Some(EditPredictionState {
7853 inlay_ids,
7854 completion,
7855 completion_id: edit_prediction.id,
7856 invalidation_range,
7857 });
7858
7859 cx.notify();
7860
7861 Some(())
7862 }
7863
7864 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7865 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7866 }
7867
7868 fn clear_tasks(&mut self) {
7869 self.tasks.clear()
7870 }
7871
7872 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7873 if self.tasks.insert(key, value).is_some() {
7874 // This case should hopefully be rare, but just in case...
7875 log::error!(
7876 "multiple different run targets found on a single line, only the last target will be rendered"
7877 )
7878 }
7879 }
7880
7881 /// Get all display points of breakpoints that will be rendered within editor
7882 ///
7883 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7884 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7885 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7886 fn active_breakpoints(
7887 &self,
7888 range: Range<DisplayRow>,
7889 window: &mut Window,
7890 cx: &mut Context<Self>,
7891 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7892 let mut breakpoint_display_points = HashMap::default();
7893
7894 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7895 return breakpoint_display_points;
7896 };
7897
7898 let snapshot = self.snapshot(window, cx);
7899
7900 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7901 let Some(project) = self.project() else {
7902 return breakpoint_display_points;
7903 };
7904
7905 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7906 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7907
7908 for (buffer_snapshot, range, excerpt_id) in
7909 multi_buffer_snapshot.range_to_buffer_ranges(range)
7910 {
7911 let Some(buffer) = project
7912 .read(cx)
7913 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7914 else {
7915 continue;
7916 };
7917 let breakpoints = breakpoint_store.read(cx).breakpoints(
7918 &buffer,
7919 Some(
7920 buffer_snapshot.anchor_before(range.start)
7921 ..buffer_snapshot.anchor_after(range.end),
7922 ),
7923 buffer_snapshot,
7924 cx,
7925 );
7926 for (breakpoint, state) in breakpoints {
7927 let multi_buffer_anchor =
7928 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7929 let position = multi_buffer_anchor
7930 .to_point(multi_buffer_snapshot)
7931 .to_display_point(&snapshot);
7932
7933 breakpoint_display_points.insert(
7934 position.row(),
7935 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7936 );
7937 }
7938 }
7939
7940 breakpoint_display_points
7941 }
7942
7943 fn breakpoint_context_menu(
7944 &self,
7945 anchor: Anchor,
7946 window: &mut Window,
7947 cx: &mut Context<Self>,
7948 ) -> Entity<ui::ContextMenu> {
7949 let weak_editor = cx.weak_entity();
7950 let focus_handle = self.focus_handle(cx);
7951
7952 let row = self
7953 .buffer
7954 .read(cx)
7955 .snapshot(cx)
7956 .summary_for_anchor::<Point>(&anchor)
7957 .row;
7958
7959 let breakpoint = self
7960 .breakpoint_at_row(row, window, cx)
7961 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7962
7963 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7964 "Edit Log Breakpoint"
7965 } else {
7966 "Set Log Breakpoint"
7967 };
7968
7969 let condition_breakpoint_msg = if breakpoint
7970 .as_ref()
7971 .is_some_and(|bp| bp.1.condition.is_some())
7972 {
7973 "Edit Condition Breakpoint"
7974 } else {
7975 "Set Condition Breakpoint"
7976 };
7977
7978 let hit_condition_breakpoint_msg = if breakpoint
7979 .as_ref()
7980 .is_some_and(|bp| bp.1.hit_condition.is_some())
7981 {
7982 "Edit Hit Condition Breakpoint"
7983 } else {
7984 "Set Hit Condition Breakpoint"
7985 };
7986
7987 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7988 "Unset Breakpoint"
7989 } else {
7990 "Set Breakpoint"
7991 };
7992
7993 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7994
7995 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7996 BreakpointState::Enabled => Some("Disable"),
7997 BreakpointState::Disabled => Some("Enable"),
7998 });
7999
8000 let (anchor, breakpoint) =
8001 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8002
8003 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8004 menu.on_blur_subscription(Subscription::new(|| {}))
8005 .context(focus_handle)
8006 .when(run_to_cursor, |this| {
8007 let weak_editor = weak_editor.clone();
8008 this.entry("Run to cursor", None, move |window, cx| {
8009 weak_editor
8010 .update(cx, |editor, cx| {
8011 editor.change_selections(
8012 SelectionEffects::no_scroll(),
8013 window,
8014 cx,
8015 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8016 );
8017 })
8018 .ok();
8019
8020 window.dispatch_action(Box::new(RunToCursor), cx);
8021 })
8022 .separator()
8023 })
8024 .when_some(toggle_state_msg, |this, msg| {
8025 this.entry(msg, None, {
8026 let weak_editor = weak_editor.clone();
8027 let breakpoint = breakpoint.clone();
8028 move |_window, cx| {
8029 weak_editor
8030 .update(cx, |this, cx| {
8031 this.edit_breakpoint_at_anchor(
8032 anchor,
8033 breakpoint.as_ref().clone(),
8034 BreakpointEditAction::InvertState,
8035 cx,
8036 );
8037 })
8038 .log_err();
8039 }
8040 })
8041 })
8042 .entry(set_breakpoint_msg, None, {
8043 let weak_editor = weak_editor.clone();
8044 let breakpoint = breakpoint.clone();
8045 move |_window, cx| {
8046 weak_editor
8047 .update(cx, |this, cx| {
8048 this.edit_breakpoint_at_anchor(
8049 anchor,
8050 breakpoint.as_ref().clone(),
8051 BreakpointEditAction::Toggle,
8052 cx,
8053 );
8054 })
8055 .log_err();
8056 }
8057 })
8058 .entry(log_breakpoint_msg, None, {
8059 let breakpoint = breakpoint.clone();
8060 let weak_editor = weak_editor.clone();
8061 move |window, cx| {
8062 weak_editor
8063 .update(cx, |this, cx| {
8064 this.add_edit_breakpoint_block(
8065 anchor,
8066 breakpoint.as_ref(),
8067 BreakpointPromptEditAction::Log,
8068 window,
8069 cx,
8070 );
8071 })
8072 .log_err();
8073 }
8074 })
8075 .entry(condition_breakpoint_msg, None, {
8076 let breakpoint = breakpoint.clone();
8077 let weak_editor = weak_editor.clone();
8078 move |window, cx| {
8079 weak_editor
8080 .update(cx, |this, cx| {
8081 this.add_edit_breakpoint_block(
8082 anchor,
8083 breakpoint.as_ref(),
8084 BreakpointPromptEditAction::Condition,
8085 window,
8086 cx,
8087 );
8088 })
8089 .log_err();
8090 }
8091 })
8092 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8093 weak_editor
8094 .update(cx, |this, cx| {
8095 this.add_edit_breakpoint_block(
8096 anchor,
8097 breakpoint.as_ref(),
8098 BreakpointPromptEditAction::HitCondition,
8099 window,
8100 cx,
8101 );
8102 })
8103 .log_err();
8104 })
8105 })
8106 }
8107
8108 fn render_breakpoint(
8109 &self,
8110 position: Anchor,
8111 row: DisplayRow,
8112 breakpoint: &Breakpoint,
8113 state: Option<BreakpointSessionState>,
8114 cx: &mut Context<Self>,
8115 ) -> IconButton {
8116 let is_rejected = state.is_some_and(|s| !s.verified);
8117 // Is it a breakpoint that shows up when hovering over gutter?
8118 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8119 (false, false),
8120 |PhantomBreakpointIndicator {
8121 is_active,
8122 display_row,
8123 collides_with_existing_breakpoint,
8124 }| {
8125 (
8126 is_active && display_row == row,
8127 collides_with_existing_breakpoint,
8128 )
8129 },
8130 );
8131
8132 let (color, icon) = {
8133 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8134 (false, false) => ui::IconName::DebugBreakpoint,
8135 (true, false) => ui::IconName::DebugLogBreakpoint,
8136 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8137 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8138 };
8139
8140 let color = if is_phantom {
8141 Color::Hint
8142 } else if is_rejected {
8143 Color::Disabled
8144 } else {
8145 Color::Debugger
8146 };
8147
8148 (color, icon)
8149 };
8150
8151 let breakpoint = Arc::from(breakpoint.clone());
8152
8153 let alt_as_text = gpui::Keystroke {
8154 modifiers: Modifiers::secondary_key(),
8155 ..Default::default()
8156 };
8157 let primary_action_text = if breakpoint.is_disabled() {
8158 "Enable breakpoint"
8159 } else if is_phantom && !collides_with_existing {
8160 "Set breakpoint"
8161 } else {
8162 "Unset breakpoint"
8163 };
8164 let focus_handle = self.focus_handle.clone();
8165
8166 let meta = if is_rejected {
8167 SharedString::from("No executable code is associated with this line.")
8168 } else if collides_with_existing && !breakpoint.is_disabled() {
8169 SharedString::from(format!(
8170 "{alt_as_text}-click to disable,\nright-click for more options."
8171 ))
8172 } else {
8173 SharedString::from("Right-click for more options.")
8174 };
8175 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8176 .icon_size(IconSize::XSmall)
8177 .size(ui::ButtonSize::None)
8178 .when(is_rejected, |this| {
8179 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8180 })
8181 .icon_color(color)
8182 .style(ButtonStyle::Transparent)
8183 .on_click(cx.listener({
8184 move |editor, event: &ClickEvent, window, cx| {
8185 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8186 BreakpointEditAction::InvertState
8187 } else {
8188 BreakpointEditAction::Toggle
8189 };
8190
8191 window.focus(&editor.focus_handle(cx));
8192 editor.edit_breakpoint_at_anchor(
8193 position,
8194 breakpoint.as_ref().clone(),
8195 edit_action,
8196 cx,
8197 );
8198 }
8199 }))
8200 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8201 editor.set_breakpoint_context_menu(
8202 row,
8203 Some(position),
8204 event.position(),
8205 window,
8206 cx,
8207 );
8208 }))
8209 .tooltip(move |window, cx| {
8210 Tooltip::with_meta_in(
8211 primary_action_text,
8212 Some(&ToggleBreakpoint),
8213 meta.clone(),
8214 &focus_handle,
8215 window,
8216 cx,
8217 )
8218 })
8219 }
8220
8221 fn build_tasks_context(
8222 project: &Entity<Project>,
8223 buffer: &Entity<Buffer>,
8224 buffer_row: u32,
8225 tasks: &Arc<RunnableTasks>,
8226 cx: &mut Context<Self>,
8227 ) -> Task<Option<task::TaskContext>> {
8228 let position = Point::new(buffer_row, tasks.column);
8229 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8230 let location = Location {
8231 buffer: buffer.clone(),
8232 range: range_start..range_start,
8233 };
8234 // Fill in the environmental variables from the tree-sitter captures
8235 let mut captured_task_variables = TaskVariables::default();
8236 for (capture_name, value) in tasks.extra_variables.clone() {
8237 captured_task_variables.insert(
8238 task::VariableName::Custom(capture_name.into()),
8239 value.clone(),
8240 );
8241 }
8242 project.update(cx, |project, cx| {
8243 project.task_store().update(cx, |task_store, cx| {
8244 task_store.task_context_for_location(captured_task_variables, location, cx)
8245 })
8246 })
8247 }
8248
8249 pub fn spawn_nearest_task(
8250 &mut self,
8251 action: &SpawnNearestTask,
8252 window: &mut Window,
8253 cx: &mut Context<Self>,
8254 ) {
8255 let Some((workspace, _)) = self.workspace.clone() else {
8256 return;
8257 };
8258 let Some(project) = self.project.clone() else {
8259 return;
8260 };
8261
8262 // Try to find a closest, enclosing node using tree-sitter that has a task
8263 let Some((buffer, buffer_row, tasks)) = self
8264 .find_enclosing_node_task(cx)
8265 // Or find the task that's closest in row-distance.
8266 .or_else(|| self.find_closest_task(cx))
8267 else {
8268 return;
8269 };
8270
8271 let reveal_strategy = action.reveal;
8272 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8273 cx.spawn_in(window, async move |_, cx| {
8274 let context = task_context.await?;
8275 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8276
8277 let resolved = &mut resolved_task.resolved;
8278 resolved.reveal = reveal_strategy;
8279
8280 workspace
8281 .update_in(cx, |workspace, window, cx| {
8282 workspace.schedule_resolved_task(
8283 task_source_kind,
8284 resolved_task,
8285 false,
8286 window,
8287 cx,
8288 );
8289 })
8290 .ok()
8291 })
8292 .detach();
8293 }
8294
8295 fn find_closest_task(
8296 &mut self,
8297 cx: &mut Context<Self>,
8298 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8299 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8300
8301 let ((buffer_id, row), tasks) = self
8302 .tasks
8303 .iter()
8304 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8305
8306 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8307 let tasks = Arc::new(tasks.to_owned());
8308 Some((buffer, *row, tasks))
8309 }
8310
8311 fn find_enclosing_node_task(
8312 &mut self,
8313 cx: &mut Context<Self>,
8314 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8315 let snapshot = self.buffer.read(cx).snapshot(cx);
8316 let offset = self.selections.newest::<usize>(cx).head();
8317 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8318 let buffer_id = excerpt.buffer().remote_id();
8319
8320 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8321 let mut cursor = layer.node().walk();
8322
8323 while cursor.goto_first_child_for_byte(offset).is_some() {
8324 if cursor.node().end_byte() == offset {
8325 cursor.goto_next_sibling();
8326 }
8327 }
8328
8329 // Ascend to the smallest ancestor that contains the range and has a task.
8330 loop {
8331 let node = cursor.node();
8332 let node_range = node.byte_range();
8333 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8334
8335 // Check if this node contains our offset
8336 if node_range.start <= offset && node_range.end >= offset {
8337 // If it contains offset, check for task
8338 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8339 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8340 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8341 }
8342 }
8343
8344 if !cursor.goto_parent() {
8345 break;
8346 }
8347 }
8348 None
8349 }
8350
8351 fn render_run_indicator(
8352 &self,
8353 _style: &EditorStyle,
8354 is_active: bool,
8355 row: DisplayRow,
8356 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8357 cx: &mut Context<Self>,
8358 ) -> IconButton {
8359 let color = Color::Muted;
8360 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8361
8362 IconButton::new(
8363 ("run_indicator", row.0 as usize),
8364 ui::IconName::PlayOutlined,
8365 )
8366 .shape(ui::IconButtonShape::Square)
8367 .icon_size(IconSize::XSmall)
8368 .icon_color(color)
8369 .toggle_state(is_active)
8370 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8371 let quick_launch = match e {
8372 ClickEvent::Keyboard(_) => true,
8373 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8374 };
8375
8376 window.focus(&editor.focus_handle(cx));
8377 editor.toggle_code_actions(
8378 &ToggleCodeActions {
8379 deployed_from: Some(CodeActionSource::RunMenu(row)),
8380 quick_launch,
8381 },
8382 window,
8383 cx,
8384 );
8385 }))
8386 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8387 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8388 }))
8389 }
8390
8391 pub fn context_menu_visible(&self) -> bool {
8392 !self.edit_prediction_preview_is_active()
8393 && self
8394 .context_menu
8395 .borrow()
8396 .as_ref()
8397 .is_some_and(|menu| menu.visible())
8398 }
8399
8400 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8401 self.context_menu
8402 .borrow()
8403 .as_ref()
8404 .map(|menu| menu.origin())
8405 }
8406
8407 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8408 self.context_menu_options = Some(options);
8409 }
8410
8411 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8412 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8413
8414 fn render_edit_prediction_popover(
8415 &mut self,
8416 text_bounds: &Bounds<Pixels>,
8417 content_origin: gpui::Point<Pixels>,
8418 right_margin: Pixels,
8419 editor_snapshot: &EditorSnapshot,
8420 visible_row_range: Range<DisplayRow>,
8421 scroll_top: f32,
8422 scroll_bottom: f32,
8423 line_layouts: &[LineWithInvisibles],
8424 line_height: Pixels,
8425 scroll_pixel_position: gpui::Point<Pixels>,
8426 newest_selection_head: Option<DisplayPoint>,
8427 editor_width: Pixels,
8428 style: &EditorStyle,
8429 window: &mut Window,
8430 cx: &mut App,
8431 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8432 if self.mode().is_minimap() {
8433 return None;
8434 }
8435 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8436
8437 if self.edit_prediction_visible_in_cursor_popover(true) {
8438 return None;
8439 }
8440
8441 match &active_edit_prediction.completion {
8442 EditPrediction::Move { target, .. } => {
8443 let target_display_point = target.to_display_point(editor_snapshot);
8444
8445 if self.edit_prediction_requires_modifier() {
8446 if !self.edit_prediction_preview_is_active() {
8447 return None;
8448 }
8449
8450 self.render_edit_prediction_modifier_jump_popover(
8451 text_bounds,
8452 content_origin,
8453 visible_row_range,
8454 line_layouts,
8455 line_height,
8456 scroll_pixel_position,
8457 newest_selection_head,
8458 target_display_point,
8459 window,
8460 cx,
8461 )
8462 } else {
8463 self.render_edit_prediction_eager_jump_popover(
8464 text_bounds,
8465 content_origin,
8466 editor_snapshot,
8467 visible_row_range,
8468 scroll_top,
8469 scroll_bottom,
8470 line_height,
8471 scroll_pixel_position,
8472 target_display_point,
8473 editor_width,
8474 window,
8475 cx,
8476 )
8477 }
8478 }
8479 EditPrediction::Edit {
8480 display_mode: EditDisplayMode::Inline,
8481 ..
8482 } => None,
8483 EditPrediction::Edit {
8484 display_mode: EditDisplayMode::TabAccept,
8485 edits,
8486 ..
8487 } => {
8488 let range = &edits.first()?.0;
8489 let target_display_point = range.end.to_display_point(editor_snapshot);
8490
8491 self.render_edit_prediction_end_of_line_popover(
8492 "Accept",
8493 editor_snapshot,
8494 visible_row_range,
8495 target_display_point,
8496 line_height,
8497 scroll_pixel_position,
8498 content_origin,
8499 editor_width,
8500 window,
8501 cx,
8502 )
8503 }
8504 EditPrediction::Edit {
8505 edits,
8506 edit_preview,
8507 display_mode: EditDisplayMode::DiffPopover,
8508 snapshot,
8509 } => self.render_edit_prediction_diff_popover(
8510 text_bounds,
8511 content_origin,
8512 right_margin,
8513 editor_snapshot,
8514 visible_row_range,
8515 line_layouts,
8516 line_height,
8517 scroll_pixel_position,
8518 newest_selection_head,
8519 editor_width,
8520 style,
8521 edits,
8522 edit_preview,
8523 snapshot,
8524 window,
8525 cx,
8526 ),
8527 }
8528 }
8529
8530 fn render_edit_prediction_modifier_jump_popover(
8531 &mut self,
8532 text_bounds: &Bounds<Pixels>,
8533 content_origin: gpui::Point<Pixels>,
8534 visible_row_range: Range<DisplayRow>,
8535 line_layouts: &[LineWithInvisibles],
8536 line_height: Pixels,
8537 scroll_pixel_position: gpui::Point<Pixels>,
8538 newest_selection_head: Option<DisplayPoint>,
8539 target_display_point: DisplayPoint,
8540 window: &mut Window,
8541 cx: &mut App,
8542 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8543 let scrolled_content_origin =
8544 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8545
8546 const SCROLL_PADDING_Y: Pixels = px(12.);
8547
8548 if target_display_point.row() < visible_row_range.start {
8549 return self.render_edit_prediction_scroll_popover(
8550 |_| SCROLL_PADDING_Y,
8551 IconName::ArrowUp,
8552 visible_row_range,
8553 line_layouts,
8554 newest_selection_head,
8555 scrolled_content_origin,
8556 window,
8557 cx,
8558 );
8559 } else if target_display_point.row() >= visible_row_range.end {
8560 return self.render_edit_prediction_scroll_popover(
8561 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8562 IconName::ArrowDown,
8563 visible_row_range,
8564 line_layouts,
8565 newest_selection_head,
8566 scrolled_content_origin,
8567 window,
8568 cx,
8569 );
8570 }
8571
8572 const POLE_WIDTH: Pixels = px(2.);
8573
8574 let line_layout =
8575 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8576 let target_column = target_display_point.column() as usize;
8577
8578 let target_x = line_layout.x_for_index(target_column);
8579 let target_y =
8580 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8581
8582 let flag_on_right = target_x < text_bounds.size.width / 2.;
8583
8584 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8585 border_color.l += 0.001;
8586
8587 let mut element = v_flex()
8588 .items_end()
8589 .when(flag_on_right, |el| el.items_start())
8590 .child(if flag_on_right {
8591 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8592 .rounded_bl(px(0.))
8593 .rounded_tl(px(0.))
8594 .border_l_2()
8595 .border_color(border_color)
8596 } else {
8597 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8598 .rounded_br(px(0.))
8599 .rounded_tr(px(0.))
8600 .border_r_2()
8601 .border_color(border_color)
8602 })
8603 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8604 .into_any();
8605
8606 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8607
8608 let mut origin = scrolled_content_origin + point(target_x, target_y)
8609 - point(
8610 if flag_on_right {
8611 POLE_WIDTH
8612 } else {
8613 size.width - POLE_WIDTH
8614 },
8615 size.height - line_height,
8616 );
8617
8618 origin.x = origin.x.max(content_origin.x);
8619
8620 element.prepaint_at(origin, window, cx);
8621
8622 Some((element, origin))
8623 }
8624
8625 fn render_edit_prediction_scroll_popover(
8626 &mut self,
8627 to_y: impl Fn(Size<Pixels>) -> Pixels,
8628 scroll_icon: IconName,
8629 visible_row_range: Range<DisplayRow>,
8630 line_layouts: &[LineWithInvisibles],
8631 newest_selection_head: Option<DisplayPoint>,
8632 scrolled_content_origin: gpui::Point<Pixels>,
8633 window: &mut Window,
8634 cx: &mut App,
8635 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8636 let mut element = self
8637 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8638 .into_any();
8639
8640 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8641
8642 let cursor = newest_selection_head?;
8643 let cursor_row_layout =
8644 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8645 let cursor_column = cursor.column() as usize;
8646
8647 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8648
8649 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8650
8651 element.prepaint_at(origin, window, cx);
8652 Some((element, origin))
8653 }
8654
8655 fn render_edit_prediction_eager_jump_popover(
8656 &mut self,
8657 text_bounds: &Bounds<Pixels>,
8658 content_origin: gpui::Point<Pixels>,
8659 editor_snapshot: &EditorSnapshot,
8660 visible_row_range: Range<DisplayRow>,
8661 scroll_top: f32,
8662 scroll_bottom: f32,
8663 line_height: Pixels,
8664 scroll_pixel_position: gpui::Point<Pixels>,
8665 target_display_point: DisplayPoint,
8666 editor_width: Pixels,
8667 window: &mut Window,
8668 cx: &mut App,
8669 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8670 if target_display_point.row().as_f32() < scroll_top {
8671 let mut element = self
8672 .render_edit_prediction_line_popover(
8673 "Jump to Edit",
8674 Some(IconName::ArrowUp),
8675 window,
8676 cx,
8677 )?
8678 .into_any();
8679
8680 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8681 let offset = point(
8682 (text_bounds.size.width - size.width) / 2.,
8683 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8684 );
8685
8686 let origin = text_bounds.origin + offset;
8687 element.prepaint_at(origin, window, cx);
8688 Some((element, origin))
8689 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8690 let mut element = self
8691 .render_edit_prediction_line_popover(
8692 "Jump to Edit",
8693 Some(IconName::ArrowDown),
8694 window,
8695 cx,
8696 )?
8697 .into_any();
8698
8699 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8700 let offset = point(
8701 (text_bounds.size.width - size.width) / 2.,
8702 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8703 );
8704
8705 let origin = text_bounds.origin + offset;
8706 element.prepaint_at(origin, window, cx);
8707 Some((element, origin))
8708 } else {
8709 self.render_edit_prediction_end_of_line_popover(
8710 "Jump to Edit",
8711 editor_snapshot,
8712 visible_row_range,
8713 target_display_point,
8714 line_height,
8715 scroll_pixel_position,
8716 content_origin,
8717 editor_width,
8718 window,
8719 cx,
8720 )
8721 }
8722 }
8723
8724 fn render_edit_prediction_end_of_line_popover(
8725 self: &mut Editor,
8726 label: &'static str,
8727 editor_snapshot: &EditorSnapshot,
8728 visible_row_range: Range<DisplayRow>,
8729 target_display_point: DisplayPoint,
8730 line_height: Pixels,
8731 scroll_pixel_position: gpui::Point<Pixels>,
8732 content_origin: gpui::Point<Pixels>,
8733 editor_width: Pixels,
8734 window: &mut Window,
8735 cx: &mut App,
8736 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8737 let target_line_end = DisplayPoint::new(
8738 target_display_point.row(),
8739 editor_snapshot.line_len(target_display_point.row()),
8740 );
8741
8742 let mut element = self
8743 .render_edit_prediction_line_popover(label, None, window, cx)?
8744 .into_any();
8745
8746 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8747
8748 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8749
8750 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8751 let mut origin = start_point
8752 + line_origin
8753 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8754 origin.x = origin.x.max(content_origin.x);
8755
8756 let max_x = content_origin.x + editor_width - size.width;
8757
8758 if origin.x > max_x {
8759 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8760
8761 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8762 origin.y += offset;
8763 IconName::ArrowUp
8764 } else {
8765 origin.y -= offset;
8766 IconName::ArrowDown
8767 };
8768
8769 element = self
8770 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8771 .into_any();
8772
8773 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8774
8775 origin.x = content_origin.x + editor_width - size.width - px(2.);
8776 }
8777
8778 element.prepaint_at(origin, window, cx);
8779 Some((element, origin))
8780 }
8781
8782 fn render_edit_prediction_diff_popover(
8783 self: &Editor,
8784 text_bounds: &Bounds<Pixels>,
8785 content_origin: gpui::Point<Pixels>,
8786 right_margin: Pixels,
8787 editor_snapshot: &EditorSnapshot,
8788 visible_row_range: Range<DisplayRow>,
8789 line_layouts: &[LineWithInvisibles],
8790 line_height: Pixels,
8791 scroll_pixel_position: gpui::Point<Pixels>,
8792 newest_selection_head: Option<DisplayPoint>,
8793 editor_width: Pixels,
8794 style: &EditorStyle,
8795 edits: &Vec<(Range<Anchor>, String)>,
8796 edit_preview: &Option<language::EditPreview>,
8797 snapshot: &language::BufferSnapshot,
8798 window: &mut Window,
8799 cx: &mut App,
8800 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8801 let edit_start = edits
8802 .first()
8803 .unwrap()
8804 .0
8805 .start
8806 .to_display_point(editor_snapshot);
8807 let edit_end = edits
8808 .last()
8809 .unwrap()
8810 .0
8811 .end
8812 .to_display_point(editor_snapshot);
8813
8814 let is_visible = visible_row_range.contains(&edit_start.row())
8815 || visible_row_range.contains(&edit_end.row());
8816 if !is_visible {
8817 return None;
8818 }
8819
8820 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8821 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8822 } else {
8823 // Fallback for providers without edit_preview
8824 crate::edit_prediction_fallback_text(edits, cx)
8825 };
8826
8827 let styled_text = highlighted_edits.to_styled_text(&style.text);
8828 let line_count = highlighted_edits.text.lines().count();
8829
8830 const BORDER_WIDTH: Pixels = px(1.);
8831
8832 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8833 let has_keybind = keybind.is_some();
8834
8835 let mut element = h_flex()
8836 .items_start()
8837 .child(
8838 h_flex()
8839 .bg(cx.theme().colors().editor_background)
8840 .border(BORDER_WIDTH)
8841 .shadow_xs()
8842 .border_color(cx.theme().colors().border)
8843 .rounded_l_lg()
8844 .when(line_count > 1, |el| el.rounded_br_lg())
8845 .pr_1()
8846 .child(styled_text),
8847 )
8848 .child(
8849 h_flex()
8850 .h(line_height + BORDER_WIDTH * 2.)
8851 .px_1p5()
8852 .gap_1()
8853 // Workaround: For some reason, there's a gap if we don't do this
8854 .ml(-BORDER_WIDTH)
8855 .shadow(vec![gpui::BoxShadow {
8856 color: gpui::black().opacity(0.05),
8857 offset: point(px(1.), px(1.)),
8858 blur_radius: px(2.),
8859 spread_radius: px(0.),
8860 }])
8861 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8862 .border(BORDER_WIDTH)
8863 .border_color(cx.theme().colors().border)
8864 .rounded_r_lg()
8865 .id("edit_prediction_diff_popover_keybind")
8866 .when(!has_keybind, |el| {
8867 let status_colors = cx.theme().status();
8868
8869 el.bg(status_colors.error_background)
8870 .border_color(status_colors.error.opacity(0.6))
8871 .child(Icon::new(IconName::Info).color(Color::Error))
8872 .cursor_default()
8873 .hoverable_tooltip(move |_window, cx| {
8874 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8875 })
8876 })
8877 .children(keybind),
8878 )
8879 .into_any();
8880
8881 let longest_row =
8882 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8883 let longest_line_width = if visible_row_range.contains(&longest_row) {
8884 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8885 } else {
8886 layout_line(
8887 longest_row,
8888 editor_snapshot,
8889 style,
8890 editor_width,
8891 |_| false,
8892 window,
8893 cx,
8894 )
8895 .width
8896 };
8897
8898 let viewport_bounds =
8899 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8900 right: -right_margin,
8901 ..Default::default()
8902 });
8903
8904 let x_after_longest =
8905 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8906 - scroll_pixel_position.x;
8907
8908 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8909
8910 // Fully visible if it can be displayed within the window (allow overlapping other
8911 // panes). However, this is only allowed if the popover starts within text_bounds.
8912 let can_position_to_the_right = x_after_longest < text_bounds.right()
8913 && x_after_longest + element_bounds.width < viewport_bounds.right();
8914
8915 let mut origin = if can_position_to_the_right {
8916 point(
8917 x_after_longest,
8918 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8919 - scroll_pixel_position.y,
8920 )
8921 } else {
8922 let cursor_row = newest_selection_head.map(|head| head.row());
8923 let above_edit = edit_start
8924 .row()
8925 .0
8926 .checked_sub(line_count as u32)
8927 .map(DisplayRow);
8928 let below_edit = Some(edit_end.row() + 1);
8929 let above_cursor =
8930 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8931 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8932
8933 // Place the edit popover adjacent to the edit if there is a location
8934 // available that is onscreen and does not obscure the cursor. Otherwise,
8935 // place it adjacent to the cursor.
8936 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8937 .into_iter()
8938 .flatten()
8939 .find(|&start_row| {
8940 let end_row = start_row + line_count as u32;
8941 visible_row_range.contains(&start_row)
8942 && visible_row_range.contains(&end_row)
8943 && cursor_row
8944 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
8945 })?;
8946
8947 content_origin
8948 + point(
8949 -scroll_pixel_position.x,
8950 row_target.as_f32() * line_height - scroll_pixel_position.y,
8951 )
8952 };
8953
8954 origin.x -= BORDER_WIDTH;
8955
8956 window.defer_draw(element, origin, 1);
8957
8958 // Do not return an element, since it will already be drawn due to defer_draw.
8959 None
8960 }
8961
8962 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8963 px(30.)
8964 }
8965
8966 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8967 if self.read_only(cx) {
8968 cx.theme().players().read_only()
8969 } else {
8970 self.style.as_ref().unwrap().local_player
8971 }
8972 }
8973
8974 fn render_edit_prediction_accept_keybind(
8975 &self,
8976 window: &mut Window,
8977 cx: &App,
8978 ) -> Option<AnyElement> {
8979 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8980 let accept_keystroke = accept_binding.keystroke()?;
8981
8982 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8983
8984 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8985 Color::Accent
8986 } else {
8987 Color::Muted
8988 };
8989
8990 h_flex()
8991 .px_0p5()
8992 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8993 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8994 .text_size(TextSize::XSmall.rems(cx))
8995 .child(h_flex().children(ui::render_modifiers(
8996 &accept_keystroke.modifiers,
8997 PlatformStyle::platform(),
8998 Some(modifiers_color),
8999 Some(IconSize::XSmall.rems().into()),
9000 true,
9001 )))
9002 .when(is_platform_style_mac, |parent| {
9003 parent.child(accept_keystroke.key.clone())
9004 })
9005 .when(!is_platform_style_mac, |parent| {
9006 parent.child(
9007 Key::new(
9008 util::capitalize(&accept_keystroke.key),
9009 Some(Color::Default),
9010 )
9011 .size(Some(IconSize::XSmall.rems().into())),
9012 )
9013 })
9014 .into_any()
9015 .into()
9016 }
9017
9018 fn render_edit_prediction_line_popover(
9019 &self,
9020 label: impl Into<SharedString>,
9021 icon: Option<IconName>,
9022 window: &mut Window,
9023 cx: &App,
9024 ) -> Option<Stateful<Div>> {
9025 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9026
9027 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9028 let has_keybind = keybind.is_some();
9029
9030 let result = h_flex()
9031 .id("ep-line-popover")
9032 .py_0p5()
9033 .pl_1()
9034 .pr(padding_right)
9035 .gap_1()
9036 .rounded_md()
9037 .border_1()
9038 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9039 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9040 .shadow_xs()
9041 .when(!has_keybind, |el| {
9042 let status_colors = cx.theme().status();
9043
9044 el.bg(status_colors.error_background)
9045 .border_color(status_colors.error.opacity(0.6))
9046 .pl_2()
9047 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9048 .cursor_default()
9049 .hoverable_tooltip(move |_window, cx| {
9050 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9051 })
9052 })
9053 .children(keybind)
9054 .child(
9055 Label::new(label)
9056 .size(LabelSize::Small)
9057 .when(!has_keybind, |el| {
9058 el.color(cx.theme().status().error.into()).strikethrough()
9059 }),
9060 )
9061 .when(!has_keybind, |el| {
9062 el.child(
9063 h_flex().ml_1().child(
9064 Icon::new(IconName::Info)
9065 .size(IconSize::Small)
9066 .color(cx.theme().status().error.into()),
9067 ),
9068 )
9069 })
9070 .when_some(icon, |element, icon| {
9071 element.child(
9072 div()
9073 .mt(px(1.5))
9074 .child(Icon::new(icon).size(IconSize::Small)),
9075 )
9076 });
9077
9078 Some(result)
9079 }
9080
9081 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9082 let accent_color = cx.theme().colors().text_accent;
9083 let editor_bg_color = cx.theme().colors().editor_background;
9084 editor_bg_color.blend(accent_color.opacity(0.1))
9085 }
9086
9087 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9088 let accent_color = cx.theme().colors().text_accent;
9089 let editor_bg_color = cx.theme().colors().editor_background;
9090 editor_bg_color.blend(accent_color.opacity(0.6))
9091 }
9092 fn get_prediction_provider_icon_name(
9093 provider: &Option<RegisteredEditPredictionProvider>,
9094 ) -> IconName {
9095 match provider {
9096 Some(provider) => match provider.provider.name() {
9097 "copilot" => IconName::Copilot,
9098 "supermaven" => IconName::Supermaven,
9099 _ => IconName::ZedPredict,
9100 },
9101 None => IconName::ZedPredict,
9102 }
9103 }
9104
9105 fn render_edit_prediction_cursor_popover(
9106 &self,
9107 min_width: Pixels,
9108 max_width: Pixels,
9109 cursor_point: Point,
9110 style: &EditorStyle,
9111 accept_keystroke: Option<&gpui::Keystroke>,
9112 _window: &Window,
9113 cx: &mut Context<Editor>,
9114 ) -> Option<AnyElement> {
9115 let provider = self.edit_prediction_provider.as_ref()?;
9116 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9117
9118 if provider.provider.needs_terms_acceptance(cx) {
9119 return Some(
9120 h_flex()
9121 .min_w(min_width)
9122 .flex_1()
9123 .px_2()
9124 .py_1()
9125 .gap_3()
9126 .elevation_2(cx)
9127 .hover(|style| style.bg(cx.theme().colors().element_hover))
9128 .id("accept-terms")
9129 .cursor_pointer()
9130 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9131 .on_click(cx.listener(|this, _event, window, cx| {
9132 cx.stop_propagation();
9133 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9134 window.dispatch_action(
9135 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9136 cx,
9137 );
9138 }))
9139 .child(
9140 h_flex()
9141 .flex_1()
9142 .gap_2()
9143 .child(Icon::new(provider_icon))
9144 .child(Label::new("Accept Terms of Service"))
9145 .child(div().w_full())
9146 .child(
9147 Icon::new(IconName::ArrowUpRight)
9148 .color(Color::Muted)
9149 .size(IconSize::Small),
9150 )
9151 .into_any_element(),
9152 )
9153 .into_any(),
9154 );
9155 }
9156
9157 let is_refreshing = provider.provider.is_refreshing(cx);
9158
9159 fn pending_completion_container(icon: IconName) -> Div {
9160 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9161 }
9162
9163 let completion = match &self.active_edit_prediction {
9164 Some(prediction) => {
9165 if !self.has_visible_completions_menu() {
9166 const RADIUS: Pixels = px(6.);
9167 const BORDER_WIDTH: Pixels = px(1.);
9168
9169 return Some(
9170 h_flex()
9171 .elevation_2(cx)
9172 .border(BORDER_WIDTH)
9173 .border_color(cx.theme().colors().border)
9174 .when(accept_keystroke.is_none(), |el| {
9175 el.border_color(cx.theme().status().error)
9176 })
9177 .rounded(RADIUS)
9178 .rounded_tl(px(0.))
9179 .overflow_hidden()
9180 .child(div().px_1p5().child(match &prediction.completion {
9181 EditPrediction::Move { target, snapshot } => {
9182 use text::ToPoint as _;
9183 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9184 {
9185 Icon::new(IconName::ZedPredictDown)
9186 } else {
9187 Icon::new(IconName::ZedPredictUp)
9188 }
9189 }
9190 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9191 }))
9192 .child(
9193 h_flex()
9194 .gap_1()
9195 .py_1()
9196 .px_2()
9197 .rounded_r(RADIUS - BORDER_WIDTH)
9198 .border_l_1()
9199 .border_color(cx.theme().colors().border)
9200 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9201 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9202 el.child(
9203 Label::new("Hold")
9204 .size(LabelSize::Small)
9205 .when(accept_keystroke.is_none(), |el| {
9206 el.strikethrough()
9207 })
9208 .line_height_style(LineHeightStyle::UiLabel),
9209 )
9210 })
9211 .id("edit_prediction_cursor_popover_keybind")
9212 .when(accept_keystroke.is_none(), |el| {
9213 let status_colors = cx.theme().status();
9214
9215 el.bg(status_colors.error_background)
9216 .border_color(status_colors.error.opacity(0.6))
9217 .child(Icon::new(IconName::Info).color(Color::Error))
9218 .cursor_default()
9219 .hoverable_tooltip(move |_window, cx| {
9220 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9221 .into()
9222 })
9223 })
9224 .when_some(
9225 accept_keystroke.as_ref(),
9226 |el, accept_keystroke| {
9227 el.child(h_flex().children(ui::render_modifiers(
9228 &accept_keystroke.modifiers,
9229 PlatformStyle::platform(),
9230 Some(Color::Default),
9231 Some(IconSize::XSmall.rems().into()),
9232 false,
9233 )))
9234 },
9235 ),
9236 )
9237 .into_any(),
9238 );
9239 }
9240
9241 self.render_edit_prediction_cursor_popover_preview(
9242 prediction,
9243 cursor_point,
9244 style,
9245 cx,
9246 )?
9247 }
9248
9249 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9250 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9251 stale_completion,
9252 cursor_point,
9253 style,
9254 cx,
9255 )?,
9256
9257 None => pending_completion_container(provider_icon)
9258 .child(Label::new("...").size(LabelSize::Small)),
9259 },
9260
9261 None => pending_completion_container(provider_icon)
9262 .child(Label::new("...").size(LabelSize::Small)),
9263 };
9264
9265 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9266 completion
9267 .with_animation(
9268 "loading-completion",
9269 Animation::new(Duration::from_secs(2))
9270 .repeat()
9271 .with_easing(pulsating_between(0.4, 0.8)),
9272 |label, delta| label.opacity(delta),
9273 )
9274 .into_any_element()
9275 } else {
9276 completion.into_any_element()
9277 };
9278
9279 let has_completion = self.active_edit_prediction.is_some();
9280
9281 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9282 Some(
9283 h_flex()
9284 .min_w(min_width)
9285 .max_w(max_width)
9286 .flex_1()
9287 .elevation_2(cx)
9288 .border_color(cx.theme().colors().border)
9289 .child(
9290 div()
9291 .flex_1()
9292 .py_1()
9293 .px_2()
9294 .overflow_hidden()
9295 .child(completion),
9296 )
9297 .when_some(accept_keystroke, |el, accept_keystroke| {
9298 if !accept_keystroke.modifiers.modified() {
9299 return el;
9300 }
9301
9302 el.child(
9303 h_flex()
9304 .h_full()
9305 .border_l_1()
9306 .rounded_r_lg()
9307 .border_color(cx.theme().colors().border)
9308 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9309 .gap_1()
9310 .py_1()
9311 .px_2()
9312 .child(
9313 h_flex()
9314 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9315 .when(is_platform_style_mac, |parent| parent.gap_1())
9316 .child(h_flex().children(ui::render_modifiers(
9317 &accept_keystroke.modifiers,
9318 PlatformStyle::platform(),
9319 Some(if !has_completion {
9320 Color::Muted
9321 } else {
9322 Color::Default
9323 }),
9324 None,
9325 false,
9326 ))),
9327 )
9328 .child(Label::new("Preview").into_any_element())
9329 .opacity(if has_completion { 1.0 } else { 0.4 }),
9330 )
9331 })
9332 .into_any(),
9333 )
9334 }
9335
9336 fn render_edit_prediction_cursor_popover_preview(
9337 &self,
9338 completion: &EditPredictionState,
9339 cursor_point: Point,
9340 style: &EditorStyle,
9341 cx: &mut Context<Editor>,
9342 ) -> Option<Div> {
9343 use text::ToPoint as _;
9344
9345 fn render_relative_row_jump(
9346 prefix: impl Into<String>,
9347 current_row: u32,
9348 target_row: u32,
9349 ) -> Div {
9350 let (row_diff, arrow) = if target_row < current_row {
9351 (current_row - target_row, IconName::ArrowUp)
9352 } else {
9353 (target_row - current_row, IconName::ArrowDown)
9354 };
9355
9356 h_flex()
9357 .child(
9358 Label::new(format!("{}{}", prefix.into(), row_diff))
9359 .color(Color::Muted)
9360 .size(LabelSize::Small),
9361 )
9362 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9363 }
9364
9365 let supports_jump = self
9366 .edit_prediction_provider
9367 .as_ref()
9368 .map(|provider| provider.provider.supports_jump_to_edit())
9369 .unwrap_or(true);
9370
9371 match &completion.completion {
9372 EditPrediction::Move {
9373 target, snapshot, ..
9374 } => {
9375 if !supports_jump {
9376 return None;
9377 }
9378
9379 Some(
9380 h_flex()
9381 .px_2()
9382 .gap_2()
9383 .flex_1()
9384 .child(
9385 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9386 Icon::new(IconName::ZedPredictDown)
9387 } else {
9388 Icon::new(IconName::ZedPredictUp)
9389 },
9390 )
9391 .child(Label::new("Jump to Edit")),
9392 )
9393 }
9394
9395 EditPrediction::Edit {
9396 edits,
9397 edit_preview,
9398 snapshot,
9399 display_mode: _,
9400 } => {
9401 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9402
9403 let (highlighted_edits, has_more_lines) =
9404 if let Some(edit_preview) = edit_preview.as_ref() {
9405 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9406 .first_line_preview()
9407 } else {
9408 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9409 };
9410
9411 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9412 .with_default_highlights(&style.text, highlighted_edits.highlights);
9413
9414 let preview = h_flex()
9415 .gap_1()
9416 .min_w_16()
9417 .child(styled_text)
9418 .when(has_more_lines, |parent| parent.child("…"));
9419
9420 let left = if supports_jump && first_edit_row != cursor_point.row {
9421 render_relative_row_jump("", cursor_point.row, first_edit_row)
9422 .into_any_element()
9423 } else {
9424 let icon_name =
9425 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9426 Icon::new(icon_name).into_any_element()
9427 };
9428
9429 Some(
9430 h_flex()
9431 .h_full()
9432 .flex_1()
9433 .gap_2()
9434 .pr_1()
9435 .overflow_x_hidden()
9436 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9437 .child(left)
9438 .child(preview),
9439 )
9440 }
9441 }
9442 }
9443
9444 pub fn render_context_menu(
9445 &self,
9446 style: &EditorStyle,
9447 max_height_in_lines: u32,
9448 window: &mut Window,
9449 cx: &mut Context<Editor>,
9450 ) -> Option<AnyElement> {
9451 let menu = self.context_menu.borrow();
9452 let menu = menu.as_ref()?;
9453 if !menu.visible() {
9454 return None;
9455 };
9456 Some(menu.render(style, max_height_in_lines, window, cx))
9457 }
9458
9459 fn render_context_menu_aside(
9460 &mut self,
9461 max_size: Size<Pixels>,
9462 window: &mut Window,
9463 cx: &mut Context<Editor>,
9464 ) -> Option<AnyElement> {
9465 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9466 if menu.visible() {
9467 menu.render_aside(max_size, window, cx)
9468 } else {
9469 None
9470 }
9471 })
9472 }
9473
9474 fn hide_context_menu(
9475 &mut self,
9476 window: &mut Window,
9477 cx: &mut Context<Self>,
9478 ) -> Option<CodeContextMenu> {
9479 cx.notify();
9480 self.completion_tasks.clear();
9481 let context_menu = self.context_menu.borrow_mut().take();
9482 self.stale_edit_prediction_in_menu.take();
9483 self.update_visible_edit_prediction(window, cx);
9484 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9485 && let Some(completion_provider) = &self.completion_provider
9486 {
9487 completion_provider.selection_changed(None, window, cx);
9488 }
9489 context_menu
9490 }
9491
9492 fn show_snippet_choices(
9493 &mut self,
9494 choices: &Vec<String>,
9495 selection: Range<Anchor>,
9496 cx: &mut Context<Self>,
9497 ) {
9498 let Some((_, buffer, _)) = self
9499 .buffer()
9500 .read(cx)
9501 .excerpt_containing(selection.start, cx)
9502 else {
9503 return;
9504 };
9505 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9506 else {
9507 return;
9508 };
9509 if buffer != end_buffer {
9510 log::error!("expected anchor range to have matching buffer IDs");
9511 return;
9512 }
9513
9514 let id = post_inc(&mut self.next_completion_id);
9515 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9516 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9517 CompletionsMenu::new_snippet_choices(
9518 id,
9519 true,
9520 choices,
9521 selection,
9522 buffer,
9523 snippet_sort_order,
9524 ),
9525 ));
9526 }
9527
9528 pub fn insert_snippet(
9529 &mut self,
9530 insertion_ranges: &[Range<usize>],
9531 snippet: Snippet,
9532 window: &mut Window,
9533 cx: &mut Context<Self>,
9534 ) -> Result<()> {
9535 struct Tabstop<T> {
9536 is_end_tabstop: bool,
9537 ranges: Vec<Range<T>>,
9538 choices: Option<Vec<String>>,
9539 }
9540
9541 let tabstops = self.buffer.update(cx, |buffer, cx| {
9542 let snippet_text: Arc<str> = snippet.text.clone().into();
9543 let edits = insertion_ranges
9544 .iter()
9545 .cloned()
9546 .map(|range| (range, snippet_text.clone()));
9547 let autoindent_mode = AutoindentMode::Block {
9548 original_indent_columns: Vec::new(),
9549 };
9550 buffer.edit(edits, Some(autoindent_mode), cx);
9551
9552 let snapshot = &*buffer.read(cx);
9553 let snippet = &snippet;
9554 snippet
9555 .tabstops
9556 .iter()
9557 .map(|tabstop| {
9558 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9559 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9560 });
9561 let mut tabstop_ranges = tabstop
9562 .ranges
9563 .iter()
9564 .flat_map(|tabstop_range| {
9565 let mut delta = 0_isize;
9566 insertion_ranges.iter().map(move |insertion_range| {
9567 let insertion_start = insertion_range.start as isize + delta;
9568 delta +=
9569 snippet.text.len() as isize - insertion_range.len() as isize;
9570
9571 let start = ((insertion_start + tabstop_range.start) as usize)
9572 .min(snapshot.len());
9573 let end = ((insertion_start + tabstop_range.end) as usize)
9574 .min(snapshot.len());
9575 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9576 })
9577 })
9578 .collect::<Vec<_>>();
9579 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9580
9581 Tabstop {
9582 is_end_tabstop,
9583 ranges: tabstop_ranges,
9584 choices: tabstop.choices.clone(),
9585 }
9586 })
9587 .collect::<Vec<_>>()
9588 });
9589 if let Some(tabstop) = tabstops.first() {
9590 self.change_selections(Default::default(), window, cx, |s| {
9591 // Reverse order so that the first range is the newest created selection.
9592 // Completions will use it and autoscroll will prioritize it.
9593 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9594 });
9595
9596 if let Some(choices) = &tabstop.choices
9597 && let Some(selection) = tabstop.ranges.first()
9598 {
9599 self.show_snippet_choices(choices, selection.clone(), cx)
9600 }
9601
9602 // If we're already at the last tabstop and it's at the end of the snippet,
9603 // we're done, we don't need to keep the state around.
9604 if !tabstop.is_end_tabstop {
9605 let choices = tabstops
9606 .iter()
9607 .map(|tabstop| tabstop.choices.clone())
9608 .collect();
9609
9610 let ranges = tabstops
9611 .into_iter()
9612 .map(|tabstop| tabstop.ranges)
9613 .collect::<Vec<_>>();
9614
9615 self.snippet_stack.push(SnippetState {
9616 active_index: 0,
9617 ranges,
9618 choices,
9619 });
9620 }
9621
9622 // Check whether the just-entered snippet ends with an auto-closable bracket.
9623 if self.autoclose_regions.is_empty() {
9624 let snapshot = self.buffer.read(cx).snapshot(cx);
9625 let mut all_selections = self.selections.all::<Point>(cx);
9626 for selection in &mut all_selections {
9627 let selection_head = selection.head();
9628 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9629 continue;
9630 };
9631
9632 let mut bracket_pair = None;
9633 let max_lookup_length = scope
9634 .brackets()
9635 .map(|(pair, _)| {
9636 pair.start
9637 .as_str()
9638 .chars()
9639 .count()
9640 .max(pair.end.as_str().chars().count())
9641 })
9642 .max();
9643 if let Some(max_lookup_length) = max_lookup_length {
9644 let next_text = snapshot
9645 .chars_at(selection_head)
9646 .take(max_lookup_length)
9647 .collect::<String>();
9648 let prev_text = snapshot
9649 .reversed_chars_at(selection_head)
9650 .take(max_lookup_length)
9651 .collect::<String>();
9652
9653 for (pair, enabled) in scope.brackets() {
9654 if enabled
9655 && pair.close
9656 && prev_text.starts_with(pair.start.as_str())
9657 && next_text.starts_with(pair.end.as_str())
9658 {
9659 bracket_pair = Some(pair.clone());
9660 break;
9661 }
9662 }
9663 }
9664
9665 if let Some(pair) = bracket_pair {
9666 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9667 let autoclose_enabled =
9668 self.use_autoclose && snapshot_settings.use_autoclose;
9669 if autoclose_enabled {
9670 let start = snapshot.anchor_after(selection_head);
9671 let end = snapshot.anchor_after(selection_head);
9672 self.autoclose_regions.push(AutocloseRegion {
9673 selection_id: selection.id,
9674 range: start..end,
9675 pair,
9676 });
9677 }
9678 }
9679 }
9680 }
9681 }
9682 Ok(())
9683 }
9684
9685 pub fn move_to_next_snippet_tabstop(
9686 &mut self,
9687 window: &mut Window,
9688 cx: &mut Context<Self>,
9689 ) -> bool {
9690 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9691 }
9692
9693 pub fn move_to_prev_snippet_tabstop(
9694 &mut self,
9695 window: &mut Window,
9696 cx: &mut Context<Self>,
9697 ) -> bool {
9698 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9699 }
9700
9701 pub fn move_to_snippet_tabstop(
9702 &mut self,
9703 bias: Bias,
9704 window: &mut Window,
9705 cx: &mut Context<Self>,
9706 ) -> bool {
9707 if let Some(mut snippet) = self.snippet_stack.pop() {
9708 match bias {
9709 Bias::Left => {
9710 if snippet.active_index > 0 {
9711 snippet.active_index -= 1;
9712 } else {
9713 self.snippet_stack.push(snippet);
9714 return false;
9715 }
9716 }
9717 Bias::Right => {
9718 if snippet.active_index + 1 < snippet.ranges.len() {
9719 snippet.active_index += 1;
9720 } else {
9721 self.snippet_stack.push(snippet);
9722 return false;
9723 }
9724 }
9725 }
9726 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9727 self.change_selections(Default::default(), window, cx, |s| {
9728 // Reverse order so that the first range is the newest created selection.
9729 // Completions will use it and autoscroll will prioritize it.
9730 s.select_ranges(current_ranges.iter().rev().cloned())
9731 });
9732
9733 if let Some(choices) = &snippet.choices[snippet.active_index]
9734 && let Some(selection) = current_ranges.first()
9735 {
9736 self.show_snippet_choices(choices, selection.clone(), cx);
9737 }
9738
9739 // If snippet state is not at the last tabstop, push it back on the stack
9740 if snippet.active_index + 1 < snippet.ranges.len() {
9741 self.snippet_stack.push(snippet);
9742 }
9743 return true;
9744 }
9745 }
9746
9747 false
9748 }
9749
9750 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9751 self.transact(window, cx, |this, window, cx| {
9752 this.select_all(&SelectAll, window, cx);
9753 this.insert("", window, cx);
9754 });
9755 }
9756
9757 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9759 self.transact(window, cx, |this, window, cx| {
9760 this.select_autoclose_pair(window, cx);
9761 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9762 if !this.linked_edit_ranges.is_empty() {
9763 let selections = this.selections.all::<MultiBufferPoint>(cx);
9764 let snapshot = this.buffer.read(cx).snapshot(cx);
9765
9766 for selection in selections.iter() {
9767 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9768 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9769 if selection_start.buffer_id != selection_end.buffer_id {
9770 continue;
9771 }
9772 if let Some(ranges) =
9773 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9774 {
9775 for (buffer, entries) in ranges {
9776 linked_ranges.entry(buffer).or_default().extend(entries);
9777 }
9778 }
9779 }
9780 }
9781
9782 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9783 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9784 for selection in &mut selections {
9785 if selection.is_empty() {
9786 let old_head = selection.head();
9787 let mut new_head =
9788 movement::left(&display_map, old_head.to_display_point(&display_map))
9789 .to_point(&display_map);
9790 if let Some((buffer, line_buffer_range)) = display_map
9791 .buffer_snapshot
9792 .buffer_line_for_row(MultiBufferRow(old_head.row))
9793 {
9794 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9795 let indent_len = match indent_size.kind {
9796 IndentKind::Space => {
9797 buffer.settings_at(line_buffer_range.start, cx).tab_size
9798 }
9799 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9800 };
9801 if old_head.column <= indent_size.len && old_head.column > 0 {
9802 let indent_len = indent_len.get();
9803 new_head = cmp::min(
9804 new_head,
9805 MultiBufferPoint::new(
9806 old_head.row,
9807 ((old_head.column - 1) / indent_len) * indent_len,
9808 ),
9809 );
9810 }
9811 }
9812
9813 selection.set_head(new_head, SelectionGoal::None);
9814 }
9815 }
9816
9817 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9818 this.insert("", window, cx);
9819 let empty_str: Arc<str> = Arc::from("");
9820 for (buffer, edits) in linked_ranges {
9821 let snapshot = buffer.read(cx).snapshot();
9822 use text::ToPoint as TP;
9823
9824 let edits = edits
9825 .into_iter()
9826 .map(|range| {
9827 let end_point = TP::to_point(&range.end, &snapshot);
9828 let mut start_point = TP::to_point(&range.start, &snapshot);
9829
9830 if end_point == start_point {
9831 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9832 .saturating_sub(1);
9833 start_point =
9834 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9835 };
9836
9837 (start_point..end_point, empty_str.clone())
9838 })
9839 .sorted_by_key(|(range, _)| range.start)
9840 .collect::<Vec<_>>();
9841 buffer.update(cx, |this, cx| {
9842 this.edit(edits, None, cx);
9843 })
9844 }
9845 this.refresh_edit_prediction(true, false, window, cx);
9846 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9847 });
9848 }
9849
9850 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9852 self.transact(window, cx, |this, window, cx| {
9853 this.change_selections(Default::default(), window, cx, |s| {
9854 s.move_with(|map, selection| {
9855 if selection.is_empty() {
9856 let cursor = movement::right(map, selection.head());
9857 selection.end = cursor;
9858 selection.reversed = true;
9859 selection.goal = SelectionGoal::None;
9860 }
9861 })
9862 });
9863 this.insert("", window, cx);
9864 this.refresh_edit_prediction(true, false, window, cx);
9865 });
9866 }
9867
9868 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9869 if self.mode.is_single_line() {
9870 cx.propagate();
9871 return;
9872 }
9873
9874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9875 if self.move_to_prev_snippet_tabstop(window, cx) {
9876 return;
9877 }
9878 self.outdent(&Outdent, window, cx);
9879 }
9880
9881 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9882 if self.mode.is_single_line() {
9883 cx.propagate();
9884 return;
9885 }
9886
9887 if self.move_to_next_snippet_tabstop(window, cx) {
9888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9889 return;
9890 }
9891 if self.read_only(cx) {
9892 return;
9893 }
9894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9895 let mut selections = self.selections.all_adjusted(cx);
9896 let buffer = self.buffer.read(cx);
9897 let snapshot = buffer.snapshot(cx);
9898 let rows_iter = selections.iter().map(|s| s.head().row);
9899 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9900
9901 let has_some_cursor_in_whitespace = selections
9902 .iter()
9903 .filter(|selection| selection.is_empty())
9904 .any(|selection| {
9905 let cursor = selection.head();
9906 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9907 cursor.column < current_indent.len
9908 });
9909
9910 let mut edits = Vec::new();
9911 let mut prev_edited_row = 0;
9912 let mut row_delta = 0;
9913 for selection in &mut selections {
9914 if selection.start.row != prev_edited_row {
9915 row_delta = 0;
9916 }
9917 prev_edited_row = selection.end.row;
9918
9919 // If the selection is non-empty, then increase the indentation of the selected lines.
9920 if !selection.is_empty() {
9921 row_delta =
9922 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9923 continue;
9924 }
9925
9926 let cursor = selection.head();
9927 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9928 if let Some(suggested_indent) =
9929 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9930 {
9931 // Don't do anything if already at suggested indent
9932 // and there is any other cursor which is not
9933 if has_some_cursor_in_whitespace
9934 && cursor.column == current_indent.len
9935 && current_indent.len == suggested_indent.len
9936 {
9937 continue;
9938 }
9939
9940 // Adjust line and move cursor to suggested indent
9941 // if cursor is not at suggested indent
9942 if cursor.column < suggested_indent.len
9943 && cursor.column <= current_indent.len
9944 && current_indent.len <= suggested_indent.len
9945 {
9946 selection.start = Point::new(cursor.row, suggested_indent.len);
9947 selection.end = selection.start;
9948 if row_delta == 0 {
9949 edits.extend(Buffer::edit_for_indent_size_adjustment(
9950 cursor.row,
9951 current_indent,
9952 suggested_indent,
9953 ));
9954 row_delta = suggested_indent.len - current_indent.len;
9955 }
9956 continue;
9957 }
9958
9959 // If current indent is more than suggested indent
9960 // only move cursor to current indent and skip indent
9961 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9962 selection.start = Point::new(cursor.row, current_indent.len);
9963 selection.end = selection.start;
9964 continue;
9965 }
9966 }
9967
9968 // Otherwise, insert a hard or soft tab.
9969 let settings = buffer.language_settings_at(cursor, cx);
9970 let tab_size = if settings.hard_tabs {
9971 IndentSize::tab()
9972 } else {
9973 let tab_size = settings.tab_size.get();
9974 let indent_remainder = snapshot
9975 .text_for_range(Point::new(cursor.row, 0)..cursor)
9976 .flat_map(str::chars)
9977 .fold(row_delta % tab_size, |counter: u32, c| {
9978 if c == '\t' {
9979 0
9980 } else {
9981 (counter + 1) % tab_size
9982 }
9983 });
9984
9985 let chars_to_next_tab_stop = tab_size - indent_remainder;
9986 IndentSize::spaces(chars_to_next_tab_stop)
9987 };
9988 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9989 selection.end = selection.start;
9990 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9991 row_delta += tab_size.len;
9992 }
9993
9994 self.transact(window, cx, |this, window, cx| {
9995 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9996 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9997 this.refresh_edit_prediction(true, false, window, cx);
9998 });
9999 }
10000
10001 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10002 if self.read_only(cx) {
10003 return;
10004 }
10005 if self.mode.is_single_line() {
10006 cx.propagate();
10007 return;
10008 }
10009
10010 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10011 let mut selections = self.selections.all::<Point>(cx);
10012 let mut prev_edited_row = 0;
10013 let mut row_delta = 0;
10014 let mut edits = Vec::new();
10015 let buffer = self.buffer.read(cx);
10016 let snapshot = buffer.snapshot(cx);
10017 for selection in &mut selections {
10018 if selection.start.row != prev_edited_row {
10019 row_delta = 0;
10020 }
10021 prev_edited_row = selection.end.row;
10022
10023 row_delta =
10024 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10025 }
10026
10027 self.transact(window, cx, |this, window, cx| {
10028 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10029 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10030 });
10031 }
10032
10033 fn indent_selection(
10034 buffer: &MultiBuffer,
10035 snapshot: &MultiBufferSnapshot,
10036 selection: &mut Selection<Point>,
10037 edits: &mut Vec<(Range<Point>, String)>,
10038 delta_for_start_row: u32,
10039 cx: &App,
10040 ) -> u32 {
10041 let settings = buffer.language_settings_at(selection.start, cx);
10042 let tab_size = settings.tab_size.get();
10043 let indent_kind = if settings.hard_tabs {
10044 IndentKind::Tab
10045 } else {
10046 IndentKind::Space
10047 };
10048 let mut start_row = selection.start.row;
10049 let mut end_row = selection.end.row + 1;
10050
10051 // If a selection ends at the beginning of a line, don't indent
10052 // that last line.
10053 if selection.end.column == 0 && selection.end.row > selection.start.row {
10054 end_row -= 1;
10055 }
10056
10057 // Avoid re-indenting a row that has already been indented by a
10058 // previous selection, but still update this selection's column
10059 // to reflect that indentation.
10060 if delta_for_start_row > 0 {
10061 start_row += 1;
10062 selection.start.column += delta_for_start_row;
10063 if selection.end.row == selection.start.row {
10064 selection.end.column += delta_for_start_row;
10065 }
10066 }
10067
10068 let mut delta_for_end_row = 0;
10069 let has_multiple_rows = start_row + 1 != end_row;
10070 for row in start_row..end_row {
10071 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10072 let indent_delta = match (current_indent.kind, indent_kind) {
10073 (IndentKind::Space, IndentKind::Space) => {
10074 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10075 IndentSize::spaces(columns_to_next_tab_stop)
10076 }
10077 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10078 (_, IndentKind::Tab) => IndentSize::tab(),
10079 };
10080
10081 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10082 0
10083 } else {
10084 selection.start.column
10085 };
10086 let row_start = Point::new(row, start);
10087 edits.push((
10088 row_start..row_start,
10089 indent_delta.chars().collect::<String>(),
10090 ));
10091
10092 // Update this selection's endpoints to reflect the indentation.
10093 if row == selection.start.row {
10094 selection.start.column += indent_delta.len;
10095 }
10096 if row == selection.end.row {
10097 selection.end.column += indent_delta.len;
10098 delta_for_end_row = indent_delta.len;
10099 }
10100 }
10101
10102 if selection.start.row == selection.end.row {
10103 delta_for_start_row + delta_for_end_row
10104 } else {
10105 delta_for_end_row
10106 }
10107 }
10108
10109 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10110 if self.read_only(cx) {
10111 return;
10112 }
10113 if self.mode.is_single_line() {
10114 cx.propagate();
10115 return;
10116 }
10117
10118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10120 let selections = self.selections.all::<Point>(cx);
10121 let mut deletion_ranges = Vec::new();
10122 let mut last_outdent = None;
10123 {
10124 let buffer = self.buffer.read(cx);
10125 let snapshot = buffer.snapshot(cx);
10126 for selection in &selections {
10127 let settings = buffer.language_settings_at(selection.start, cx);
10128 let tab_size = settings.tab_size.get();
10129 let mut rows = selection.spanned_rows(false, &display_map);
10130
10131 // Avoid re-outdenting a row that has already been outdented by a
10132 // previous selection.
10133 if let Some(last_row) = last_outdent
10134 && last_row == rows.start
10135 {
10136 rows.start = rows.start.next_row();
10137 }
10138 let has_multiple_rows = rows.len() > 1;
10139 for row in rows.iter_rows() {
10140 let indent_size = snapshot.indent_size_for_line(row);
10141 if indent_size.len > 0 {
10142 let deletion_len = match indent_size.kind {
10143 IndentKind::Space => {
10144 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10145 if columns_to_prev_tab_stop == 0 {
10146 tab_size
10147 } else {
10148 columns_to_prev_tab_stop
10149 }
10150 }
10151 IndentKind::Tab => 1,
10152 };
10153 let start = if has_multiple_rows
10154 || deletion_len > selection.start.column
10155 || indent_size.len < selection.start.column
10156 {
10157 0
10158 } else {
10159 selection.start.column - deletion_len
10160 };
10161 deletion_ranges.push(
10162 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10163 );
10164 last_outdent = Some(row);
10165 }
10166 }
10167 }
10168 }
10169
10170 self.transact(window, cx, |this, window, cx| {
10171 this.buffer.update(cx, |buffer, cx| {
10172 let empty_str: Arc<str> = Arc::default();
10173 buffer.edit(
10174 deletion_ranges
10175 .into_iter()
10176 .map(|range| (range, empty_str.clone())),
10177 None,
10178 cx,
10179 );
10180 });
10181 let selections = this.selections.all::<usize>(cx);
10182 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10183 });
10184 }
10185
10186 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10187 if self.read_only(cx) {
10188 return;
10189 }
10190 if self.mode.is_single_line() {
10191 cx.propagate();
10192 return;
10193 }
10194
10195 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10196 let selections = self
10197 .selections
10198 .all::<usize>(cx)
10199 .into_iter()
10200 .map(|s| s.range());
10201
10202 self.transact(window, cx, |this, window, cx| {
10203 this.buffer.update(cx, |buffer, cx| {
10204 buffer.autoindent_ranges(selections, cx);
10205 });
10206 let selections = this.selections.all::<usize>(cx);
10207 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10208 });
10209 }
10210
10211 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10212 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10213 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10214 let selections = self.selections.all::<Point>(cx);
10215
10216 let mut new_cursors = Vec::new();
10217 let mut edit_ranges = Vec::new();
10218 let mut selections = selections.iter().peekable();
10219 while let Some(selection) = selections.next() {
10220 let mut rows = selection.spanned_rows(false, &display_map);
10221 let goal_display_column = selection.head().to_display_point(&display_map).column();
10222
10223 // Accumulate contiguous regions of rows that we want to delete.
10224 while let Some(next_selection) = selections.peek() {
10225 let next_rows = next_selection.spanned_rows(false, &display_map);
10226 if next_rows.start <= rows.end {
10227 rows.end = next_rows.end;
10228 selections.next().unwrap();
10229 } else {
10230 break;
10231 }
10232 }
10233
10234 let buffer = &display_map.buffer_snapshot;
10235 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10236 let edit_end;
10237 let cursor_buffer_row;
10238 if buffer.max_point().row >= rows.end.0 {
10239 // If there's a line after the range, delete the \n from the end of the row range
10240 // and position the cursor on the next line.
10241 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10242 cursor_buffer_row = rows.end;
10243 } else {
10244 // If there isn't a line after the range, delete the \n from the line before the
10245 // start of the row range and position the cursor there.
10246 edit_start = edit_start.saturating_sub(1);
10247 edit_end = buffer.len();
10248 cursor_buffer_row = rows.start.previous_row();
10249 }
10250
10251 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10252 *cursor.column_mut() =
10253 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10254
10255 new_cursors.push((
10256 selection.id,
10257 buffer.anchor_after(cursor.to_point(&display_map)),
10258 ));
10259 edit_ranges.push(edit_start..edit_end);
10260 }
10261
10262 self.transact(window, cx, |this, window, cx| {
10263 let buffer = this.buffer.update(cx, |buffer, cx| {
10264 let empty_str: Arc<str> = Arc::default();
10265 buffer.edit(
10266 edit_ranges
10267 .into_iter()
10268 .map(|range| (range, empty_str.clone())),
10269 None,
10270 cx,
10271 );
10272 buffer.snapshot(cx)
10273 });
10274 let new_selections = new_cursors
10275 .into_iter()
10276 .map(|(id, cursor)| {
10277 let cursor = cursor.to_point(&buffer);
10278 Selection {
10279 id,
10280 start: cursor,
10281 end: cursor,
10282 reversed: false,
10283 goal: SelectionGoal::None,
10284 }
10285 })
10286 .collect();
10287
10288 this.change_selections(Default::default(), window, cx, |s| {
10289 s.select(new_selections);
10290 });
10291 });
10292 }
10293
10294 pub fn join_lines_impl(
10295 &mut self,
10296 insert_whitespace: bool,
10297 window: &mut Window,
10298 cx: &mut Context<Self>,
10299 ) {
10300 if self.read_only(cx) {
10301 return;
10302 }
10303 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10304 for selection in self.selections.all::<Point>(cx) {
10305 let start = MultiBufferRow(selection.start.row);
10306 // Treat single line selections as if they include the next line. Otherwise this action
10307 // would do nothing for single line selections individual cursors.
10308 let end = if selection.start.row == selection.end.row {
10309 MultiBufferRow(selection.start.row + 1)
10310 } else {
10311 MultiBufferRow(selection.end.row)
10312 };
10313
10314 if let Some(last_row_range) = row_ranges.last_mut()
10315 && start <= last_row_range.end
10316 {
10317 last_row_range.end = end;
10318 continue;
10319 }
10320 row_ranges.push(start..end);
10321 }
10322
10323 let snapshot = self.buffer.read(cx).snapshot(cx);
10324 let mut cursor_positions = Vec::new();
10325 for row_range in &row_ranges {
10326 let anchor = snapshot.anchor_before(Point::new(
10327 row_range.end.previous_row().0,
10328 snapshot.line_len(row_range.end.previous_row()),
10329 ));
10330 cursor_positions.push(anchor..anchor);
10331 }
10332
10333 self.transact(window, cx, |this, window, cx| {
10334 for row_range in row_ranges.into_iter().rev() {
10335 for row in row_range.iter_rows().rev() {
10336 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10337 let next_line_row = row.next_row();
10338 let indent = snapshot.indent_size_for_line(next_line_row);
10339 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10340
10341 let replace =
10342 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10343 " "
10344 } else {
10345 ""
10346 };
10347
10348 this.buffer.update(cx, |buffer, cx| {
10349 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10350 });
10351 }
10352 }
10353
10354 this.change_selections(Default::default(), window, cx, |s| {
10355 s.select_anchor_ranges(cursor_positions)
10356 });
10357 });
10358 }
10359
10360 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10361 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10362 self.join_lines_impl(true, window, cx);
10363 }
10364
10365 pub fn sort_lines_case_sensitive(
10366 &mut self,
10367 _: &SortLinesCaseSensitive,
10368 window: &mut Window,
10369 cx: &mut Context<Self>,
10370 ) {
10371 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10372 }
10373
10374 pub fn sort_lines_by_length(
10375 &mut self,
10376 _: &SortLinesByLength,
10377 window: &mut Window,
10378 cx: &mut Context<Self>,
10379 ) {
10380 self.manipulate_immutable_lines(window, cx, |lines| {
10381 lines.sort_by_key(|&line| line.chars().count())
10382 })
10383 }
10384
10385 pub fn sort_lines_case_insensitive(
10386 &mut self,
10387 _: &SortLinesCaseInsensitive,
10388 window: &mut Window,
10389 cx: &mut Context<Self>,
10390 ) {
10391 self.manipulate_immutable_lines(window, cx, |lines| {
10392 lines.sort_by_key(|line| line.to_lowercase())
10393 })
10394 }
10395
10396 pub fn unique_lines_case_insensitive(
10397 &mut self,
10398 _: &UniqueLinesCaseInsensitive,
10399 window: &mut Window,
10400 cx: &mut Context<Self>,
10401 ) {
10402 self.manipulate_immutable_lines(window, cx, |lines| {
10403 let mut seen = HashSet::default();
10404 lines.retain(|line| seen.insert(line.to_lowercase()));
10405 })
10406 }
10407
10408 pub fn unique_lines_case_sensitive(
10409 &mut self,
10410 _: &UniqueLinesCaseSensitive,
10411 window: &mut Window,
10412 cx: &mut Context<Self>,
10413 ) {
10414 self.manipulate_immutable_lines(window, cx, |lines| {
10415 let mut seen = HashSet::default();
10416 lines.retain(|line| seen.insert(*line));
10417 })
10418 }
10419
10420 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10421 let Some(project) = self.project.clone() else {
10422 return;
10423 };
10424 self.reload(project, window, cx)
10425 .detach_and_notify_err(window, cx);
10426 }
10427
10428 pub fn restore_file(
10429 &mut self,
10430 _: &::git::RestoreFile,
10431 window: &mut Window,
10432 cx: &mut Context<Self>,
10433 ) {
10434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10435 let mut buffer_ids = HashSet::default();
10436 let snapshot = self.buffer().read(cx).snapshot(cx);
10437 for selection in self.selections.all::<usize>(cx) {
10438 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10439 }
10440
10441 let buffer = self.buffer().read(cx);
10442 let ranges = buffer_ids
10443 .into_iter()
10444 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10445 .collect::<Vec<_>>();
10446
10447 self.restore_hunks_in_ranges(ranges, window, cx);
10448 }
10449
10450 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10451 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10452 let selections = self
10453 .selections
10454 .all(cx)
10455 .into_iter()
10456 .map(|s| s.range())
10457 .collect();
10458 self.restore_hunks_in_ranges(selections, window, cx);
10459 }
10460
10461 pub fn restore_hunks_in_ranges(
10462 &mut self,
10463 ranges: Vec<Range<Point>>,
10464 window: &mut Window,
10465 cx: &mut Context<Editor>,
10466 ) {
10467 let mut revert_changes = HashMap::default();
10468 let chunk_by = self
10469 .snapshot(window, cx)
10470 .hunks_for_ranges(ranges)
10471 .into_iter()
10472 .chunk_by(|hunk| hunk.buffer_id);
10473 for (buffer_id, hunks) in &chunk_by {
10474 let hunks = hunks.collect::<Vec<_>>();
10475 for hunk in &hunks {
10476 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10477 }
10478 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10479 }
10480 drop(chunk_by);
10481 if !revert_changes.is_empty() {
10482 self.transact(window, cx, |editor, window, cx| {
10483 editor.restore(revert_changes, window, cx);
10484 });
10485 }
10486 }
10487
10488 pub fn open_active_item_in_terminal(
10489 &mut self,
10490 _: &OpenInTerminal,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10495 let project_path = buffer.read(cx).project_path(cx)?;
10496 let project = self.project()?.read(cx);
10497 let entry = project.entry_for_path(&project_path, cx)?;
10498 let parent = match &entry.canonical_path {
10499 Some(canonical_path) => canonical_path.to_path_buf(),
10500 None => project.absolute_path(&project_path, cx)?,
10501 }
10502 .parent()?
10503 .to_path_buf();
10504 Some(parent)
10505 }) {
10506 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10507 }
10508 }
10509
10510 fn set_breakpoint_context_menu(
10511 &mut self,
10512 display_row: DisplayRow,
10513 position: Option<Anchor>,
10514 clicked_point: gpui::Point<Pixels>,
10515 window: &mut Window,
10516 cx: &mut Context<Self>,
10517 ) {
10518 let source = self
10519 .buffer
10520 .read(cx)
10521 .snapshot(cx)
10522 .anchor_before(Point::new(display_row.0, 0u32));
10523
10524 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10525
10526 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10527 self,
10528 source,
10529 clicked_point,
10530 context_menu,
10531 window,
10532 cx,
10533 );
10534 }
10535
10536 fn add_edit_breakpoint_block(
10537 &mut self,
10538 anchor: Anchor,
10539 breakpoint: &Breakpoint,
10540 edit_action: BreakpointPromptEditAction,
10541 window: &mut Window,
10542 cx: &mut Context<Self>,
10543 ) {
10544 let weak_editor = cx.weak_entity();
10545 let bp_prompt = cx.new(|cx| {
10546 BreakpointPromptEditor::new(
10547 weak_editor,
10548 anchor,
10549 breakpoint.clone(),
10550 edit_action,
10551 window,
10552 cx,
10553 )
10554 });
10555
10556 let height = bp_prompt.update(cx, |this, cx| {
10557 this.prompt
10558 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10559 });
10560 let cloned_prompt = bp_prompt.clone();
10561 let blocks = vec![BlockProperties {
10562 style: BlockStyle::Sticky,
10563 placement: BlockPlacement::Above(anchor),
10564 height: Some(height),
10565 render: Arc::new(move |cx| {
10566 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10567 cloned_prompt.clone().into_any_element()
10568 }),
10569 priority: 0,
10570 }];
10571
10572 let focus_handle = bp_prompt.focus_handle(cx);
10573 window.focus(&focus_handle);
10574
10575 let block_ids = self.insert_blocks(blocks, None, cx);
10576 bp_prompt.update(cx, |prompt, _| {
10577 prompt.add_block_ids(block_ids);
10578 });
10579 }
10580
10581 pub(crate) fn breakpoint_at_row(
10582 &self,
10583 row: u32,
10584 window: &mut Window,
10585 cx: &mut Context<Self>,
10586 ) -> Option<(Anchor, Breakpoint)> {
10587 let snapshot = self.snapshot(window, cx);
10588 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10589
10590 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10591 }
10592
10593 pub(crate) fn breakpoint_at_anchor(
10594 &self,
10595 breakpoint_position: Anchor,
10596 snapshot: &EditorSnapshot,
10597 cx: &mut Context<Self>,
10598 ) -> Option<(Anchor, Breakpoint)> {
10599 let buffer = self
10600 .buffer
10601 .read(cx)
10602 .buffer_for_anchor(breakpoint_position, cx)?;
10603
10604 let enclosing_excerpt = breakpoint_position.excerpt_id;
10605 let buffer_snapshot = buffer.read(cx).snapshot();
10606
10607 let row = buffer_snapshot
10608 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10609 .row;
10610
10611 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10612 let anchor_end = snapshot
10613 .buffer_snapshot
10614 .anchor_after(Point::new(row, line_len));
10615
10616 self.breakpoint_store
10617 .as_ref()?
10618 .read_with(cx, |breakpoint_store, cx| {
10619 breakpoint_store
10620 .breakpoints(
10621 &buffer,
10622 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10623 &buffer_snapshot,
10624 cx,
10625 )
10626 .next()
10627 .and_then(|(bp, _)| {
10628 let breakpoint_row = buffer_snapshot
10629 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10630 .row;
10631
10632 if breakpoint_row == row {
10633 snapshot
10634 .buffer_snapshot
10635 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10636 .map(|position| (position, bp.bp.clone()))
10637 } else {
10638 None
10639 }
10640 })
10641 })
10642 }
10643
10644 pub fn edit_log_breakpoint(
10645 &mut self,
10646 _: &EditLogBreakpoint,
10647 window: &mut Window,
10648 cx: &mut Context<Self>,
10649 ) {
10650 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10651 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10652 message: None,
10653 state: BreakpointState::Enabled,
10654 condition: None,
10655 hit_condition: None,
10656 });
10657
10658 self.add_edit_breakpoint_block(
10659 anchor,
10660 &breakpoint,
10661 BreakpointPromptEditAction::Log,
10662 window,
10663 cx,
10664 );
10665 }
10666 }
10667
10668 fn breakpoints_at_cursors(
10669 &self,
10670 window: &mut Window,
10671 cx: &mut Context<Self>,
10672 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10673 let snapshot = self.snapshot(window, cx);
10674 let cursors = self
10675 .selections
10676 .disjoint_anchors()
10677 .iter()
10678 .map(|selection| {
10679 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10680
10681 let breakpoint_position = self
10682 .breakpoint_at_row(cursor_position.row, window, cx)
10683 .map(|bp| bp.0)
10684 .unwrap_or_else(|| {
10685 snapshot
10686 .display_snapshot
10687 .buffer_snapshot
10688 .anchor_after(Point::new(cursor_position.row, 0))
10689 });
10690
10691 let breakpoint = self
10692 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10693 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10694
10695 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10696 })
10697 // 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.
10698 .collect::<HashMap<Anchor, _>>();
10699
10700 cursors.into_iter().collect()
10701 }
10702
10703 pub fn enable_breakpoint(
10704 &mut self,
10705 _: &crate::actions::EnableBreakpoint,
10706 window: &mut Window,
10707 cx: &mut Context<Self>,
10708 ) {
10709 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10710 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10711 continue;
10712 };
10713 self.edit_breakpoint_at_anchor(
10714 anchor,
10715 breakpoint,
10716 BreakpointEditAction::InvertState,
10717 cx,
10718 );
10719 }
10720 }
10721
10722 pub fn disable_breakpoint(
10723 &mut self,
10724 _: &crate::actions::DisableBreakpoint,
10725 window: &mut Window,
10726 cx: &mut Context<Self>,
10727 ) {
10728 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10729 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10730 continue;
10731 };
10732 self.edit_breakpoint_at_anchor(
10733 anchor,
10734 breakpoint,
10735 BreakpointEditAction::InvertState,
10736 cx,
10737 );
10738 }
10739 }
10740
10741 pub fn toggle_breakpoint(
10742 &mut self,
10743 _: &crate::actions::ToggleBreakpoint,
10744 window: &mut Window,
10745 cx: &mut Context<Self>,
10746 ) {
10747 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10748 if let Some(breakpoint) = breakpoint {
10749 self.edit_breakpoint_at_anchor(
10750 anchor,
10751 breakpoint,
10752 BreakpointEditAction::Toggle,
10753 cx,
10754 );
10755 } else {
10756 self.edit_breakpoint_at_anchor(
10757 anchor,
10758 Breakpoint::new_standard(),
10759 BreakpointEditAction::Toggle,
10760 cx,
10761 );
10762 }
10763 }
10764 }
10765
10766 pub fn edit_breakpoint_at_anchor(
10767 &mut self,
10768 breakpoint_position: Anchor,
10769 breakpoint: Breakpoint,
10770 edit_action: BreakpointEditAction,
10771 cx: &mut Context<Self>,
10772 ) {
10773 let Some(breakpoint_store) = &self.breakpoint_store else {
10774 return;
10775 };
10776
10777 let Some(buffer) = self
10778 .buffer
10779 .read(cx)
10780 .buffer_for_anchor(breakpoint_position, cx)
10781 else {
10782 return;
10783 };
10784
10785 breakpoint_store.update(cx, |breakpoint_store, cx| {
10786 breakpoint_store.toggle_breakpoint(
10787 buffer,
10788 BreakpointWithPosition {
10789 position: breakpoint_position.text_anchor,
10790 bp: breakpoint,
10791 },
10792 edit_action,
10793 cx,
10794 );
10795 });
10796
10797 cx.notify();
10798 }
10799
10800 #[cfg(any(test, feature = "test-support"))]
10801 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10802 self.breakpoint_store.clone()
10803 }
10804
10805 pub fn prepare_restore_change(
10806 &self,
10807 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10808 hunk: &MultiBufferDiffHunk,
10809 cx: &mut App,
10810 ) -> Option<()> {
10811 if hunk.is_created_file() {
10812 return None;
10813 }
10814 let buffer = self.buffer.read(cx);
10815 let diff = buffer.diff_for(hunk.buffer_id)?;
10816 let buffer = buffer.buffer(hunk.buffer_id)?;
10817 let buffer = buffer.read(cx);
10818 let original_text = diff
10819 .read(cx)
10820 .base_text()
10821 .as_rope()
10822 .slice(hunk.diff_base_byte_range.clone());
10823 let buffer_snapshot = buffer.snapshot();
10824 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10825 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10826 probe
10827 .0
10828 .start
10829 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10830 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10831 }) {
10832 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10833 Some(())
10834 } else {
10835 None
10836 }
10837 }
10838
10839 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10840 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10841 }
10842
10843 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10844 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10845 }
10846
10847 fn manipulate_lines<M>(
10848 &mut self,
10849 window: &mut Window,
10850 cx: &mut Context<Self>,
10851 mut manipulate: M,
10852 ) where
10853 M: FnMut(&str) -> LineManipulationResult,
10854 {
10855 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10856
10857 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10858 let buffer = self.buffer.read(cx).snapshot(cx);
10859
10860 let mut edits = Vec::new();
10861
10862 let selections = self.selections.all::<Point>(cx);
10863 let mut selections = selections.iter().peekable();
10864 let mut contiguous_row_selections = Vec::new();
10865 let mut new_selections = Vec::new();
10866 let mut added_lines = 0;
10867 let mut removed_lines = 0;
10868
10869 while let Some(selection) = selections.next() {
10870 let (start_row, end_row) = consume_contiguous_rows(
10871 &mut contiguous_row_selections,
10872 selection,
10873 &display_map,
10874 &mut selections,
10875 );
10876
10877 let start_point = Point::new(start_row.0, 0);
10878 let end_point = Point::new(
10879 end_row.previous_row().0,
10880 buffer.line_len(end_row.previous_row()),
10881 );
10882 let text = buffer
10883 .text_for_range(start_point..end_point)
10884 .collect::<String>();
10885
10886 let LineManipulationResult {
10887 new_text,
10888 line_count_before,
10889 line_count_after,
10890 } = manipulate(&text);
10891
10892 edits.push((start_point..end_point, new_text));
10893
10894 // Selections must change based on added and removed line count
10895 let start_row =
10896 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10897 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10898 new_selections.push(Selection {
10899 id: selection.id,
10900 start: start_row,
10901 end: end_row,
10902 goal: SelectionGoal::None,
10903 reversed: selection.reversed,
10904 });
10905
10906 if line_count_after > line_count_before {
10907 added_lines += line_count_after - line_count_before;
10908 } else if line_count_before > line_count_after {
10909 removed_lines += line_count_before - line_count_after;
10910 }
10911 }
10912
10913 self.transact(window, cx, |this, window, cx| {
10914 let buffer = this.buffer.update(cx, |buffer, cx| {
10915 buffer.edit(edits, None, cx);
10916 buffer.snapshot(cx)
10917 });
10918
10919 // Recalculate offsets on newly edited buffer
10920 let new_selections = new_selections
10921 .iter()
10922 .map(|s| {
10923 let start_point = Point::new(s.start.0, 0);
10924 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10925 Selection {
10926 id: s.id,
10927 start: buffer.point_to_offset(start_point),
10928 end: buffer.point_to_offset(end_point),
10929 goal: s.goal,
10930 reversed: s.reversed,
10931 }
10932 })
10933 .collect();
10934
10935 this.change_selections(Default::default(), window, cx, |s| {
10936 s.select(new_selections);
10937 });
10938
10939 this.request_autoscroll(Autoscroll::fit(), cx);
10940 });
10941 }
10942
10943 fn manipulate_immutable_lines<Fn>(
10944 &mut self,
10945 window: &mut Window,
10946 cx: &mut Context<Self>,
10947 mut callback: Fn,
10948 ) where
10949 Fn: FnMut(&mut Vec<&str>),
10950 {
10951 self.manipulate_lines(window, cx, |text| {
10952 let mut lines: Vec<&str> = text.split('\n').collect();
10953 let line_count_before = lines.len();
10954
10955 callback(&mut lines);
10956
10957 LineManipulationResult {
10958 new_text: lines.join("\n"),
10959 line_count_before,
10960 line_count_after: lines.len(),
10961 }
10962 });
10963 }
10964
10965 fn manipulate_mutable_lines<Fn>(
10966 &mut self,
10967 window: &mut Window,
10968 cx: &mut Context<Self>,
10969 mut callback: Fn,
10970 ) where
10971 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10972 {
10973 self.manipulate_lines(window, cx, |text| {
10974 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10975 let line_count_before = lines.len();
10976
10977 callback(&mut lines);
10978
10979 LineManipulationResult {
10980 new_text: lines.join("\n"),
10981 line_count_before,
10982 line_count_after: lines.len(),
10983 }
10984 });
10985 }
10986
10987 pub fn convert_indentation_to_spaces(
10988 &mut self,
10989 _: &ConvertIndentationToSpaces,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 ) {
10993 let settings = self.buffer.read(cx).language_settings(cx);
10994 let tab_size = settings.tab_size.get() as usize;
10995
10996 self.manipulate_mutable_lines(window, cx, |lines| {
10997 // Allocates a reasonably sized scratch buffer once for the whole loop
10998 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10999 // Avoids recomputing spaces that could be inserted many times
11000 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11001 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11002 .collect();
11003
11004 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11005 let mut chars = line.as_ref().chars();
11006 let mut col = 0;
11007 let mut changed = false;
11008
11009 for ch in chars.by_ref() {
11010 match ch {
11011 ' ' => {
11012 reindented_line.push(' ');
11013 col += 1;
11014 }
11015 '\t' => {
11016 // \t are converted to spaces depending on the current column
11017 let spaces_len = tab_size - (col % tab_size);
11018 reindented_line.extend(&space_cache[spaces_len - 1]);
11019 col += spaces_len;
11020 changed = true;
11021 }
11022 _ => {
11023 // If we dont append before break, the character is consumed
11024 reindented_line.push(ch);
11025 break;
11026 }
11027 }
11028 }
11029
11030 if !changed {
11031 reindented_line.clear();
11032 continue;
11033 }
11034 // Append the rest of the line and replace old reference with new one
11035 reindented_line.extend(chars);
11036 *line = Cow::Owned(reindented_line.clone());
11037 reindented_line.clear();
11038 }
11039 });
11040 }
11041
11042 pub fn convert_indentation_to_tabs(
11043 &mut self,
11044 _: &ConvertIndentationToTabs,
11045 window: &mut Window,
11046 cx: &mut Context<Self>,
11047 ) {
11048 let settings = self.buffer.read(cx).language_settings(cx);
11049 let tab_size = settings.tab_size.get() as usize;
11050
11051 self.manipulate_mutable_lines(window, cx, |lines| {
11052 // Allocates a reasonably sized buffer once for the whole loop
11053 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11054 // Avoids recomputing spaces that could be inserted many times
11055 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11056 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11057 .collect();
11058
11059 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11060 let mut chars = line.chars();
11061 let mut spaces_count = 0;
11062 let mut first_non_indent_char = None;
11063 let mut changed = false;
11064
11065 for ch in chars.by_ref() {
11066 match ch {
11067 ' ' => {
11068 // Keep track of spaces. Append \t when we reach tab_size
11069 spaces_count += 1;
11070 changed = true;
11071 if spaces_count == tab_size {
11072 reindented_line.push('\t');
11073 spaces_count = 0;
11074 }
11075 }
11076 '\t' => {
11077 reindented_line.push('\t');
11078 spaces_count = 0;
11079 }
11080 _ => {
11081 // Dont append it yet, we might have remaining spaces
11082 first_non_indent_char = Some(ch);
11083 break;
11084 }
11085 }
11086 }
11087
11088 if !changed {
11089 reindented_line.clear();
11090 continue;
11091 }
11092 // Remaining spaces that didn't make a full tab stop
11093 if spaces_count > 0 {
11094 reindented_line.extend(&space_cache[spaces_count - 1]);
11095 }
11096 // If we consume an extra character that was not indentation, add it back
11097 if let Some(extra_char) = first_non_indent_char {
11098 reindented_line.push(extra_char);
11099 }
11100 // Append the rest of the line and replace old reference with new one
11101 reindented_line.extend(chars);
11102 *line = Cow::Owned(reindented_line.clone());
11103 reindented_line.clear();
11104 }
11105 });
11106 }
11107
11108 pub fn convert_to_upper_case(
11109 &mut self,
11110 _: &ConvertToUpperCase,
11111 window: &mut Window,
11112 cx: &mut Context<Self>,
11113 ) {
11114 self.manipulate_text(window, cx, |text| text.to_uppercase())
11115 }
11116
11117 pub fn convert_to_lower_case(
11118 &mut self,
11119 _: &ConvertToLowerCase,
11120 window: &mut Window,
11121 cx: &mut Context<Self>,
11122 ) {
11123 self.manipulate_text(window, cx, |text| text.to_lowercase())
11124 }
11125
11126 pub fn convert_to_title_case(
11127 &mut self,
11128 _: &ConvertToTitleCase,
11129 window: &mut Window,
11130 cx: &mut Context<Self>,
11131 ) {
11132 self.manipulate_text(window, cx, |text| {
11133 text.split('\n')
11134 .map(|line| line.to_case(Case::Title))
11135 .join("\n")
11136 })
11137 }
11138
11139 pub fn convert_to_snake_case(
11140 &mut self,
11141 _: &ConvertToSnakeCase,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 ) {
11145 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11146 }
11147
11148 pub fn convert_to_kebab_case(
11149 &mut self,
11150 _: &ConvertToKebabCase,
11151 window: &mut Window,
11152 cx: &mut Context<Self>,
11153 ) {
11154 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11155 }
11156
11157 pub fn convert_to_upper_camel_case(
11158 &mut self,
11159 _: &ConvertToUpperCamelCase,
11160 window: &mut Window,
11161 cx: &mut Context<Self>,
11162 ) {
11163 self.manipulate_text(window, cx, |text| {
11164 text.split('\n')
11165 .map(|line| line.to_case(Case::UpperCamel))
11166 .join("\n")
11167 })
11168 }
11169
11170 pub fn convert_to_lower_camel_case(
11171 &mut self,
11172 _: &ConvertToLowerCamelCase,
11173 window: &mut Window,
11174 cx: &mut Context<Self>,
11175 ) {
11176 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11177 }
11178
11179 pub fn convert_to_opposite_case(
11180 &mut self,
11181 _: &ConvertToOppositeCase,
11182 window: &mut Window,
11183 cx: &mut Context<Self>,
11184 ) {
11185 self.manipulate_text(window, cx, |text| {
11186 text.chars()
11187 .fold(String::with_capacity(text.len()), |mut t, c| {
11188 if c.is_uppercase() {
11189 t.extend(c.to_lowercase());
11190 } else {
11191 t.extend(c.to_uppercase());
11192 }
11193 t
11194 })
11195 })
11196 }
11197
11198 pub fn convert_to_sentence_case(
11199 &mut self,
11200 _: &ConvertToSentenceCase,
11201 window: &mut Window,
11202 cx: &mut Context<Self>,
11203 ) {
11204 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11205 }
11206
11207 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11208 self.manipulate_text(window, cx, |text| {
11209 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11210 if has_upper_case_characters {
11211 text.to_lowercase()
11212 } else {
11213 text.to_uppercase()
11214 }
11215 })
11216 }
11217
11218 pub fn convert_to_rot13(
11219 &mut self,
11220 _: &ConvertToRot13,
11221 window: &mut Window,
11222 cx: &mut Context<Self>,
11223 ) {
11224 self.manipulate_text(window, cx, |text| {
11225 text.chars()
11226 .map(|c| match c {
11227 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11228 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11229 _ => c,
11230 })
11231 .collect()
11232 })
11233 }
11234
11235 pub fn convert_to_rot47(
11236 &mut self,
11237 _: &ConvertToRot47,
11238 window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 self.manipulate_text(window, cx, |text| {
11242 text.chars()
11243 .map(|c| {
11244 let code_point = c as u32;
11245 if code_point >= 33 && code_point <= 126 {
11246 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11247 }
11248 c
11249 })
11250 .collect()
11251 })
11252 }
11253
11254 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11255 where
11256 Fn: FnMut(&str) -> String,
11257 {
11258 let buffer = self.buffer.read(cx).snapshot(cx);
11259
11260 let mut new_selections = Vec::new();
11261 let mut edits = Vec::new();
11262 let mut selection_adjustment = 0i32;
11263
11264 for selection in self.selections.all::<usize>(cx) {
11265 let selection_is_empty = selection.is_empty();
11266
11267 let (start, end) = if selection_is_empty {
11268 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11269 (word_range.start, word_range.end)
11270 } else {
11271 (selection.start, selection.end)
11272 };
11273
11274 let text = buffer.text_for_range(start..end).collect::<String>();
11275 let old_length = text.len() as i32;
11276 let text = callback(&text);
11277
11278 new_selections.push(Selection {
11279 start: (start as i32 - selection_adjustment) as usize,
11280 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11281 goal: SelectionGoal::None,
11282 ..selection
11283 });
11284
11285 selection_adjustment += old_length - text.len() as i32;
11286
11287 edits.push((start..end, text));
11288 }
11289
11290 self.transact(window, cx, |this, window, cx| {
11291 this.buffer.update(cx, |buffer, cx| {
11292 buffer.edit(edits, None, cx);
11293 });
11294
11295 this.change_selections(Default::default(), window, cx, |s| {
11296 s.select(new_selections);
11297 });
11298
11299 this.request_autoscroll(Autoscroll::fit(), cx);
11300 });
11301 }
11302
11303 pub fn move_selection_on_drop(
11304 &mut self,
11305 selection: &Selection<Anchor>,
11306 target: DisplayPoint,
11307 is_cut: bool,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11312 let buffer = &display_map.buffer_snapshot;
11313 let mut edits = Vec::new();
11314 let insert_point = display_map
11315 .clip_point(target, Bias::Left)
11316 .to_point(&display_map);
11317 let text = buffer
11318 .text_for_range(selection.start..selection.end)
11319 .collect::<String>();
11320 if is_cut {
11321 edits.push(((selection.start..selection.end), String::new()));
11322 }
11323 let insert_anchor = buffer.anchor_before(insert_point);
11324 edits.push(((insert_anchor..insert_anchor), text));
11325 let last_edit_start = insert_anchor.bias_left(buffer);
11326 let last_edit_end = insert_anchor.bias_right(buffer);
11327 self.transact(window, cx, |this, window, cx| {
11328 this.buffer.update(cx, |buffer, cx| {
11329 buffer.edit(edits, None, cx);
11330 });
11331 this.change_selections(Default::default(), window, cx, |s| {
11332 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11333 });
11334 });
11335 }
11336
11337 pub fn clear_selection_drag_state(&mut self) {
11338 self.selection_drag_state = SelectionDragState::None;
11339 }
11340
11341 pub fn duplicate(
11342 &mut self,
11343 upwards: bool,
11344 whole_lines: bool,
11345 window: &mut Window,
11346 cx: &mut Context<Self>,
11347 ) {
11348 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11349
11350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11351 let buffer = &display_map.buffer_snapshot;
11352 let selections = self.selections.all::<Point>(cx);
11353
11354 let mut edits = Vec::new();
11355 let mut selections_iter = selections.iter().peekable();
11356 while let Some(selection) = selections_iter.next() {
11357 let mut rows = selection.spanned_rows(false, &display_map);
11358 // duplicate line-wise
11359 if whole_lines || selection.start == selection.end {
11360 // Avoid duplicating the same lines twice.
11361 while let Some(next_selection) = selections_iter.peek() {
11362 let next_rows = next_selection.spanned_rows(false, &display_map);
11363 if next_rows.start < rows.end {
11364 rows.end = next_rows.end;
11365 selections_iter.next().unwrap();
11366 } else {
11367 break;
11368 }
11369 }
11370
11371 // Copy the text from the selected row region and splice it either at the start
11372 // or end of the region.
11373 let start = Point::new(rows.start.0, 0);
11374 let end = Point::new(
11375 rows.end.previous_row().0,
11376 buffer.line_len(rows.end.previous_row()),
11377 );
11378 let text = buffer
11379 .text_for_range(start..end)
11380 .chain(Some("\n"))
11381 .collect::<String>();
11382 let insert_location = if upwards {
11383 Point::new(rows.end.0, 0)
11384 } else {
11385 start
11386 };
11387 edits.push((insert_location..insert_location, text));
11388 } else {
11389 // duplicate character-wise
11390 let start = selection.start;
11391 let end = selection.end;
11392 let text = buffer.text_for_range(start..end).collect::<String>();
11393 edits.push((selection.end..selection.end, text));
11394 }
11395 }
11396
11397 self.transact(window, cx, |this, _, cx| {
11398 this.buffer.update(cx, |buffer, cx| {
11399 buffer.edit(edits, None, cx);
11400 });
11401
11402 this.request_autoscroll(Autoscroll::fit(), cx);
11403 });
11404 }
11405
11406 pub fn duplicate_line_up(
11407 &mut self,
11408 _: &DuplicateLineUp,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 self.duplicate(true, true, window, cx);
11413 }
11414
11415 pub fn duplicate_line_down(
11416 &mut self,
11417 _: &DuplicateLineDown,
11418 window: &mut Window,
11419 cx: &mut Context<Self>,
11420 ) {
11421 self.duplicate(false, true, window, cx);
11422 }
11423
11424 pub fn duplicate_selection(
11425 &mut self,
11426 _: &DuplicateSelection,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 self.duplicate(false, false, window, cx);
11431 }
11432
11433 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11435 if self.mode.is_single_line() {
11436 cx.propagate();
11437 return;
11438 }
11439
11440 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11441 let buffer = self.buffer.read(cx).snapshot(cx);
11442
11443 let mut edits = Vec::new();
11444 let mut unfold_ranges = Vec::new();
11445 let mut refold_creases = Vec::new();
11446
11447 let selections = self.selections.all::<Point>(cx);
11448 let mut selections = selections.iter().peekable();
11449 let mut contiguous_row_selections = Vec::new();
11450 let mut new_selections = Vec::new();
11451
11452 while let Some(selection) = selections.next() {
11453 // Find all the selections that span a contiguous row range
11454 let (start_row, end_row) = consume_contiguous_rows(
11455 &mut contiguous_row_selections,
11456 selection,
11457 &display_map,
11458 &mut selections,
11459 );
11460
11461 // Move the text spanned by the row range to be before the line preceding the row range
11462 if start_row.0 > 0 {
11463 let range_to_move = Point::new(
11464 start_row.previous_row().0,
11465 buffer.line_len(start_row.previous_row()),
11466 )
11467 ..Point::new(
11468 end_row.previous_row().0,
11469 buffer.line_len(end_row.previous_row()),
11470 );
11471 let insertion_point = display_map
11472 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11473 .0;
11474
11475 // Don't move lines across excerpts
11476 if buffer
11477 .excerpt_containing(insertion_point..range_to_move.end)
11478 .is_some()
11479 {
11480 let text = buffer
11481 .text_for_range(range_to_move.clone())
11482 .flat_map(|s| s.chars())
11483 .skip(1)
11484 .chain(['\n'])
11485 .collect::<String>();
11486
11487 edits.push((
11488 buffer.anchor_after(range_to_move.start)
11489 ..buffer.anchor_before(range_to_move.end),
11490 String::new(),
11491 ));
11492 let insertion_anchor = buffer.anchor_after(insertion_point);
11493 edits.push((insertion_anchor..insertion_anchor, text));
11494
11495 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11496
11497 // Move selections up
11498 new_selections.extend(contiguous_row_selections.drain(..).map(
11499 |mut selection| {
11500 selection.start.row -= row_delta;
11501 selection.end.row -= row_delta;
11502 selection
11503 },
11504 ));
11505
11506 // Move folds up
11507 unfold_ranges.push(range_to_move.clone());
11508 for fold in display_map.folds_in_range(
11509 buffer.anchor_before(range_to_move.start)
11510 ..buffer.anchor_after(range_to_move.end),
11511 ) {
11512 let mut start = fold.range.start.to_point(&buffer);
11513 let mut end = fold.range.end.to_point(&buffer);
11514 start.row -= row_delta;
11515 end.row -= row_delta;
11516 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11517 }
11518 }
11519 }
11520
11521 // If we didn't move line(s), preserve the existing selections
11522 new_selections.append(&mut contiguous_row_selections);
11523 }
11524
11525 self.transact(window, cx, |this, window, cx| {
11526 this.unfold_ranges(&unfold_ranges, true, true, cx);
11527 this.buffer.update(cx, |buffer, cx| {
11528 for (range, text) in edits {
11529 buffer.edit([(range, text)], None, cx);
11530 }
11531 });
11532 this.fold_creases(refold_creases, true, window, cx);
11533 this.change_selections(Default::default(), window, cx, |s| {
11534 s.select(new_selections);
11535 })
11536 });
11537 }
11538
11539 pub fn move_line_down(
11540 &mut self,
11541 _: &MoveLineDown,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 ) {
11545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11546 if self.mode.is_single_line() {
11547 cx.propagate();
11548 return;
11549 }
11550
11551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11552 let buffer = self.buffer.read(cx).snapshot(cx);
11553
11554 let mut edits = Vec::new();
11555 let mut unfold_ranges = Vec::new();
11556 let mut refold_creases = Vec::new();
11557
11558 let selections = self.selections.all::<Point>(cx);
11559 let mut selections = selections.iter().peekable();
11560 let mut contiguous_row_selections = Vec::new();
11561 let mut new_selections = Vec::new();
11562
11563 while let Some(selection) = selections.next() {
11564 // Find all the selections that span a contiguous row range
11565 let (start_row, end_row) = consume_contiguous_rows(
11566 &mut contiguous_row_selections,
11567 selection,
11568 &display_map,
11569 &mut selections,
11570 );
11571
11572 // Move the text spanned by the row range to be after the last line of the row range
11573 if end_row.0 <= buffer.max_point().row {
11574 let range_to_move =
11575 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11576 let insertion_point = display_map
11577 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11578 .0;
11579
11580 // Don't move lines across excerpt boundaries
11581 if buffer
11582 .excerpt_containing(range_to_move.start..insertion_point)
11583 .is_some()
11584 {
11585 let mut text = String::from("\n");
11586 text.extend(buffer.text_for_range(range_to_move.clone()));
11587 text.pop(); // Drop trailing newline
11588 edits.push((
11589 buffer.anchor_after(range_to_move.start)
11590 ..buffer.anchor_before(range_to_move.end),
11591 String::new(),
11592 ));
11593 let insertion_anchor = buffer.anchor_after(insertion_point);
11594 edits.push((insertion_anchor..insertion_anchor, text));
11595
11596 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11597
11598 // Move selections down
11599 new_selections.extend(contiguous_row_selections.drain(..).map(
11600 |mut selection| {
11601 selection.start.row += row_delta;
11602 selection.end.row += row_delta;
11603 selection
11604 },
11605 ));
11606
11607 // Move folds down
11608 unfold_ranges.push(range_to_move.clone());
11609 for fold in display_map.folds_in_range(
11610 buffer.anchor_before(range_to_move.start)
11611 ..buffer.anchor_after(range_to_move.end),
11612 ) {
11613 let mut start = fold.range.start.to_point(&buffer);
11614 let mut end = fold.range.end.to_point(&buffer);
11615 start.row += row_delta;
11616 end.row += row_delta;
11617 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11618 }
11619 }
11620 }
11621
11622 // If we didn't move line(s), preserve the existing selections
11623 new_selections.append(&mut contiguous_row_selections);
11624 }
11625
11626 self.transact(window, cx, |this, window, cx| {
11627 this.unfold_ranges(&unfold_ranges, true, true, cx);
11628 this.buffer.update(cx, |buffer, cx| {
11629 for (range, text) in edits {
11630 buffer.edit([(range, text)], None, cx);
11631 }
11632 });
11633 this.fold_creases(refold_creases, true, window, cx);
11634 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11635 });
11636 }
11637
11638 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11640 let text_layout_details = &self.text_layout_details(window);
11641 self.transact(window, cx, |this, window, cx| {
11642 let edits = this.change_selections(Default::default(), window, cx, |s| {
11643 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11644 s.move_with(|display_map, selection| {
11645 if !selection.is_empty() {
11646 return;
11647 }
11648
11649 let mut head = selection.head();
11650 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11651 if head.column() == display_map.line_len(head.row()) {
11652 transpose_offset = display_map
11653 .buffer_snapshot
11654 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11655 }
11656
11657 if transpose_offset == 0 {
11658 return;
11659 }
11660
11661 *head.column_mut() += 1;
11662 head = display_map.clip_point(head, Bias::Right);
11663 let goal = SelectionGoal::HorizontalPosition(
11664 display_map
11665 .x_for_display_point(head, text_layout_details)
11666 .into(),
11667 );
11668 selection.collapse_to(head, goal);
11669
11670 let transpose_start = display_map
11671 .buffer_snapshot
11672 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11673 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11674 let transpose_end = display_map
11675 .buffer_snapshot
11676 .clip_offset(transpose_offset + 1, Bias::Right);
11677 if let Some(ch) =
11678 display_map.buffer_snapshot.chars_at(transpose_start).next()
11679 {
11680 edits.push((transpose_start..transpose_offset, String::new()));
11681 edits.push((transpose_end..transpose_end, ch.to_string()));
11682 }
11683 }
11684 });
11685 edits
11686 });
11687 this.buffer
11688 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11689 let selections = this.selections.all::<usize>(cx);
11690 this.change_selections(Default::default(), window, cx, |s| {
11691 s.select(selections);
11692 });
11693 });
11694 }
11695
11696 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11697 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11698 if self.mode.is_single_line() {
11699 cx.propagate();
11700 return;
11701 }
11702
11703 self.rewrap_impl(RewrapOptions::default(), cx)
11704 }
11705
11706 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11707 let buffer = self.buffer.read(cx).snapshot(cx);
11708 let selections = self.selections.all::<Point>(cx);
11709
11710 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11711 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11712 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11713 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11714 .peekable();
11715
11716 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11717 row
11718 } else {
11719 return Vec::new();
11720 };
11721
11722 let language_settings = buffer.language_settings_at(selection.head(), cx);
11723 let language_scope = buffer.language_scope_at(selection.head());
11724
11725 let indent_and_prefix_for_row =
11726 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11727 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11728 let (comment_prefix, rewrap_prefix) =
11729 if let Some(language_scope) = &language_scope {
11730 let indent_end = Point::new(row, indent.len);
11731 let comment_prefix = language_scope
11732 .line_comment_prefixes()
11733 .iter()
11734 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11735 .map(|prefix| prefix.to_string());
11736 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11737 let line_text_after_indent = buffer
11738 .text_for_range(indent_end..line_end)
11739 .collect::<String>();
11740 let rewrap_prefix = language_scope
11741 .rewrap_prefixes()
11742 .iter()
11743 .find_map(|prefix_regex| {
11744 prefix_regex.find(&line_text_after_indent).map(|mat| {
11745 if mat.start() == 0 {
11746 Some(mat.as_str().to_string())
11747 } else {
11748 None
11749 }
11750 })
11751 })
11752 .flatten();
11753 (comment_prefix, rewrap_prefix)
11754 } else {
11755 (None, None)
11756 };
11757 (indent, comment_prefix, rewrap_prefix)
11758 };
11759
11760 let mut ranges = Vec::new();
11761 let from_empty_selection = selection.is_empty();
11762
11763 let mut current_range_start = first_row;
11764 let mut prev_row = first_row;
11765 let (
11766 mut current_range_indent,
11767 mut current_range_comment_prefix,
11768 mut current_range_rewrap_prefix,
11769 ) = indent_and_prefix_for_row(first_row);
11770
11771 for row in non_blank_rows_iter.skip(1) {
11772 let has_paragraph_break = row > prev_row + 1;
11773
11774 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11775 indent_and_prefix_for_row(row);
11776
11777 let has_indent_change = row_indent != current_range_indent;
11778 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11779
11780 let has_boundary_change = has_comment_change
11781 || row_rewrap_prefix.is_some()
11782 || (has_indent_change && current_range_comment_prefix.is_some());
11783
11784 if has_paragraph_break || has_boundary_change {
11785 ranges.push((
11786 language_settings.clone(),
11787 Point::new(current_range_start, 0)
11788 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11789 current_range_indent,
11790 current_range_comment_prefix.clone(),
11791 current_range_rewrap_prefix.clone(),
11792 from_empty_selection,
11793 ));
11794 current_range_start = row;
11795 current_range_indent = row_indent;
11796 current_range_comment_prefix = row_comment_prefix;
11797 current_range_rewrap_prefix = row_rewrap_prefix;
11798 }
11799 prev_row = row;
11800 }
11801
11802 ranges.push((
11803 language_settings.clone(),
11804 Point::new(current_range_start, 0)
11805 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11806 current_range_indent,
11807 current_range_comment_prefix,
11808 current_range_rewrap_prefix,
11809 from_empty_selection,
11810 ));
11811
11812 ranges
11813 });
11814
11815 let mut edits = Vec::new();
11816 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11817
11818 for (
11819 language_settings,
11820 wrap_range,
11821 indent_size,
11822 comment_prefix,
11823 rewrap_prefix,
11824 from_empty_selection,
11825 ) in wrap_ranges
11826 {
11827 let mut start_row = wrap_range.start.row;
11828 let mut end_row = wrap_range.end.row;
11829
11830 // Skip selections that overlap with a range that has already been rewrapped.
11831 let selection_range = start_row..end_row;
11832 if rewrapped_row_ranges
11833 .iter()
11834 .any(|range| range.overlaps(&selection_range))
11835 {
11836 continue;
11837 }
11838
11839 let tab_size = language_settings.tab_size;
11840
11841 let indent_prefix = indent_size.chars().collect::<String>();
11842 let mut line_prefix = indent_prefix.clone();
11843 let mut inside_comment = false;
11844 if let Some(prefix) = &comment_prefix {
11845 line_prefix.push_str(prefix);
11846 inside_comment = true;
11847 }
11848 if let Some(prefix) = &rewrap_prefix {
11849 line_prefix.push_str(prefix);
11850 }
11851
11852 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11853 RewrapBehavior::InComments => inside_comment,
11854 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11855 RewrapBehavior::Anywhere => true,
11856 };
11857
11858 let should_rewrap = options.override_language_settings
11859 || allow_rewrap_based_on_language
11860 || self.hard_wrap.is_some();
11861 if !should_rewrap {
11862 continue;
11863 }
11864
11865 if from_empty_selection {
11866 'expand_upwards: while start_row > 0 {
11867 let prev_row = start_row - 1;
11868 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11869 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11870 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11871 {
11872 start_row = prev_row;
11873 } else {
11874 break 'expand_upwards;
11875 }
11876 }
11877
11878 'expand_downwards: while end_row < buffer.max_point().row {
11879 let next_row = end_row + 1;
11880 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11881 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11882 && !buffer.is_line_blank(MultiBufferRow(next_row))
11883 {
11884 end_row = next_row;
11885 } else {
11886 break 'expand_downwards;
11887 }
11888 }
11889 }
11890
11891 let start = Point::new(start_row, 0);
11892 let start_offset = start.to_offset(&buffer);
11893 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11894 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11895 let Some(lines_without_prefixes) = selection_text
11896 .lines()
11897 .enumerate()
11898 .map(|(ix, line)| {
11899 let line_trimmed = line.trim_start();
11900 if rewrap_prefix.is_some() && ix > 0 {
11901 Ok(line_trimmed)
11902 } else {
11903 line_trimmed
11904 .strip_prefix(&line_prefix.trim_start())
11905 .with_context(|| {
11906 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11907 })
11908 }
11909 })
11910 .collect::<Result<Vec<_>, _>>()
11911 .log_err()
11912 else {
11913 continue;
11914 };
11915
11916 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11917 buffer
11918 .language_settings_at(Point::new(start_row, 0), cx)
11919 .preferred_line_length as usize
11920 });
11921
11922 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11923 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11924 } else {
11925 line_prefix.clone()
11926 };
11927
11928 let wrapped_text = wrap_with_prefix(
11929 line_prefix,
11930 subsequent_lines_prefix,
11931 lines_without_prefixes.join("\n"),
11932 wrap_column,
11933 tab_size,
11934 options.preserve_existing_whitespace,
11935 );
11936
11937 // TODO: should always use char-based diff while still supporting cursor behavior that
11938 // matches vim.
11939 let mut diff_options = DiffOptions::default();
11940 if options.override_language_settings {
11941 diff_options.max_word_diff_len = 0;
11942 diff_options.max_word_diff_line_count = 0;
11943 } else {
11944 diff_options.max_word_diff_len = usize::MAX;
11945 diff_options.max_word_diff_line_count = usize::MAX;
11946 }
11947
11948 for (old_range, new_text) in
11949 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11950 {
11951 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11952 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11953 edits.push((edit_start..edit_end, new_text));
11954 }
11955
11956 rewrapped_row_ranges.push(start_row..=end_row);
11957 }
11958
11959 self.buffer
11960 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11961 }
11962
11963 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11964 let mut text = String::new();
11965 let buffer = self.buffer.read(cx).snapshot(cx);
11966 let mut selections = self.selections.all::<Point>(cx);
11967 let mut clipboard_selections = Vec::with_capacity(selections.len());
11968 {
11969 let max_point = buffer.max_point();
11970 let mut is_first = true;
11971 for selection in &mut selections {
11972 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11973 if is_entire_line {
11974 selection.start = Point::new(selection.start.row, 0);
11975 if !selection.is_empty() && selection.end.column == 0 {
11976 selection.end = cmp::min(max_point, selection.end);
11977 } else {
11978 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11979 }
11980 selection.goal = SelectionGoal::None;
11981 }
11982 if is_first {
11983 is_first = false;
11984 } else {
11985 text += "\n";
11986 }
11987 let mut len = 0;
11988 for chunk in buffer.text_for_range(selection.start..selection.end) {
11989 text.push_str(chunk);
11990 len += chunk.len();
11991 }
11992 clipboard_selections.push(ClipboardSelection {
11993 len,
11994 is_entire_line,
11995 first_line_indent: buffer
11996 .indent_size_for_line(MultiBufferRow(selection.start.row))
11997 .len,
11998 });
11999 }
12000 }
12001
12002 self.transact(window, cx, |this, window, cx| {
12003 this.change_selections(Default::default(), window, cx, |s| {
12004 s.select(selections);
12005 });
12006 this.insert("", window, cx);
12007 });
12008 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12009 }
12010
12011 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12013 let item = self.cut_common(window, cx);
12014 cx.write_to_clipboard(item);
12015 }
12016
12017 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12019 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12020 s.move_with(|snapshot, sel| {
12021 if sel.is_empty() {
12022 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12023 }
12024 });
12025 });
12026 let item = self.cut_common(window, cx);
12027 cx.set_global(KillRing(item))
12028 }
12029
12030 pub fn kill_ring_yank(
12031 &mut self,
12032 _: &KillRingYank,
12033 window: &mut Window,
12034 cx: &mut Context<Self>,
12035 ) {
12036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12037 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12038 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12039 (kill_ring.text().to_string(), kill_ring.metadata_json())
12040 } else {
12041 return;
12042 }
12043 } else {
12044 return;
12045 };
12046 self.do_paste(&text, metadata, false, window, cx);
12047 }
12048
12049 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12050 self.do_copy(true, cx);
12051 }
12052
12053 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12054 self.do_copy(false, cx);
12055 }
12056
12057 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12058 let selections = self.selections.all::<Point>(cx);
12059 let buffer = self.buffer.read(cx).read(cx);
12060 let mut text = String::new();
12061
12062 let mut clipboard_selections = Vec::with_capacity(selections.len());
12063 {
12064 let max_point = buffer.max_point();
12065 let mut is_first = true;
12066 for selection in &selections {
12067 let mut start = selection.start;
12068 let mut end = selection.end;
12069 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12070 if is_entire_line {
12071 start = Point::new(start.row, 0);
12072 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12073 }
12074
12075 let mut trimmed_selections = Vec::new();
12076 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12077 let row = MultiBufferRow(start.row);
12078 let first_indent = buffer.indent_size_for_line(row);
12079 if first_indent.len == 0 || start.column > first_indent.len {
12080 trimmed_selections.push(start..end);
12081 } else {
12082 trimmed_selections.push(
12083 Point::new(row.0, first_indent.len)
12084 ..Point::new(row.0, buffer.line_len(row)),
12085 );
12086 for row in start.row + 1..=end.row {
12087 let mut line_len = buffer.line_len(MultiBufferRow(row));
12088 if row == end.row {
12089 line_len = end.column;
12090 }
12091 if line_len == 0 {
12092 trimmed_selections
12093 .push(Point::new(row, 0)..Point::new(row, line_len));
12094 continue;
12095 }
12096 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12097 if row_indent_size.len >= first_indent.len {
12098 trimmed_selections.push(
12099 Point::new(row, first_indent.len)..Point::new(row, line_len),
12100 );
12101 } else {
12102 trimmed_selections.clear();
12103 trimmed_selections.push(start..end);
12104 break;
12105 }
12106 }
12107 }
12108 } else {
12109 trimmed_selections.push(start..end);
12110 }
12111
12112 for trimmed_range in trimmed_selections {
12113 if is_first {
12114 is_first = false;
12115 } else {
12116 text += "\n";
12117 }
12118 let mut len = 0;
12119 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12120 text.push_str(chunk);
12121 len += chunk.len();
12122 }
12123 clipboard_selections.push(ClipboardSelection {
12124 len,
12125 is_entire_line,
12126 first_line_indent: buffer
12127 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12128 .len,
12129 });
12130 }
12131 }
12132 }
12133
12134 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12135 text,
12136 clipboard_selections,
12137 ));
12138 }
12139
12140 pub fn do_paste(
12141 &mut self,
12142 text: &String,
12143 clipboard_selections: Option<Vec<ClipboardSelection>>,
12144 handle_entire_lines: bool,
12145 window: &mut Window,
12146 cx: &mut Context<Self>,
12147 ) {
12148 if self.read_only(cx) {
12149 return;
12150 }
12151
12152 let clipboard_text = Cow::Borrowed(text);
12153
12154 self.transact(window, cx, |this, window, cx| {
12155 let had_active_edit_prediction = this.has_active_edit_prediction();
12156
12157 if let Some(mut clipboard_selections) = clipboard_selections {
12158 let old_selections = this.selections.all::<usize>(cx);
12159 let all_selections_were_entire_line =
12160 clipboard_selections.iter().all(|s| s.is_entire_line);
12161 let first_selection_indent_column =
12162 clipboard_selections.first().map(|s| s.first_line_indent);
12163 if clipboard_selections.len() != old_selections.len() {
12164 clipboard_selections.drain(..);
12165 }
12166 let cursor_offset = this.selections.last::<usize>(cx).head();
12167 let mut auto_indent_on_paste = true;
12168
12169 this.buffer.update(cx, |buffer, cx| {
12170 let snapshot = buffer.read(cx);
12171 auto_indent_on_paste = snapshot
12172 .language_settings_at(cursor_offset, cx)
12173 .auto_indent_on_paste;
12174
12175 let mut start_offset = 0;
12176 let mut edits = Vec::new();
12177 let mut original_indent_columns = Vec::new();
12178 for (ix, selection) in old_selections.iter().enumerate() {
12179 let to_insert;
12180 let entire_line;
12181 let original_indent_column;
12182 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12183 let end_offset = start_offset + clipboard_selection.len;
12184 to_insert = &clipboard_text[start_offset..end_offset];
12185 entire_line = clipboard_selection.is_entire_line;
12186 start_offset = end_offset + 1;
12187 original_indent_column = Some(clipboard_selection.first_line_indent);
12188 } else {
12189 to_insert = clipboard_text.as_str();
12190 entire_line = all_selections_were_entire_line;
12191 original_indent_column = first_selection_indent_column
12192 }
12193
12194 // If the corresponding selection was empty when this slice of the
12195 // clipboard text was written, then the entire line containing the
12196 // selection was copied. If this selection is also currently empty,
12197 // then paste the line before the current line of the buffer.
12198 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12199 let column = selection.start.to_point(&snapshot).column as usize;
12200 let line_start = selection.start - column;
12201 line_start..line_start
12202 } else {
12203 selection.range()
12204 };
12205
12206 edits.push((range, to_insert));
12207 original_indent_columns.push(original_indent_column);
12208 }
12209 drop(snapshot);
12210
12211 buffer.edit(
12212 edits,
12213 if auto_indent_on_paste {
12214 Some(AutoindentMode::Block {
12215 original_indent_columns,
12216 })
12217 } else {
12218 None
12219 },
12220 cx,
12221 );
12222 });
12223
12224 let selections = this.selections.all::<usize>(cx);
12225 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12226 } else {
12227 this.insert(&clipboard_text, window, cx);
12228 }
12229
12230 let trigger_in_words =
12231 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12232
12233 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12234 });
12235 }
12236
12237 pub fn diff_clipboard_with_selection(
12238 &mut self,
12239 _: &DiffClipboardWithSelection,
12240 window: &mut Window,
12241 cx: &mut Context<Self>,
12242 ) {
12243 let selections = self.selections.all::<usize>(cx);
12244
12245 if selections.is_empty() {
12246 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12247 return;
12248 };
12249
12250 let clipboard_text = match cx.read_from_clipboard() {
12251 Some(item) => match item.entries().first() {
12252 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12253 _ => None,
12254 },
12255 None => None,
12256 };
12257
12258 let Some(clipboard_text) = clipboard_text else {
12259 log::warn!("Clipboard doesn't contain text.");
12260 return;
12261 };
12262
12263 window.dispatch_action(
12264 Box::new(DiffClipboardWithSelectionData {
12265 clipboard_text,
12266 editor: cx.entity(),
12267 }),
12268 cx,
12269 );
12270 }
12271
12272 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12273 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12274 if let Some(item) = cx.read_from_clipboard() {
12275 let entries = item.entries();
12276
12277 match entries.first() {
12278 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12279 // of all the pasted entries.
12280 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12281 .do_paste(
12282 clipboard_string.text(),
12283 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12284 true,
12285 window,
12286 cx,
12287 ),
12288 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12289 }
12290 }
12291 }
12292
12293 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12294 if self.read_only(cx) {
12295 return;
12296 }
12297
12298 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12299
12300 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12301 if let Some((selections, _)) =
12302 self.selection_history.transaction(transaction_id).cloned()
12303 {
12304 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12305 s.select_anchors(selections.to_vec());
12306 });
12307 } else {
12308 log::error!(
12309 "No entry in selection_history found for undo. \
12310 This may correspond to a bug where undo does not update the selection. \
12311 If this is occurring, please add details to \
12312 https://github.com/zed-industries/zed/issues/22692"
12313 );
12314 }
12315 self.request_autoscroll(Autoscroll::fit(), cx);
12316 self.unmark_text(window, cx);
12317 self.refresh_edit_prediction(true, false, window, cx);
12318 cx.emit(EditorEvent::Edited { transaction_id });
12319 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12320 }
12321 }
12322
12323 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12324 if self.read_only(cx) {
12325 return;
12326 }
12327
12328 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12329
12330 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12331 if let Some((_, Some(selections))) =
12332 self.selection_history.transaction(transaction_id).cloned()
12333 {
12334 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12335 s.select_anchors(selections.to_vec());
12336 });
12337 } else {
12338 log::error!(
12339 "No entry in selection_history found for redo. \
12340 This may correspond to a bug where undo does not update the selection. \
12341 If this is occurring, please add details to \
12342 https://github.com/zed-industries/zed/issues/22692"
12343 );
12344 }
12345 self.request_autoscroll(Autoscroll::fit(), cx);
12346 self.unmark_text(window, cx);
12347 self.refresh_edit_prediction(true, false, window, cx);
12348 cx.emit(EditorEvent::Edited { transaction_id });
12349 }
12350 }
12351
12352 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12353 self.buffer
12354 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12355 }
12356
12357 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12358 self.buffer
12359 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12360 }
12361
12362 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12364 self.change_selections(Default::default(), window, cx, |s| {
12365 s.move_with(|map, selection| {
12366 let cursor = if selection.is_empty() {
12367 movement::left(map, selection.start)
12368 } else {
12369 selection.start
12370 };
12371 selection.collapse_to(cursor, SelectionGoal::None);
12372 });
12373 })
12374 }
12375
12376 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12378 self.change_selections(Default::default(), window, cx, |s| {
12379 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12380 })
12381 }
12382
12383 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12385 self.change_selections(Default::default(), window, cx, |s| {
12386 s.move_with(|map, selection| {
12387 let cursor = if selection.is_empty() {
12388 movement::right(map, selection.end)
12389 } else {
12390 selection.end
12391 };
12392 selection.collapse_to(cursor, SelectionGoal::None)
12393 });
12394 })
12395 }
12396
12397 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12399 self.change_selections(Default::default(), window, cx, |s| {
12400 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12401 })
12402 }
12403
12404 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12405 if self.take_rename(true, window, cx).is_some() {
12406 return;
12407 }
12408
12409 if self.mode.is_single_line() {
12410 cx.propagate();
12411 return;
12412 }
12413
12414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12415
12416 let text_layout_details = &self.text_layout_details(window);
12417 let selection_count = self.selections.count();
12418 let first_selection = self.selections.first_anchor();
12419
12420 self.change_selections(Default::default(), window, cx, |s| {
12421 s.move_with(|map, selection| {
12422 if !selection.is_empty() {
12423 selection.goal = SelectionGoal::None;
12424 }
12425 let (cursor, goal) = movement::up(
12426 map,
12427 selection.start,
12428 selection.goal,
12429 false,
12430 text_layout_details,
12431 );
12432 selection.collapse_to(cursor, goal);
12433 });
12434 });
12435
12436 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12437 {
12438 cx.propagate();
12439 }
12440 }
12441
12442 pub fn move_up_by_lines(
12443 &mut self,
12444 action: &MoveUpByLines,
12445 window: &mut Window,
12446 cx: &mut Context<Self>,
12447 ) {
12448 if self.take_rename(true, window, cx).is_some() {
12449 return;
12450 }
12451
12452 if self.mode.is_single_line() {
12453 cx.propagate();
12454 return;
12455 }
12456
12457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12458
12459 let text_layout_details = &self.text_layout_details(window);
12460
12461 self.change_selections(Default::default(), window, cx, |s| {
12462 s.move_with(|map, selection| {
12463 if !selection.is_empty() {
12464 selection.goal = SelectionGoal::None;
12465 }
12466 let (cursor, goal) = movement::up_by_rows(
12467 map,
12468 selection.start,
12469 action.lines,
12470 selection.goal,
12471 false,
12472 text_layout_details,
12473 );
12474 selection.collapse_to(cursor, goal);
12475 });
12476 })
12477 }
12478
12479 pub fn move_down_by_lines(
12480 &mut self,
12481 action: &MoveDownByLines,
12482 window: &mut Window,
12483 cx: &mut Context<Self>,
12484 ) {
12485 if self.take_rename(true, window, cx).is_some() {
12486 return;
12487 }
12488
12489 if self.mode.is_single_line() {
12490 cx.propagate();
12491 return;
12492 }
12493
12494 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12495
12496 let text_layout_details = &self.text_layout_details(window);
12497
12498 self.change_selections(Default::default(), window, cx, |s| {
12499 s.move_with(|map, selection| {
12500 if !selection.is_empty() {
12501 selection.goal = SelectionGoal::None;
12502 }
12503 let (cursor, goal) = movement::down_by_rows(
12504 map,
12505 selection.start,
12506 action.lines,
12507 selection.goal,
12508 false,
12509 text_layout_details,
12510 );
12511 selection.collapse_to(cursor, goal);
12512 });
12513 })
12514 }
12515
12516 pub fn select_down_by_lines(
12517 &mut self,
12518 action: &SelectDownByLines,
12519 window: &mut Window,
12520 cx: &mut Context<Self>,
12521 ) {
12522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12523 let text_layout_details = &self.text_layout_details(window);
12524 self.change_selections(Default::default(), window, cx, |s| {
12525 s.move_heads_with(|map, head, goal| {
12526 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12527 })
12528 })
12529 }
12530
12531 pub fn select_up_by_lines(
12532 &mut self,
12533 action: &SelectUpByLines,
12534 window: &mut Window,
12535 cx: &mut Context<Self>,
12536 ) {
12537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12538 let text_layout_details = &self.text_layout_details(window);
12539 self.change_selections(Default::default(), window, cx, |s| {
12540 s.move_heads_with(|map, head, goal| {
12541 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12542 })
12543 })
12544 }
12545
12546 pub fn select_page_up(
12547 &mut self,
12548 _: &SelectPageUp,
12549 window: &mut Window,
12550 cx: &mut Context<Self>,
12551 ) {
12552 let Some(row_count) = self.visible_row_count() else {
12553 return;
12554 };
12555
12556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12557
12558 let text_layout_details = &self.text_layout_details(window);
12559
12560 self.change_selections(Default::default(), window, cx, |s| {
12561 s.move_heads_with(|map, head, goal| {
12562 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12563 })
12564 })
12565 }
12566
12567 pub fn move_page_up(
12568 &mut self,
12569 action: &MovePageUp,
12570 window: &mut Window,
12571 cx: &mut Context<Self>,
12572 ) {
12573 if self.take_rename(true, window, cx).is_some() {
12574 return;
12575 }
12576
12577 if self
12578 .context_menu
12579 .borrow_mut()
12580 .as_mut()
12581 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12582 .unwrap_or(false)
12583 {
12584 return;
12585 }
12586
12587 if matches!(self.mode, EditorMode::SingleLine) {
12588 cx.propagate();
12589 return;
12590 }
12591
12592 let Some(row_count) = self.visible_row_count() else {
12593 return;
12594 };
12595
12596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12597
12598 let effects = if action.center_cursor {
12599 SelectionEffects::scroll(Autoscroll::center())
12600 } else {
12601 SelectionEffects::default()
12602 };
12603
12604 let text_layout_details = &self.text_layout_details(window);
12605
12606 self.change_selections(effects, window, cx, |s| {
12607 s.move_with(|map, selection| {
12608 if !selection.is_empty() {
12609 selection.goal = SelectionGoal::None;
12610 }
12611 let (cursor, goal) = movement::up_by_rows(
12612 map,
12613 selection.end,
12614 row_count,
12615 selection.goal,
12616 false,
12617 text_layout_details,
12618 );
12619 selection.collapse_to(cursor, goal);
12620 });
12621 });
12622 }
12623
12624 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12625 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12626 let text_layout_details = &self.text_layout_details(window);
12627 self.change_selections(Default::default(), window, cx, |s| {
12628 s.move_heads_with(|map, head, goal| {
12629 movement::up(map, head, goal, false, text_layout_details)
12630 })
12631 })
12632 }
12633
12634 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12635 self.take_rename(true, window, cx);
12636
12637 if self.mode.is_single_line() {
12638 cx.propagate();
12639 return;
12640 }
12641
12642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12643
12644 let text_layout_details = &self.text_layout_details(window);
12645 let selection_count = self.selections.count();
12646 let first_selection = self.selections.first_anchor();
12647
12648 self.change_selections(Default::default(), window, cx, |s| {
12649 s.move_with(|map, selection| {
12650 if !selection.is_empty() {
12651 selection.goal = SelectionGoal::None;
12652 }
12653 let (cursor, goal) = movement::down(
12654 map,
12655 selection.end,
12656 selection.goal,
12657 false,
12658 text_layout_details,
12659 );
12660 selection.collapse_to(cursor, goal);
12661 });
12662 });
12663
12664 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12665 {
12666 cx.propagate();
12667 }
12668 }
12669
12670 pub fn select_page_down(
12671 &mut self,
12672 _: &SelectPageDown,
12673 window: &mut Window,
12674 cx: &mut Context<Self>,
12675 ) {
12676 let Some(row_count) = self.visible_row_count() else {
12677 return;
12678 };
12679
12680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12681
12682 let text_layout_details = &self.text_layout_details(window);
12683
12684 self.change_selections(Default::default(), window, cx, |s| {
12685 s.move_heads_with(|map, head, goal| {
12686 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12687 })
12688 })
12689 }
12690
12691 pub fn move_page_down(
12692 &mut self,
12693 action: &MovePageDown,
12694 window: &mut Window,
12695 cx: &mut Context<Self>,
12696 ) {
12697 if self.take_rename(true, window, cx).is_some() {
12698 return;
12699 }
12700
12701 if self
12702 .context_menu
12703 .borrow_mut()
12704 .as_mut()
12705 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12706 .unwrap_or(false)
12707 {
12708 return;
12709 }
12710
12711 if matches!(self.mode, EditorMode::SingleLine) {
12712 cx.propagate();
12713 return;
12714 }
12715
12716 let Some(row_count) = self.visible_row_count() else {
12717 return;
12718 };
12719
12720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12721
12722 let effects = if action.center_cursor {
12723 SelectionEffects::scroll(Autoscroll::center())
12724 } else {
12725 SelectionEffects::default()
12726 };
12727
12728 let text_layout_details = &self.text_layout_details(window);
12729 self.change_selections(effects, window, cx, |s| {
12730 s.move_with(|map, selection| {
12731 if !selection.is_empty() {
12732 selection.goal = SelectionGoal::None;
12733 }
12734 let (cursor, goal) = movement::down_by_rows(
12735 map,
12736 selection.end,
12737 row_count,
12738 selection.goal,
12739 false,
12740 text_layout_details,
12741 );
12742 selection.collapse_to(cursor, goal);
12743 });
12744 });
12745 }
12746
12747 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12748 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12749 let text_layout_details = &self.text_layout_details(window);
12750 self.change_selections(Default::default(), window, cx, |s| {
12751 s.move_heads_with(|map, head, goal| {
12752 movement::down(map, head, goal, false, text_layout_details)
12753 })
12754 });
12755 }
12756
12757 pub fn context_menu_first(
12758 &mut self,
12759 _: &ContextMenuFirst,
12760 window: &mut Window,
12761 cx: &mut Context<Self>,
12762 ) {
12763 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12764 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12765 }
12766 }
12767
12768 pub fn context_menu_prev(
12769 &mut self,
12770 _: &ContextMenuPrevious,
12771 window: &mut Window,
12772 cx: &mut Context<Self>,
12773 ) {
12774 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12775 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12776 }
12777 }
12778
12779 pub fn context_menu_next(
12780 &mut self,
12781 _: &ContextMenuNext,
12782 window: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12786 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12787 }
12788 }
12789
12790 pub fn context_menu_last(
12791 &mut self,
12792 _: &ContextMenuLast,
12793 window: &mut Window,
12794 cx: &mut Context<Self>,
12795 ) {
12796 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12797 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12798 }
12799 }
12800
12801 pub fn signature_help_prev(
12802 &mut self,
12803 _: &SignatureHelpPrevious,
12804 _: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 if let Some(popover) = self.signature_help_state.popover_mut() {
12808 if popover.current_signature == 0 {
12809 popover.current_signature = popover.signatures.len() - 1;
12810 } else {
12811 popover.current_signature -= 1;
12812 }
12813 cx.notify();
12814 }
12815 }
12816
12817 pub fn signature_help_next(
12818 &mut self,
12819 _: &SignatureHelpNext,
12820 _: &mut Window,
12821 cx: &mut Context<Self>,
12822 ) {
12823 if let Some(popover) = self.signature_help_state.popover_mut() {
12824 if popover.current_signature + 1 == popover.signatures.len() {
12825 popover.current_signature = 0;
12826 } else {
12827 popover.current_signature += 1;
12828 }
12829 cx.notify();
12830 }
12831 }
12832
12833 pub fn move_to_previous_word_start(
12834 &mut self,
12835 _: &MoveToPreviousWordStart,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) {
12839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12840 self.change_selections(Default::default(), window, cx, |s| {
12841 s.move_cursors_with(|map, head, _| {
12842 (
12843 movement::previous_word_start(map, head),
12844 SelectionGoal::None,
12845 )
12846 });
12847 })
12848 }
12849
12850 pub fn move_to_previous_subword_start(
12851 &mut self,
12852 _: &MoveToPreviousSubwordStart,
12853 window: &mut Window,
12854 cx: &mut Context<Self>,
12855 ) {
12856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12857 self.change_selections(Default::default(), window, cx, |s| {
12858 s.move_cursors_with(|map, head, _| {
12859 (
12860 movement::previous_subword_start(map, head),
12861 SelectionGoal::None,
12862 )
12863 });
12864 })
12865 }
12866
12867 pub fn select_to_previous_word_start(
12868 &mut self,
12869 _: &SelectToPreviousWordStart,
12870 window: &mut Window,
12871 cx: &mut Context<Self>,
12872 ) {
12873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12874 self.change_selections(Default::default(), window, cx, |s| {
12875 s.move_heads_with(|map, head, _| {
12876 (
12877 movement::previous_word_start(map, head),
12878 SelectionGoal::None,
12879 )
12880 });
12881 })
12882 }
12883
12884 pub fn select_to_previous_subword_start(
12885 &mut self,
12886 _: &SelectToPreviousSubwordStart,
12887 window: &mut Window,
12888 cx: &mut Context<Self>,
12889 ) {
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12891 self.change_selections(Default::default(), window, cx, |s| {
12892 s.move_heads_with(|map, head, _| {
12893 (
12894 movement::previous_subword_start(map, head),
12895 SelectionGoal::None,
12896 )
12897 });
12898 })
12899 }
12900
12901 pub fn delete_to_previous_word_start(
12902 &mut self,
12903 action: &DeleteToPreviousWordStart,
12904 window: &mut Window,
12905 cx: &mut Context<Self>,
12906 ) {
12907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12908 self.transact(window, cx, |this, window, cx| {
12909 this.select_autoclose_pair(window, cx);
12910 this.change_selections(Default::default(), window, cx, |s| {
12911 s.move_with(|map, selection| {
12912 if selection.is_empty() {
12913 let cursor = if action.ignore_newlines {
12914 movement::previous_word_start(map, selection.head())
12915 } else {
12916 movement::previous_word_start_or_newline(map, selection.head())
12917 };
12918 selection.set_head(cursor, SelectionGoal::None);
12919 }
12920 });
12921 });
12922 this.insert("", window, cx);
12923 });
12924 }
12925
12926 pub fn delete_to_previous_subword_start(
12927 &mut self,
12928 _: &DeleteToPreviousSubwordStart,
12929 window: &mut Window,
12930 cx: &mut Context<Self>,
12931 ) {
12932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12933 self.transact(window, cx, |this, window, cx| {
12934 this.select_autoclose_pair(window, cx);
12935 this.change_selections(Default::default(), window, cx, |s| {
12936 s.move_with(|map, selection| {
12937 if selection.is_empty() {
12938 let cursor = movement::previous_subword_start(map, selection.head());
12939 selection.set_head(cursor, SelectionGoal::None);
12940 }
12941 });
12942 });
12943 this.insert("", window, cx);
12944 });
12945 }
12946
12947 pub fn move_to_next_word_end(
12948 &mut self,
12949 _: &MoveToNextWordEnd,
12950 window: &mut Window,
12951 cx: &mut Context<Self>,
12952 ) {
12953 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12954 self.change_selections(Default::default(), window, cx, |s| {
12955 s.move_cursors_with(|map, head, _| {
12956 (movement::next_word_end(map, head), SelectionGoal::None)
12957 });
12958 })
12959 }
12960
12961 pub fn move_to_next_subword_end(
12962 &mut self,
12963 _: &MoveToNextSubwordEnd,
12964 window: &mut Window,
12965 cx: &mut Context<Self>,
12966 ) {
12967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12968 self.change_selections(Default::default(), window, cx, |s| {
12969 s.move_cursors_with(|map, head, _| {
12970 (movement::next_subword_end(map, head), SelectionGoal::None)
12971 });
12972 })
12973 }
12974
12975 pub fn select_to_next_word_end(
12976 &mut self,
12977 _: &SelectToNextWordEnd,
12978 window: &mut Window,
12979 cx: &mut Context<Self>,
12980 ) {
12981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12982 self.change_selections(Default::default(), window, cx, |s| {
12983 s.move_heads_with(|map, head, _| {
12984 (movement::next_word_end(map, head), SelectionGoal::None)
12985 });
12986 })
12987 }
12988
12989 pub fn select_to_next_subword_end(
12990 &mut self,
12991 _: &SelectToNextSubwordEnd,
12992 window: &mut Window,
12993 cx: &mut Context<Self>,
12994 ) {
12995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12996 self.change_selections(Default::default(), window, cx, |s| {
12997 s.move_heads_with(|map, head, _| {
12998 (movement::next_subword_end(map, head), SelectionGoal::None)
12999 });
13000 })
13001 }
13002
13003 pub fn delete_to_next_word_end(
13004 &mut self,
13005 action: &DeleteToNextWordEnd,
13006 window: &mut Window,
13007 cx: &mut Context<Self>,
13008 ) {
13009 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13010 self.transact(window, cx, |this, window, cx| {
13011 this.change_selections(Default::default(), window, cx, |s| {
13012 s.move_with(|map, selection| {
13013 if selection.is_empty() {
13014 let cursor = if action.ignore_newlines {
13015 movement::next_word_end(map, selection.head())
13016 } else {
13017 movement::next_word_end_or_newline(map, selection.head())
13018 };
13019 selection.set_head(cursor, SelectionGoal::None);
13020 }
13021 });
13022 });
13023 this.insert("", window, cx);
13024 });
13025 }
13026
13027 pub fn delete_to_next_subword_end(
13028 &mut self,
13029 _: &DeleteToNextSubwordEnd,
13030 window: &mut Window,
13031 cx: &mut Context<Self>,
13032 ) {
13033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13034 self.transact(window, cx, |this, window, cx| {
13035 this.change_selections(Default::default(), window, cx, |s| {
13036 s.move_with(|map, selection| {
13037 if selection.is_empty() {
13038 let cursor = movement::next_subword_end(map, selection.head());
13039 selection.set_head(cursor, SelectionGoal::None);
13040 }
13041 });
13042 });
13043 this.insert("", window, cx);
13044 });
13045 }
13046
13047 pub fn move_to_beginning_of_line(
13048 &mut self,
13049 action: &MoveToBeginningOfLine,
13050 window: &mut Window,
13051 cx: &mut Context<Self>,
13052 ) {
13053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13054 self.change_selections(Default::default(), window, cx, |s| {
13055 s.move_cursors_with(|map, head, _| {
13056 (
13057 movement::indented_line_beginning(
13058 map,
13059 head,
13060 action.stop_at_soft_wraps,
13061 action.stop_at_indent,
13062 ),
13063 SelectionGoal::None,
13064 )
13065 });
13066 })
13067 }
13068
13069 pub fn select_to_beginning_of_line(
13070 &mut self,
13071 action: &SelectToBeginningOfLine,
13072 window: &mut Window,
13073 cx: &mut Context<Self>,
13074 ) {
13075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13076 self.change_selections(Default::default(), window, cx, |s| {
13077 s.move_heads_with(|map, head, _| {
13078 (
13079 movement::indented_line_beginning(
13080 map,
13081 head,
13082 action.stop_at_soft_wraps,
13083 action.stop_at_indent,
13084 ),
13085 SelectionGoal::None,
13086 )
13087 });
13088 });
13089 }
13090
13091 pub fn delete_to_beginning_of_line(
13092 &mut self,
13093 action: &DeleteToBeginningOfLine,
13094 window: &mut Window,
13095 cx: &mut Context<Self>,
13096 ) {
13097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13098 self.transact(window, cx, |this, window, cx| {
13099 this.change_selections(Default::default(), window, cx, |s| {
13100 s.move_with(|_, selection| {
13101 selection.reversed = true;
13102 });
13103 });
13104
13105 this.select_to_beginning_of_line(
13106 &SelectToBeginningOfLine {
13107 stop_at_soft_wraps: false,
13108 stop_at_indent: action.stop_at_indent,
13109 },
13110 window,
13111 cx,
13112 );
13113 this.backspace(&Backspace, window, cx);
13114 });
13115 }
13116
13117 pub fn move_to_end_of_line(
13118 &mut self,
13119 action: &MoveToEndOfLine,
13120 window: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) {
13123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13124 self.change_selections(Default::default(), window, cx, |s| {
13125 s.move_cursors_with(|map, head, _| {
13126 (
13127 movement::line_end(map, head, action.stop_at_soft_wraps),
13128 SelectionGoal::None,
13129 )
13130 });
13131 })
13132 }
13133
13134 pub fn select_to_end_of_line(
13135 &mut self,
13136 action: &SelectToEndOfLine,
13137 window: &mut Window,
13138 cx: &mut Context<Self>,
13139 ) {
13140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13141 self.change_selections(Default::default(), window, cx, |s| {
13142 s.move_heads_with(|map, head, _| {
13143 (
13144 movement::line_end(map, head, action.stop_at_soft_wraps),
13145 SelectionGoal::None,
13146 )
13147 });
13148 })
13149 }
13150
13151 pub fn delete_to_end_of_line(
13152 &mut self,
13153 _: &DeleteToEndOfLine,
13154 window: &mut Window,
13155 cx: &mut Context<Self>,
13156 ) {
13157 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13158 self.transact(window, cx, |this, window, cx| {
13159 this.select_to_end_of_line(
13160 &SelectToEndOfLine {
13161 stop_at_soft_wraps: false,
13162 },
13163 window,
13164 cx,
13165 );
13166 this.delete(&Delete, window, cx);
13167 });
13168 }
13169
13170 pub fn cut_to_end_of_line(
13171 &mut self,
13172 _: &CutToEndOfLine,
13173 window: &mut Window,
13174 cx: &mut Context<Self>,
13175 ) {
13176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13177 self.transact(window, cx, |this, window, cx| {
13178 this.select_to_end_of_line(
13179 &SelectToEndOfLine {
13180 stop_at_soft_wraps: false,
13181 },
13182 window,
13183 cx,
13184 );
13185 this.cut(&Cut, window, cx);
13186 });
13187 }
13188
13189 pub fn move_to_start_of_paragraph(
13190 &mut self,
13191 _: &MoveToStartOfParagraph,
13192 window: &mut Window,
13193 cx: &mut Context<Self>,
13194 ) {
13195 if matches!(self.mode, EditorMode::SingleLine) {
13196 cx.propagate();
13197 return;
13198 }
13199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13200 self.change_selections(Default::default(), window, cx, |s| {
13201 s.move_with(|map, selection| {
13202 selection.collapse_to(
13203 movement::start_of_paragraph(map, selection.head(), 1),
13204 SelectionGoal::None,
13205 )
13206 });
13207 })
13208 }
13209
13210 pub fn move_to_end_of_paragraph(
13211 &mut self,
13212 _: &MoveToEndOfParagraph,
13213 window: &mut Window,
13214 cx: &mut Context<Self>,
13215 ) {
13216 if matches!(self.mode, EditorMode::SingleLine) {
13217 cx.propagate();
13218 return;
13219 }
13220 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13221 self.change_selections(Default::default(), window, cx, |s| {
13222 s.move_with(|map, selection| {
13223 selection.collapse_to(
13224 movement::end_of_paragraph(map, selection.head(), 1),
13225 SelectionGoal::None,
13226 )
13227 });
13228 })
13229 }
13230
13231 pub fn select_to_start_of_paragraph(
13232 &mut self,
13233 _: &SelectToStartOfParagraph,
13234 window: &mut Window,
13235 cx: &mut Context<Self>,
13236 ) {
13237 if matches!(self.mode, EditorMode::SingleLine) {
13238 cx.propagate();
13239 return;
13240 }
13241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13242 self.change_selections(Default::default(), window, cx, |s| {
13243 s.move_heads_with(|map, head, _| {
13244 (
13245 movement::start_of_paragraph(map, head, 1),
13246 SelectionGoal::None,
13247 )
13248 });
13249 })
13250 }
13251
13252 pub fn select_to_end_of_paragraph(
13253 &mut self,
13254 _: &SelectToEndOfParagraph,
13255 window: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 if matches!(self.mode, EditorMode::SingleLine) {
13259 cx.propagate();
13260 return;
13261 }
13262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13263 self.change_selections(Default::default(), window, cx, |s| {
13264 s.move_heads_with(|map, head, _| {
13265 (
13266 movement::end_of_paragraph(map, head, 1),
13267 SelectionGoal::None,
13268 )
13269 });
13270 })
13271 }
13272
13273 pub fn move_to_start_of_excerpt(
13274 &mut self,
13275 _: &MoveToStartOfExcerpt,
13276 window: &mut Window,
13277 cx: &mut Context<Self>,
13278 ) {
13279 if matches!(self.mode, EditorMode::SingleLine) {
13280 cx.propagate();
13281 return;
13282 }
13283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13284 self.change_selections(Default::default(), window, cx, |s| {
13285 s.move_with(|map, selection| {
13286 selection.collapse_to(
13287 movement::start_of_excerpt(
13288 map,
13289 selection.head(),
13290 workspace::searchable::Direction::Prev,
13291 ),
13292 SelectionGoal::None,
13293 )
13294 });
13295 })
13296 }
13297
13298 pub fn move_to_start_of_next_excerpt(
13299 &mut self,
13300 _: &MoveToStartOfNextExcerpt,
13301 window: &mut Window,
13302 cx: &mut Context<Self>,
13303 ) {
13304 if matches!(self.mode, EditorMode::SingleLine) {
13305 cx.propagate();
13306 return;
13307 }
13308
13309 self.change_selections(Default::default(), window, cx, |s| {
13310 s.move_with(|map, selection| {
13311 selection.collapse_to(
13312 movement::start_of_excerpt(
13313 map,
13314 selection.head(),
13315 workspace::searchable::Direction::Next,
13316 ),
13317 SelectionGoal::None,
13318 )
13319 });
13320 })
13321 }
13322
13323 pub fn move_to_end_of_excerpt(
13324 &mut self,
13325 _: &MoveToEndOfExcerpt,
13326 window: &mut Window,
13327 cx: &mut Context<Self>,
13328 ) {
13329 if matches!(self.mode, EditorMode::SingleLine) {
13330 cx.propagate();
13331 return;
13332 }
13333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13334 self.change_selections(Default::default(), window, cx, |s| {
13335 s.move_with(|map, selection| {
13336 selection.collapse_to(
13337 movement::end_of_excerpt(
13338 map,
13339 selection.head(),
13340 workspace::searchable::Direction::Next,
13341 ),
13342 SelectionGoal::None,
13343 )
13344 });
13345 })
13346 }
13347
13348 pub fn move_to_end_of_previous_excerpt(
13349 &mut self,
13350 _: &MoveToEndOfPreviousExcerpt,
13351 window: &mut Window,
13352 cx: &mut Context<Self>,
13353 ) {
13354 if matches!(self.mode, EditorMode::SingleLine) {
13355 cx.propagate();
13356 return;
13357 }
13358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13359 self.change_selections(Default::default(), window, cx, |s| {
13360 s.move_with(|map, selection| {
13361 selection.collapse_to(
13362 movement::end_of_excerpt(
13363 map,
13364 selection.head(),
13365 workspace::searchable::Direction::Prev,
13366 ),
13367 SelectionGoal::None,
13368 )
13369 });
13370 })
13371 }
13372
13373 pub fn select_to_start_of_excerpt(
13374 &mut self,
13375 _: &SelectToStartOfExcerpt,
13376 window: &mut Window,
13377 cx: &mut Context<Self>,
13378 ) {
13379 if matches!(self.mode, EditorMode::SingleLine) {
13380 cx.propagate();
13381 return;
13382 }
13383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13384 self.change_selections(Default::default(), window, cx, |s| {
13385 s.move_heads_with(|map, head, _| {
13386 (
13387 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13388 SelectionGoal::None,
13389 )
13390 });
13391 })
13392 }
13393
13394 pub fn select_to_start_of_next_excerpt(
13395 &mut self,
13396 _: &SelectToStartOfNextExcerpt,
13397 window: &mut Window,
13398 cx: &mut Context<Self>,
13399 ) {
13400 if matches!(self.mode, EditorMode::SingleLine) {
13401 cx.propagate();
13402 return;
13403 }
13404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13405 self.change_selections(Default::default(), window, cx, |s| {
13406 s.move_heads_with(|map, head, _| {
13407 (
13408 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13409 SelectionGoal::None,
13410 )
13411 });
13412 })
13413 }
13414
13415 pub fn select_to_end_of_excerpt(
13416 &mut self,
13417 _: &SelectToEndOfExcerpt,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 if matches!(self.mode, EditorMode::SingleLine) {
13422 cx.propagate();
13423 return;
13424 }
13425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13426 self.change_selections(Default::default(), window, cx, |s| {
13427 s.move_heads_with(|map, head, _| {
13428 (
13429 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13430 SelectionGoal::None,
13431 )
13432 });
13433 })
13434 }
13435
13436 pub fn select_to_end_of_previous_excerpt(
13437 &mut self,
13438 _: &SelectToEndOfPreviousExcerpt,
13439 window: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 if matches!(self.mode, EditorMode::SingleLine) {
13443 cx.propagate();
13444 return;
13445 }
13446 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13447 self.change_selections(Default::default(), window, cx, |s| {
13448 s.move_heads_with(|map, head, _| {
13449 (
13450 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13451 SelectionGoal::None,
13452 )
13453 });
13454 })
13455 }
13456
13457 pub fn move_to_beginning(
13458 &mut self,
13459 _: &MoveToBeginning,
13460 window: &mut Window,
13461 cx: &mut Context<Self>,
13462 ) {
13463 if matches!(self.mode, EditorMode::SingleLine) {
13464 cx.propagate();
13465 return;
13466 }
13467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13468 self.change_selections(Default::default(), window, cx, |s| {
13469 s.select_ranges(vec![0..0]);
13470 });
13471 }
13472
13473 pub fn select_to_beginning(
13474 &mut self,
13475 _: &SelectToBeginning,
13476 window: &mut Window,
13477 cx: &mut Context<Self>,
13478 ) {
13479 let mut selection = self.selections.last::<Point>(cx);
13480 selection.set_head(Point::zero(), SelectionGoal::None);
13481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13482 self.change_selections(Default::default(), window, cx, |s| {
13483 s.select(vec![selection]);
13484 });
13485 }
13486
13487 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13488 if matches!(self.mode, EditorMode::SingleLine) {
13489 cx.propagate();
13490 return;
13491 }
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13493 let cursor = self.buffer.read(cx).read(cx).len();
13494 self.change_selections(Default::default(), window, cx, |s| {
13495 s.select_ranges(vec![cursor..cursor])
13496 });
13497 }
13498
13499 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13500 self.nav_history = nav_history;
13501 }
13502
13503 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13504 self.nav_history.as_ref()
13505 }
13506
13507 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13508 self.push_to_nav_history(
13509 self.selections.newest_anchor().head(),
13510 None,
13511 false,
13512 true,
13513 cx,
13514 );
13515 }
13516
13517 fn push_to_nav_history(
13518 &mut self,
13519 cursor_anchor: Anchor,
13520 new_position: Option<Point>,
13521 is_deactivate: bool,
13522 always: bool,
13523 cx: &mut Context<Self>,
13524 ) {
13525 if let Some(nav_history) = self.nav_history.as_mut() {
13526 let buffer = self.buffer.read(cx).read(cx);
13527 let cursor_position = cursor_anchor.to_point(&buffer);
13528 let scroll_state = self.scroll_manager.anchor();
13529 let scroll_top_row = scroll_state.top_row(&buffer);
13530 drop(buffer);
13531
13532 if let Some(new_position) = new_position {
13533 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13534 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13535 return;
13536 }
13537 }
13538
13539 nav_history.push(
13540 Some(NavigationData {
13541 cursor_anchor,
13542 cursor_position,
13543 scroll_anchor: scroll_state,
13544 scroll_top_row,
13545 }),
13546 cx,
13547 );
13548 cx.emit(EditorEvent::PushedToNavHistory {
13549 anchor: cursor_anchor,
13550 is_deactivate,
13551 })
13552 }
13553 }
13554
13555 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13557 let buffer = self.buffer.read(cx).snapshot(cx);
13558 let mut selection = self.selections.first::<usize>(cx);
13559 selection.set_head(buffer.len(), SelectionGoal::None);
13560 self.change_selections(Default::default(), window, cx, |s| {
13561 s.select(vec![selection]);
13562 });
13563 }
13564
13565 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13567 let end = self.buffer.read(cx).read(cx).len();
13568 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13569 s.select_ranges(vec![0..end]);
13570 });
13571 }
13572
13573 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13576 let mut selections = self.selections.all::<Point>(cx);
13577 let max_point = display_map.buffer_snapshot.max_point();
13578 for selection in &mut selections {
13579 let rows = selection.spanned_rows(true, &display_map);
13580 selection.start = Point::new(rows.start.0, 0);
13581 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13582 selection.reversed = false;
13583 }
13584 self.change_selections(Default::default(), window, cx, |s| {
13585 s.select(selections);
13586 });
13587 }
13588
13589 pub fn split_selection_into_lines(
13590 &mut self,
13591 action: &SplitSelectionIntoLines,
13592 window: &mut Window,
13593 cx: &mut Context<Self>,
13594 ) {
13595 let selections = self
13596 .selections
13597 .all::<Point>(cx)
13598 .into_iter()
13599 .map(|selection| selection.start..selection.end)
13600 .collect::<Vec<_>>();
13601 self.unfold_ranges(&selections, true, true, cx);
13602
13603 let mut new_selection_ranges = Vec::new();
13604 {
13605 let buffer = self.buffer.read(cx).read(cx);
13606 for selection in selections {
13607 for row in selection.start.row..selection.end.row {
13608 let line_start = Point::new(row, 0);
13609 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13610
13611 if action.keep_selections {
13612 // Keep the selection range for each line
13613 let selection_start = if row == selection.start.row {
13614 selection.start
13615 } else {
13616 line_start
13617 };
13618 new_selection_ranges.push(selection_start..line_end);
13619 } else {
13620 // Collapse to cursor at end of line
13621 new_selection_ranges.push(line_end..line_end);
13622 }
13623 }
13624
13625 let is_multiline_selection = selection.start.row != selection.end.row;
13626 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13627 // so this action feels more ergonomic when paired with other selection operations
13628 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13629 if !should_skip_last {
13630 if action.keep_selections {
13631 if is_multiline_selection {
13632 let line_start = Point::new(selection.end.row, 0);
13633 new_selection_ranges.push(line_start..selection.end);
13634 } else {
13635 new_selection_ranges.push(selection.start..selection.end);
13636 }
13637 } else {
13638 new_selection_ranges.push(selection.end..selection.end);
13639 }
13640 }
13641 }
13642 }
13643 self.change_selections(Default::default(), window, cx, |s| {
13644 s.select_ranges(new_selection_ranges);
13645 });
13646 }
13647
13648 pub fn add_selection_above(
13649 &mut self,
13650 _: &AddSelectionAbove,
13651 window: &mut Window,
13652 cx: &mut Context<Self>,
13653 ) {
13654 self.add_selection(true, window, cx);
13655 }
13656
13657 pub fn add_selection_below(
13658 &mut self,
13659 _: &AddSelectionBelow,
13660 window: &mut Window,
13661 cx: &mut Context<Self>,
13662 ) {
13663 self.add_selection(false, window, cx);
13664 }
13665
13666 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13668
13669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13670 let all_selections = self.selections.all::<Point>(cx);
13671 let text_layout_details = self.text_layout_details(window);
13672
13673 let (mut columnar_selections, new_selections_to_columnarize) = {
13674 if let Some(state) = self.add_selections_state.as_ref() {
13675 let columnar_selection_ids: HashSet<_> = state
13676 .groups
13677 .iter()
13678 .flat_map(|group| group.stack.iter())
13679 .copied()
13680 .collect();
13681
13682 all_selections
13683 .into_iter()
13684 .partition(|s| columnar_selection_ids.contains(&s.id))
13685 } else {
13686 (Vec::new(), all_selections)
13687 }
13688 };
13689
13690 let mut state = self
13691 .add_selections_state
13692 .take()
13693 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13694
13695 for selection in new_selections_to_columnarize {
13696 let range = selection.display_range(&display_map).sorted();
13697 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13698 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13699 let positions = start_x.min(end_x)..start_x.max(end_x);
13700 let mut stack = Vec::new();
13701 for row in range.start.row().0..=range.end.row().0 {
13702 if let Some(selection) = self.selections.build_columnar_selection(
13703 &display_map,
13704 DisplayRow(row),
13705 &positions,
13706 selection.reversed,
13707 &text_layout_details,
13708 ) {
13709 stack.push(selection.id);
13710 columnar_selections.push(selection);
13711 }
13712 }
13713 if !stack.is_empty() {
13714 if above {
13715 stack.reverse();
13716 }
13717 state.groups.push(AddSelectionsGroup { above, stack });
13718 }
13719 }
13720
13721 let mut final_selections = Vec::new();
13722 let end_row = if above {
13723 DisplayRow(0)
13724 } else {
13725 display_map.max_point().row()
13726 };
13727
13728 let mut last_added_item_per_group = HashMap::default();
13729 for group in state.groups.iter_mut() {
13730 if let Some(last_id) = group.stack.last() {
13731 last_added_item_per_group.insert(*last_id, group);
13732 }
13733 }
13734
13735 for selection in columnar_selections {
13736 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13737 if above == group.above {
13738 let range = selection.display_range(&display_map).sorted();
13739 debug_assert_eq!(range.start.row(), range.end.row());
13740 let mut row = range.start.row();
13741 let positions =
13742 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13743 px(start)..px(end)
13744 } else {
13745 let start_x =
13746 display_map.x_for_display_point(range.start, &text_layout_details);
13747 let end_x =
13748 display_map.x_for_display_point(range.end, &text_layout_details);
13749 start_x.min(end_x)..start_x.max(end_x)
13750 };
13751
13752 let mut maybe_new_selection = None;
13753 while row != end_row {
13754 if above {
13755 row.0 -= 1;
13756 } else {
13757 row.0 += 1;
13758 }
13759 if let Some(new_selection) = self.selections.build_columnar_selection(
13760 &display_map,
13761 row,
13762 &positions,
13763 selection.reversed,
13764 &text_layout_details,
13765 ) {
13766 maybe_new_selection = Some(new_selection);
13767 break;
13768 }
13769 }
13770
13771 if let Some(new_selection) = maybe_new_selection {
13772 group.stack.push(new_selection.id);
13773 if above {
13774 final_selections.push(new_selection);
13775 final_selections.push(selection);
13776 } else {
13777 final_selections.push(selection);
13778 final_selections.push(new_selection);
13779 }
13780 } else {
13781 final_selections.push(selection);
13782 }
13783 } else {
13784 group.stack.pop();
13785 }
13786 } else {
13787 final_selections.push(selection);
13788 }
13789 }
13790
13791 self.change_selections(Default::default(), window, cx, |s| {
13792 s.select(final_selections);
13793 });
13794
13795 let final_selection_ids: HashSet<_> = self
13796 .selections
13797 .all::<Point>(cx)
13798 .iter()
13799 .map(|s| s.id)
13800 .collect();
13801 state.groups.retain_mut(|group| {
13802 // selections might get merged above so we remove invalid items from stacks
13803 group.stack.retain(|id| final_selection_ids.contains(id));
13804
13805 // single selection in stack can be treated as initial state
13806 group.stack.len() > 1
13807 });
13808
13809 if !state.groups.is_empty() {
13810 self.add_selections_state = Some(state);
13811 }
13812 }
13813
13814 fn select_match_ranges(
13815 &mut self,
13816 range: Range<usize>,
13817 reversed: bool,
13818 replace_newest: bool,
13819 auto_scroll: Option<Autoscroll>,
13820 window: &mut Window,
13821 cx: &mut Context<Editor>,
13822 ) {
13823 self.unfold_ranges(
13824 std::slice::from_ref(&range),
13825 false,
13826 auto_scroll.is_some(),
13827 cx,
13828 );
13829 let effects = if let Some(scroll) = auto_scroll {
13830 SelectionEffects::scroll(scroll)
13831 } else {
13832 SelectionEffects::no_scroll()
13833 };
13834 self.change_selections(effects, window, cx, |s| {
13835 if replace_newest {
13836 s.delete(s.newest_anchor().id);
13837 }
13838 if reversed {
13839 s.insert_range(range.end..range.start);
13840 } else {
13841 s.insert_range(range);
13842 }
13843 });
13844 }
13845
13846 pub fn select_next_match_internal(
13847 &mut self,
13848 display_map: &DisplaySnapshot,
13849 replace_newest: bool,
13850 autoscroll: Option<Autoscroll>,
13851 window: &mut Window,
13852 cx: &mut Context<Self>,
13853 ) -> Result<()> {
13854 let buffer = &display_map.buffer_snapshot;
13855 let mut selections = self.selections.all::<usize>(cx);
13856 if let Some(mut select_next_state) = self.select_next_state.take() {
13857 let query = &select_next_state.query;
13858 if !select_next_state.done {
13859 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13860 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13861 let mut next_selected_range = None;
13862
13863 let bytes_after_last_selection =
13864 buffer.bytes_in_range(last_selection.end..buffer.len());
13865 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13866 let query_matches = query
13867 .stream_find_iter(bytes_after_last_selection)
13868 .map(|result| (last_selection.end, result))
13869 .chain(
13870 query
13871 .stream_find_iter(bytes_before_first_selection)
13872 .map(|result| (0, result)),
13873 );
13874
13875 for (start_offset, query_match) in query_matches {
13876 let query_match = query_match.unwrap(); // can only fail due to I/O
13877 let offset_range =
13878 start_offset + query_match.start()..start_offset + query_match.end();
13879
13880 if !select_next_state.wordwise
13881 || (!buffer.is_inside_word(offset_range.start, false)
13882 && !buffer.is_inside_word(offset_range.end, false))
13883 {
13884 // TODO: This is n^2, because we might check all the selections
13885 if !selections
13886 .iter()
13887 .any(|selection| selection.range().overlaps(&offset_range))
13888 {
13889 next_selected_range = Some(offset_range);
13890 break;
13891 }
13892 }
13893 }
13894
13895 if let Some(next_selected_range) = next_selected_range {
13896 self.select_match_ranges(
13897 next_selected_range,
13898 last_selection.reversed,
13899 replace_newest,
13900 autoscroll,
13901 window,
13902 cx,
13903 );
13904 } else {
13905 select_next_state.done = true;
13906 }
13907 }
13908
13909 self.select_next_state = Some(select_next_state);
13910 } else {
13911 let mut only_carets = true;
13912 let mut same_text_selected = true;
13913 let mut selected_text = None;
13914
13915 let mut selections_iter = selections.iter().peekable();
13916 while let Some(selection) = selections_iter.next() {
13917 if selection.start != selection.end {
13918 only_carets = false;
13919 }
13920
13921 if same_text_selected {
13922 if selected_text.is_none() {
13923 selected_text =
13924 Some(buffer.text_for_range(selection.range()).collect::<String>());
13925 }
13926
13927 if let Some(next_selection) = selections_iter.peek() {
13928 if next_selection.range().len() == selection.range().len() {
13929 let next_selected_text = buffer
13930 .text_for_range(next_selection.range())
13931 .collect::<String>();
13932 if Some(next_selected_text) != selected_text {
13933 same_text_selected = false;
13934 selected_text = None;
13935 }
13936 } else {
13937 same_text_selected = false;
13938 selected_text = None;
13939 }
13940 }
13941 }
13942 }
13943
13944 if only_carets {
13945 for selection in &mut selections {
13946 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13947 selection.start = word_range.start;
13948 selection.end = word_range.end;
13949 selection.goal = SelectionGoal::None;
13950 selection.reversed = false;
13951 self.select_match_ranges(
13952 selection.start..selection.end,
13953 selection.reversed,
13954 replace_newest,
13955 autoscroll,
13956 window,
13957 cx,
13958 );
13959 }
13960
13961 if selections.len() == 1 {
13962 let selection = selections
13963 .last()
13964 .expect("ensured that there's only one selection");
13965 let query = buffer
13966 .text_for_range(selection.start..selection.end)
13967 .collect::<String>();
13968 let is_empty = query.is_empty();
13969 let select_state = SelectNextState {
13970 query: AhoCorasick::new(&[query])?,
13971 wordwise: true,
13972 done: is_empty,
13973 };
13974 self.select_next_state = Some(select_state);
13975 } else {
13976 self.select_next_state = None;
13977 }
13978 } else if let Some(selected_text) = selected_text {
13979 self.select_next_state = Some(SelectNextState {
13980 query: AhoCorasick::new(&[selected_text])?,
13981 wordwise: false,
13982 done: false,
13983 });
13984 self.select_next_match_internal(
13985 display_map,
13986 replace_newest,
13987 autoscroll,
13988 window,
13989 cx,
13990 )?;
13991 }
13992 }
13993 Ok(())
13994 }
13995
13996 pub fn select_all_matches(
13997 &mut self,
13998 _action: &SelectAllMatches,
13999 window: &mut Window,
14000 cx: &mut Context<Self>,
14001 ) -> Result<()> {
14002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14003
14004 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14005
14006 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14007 let Some(select_next_state) = self.select_next_state.as_mut() else {
14008 return Ok(());
14009 };
14010 if select_next_state.done {
14011 return Ok(());
14012 }
14013
14014 let mut new_selections = Vec::new();
14015
14016 let reversed = self.selections.oldest::<usize>(cx).reversed;
14017 let buffer = &display_map.buffer_snapshot;
14018 let query_matches = select_next_state
14019 .query
14020 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14021
14022 for query_match in query_matches.into_iter() {
14023 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14024 let offset_range = if reversed {
14025 query_match.end()..query_match.start()
14026 } else {
14027 query_match.start()..query_match.end()
14028 };
14029
14030 if !select_next_state.wordwise
14031 || (!buffer.is_inside_word(offset_range.start, false)
14032 && !buffer.is_inside_word(offset_range.end, false))
14033 {
14034 new_selections.push(offset_range.start..offset_range.end);
14035 }
14036 }
14037
14038 select_next_state.done = true;
14039
14040 if new_selections.is_empty() {
14041 log::error!("bug: new_selections is empty in select_all_matches");
14042 return Ok(());
14043 }
14044
14045 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14046 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14047 selections.select_ranges(new_selections)
14048 });
14049
14050 Ok(())
14051 }
14052
14053 pub fn select_next(
14054 &mut self,
14055 action: &SelectNext,
14056 window: &mut Window,
14057 cx: &mut Context<Self>,
14058 ) -> Result<()> {
14059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14061 self.select_next_match_internal(
14062 &display_map,
14063 action.replace_newest,
14064 Some(Autoscroll::newest()),
14065 window,
14066 cx,
14067 )?;
14068 Ok(())
14069 }
14070
14071 pub fn select_previous(
14072 &mut self,
14073 action: &SelectPrevious,
14074 window: &mut Window,
14075 cx: &mut Context<Self>,
14076 ) -> Result<()> {
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14078 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14079 let buffer = &display_map.buffer_snapshot;
14080 let mut selections = self.selections.all::<usize>(cx);
14081 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14082 let query = &select_prev_state.query;
14083 if !select_prev_state.done {
14084 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14085 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14086 let mut next_selected_range = None;
14087 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14088 let bytes_before_last_selection =
14089 buffer.reversed_bytes_in_range(0..last_selection.start);
14090 let bytes_after_first_selection =
14091 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14092 let query_matches = query
14093 .stream_find_iter(bytes_before_last_selection)
14094 .map(|result| (last_selection.start, result))
14095 .chain(
14096 query
14097 .stream_find_iter(bytes_after_first_selection)
14098 .map(|result| (buffer.len(), result)),
14099 );
14100 for (end_offset, query_match) in query_matches {
14101 let query_match = query_match.unwrap(); // can only fail due to I/O
14102 let offset_range =
14103 end_offset - query_match.end()..end_offset - query_match.start();
14104
14105 if !select_prev_state.wordwise
14106 || (!buffer.is_inside_word(offset_range.start, false)
14107 && !buffer.is_inside_word(offset_range.end, false))
14108 {
14109 next_selected_range = Some(offset_range);
14110 break;
14111 }
14112 }
14113
14114 if let Some(next_selected_range) = next_selected_range {
14115 self.select_match_ranges(
14116 next_selected_range,
14117 last_selection.reversed,
14118 action.replace_newest,
14119 Some(Autoscroll::newest()),
14120 window,
14121 cx,
14122 );
14123 } else {
14124 select_prev_state.done = true;
14125 }
14126 }
14127
14128 self.select_prev_state = Some(select_prev_state);
14129 } else {
14130 let mut only_carets = true;
14131 let mut same_text_selected = true;
14132 let mut selected_text = None;
14133
14134 let mut selections_iter = selections.iter().peekable();
14135 while let Some(selection) = selections_iter.next() {
14136 if selection.start != selection.end {
14137 only_carets = false;
14138 }
14139
14140 if same_text_selected {
14141 if selected_text.is_none() {
14142 selected_text =
14143 Some(buffer.text_for_range(selection.range()).collect::<String>());
14144 }
14145
14146 if let Some(next_selection) = selections_iter.peek() {
14147 if next_selection.range().len() == selection.range().len() {
14148 let next_selected_text = buffer
14149 .text_for_range(next_selection.range())
14150 .collect::<String>();
14151 if Some(next_selected_text) != selected_text {
14152 same_text_selected = false;
14153 selected_text = None;
14154 }
14155 } else {
14156 same_text_selected = false;
14157 selected_text = None;
14158 }
14159 }
14160 }
14161 }
14162
14163 if only_carets {
14164 for selection in &mut selections {
14165 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14166 selection.start = word_range.start;
14167 selection.end = word_range.end;
14168 selection.goal = SelectionGoal::None;
14169 selection.reversed = false;
14170 self.select_match_ranges(
14171 selection.start..selection.end,
14172 selection.reversed,
14173 action.replace_newest,
14174 Some(Autoscroll::newest()),
14175 window,
14176 cx,
14177 );
14178 }
14179 if selections.len() == 1 {
14180 let selection = selections
14181 .last()
14182 .expect("ensured that there's only one selection");
14183 let query = buffer
14184 .text_for_range(selection.start..selection.end)
14185 .collect::<String>();
14186 let is_empty = query.is_empty();
14187 let select_state = SelectNextState {
14188 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14189 wordwise: true,
14190 done: is_empty,
14191 };
14192 self.select_prev_state = Some(select_state);
14193 } else {
14194 self.select_prev_state = None;
14195 }
14196 } else if let Some(selected_text) = selected_text {
14197 self.select_prev_state = Some(SelectNextState {
14198 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14199 wordwise: false,
14200 done: false,
14201 });
14202 self.select_previous(action, window, cx)?;
14203 }
14204 }
14205 Ok(())
14206 }
14207
14208 pub fn find_next_match(
14209 &mut self,
14210 _: &FindNextMatch,
14211 window: &mut Window,
14212 cx: &mut Context<Self>,
14213 ) -> Result<()> {
14214 let selections = self.selections.disjoint_anchors();
14215 match selections.first() {
14216 Some(first) if selections.len() >= 2 => {
14217 self.change_selections(Default::default(), window, cx, |s| {
14218 s.select_ranges([first.range()]);
14219 });
14220 }
14221 _ => self.select_next(
14222 &SelectNext {
14223 replace_newest: true,
14224 },
14225 window,
14226 cx,
14227 )?,
14228 }
14229 Ok(())
14230 }
14231
14232 pub fn find_previous_match(
14233 &mut self,
14234 _: &FindPreviousMatch,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) -> Result<()> {
14238 let selections = self.selections.disjoint_anchors();
14239 match selections.last() {
14240 Some(last) if selections.len() >= 2 => {
14241 self.change_selections(Default::default(), window, cx, |s| {
14242 s.select_ranges([last.range()]);
14243 });
14244 }
14245 _ => self.select_previous(
14246 &SelectPrevious {
14247 replace_newest: true,
14248 },
14249 window,
14250 cx,
14251 )?,
14252 }
14253 Ok(())
14254 }
14255
14256 pub fn toggle_comments(
14257 &mut self,
14258 action: &ToggleComments,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) {
14262 if self.read_only(cx) {
14263 return;
14264 }
14265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14266 let text_layout_details = &self.text_layout_details(window);
14267 self.transact(window, cx, |this, window, cx| {
14268 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14269 let mut edits = Vec::new();
14270 let mut selection_edit_ranges = Vec::new();
14271 let mut last_toggled_row = None;
14272 let snapshot = this.buffer.read(cx).read(cx);
14273 let empty_str: Arc<str> = Arc::default();
14274 let mut suffixes_inserted = Vec::new();
14275 let ignore_indent = action.ignore_indent;
14276
14277 fn comment_prefix_range(
14278 snapshot: &MultiBufferSnapshot,
14279 row: MultiBufferRow,
14280 comment_prefix: &str,
14281 comment_prefix_whitespace: &str,
14282 ignore_indent: bool,
14283 ) -> Range<Point> {
14284 let indent_size = if ignore_indent {
14285 0
14286 } else {
14287 snapshot.indent_size_for_line(row).len
14288 };
14289
14290 let start = Point::new(row.0, indent_size);
14291
14292 let mut line_bytes = snapshot
14293 .bytes_in_range(start..snapshot.max_point())
14294 .flatten()
14295 .copied();
14296
14297 // If this line currently begins with the line comment prefix, then record
14298 // the range containing the prefix.
14299 if line_bytes
14300 .by_ref()
14301 .take(comment_prefix.len())
14302 .eq(comment_prefix.bytes())
14303 {
14304 // Include any whitespace that matches the comment prefix.
14305 let matching_whitespace_len = line_bytes
14306 .zip(comment_prefix_whitespace.bytes())
14307 .take_while(|(a, b)| a == b)
14308 .count() as u32;
14309 let end = Point::new(
14310 start.row,
14311 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14312 );
14313 start..end
14314 } else {
14315 start..start
14316 }
14317 }
14318
14319 fn comment_suffix_range(
14320 snapshot: &MultiBufferSnapshot,
14321 row: MultiBufferRow,
14322 comment_suffix: &str,
14323 comment_suffix_has_leading_space: bool,
14324 ) -> Range<Point> {
14325 let end = Point::new(row.0, snapshot.line_len(row));
14326 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14327
14328 let mut line_end_bytes = snapshot
14329 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14330 .flatten()
14331 .copied();
14332
14333 let leading_space_len = if suffix_start_column > 0
14334 && line_end_bytes.next() == Some(b' ')
14335 && comment_suffix_has_leading_space
14336 {
14337 1
14338 } else {
14339 0
14340 };
14341
14342 // If this line currently begins with the line comment prefix, then record
14343 // the range containing the prefix.
14344 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14345 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14346 start..end
14347 } else {
14348 end..end
14349 }
14350 }
14351
14352 // TODO: Handle selections that cross excerpts
14353 for selection in &mut selections {
14354 let start_column = snapshot
14355 .indent_size_for_line(MultiBufferRow(selection.start.row))
14356 .len;
14357 let language = if let Some(language) =
14358 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14359 {
14360 language
14361 } else {
14362 continue;
14363 };
14364
14365 selection_edit_ranges.clear();
14366
14367 // If multiple selections contain a given row, avoid processing that
14368 // row more than once.
14369 let mut start_row = MultiBufferRow(selection.start.row);
14370 if last_toggled_row == Some(start_row) {
14371 start_row = start_row.next_row();
14372 }
14373 let end_row =
14374 if selection.end.row > selection.start.row && selection.end.column == 0 {
14375 MultiBufferRow(selection.end.row - 1)
14376 } else {
14377 MultiBufferRow(selection.end.row)
14378 };
14379 last_toggled_row = Some(end_row);
14380
14381 if start_row > end_row {
14382 continue;
14383 }
14384
14385 // If the language has line comments, toggle those.
14386 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14387
14388 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14389 if ignore_indent {
14390 full_comment_prefixes = full_comment_prefixes
14391 .into_iter()
14392 .map(|s| Arc::from(s.trim_end()))
14393 .collect();
14394 }
14395
14396 if !full_comment_prefixes.is_empty() {
14397 let first_prefix = full_comment_prefixes
14398 .first()
14399 .expect("prefixes is non-empty");
14400 let prefix_trimmed_lengths = full_comment_prefixes
14401 .iter()
14402 .map(|p| p.trim_end_matches(' ').len())
14403 .collect::<SmallVec<[usize; 4]>>();
14404
14405 let mut all_selection_lines_are_comments = true;
14406
14407 for row in start_row.0..=end_row.0 {
14408 let row = MultiBufferRow(row);
14409 if start_row < end_row && snapshot.is_line_blank(row) {
14410 continue;
14411 }
14412
14413 let prefix_range = full_comment_prefixes
14414 .iter()
14415 .zip(prefix_trimmed_lengths.iter().copied())
14416 .map(|(prefix, trimmed_prefix_len)| {
14417 comment_prefix_range(
14418 snapshot.deref(),
14419 row,
14420 &prefix[..trimmed_prefix_len],
14421 &prefix[trimmed_prefix_len..],
14422 ignore_indent,
14423 )
14424 })
14425 .max_by_key(|range| range.end.column - range.start.column)
14426 .expect("prefixes is non-empty");
14427
14428 if prefix_range.is_empty() {
14429 all_selection_lines_are_comments = false;
14430 }
14431
14432 selection_edit_ranges.push(prefix_range);
14433 }
14434
14435 if all_selection_lines_are_comments {
14436 edits.extend(
14437 selection_edit_ranges
14438 .iter()
14439 .cloned()
14440 .map(|range| (range, empty_str.clone())),
14441 );
14442 } else {
14443 let min_column = selection_edit_ranges
14444 .iter()
14445 .map(|range| range.start.column)
14446 .min()
14447 .unwrap_or(0);
14448 edits.extend(selection_edit_ranges.iter().map(|range| {
14449 let position = Point::new(range.start.row, min_column);
14450 (position..position, first_prefix.clone())
14451 }));
14452 }
14453 } else if let Some(BlockCommentConfig {
14454 start: full_comment_prefix,
14455 end: comment_suffix,
14456 ..
14457 }) = language.block_comment()
14458 {
14459 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14460 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14461 let prefix_range = comment_prefix_range(
14462 snapshot.deref(),
14463 start_row,
14464 comment_prefix,
14465 comment_prefix_whitespace,
14466 ignore_indent,
14467 );
14468 let suffix_range = comment_suffix_range(
14469 snapshot.deref(),
14470 end_row,
14471 comment_suffix.trim_start_matches(' '),
14472 comment_suffix.starts_with(' '),
14473 );
14474
14475 if prefix_range.is_empty() || suffix_range.is_empty() {
14476 edits.push((
14477 prefix_range.start..prefix_range.start,
14478 full_comment_prefix.clone(),
14479 ));
14480 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14481 suffixes_inserted.push((end_row, comment_suffix.len()));
14482 } else {
14483 edits.push((prefix_range, empty_str.clone()));
14484 edits.push((suffix_range, empty_str.clone()));
14485 }
14486 } else {
14487 continue;
14488 }
14489 }
14490
14491 drop(snapshot);
14492 this.buffer.update(cx, |buffer, cx| {
14493 buffer.edit(edits, None, cx);
14494 });
14495
14496 // Adjust selections so that they end before any comment suffixes that
14497 // were inserted.
14498 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14499 let mut selections = this.selections.all::<Point>(cx);
14500 let snapshot = this.buffer.read(cx).read(cx);
14501 for selection in &mut selections {
14502 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14503 match row.cmp(&MultiBufferRow(selection.end.row)) {
14504 Ordering::Less => {
14505 suffixes_inserted.next();
14506 continue;
14507 }
14508 Ordering::Greater => break,
14509 Ordering::Equal => {
14510 if selection.end.column == snapshot.line_len(row) {
14511 if selection.is_empty() {
14512 selection.start.column -= suffix_len as u32;
14513 }
14514 selection.end.column -= suffix_len as u32;
14515 }
14516 break;
14517 }
14518 }
14519 }
14520 }
14521
14522 drop(snapshot);
14523 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14524
14525 let selections = this.selections.all::<Point>(cx);
14526 let selections_on_single_row = selections.windows(2).all(|selections| {
14527 selections[0].start.row == selections[1].start.row
14528 && selections[0].end.row == selections[1].end.row
14529 && selections[0].start.row == selections[0].end.row
14530 });
14531 let selections_selecting = selections
14532 .iter()
14533 .any(|selection| selection.start != selection.end);
14534 let advance_downwards = action.advance_downwards
14535 && selections_on_single_row
14536 && !selections_selecting
14537 && !matches!(this.mode, EditorMode::SingleLine);
14538
14539 if advance_downwards {
14540 let snapshot = this.buffer.read(cx).snapshot(cx);
14541
14542 this.change_selections(Default::default(), window, cx, |s| {
14543 s.move_cursors_with(|display_snapshot, display_point, _| {
14544 let mut point = display_point.to_point(display_snapshot);
14545 point.row += 1;
14546 point = snapshot.clip_point(point, Bias::Left);
14547 let display_point = point.to_display_point(display_snapshot);
14548 let goal = SelectionGoal::HorizontalPosition(
14549 display_snapshot
14550 .x_for_display_point(display_point, text_layout_details)
14551 .into(),
14552 );
14553 (display_point, goal)
14554 })
14555 });
14556 }
14557 });
14558 }
14559
14560 pub fn select_enclosing_symbol(
14561 &mut self,
14562 _: &SelectEnclosingSymbol,
14563 window: &mut Window,
14564 cx: &mut Context<Self>,
14565 ) {
14566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14567
14568 let buffer = self.buffer.read(cx).snapshot(cx);
14569 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14570
14571 fn update_selection(
14572 selection: &Selection<usize>,
14573 buffer_snap: &MultiBufferSnapshot,
14574 ) -> Option<Selection<usize>> {
14575 let cursor = selection.head();
14576 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14577 for symbol in symbols.iter().rev() {
14578 let start = symbol.range.start.to_offset(buffer_snap);
14579 let end = symbol.range.end.to_offset(buffer_snap);
14580 let new_range = start..end;
14581 if start < selection.start || end > selection.end {
14582 return Some(Selection {
14583 id: selection.id,
14584 start: new_range.start,
14585 end: new_range.end,
14586 goal: SelectionGoal::None,
14587 reversed: selection.reversed,
14588 });
14589 }
14590 }
14591 None
14592 }
14593
14594 let mut selected_larger_symbol = false;
14595 let new_selections = old_selections
14596 .iter()
14597 .map(|selection| match update_selection(selection, &buffer) {
14598 Some(new_selection) => {
14599 if new_selection.range() != selection.range() {
14600 selected_larger_symbol = true;
14601 }
14602 new_selection
14603 }
14604 None => selection.clone(),
14605 })
14606 .collect::<Vec<_>>();
14607
14608 if selected_larger_symbol {
14609 self.change_selections(Default::default(), window, cx, |s| {
14610 s.select(new_selections);
14611 });
14612 }
14613 }
14614
14615 pub fn select_larger_syntax_node(
14616 &mut self,
14617 _: &SelectLargerSyntaxNode,
14618 window: &mut Window,
14619 cx: &mut Context<Self>,
14620 ) {
14621 let Some(visible_row_count) = self.visible_row_count() else {
14622 return;
14623 };
14624 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14625 if old_selections.is_empty() {
14626 return;
14627 }
14628
14629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14630
14631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14632 let buffer = self.buffer.read(cx).snapshot(cx);
14633
14634 let mut selected_larger_node = false;
14635 let mut new_selections = old_selections
14636 .iter()
14637 .map(|selection| {
14638 let old_range = selection.start..selection.end;
14639
14640 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14641 // manually select word at selection
14642 if ["string_content", "inline"].contains(&node.kind()) {
14643 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14644 // ignore if word is already selected
14645 if !word_range.is_empty() && old_range != word_range {
14646 let (last_word_range, _) =
14647 buffer.surrounding_word(old_range.end, false);
14648 // only select word if start and end point belongs to same word
14649 if word_range == last_word_range {
14650 selected_larger_node = true;
14651 return Selection {
14652 id: selection.id,
14653 start: word_range.start,
14654 end: word_range.end,
14655 goal: SelectionGoal::None,
14656 reversed: selection.reversed,
14657 };
14658 }
14659 }
14660 }
14661 }
14662
14663 let mut new_range = old_range.clone();
14664 while let Some((_node, containing_range)) =
14665 buffer.syntax_ancestor(new_range.clone())
14666 {
14667 new_range = match containing_range {
14668 MultiOrSingleBufferOffsetRange::Single(_) => break,
14669 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14670 };
14671 if !display_map.intersects_fold(new_range.start)
14672 && !display_map.intersects_fold(new_range.end)
14673 {
14674 break;
14675 }
14676 }
14677
14678 selected_larger_node |= new_range != old_range;
14679 Selection {
14680 id: selection.id,
14681 start: new_range.start,
14682 end: new_range.end,
14683 goal: SelectionGoal::None,
14684 reversed: selection.reversed,
14685 }
14686 })
14687 .collect::<Vec<_>>();
14688
14689 if !selected_larger_node {
14690 return; // don't put this call in the history
14691 }
14692
14693 // scroll based on transformation done to the last selection created by the user
14694 let (last_old, last_new) = old_selections
14695 .last()
14696 .zip(new_selections.last().cloned())
14697 .expect("old_selections isn't empty");
14698
14699 // revert selection
14700 let is_selection_reversed = {
14701 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14702 new_selections.last_mut().expect("checked above").reversed =
14703 should_newest_selection_be_reversed;
14704 should_newest_selection_be_reversed
14705 };
14706
14707 if selected_larger_node {
14708 self.select_syntax_node_history.disable_clearing = true;
14709 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14710 s.select(new_selections.clone());
14711 });
14712 self.select_syntax_node_history.disable_clearing = false;
14713 }
14714
14715 let start_row = last_new.start.to_display_point(&display_map).row().0;
14716 let end_row = last_new.end.to_display_point(&display_map).row().0;
14717 let selection_height = end_row - start_row + 1;
14718 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14719
14720 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14721 let scroll_behavior = if fits_on_the_screen {
14722 self.request_autoscroll(Autoscroll::fit(), cx);
14723 SelectSyntaxNodeScrollBehavior::FitSelection
14724 } else if is_selection_reversed {
14725 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14726 SelectSyntaxNodeScrollBehavior::CursorTop
14727 } else {
14728 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14729 SelectSyntaxNodeScrollBehavior::CursorBottom
14730 };
14731
14732 self.select_syntax_node_history.push((
14733 old_selections,
14734 scroll_behavior,
14735 is_selection_reversed,
14736 ));
14737 }
14738
14739 pub fn select_smaller_syntax_node(
14740 &mut self,
14741 _: &SelectSmallerSyntaxNode,
14742 window: &mut Window,
14743 cx: &mut Context<Self>,
14744 ) {
14745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14746
14747 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14748 self.select_syntax_node_history.pop()
14749 {
14750 if let Some(selection) = selections.last_mut() {
14751 selection.reversed = is_selection_reversed;
14752 }
14753
14754 self.select_syntax_node_history.disable_clearing = true;
14755 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14756 s.select(selections.to_vec());
14757 });
14758 self.select_syntax_node_history.disable_clearing = false;
14759
14760 match scroll_behavior {
14761 SelectSyntaxNodeScrollBehavior::CursorTop => {
14762 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14763 }
14764 SelectSyntaxNodeScrollBehavior::FitSelection => {
14765 self.request_autoscroll(Autoscroll::fit(), cx);
14766 }
14767 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14768 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14769 }
14770 }
14771 }
14772 }
14773
14774 pub fn unwrap_syntax_node(
14775 &mut self,
14776 _: &UnwrapSyntaxNode,
14777 window: &mut Window,
14778 cx: &mut Context<Self>,
14779 ) {
14780 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14781
14782 let buffer = self.buffer.read(cx).snapshot(cx);
14783 let selections = self
14784 .selections
14785 .all::<usize>(cx)
14786 .into_iter()
14787 // subtracting the offset requires sorting
14788 .sorted_by_key(|i| i.start);
14789
14790 let full_edits = selections
14791 .into_iter()
14792 .filter_map(|selection| {
14793 // Only requires two branches once if-let-chains stabilize (#53667)
14794 let child = if !selection.is_empty() {
14795 selection.range()
14796 } else if let Some((_, ancestor_range)) =
14797 buffer.syntax_ancestor(selection.start..selection.end)
14798 {
14799 match ancestor_range {
14800 MultiOrSingleBufferOffsetRange::Single(range) => range,
14801 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14802 }
14803 } else {
14804 selection.range()
14805 };
14806
14807 let mut parent = child.clone();
14808 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14809 parent = match ancestor_range {
14810 MultiOrSingleBufferOffsetRange::Single(range) => range,
14811 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14812 };
14813 if parent.start < child.start || parent.end > child.end {
14814 break;
14815 }
14816 }
14817
14818 if parent == child {
14819 return None;
14820 }
14821 let text = buffer.text_for_range(child).collect::<String>();
14822 Some((selection.id, parent, text))
14823 })
14824 .collect::<Vec<_>>();
14825
14826 self.transact(window, cx, |this, window, cx| {
14827 this.buffer.update(cx, |buffer, cx| {
14828 buffer.edit(
14829 full_edits
14830 .iter()
14831 .map(|(_, p, t)| (p.clone(), t.clone()))
14832 .collect::<Vec<_>>(),
14833 None,
14834 cx,
14835 );
14836 });
14837 this.change_selections(Default::default(), window, cx, |s| {
14838 let mut offset = 0;
14839 let mut selections = vec![];
14840 for (id, parent, text) in full_edits {
14841 let start = parent.start - offset;
14842 offset += parent.len() - text.len();
14843 selections.push(Selection {
14844 id,
14845 start,
14846 end: start + text.len(),
14847 reversed: false,
14848 goal: Default::default(),
14849 });
14850 }
14851 s.select(selections);
14852 });
14853 });
14854 }
14855
14856 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14857 if !EditorSettings::get_global(cx).gutter.runnables {
14858 self.clear_tasks();
14859 return Task::ready(());
14860 }
14861 let project = self.project().map(Entity::downgrade);
14862 let task_sources = self.lsp_task_sources(cx);
14863 let multi_buffer = self.buffer.downgrade();
14864 cx.spawn_in(window, async move |editor, cx| {
14865 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14866 let Some(project) = project.and_then(|p| p.upgrade()) else {
14867 return;
14868 };
14869 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14870 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14871 }) else {
14872 return;
14873 };
14874
14875 let hide_runnables = project
14876 .update(cx, |project, _| project.is_via_collab())
14877 .unwrap_or(true);
14878 if hide_runnables {
14879 return;
14880 }
14881 let new_rows =
14882 cx.background_spawn({
14883 let snapshot = display_snapshot.clone();
14884 async move {
14885 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14886 }
14887 })
14888 .await;
14889 let Ok(lsp_tasks) =
14890 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14891 else {
14892 return;
14893 };
14894 let lsp_tasks = lsp_tasks.await;
14895
14896 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14897 lsp_tasks
14898 .into_iter()
14899 .flat_map(|(kind, tasks)| {
14900 tasks.into_iter().filter_map(move |(location, task)| {
14901 Some((kind.clone(), location?, task))
14902 })
14903 })
14904 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14905 let buffer = location.target.buffer;
14906 let buffer_snapshot = buffer.read(cx).snapshot();
14907 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14908 |(excerpt_id, snapshot, _)| {
14909 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14910 display_snapshot
14911 .buffer_snapshot
14912 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14913 } else {
14914 None
14915 }
14916 },
14917 );
14918 if let Some(offset) = offset {
14919 let task_buffer_range =
14920 location.target.range.to_point(&buffer_snapshot);
14921 let context_buffer_range =
14922 task_buffer_range.to_offset(&buffer_snapshot);
14923 let context_range = BufferOffset(context_buffer_range.start)
14924 ..BufferOffset(context_buffer_range.end);
14925
14926 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14927 .or_insert_with(|| RunnableTasks {
14928 templates: Vec::new(),
14929 offset,
14930 column: task_buffer_range.start.column,
14931 extra_variables: HashMap::default(),
14932 context_range,
14933 })
14934 .templates
14935 .push((kind, task.original_task().clone()));
14936 }
14937
14938 acc
14939 })
14940 }) else {
14941 return;
14942 };
14943
14944 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14945 buffer.language_settings(cx).tasks.prefer_lsp
14946 }) else {
14947 return;
14948 };
14949
14950 let rows = Self::runnable_rows(
14951 project,
14952 display_snapshot,
14953 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14954 new_rows,
14955 cx.clone(),
14956 )
14957 .await;
14958 editor
14959 .update(cx, |editor, _| {
14960 editor.clear_tasks();
14961 for (key, mut value) in rows {
14962 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14963 value.templates.extend(lsp_tasks.templates);
14964 }
14965
14966 editor.insert_tasks(key, value);
14967 }
14968 for (key, value) in lsp_tasks_by_rows {
14969 editor.insert_tasks(key, value);
14970 }
14971 })
14972 .ok();
14973 })
14974 }
14975 fn fetch_runnable_ranges(
14976 snapshot: &DisplaySnapshot,
14977 range: Range<Anchor>,
14978 ) -> Vec<language::RunnableRange> {
14979 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14980 }
14981
14982 fn runnable_rows(
14983 project: Entity<Project>,
14984 snapshot: DisplaySnapshot,
14985 prefer_lsp: bool,
14986 runnable_ranges: Vec<RunnableRange>,
14987 cx: AsyncWindowContext,
14988 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14989 cx.spawn(async move |cx| {
14990 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14991 for mut runnable in runnable_ranges {
14992 let Some(tasks) = cx
14993 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14994 .ok()
14995 else {
14996 continue;
14997 };
14998 let mut tasks = tasks.await;
14999
15000 if prefer_lsp {
15001 tasks.retain(|(task_kind, _)| {
15002 !matches!(task_kind, TaskSourceKind::Language { .. })
15003 });
15004 }
15005 if tasks.is_empty() {
15006 continue;
15007 }
15008
15009 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15010 let Some(row) = snapshot
15011 .buffer_snapshot
15012 .buffer_line_for_row(MultiBufferRow(point.row))
15013 .map(|(_, range)| range.start.row)
15014 else {
15015 continue;
15016 };
15017
15018 let context_range =
15019 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15020 runnable_rows.push((
15021 (runnable.buffer_id, row),
15022 RunnableTasks {
15023 templates: tasks,
15024 offset: snapshot
15025 .buffer_snapshot
15026 .anchor_before(runnable.run_range.start),
15027 context_range,
15028 column: point.column,
15029 extra_variables: runnable.extra_captures,
15030 },
15031 ));
15032 }
15033 runnable_rows
15034 })
15035 }
15036
15037 fn templates_with_tags(
15038 project: &Entity<Project>,
15039 runnable: &mut Runnable,
15040 cx: &mut App,
15041 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15042 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15043 let (worktree_id, file) = project
15044 .buffer_for_id(runnable.buffer, cx)
15045 .and_then(|buffer| buffer.read(cx).file())
15046 .map(|file| (file.worktree_id(cx), file.clone()))
15047 .unzip();
15048
15049 (
15050 project.task_store().read(cx).task_inventory().cloned(),
15051 worktree_id,
15052 file,
15053 )
15054 });
15055
15056 let tags = mem::take(&mut runnable.tags);
15057 let language = runnable.language.clone();
15058 cx.spawn(async move |cx| {
15059 let mut templates_with_tags = Vec::new();
15060 if let Some(inventory) = inventory {
15061 for RunnableTag(tag) in tags {
15062 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15063 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15064 }) else {
15065 return templates_with_tags;
15066 };
15067 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15068 move |(_, template)| {
15069 template.tags.iter().any(|source_tag| source_tag == &tag)
15070 },
15071 ));
15072 }
15073 }
15074 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15075
15076 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15077 // Strongest source wins; if we have worktree tag binding, prefer that to
15078 // global and language bindings;
15079 // if we have a global binding, prefer that to language binding.
15080 let first_mismatch = templates_with_tags
15081 .iter()
15082 .position(|(tag_source, _)| tag_source != leading_tag_source);
15083 if let Some(index) = first_mismatch {
15084 templates_with_tags.truncate(index);
15085 }
15086 }
15087
15088 templates_with_tags
15089 })
15090 }
15091
15092 pub fn move_to_enclosing_bracket(
15093 &mut self,
15094 _: &MoveToEnclosingBracket,
15095 window: &mut Window,
15096 cx: &mut Context<Self>,
15097 ) {
15098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15099 self.change_selections(Default::default(), window, cx, |s| {
15100 s.move_offsets_with(|snapshot, selection| {
15101 let Some(enclosing_bracket_ranges) =
15102 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15103 else {
15104 return;
15105 };
15106
15107 let mut best_length = usize::MAX;
15108 let mut best_inside = false;
15109 let mut best_in_bracket_range = false;
15110 let mut best_destination = None;
15111 for (open, close) in enclosing_bracket_ranges {
15112 let close = close.to_inclusive();
15113 let length = close.end() - open.start;
15114 let inside = selection.start >= open.end && selection.end <= *close.start();
15115 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15116 || close.contains(&selection.head());
15117
15118 // If best is next to a bracket and current isn't, skip
15119 if !in_bracket_range && best_in_bracket_range {
15120 continue;
15121 }
15122
15123 // Prefer smaller lengths unless best is inside and current isn't
15124 if length > best_length && (best_inside || !inside) {
15125 continue;
15126 }
15127
15128 best_length = length;
15129 best_inside = inside;
15130 best_in_bracket_range = in_bracket_range;
15131 best_destination = Some(
15132 if close.contains(&selection.start) && close.contains(&selection.end) {
15133 if inside { open.end } else { open.start }
15134 } else if inside {
15135 *close.start()
15136 } else {
15137 *close.end()
15138 },
15139 );
15140 }
15141
15142 if let Some(destination) = best_destination {
15143 selection.collapse_to(destination, SelectionGoal::None);
15144 }
15145 })
15146 });
15147 }
15148
15149 pub fn undo_selection(
15150 &mut self,
15151 _: &UndoSelection,
15152 window: &mut Window,
15153 cx: &mut Context<Self>,
15154 ) {
15155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15156 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15157 self.selection_history.mode = SelectionHistoryMode::Undoing;
15158 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15159 this.end_selection(window, cx);
15160 this.change_selections(
15161 SelectionEffects::scroll(Autoscroll::newest()),
15162 window,
15163 cx,
15164 |s| s.select_anchors(entry.selections.to_vec()),
15165 );
15166 });
15167 self.selection_history.mode = SelectionHistoryMode::Normal;
15168
15169 self.select_next_state = entry.select_next_state;
15170 self.select_prev_state = entry.select_prev_state;
15171 self.add_selections_state = entry.add_selections_state;
15172 }
15173 }
15174
15175 pub fn redo_selection(
15176 &mut self,
15177 _: &RedoSelection,
15178 window: &mut Window,
15179 cx: &mut Context<Self>,
15180 ) {
15181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15182 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15183 self.selection_history.mode = SelectionHistoryMode::Redoing;
15184 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15185 this.end_selection(window, cx);
15186 this.change_selections(
15187 SelectionEffects::scroll(Autoscroll::newest()),
15188 window,
15189 cx,
15190 |s| s.select_anchors(entry.selections.to_vec()),
15191 );
15192 });
15193 self.selection_history.mode = SelectionHistoryMode::Normal;
15194
15195 self.select_next_state = entry.select_next_state;
15196 self.select_prev_state = entry.select_prev_state;
15197 self.add_selections_state = entry.add_selections_state;
15198 }
15199 }
15200
15201 pub fn expand_excerpts(
15202 &mut self,
15203 action: &ExpandExcerpts,
15204 _: &mut Window,
15205 cx: &mut Context<Self>,
15206 ) {
15207 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15208 }
15209
15210 pub fn expand_excerpts_down(
15211 &mut self,
15212 action: &ExpandExcerptsDown,
15213 _: &mut Window,
15214 cx: &mut Context<Self>,
15215 ) {
15216 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15217 }
15218
15219 pub fn expand_excerpts_up(
15220 &mut self,
15221 action: &ExpandExcerptsUp,
15222 _: &mut Window,
15223 cx: &mut Context<Self>,
15224 ) {
15225 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15226 }
15227
15228 pub fn expand_excerpts_for_direction(
15229 &mut self,
15230 lines: u32,
15231 direction: ExpandExcerptDirection,
15232
15233 cx: &mut Context<Self>,
15234 ) {
15235 let selections = self.selections.disjoint_anchors();
15236
15237 let lines = if lines == 0 {
15238 EditorSettings::get_global(cx).expand_excerpt_lines
15239 } else {
15240 lines
15241 };
15242
15243 self.buffer.update(cx, |buffer, cx| {
15244 let snapshot = buffer.snapshot(cx);
15245 let mut excerpt_ids = selections
15246 .iter()
15247 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15248 .collect::<Vec<_>>();
15249 excerpt_ids.sort();
15250 excerpt_ids.dedup();
15251 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15252 })
15253 }
15254
15255 pub fn expand_excerpt(
15256 &mut self,
15257 excerpt: ExcerptId,
15258 direction: ExpandExcerptDirection,
15259 window: &mut Window,
15260 cx: &mut Context<Self>,
15261 ) {
15262 let current_scroll_position = self.scroll_position(cx);
15263 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15264 let mut should_scroll_up = false;
15265
15266 if direction == ExpandExcerptDirection::Down {
15267 let multi_buffer = self.buffer.read(cx);
15268 let snapshot = multi_buffer.snapshot(cx);
15269 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15270 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15271 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15272 {
15273 let buffer_snapshot = buffer.read(cx).snapshot();
15274 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15275 let last_row = buffer_snapshot.max_point().row;
15276 let lines_below = last_row.saturating_sub(excerpt_end_row);
15277 should_scroll_up = lines_below >= lines_to_expand;
15278 }
15279 }
15280
15281 self.buffer.update(cx, |buffer, cx| {
15282 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15283 });
15284
15285 if should_scroll_up {
15286 let new_scroll_position =
15287 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15288 self.set_scroll_position(new_scroll_position, window, cx);
15289 }
15290 }
15291
15292 pub fn go_to_singleton_buffer_point(
15293 &mut self,
15294 point: Point,
15295 window: &mut Window,
15296 cx: &mut Context<Self>,
15297 ) {
15298 self.go_to_singleton_buffer_range(point..point, window, cx);
15299 }
15300
15301 pub fn go_to_singleton_buffer_range(
15302 &mut self,
15303 range: Range<Point>,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 let multibuffer = self.buffer().read(cx);
15308 let Some(buffer) = multibuffer.as_singleton() else {
15309 return;
15310 };
15311 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15312 return;
15313 };
15314 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15315 return;
15316 };
15317 self.change_selections(
15318 SelectionEffects::default().nav_history(true),
15319 window,
15320 cx,
15321 |s| s.select_anchor_ranges([start..end]),
15322 );
15323 }
15324
15325 pub fn go_to_diagnostic(
15326 &mut self,
15327 action: &GoToDiagnostic,
15328 window: &mut Window,
15329 cx: &mut Context<Self>,
15330 ) {
15331 if !self.diagnostics_enabled() {
15332 return;
15333 }
15334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15335 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15336 }
15337
15338 pub fn go_to_prev_diagnostic(
15339 &mut self,
15340 action: &GoToPreviousDiagnostic,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) {
15344 if !self.diagnostics_enabled() {
15345 return;
15346 }
15347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15348 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15349 }
15350
15351 pub fn go_to_diagnostic_impl(
15352 &mut self,
15353 direction: Direction,
15354 severity: GoToDiagnosticSeverityFilter,
15355 window: &mut Window,
15356 cx: &mut Context<Self>,
15357 ) {
15358 let buffer = self.buffer.read(cx).snapshot(cx);
15359 let selection = self.selections.newest::<usize>(cx);
15360
15361 let mut active_group_id = None;
15362 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15363 && active_group.active_range.start.to_offset(&buffer) == selection.start
15364 {
15365 active_group_id = Some(active_group.group_id);
15366 }
15367
15368 fn filtered(
15369 snapshot: EditorSnapshot,
15370 severity: GoToDiagnosticSeverityFilter,
15371 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15372 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15373 diagnostics
15374 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15375 .filter(|entry| entry.range.start != entry.range.end)
15376 .filter(|entry| !entry.diagnostic.is_unnecessary)
15377 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15378 }
15379
15380 let snapshot = self.snapshot(window, cx);
15381 let before = filtered(
15382 snapshot.clone(),
15383 severity,
15384 buffer
15385 .diagnostics_in_range(0..selection.start)
15386 .filter(|entry| entry.range.start <= selection.start),
15387 );
15388 let after = filtered(
15389 snapshot,
15390 severity,
15391 buffer
15392 .diagnostics_in_range(selection.start..buffer.len())
15393 .filter(|entry| entry.range.start >= selection.start),
15394 );
15395
15396 let mut found: Option<DiagnosticEntry<usize>> = None;
15397 if direction == Direction::Prev {
15398 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15399 {
15400 for diagnostic in prev_diagnostics.into_iter().rev() {
15401 if diagnostic.range.start != selection.start
15402 || active_group_id
15403 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15404 {
15405 found = Some(diagnostic);
15406 break 'outer;
15407 }
15408 }
15409 }
15410 } else {
15411 for diagnostic in after.chain(before) {
15412 if diagnostic.range.start != selection.start
15413 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15414 {
15415 found = Some(diagnostic);
15416 break;
15417 }
15418 }
15419 }
15420 let Some(next_diagnostic) = found else {
15421 return;
15422 };
15423
15424 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15425 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15426 return;
15427 };
15428 self.change_selections(Default::default(), window, cx, |s| {
15429 s.select_ranges(vec![
15430 next_diagnostic.range.start..next_diagnostic.range.start,
15431 ])
15432 });
15433 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15434 self.refresh_edit_prediction(false, true, window, cx);
15435 }
15436
15437 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15439 let snapshot = self.snapshot(window, cx);
15440 let selection = self.selections.newest::<Point>(cx);
15441 self.go_to_hunk_before_or_after_position(
15442 &snapshot,
15443 selection.head(),
15444 Direction::Next,
15445 window,
15446 cx,
15447 );
15448 }
15449
15450 pub fn go_to_hunk_before_or_after_position(
15451 &mut self,
15452 snapshot: &EditorSnapshot,
15453 position: Point,
15454 direction: Direction,
15455 window: &mut Window,
15456 cx: &mut Context<Editor>,
15457 ) {
15458 let row = if direction == Direction::Next {
15459 self.hunk_after_position(snapshot, position)
15460 .map(|hunk| hunk.row_range.start)
15461 } else {
15462 self.hunk_before_position(snapshot, position)
15463 };
15464
15465 if let Some(row) = row {
15466 let destination = Point::new(row.0, 0);
15467 let autoscroll = Autoscroll::center();
15468
15469 self.unfold_ranges(&[destination..destination], false, false, cx);
15470 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15471 s.select_ranges([destination..destination]);
15472 });
15473 }
15474 }
15475
15476 fn hunk_after_position(
15477 &mut self,
15478 snapshot: &EditorSnapshot,
15479 position: Point,
15480 ) -> Option<MultiBufferDiffHunk> {
15481 snapshot
15482 .buffer_snapshot
15483 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15484 .find(|hunk| hunk.row_range.start.0 > position.row)
15485 .or_else(|| {
15486 snapshot
15487 .buffer_snapshot
15488 .diff_hunks_in_range(Point::zero()..position)
15489 .find(|hunk| hunk.row_range.end.0 < position.row)
15490 })
15491 }
15492
15493 fn go_to_prev_hunk(
15494 &mut self,
15495 _: &GoToPreviousHunk,
15496 window: &mut Window,
15497 cx: &mut Context<Self>,
15498 ) {
15499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15500 let snapshot = self.snapshot(window, cx);
15501 let selection = self.selections.newest::<Point>(cx);
15502 self.go_to_hunk_before_or_after_position(
15503 &snapshot,
15504 selection.head(),
15505 Direction::Prev,
15506 window,
15507 cx,
15508 );
15509 }
15510
15511 fn hunk_before_position(
15512 &mut self,
15513 snapshot: &EditorSnapshot,
15514 position: Point,
15515 ) -> Option<MultiBufferRow> {
15516 snapshot
15517 .buffer_snapshot
15518 .diff_hunk_before(position)
15519 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15520 }
15521
15522 fn go_to_next_change(
15523 &mut self,
15524 _: &GoToNextChange,
15525 window: &mut Window,
15526 cx: &mut Context<Self>,
15527 ) {
15528 if let Some(selections) = self
15529 .change_list
15530 .next_change(1, Direction::Next)
15531 .map(|s| s.to_vec())
15532 {
15533 self.change_selections(Default::default(), window, cx, |s| {
15534 let map = s.display_map();
15535 s.select_display_ranges(selections.iter().map(|a| {
15536 let point = a.to_display_point(&map);
15537 point..point
15538 }))
15539 })
15540 }
15541 }
15542
15543 fn go_to_previous_change(
15544 &mut self,
15545 _: &GoToPreviousChange,
15546 window: &mut Window,
15547 cx: &mut Context<Self>,
15548 ) {
15549 if let Some(selections) = self
15550 .change_list
15551 .next_change(1, Direction::Prev)
15552 .map(|s| s.to_vec())
15553 {
15554 self.change_selections(Default::default(), window, cx, |s| {
15555 let map = s.display_map();
15556 s.select_display_ranges(selections.iter().map(|a| {
15557 let point = a.to_display_point(&map);
15558 point..point
15559 }))
15560 })
15561 }
15562 }
15563
15564 fn go_to_line<T: 'static>(
15565 &mut self,
15566 position: Anchor,
15567 highlight_color: Option<Hsla>,
15568 window: &mut Window,
15569 cx: &mut Context<Self>,
15570 ) {
15571 let snapshot = self.snapshot(window, cx).display_snapshot;
15572 let position = position.to_point(&snapshot.buffer_snapshot);
15573 let start = snapshot
15574 .buffer_snapshot
15575 .clip_point(Point::new(position.row, 0), Bias::Left);
15576 let end = start + Point::new(1, 0);
15577 let start = snapshot.buffer_snapshot.anchor_before(start);
15578 let end = snapshot.buffer_snapshot.anchor_before(end);
15579
15580 self.highlight_rows::<T>(
15581 start..end,
15582 highlight_color
15583 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15584 Default::default(),
15585 cx,
15586 );
15587
15588 if self.buffer.read(cx).is_singleton() {
15589 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15590 }
15591 }
15592
15593 pub fn go_to_definition(
15594 &mut self,
15595 _: &GoToDefinition,
15596 window: &mut Window,
15597 cx: &mut Context<Self>,
15598 ) -> Task<Result<Navigated>> {
15599 let definition =
15600 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15601 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15602 cx.spawn_in(window, async move |editor, cx| {
15603 if definition.await? == Navigated::Yes {
15604 return Ok(Navigated::Yes);
15605 }
15606 match fallback_strategy {
15607 GoToDefinitionFallback::None => Ok(Navigated::No),
15608 GoToDefinitionFallback::FindAllReferences => {
15609 match editor.update_in(cx, |editor, window, cx| {
15610 editor.find_all_references(&FindAllReferences, window, cx)
15611 })? {
15612 Some(references) => references.await,
15613 None => Ok(Navigated::No),
15614 }
15615 }
15616 }
15617 })
15618 }
15619
15620 pub fn go_to_declaration(
15621 &mut self,
15622 _: &GoToDeclaration,
15623 window: &mut Window,
15624 cx: &mut Context<Self>,
15625 ) -> Task<Result<Navigated>> {
15626 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15627 }
15628
15629 pub fn go_to_declaration_split(
15630 &mut self,
15631 _: &GoToDeclaration,
15632 window: &mut Window,
15633 cx: &mut Context<Self>,
15634 ) -> Task<Result<Navigated>> {
15635 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15636 }
15637
15638 pub fn go_to_implementation(
15639 &mut self,
15640 _: &GoToImplementation,
15641 window: &mut Window,
15642 cx: &mut Context<Self>,
15643 ) -> Task<Result<Navigated>> {
15644 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15645 }
15646
15647 pub fn go_to_implementation_split(
15648 &mut self,
15649 _: &GoToImplementationSplit,
15650 window: &mut Window,
15651 cx: &mut Context<Self>,
15652 ) -> Task<Result<Navigated>> {
15653 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15654 }
15655
15656 pub fn go_to_type_definition(
15657 &mut self,
15658 _: &GoToTypeDefinition,
15659 window: &mut Window,
15660 cx: &mut Context<Self>,
15661 ) -> Task<Result<Navigated>> {
15662 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15663 }
15664
15665 pub fn go_to_definition_split(
15666 &mut self,
15667 _: &GoToDefinitionSplit,
15668 window: &mut Window,
15669 cx: &mut Context<Self>,
15670 ) -> Task<Result<Navigated>> {
15671 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15672 }
15673
15674 pub fn go_to_type_definition_split(
15675 &mut self,
15676 _: &GoToTypeDefinitionSplit,
15677 window: &mut Window,
15678 cx: &mut Context<Self>,
15679 ) -> Task<Result<Navigated>> {
15680 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15681 }
15682
15683 fn go_to_definition_of_kind(
15684 &mut self,
15685 kind: GotoDefinitionKind,
15686 split: bool,
15687 window: &mut Window,
15688 cx: &mut Context<Self>,
15689 ) -> Task<Result<Navigated>> {
15690 let Some(provider) = self.semantics_provider.clone() else {
15691 return Task::ready(Ok(Navigated::No));
15692 };
15693 let head = self.selections.newest::<usize>(cx).head();
15694 let buffer = self.buffer.read(cx);
15695 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15696 return Task::ready(Ok(Navigated::No));
15697 };
15698 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15699 return Task::ready(Ok(Navigated::No));
15700 };
15701
15702 cx.spawn_in(window, async move |editor, cx| {
15703 let Some(definitions) = definitions.await? else {
15704 return Ok(Navigated::No);
15705 };
15706 let navigated = editor
15707 .update_in(cx, |editor, window, cx| {
15708 editor.navigate_to_hover_links(
15709 Some(kind),
15710 definitions
15711 .into_iter()
15712 .filter(|location| {
15713 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15714 })
15715 .map(HoverLink::Text)
15716 .collect::<Vec<_>>(),
15717 split,
15718 window,
15719 cx,
15720 )
15721 })?
15722 .await?;
15723 anyhow::Ok(navigated)
15724 })
15725 }
15726
15727 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15728 let selection = self.selections.newest_anchor();
15729 let head = selection.head();
15730 let tail = selection.tail();
15731
15732 let Some((buffer, start_position)) =
15733 self.buffer.read(cx).text_anchor_for_position(head, cx)
15734 else {
15735 return;
15736 };
15737
15738 let end_position = if head != tail {
15739 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15740 return;
15741 };
15742 Some(pos)
15743 } else {
15744 None
15745 };
15746
15747 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15748 let url = if let Some(end_pos) = end_position {
15749 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15750 } else {
15751 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15752 };
15753
15754 if let Some(url) = url {
15755 editor.update(cx, |_, cx| {
15756 cx.open_url(&url);
15757 })
15758 } else {
15759 Ok(())
15760 }
15761 });
15762
15763 url_finder.detach();
15764 }
15765
15766 pub fn open_selected_filename(
15767 &mut self,
15768 _: &OpenSelectedFilename,
15769 window: &mut Window,
15770 cx: &mut Context<Self>,
15771 ) {
15772 let Some(workspace) = self.workspace() else {
15773 return;
15774 };
15775
15776 let position = self.selections.newest_anchor().head();
15777
15778 let Some((buffer, buffer_position)) =
15779 self.buffer.read(cx).text_anchor_for_position(position, cx)
15780 else {
15781 return;
15782 };
15783
15784 let project = self.project.clone();
15785
15786 cx.spawn_in(window, async move |_, cx| {
15787 let result = find_file(&buffer, project, buffer_position, cx).await;
15788
15789 if let Some((_, path)) = result {
15790 workspace
15791 .update_in(cx, |workspace, window, cx| {
15792 workspace.open_resolved_path(path, window, cx)
15793 })?
15794 .await?;
15795 }
15796 anyhow::Ok(())
15797 })
15798 .detach();
15799 }
15800
15801 pub(crate) fn navigate_to_hover_links(
15802 &mut self,
15803 kind: Option<GotoDefinitionKind>,
15804 definitions: Vec<HoverLink>,
15805 split: bool,
15806 window: &mut Window,
15807 cx: &mut Context<Editor>,
15808 ) -> Task<Result<Navigated>> {
15809 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15810 let mut first_url_or_file = None;
15811 let definitions: Vec<_> = definitions
15812 .into_iter()
15813 .filter_map(|def| match def {
15814 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15815 HoverLink::InlayHint(lsp_location, server_id) => {
15816 let computation =
15817 self.compute_target_location(lsp_location, server_id, window, cx);
15818 Some(cx.background_spawn(computation))
15819 }
15820 HoverLink::Url(url) => {
15821 first_url_or_file = Some(Either::Left(url));
15822 None
15823 }
15824 HoverLink::File(path) => {
15825 first_url_or_file = Some(Either::Right(path));
15826 None
15827 }
15828 })
15829 .collect();
15830
15831 let workspace = self.workspace();
15832
15833 cx.spawn_in(window, async move |editor, acx| {
15834 let mut locations: Vec<Location> = future::join_all(definitions)
15835 .await
15836 .into_iter()
15837 .filter_map(|location| location.transpose())
15838 .collect::<Result<_>>()
15839 .context("location tasks")?;
15840
15841 if locations.len() > 1 {
15842 let Some(workspace) = workspace else {
15843 return Ok(Navigated::No);
15844 };
15845
15846 let tab_kind = match kind {
15847 Some(GotoDefinitionKind::Implementation) => "Implementations",
15848 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15849 Some(GotoDefinitionKind::Declaration) => "Declarations",
15850 Some(GotoDefinitionKind::Type) => "Types",
15851 };
15852 let title = editor
15853 .update_in(acx, |_, _, cx| {
15854 let target = locations
15855 .iter()
15856 .map(|location| {
15857 location
15858 .buffer
15859 .read(cx)
15860 .text_for_range(location.range.clone())
15861 .collect::<String>()
15862 })
15863 .filter(|text| !text.contains('\n'))
15864 .unique()
15865 .take(3)
15866 .join(", ");
15867 if target.is_empty() {
15868 tab_kind.to_owned()
15869 } else {
15870 format!("{tab_kind} for {target}")
15871 }
15872 })
15873 .context("buffer title")?;
15874
15875 let opened = workspace
15876 .update_in(acx, |workspace, window, cx| {
15877 Self::open_locations_in_multibuffer(
15878 workspace,
15879 locations,
15880 title,
15881 split,
15882 MultibufferSelectionMode::First,
15883 window,
15884 cx,
15885 )
15886 })
15887 .is_ok();
15888
15889 anyhow::Ok(Navigated::from_bool(opened))
15890 } else if locations.is_empty() {
15891 // If there is one definition, just open it directly
15892 match first_url_or_file {
15893 Some(Either::Left(url)) => {
15894 acx.update(|_, cx| cx.open_url(&url))?;
15895 Ok(Navigated::Yes)
15896 }
15897 Some(Either::Right(path)) => {
15898 let Some(workspace) = workspace else {
15899 return Ok(Navigated::No);
15900 };
15901
15902 workspace
15903 .update_in(acx, |workspace, window, cx| {
15904 workspace.open_resolved_path(path, window, cx)
15905 })?
15906 .await?;
15907 Ok(Navigated::Yes)
15908 }
15909 None => Ok(Navigated::No),
15910 }
15911 } else {
15912 let Some(workspace) = workspace else {
15913 return Ok(Navigated::No);
15914 };
15915
15916 let target = locations.pop().unwrap();
15917 editor.update_in(acx, |editor, window, cx| {
15918 let pane = workspace.read(cx).active_pane().clone();
15919
15920 let range = target.range.to_point(target.buffer.read(cx));
15921 let range = editor.range_for_match(&range);
15922 let range = collapse_multiline_range(range);
15923
15924 if !split
15925 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15926 {
15927 editor.go_to_singleton_buffer_range(range, window, cx);
15928 } else {
15929 window.defer(cx, move |window, cx| {
15930 let target_editor: Entity<Self> =
15931 workspace.update(cx, |workspace, cx| {
15932 let pane = if split {
15933 workspace.adjacent_pane(window, cx)
15934 } else {
15935 workspace.active_pane().clone()
15936 };
15937
15938 workspace.open_project_item(
15939 pane,
15940 target.buffer.clone(),
15941 true,
15942 true,
15943 window,
15944 cx,
15945 )
15946 });
15947 target_editor.update(cx, |target_editor, cx| {
15948 // When selecting a definition in a different buffer, disable the nav history
15949 // to avoid creating a history entry at the previous cursor location.
15950 pane.update(cx, |pane, _| pane.disable_history());
15951 target_editor.go_to_singleton_buffer_range(range, window, cx);
15952 pane.update(cx, |pane, _| pane.enable_history());
15953 });
15954 });
15955 }
15956 Navigated::Yes
15957 })
15958 }
15959 })
15960 }
15961
15962 fn compute_target_location(
15963 &self,
15964 lsp_location: lsp::Location,
15965 server_id: LanguageServerId,
15966 window: &mut Window,
15967 cx: &mut Context<Self>,
15968 ) -> Task<anyhow::Result<Option<Location>>> {
15969 let Some(project) = self.project.clone() else {
15970 return Task::ready(Ok(None));
15971 };
15972
15973 cx.spawn_in(window, async move |editor, cx| {
15974 let location_task = editor.update(cx, |_, cx| {
15975 project.update(cx, |project, cx| {
15976 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
15977 })
15978 })?;
15979 let location = Some({
15980 let target_buffer_handle = location_task.await.context("open local buffer")?;
15981 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15982 let target_start = target_buffer
15983 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15984 let target_end = target_buffer
15985 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15986 target_buffer.anchor_after(target_start)
15987 ..target_buffer.anchor_before(target_end)
15988 })?;
15989 Location {
15990 buffer: target_buffer_handle,
15991 range,
15992 }
15993 });
15994 Ok(location)
15995 })
15996 }
15997
15998 pub fn find_all_references(
15999 &mut self,
16000 _: &FindAllReferences,
16001 window: &mut Window,
16002 cx: &mut Context<Self>,
16003 ) -> Option<Task<Result<Navigated>>> {
16004 let selection = self.selections.newest::<usize>(cx);
16005 let multi_buffer = self.buffer.read(cx);
16006 let head = selection.head();
16007
16008 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16009 let head_anchor = multi_buffer_snapshot.anchor_at(
16010 head,
16011 if head < selection.tail() {
16012 Bias::Right
16013 } else {
16014 Bias::Left
16015 },
16016 );
16017
16018 match self
16019 .find_all_references_task_sources
16020 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16021 {
16022 Ok(_) => {
16023 log::info!(
16024 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16025 );
16026 return None;
16027 }
16028 Err(i) => {
16029 self.find_all_references_task_sources.insert(i, head_anchor);
16030 }
16031 }
16032
16033 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16034 let workspace = self.workspace()?;
16035 let project = workspace.read(cx).project().clone();
16036 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16037 Some(cx.spawn_in(window, async move |editor, cx| {
16038 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16039 if let Ok(i) = editor
16040 .find_all_references_task_sources
16041 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16042 {
16043 editor.find_all_references_task_sources.remove(i);
16044 }
16045 });
16046
16047 let Some(locations) = references.await? else {
16048 return anyhow::Ok(Navigated::No);
16049 };
16050 if locations.is_empty() {
16051 return anyhow::Ok(Navigated::No);
16052 }
16053
16054 workspace.update_in(cx, |workspace, window, cx| {
16055 let target = locations
16056 .iter()
16057 .map(|location| {
16058 location
16059 .buffer
16060 .read(cx)
16061 .text_for_range(location.range.clone())
16062 .collect::<String>()
16063 })
16064 .filter(|text| !text.contains('\n'))
16065 .unique()
16066 .take(3)
16067 .join(", ");
16068 let title = if target.is_empty() {
16069 "References".to_owned()
16070 } else {
16071 format!("References to {target}")
16072 };
16073 Self::open_locations_in_multibuffer(
16074 workspace,
16075 locations,
16076 title,
16077 false,
16078 MultibufferSelectionMode::First,
16079 window,
16080 cx,
16081 );
16082 Navigated::Yes
16083 })
16084 }))
16085 }
16086
16087 /// Opens a multibuffer with the given project locations in it
16088 pub fn open_locations_in_multibuffer(
16089 workspace: &mut Workspace,
16090 mut locations: Vec<Location>,
16091 title: String,
16092 split: bool,
16093 multibuffer_selection_mode: MultibufferSelectionMode,
16094 window: &mut Window,
16095 cx: &mut Context<Workspace>,
16096 ) {
16097 if locations.is_empty() {
16098 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16099 return;
16100 }
16101
16102 // If there are multiple definitions, open them in a multibuffer
16103 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16104 let mut locations = locations.into_iter().peekable();
16105 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16106 let capability = workspace.project().read(cx).capability();
16107
16108 let excerpt_buffer = cx.new(|cx| {
16109 let mut multibuffer = MultiBuffer::new(capability);
16110 while let Some(location) = locations.next() {
16111 let buffer = location.buffer.read(cx);
16112 let mut ranges_for_buffer = Vec::new();
16113 let range = location.range.to_point(buffer);
16114 ranges_for_buffer.push(range.clone());
16115
16116 while let Some(next_location) = locations.peek() {
16117 if next_location.buffer == location.buffer {
16118 ranges_for_buffer.push(next_location.range.to_point(buffer));
16119 locations.next();
16120 } else {
16121 break;
16122 }
16123 }
16124
16125 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16126 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16127 PathKey::for_buffer(&location.buffer, cx),
16128 location.buffer.clone(),
16129 ranges_for_buffer,
16130 DEFAULT_MULTIBUFFER_CONTEXT,
16131 cx,
16132 );
16133 ranges.extend(new_ranges)
16134 }
16135
16136 multibuffer.with_title(title)
16137 });
16138
16139 let editor = cx.new(|cx| {
16140 Editor::for_multibuffer(
16141 excerpt_buffer,
16142 Some(workspace.project().clone()),
16143 window,
16144 cx,
16145 )
16146 });
16147 editor.update(cx, |editor, cx| {
16148 match multibuffer_selection_mode {
16149 MultibufferSelectionMode::First => {
16150 if let Some(first_range) = ranges.first() {
16151 editor.change_selections(
16152 SelectionEffects::no_scroll(),
16153 window,
16154 cx,
16155 |selections| {
16156 selections.clear_disjoint();
16157 selections
16158 .select_anchor_ranges(std::iter::once(first_range.clone()));
16159 },
16160 );
16161 }
16162 editor.highlight_background::<Self>(
16163 &ranges,
16164 |theme| theme.colors().editor_highlighted_line_background,
16165 cx,
16166 );
16167 }
16168 MultibufferSelectionMode::All => {
16169 editor.change_selections(
16170 SelectionEffects::no_scroll(),
16171 window,
16172 cx,
16173 |selections| {
16174 selections.clear_disjoint();
16175 selections.select_anchor_ranges(ranges);
16176 },
16177 );
16178 }
16179 }
16180 editor.register_buffers_with_language_servers(cx);
16181 });
16182
16183 let item = Box::new(editor);
16184 let item_id = item.item_id();
16185
16186 if split {
16187 workspace.split_item(SplitDirection::Right, item, window, cx);
16188 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16189 let (preview_item_id, preview_item_idx) =
16190 workspace.active_pane().read_with(cx, |pane, _| {
16191 (pane.preview_item_id(), pane.preview_item_idx())
16192 });
16193
16194 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16195
16196 if let Some(preview_item_id) = preview_item_id {
16197 workspace.active_pane().update(cx, |pane, cx| {
16198 pane.remove_item(preview_item_id, false, false, window, cx);
16199 });
16200 }
16201 } else {
16202 workspace.add_item_to_active_pane(item, None, true, window, cx);
16203 }
16204 workspace.active_pane().update(cx, |pane, cx| {
16205 pane.set_preview_item_id(Some(item_id), cx);
16206 });
16207 }
16208
16209 pub fn rename(
16210 &mut self,
16211 _: &Rename,
16212 window: &mut Window,
16213 cx: &mut Context<Self>,
16214 ) -> Option<Task<Result<()>>> {
16215 use language::ToOffset as _;
16216
16217 let provider = self.semantics_provider.clone()?;
16218 let selection = self.selections.newest_anchor().clone();
16219 let (cursor_buffer, cursor_buffer_position) = self
16220 .buffer
16221 .read(cx)
16222 .text_anchor_for_position(selection.head(), cx)?;
16223 let (tail_buffer, cursor_buffer_position_end) = self
16224 .buffer
16225 .read(cx)
16226 .text_anchor_for_position(selection.tail(), cx)?;
16227 if tail_buffer != cursor_buffer {
16228 return None;
16229 }
16230
16231 let snapshot = cursor_buffer.read(cx).snapshot();
16232 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16233 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16234 let prepare_rename = provider
16235 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16236 .unwrap_or_else(|| Task::ready(Ok(None)));
16237 drop(snapshot);
16238
16239 Some(cx.spawn_in(window, async move |this, cx| {
16240 let rename_range = if let Some(range) = prepare_rename.await? {
16241 Some(range)
16242 } else {
16243 this.update(cx, |this, cx| {
16244 let buffer = this.buffer.read(cx).snapshot(cx);
16245 let mut buffer_highlights = this
16246 .document_highlights_for_position(selection.head(), &buffer)
16247 .filter(|highlight| {
16248 highlight.start.excerpt_id == selection.head().excerpt_id
16249 && highlight.end.excerpt_id == selection.head().excerpt_id
16250 });
16251 buffer_highlights
16252 .next()
16253 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16254 })?
16255 };
16256 if let Some(rename_range) = rename_range {
16257 this.update_in(cx, |this, window, cx| {
16258 let snapshot = cursor_buffer.read(cx).snapshot();
16259 let rename_buffer_range = rename_range.to_offset(&snapshot);
16260 let cursor_offset_in_rename_range =
16261 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16262 let cursor_offset_in_rename_range_end =
16263 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16264
16265 this.take_rename(false, window, cx);
16266 let buffer = this.buffer.read(cx).read(cx);
16267 let cursor_offset = selection.head().to_offset(&buffer);
16268 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16269 let rename_end = rename_start + rename_buffer_range.len();
16270 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16271 let mut old_highlight_id = None;
16272 let old_name: Arc<str> = buffer
16273 .chunks(rename_start..rename_end, true)
16274 .map(|chunk| {
16275 if old_highlight_id.is_none() {
16276 old_highlight_id = chunk.syntax_highlight_id;
16277 }
16278 chunk.text
16279 })
16280 .collect::<String>()
16281 .into();
16282
16283 drop(buffer);
16284
16285 // Position the selection in the rename editor so that it matches the current selection.
16286 this.show_local_selections = false;
16287 let rename_editor = cx.new(|cx| {
16288 let mut editor = Editor::single_line(window, cx);
16289 editor.buffer.update(cx, |buffer, cx| {
16290 buffer.edit([(0..0, old_name.clone())], None, cx)
16291 });
16292 let rename_selection_range = match cursor_offset_in_rename_range
16293 .cmp(&cursor_offset_in_rename_range_end)
16294 {
16295 Ordering::Equal => {
16296 editor.select_all(&SelectAll, window, cx);
16297 return editor;
16298 }
16299 Ordering::Less => {
16300 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16301 }
16302 Ordering::Greater => {
16303 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16304 }
16305 };
16306 if rename_selection_range.end > old_name.len() {
16307 editor.select_all(&SelectAll, window, cx);
16308 } else {
16309 editor.change_selections(Default::default(), window, cx, |s| {
16310 s.select_ranges([rename_selection_range]);
16311 });
16312 }
16313 editor
16314 });
16315 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16316 if e == &EditorEvent::Focused {
16317 cx.emit(EditorEvent::FocusedIn)
16318 }
16319 })
16320 .detach();
16321
16322 let write_highlights =
16323 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16324 let read_highlights =
16325 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16326 let ranges = write_highlights
16327 .iter()
16328 .flat_map(|(_, ranges)| ranges.iter())
16329 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16330 .cloned()
16331 .collect();
16332
16333 this.highlight_text::<Rename>(
16334 ranges,
16335 HighlightStyle {
16336 fade_out: Some(0.6),
16337 ..Default::default()
16338 },
16339 cx,
16340 );
16341 let rename_focus_handle = rename_editor.focus_handle(cx);
16342 window.focus(&rename_focus_handle);
16343 let block_id = this.insert_blocks(
16344 [BlockProperties {
16345 style: BlockStyle::Flex,
16346 placement: BlockPlacement::Below(range.start),
16347 height: Some(1),
16348 render: Arc::new({
16349 let rename_editor = rename_editor.clone();
16350 move |cx: &mut BlockContext| {
16351 let mut text_style = cx.editor_style.text.clone();
16352 if let Some(highlight_style) = old_highlight_id
16353 .and_then(|h| h.style(&cx.editor_style.syntax))
16354 {
16355 text_style = text_style.highlight(highlight_style);
16356 }
16357 div()
16358 .block_mouse_except_scroll()
16359 .pl(cx.anchor_x)
16360 .child(EditorElement::new(
16361 &rename_editor,
16362 EditorStyle {
16363 background: cx.theme().system().transparent,
16364 local_player: cx.editor_style.local_player,
16365 text: text_style,
16366 scrollbar_width: cx.editor_style.scrollbar_width,
16367 syntax: cx.editor_style.syntax.clone(),
16368 status: cx.editor_style.status.clone(),
16369 inlay_hints_style: HighlightStyle {
16370 font_weight: Some(FontWeight::BOLD),
16371 ..make_inlay_hints_style(cx.app)
16372 },
16373 edit_prediction_styles: make_suggestion_styles(
16374 cx.app,
16375 ),
16376 ..EditorStyle::default()
16377 },
16378 ))
16379 .into_any_element()
16380 }
16381 }),
16382 priority: 0,
16383 }],
16384 Some(Autoscroll::fit()),
16385 cx,
16386 )[0];
16387 this.pending_rename = Some(RenameState {
16388 range,
16389 old_name,
16390 editor: rename_editor,
16391 block_id,
16392 });
16393 })?;
16394 }
16395
16396 Ok(())
16397 }))
16398 }
16399
16400 pub fn confirm_rename(
16401 &mut self,
16402 _: &ConfirmRename,
16403 window: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) -> Option<Task<Result<()>>> {
16406 let rename = self.take_rename(false, window, cx)?;
16407 let workspace = self.workspace()?.downgrade();
16408 let (buffer, start) = self
16409 .buffer
16410 .read(cx)
16411 .text_anchor_for_position(rename.range.start, cx)?;
16412 let (end_buffer, _) = self
16413 .buffer
16414 .read(cx)
16415 .text_anchor_for_position(rename.range.end, cx)?;
16416 if buffer != end_buffer {
16417 return None;
16418 }
16419
16420 let old_name = rename.old_name;
16421 let new_name = rename.editor.read(cx).text(cx);
16422
16423 let rename = self.semantics_provider.as_ref()?.perform_rename(
16424 &buffer,
16425 start,
16426 new_name.clone(),
16427 cx,
16428 )?;
16429
16430 Some(cx.spawn_in(window, async move |editor, cx| {
16431 let project_transaction = rename.await?;
16432 Self::open_project_transaction(
16433 &editor,
16434 workspace,
16435 project_transaction,
16436 format!("Rename: {} → {}", old_name, new_name),
16437 cx,
16438 )
16439 .await?;
16440
16441 editor.update(cx, |editor, cx| {
16442 editor.refresh_document_highlights(cx);
16443 })?;
16444 Ok(())
16445 }))
16446 }
16447
16448 fn take_rename(
16449 &mut self,
16450 moving_cursor: bool,
16451 window: &mut Window,
16452 cx: &mut Context<Self>,
16453 ) -> Option<RenameState> {
16454 let rename = self.pending_rename.take()?;
16455 if rename.editor.focus_handle(cx).is_focused(window) {
16456 window.focus(&self.focus_handle);
16457 }
16458
16459 self.remove_blocks(
16460 [rename.block_id].into_iter().collect(),
16461 Some(Autoscroll::fit()),
16462 cx,
16463 );
16464 self.clear_highlights::<Rename>(cx);
16465 self.show_local_selections = true;
16466
16467 if moving_cursor {
16468 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16469 editor.selections.newest::<usize>(cx).head()
16470 });
16471
16472 // Update the selection to match the position of the selection inside
16473 // the rename editor.
16474 let snapshot = self.buffer.read(cx).read(cx);
16475 let rename_range = rename.range.to_offset(&snapshot);
16476 let cursor_in_editor = snapshot
16477 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16478 .min(rename_range.end);
16479 drop(snapshot);
16480
16481 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16482 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16483 });
16484 } else {
16485 self.refresh_document_highlights(cx);
16486 }
16487
16488 Some(rename)
16489 }
16490
16491 pub fn pending_rename(&self) -> Option<&RenameState> {
16492 self.pending_rename.as_ref()
16493 }
16494
16495 fn format(
16496 &mut self,
16497 _: &Format,
16498 window: &mut Window,
16499 cx: &mut Context<Self>,
16500 ) -> Option<Task<Result<()>>> {
16501 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16502
16503 let project = match &self.project {
16504 Some(project) => project.clone(),
16505 None => return None,
16506 };
16507
16508 Some(self.perform_format(
16509 project,
16510 FormatTrigger::Manual,
16511 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16512 window,
16513 cx,
16514 ))
16515 }
16516
16517 fn format_selections(
16518 &mut self,
16519 _: &FormatSelections,
16520 window: &mut Window,
16521 cx: &mut Context<Self>,
16522 ) -> Option<Task<Result<()>>> {
16523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16524
16525 let project = match &self.project {
16526 Some(project) => project.clone(),
16527 None => return None,
16528 };
16529
16530 let ranges = self
16531 .selections
16532 .all_adjusted(cx)
16533 .into_iter()
16534 .map(|selection| selection.range())
16535 .collect_vec();
16536
16537 Some(self.perform_format(
16538 project,
16539 FormatTrigger::Manual,
16540 FormatTarget::Ranges(ranges),
16541 window,
16542 cx,
16543 ))
16544 }
16545
16546 fn perform_format(
16547 &mut self,
16548 project: Entity<Project>,
16549 trigger: FormatTrigger,
16550 target: FormatTarget,
16551 window: &mut Window,
16552 cx: &mut Context<Self>,
16553 ) -> Task<Result<()>> {
16554 let buffer = self.buffer.clone();
16555 let (buffers, target) = match target {
16556 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16557 FormatTarget::Ranges(selection_ranges) => {
16558 let multi_buffer = buffer.read(cx);
16559 let snapshot = multi_buffer.read(cx);
16560 let mut buffers = HashSet::default();
16561 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16562 BTreeMap::new();
16563 for selection_range in selection_ranges {
16564 for (buffer, buffer_range, _) in
16565 snapshot.range_to_buffer_ranges(selection_range)
16566 {
16567 let buffer_id = buffer.remote_id();
16568 let start = buffer.anchor_before(buffer_range.start);
16569 let end = buffer.anchor_after(buffer_range.end);
16570 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16571 buffer_id_to_ranges
16572 .entry(buffer_id)
16573 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16574 .or_insert_with(|| vec![start..end]);
16575 }
16576 }
16577 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16578 }
16579 };
16580
16581 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16582 let selections_prev = transaction_id_prev
16583 .and_then(|transaction_id_prev| {
16584 // default to selections as they were after the last edit, if we have them,
16585 // instead of how they are now.
16586 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16587 // will take you back to where you made the last edit, instead of staying where you scrolled
16588 self.selection_history
16589 .transaction(transaction_id_prev)
16590 .map(|t| t.0.clone())
16591 })
16592 .unwrap_or_else(|| self.selections.disjoint_anchors());
16593
16594 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16595 let format = project.update(cx, |project, cx| {
16596 project.format(buffers, target, true, trigger, cx)
16597 });
16598
16599 cx.spawn_in(window, async move |editor, cx| {
16600 let transaction = futures::select_biased! {
16601 transaction = format.log_err().fuse() => transaction,
16602 () = timeout => {
16603 log::warn!("timed out waiting for formatting");
16604 None
16605 }
16606 };
16607
16608 buffer
16609 .update(cx, |buffer, cx| {
16610 if let Some(transaction) = transaction
16611 && !buffer.is_singleton()
16612 {
16613 buffer.push_transaction(&transaction.0, cx);
16614 }
16615 cx.notify();
16616 })
16617 .ok();
16618
16619 if let Some(transaction_id_now) =
16620 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16621 {
16622 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16623 if has_new_transaction {
16624 _ = editor.update(cx, |editor, _| {
16625 editor
16626 .selection_history
16627 .insert_transaction(transaction_id_now, selections_prev);
16628 });
16629 }
16630 }
16631
16632 Ok(())
16633 })
16634 }
16635
16636 fn organize_imports(
16637 &mut self,
16638 _: &OrganizeImports,
16639 window: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) -> Option<Task<Result<()>>> {
16642 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16643 let project = match &self.project {
16644 Some(project) => project.clone(),
16645 None => return None,
16646 };
16647 Some(self.perform_code_action_kind(
16648 project,
16649 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16650 window,
16651 cx,
16652 ))
16653 }
16654
16655 fn perform_code_action_kind(
16656 &mut self,
16657 project: Entity<Project>,
16658 kind: CodeActionKind,
16659 window: &mut Window,
16660 cx: &mut Context<Self>,
16661 ) -> Task<Result<()>> {
16662 let buffer = self.buffer.clone();
16663 let buffers = buffer.read(cx).all_buffers();
16664 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16665 let apply_action = project.update(cx, |project, cx| {
16666 project.apply_code_action_kind(buffers, kind, true, cx)
16667 });
16668 cx.spawn_in(window, async move |_, cx| {
16669 let transaction = futures::select_biased! {
16670 () = timeout => {
16671 log::warn!("timed out waiting for executing code action");
16672 None
16673 }
16674 transaction = apply_action.log_err().fuse() => transaction,
16675 };
16676 buffer
16677 .update(cx, |buffer, cx| {
16678 // check if we need this
16679 if let Some(transaction) = transaction
16680 && !buffer.is_singleton()
16681 {
16682 buffer.push_transaction(&transaction.0, cx);
16683 }
16684 cx.notify();
16685 })
16686 .ok();
16687 Ok(())
16688 })
16689 }
16690
16691 pub fn restart_language_server(
16692 &mut self,
16693 _: &RestartLanguageServer,
16694 _: &mut Window,
16695 cx: &mut Context<Self>,
16696 ) {
16697 if let Some(project) = self.project.clone() {
16698 self.buffer.update(cx, |multi_buffer, cx| {
16699 project.update(cx, |project, cx| {
16700 project.restart_language_servers_for_buffers(
16701 multi_buffer.all_buffers().into_iter().collect(),
16702 HashSet::default(),
16703 cx,
16704 );
16705 });
16706 })
16707 }
16708 }
16709
16710 pub fn stop_language_server(
16711 &mut self,
16712 _: &StopLanguageServer,
16713 _: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) {
16716 if let Some(project) = self.project.clone() {
16717 self.buffer.update(cx, |multi_buffer, cx| {
16718 project.update(cx, |project, cx| {
16719 project.stop_language_servers_for_buffers(
16720 multi_buffer.all_buffers().into_iter().collect(),
16721 HashSet::default(),
16722 cx,
16723 );
16724 cx.emit(project::Event::RefreshInlayHints);
16725 });
16726 });
16727 }
16728 }
16729
16730 fn cancel_language_server_work(
16731 workspace: &mut Workspace,
16732 _: &actions::CancelLanguageServerWork,
16733 _: &mut Window,
16734 cx: &mut Context<Workspace>,
16735 ) {
16736 let project = workspace.project();
16737 let buffers = workspace
16738 .active_item(cx)
16739 .and_then(|item| item.act_as::<Editor>(cx))
16740 .map_or(HashSet::default(), |editor| {
16741 editor.read(cx).buffer.read(cx).all_buffers()
16742 });
16743 project.update(cx, |project, cx| {
16744 project.cancel_language_server_work_for_buffers(buffers, cx);
16745 });
16746 }
16747
16748 fn show_character_palette(
16749 &mut self,
16750 _: &ShowCharacterPalette,
16751 window: &mut Window,
16752 _: &mut Context<Self>,
16753 ) {
16754 window.show_character_palette();
16755 }
16756
16757 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16758 if !self.diagnostics_enabled() {
16759 return;
16760 }
16761
16762 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16763 let buffer = self.buffer.read(cx).snapshot(cx);
16764 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16765 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16766 let is_valid = buffer
16767 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16768 .any(|entry| {
16769 entry.diagnostic.is_primary
16770 && !entry.range.is_empty()
16771 && entry.range.start == primary_range_start
16772 && entry.diagnostic.message == active_diagnostics.active_message
16773 });
16774
16775 if !is_valid {
16776 self.dismiss_diagnostics(cx);
16777 }
16778 }
16779 }
16780
16781 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16782 match &self.active_diagnostics {
16783 ActiveDiagnostic::Group(group) => Some(group),
16784 _ => None,
16785 }
16786 }
16787
16788 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16789 if !self.diagnostics_enabled() {
16790 return;
16791 }
16792 self.dismiss_diagnostics(cx);
16793 self.active_diagnostics = ActiveDiagnostic::All;
16794 }
16795
16796 fn activate_diagnostics(
16797 &mut self,
16798 buffer_id: BufferId,
16799 diagnostic: DiagnosticEntry<usize>,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16804 return;
16805 }
16806 self.dismiss_diagnostics(cx);
16807 let snapshot = self.snapshot(window, cx);
16808 let buffer = self.buffer.read(cx).snapshot(cx);
16809 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16810 return;
16811 };
16812
16813 let diagnostic_group = buffer
16814 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16815 .collect::<Vec<_>>();
16816
16817 let blocks =
16818 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16819
16820 let blocks = self.display_map.update(cx, |display_map, cx| {
16821 display_map.insert_blocks(blocks, cx).into_iter().collect()
16822 });
16823 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16824 active_range: buffer.anchor_before(diagnostic.range.start)
16825 ..buffer.anchor_after(diagnostic.range.end),
16826 active_message: diagnostic.diagnostic.message.clone(),
16827 group_id: diagnostic.diagnostic.group_id,
16828 blocks,
16829 });
16830 cx.notify();
16831 }
16832
16833 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16834 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16835 return;
16836 };
16837
16838 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16839 if let ActiveDiagnostic::Group(group) = prev {
16840 self.display_map.update(cx, |display_map, cx| {
16841 display_map.remove_blocks(group.blocks, cx);
16842 });
16843 cx.notify();
16844 }
16845 }
16846
16847 /// Disable inline diagnostics rendering for this editor.
16848 pub fn disable_inline_diagnostics(&mut self) {
16849 self.inline_diagnostics_enabled = false;
16850 self.inline_diagnostics_update = Task::ready(());
16851 self.inline_diagnostics.clear();
16852 }
16853
16854 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16855 self.diagnostics_enabled = false;
16856 self.dismiss_diagnostics(cx);
16857 self.inline_diagnostics_update = Task::ready(());
16858 self.inline_diagnostics.clear();
16859 }
16860
16861 pub fn diagnostics_enabled(&self) -> bool {
16862 self.diagnostics_enabled && self.mode.is_full()
16863 }
16864
16865 pub fn inline_diagnostics_enabled(&self) -> bool {
16866 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16867 }
16868
16869 pub fn show_inline_diagnostics(&self) -> bool {
16870 self.show_inline_diagnostics
16871 }
16872
16873 pub fn toggle_inline_diagnostics(
16874 &mut self,
16875 _: &ToggleInlineDiagnostics,
16876 window: &mut Window,
16877 cx: &mut Context<Editor>,
16878 ) {
16879 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16880 self.refresh_inline_diagnostics(false, window, cx);
16881 }
16882
16883 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16884 self.diagnostics_max_severity = severity;
16885 self.display_map.update(cx, |display_map, _| {
16886 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16887 });
16888 }
16889
16890 pub fn toggle_diagnostics(
16891 &mut self,
16892 _: &ToggleDiagnostics,
16893 window: &mut Window,
16894 cx: &mut Context<Editor>,
16895 ) {
16896 if !self.diagnostics_enabled() {
16897 return;
16898 }
16899
16900 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16901 EditorSettings::get_global(cx)
16902 .diagnostics_max_severity
16903 .filter(|severity| severity != &DiagnosticSeverity::Off)
16904 .unwrap_or(DiagnosticSeverity::Hint)
16905 } else {
16906 DiagnosticSeverity::Off
16907 };
16908 self.set_max_diagnostics_severity(new_severity, cx);
16909 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16910 self.active_diagnostics = ActiveDiagnostic::None;
16911 self.inline_diagnostics_update = Task::ready(());
16912 self.inline_diagnostics.clear();
16913 } else {
16914 self.refresh_inline_diagnostics(false, window, cx);
16915 }
16916
16917 cx.notify();
16918 }
16919
16920 pub fn toggle_minimap(
16921 &mut self,
16922 _: &ToggleMinimap,
16923 window: &mut Window,
16924 cx: &mut Context<Editor>,
16925 ) {
16926 if self.supports_minimap(cx) {
16927 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16928 }
16929 }
16930
16931 fn refresh_inline_diagnostics(
16932 &mut self,
16933 debounce: bool,
16934 window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) {
16937 let max_severity = ProjectSettings::get_global(cx)
16938 .diagnostics
16939 .inline
16940 .max_severity
16941 .unwrap_or(self.diagnostics_max_severity);
16942
16943 if !self.inline_diagnostics_enabled()
16944 || !self.show_inline_diagnostics
16945 || max_severity == DiagnosticSeverity::Off
16946 {
16947 self.inline_diagnostics_update = Task::ready(());
16948 self.inline_diagnostics.clear();
16949 return;
16950 }
16951
16952 let debounce_ms = ProjectSettings::get_global(cx)
16953 .diagnostics
16954 .inline
16955 .update_debounce_ms;
16956 let debounce = if debounce && debounce_ms > 0 {
16957 Some(Duration::from_millis(debounce_ms))
16958 } else {
16959 None
16960 };
16961 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16962 if let Some(debounce) = debounce {
16963 cx.background_executor().timer(debounce).await;
16964 }
16965 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16966 editor
16967 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16968 .ok()
16969 }) else {
16970 return;
16971 };
16972
16973 let new_inline_diagnostics = cx
16974 .background_spawn(async move {
16975 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16976 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16977 let message = diagnostic_entry
16978 .diagnostic
16979 .message
16980 .split_once('\n')
16981 .map(|(line, _)| line)
16982 .map(SharedString::new)
16983 .unwrap_or_else(|| {
16984 SharedString::from(diagnostic_entry.diagnostic.message)
16985 });
16986 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16987 let (Ok(i) | Err(i)) = inline_diagnostics
16988 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16989 inline_diagnostics.insert(
16990 i,
16991 (
16992 start_anchor,
16993 InlineDiagnostic {
16994 message,
16995 group_id: diagnostic_entry.diagnostic.group_id,
16996 start: diagnostic_entry.range.start.to_point(&snapshot),
16997 is_primary: diagnostic_entry.diagnostic.is_primary,
16998 severity: diagnostic_entry.diagnostic.severity,
16999 },
17000 ),
17001 );
17002 }
17003 inline_diagnostics
17004 })
17005 .await;
17006
17007 editor
17008 .update(cx, |editor, cx| {
17009 editor.inline_diagnostics = new_inline_diagnostics;
17010 cx.notify();
17011 })
17012 .ok();
17013 });
17014 }
17015
17016 fn pull_diagnostics(
17017 &mut self,
17018 buffer_id: Option<BufferId>,
17019 window: &Window,
17020 cx: &mut Context<Self>,
17021 ) -> Option<()> {
17022 if !self.mode().is_full() {
17023 return None;
17024 }
17025 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17026 .diagnostics
17027 .lsp_pull_diagnostics;
17028 if !pull_diagnostics_settings.enabled {
17029 return None;
17030 }
17031 let project = self.project()?.downgrade();
17032 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17033 let mut buffers = self.buffer.read(cx).all_buffers();
17034 if let Some(buffer_id) = buffer_id {
17035 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17036 }
17037
17038 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17039 cx.background_executor().timer(debounce).await;
17040
17041 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17042 buffers
17043 .into_iter()
17044 .filter_map(|buffer| {
17045 project
17046 .update(cx, |project, cx| {
17047 project.lsp_store().update(cx, |lsp_store, cx| {
17048 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17049 })
17050 })
17051 .ok()
17052 })
17053 .collect::<FuturesUnordered<_>>()
17054 }) else {
17055 return;
17056 };
17057
17058 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17059 match pull_task {
17060 Ok(()) => {
17061 if editor
17062 .update_in(cx, |editor, window, cx| {
17063 editor.update_diagnostics_state(window, cx);
17064 })
17065 .is_err()
17066 {
17067 return;
17068 }
17069 }
17070 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17071 }
17072 }
17073 });
17074
17075 Some(())
17076 }
17077
17078 pub fn set_selections_from_remote(
17079 &mut self,
17080 selections: Vec<Selection<Anchor>>,
17081 pending_selection: Option<Selection<Anchor>>,
17082 window: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) {
17085 let old_cursor_position = self.selections.newest_anchor().head();
17086 self.selections.change_with(cx, |s| {
17087 s.select_anchors(selections);
17088 if let Some(pending_selection) = pending_selection {
17089 s.set_pending(pending_selection, SelectMode::Character);
17090 } else {
17091 s.clear_pending();
17092 }
17093 });
17094 self.selections_did_change(
17095 false,
17096 &old_cursor_position,
17097 SelectionEffects::default(),
17098 window,
17099 cx,
17100 );
17101 }
17102
17103 pub fn transact(
17104 &mut self,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17108 ) -> Option<TransactionId> {
17109 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17110 this.start_transaction_at(Instant::now(), window, cx);
17111 update(this, window, cx);
17112 this.end_transaction_at(Instant::now(), cx)
17113 })
17114 }
17115
17116 pub fn start_transaction_at(
17117 &mut self,
17118 now: Instant,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) -> Option<TransactionId> {
17122 self.end_selection(window, cx);
17123 if let Some(tx_id) = self
17124 .buffer
17125 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17126 {
17127 self.selection_history
17128 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17129 cx.emit(EditorEvent::TransactionBegun {
17130 transaction_id: tx_id,
17131 });
17132 Some(tx_id)
17133 } else {
17134 None
17135 }
17136 }
17137
17138 pub fn end_transaction_at(
17139 &mut self,
17140 now: Instant,
17141 cx: &mut Context<Self>,
17142 ) -> Option<TransactionId> {
17143 if let Some(transaction_id) = self
17144 .buffer
17145 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17146 {
17147 if let Some((_, end_selections)) =
17148 self.selection_history.transaction_mut(transaction_id)
17149 {
17150 *end_selections = Some(self.selections.disjoint_anchors());
17151 } else {
17152 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17153 }
17154
17155 cx.emit(EditorEvent::Edited { transaction_id });
17156 Some(transaction_id)
17157 } else {
17158 None
17159 }
17160 }
17161
17162 pub fn modify_transaction_selection_history(
17163 &mut self,
17164 transaction_id: TransactionId,
17165 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17166 ) -> bool {
17167 self.selection_history
17168 .transaction_mut(transaction_id)
17169 .map(modify)
17170 .is_some()
17171 }
17172
17173 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17174 if self.selection_mark_mode {
17175 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17176 s.move_with(|_, sel| {
17177 sel.collapse_to(sel.head(), SelectionGoal::None);
17178 });
17179 })
17180 }
17181 self.selection_mark_mode = true;
17182 cx.notify();
17183 }
17184
17185 pub fn swap_selection_ends(
17186 &mut self,
17187 _: &actions::SwapSelectionEnds,
17188 window: &mut Window,
17189 cx: &mut Context<Self>,
17190 ) {
17191 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17192 s.move_with(|_, sel| {
17193 if sel.start != sel.end {
17194 sel.reversed = !sel.reversed
17195 }
17196 });
17197 });
17198 self.request_autoscroll(Autoscroll::newest(), cx);
17199 cx.notify();
17200 }
17201
17202 pub fn toggle_focus(
17203 workspace: &mut Workspace,
17204 _: &actions::ToggleFocus,
17205 window: &mut Window,
17206 cx: &mut Context<Workspace>,
17207 ) {
17208 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17209 return;
17210 };
17211 workspace.activate_item(&item, true, true, window, cx);
17212 }
17213
17214 pub fn toggle_fold(
17215 &mut self,
17216 _: &actions::ToggleFold,
17217 window: &mut Window,
17218 cx: &mut Context<Self>,
17219 ) {
17220 if self.is_singleton(cx) {
17221 let selection = self.selections.newest::<Point>(cx);
17222
17223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17224 let range = if selection.is_empty() {
17225 let point = selection.head().to_display_point(&display_map);
17226 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17227 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17228 .to_point(&display_map);
17229 start..end
17230 } else {
17231 selection.range()
17232 };
17233 if display_map.folds_in_range(range).next().is_some() {
17234 self.unfold_lines(&Default::default(), window, cx)
17235 } else {
17236 self.fold(&Default::default(), window, cx)
17237 }
17238 } else {
17239 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17240 let buffer_ids: HashSet<_> = self
17241 .selections
17242 .disjoint_anchor_ranges()
17243 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17244 .collect();
17245
17246 let should_unfold = buffer_ids
17247 .iter()
17248 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17249
17250 for buffer_id in buffer_ids {
17251 if should_unfold {
17252 self.unfold_buffer(buffer_id, cx);
17253 } else {
17254 self.fold_buffer(buffer_id, cx);
17255 }
17256 }
17257 }
17258 }
17259
17260 pub fn toggle_fold_recursive(
17261 &mut self,
17262 _: &actions::ToggleFoldRecursive,
17263 window: &mut Window,
17264 cx: &mut Context<Self>,
17265 ) {
17266 let selection = self.selections.newest::<Point>(cx);
17267
17268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17269 let range = if selection.is_empty() {
17270 let point = selection.head().to_display_point(&display_map);
17271 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17272 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17273 .to_point(&display_map);
17274 start..end
17275 } else {
17276 selection.range()
17277 };
17278 if display_map.folds_in_range(range).next().is_some() {
17279 self.unfold_recursive(&Default::default(), window, cx)
17280 } else {
17281 self.fold_recursive(&Default::default(), window, cx)
17282 }
17283 }
17284
17285 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17286 if self.is_singleton(cx) {
17287 let mut to_fold = Vec::new();
17288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17289 let selections = self.selections.all_adjusted(cx);
17290
17291 for selection in selections {
17292 let range = selection.range().sorted();
17293 let buffer_start_row = range.start.row;
17294
17295 if range.start.row != range.end.row {
17296 let mut found = false;
17297 let mut row = range.start.row;
17298 while row <= range.end.row {
17299 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17300 {
17301 found = true;
17302 row = crease.range().end.row + 1;
17303 to_fold.push(crease);
17304 } else {
17305 row += 1
17306 }
17307 }
17308 if found {
17309 continue;
17310 }
17311 }
17312
17313 for row in (0..=range.start.row).rev() {
17314 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17315 && crease.range().end.row >= buffer_start_row
17316 {
17317 to_fold.push(crease);
17318 if row <= range.start.row {
17319 break;
17320 }
17321 }
17322 }
17323 }
17324
17325 self.fold_creases(to_fold, true, window, cx);
17326 } else {
17327 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17328 let buffer_ids = self
17329 .selections
17330 .disjoint_anchor_ranges()
17331 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17332 .collect::<HashSet<_>>();
17333 for buffer_id in buffer_ids {
17334 self.fold_buffer(buffer_id, cx);
17335 }
17336 }
17337 }
17338
17339 pub fn toggle_fold_all(
17340 &mut self,
17341 _: &actions::ToggleFoldAll,
17342 window: &mut Window,
17343 cx: &mut Context<Self>,
17344 ) {
17345 if self.buffer.read(cx).is_singleton() {
17346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17347 let has_folds = display_map
17348 .folds_in_range(0..display_map.buffer_snapshot.len())
17349 .next()
17350 .is_some();
17351
17352 if has_folds {
17353 self.unfold_all(&actions::UnfoldAll, window, cx);
17354 } else {
17355 self.fold_all(&actions::FoldAll, window, cx);
17356 }
17357 } else {
17358 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17359 let should_unfold = buffer_ids
17360 .iter()
17361 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17362
17363 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17364 editor
17365 .update_in(cx, |editor, _, cx| {
17366 for buffer_id in buffer_ids {
17367 if should_unfold {
17368 editor.unfold_buffer(buffer_id, cx);
17369 } else {
17370 editor.fold_buffer(buffer_id, cx);
17371 }
17372 }
17373 })
17374 .ok();
17375 });
17376 }
17377 }
17378
17379 fn fold_at_level(
17380 &mut self,
17381 fold_at: &FoldAtLevel,
17382 window: &mut Window,
17383 cx: &mut Context<Self>,
17384 ) {
17385 if !self.buffer.read(cx).is_singleton() {
17386 return;
17387 }
17388
17389 let fold_at_level = fold_at.0;
17390 let snapshot = self.buffer.read(cx).snapshot(cx);
17391 let mut to_fold = Vec::new();
17392 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17393
17394 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17395 while start_row < end_row {
17396 match self
17397 .snapshot(window, cx)
17398 .crease_for_buffer_row(MultiBufferRow(start_row))
17399 {
17400 Some(crease) => {
17401 let nested_start_row = crease.range().start.row + 1;
17402 let nested_end_row = crease.range().end.row;
17403
17404 if current_level < fold_at_level {
17405 stack.push((nested_start_row, nested_end_row, current_level + 1));
17406 } else if current_level == fold_at_level {
17407 to_fold.push(crease);
17408 }
17409
17410 start_row = nested_end_row + 1;
17411 }
17412 None => start_row += 1,
17413 }
17414 }
17415 }
17416
17417 self.fold_creases(to_fold, true, window, cx);
17418 }
17419
17420 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17421 if self.buffer.read(cx).is_singleton() {
17422 let mut fold_ranges = Vec::new();
17423 let snapshot = self.buffer.read(cx).snapshot(cx);
17424
17425 for row in 0..snapshot.max_row().0 {
17426 if let Some(foldable_range) = self
17427 .snapshot(window, cx)
17428 .crease_for_buffer_row(MultiBufferRow(row))
17429 {
17430 fold_ranges.push(foldable_range);
17431 }
17432 }
17433
17434 self.fold_creases(fold_ranges, true, window, cx);
17435 } else {
17436 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17437 editor
17438 .update_in(cx, |editor, _, cx| {
17439 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17440 editor.fold_buffer(buffer_id, cx);
17441 }
17442 })
17443 .ok();
17444 });
17445 }
17446 }
17447
17448 pub fn fold_function_bodies(
17449 &mut self,
17450 _: &actions::FoldFunctionBodies,
17451 window: &mut Window,
17452 cx: &mut Context<Self>,
17453 ) {
17454 let snapshot = self.buffer.read(cx).snapshot(cx);
17455
17456 let ranges = snapshot
17457 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17458 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17459 .collect::<Vec<_>>();
17460
17461 let creases = ranges
17462 .into_iter()
17463 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17464 .collect();
17465
17466 self.fold_creases(creases, true, window, cx);
17467 }
17468
17469 pub fn fold_recursive(
17470 &mut self,
17471 _: &actions::FoldRecursive,
17472 window: &mut Window,
17473 cx: &mut Context<Self>,
17474 ) {
17475 let mut to_fold = Vec::new();
17476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17477 let selections = self.selections.all_adjusted(cx);
17478
17479 for selection in selections {
17480 let range = selection.range().sorted();
17481 let buffer_start_row = range.start.row;
17482
17483 if range.start.row != range.end.row {
17484 let mut found = false;
17485 for row in range.start.row..=range.end.row {
17486 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17487 found = true;
17488 to_fold.push(crease);
17489 }
17490 }
17491 if found {
17492 continue;
17493 }
17494 }
17495
17496 for row in (0..=range.start.row).rev() {
17497 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17498 if crease.range().end.row >= buffer_start_row {
17499 to_fold.push(crease);
17500 } else {
17501 break;
17502 }
17503 }
17504 }
17505 }
17506
17507 self.fold_creases(to_fold, true, window, cx);
17508 }
17509
17510 pub fn fold_at(
17511 &mut self,
17512 buffer_row: MultiBufferRow,
17513 window: &mut Window,
17514 cx: &mut Context<Self>,
17515 ) {
17516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17517
17518 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17519 let autoscroll = self
17520 .selections
17521 .all::<Point>(cx)
17522 .iter()
17523 .any(|selection| crease.range().overlaps(&selection.range()));
17524
17525 self.fold_creases(vec![crease], autoscroll, window, cx);
17526 }
17527 }
17528
17529 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17530 if self.is_singleton(cx) {
17531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17532 let buffer = &display_map.buffer_snapshot;
17533 let selections = self.selections.all::<Point>(cx);
17534 let ranges = selections
17535 .iter()
17536 .map(|s| {
17537 let range = s.display_range(&display_map).sorted();
17538 let mut start = range.start.to_point(&display_map);
17539 let mut end = range.end.to_point(&display_map);
17540 start.column = 0;
17541 end.column = buffer.line_len(MultiBufferRow(end.row));
17542 start..end
17543 })
17544 .collect::<Vec<_>>();
17545
17546 self.unfold_ranges(&ranges, true, true, cx);
17547 } else {
17548 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17549 let buffer_ids = self
17550 .selections
17551 .disjoint_anchor_ranges()
17552 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17553 .collect::<HashSet<_>>();
17554 for buffer_id in buffer_ids {
17555 self.unfold_buffer(buffer_id, cx);
17556 }
17557 }
17558 }
17559
17560 pub fn unfold_recursive(
17561 &mut self,
17562 _: &UnfoldRecursive,
17563 _window: &mut Window,
17564 cx: &mut Context<Self>,
17565 ) {
17566 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17567 let selections = self.selections.all::<Point>(cx);
17568 let ranges = selections
17569 .iter()
17570 .map(|s| {
17571 let mut range = s.display_range(&display_map).sorted();
17572 *range.start.column_mut() = 0;
17573 *range.end.column_mut() = display_map.line_len(range.end.row());
17574 let start = range.start.to_point(&display_map);
17575 let end = range.end.to_point(&display_map);
17576 start..end
17577 })
17578 .collect::<Vec<_>>();
17579
17580 self.unfold_ranges(&ranges, true, true, cx);
17581 }
17582
17583 pub fn unfold_at(
17584 &mut self,
17585 buffer_row: MultiBufferRow,
17586 _window: &mut Window,
17587 cx: &mut Context<Self>,
17588 ) {
17589 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17590
17591 let intersection_range = Point::new(buffer_row.0, 0)
17592 ..Point::new(
17593 buffer_row.0,
17594 display_map.buffer_snapshot.line_len(buffer_row),
17595 );
17596
17597 let autoscroll = self
17598 .selections
17599 .all::<Point>(cx)
17600 .iter()
17601 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17602
17603 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17604 }
17605
17606 pub fn unfold_all(
17607 &mut self,
17608 _: &actions::UnfoldAll,
17609 _window: &mut Window,
17610 cx: &mut Context<Self>,
17611 ) {
17612 if self.buffer.read(cx).is_singleton() {
17613 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17614 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17615 } else {
17616 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17617 editor
17618 .update(cx, |editor, cx| {
17619 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17620 editor.unfold_buffer(buffer_id, cx);
17621 }
17622 })
17623 .ok();
17624 });
17625 }
17626 }
17627
17628 pub fn fold_selected_ranges(
17629 &mut self,
17630 _: &FoldSelectedRanges,
17631 window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 let selections = self.selections.all_adjusted(cx);
17635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17636 let ranges = selections
17637 .into_iter()
17638 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17639 .collect::<Vec<_>>();
17640 self.fold_creases(ranges, true, window, cx);
17641 }
17642
17643 pub fn fold_ranges<T: ToOffset + Clone>(
17644 &mut self,
17645 ranges: Vec<Range<T>>,
17646 auto_scroll: bool,
17647 window: &mut Window,
17648 cx: &mut Context<Self>,
17649 ) {
17650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17651 let ranges = ranges
17652 .into_iter()
17653 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17654 .collect::<Vec<_>>();
17655 self.fold_creases(ranges, auto_scroll, window, cx);
17656 }
17657
17658 pub fn fold_creases<T: ToOffset + Clone>(
17659 &mut self,
17660 creases: Vec<Crease<T>>,
17661 auto_scroll: bool,
17662 _window: &mut Window,
17663 cx: &mut Context<Self>,
17664 ) {
17665 if creases.is_empty() {
17666 return;
17667 }
17668
17669 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17670
17671 if auto_scroll {
17672 self.request_autoscroll(Autoscroll::fit(), cx);
17673 }
17674
17675 cx.notify();
17676
17677 self.scrollbar_marker_state.dirty = true;
17678 self.folds_did_change(cx);
17679 }
17680
17681 /// Removes any folds whose ranges intersect any of the given ranges.
17682 pub fn unfold_ranges<T: ToOffset + Clone>(
17683 &mut self,
17684 ranges: &[Range<T>],
17685 inclusive: bool,
17686 auto_scroll: bool,
17687 cx: &mut Context<Self>,
17688 ) {
17689 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17690 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17691 });
17692 self.folds_did_change(cx);
17693 }
17694
17695 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17696 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17697 return;
17698 }
17699 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17700 self.display_map.update(cx, |display_map, cx| {
17701 display_map.fold_buffers([buffer_id], cx)
17702 });
17703 cx.emit(EditorEvent::BufferFoldToggled {
17704 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17705 folded: true,
17706 });
17707 cx.notify();
17708 }
17709
17710 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17711 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17712 return;
17713 }
17714 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17715 self.display_map.update(cx, |display_map, cx| {
17716 display_map.unfold_buffers([buffer_id], cx);
17717 });
17718 cx.emit(EditorEvent::BufferFoldToggled {
17719 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17720 folded: false,
17721 });
17722 cx.notify();
17723 }
17724
17725 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17726 self.display_map.read(cx).is_buffer_folded(buffer)
17727 }
17728
17729 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17730 self.display_map.read(cx).folded_buffers()
17731 }
17732
17733 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17734 self.display_map.update(cx, |display_map, cx| {
17735 display_map.disable_header_for_buffer(buffer_id, cx);
17736 });
17737 cx.notify();
17738 }
17739
17740 /// Removes any folds with the given ranges.
17741 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17742 &mut self,
17743 ranges: &[Range<T>],
17744 type_id: TypeId,
17745 auto_scroll: bool,
17746 cx: &mut Context<Self>,
17747 ) {
17748 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17749 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17750 });
17751 self.folds_did_change(cx);
17752 }
17753
17754 fn remove_folds_with<T: ToOffset + Clone>(
17755 &mut self,
17756 ranges: &[Range<T>],
17757 auto_scroll: bool,
17758 cx: &mut Context<Self>,
17759 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17760 ) {
17761 if ranges.is_empty() {
17762 return;
17763 }
17764
17765 let mut buffers_affected = HashSet::default();
17766 let multi_buffer = self.buffer().read(cx);
17767 for range in ranges {
17768 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17769 buffers_affected.insert(buffer.read(cx).remote_id());
17770 };
17771 }
17772
17773 self.display_map.update(cx, update);
17774
17775 if auto_scroll {
17776 self.request_autoscroll(Autoscroll::fit(), cx);
17777 }
17778
17779 cx.notify();
17780 self.scrollbar_marker_state.dirty = true;
17781 self.active_indent_guides_state.dirty = true;
17782 }
17783
17784 pub fn update_renderer_widths(
17785 &mut self,
17786 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17787 cx: &mut Context<Self>,
17788 ) -> bool {
17789 self.display_map
17790 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17791 }
17792
17793 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17794 self.display_map.read(cx).fold_placeholder.clone()
17795 }
17796
17797 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17798 self.buffer.update(cx, |buffer, cx| {
17799 buffer.set_all_diff_hunks_expanded(cx);
17800 });
17801 }
17802
17803 pub fn expand_all_diff_hunks(
17804 &mut self,
17805 _: &ExpandAllDiffHunks,
17806 _window: &mut Window,
17807 cx: &mut Context<Self>,
17808 ) {
17809 self.buffer.update(cx, |buffer, cx| {
17810 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17811 });
17812 }
17813
17814 pub fn toggle_selected_diff_hunks(
17815 &mut self,
17816 _: &ToggleSelectedDiffHunks,
17817 _window: &mut Window,
17818 cx: &mut Context<Self>,
17819 ) {
17820 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17821 self.toggle_diff_hunks_in_ranges(ranges, cx);
17822 }
17823
17824 pub fn diff_hunks_in_ranges<'a>(
17825 &'a self,
17826 ranges: &'a [Range<Anchor>],
17827 buffer: &'a MultiBufferSnapshot,
17828 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17829 ranges.iter().flat_map(move |range| {
17830 let end_excerpt_id = range.end.excerpt_id;
17831 let range = range.to_point(buffer);
17832 let mut peek_end = range.end;
17833 if range.end.row < buffer.max_row().0 {
17834 peek_end = Point::new(range.end.row + 1, 0);
17835 }
17836 buffer
17837 .diff_hunks_in_range(range.start..peek_end)
17838 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17839 })
17840 }
17841
17842 pub fn has_stageable_diff_hunks_in_ranges(
17843 &self,
17844 ranges: &[Range<Anchor>],
17845 snapshot: &MultiBufferSnapshot,
17846 ) -> bool {
17847 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17848 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17849 }
17850
17851 pub fn toggle_staged_selected_diff_hunks(
17852 &mut self,
17853 _: &::git::ToggleStaged,
17854 _: &mut Window,
17855 cx: &mut Context<Self>,
17856 ) {
17857 let snapshot = self.buffer.read(cx).snapshot(cx);
17858 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17859 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17860 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17861 }
17862
17863 pub fn set_render_diff_hunk_controls(
17864 &mut self,
17865 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17866 cx: &mut Context<Self>,
17867 ) {
17868 self.render_diff_hunk_controls = render_diff_hunk_controls;
17869 cx.notify();
17870 }
17871
17872 pub fn stage_and_next(
17873 &mut self,
17874 _: &::git::StageAndNext,
17875 window: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 self.do_stage_or_unstage_and_next(true, window, cx);
17879 }
17880
17881 pub fn unstage_and_next(
17882 &mut self,
17883 _: &::git::UnstageAndNext,
17884 window: &mut Window,
17885 cx: &mut Context<Self>,
17886 ) {
17887 self.do_stage_or_unstage_and_next(false, window, cx);
17888 }
17889
17890 pub fn stage_or_unstage_diff_hunks(
17891 &mut self,
17892 stage: bool,
17893 ranges: Vec<Range<Anchor>>,
17894 cx: &mut Context<Self>,
17895 ) {
17896 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17897 cx.spawn(async move |this, cx| {
17898 task.await?;
17899 this.update(cx, |this, cx| {
17900 let snapshot = this.buffer.read(cx).snapshot(cx);
17901 let chunk_by = this
17902 .diff_hunks_in_ranges(&ranges, &snapshot)
17903 .chunk_by(|hunk| hunk.buffer_id);
17904 for (buffer_id, hunks) in &chunk_by {
17905 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17906 }
17907 })
17908 })
17909 .detach_and_log_err(cx);
17910 }
17911
17912 fn save_buffers_for_ranges_if_needed(
17913 &mut self,
17914 ranges: &[Range<Anchor>],
17915 cx: &mut Context<Editor>,
17916 ) -> Task<Result<()>> {
17917 let multibuffer = self.buffer.read(cx);
17918 let snapshot = multibuffer.read(cx);
17919 let buffer_ids: HashSet<_> = ranges
17920 .iter()
17921 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17922 .collect();
17923 drop(snapshot);
17924
17925 let mut buffers = HashSet::default();
17926 for buffer_id in buffer_ids {
17927 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17928 let buffer = buffer_entity.read(cx);
17929 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17930 {
17931 buffers.insert(buffer_entity);
17932 }
17933 }
17934 }
17935
17936 if let Some(project) = &self.project {
17937 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17938 } else {
17939 Task::ready(Ok(()))
17940 }
17941 }
17942
17943 fn do_stage_or_unstage_and_next(
17944 &mut self,
17945 stage: bool,
17946 window: &mut Window,
17947 cx: &mut Context<Self>,
17948 ) {
17949 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17950
17951 if ranges.iter().any(|range| range.start != range.end) {
17952 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17953 return;
17954 }
17955
17956 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17957 let snapshot = self.snapshot(window, cx);
17958 let position = self.selections.newest::<Point>(cx).head();
17959 let mut row = snapshot
17960 .buffer_snapshot
17961 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17962 .find(|hunk| hunk.row_range.start.0 > position.row)
17963 .map(|hunk| hunk.row_range.start);
17964
17965 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17966 // Outside of the project diff editor, wrap around to the beginning.
17967 if !all_diff_hunks_expanded {
17968 row = row.or_else(|| {
17969 snapshot
17970 .buffer_snapshot
17971 .diff_hunks_in_range(Point::zero()..position)
17972 .find(|hunk| hunk.row_range.end.0 < position.row)
17973 .map(|hunk| hunk.row_range.start)
17974 });
17975 }
17976
17977 if let Some(row) = row {
17978 let destination = Point::new(row.0, 0);
17979 let autoscroll = Autoscroll::center();
17980
17981 self.unfold_ranges(&[destination..destination], false, false, cx);
17982 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17983 s.select_ranges([destination..destination]);
17984 });
17985 }
17986 }
17987
17988 fn do_stage_or_unstage(
17989 &self,
17990 stage: bool,
17991 buffer_id: BufferId,
17992 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17993 cx: &mut App,
17994 ) -> Option<()> {
17995 let project = self.project()?;
17996 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17997 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17998 let buffer_snapshot = buffer.read(cx).snapshot();
17999 let file_exists = buffer_snapshot
18000 .file()
18001 .is_some_and(|file| file.disk_state().exists());
18002 diff.update(cx, |diff, cx| {
18003 diff.stage_or_unstage_hunks(
18004 stage,
18005 &hunks
18006 .map(|hunk| buffer_diff::DiffHunk {
18007 buffer_range: hunk.buffer_range,
18008 diff_base_byte_range: hunk.diff_base_byte_range,
18009 secondary_status: hunk.secondary_status,
18010 range: Point::zero()..Point::zero(), // unused
18011 })
18012 .collect::<Vec<_>>(),
18013 &buffer_snapshot,
18014 file_exists,
18015 cx,
18016 )
18017 });
18018 None
18019 }
18020
18021 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18022 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18023 self.buffer
18024 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18025 }
18026
18027 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18028 self.buffer.update(cx, |buffer, cx| {
18029 let ranges = vec![Anchor::min()..Anchor::max()];
18030 if !buffer.all_diff_hunks_expanded()
18031 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18032 {
18033 buffer.collapse_diff_hunks(ranges, cx);
18034 true
18035 } else {
18036 false
18037 }
18038 })
18039 }
18040
18041 fn toggle_diff_hunks_in_ranges(
18042 &mut self,
18043 ranges: Vec<Range<Anchor>>,
18044 cx: &mut Context<Editor>,
18045 ) {
18046 self.buffer.update(cx, |buffer, cx| {
18047 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18048 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18049 })
18050 }
18051
18052 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18053 self.buffer.update(cx, |buffer, cx| {
18054 let snapshot = buffer.snapshot(cx);
18055 let excerpt_id = range.end.excerpt_id;
18056 let point_range = range.to_point(&snapshot);
18057 let expand = !buffer.single_hunk_is_expanded(range, cx);
18058 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18059 })
18060 }
18061
18062 pub(crate) fn apply_all_diff_hunks(
18063 &mut self,
18064 _: &ApplyAllDiffHunks,
18065 window: &mut Window,
18066 cx: &mut Context<Self>,
18067 ) {
18068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18069
18070 let buffers = self.buffer.read(cx).all_buffers();
18071 for branch_buffer in buffers {
18072 branch_buffer.update(cx, |branch_buffer, cx| {
18073 branch_buffer.merge_into_base(Vec::new(), cx);
18074 });
18075 }
18076
18077 if let Some(project) = self.project.clone() {
18078 self.save(
18079 SaveOptions {
18080 format: true,
18081 autosave: false,
18082 },
18083 project,
18084 window,
18085 cx,
18086 )
18087 .detach_and_log_err(cx);
18088 }
18089 }
18090
18091 pub(crate) fn apply_selected_diff_hunks(
18092 &mut self,
18093 _: &ApplyDiffHunk,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18098 let snapshot = self.snapshot(window, cx);
18099 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18100 let mut ranges_by_buffer = HashMap::default();
18101 self.transact(window, cx, |editor, _window, cx| {
18102 for hunk in hunks {
18103 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18104 ranges_by_buffer
18105 .entry(buffer.clone())
18106 .or_insert_with(Vec::new)
18107 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18108 }
18109 }
18110
18111 for (buffer, ranges) in ranges_by_buffer {
18112 buffer.update(cx, |buffer, cx| {
18113 buffer.merge_into_base(ranges, cx);
18114 });
18115 }
18116 });
18117
18118 if let Some(project) = self.project.clone() {
18119 self.save(
18120 SaveOptions {
18121 format: true,
18122 autosave: false,
18123 },
18124 project,
18125 window,
18126 cx,
18127 )
18128 .detach_and_log_err(cx);
18129 }
18130 }
18131
18132 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18133 if hovered != self.gutter_hovered {
18134 self.gutter_hovered = hovered;
18135 cx.notify();
18136 }
18137 }
18138
18139 pub fn insert_blocks(
18140 &mut self,
18141 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18142 autoscroll: Option<Autoscroll>,
18143 cx: &mut Context<Self>,
18144 ) -> Vec<CustomBlockId> {
18145 let blocks = self
18146 .display_map
18147 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18148 if let Some(autoscroll) = autoscroll {
18149 self.request_autoscroll(autoscroll, cx);
18150 }
18151 cx.notify();
18152 blocks
18153 }
18154
18155 pub fn resize_blocks(
18156 &mut self,
18157 heights: HashMap<CustomBlockId, u32>,
18158 autoscroll: Option<Autoscroll>,
18159 cx: &mut Context<Self>,
18160 ) {
18161 self.display_map
18162 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18163 if let Some(autoscroll) = autoscroll {
18164 self.request_autoscroll(autoscroll, cx);
18165 }
18166 cx.notify();
18167 }
18168
18169 pub fn replace_blocks(
18170 &mut self,
18171 renderers: HashMap<CustomBlockId, RenderBlock>,
18172 autoscroll: Option<Autoscroll>,
18173 cx: &mut Context<Self>,
18174 ) {
18175 self.display_map
18176 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18177 if let Some(autoscroll) = autoscroll {
18178 self.request_autoscroll(autoscroll, cx);
18179 }
18180 cx.notify();
18181 }
18182
18183 pub fn remove_blocks(
18184 &mut self,
18185 block_ids: HashSet<CustomBlockId>,
18186 autoscroll: Option<Autoscroll>,
18187 cx: &mut Context<Self>,
18188 ) {
18189 self.display_map.update(cx, |display_map, cx| {
18190 display_map.remove_blocks(block_ids, cx)
18191 });
18192 if let Some(autoscroll) = autoscroll {
18193 self.request_autoscroll(autoscroll, cx);
18194 }
18195 cx.notify();
18196 }
18197
18198 pub fn row_for_block(
18199 &self,
18200 block_id: CustomBlockId,
18201 cx: &mut Context<Self>,
18202 ) -> Option<DisplayRow> {
18203 self.display_map
18204 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18205 }
18206
18207 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18208 self.focused_block = Some(focused_block);
18209 }
18210
18211 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18212 self.focused_block.take()
18213 }
18214
18215 pub fn insert_creases(
18216 &mut self,
18217 creases: impl IntoIterator<Item = Crease<Anchor>>,
18218 cx: &mut Context<Self>,
18219 ) -> Vec<CreaseId> {
18220 self.display_map
18221 .update(cx, |map, cx| map.insert_creases(creases, cx))
18222 }
18223
18224 pub fn remove_creases(
18225 &mut self,
18226 ids: impl IntoIterator<Item = CreaseId>,
18227 cx: &mut Context<Self>,
18228 ) -> Vec<(CreaseId, Range<Anchor>)> {
18229 self.display_map
18230 .update(cx, |map, cx| map.remove_creases(ids, cx))
18231 }
18232
18233 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18234 self.display_map
18235 .update(cx, |map, cx| map.snapshot(cx))
18236 .longest_row()
18237 }
18238
18239 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18240 self.display_map
18241 .update(cx, |map, cx| map.snapshot(cx))
18242 .max_point()
18243 }
18244
18245 pub fn text(&self, cx: &App) -> String {
18246 self.buffer.read(cx).read(cx).text()
18247 }
18248
18249 pub fn is_empty(&self, cx: &App) -> bool {
18250 self.buffer.read(cx).read(cx).is_empty()
18251 }
18252
18253 pub fn text_option(&self, cx: &App) -> Option<String> {
18254 let text = self.text(cx);
18255 let text = text.trim();
18256
18257 if text.is_empty() {
18258 return None;
18259 }
18260
18261 Some(text.to_string())
18262 }
18263
18264 pub fn set_text(
18265 &mut self,
18266 text: impl Into<Arc<str>>,
18267 window: &mut Window,
18268 cx: &mut Context<Self>,
18269 ) {
18270 self.transact(window, cx, |this, _, cx| {
18271 this.buffer
18272 .read(cx)
18273 .as_singleton()
18274 .expect("you can only call set_text on editors for singleton buffers")
18275 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18276 });
18277 }
18278
18279 pub fn display_text(&self, cx: &mut App) -> String {
18280 self.display_map
18281 .update(cx, |map, cx| map.snapshot(cx))
18282 .text()
18283 }
18284
18285 fn create_minimap(
18286 &self,
18287 minimap_settings: MinimapSettings,
18288 window: &mut Window,
18289 cx: &mut Context<Self>,
18290 ) -> Option<Entity<Self>> {
18291 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18292 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18293 }
18294
18295 fn initialize_new_minimap(
18296 &self,
18297 minimap_settings: MinimapSettings,
18298 window: &mut Window,
18299 cx: &mut Context<Self>,
18300 ) -> Entity<Self> {
18301 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18302
18303 let mut minimap = Editor::new_internal(
18304 EditorMode::Minimap {
18305 parent: cx.weak_entity(),
18306 },
18307 self.buffer.clone(),
18308 None,
18309 Some(self.display_map.clone()),
18310 window,
18311 cx,
18312 );
18313 minimap.scroll_manager.clone_state(&self.scroll_manager);
18314 minimap.set_text_style_refinement(TextStyleRefinement {
18315 font_size: Some(MINIMAP_FONT_SIZE),
18316 font_weight: Some(MINIMAP_FONT_WEIGHT),
18317 ..Default::default()
18318 });
18319 minimap.update_minimap_configuration(minimap_settings, cx);
18320 cx.new(|_| minimap)
18321 }
18322
18323 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18324 let current_line_highlight = minimap_settings
18325 .current_line_highlight
18326 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18327 self.set_current_line_highlight(Some(current_line_highlight));
18328 }
18329
18330 pub fn minimap(&self) -> Option<&Entity<Self>> {
18331 self.minimap
18332 .as_ref()
18333 .filter(|_| self.minimap_visibility.visible())
18334 }
18335
18336 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18337 let mut wrap_guides = smallvec![];
18338
18339 if self.show_wrap_guides == Some(false) {
18340 return wrap_guides;
18341 }
18342
18343 let settings = self.buffer.read(cx).language_settings(cx);
18344 if settings.show_wrap_guides {
18345 match self.soft_wrap_mode(cx) {
18346 SoftWrap::Column(soft_wrap) => {
18347 wrap_guides.push((soft_wrap as usize, true));
18348 }
18349 SoftWrap::Bounded(soft_wrap) => {
18350 wrap_guides.push((soft_wrap as usize, true));
18351 }
18352 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18353 }
18354 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18355 }
18356
18357 wrap_guides
18358 }
18359
18360 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18361 let settings = self.buffer.read(cx).language_settings(cx);
18362 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18363 match mode {
18364 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18365 SoftWrap::None
18366 }
18367 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18368 language_settings::SoftWrap::PreferredLineLength => {
18369 SoftWrap::Column(settings.preferred_line_length)
18370 }
18371 language_settings::SoftWrap::Bounded => {
18372 SoftWrap::Bounded(settings.preferred_line_length)
18373 }
18374 }
18375 }
18376
18377 pub fn set_soft_wrap_mode(
18378 &mut self,
18379 mode: language_settings::SoftWrap,
18380
18381 cx: &mut Context<Self>,
18382 ) {
18383 self.soft_wrap_mode_override = Some(mode);
18384 cx.notify();
18385 }
18386
18387 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18388 self.hard_wrap = hard_wrap;
18389 cx.notify();
18390 }
18391
18392 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18393 self.text_style_refinement = Some(style);
18394 }
18395
18396 /// called by the Element so we know what style we were most recently rendered with.
18397 pub(crate) fn set_style(
18398 &mut self,
18399 style: EditorStyle,
18400 window: &mut Window,
18401 cx: &mut Context<Self>,
18402 ) {
18403 // We intentionally do not inform the display map about the minimap style
18404 // so that wrapping is not recalculated and stays consistent for the editor
18405 // and its linked minimap.
18406 if !self.mode.is_minimap() {
18407 let rem_size = window.rem_size();
18408 self.display_map.update(cx, |map, cx| {
18409 map.set_font(
18410 style.text.font(),
18411 style.text.font_size.to_pixels(rem_size),
18412 cx,
18413 )
18414 });
18415 }
18416 self.style = Some(style);
18417 }
18418
18419 pub fn style(&self) -> Option<&EditorStyle> {
18420 self.style.as_ref()
18421 }
18422
18423 // Called by the element. This method is not designed to be called outside of the editor
18424 // element's layout code because it does not notify when rewrapping is computed synchronously.
18425 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18426 self.display_map
18427 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18428 }
18429
18430 pub fn set_soft_wrap(&mut self) {
18431 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18432 }
18433
18434 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18435 if self.soft_wrap_mode_override.is_some() {
18436 self.soft_wrap_mode_override.take();
18437 } else {
18438 let soft_wrap = match self.soft_wrap_mode(cx) {
18439 SoftWrap::GitDiff => return,
18440 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18441 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18442 language_settings::SoftWrap::None
18443 }
18444 };
18445 self.soft_wrap_mode_override = Some(soft_wrap);
18446 }
18447 cx.notify();
18448 }
18449
18450 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18451 let Some(workspace) = self.workspace() else {
18452 return;
18453 };
18454 let fs = workspace.read(cx).app_state().fs.clone();
18455 let current_show = TabBarSettings::get_global(cx).show;
18456 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18457 setting.show = Some(!current_show);
18458 });
18459 }
18460
18461 pub fn toggle_indent_guides(
18462 &mut self,
18463 _: &ToggleIndentGuides,
18464 _: &mut Window,
18465 cx: &mut Context<Self>,
18466 ) {
18467 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18468 self.buffer
18469 .read(cx)
18470 .language_settings(cx)
18471 .indent_guides
18472 .enabled
18473 });
18474 self.show_indent_guides = Some(!currently_enabled);
18475 cx.notify();
18476 }
18477
18478 fn should_show_indent_guides(&self) -> Option<bool> {
18479 self.show_indent_guides
18480 }
18481
18482 pub fn toggle_line_numbers(
18483 &mut self,
18484 _: &ToggleLineNumbers,
18485 _: &mut Window,
18486 cx: &mut Context<Self>,
18487 ) {
18488 let mut editor_settings = EditorSettings::get_global(cx).clone();
18489 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18490 EditorSettings::override_global(editor_settings, cx);
18491 }
18492
18493 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18494 if let Some(show_line_numbers) = self.show_line_numbers {
18495 return show_line_numbers;
18496 }
18497 EditorSettings::get_global(cx).gutter.line_numbers
18498 }
18499
18500 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18501 self.use_relative_line_numbers
18502 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18503 }
18504
18505 pub fn toggle_relative_line_numbers(
18506 &mut self,
18507 _: &ToggleRelativeLineNumbers,
18508 _: &mut Window,
18509 cx: &mut Context<Self>,
18510 ) {
18511 let is_relative = self.should_use_relative_line_numbers(cx);
18512 self.set_relative_line_number(Some(!is_relative), cx)
18513 }
18514
18515 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18516 self.use_relative_line_numbers = is_relative;
18517 cx.notify();
18518 }
18519
18520 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18521 self.show_gutter = show_gutter;
18522 cx.notify();
18523 }
18524
18525 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18526 self.show_scrollbars = ScrollbarAxes {
18527 horizontal: show,
18528 vertical: show,
18529 };
18530 cx.notify();
18531 }
18532
18533 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18534 self.show_scrollbars.vertical = show;
18535 cx.notify();
18536 }
18537
18538 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18539 self.show_scrollbars.horizontal = show;
18540 cx.notify();
18541 }
18542
18543 pub fn set_minimap_visibility(
18544 &mut self,
18545 minimap_visibility: MinimapVisibility,
18546 window: &mut Window,
18547 cx: &mut Context<Self>,
18548 ) {
18549 if self.minimap_visibility != minimap_visibility {
18550 if minimap_visibility.visible() && self.minimap.is_none() {
18551 let minimap_settings = EditorSettings::get_global(cx).minimap;
18552 self.minimap =
18553 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18554 }
18555 self.minimap_visibility = minimap_visibility;
18556 cx.notify();
18557 }
18558 }
18559
18560 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18561 self.set_show_scrollbars(false, cx);
18562 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18563 }
18564
18565 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18566 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18567 }
18568
18569 /// Normally the text in full mode and auto height editors is padded on the
18570 /// left side by roughly half a character width for improved hit testing.
18571 ///
18572 /// Use this method to disable this for cases where this is not wanted (e.g.
18573 /// if you want to align the editor text with some other text above or below)
18574 /// or if you want to add this padding to single-line editors.
18575 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18576 self.offset_content = offset_content;
18577 cx.notify();
18578 }
18579
18580 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18581 self.show_line_numbers = Some(show_line_numbers);
18582 cx.notify();
18583 }
18584
18585 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18586 self.disable_expand_excerpt_buttons = true;
18587 cx.notify();
18588 }
18589
18590 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18591 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18592 cx.notify();
18593 }
18594
18595 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18596 self.show_code_actions = Some(show_code_actions);
18597 cx.notify();
18598 }
18599
18600 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18601 self.show_runnables = Some(show_runnables);
18602 cx.notify();
18603 }
18604
18605 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18606 self.show_breakpoints = Some(show_breakpoints);
18607 cx.notify();
18608 }
18609
18610 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18611 if self.display_map.read(cx).masked != masked {
18612 self.display_map.update(cx, |map, _| map.masked = masked);
18613 }
18614 cx.notify()
18615 }
18616
18617 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18618 self.show_wrap_guides = Some(show_wrap_guides);
18619 cx.notify();
18620 }
18621
18622 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18623 self.show_indent_guides = Some(show_indent_guides);
18624 cx.notify();
18625 }
18626
18627 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18628 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18629 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18630 && let Some(dir) = file.abs_path(cx).parent()
18631 {
18632 return Some(dir.to_owned());
18633 }
18634
18635 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18636 return Some(project_path.path.to_path_buf());
18637 }
18638 }
18639
18640 None
18641 }
18642
18643 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18644 self.active_excerpt(cx)?
18645 .1
18646 .read(cx)
18647 .file()
18648 .and_then(|f| f.as_local())
18649 }
18650
18651 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18652 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18653 let buffer = buffer.read(cx);
18654 if let Some(project_path) = buffer.project_path(cx) {
18655 let project = self.project()?.read(cx);
18656 project.absolute_path(&project_path, cx)
18657 } else {
18658 buffer
18659 .file()
18660 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18661 }
18662 })
18663 }
18664
18665 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18666 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18667 let project_path = buffer.read(cx).project_path(cx)?;
18668 let project = self.project()?.read(cx);
18669 let entry = project.entry_for_path(&project_path, cx)?;
18670 let path = entry.path.to_path_buf();
18671 Some(path)
18672 })
18673 }
18674
18675 pub fn reveal_in_finder(
18676 &mut self,
18677 _: &RevealInFileManager,
18678 _window: &mut Window,
18679 cx: &mut Context<Self>,
18680 ) {
18681 if let Some(target) = self.target_file(cx) {
18682 cx.reveal_path(&target.abs_path(cx));
18683 }
18684 }
18685
18686 pub fn copy_path(
18687 &mut self,
18688 _: &zed_actions::workspace::CopyPath,
18689 _window: &mut Window,
18690 cx: &mut Context<Self>,
18691 ) {
18692 if let Some(path) = self.target_file_abs_path(cx)
18693 && let Some(path) = path.to_str()
18694 {
18695 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18696 }
18697 }
18698
18699 pub fn copy_relative_path(
18700 &mut self,
18701 _: &zed_actions::workspace::CopyRelativePath,
18702 _window: &mut Window,
18703 cx: &mut Context<Self>,
18704 ) {
18705 if let Some(path) = self.target_file_path(cx)
18706 && let Some(path) = path.to_str()
18707 {
18708 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18709 }
18710 }
18711
18712 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18713 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18714 buffer.read(cx).project_path(cx)
18715 } else {
18716 None
18717 }
18718 }
18719
18720 // Returns true if the editor handled a go-to-line request
18721 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18722 maybe!({
18723 let breakpoint_store = self.breakpoint_store.as_ref()?;
18724
18725 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18726 else {
18727 self.clear_row_highlights::<ActiveDebugLine>();
18728 return None;
18729 };
18730
18731 let position = active_stack_frame.position;
18732 let buffer_id = position.buffer_id?;
18733 let snapshot = self
18734 .project
18735 .as_ref()?
18736 .read(cx)
18737 .buffer_for_id(buffer_id, cx)?
18738 .read(cx)
18739 .snapshot();
18740
18741 let mut handled = false;
18742 for (id, ExcerptRange { context, .. }) in
18743 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18744 {
18745 if context.start.cmp(&position, &snapshot).is_ge()
18746 || context.end.cmp(&position, &snapshot).is_lt()
18747 {
18748 continue;
18749 }
18750 let snapshot = self.buffer.read(cx).snapshot(cx);
18751 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18752
18753 handled = true;
18754 self.clear_row_highlights::<ActiveDebugLine>();
18755
18756 self.go_to_line::<ActiveDebugLine>(
18757 multibuffer_anchor,
18758 Some(cx.theme().colors().editor_debugger_active_line_background),
18759 window,
18760 cx,
18761 );
18762
18763 cx.notify();
18764 }
18765
18766 handled.then_some(())
18767 })
18768 .is_some()
18769 }
18770
18771 pub fn copy_file_name_without_extension(
18772 &mut self,
18773 _: &CopyFileNameWithoutExtension,
18774 _: &mut Window,
18775 cx: &mut Context<Self>,
18776 ) {
18777 if let Some(file) = self.target_file(cx)
18778 && let Some(file_stem) = file.path().file_stem()
18779 && let Some(name) = file_stem.to_str()
18780 {
18781 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18782 }
18783 }
18784
18785 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18786 if let Some(file) = self.target_file(cx)
18787 && let Some(file_name) = file.path().file_name()
18788 && let Some(name) = file_name.to_str()
18789 {
18790 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18791 }
18792 }
18793
18794 pub fn toggle_git_blame(
18795 &mut self,
18796 _: &::git::Blame,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18801
18802 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18803 self.start_git_blame(true, window, cx);
18804 }
18805
18806 cx.notify();
18807 }
18808
18809 pub fn toggle_git_blame_inline(
18810 &mut self,
18811 _: &ToggleGitBlameInline,
18812 window: &mut Window,
18813 cx: &mut Context<Self>,
18814 ) {
18815 self.toggle_git_blame_inline_internal(true, window, cx);
18816 cx.notify();
18817 }
18818
18819 pub fn open_git_blame_commit(
18820 &mut self,
18821 _: &OpenGitBlameCommit,
18822 window: &mut Window,
18823 cx: &mut Context<Self>,
18824 ) {
18825 self.open_git_blame_commit_internal(window, cx);
18826 }
18827
18828 fn open_git_blame_commit_internal(
18829 &mut self,
18830 window: &mut Window,
18831 cx: &mut Context<Self>,
18832 ) -> Option<()> {
18833 let blame = self.blame.as_ref()?;
18834 let snapshot = self.snapshot(window, cx);
18835 let cursor = self.selections.newest::<Point>(cx).head();
18836 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18837 let blame_entry = blame
18838 .update(cx, |blame, cx| {
18839 blame
18840 .blame_for_rows(
18841 &[RowInfo {
18842 buffer_id: Some(buffer.remote_id()),
18843 buffer_row: Some(point.row),
18844 ..Default::default()
18845 }],
18846 cx,
18847 )
18848 .next()
18849 })
18850 .flatten()?;
18851 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18852 let repo = blame.read(cx).repository(cx)?;
18853 let workspace = self.workspace()?.downgrade();
18854 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18855 None
18856 }
18857
18858 pub fn git_blame_inline_enabled(&self) -> bool {
18859 self.git_blame_inline_enabled
18860 }
18861
18862 pub fn toggle_selection_menu(
18863 &mut self,
18864 _: &ToggleSelectionMenu,
18865 _: &mut Window,
18866 cx: &mut Context<Self>,
18867 ) {
18868 self.show_selection_menu = self
18869 .show_selection_menu
18870 .map(|show_selections_menu| !show_selections_menu)
18871 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18872
18873 cx.notify();
18874 }
18875
18876 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18877 self.show_selection_menu
18878 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18879 }
18880
18881 fn start_git_blame(
18882 &mut self,
18883 user_triggered: bool,
18884 window: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) {
18887 if let Some(project) = self.project() {
18888 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18889 return;
18890 };
18891
18892 if buffer.read(cx).file().is_none() {
18893 return;
18894 }
18895
18896 let focused = self.focus_handle(cx).contains_focused(window, cx);
18897
18898 let project = project.clone();
18899 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18900 self.blame_subscription =
18901 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18902 self.blame = Some(blame);
18903 }
18904 }
18905
18906 fn toggle_git_blame_inline_internal(
18907 &mut self,
18908 user_triggered: bool,
18909 window: &mut Window,
18910 cx: &mut Context<Self>,
18911 ) {
18912 if self.git_blame_inline_enabled {
18913 self.git_blame_inline_enabled = false;
18914 self.show_git_blame_inline = false;
18915 self.show_git_blame_inline_delay_task.take();
18916 } else {
18917 self.git_blame_inline_enabled = true;
18918 self.start_git_blame_inline(user_triggered, window, cx);
18919 }
18920
18921 cx.notify();
18922 }
18923
18924 fn start_git_blame_inline(
18925 &mut self,
18926 user_triggered: bool,
18927 window: &mut Window,
18928 cx: &mut Context<Self>,
18929 ) {
18930 self.start_git_blame(user_triggered, window, cx);
18931
18932 if ProjectSettings::get_global(cx)
18933 .git
18934 .inline_blame_delay()
18935 .is_some()
18936 {
18937 self.start_inline_blame_timer(window, cx);
18938 } else {
18939 self.show_git_blame_inline = true
18940 }
18941 }
18942
18943 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18944 self.blame.as_ref()
18945 }
18946
18947 pub fn show_git_blame_gutter(&self) -> bool {
18948 self.show_git_blame_gutter
18949 }
18950
18951 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18952 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18953 }
18954
18955 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18956 self.show_git_blame_inline
18957 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18958 && !self.newest_selection_head_on_empty_line(cx)
18959 && self.has_blame_entries(cx)
18960 }
18961
18962 fn has_blame_entries(&self, cx: &App) -> bool {
18963 self.blame()
18964 .is_some_and(|blame| blame.read(cx).has_generated_entries())
18965 }
18966
18967 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18968 let cursor_anchor = self.selections.newest_anchor().head();
18969
18970 let snapshot = self.buffer.read(cx).snapshot(cx);
18971 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18972
18973 snapshot.line_len(buffer_row) == 0
18974 }
18975
18976 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18977 let buffer_and_selection = maybe!({
18978 let selection = self.selections.newest::<Point>(cx);
18979 let selection_range = selection.range();
18980
18981 let multi_buffer = self.buffer().read(cx);
18982 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18983 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18984
18985 let (buffer, range, _) = if selection.reversed {
18986 buffer_ranges.first()
18987 } else {
18988 buffer_ranges.last()
18989 }?;
18990
18991 let selection = text::ToPoint::to_point(&range.start, buffer).row
18992 ..text::ToPoint::to_point(&range.end, buffer).row;
18993 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
18994 });
18995
18996 let Some((buffer, selection)) = buffer_and_selection else {
18997 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18998 };
18999
19000 let Some(project) = self.project() else {
19001 return Task::ready(Err(anyhow!("editor does not have project")));
19002 };
19003
19004 project.update(cx, |project, cx| {
19005 project.get_permalink_to_line(&buffer, selection, cx)
19006 })
19007 }
19008
19009 pub fn copy_permalink_to_line(
19010 &mut self,
19011 _: &CopyPermalinkToLine,
19012 window: &mut Window,
19013 cx: &mut Context<Self>,
19014 ) {
19015 let permalink_task = self.get_permalink_to_line(cx);
19016 let workspace = self.workspace();
19017
19018 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19019 Ok(permalink) => {
19020 cx.update(|_, cx| {
19021 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19022 })
19023 .ok();
19024 }
19025 Err(err) => {
19026 let message = format!("Failed to copy permalink: {err}");
19027
19028 anyhow::Result::<()>::Err(err).log_err();
19029
19030 if let Some(workspace) = workspace {
19031 workspace
19032 .update_in(cx, |workspace, _, cx| {
19033 struct CopyPermalinkToLine;
19034
19035 workspace.show_toast(
19036 Toast::new(
19037 NotificationId::unique::<CopyPermalinkToLine>(),
19038 message,
19039 ),
19040 cx,
19041 )
19042 })
19043 .ok();
19044 }
19045 }
19046 })
19047 .detach();
19048 }
19049
19050 pub fn copy_file_location(
19051 &mut self,
19052 _: &CopyFileLocation,
19053 _: &mut Window,
19054 cx: &mut Context<Self>,
19055 ) {
19056 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19057 if let Some(file) = self.target_file(cx)
19058 && let Some(path) = file.path().to_str()
19059 {
19060 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19061 }
19062 }
19063
19064 pub fn open_permalink_to_line(
19065 &mut self,
19066 _: &OpenPermalinkToLine,
19067 window: &mut Window,
19068 cx: &mut Context<Self>,
19069 ) {
19070 let permalink_task = self.get_permalink_to_line(cx);
19071 let workspace = self.workspace();
19072
19073 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19074 Ok(permalink) => {
19075 cx.update(|_, cx| {
19076 cx.open_url(permalink.as_ref());
19077 })
19078 .ok();
19079 }
19080 Err(err) => {
19081 let message = format!("Failed to open permalink: {err}");
19082
19083 anyhow::Result::<()>::Err(err).log_err();
19084
19085 if let Some(workspace) = workspace {
19086 workspace
19087 .update(cx, |workspace, cx| {
19088 struct OpenPermalinkToLine;
19089
19090 workspace.show_toast(
19091 Toast::new(
19092 NotificationId::unique::<OpenPermalinkToLine>(),
19093 message,
19094 ),
19095 cx,
19096 )
19097 })
19098 .ok();
19099 }
19100 }
19101 })
19102 .detach();
19103 }
19104
19105 pub fn insert_uuid_v4(
19106 &mut self,
19107 _: &InsertUuidV4,
19108 window: &mut Window,
19109 cx: &mut Context<Self>,
19110 ) {
19111 self.insert_uuid(UuidVersion::V4, window, cx);
19112 }
19113
19114 pub fn insert_uuid_v7(
19115 &mut self,
19116 _: &InsertUuidV7,
19117 window: &mut Window,
19118 cx: &mut Context<Self>,
19119 ) {
19120 self.insert_uuid(UuidVersion::V7, window, cx);
19121 }
19122
19123 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19124 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19125 self.transact(window, cx, |this, window, cx| {
19126 let edits = this
19127 .selections
19128 .all::<Point>(cx)
19129 .into_iter()
19130 .map(|selection| {
19131 let uuid = match version {
19132 UuidVersion::V4 => uuid::Uuid::new_v4(),
19133 UuidVersion::V7 => uuid::Uuid::now_v7(),
19134 };
19135
19136 (selection.range(), uuid.to_string())
19137 });
19138 this.edit(edits, cx);
19139 this.refresh_edit_prediction(true, false, window, cx);
19140 });
19141 }
19142
19143 pub fn open_selections_in_multibuffer(
19144 &mut self,
19145 _: &OpenSelectionsInMultibuffer,
19146 window: &mut Window,
19147 cx: &mut Context<Self>,
19148 ) {
19149 let multibuffer = self.buffer.read(cx);
19150
19151 let Some(buffer) = multibuffer.as_singleton() else {
19152 return;
19153 };
19154
19155 let Some(workspace) = self.workspace() else {
19156 return;
19157 };
19158
19159 let title = multibuffer.title(cx).to_string();
19160
19161 let locations = self
19162 .selections
19163 .all_anchors(cx)
19164 .iter()
19165 .map(|selection| Location {
19166 buffer: buffer.clone(),
19167 range: selection.start.text_anchor..selection.end.text_anchor,
19168 })
19169 .collect::<Vec<_>>();
19170
19171 cx.spawn_in(window, async move |_, cx| {
19172 workspace.update_in(cx, |workspace, window, cx| {
19173 Self::open_locations_in_multibuffer(
19174 workspace,
19175 locations,
19176 format!("Selections for '{title}'"),
19177 false,
19178 MultibufferSelectionMode::All,
19179 window,
19180 cx,
19181 );
19182 })
19183 })
19184 .detach();
19185 }
19186
19187 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19188 /// last highlight added will be used.
19189 ///
19190 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19191 pub fn highlight_rows<T: 'static>(
19192 &mut self,
19193 range: Range<Anchor>,
19194 color: Hsla,
19195 options: RowHighlightOptions,
19196 cx: &mut Context<Self>,
19197 ) {
19198 let snapshot = self.buffer().read(cx).snapshot(cx);
19199 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19200 let ix = row_highlights.binary_search_by(|highlight| {
19201 Ordering::Equal
19202 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19203 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19204 });
19205
19206 if let Err(mut ix) = ix {
19207 let index = post_inc(&mut self.highlight_order);
19208
19209 // If this range intersects with the preceding highlight, then merge it with
19210 // the preceding highlight. Otherwise insert a new highlight.
19211 let mut merged = false;
19212 if ix > 0 {
19213 let prev_highlight = &mut row_highlights[ix - 1];
19214 if prev_highlight
19215 .range
19216 .end
19217 .cmp(&range.start, &snapshot)
19218 .is_ge()
19219 {
19220 ix -= 1;
19221 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19222 prev_highlight.range.end = range.end;
19223 }
19224 merged = true;
19225 prev_highlight.index = index;
19226 prev_highlight.color = color;
19227 prev_highlight.options = options;
19228 }
19229 }
19230
19231 if !merged {
19232 row_highlights.insert(
19233 ix,
19234 RowHighlight {
19235 range,
19236 index,
19237 color,
19238 options,
19239 type_id: TypeId::of::<T>(),
19240 },
19241 );
19242 }
19243
19244 // If any of the following highlights intersect with this one, merge them.
19245 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19246 let highlight = &row_highlights[ix];
19247 if next_highlight
19248 .range
19249 .start
19250 .cmp(&highlight.range.end, &snapshot)
19251 .is_le()
19252 {
19253 if next_highlight
19254 .range
19255 .end
19256 .cmp(&highlight.range.end, &snapshot)
19257 .is_gt()
19258 {
19259 row_highlights[ix].range.end = next_highlight.range.end;
19260 }
19261 row_highlights.remove(ix + 1);
19262 } else {
19263 break;
19264 }
19265 }
19266 }
19267 }
19268
19269 /// Remove any highlighted row ranges of the given type that intersect the
19270 /// given ranges.
19271 pub fn remove_highlighted_rows<T: 'static>(
19272 &mut self,
19273 ranges_to_remove: Vec<Range<Anchor>>,
19274 cx: &mut Context<Self>,
19275 ) {
19276 let snapshot = self.buffer().read(cx).snapshot(cx);
19277 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19278 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19279 row_highlights.retain(|highlight| {
19280 while let Some(range_to_remove) = ranges_to_remove.peek() {
19281 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19282 Ordering::Less | Ordering::Equal => {
19283 ranges_to_remove.next();
19284 }
19285 Ordering::Greater => {
19286 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19287 Ordering::Less | Ordering::Equal => {
19288 return false;
19289 }
19290 Ordering::Greater => break,
19291 }
19292 }
19293 }
19294 }
19295
19296 true
19297 })
19298 }
19299
19300 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19301 pub fn clear_row_highlights<T: 'static>(&mut self) {
19302 self.highlighted_rows.remove(&TypeId::of::<T>());
19303 }
19304
19305 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19306 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19307 self.highlighted_rows
19308 .get(&TypeId::of::<T>())
19309 .map_or(&[] as &[_], |vec| vec.as_slice())
19310 .iter()
19311 .map(|highlight| (highlight.range.clone(), highlight.color))
19312 }
19313
19314 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19315 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19316 /// Allows to ignore certain kinds of highlights.
19317 pub fn highlighted_display_rows(
19318 &self,
19319 window: &mut Window,
19320 cx: &mut App,
19321 ) -> BTreeMap<DisplayRow, LineHighlight> {
19322 let snapshot = self.snapshot(window, cx);
19323 let mut used_highlight_orders = HashMap::default();
19324 self.highlighted_rows
19325 .iter()
19326 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19327 .fold(
19328 BTreeMap::<DisplayRow, LineHighlight>::new(),
19329 |mut unique_rows, highlight| {
19330 let start = highlight.range.start.to_display_point(&snapshot);
19331 let end = highlight.range.end.to_display_point(&snapshot);
19332 let start_row = start.row().0;
19333 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19334 && end.column() == 0
19335 {
19336 end.row().0.saturating_sub(1)
19337 } else {
19338 end.row().0
19339 };
19340 for row in start_row..=end_row {
19341 let used_index =
19342 used_highlight_orders.entry(row).or_insert(highlight.index);
19343 if highlight.index >= *used_index {
19344 *used_index = highlight.index;
19345 unique_rows.insert(
19346 DisplayRow(row),
19347 LineHighlight {
19348 include_gutter: highlight.options.include_gutter,
19349 border: None,
19350 background: highlight.color.into(),
19351 type_id: Some(highlight.type_id),
19352 },
19353 );
19354 }
19355 }
19356 unique_rows
19357 },
19358 )
19359 }
19360
19361 pub fn highlighted_display_row_for_autoscroll(
19362 &self,
19363 snapshot: &DisplaySnapshot,
19364 ) -> Option<DisplayRow> {
19365 self.highlighted_rows
19366 .values()
19367 .flat_map(|highlighted_rows| highlighted_rows.iter())
19368 .filter_map(|highlight| {
19369 if highlight.options.autoscroll {
19370 Some(highlight.range.start.to_display_point(snapshot).row())
19371 } else {
19372 None
19373 }
19374 })
19375 .min()
19376 }
19377
19378 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19379 self.highlight_background::<SearchWithinRange>(
19380 ranges,
19381 |colors| colors.colors().editor_document_highlight_read_background,
19382 cx,
19383 )
19384 }
19385
19386 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19387 self.breadcrumb_header = Some(new_header);
19388 }
19389
19390 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19391 self.clear_background_highlights::<SearchWithinRange>(cx);
19392 }
19393
19394 pub fn highlight_background<T: 'static>(
19395 &mut self,
19396 ranges: &[Range<Anchor>],
19397 color_fetcher: fn(&Theme) -> Hsla,
19398 cx: &mut Context<Self>,
19399 ) {
19400 self.background_highlights.insert(
19401 HighlightKey::Type(TypeId::of::<T>()),
19402 (color_fetcher, Arc::from(ranges)),
19403 );
19404 self.scrollbar_marker_state.dirty = true;
19405 cx.notify();
19406 }
19407
19408 pub fn highlight_background_key<T: 'static>(
19409 &mut self,
19410 key: usize,
19411 ranges: &[Range<Anchor>],
19412 color_fetcher: fn(&Theme) -> Hsla,
19413 cx: &mut Context<Self>,
19414 ) {
19415 self.background_highlights.insert(
19416 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19417 (color_fetcher, Arc::from(ranges)),
19418 );
19419 self.scrollbar_marker_state.dirty = true;
19420 cx.notify();
19421 }
19422
19423 pub fn clear_background_highlights<T: 'static>(
19424 &mut self,
19425 cx: &mut Context<Self>,
19426 ) -> Option<BackgroundHighlight> {
19427 let text_highlights = self
19428 .background_highlights
19429 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19430 if !text_highlights.1.is_empty() {
19431 self.scrollbar_marker_state.dirty = true;
19432 cx.notify();
19433 }
19434 Some(text_highlights)
19435 }
19436
19437 pub fn highlight_gutter<T: 'static>(
19438 &mut self,
19439 ranges: impl Into<Vec<Range<Anchor>>>,
19440 color_fetcher: fn(&App) -> Hsla,
19441 cx: &mut Context<Self>,
19442 ) {
19443 self.gutter_highlights
19444 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19445 cx.notify();
19446 }
19447
19448 pub fn clear_gutter_highlights<T: 'static>(
19449 &mut self,
19450 cx: &mut Context<Self>,
19451 ) -> Option<GutterHighlight> {
19452 cx.notify();
19453 self.gutter_highlights.remove(&TypeId::of::<T>())
19454 }
19455
19456 pub fn insert_gutter_highlight<T: 'static>(
19457 &mut self,
19458 range: Range<Anchor>,
19459 color_fetcher: fn(&App) -> Hsla,
19460 cx: &mut Context<Self>,
19461 ) {
19462 let snapshot = self.buffer().read(cx).snapshot(cx);
19463 let mut highlights = self
19464 .gutter_highlights
19465 .remove(&TypeId::of::<T>())
19466 .map(|(_, highlights)| highlights)
19467 .unwrap_or_default();
19468 let ix = highlights.binary_search_by(|highlight| {
19469 Ordering::Equal
19470 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19471 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19472 });
19473 if let Err(ix) = ix {
19474 highlights.insert(ix, range);
19475 }
19476 self.gutter_highlights
19477 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19478 }
19479
19480 pub fn remove_gutter_highlights<T: 'static>(
19481 &mut self,
19482 ranges_to_remove: Vec<Range<Anchor>>,
19483 cx: &mut Context<Self>,
19484 ) {
19485 let snapshot = self.buffer().read(cx).snapshot(cx);
19486 let Some((color_fetcher, mut gutter_highlights)) =
19487 self.gutter_highlights.remove(&TypeId::of::<T>())
19488 else {
19489 return;
19490 };
19491 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19492 gutter_highlights.retain(|highlight| {
19493 while let Some(range_to_remove) = ranges_to_remove.peek() {
19494 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19495 Ordering::Less | Ordering::Equal => {
19496 ranges_to_remove.next();
19497 }
19498 Ordering::Greater => {
19499 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19500 Ordering::Less | Ordering::Equal => {
19501 return false;
19502 }
19503 Ordering::Greater => break,
19504 }
19505 }
19506 }
19507 }
19508
19509 true
19510 });
19511 self.gutter_highlights
19512 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19513 }
19514
19515 #[cfg(feature = "test-support")]
19516 pub fn all_text_highlights(
19517 &self,
19518 window: &mut Window,
19519 cx: &mut Context<Self>,
19520 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19521 let snapshot = self.snapshot(window, cx);
19522 self.display_map.update(cx, |display_map, _| {
19523 display_map
19524 .all_text_highlights()
19525 .map(|highlight| {
19526 let (style, ranges) = highlight.as_ref();
19527 (
19528 *style,
19529 ranges
19530 .iter()
19531 .map(|range| range.clone().to_display_points(&snapshot))
19532 .collect(),
19533 )
19534 })
19535 .collect()
19536 })
19537 }
19538
19539 #[cfg(feature = "test-support")]
19540 pub fn all_text_background_highlights(
19541 &self,
19542 window: &mut Window,
19543 cx: &mut Context<Self>,
19544 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19545 let snapshot = self.snapshot(window, cx);
19546 let buffer = &snapshot.buffer_snapshot;
19547 let start = buffer.anchor_before(0);
19548 let end = buffer.anchor_after(buffer.len());
19549 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19550 }
19551
19552 #[cfg(feature = "test-support")]
19553 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19554 let snapshot = self.buffer().read(cx).snapshot(cx);
19555
19556 let highlights = self
19557 .background_highlights
19558 .get(&HighlightKey::Type(TypeId::of::<
19559 items::BufferSearchHighlights,
19560 >()));
19561
19562 if let Some((_color, ranges)) = highlights {
19563 ranges
19564 .iter()
19565 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19566 .collect_vec()
19567 } else {
19568 vec![]
19569 }
19570 }
19571
19572 fn document_highlights_for_position<'a>(
19573 &'a self,
19574 position: Anchor,
19575 buffer: &'a MultiBufferSnapshot,
19576 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19577 let read_highlights = self
19578 .background_highlights
19579 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19580 .map(|h| &h.1);
19581 let write_highlights = self
19582 .background_highlights
19583 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19584 .map(|h| &h.1);
19585 let left_position = position.bias_left(buffer);
19586 let right_position = position.bias_right(buffer);
19587 read_highlights
19588 .into_iter()
19589 .chain(write_highlights)
19590 .flat_map(move |ranges| {
19591 let start_ix = match ranges.binary_search_by(|probe| {
19592 let cmp = probe.end.cmp(&left_position, buffer);
19593 if cmp.is_ge() {
19594 Ordering::Greater
19595 } else {
19596 Ordering::Less
19597 }
19598 }) {
19599 Ok(i) | Err(i) => i,
19600 };
19601
19602 ranges[start_ix..]
19603 .iter()
19604 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19605 })
19606 }
19607
19608 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19609 self.background_highlights
19610 .get(&HighlightKey::Type(TypeId::of::<T>()))
19611 .is_some_and(|(_, highlights)| !highlights.is_empty())
19612 }
19613
19614 pub fn background_highlights_in_range(
19615 &self,
19616 search_range: Range<Anchor>,
19617 display_snapshot: &DisplaySnapshot,
19618 theme: &Theme,
19619 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19620 let mut results = Vec::new();
19621 for (color_fetcher, ranges) in self.background_highlights.values() {
19622 let color = color_fetcher(theme);
19623 let start_ix = match ranges.binary_search_by(|probe| {
19624 let cmp = probe
19625 .end
19626 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19627 if cmp.is_gt() {
19628 Ordering::Greater
19629 } else {
19630 Ordering::Less
19631 }
19632 }) {
19633 Ok(i) | Err(i) => i,
19634 };
19635 for range in &ranges[start_ix..] {
19636 if range
19637 .start
19638 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19639 .is_ge()
19640 {
19641 break;
19642 }
19643
19644 let start = range.start.to_display_point(display_snapshot);
19645 let end = range.end.to_display_point(display_snapshot);
19646 results.push((start..end, color))
19647 }
19648 }
19649 results
19650 }
19651
19652 pub fn background_highlight_row_ranges<T: 'static>(
19653 &self,
19654 search_range: Range<Anchor>,
19655 display_snapshot: &DisplaySnapshot,
19656 count: usize,
19657 ) -> Vec<RangeInclusive<DisplayPoint>> {
19658 let mut results = Vec::new();
19659 let Some((_, ranges)) = self
19660 .background_highlights
19661 .get(&HighlightKey::Type(TypeId::of::<T>()))
19662 else {
19663 return vec![];
19664 };
19665
19666 let start_ix = match ranges.binary_search_by(|probe| {
19667 let cmp = probe
19668 .end
19669 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19670 if cmp.is_gt() {
19671 Ordering::Greater
19672 } else {
19673 Ordering::Less
19674 }
19675 }) {
19676 Ok(i) | Err(i) => i,
19677 };
19678 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19679 if let (Some(start_display), Some(end_display)) = (start, end) {
19680 results.push(
19681 start_display.to_display_point(display_snapshot)
19682 ..=end_display.to_display_point(display_snapshot),
19683 );
19684 }
19685 };
19686 let mut start_row: Option<Point> = None;
19687 let mut end_row: Option<Point> = None;
19688 if ranges.len() > count {
19689 return Vec::new();
19690 }
19691 for range in &ranges[start_ix..] {
19692 if range
19693 .start
19694 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19695 .is_ge()
19696 {
19697 break;
19698 }
19699 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19700 if let Some(current_row) = &end_row
19701 && end.row == current_row.row
19702 {
19703 continue;
19704 }
19705 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19706 if start_row.is_none() {
19707 assert_eq!(end_row, None);
19708 start_row = Some(start);
19709 end_row = Some(end);
19710 continue;
19711 }
19712 if let Some(current_end) = end_row.as_mut() {
19713 if start.row > current_end.row + 1 {
19714 push_region(start_row, end_row);
19715 start_row = Some(start);
19716 end_row = Some(end);
19717 } else {
19718 // Merge two hunks.
19719 *current_end = end;
19720 }
19721 } else {
19722 unreachable!();
19723 }
19724 }
19725 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19726 push_region(start_row, end_row);
19727 results
19728 }
19729
19730 pub fn gutter_highlights_in_range(
19731 &self,
19732 search_range: Range<Anchor>,
19733 display_snapshot: &DisplaySnapshot,
19734 cx: &App,
19735 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19736 let mut results = Vec::new();
19737 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19738 let color = color_fetcher(cx);
19739 let start_ix = match ranges.binary_search_by(|probe| {
19740 let cmp = probe
19741 .end
19742 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19743 if cmp.is_gt() {
19744 Ordering::Greater
19745 } else {
19746 Ordering::Less
19747 }
19748 }) {
19749 Ok(i) | Err(i) => i,
19750 };
19751 for range in &ranges[start_ix..] {
19752 if range
19753 .start
19754 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19755 .is_ge()
19756 {
19757 break;
19758 }
19759
19760 let start = range.start.to_display_point(display_snapshot);
19761 let end = range.end.to_display_point(display_snapshot);
19762 results.push((start..end, color))
19763 }
19764 }
19765 results
19766 }
19767
19768 /// Get the text ranges corresponding to the redaction query
19769 pub fn redacted_ranges(
19770 &self,
19771 search_range: Range<Anchor>,
19772 display_snapshot: &DisplaySnapshot,
19773 cx: &App,
19774 ) -> Vec<Range<DisplayPoint>> {
19775 display_snapshot
19776 .buffer_snapshot
19777 .redacted_ranges(search_range, |file| {
19778 if let Some(file) = file {
19779 file.is_private()
19780 && EditorSettings::get(
19781 Some(SettingsLocation {
19782 worktree_id: file.worktree_id(cx),
19783 path: file.path().as_ref(),
19784 }),
19785 cx,
19786 )
19787 .redact_private_values
19788 } else {
19789 false
19790 }
19791 })
19792 .map(|range| {
19793 range.start.to_display_point(display_snapshot)
19794 ..range.end.to_display_point(display_snapshot)
19795 })
19796 .collect()
19797 }
19798
19799 pub fn highlight_text_key<T: 'static>(
19800 &mut self,
19801 key: usize,
19802 ranges: Vec<Range<Anchor>>,
19803 style: HighlightStyle,
19804 cx: &mut Context<Self>,
19805 ) {
19806 self.display_map.update(cx, |map, _| {
19807 map.highlight_text(
19808 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19809 ranges,
19810 style,
19811 );
19812 });
19813 cx.notify();
19814 }
19815
19816 pub fn highlight_text<T: 'static>(
19817 &mut self,
19818 ranges: Vec<Range<Anchor>>,
19819 style: HighlightStyle,
19820 cx: &mut Context<Self>,
19821 ) {
19822 self.display_map.update(cx, |map, _| {
19823 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19824 });
19825 cx.notify();
19826 }
19827
19828 pub(crate) fn highlight_inlays<T: 'static>(
19829 &mut self,
19830 highlights: Vec<InlayHighlight>,
19831 style: HighlightStyle,
19832 cx: &mut Context<Self>,
19833 ) {
19834 self.display_map.update(cx, |map, _| {
19835 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19836 });
19837 cx.notify();
19838 }
19839
19840 pub fn text_highlights<'a, T: 'static>(
19841 &'a self,
19842 cx: &'a App,
19843 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19844 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19845 }
19846
19847 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19848 let cleared = self
19849 .display_map
19850 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19851 if cleared {
19852 cx.notify();
19853 }
19854 }
19855
19856 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19857 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19858 && self.focus_handle.is_focused(window)
19859 }
19860
19861 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19862 self.show_cursor_when_unfocused = is_enabled;
19863 cx.notify();
19864 }
19865
19866 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19867 cx.notify();
19868 }
19869
19870 fn on_debug_session_event(
19871 &mut self,
19872 _session: Entity<Session>,
19873 event: &SessionEvent,
19874 cx: &mut Context<Self>,
19875 ) {
19876 if let SessionEvent::InvalidateInlineValue = event {
19877 self.refresh_inline_values(cx);
19878 }
19879 }
19880
19881 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19882 let Some(project) = self.project.clone() else {
19883 return;
19884 };
19885
19886 if !self.inline_value_cache.enabled {
19887 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19888 self.splice_inlays(&inlays, Vec::new(), cx);
19889 return;
19890 }
19891
19892 let current_execution_position = self
19893 .highlighted_rows
19894 .get(&TypeId::of::<ActiveDebugLine>())
19895 .and_then(|lines| lines.last().map(|line| line.range.end));
19896
19897 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19898 let inline_values = editor
19899 .update(cx, |editor, cx| {
19900 let Some(current_execution_position) = current_execution_position else {
19901 return Some(Task::ready(Ok(Vec::new())));
19902 };
19903
19904 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19905 let snapshot = buffer.snapshot(cx);
19906
19907 let excerpt = snapshot.excerpt_containing(
19908 current_execution_position..current_execution_position,
19909 )?;
19910
19911 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19912 })?;
19913
19914 let range =
19915 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19916
19917 project.inline_values(buffer, range, cx)
19918 })
19919 .ok()
19920 .flatten()?
19921 .await
19922 .context("refreshing debugger inlays")
19923 .log_err()?;
19924
19925 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19926
19927 for (buffer_id, inline_value) in inline_values
19928 .into_iter()
19929 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19930 {
19931 buffer_inline_values
19932 .entry(buffer_id)
19933 .or_default()
19934 .push(inline_value);
19935 }
19936
19937 editor
19938 .update(cx, |editor, cx| {
19939 let snapshot = editor.buffer.read(cx).snapshot(cx);
19940 let mut new_inlays = Vec::default();
19941
19942 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19943 let buffer_id = buffer_snapshot.remote_id();
19944 buffer_inline_values
19945 .get(&buffer_id)
19946 .into_iter()
19947 .flatten()
19948 .for_each(|hint| {
19949 let inlay = Inlay::debugger(
19950 post_inc(&mut editor.next_inlay_id),
19951 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19952 hint.text(),
19953 );
19954 if !inlay.text.chars().contains(&'\n') {
19955 new_inlays.push(inlay);
19956 }
19957 });
19958 }
19959
19960 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19961 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19962
19963 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19964 })
19965 .ok()?;
19966 Some(())
19967 });
19968 }
19969
19970 fn on_buffer_event(
19971 &mut self,
19972 multibuffer: &Entity<MultiBuffer>,
19973 event: &multi_buffer::Event,
19974 window: &mut Window,
19975 cx: &mut Context<Self>,
19976 ) {
19977 match event {
19978 multi_buffer::Event::Edited {
19979 singleton_buffer_edited,
19980 edited_buffer,
19981 } => {
19982 self.scrollbar_marker_state.dirty = true;
19983 self.active_indent_guides_state.dirty = true;
19984 self.refresh_active_diagnostics(cx);
19985 self.refresh_code_actions(window, cx);
19986 self.refresh_selected_text_highlights(true, window, cx);
19987 self.refresh_single_line_folds(window, cx);
19988 refresh_matching_bracket_highlights(self, window, cx);
19989 if self.has_active_edit_prediction() {
19990 self.update_visible_edit_prediction(window, cx);
19991 }
19992 if let Some(project) = self.project.as_ref()
19993 && let Some(edited_buffer) = edited_buffer
19994 {
19995 project.update(cx, |project, cx| {
19996 self.registered_buffers
19997 .entry(edited_buffer.read(cx).remote_id())
19998 .or_insert_with(|| {
19999 project.register_buffer_with_language_servers(edited_buffer, cx)
20000 });
20001 });
20002 }
20003 cx.emit(EditorEvent::BufferEdited);
20004 cx.emit(SearchEvent::MatchesInvalidated);
20005
20006 if let Some(buffer) = edited_buffer {
20007 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20008 }
20009
20010 if *singleton_buffer_edited {
20011 if let Some(buffer) = edited_buffer
20012 && buffer.read(cx).file().is_none()
20013 {
20014 cx.emit(EditorEvent::TitleChanged);
20015 }
20016 if let Some(project) = &self.project {
20017 #[allow(clippy::mutable_key_type)]
20018 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20019 multibuffer
20020 .all_buffers()
20021 .into_iter()
20022 .filter_map(|buffer| {
20023 buffer.update(cx, |buffer, cx| {
20024 let language = buffer.language()?;
20025 let should_discard = project.update(cx, |project, cx| {
20026 project.is_local()
20027 && !project.has_language_servers_for(buffer, cx)
20028 });
20029 should_discard.not().then_some(language.clone())
20030 })
20031 })
20032 .collect::<HashSet<_>>()
20033 });
20034 if !languages_affected.is_empty() {
20035 self.refresh_inlay_hints(
20036 InlayHintRefreshReason::BufferEdited(languages_affected),
20037 cx,
20038 );
20039 }
20040 }
20041 }
20042
20043 let Some(project) = &self.project else { return };
20044 let (telemetry, is_via_ssh) = {
20045 let project = project.read(cx);
20046 let telemetry = project.client().telemetry().clone();
20047 let is_via_ssh = project.is_via_ssh();
20048 (telemetry, is_via_ssh)
20049 };
20050 refresh_linked_ranges(self, window, cx);
20051 telemetry.log_edit_event("editor", is_via_ssh);
20052 }
20053 multi_buffer::Event::ExcerptsAdded {
20054 buffer,
20055 predecessor,
20056 excerpts,
20057 } => {
20058 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20059 let buffer_id = buffer.read(cx).remote_id();
20060 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20061 && let Some(project) = &self.project
20062 {
20063 update_uncommitted_diff_for_buffer(
20064 cx.entity(),
20065 project,
20066 [buffer.clone()],
20067 self.buffer.clone(),
20068 cx,
20069 )
20070 .detach();
20071 }
20072 self.update_lsp_data(false, Some(buffer_id), window, cx);
20073 cx.emit(EditorEvent::ExcerptsAdded {
20074 buffer: buffer.clone(),
20075 predecessor: *predecessor,
20076 excerpts: excerpts.clone(),
20077 });
20078 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20079 }
20080 multi_buffer::Event::ExcerptsRemoved {
20081 ids,
20082 removed_buffer_ids,
20083 } => {
20084 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20085 let buffer = self.buffer.read(cx);
20086 self.registered_buffers
20087 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20088 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20089 cx.emit(EditorEvent::ExcerptsRemoved {
20090 ids: ids.clone(),
20091 removed_buffer_ids: removed_buffer_ids.clone(),
20092 });
20093 }
20094 multi_buffer::Event::ExcerptsEdited {
20095 excerpt_ids,
20096 buffer_ids,
20097 } => {
20098 self.display_map.update(cx, |map, cx| {
20099 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20100 });
20101 cx.emit(EditorEvent::ExcerptsEdited {
20102 ids: excerpt_ids.clone(),
20103 });
20104 }
20105 multi_buffer::Event::ExcerptsExpanded { ids } => {
20106 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20107 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20108 }
20109 multi_buffer::Event::Reparsed(buffer_id) => {
20110 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20111 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20112
20113 cx.emit(EditorEvent::Reparsed(*buffer_id));
20114 }
20115 multi_buffer::Event::DiffHunksToggled => {
20116 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20117 }
20118 multi_buffer::Event::LanguageChanged(buffer_id) => {
20119 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20120 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20121 cx.emit(EditorEvent::Reparsed(*buffer_id));
20122 cx.notify();
20123 }
20124 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20125 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20126 multi_buffer::Event::FileHandleChanged
20127 | multi_buffer::Event::Reloaded
20128 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20129 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20130 multi_buffer::Event::DiagnosticsUpdated => {
20131 self.update_diagnostics_state(window, cx);
20132 }
20133 _ => {}
20134 };
20135 }
20136
20137 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20138 if !self.diagnostics_enabled() {
20139 return;
20140 }
20141 self.refresh_active_diagnostics(cx);
20142 self.refresh_inline_diagnostics(true, window, cx);
20143 self.scrollbar_marker_state.dirty = true;
20144 cx.notify();
20145 }
20146
20147 pub fn start_temporary_diff_override(&mut self) {
20148 self.load_diff_task.take();
20149 self.temporary_diff_override = true;
20150 }
20151
20152 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20153 self.temporary_diff_override = false;
20154 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20155 self.buffer.update(cx, |buffer, cx| {
20156 buffer.set_all_diff_hunks_collapsed(cx);
20157 });
20158
20159 if let Some(project) = self.project.clone() {
20160 self.load_diff_task = Some(
20161 update_uncommitted_diff_for_buffer(
20162 cx.entity(),
20163 &project,
20164 self.buffer.read(cx).all_buffers(),
20165 self.buffer.clone(),
20166 cx,
20167 )
20168 .shared(),
20169 );
20170 }
20171 }
20172
20173 fn on_display_map_changed(
20174 &mut self,
20175 _: Entity<DisplayMap>,
20176 _: &mut Window,
20177 cx: &mut Context<Self>,
20178 ) {
20179 cx.notify();
20180 }
20181
20182 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20183 if self.diagnostics_enabled() {
20184 let new_severity = EditorSettings::get_global(cx)
20185 .diagnostics_max_severity
20186 .unwrap_or(DiagnosticSeverity::Hint);
20187 self.set_max_diagnostics_severity(new_severity, cx);
20188 }
20189 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20190 self.update_edit_prediction_settings(cx);
20191 self.refresh_edit_prediction(true, false, window, cx);
20192 self.refresh_inline_values(cx);
20193 self.refresh_inlay_hints(
20194 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20195 self.selections.newest_anchor().head(),
20196 &self.buffer.read(cx).snapshot(cx),
20197 cx,
20198 )),
20199 cx,
20200 );
20201
20202 let old_cursor_shape = self.cursor_shape;
20203 let old_show_breadcrumbs = self.show_breadcrumbs;
20204
20205 {
20206 let editor_settings = EditorSettings::get_global(cx);
20207 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20208 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20209 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20210 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20211 }
20212
20213 if old_cursor_shape != self.cursor_shape {
20214 cx.emit(EditorEvent::CursorShapeChanged);
20215 }
20216
20217 if old_show_breadcrumbs != self.show_breadcrumbs {
20218 cx.emit(EditorEvent::BreadcrumbsChanged);
20219 }
20220
20221 let project_settings = ProjectSettings::get_global(cx);
20222 self.serialize_dirty_buffers =
20223 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20224
20225 if self.mode.is_full() {
20226 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20227 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20228 if self.show_inline_diagnostics != show_inline_diagnostics {
20229 self.show_inline_diagnostics = show_inline_diagnostics;
20230 self.refresh_inline_diagnostics(false, window, cx);
20231 }
20232
20233 if self.git_blame_inline_enabled != inline_blame_enabled {
20234 self.toggle_git_blame_inline_internal(false, window, cx);
20235 }
20236
20237 let minimap_settings = EditorSettings::get_global(cx).minimap;
20238 if self.minimap_visibility != MinimapVisibility::Disabled {
20239 if self.minimap_visibility.settings_visibility()
20240 != minimap_settings.minimap_enabled()
20241 {
20242 self.set_minimap_visibility(
20243 MinimapVisibility::for_mode(self.mode(), cx),
20244 window,
20245 cx,
20246 );
20247 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20248 minimap_entity.update(cx, |minimap_editor, cx| {
20249 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20250 })
20251 }
20252 }
20253 }
20254
20255 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20256 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20257 }) {
20258 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20259 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20260 }
20261 self.refresh_colors(false, None, window, cx);
20262 }
20263
20264 cx.notify();
20265 }
20266
20267 pub fn set_searchable(&mut self, searchable: bool) {
20268 self.searchable = searchable;
20269 }
20270
20271 pub fn searchable(&self) -> bool {
20272 self.searchable
20273 }
20274
20275 fn open_proposed_changes_editor(
20276 &mut self,
20277 _: &OpenProposedChangesEditor,
20278 window: &mut Window,
20279 cx: &mut Context<Self>,
20280 ) {
20281 let Some(workspace) = self.workspace() else {
20282 cx.propagate();
20283 return;
20284 };
20285
20286 let selections = self.selections.all::<usize>(cx);
20287 let multi_buffer = self.buffer.read(cx);
20288 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20289 let mut new_selections_by_buffer = HashMap::default();
20290 for selection in selections {
20291 for (buffer, range, _) in
20292 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20293 {
20294 let mut range = range.to_point(buffer);
20295 range.start.column = 0;
20296 range.end.column = buffer.line_len(range.end.row);
20297 new_selections_by_buffer
20298 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20299 .or_insert(Vec::new())
20300 .push(range)
20301 }
20302 }
20303
20304 let proposed_changes_buffers = new_selections_by_buffer
20305 .into_iter()
20306 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20307 .collect::<Vec<_>>();
20308 let proposed_changes_editor = cx.new(|cx| {
20309 ProposedChangesEditor::new(
20310 "Proposed changes",
20311 proposed_changes_buffers,
20312 self.project.clone(),
20313 window,
20314 cx,
20315 )
20316 });
20317
20318 window.defer(cx, move |window, cx| {
20319 workspace.update(cx, |workspace, cx| {
20320 workspace.active_pane().update(cx, |pane, cx| {
20321 pane.add_item(
20322 Box::new(proposed_changes_editor),
20323 true,
20324 true,
20325 None,
20326 window,
20327 cx,
20328 );
20329 });
20330 });
20331 });
20332 }
20333
20334 pub fn open_excerpts_in_split(
20335 &mut self,
20336 _: &OpenExcerptsSplit,
20337 window: &mut Window,
20338 cx: &mut Context<Self>,
20339 ) {
20340 self.open_excerpts_common(None, true, window, cx)
20341 }
20342
20343 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20344 self.open_excerpts_common(None, false, window, cx)
20345 }
20346
20347 fn open_excerpts_common(
20348 &mut self,
20349 jump_data: Option<JumpData>,
20350 split: bool,
20351 window: &mut Window,
20352 cx: &mut Context<Self>,
20353 ) {
20354 let Some(workspace) = self.workspace() else {
20355 cx.propagate();
20356 return;
20357 };
20358
20359 if self.buffer.read(cx).is_singleton() {
20360 cx.propagate();
20361 return;
20362 }
20363
20364 let mut new_selections_by_buffer = HashMap::default();
20365 match &jump_data {
20366 Some(JumpData::MultiBufferPoint {
20367 excerpt_id,
20368 position,
20369 anchor,
20370 line_offset_from_top,
20371 }) => {
20372 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20373 if let Some(buffer) = multi_buffer_snapshot
20374 .buffer_id_for_excerpt(*excerpt_id)
20375 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20376 {
20377 let buffer_snapshot = buffer.read(cx).snapshot();
20378 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20379 language::ToPoint::to_point(anchor, &buffer_snapshot)
20380 } else {
20381 buffer_snapshot.clip_point(*position, Bias::Left)
20382 };
20383 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20384 new_selections_by_buffer.insert(
20385 buffer,
20386 (
20387 vec![jump_to_offset..jump_to_offset],
20388 Some(*line_offset_from_top),
20389 ),
20390 );
20391 }
20392 }
20393 Some(JumpData::MultiBufferRow {
20394 row,
20395 line_offset_from_top,
20396 }) => {
20397 let point = MultiBufferPoint::new(row.0, 0);
20398 if let Some((buffer, buffer_point, _)) =
20399 self.buffer.read(cx).point_to_buffer_point(point, cx)
20400 {
20401 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20402 new_selections_by_buffer
20403 .entry(buffer)
20404 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20405 .0
20406 .push(buffer_offset..buffer_offset)
20407 }
20408 }
20409 None => {
20410 let selections = self.selections.all::<usize>(cx);
20411 let multi_buffer = self.buffer.read(cx);
20412 for selection in selections {
20413 for (snapshot, range, _, anchor) in multi_buffer
20414 .snapshot(cx)
20415 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20416 {
20417 if let Some(anchor) = anchor {
20418 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20419 else {
20420 continue;
20421 };
20422 let offset = text::ToOffset::to_offset(
20423 &anchor.text_anchor,
20424 &buffer_handle.read(cx).snapshot(),
20425 );
20426 let range = offset..offset;
20427 new_selections_by_buffer
20428 .entry(buffer_handle)
20429 .or_insert((Vec::new(), None))
20430 .0
20431 .push(range)
20432 } else {
20433 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20434 else {
20435 continue;
20436 };
20437 new_selections_by_buffer
20438 .entry(buffer_handle)
20439 .or_insert((Vec::new(), None))
20440 .0
20441 .push(range)
20442 }
20443 }
20444 }
20445 }
20446 }
20447
20448 new_selections_by_buffer
20449 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20450
20451 if new_selections_by_buffer.is_empty() {
20452 return;
20453 }
20454
20455 // We defer the pane interaction because we ourselves are a workspace item
20456 // and activating a new item causes the pane to call a method on us reentrantly,
20457 // which panics if we're on the stack.
20458 window.defer(cx, move |window, cx| {
20459 workspace.update(cx, |workspace, cx| {
20460 let pane = if split {
20461 workspace.adjacent_pane(window, cx)
20462 } else {
20463 workspace.active_pane().clone()
20464 };
20465
20466 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20467 let editor = buffer
20468 .read(cx)
20469 .file()
20470 .is_none()
20471 .then(|| {
20472 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20473 // so `workspace.open_project_item` will never find them, always opening a new editor.
20474 // Instead, we try to activate the existing editor in the pane first.
20475 let (editor, pane_item_index) =
20476 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20477 let editor = item.downcast::<Editor>()?;
20478 let singleton_buffer =
20479 editor.read(cx).buffer().read(cx).as_singleton()?;
20480 if singleton_buffer == buffer {
20481 Some((editor, i))
20482 } else {
20483 None
20484 }
20485 })?;
20486 pane.update(cx, |pane, cx| {
20487 pane.activate_item(pane_item_index, true, true, window, cx)
20488 });
20489 Some(editor)
20490 })
20491 .flatten()
20492 .unwrap_or_else(|| {
20493 workspace.open_project_item::<Self>(
20494 pane.clone(),
20495 buffer,
20496 true,
20497 true,
20498 window,
20499 cx,
20500 )
20501 });
20502
20503 editor.update(cx, |editor, cx| {
20504 let autoscroll = match scroll_offset {
20505 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20506 None => Autoscroll::newest(),
20507 };
20508 let nav_history = editor.nav_history.take();
20509 editor.change_selections(
20510 SelectionEffects::scroll(autoscroll),
20511 window,
20512 cx,
20513 |s| {
20514 s.select_ranges(ranges);
20515 },
20516 );
20517 editor.nav_history = nav_history;
20518 });
20519 }
20520 })
20521 });
20522 }
20523
20524 // For now, don't allow opening excerpts in buffers that aren't backed by
20525 // regular project files.
20526 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20527 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20528 }
20529
20530 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20531 let snapshot = self.buffer.read(cx).read(cx);
20532 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20533 Some(
20534 ranges
20535 .iter()
20536 .map(move |range| {
20537 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20538 })
20539 .collect(),
20540 )
20541 }
20542
20543 fn selection_replacement_ranges(
20544 &self,
20545 range: Range<OffsetUtf16>,
20546 cx: &mut App,
20547 ) -> Vec<Range<OffsetUtf16>> {
20548 let selections = self.selections.all::<OffsetUtf16>(cx);
20549 let newest_selection = selections
20550 .iter()
20551 .max_by_key(|selection| selection.id)
20552 .unwrap();
20553 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20554 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20555 let snapshot = self.buffer.read(cx).read(cx);
20556 selections
20557 .into_iter()
20558 .map(|mut selection| {
20559 selection.start.0 =
20560 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20561 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20562 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20563 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20564 })
20565 .collect()
20566 }
20567
20568 fn report_editor_event(
20569 &self,
20570 reported_event: ReportEditorEvent,
20571 file_extension: Option<String>,
20572 cx: &App,
20573 ) {
20574 if cfg!(any(test, feature = "test-support")) {
20575 return;
20576 }
20577
20578 let Some(project) = &self.project else { return };
20579
20580 // If None, we are in a file without an extension
20581 let file = self
20582 .buffer
20583 .read(cx)
20584 .as_singleton()
20585 .and_then(|b| b.read(cx).file());
20586 let file_extension = file_extension.or(file
20587 .as_ref()
20588 .and_then(|file| Path::new(file.file_name(cx)).extension())
20589 .and_then(|e| e.to_str())
20590 .map(|a| a.to_string()));
20591
20592 let vim_mode = vim_enabled(cx);
20593
20594 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20595 let copilot_enabled = edit_predictions_provider
20596 == language::language_settings::EditPredictionProvider::Copilot;
20597 let copilot_enabled_for_language = self
20598 .buffer
20599 .read(cx)
20600 .language_settings(cx)
20601 .show_edit_predictions;
20602
20603 let project = project.read(cx);
20604 let event_type = reported_event.event_type();
20605
20606 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20607 telemetry::event!(
20608 event_type,
20609 type = if auto_saved {"autosave"} else {"manual"},
20610 file_extension,
20611 vim_mode,
20612 copilot_enabled,
20613 copilot_enabled_for_language,
20614 edit_predictions_provider,
20615 is_via_ssh = project.is_via_ssh(),
20616 );
20617 } else {
20618 telemetry::event!(
20619 event_type,
20620 file_extension,
20621 vim_mode,
20622 copilot_enabled,
20623 copilot_enabled_for_language,
20624 edit_predictions_provider,
20625 is_via_ssh = project.is_via_ssh(),
20626 );
20627 };
20628 }
20629
20630 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20631 /// with each line being an array of {text, highlight} objects.
20632 fn copy_highlight_json(
20633 &mut self,
20634 _: &CopyHighlightJson,
20635 window: &mut Window,
20636 cx: &mut Context<Self>,
20637 ) {
20638 #[derive(Serialize)]
20639 struct Chunk<'a> {
20640 text: String,
20641 highlight: Option<&'a str>,
20642 }
20643
20644 let snapshot = self.buffer.read(cx).snapshot(cx);
20645 let range = self
20646 .selected_text_range(false, window, cx)
20647 .and_then(|selection| {
20648 if selection.range.is_empty() {
20649 None
20650 } else {
20651 Some(selection.range)
20652 }
20653 })
20654 .unwrap_or_else(|| 0..snapshot.len());
20655
20656 let chunks = snapshot.chunks(range, true);
20657 let mut lines = Vec::new();
20658 let mut line: VecDeque<Chunk> = VecDeque::new();
20659
20660 let Some(style) = self.style.as_ref() else {
20661 return;
20662 };
20663
20664 for chunk in chunks {
20665 let highlight = chunk
20666 .syntax_highlight_id
20667 .and_then(|id| id.name(&style.syntax));
20668 let mut chunk_lines = chunk.text.split('\n').peekable();
20669 while let Some(text) = chunk_lines.next() {
20670 let mut merged_with_last_token = false;
20671 if let Some(last_token) = line.back_mut()
20672 && last_token.highlight == highlight
20673 {
20674 last_token.text.push_str(text);
20675 merged_with_last_token = true;
20676 }
20677
20678 if !merged_with_last_token {
20679 line.push_back(Chunk {
20680 text: text.into(),
20681 highlight,
20682 });
20683 }
20684
20685 if chunk_lines.peek().is_some() {
20686 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20687 line.pop_front();
20688 }
20689 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20690 line.pop_back();
20691 }
20692
20693 lines.push(mem::take(&mut line));
20694 }
20695 }
20696 }
20697
20698 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20699 return;
20700 };
20701 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20702 }
20703
20704 pub fn open_context_menu(
20705 &mut self,
20706 _: &OpenContextMenu,
20707 window: &mut Window,
20708 cx: &mut Context<Self>,
20709 ) {
20710 self.request_autoscroll(Autoscroll::newest(), cx);
20711 let position = self.selections.newest_display(cx).start;
20712 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20713 }
20714
20715 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20716 &self.inlay_hint_cache
20717 }
20718
20719 pub fn replay_insert_event(
20720 &mut self,
20721 text: &str,
20722 relative_utf16_range: Option<Range<isize>>,
20723 window: &mut Window,
20724 cx: &mut Context<Self>,
20725 ) {
20726 if !self.input_enabled {
20727 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20728 return;
20729 }
20730 if let Some(relative_utf16_range) = relative_utf16_range {
20731 let selections = self.selections.all::<OffsetUtf16>(cx);
20732 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20733 let new_ranges = selections.into_iter().map(|range| {
20734 let start = OffsetUtf16(
20735 range
20736 .head()
20737 .0
20738 .saturating_add_signed(relative_utf16_range.start),
20739 );
20740 let end = OffsetUtf16(
20741 range
20742 .head()
20743 .0
20744 .saturating_add_signed(relative_utf16_range.end),
20745 );
20746 start..end
20747 });
20748 s.select_ranges(new_ranges);
20749 });
20750 }
20751
20752 self.handle_input(text, window, cx);
20753 }
20754
20755 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20756 let Some(provider) = self.semantics_provider.as_ref() else {
20757 return false;
20758 };
20759
20760 let mut supports = false;
20761 self.buffer().update(cx, |this, cx| {
20762 this.for_each_buffer(|buffer| {
20763 supports |= provider.supports_inlay_hints(buffer, cx);
20764 });
20765 });
20766
20767 supports
20768 }
20769
20770 pub fn is_focused(&self, window: &Window) -> bool {
20771 self.focus_handle.is_focused(window)
20772 }
20773
20774 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20775 cx.emit(EditorEvent::Focused);
20776
20777 if let Some(descendant) = self
20778 .last_focused_descendant
20779 .take()
20780 .and_then(|descendant| descendant.upgrade())
20781 {
20782 window.focus(&descendant);
20783 } else {
20784 if let Some(blame) = self.blame.as_ref() {
20785 blame.update(cx, GitBlame::focus)
20786 }
20787
20788 self.blink_manager.update(cx, BlinkManager::enable);
20789 self.show_cursor_names(window, cx);
20790 self.buffer.update(cx, |buffer, cx| {
20791 buffer.finalize_last_transaction(cx);
20792 if self.leader_id.is_none() {
20793 buffer.set_active_selections(
20794 &self.selections.disjoint_anchors(),
20795 self.selections.line_mode,
20796 self.cursor_shape,
20797 cx,
20798 );
20799 }
20800 });
20801 }
20802 }
20803
20804 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20805 cx.emit(EditorEvent::FocusedIn)
20806 }
20807
20808 fn handle_focus_out(
20809 &mut self,
20810 event: FocusOutEvent,
20811 _window: &mut Window,
20812 cx: &mut Context<Self>,
20813 ) {
20814 if event.blurred != self.focus_handle {
20815 self.last_focused_descendant = Some(event.blurred);
20816 }
20817 self.selection_drag_state = SelectionDragState::None;
20818 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20819 }
20820
20821 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20822 self.blink_manager.update(cx, BlinkManager::disable);
20823 self.buffer
20824 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20825
20826 if let Some(blame) = self.blame.as_ref() {
20827 blame.update(cx, GitBlame::blur)
20828 }
20829 if !self.hover_state.focused(window, cx) {
20830 hide_hover(self, cx);
20831 }
20832 if !self
20833 .context_menu
20834 .borrow()
20835 .as_ref()
20836 .is_some_and(|context_menu| context_menu.focused(window, cx))
20837 {
20838 self.hide_context_menu(window, cx);
20839 }
20840 self.discard_edit_prediction(false, cx);
20841 cx.emit(EditorEvent::Blurred);
20842 cx.notify();
20843 }
20844
20845 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20846 let mut pending: String = window
20847 .pending_input_keystrokes()
20848 .into_iter()
20849 .flatten()
20850 .filter_map(|keystroke| {
20851 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20852 keystroke.key_char.clone()
20853 } else {
20854 None
20855 }
20856 })
20857 .collect();
20858
20859 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20860 pending = "".to_string();
20861 }
20862
20863 let existing_pending = self
20864 .text_highlights::<PendingInput>(cx)
20865 .map(|(_, ranges)| ranges.to_vec());
20866 if existing_pending.is_none() && pending.is_empty() {
20867 return;
20868 }
20869 let transaction =
20870 self.transact(window, cx, |this, window, cx| {
20871 let selections = this.selections.all::<usize>(cx);
20872 let edits = selections
20873 .iter()
20874 .map(|selection| (selection.end..selection.end, pending.clone()));
20875 this.edit(edits, cx);
20876 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20877 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20878 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20879 }));
20880 });
20881 if let Some(existing_ranges) = existing_pending {
20882 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20883 this.edit(edits, cx);
20884 }
20885 });
20886
20887 let snapshot = self.snapshot(window, cx);
20888 let ranges = self
20889 .selections
20890 .all::<usize>(cx)
20891 .into_iter()
20892 .map(|selection| {
20893 snapshot.buffer_snapshot.anchor_after(selection.end)
20894 ..snapshot
20895 .buffer_snapshot
20896 .anchor_before(selection.end + pending.len())
20897 })
20898 .collect();
20899
20900 if pending.is_empty() {
20901 self.clear_highlights::<PendingInput>(cx);
20902 } else {
20903 self.highlight_text::<PendingInput>(
20904 ranges,
20905 HighlightStyle {
20906 underline: Some(UnderlineStyle {
20907 thickness: px(1.),
20908 color: None,
20909 wavy: false,
20910 }),
20911 ..Default::default()
20912 },
20913 cx,
20914 );
20915 }
20916
20917 self.ime_transaction = self.ime_transaction.or(transaction);
20918 if let Some(transaction) = self.ime_transaction {
20919 self.buffer.update(cx, |buffer, cx| {
20920 buffer.group_until_transaction(transaction, cx);
20921 });
20922 }
20923
20924 if self.text_highlights::<PendingInput>(cx).is_none() {
20925 self.ime_transaction.take();
20926 }
20927 }
20928
20929 pub fn register_action_renderer(
20930 &mut self,
20931 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20932 ) -> Subscription {
20933 let id = self.next_editor_action_id.post_inc();
20934 self.editor_actions
20935 .borrow_mut()
20936 .insert(id, Box::new(listener));
20937
20938 let editor_actions = self.editor_actions.clone();
20939 Subscription::new(move || {
20940 editor_actions.borrow_mut().remove(&id);
20941 })
20942 }
20943
20944 pub fn register_action<A: Action>(
20945 &mut self,
20946 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20947 ) -> Subscription {
20948 let id = self.next_editor_action_id.post_inc();
20949 let listener = Arc::new(listener);
20950 self.editor_actions.borrow_mut().insert(
20951 id,
20952 Box::new(move |_, window, _| {
20953 let listener = listener.clone();
20954 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20955 let action = action.downcast_ref().unwrap();
20956 if phase == DispatchPhase::Bubble {
20957 listener(action, window, cx)
20958 }
20959 })
20960 }),
20961 );
20962
20963 let editor_actions = self.editor_actions.clone();
20964 Subscription::new(move || {
20965 editor_actions.borrow_mut().remove(&id);
20966 })
20967 }
20968
20969 pub fn file_header_size(&self) -> u32 {
20970 FILE_HEADER_HEIGHT
20971 }
20972
20973 pub fn restore(
20974 &mut self,
20975 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20976 window: &mut Window,
20977 cx: &mut Context<Self>,
20978 ) {
20979 let workspace = self.workspace();
20980 let project = self.project();
20981 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20982 let mut tasks = Vec::new();
20983 for (buffer_id, changes) in revert_changes {
20984 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20985 buffer.update(cx, |buffer, cx| {
20986 buffer.edit(
20987 changes
20988 .into_iter()
20989 .map(|(range, text)| (range, text.to_string())),
20990 None,
20991 cx,
20992 );
20993 });
20994
20995 if let Some(project) =
20996 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20997 {
20998 project.update(cx, |project, cx| {
20999 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21000 })
21001 }
21002 }
21003 }
21004 tasks
21005 });
21006 cx.spawn_in(window, async move |_, cx| {
21007 for (buffer, task) in save_tasks {
21008 let result = task.await;
21009 if result.is_err() {
21010 let Some(path) = buffer
21011 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21012 .ok()
21013 else {
21014 continue;
21015 };
21016 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21017 let Some(task) = cx
21018 .update_window_entity(workspace, |workspace, window, cx| {
21019 workspace
21020 .open_path_preview(path, None, false, false, false, window, cx)
21021 })
21022 .ok()
21023 else {
21024 continue;
21025 };
21026 task.await.log_err();
21027 }
21028 }
21029 }
21030 })
21031 .detach();
21032 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21033 selections.refresh()
21034 });
21035 }
21036
21037 pub fn to_pixel_point(
21038 &self,
21039 source: multi_buffer::Anchor,
21040 editor_snapshot: &EditorSnapshot,
21041 window: &mut Window,
21042 ) -> Option<gpui::Point<Pixels>> {
21043 let source_point = source.to_display_point(editor_snapshot);
21044 self.display_to_pixel_point(source_point, editor_snapshot, window)
21045 }
21046
21047 pub fn display_to_pixel_point(
21048 &self,
21049 source: DisplayPoint,
21050 editor_snapshot: &EditorSnapshot,
21051 window: &mut Window,
21052 ) -> Option<gpui::Point<Pixels>> {
21053 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21054 let text_layout_details = self.text_layout_details(window);
21055 let scroll_top = text_layout_details
21056 .scroll_anchor
21057 .scroll_position(editor_snapshot)
21058 .y;
21059
21060 if source.row().as_f32() < scroll_top.floor() {
21061 return None;
21062 }
21063 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21064 let source_y = line_height * (source.row().as_f32() - scroll_top);
21065 Some(gpui::Point::new(source_x, source_y))
21066 }
21067
21068 pub fn has_visible_completions_menu(&self) -> bool {
21069 !self.edit_prediction_preview_is_active()
21070 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21071 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21072 })
21073 }
21074
21075 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21076 if self.mode.is_minimap() {
21077 return;
21078 }
21079 self.addons
21080 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21081 }
21082
21083 pub fn unregister_addon<T: Addon>(&mut self) {
21084 self.addons.remove(&std::any::TypeId::of::<T>());
21085 }
21086
21087 pub fn addon<T: Addon>(&self) -> Option<&T> {
21088 let type_id = std::any::TypeId::of::<T>();
21089 self.addons
21090 .get(&type_id)
21091 .and_then(|item| item.to_any().downcast_ref::<T>())
21092 }
21093
21094 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21095 let type_id = std::any::TypeId::of::<T>();
21096 self.addons
21097 .get_mut(&type_id)
21098 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21099 }
21100
21101 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21102 let text_layout_details = self.text_layout_details(window);
21103 let style = &text_layout_details.editor_style;
21104 let font_id = window.text_system().resolve_font(&style.text.font());
21105 let font_size = style.text.font_size.to_pixels(window.rem_size());
21106 let line_height = style.text.line_height_in_pixels(window.rem_size());
21107 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21108 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21109
21110 CharacterDimensions {
21111 em_width,
21112 em_advance,
21113 line_height,
21114 }
21115 }
21116
21117 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21118 self.load_diff_task.clone()
21119 }
21120
21121 fn read_metadata_from_db(
21122 &mut self,
21123 item_id: u64,
21124 workspace_id: WorkspaceId,
21125 window: &mut Window,
21126 cx: &mut Context<Editor>,
21127 ) {
21128 if self.is_singleton(cx)
21129 && !self.mode.is_minimap()
21130 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21131 {
21132 let buffer_snapshot = OnceCell::new();
21133
21134 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21135 && !folds.is_empty()
21136 {
21137 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21138 self.fold_ranges(
21139 folds
21140 .into_iter()
21141 .map(|(start, end)| {
21142 snapshot.clip_offset(start, Bias::Left)
21143 ..snapshot.clip_offset(end, Bias::Right)
21144 })
21145 .collect(),
21146 false,
21147 window,
21148 cx,
21149 );
21150 }
21151
21152 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21153 && !selections.is_empty()
21154 {
21155 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21156 // skip adding the initial selection to selection history
21157 self.selection_history.mode = SelectionHistoryMode::Skipping;
21158 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21159 s.select_ranges(selections.into_iter().map(|(start, end)| {
21160 snapshot.clip_offset(start, Bias::Left)
21161 ..snapshot.clip_offset(end, Bias::Right)
21162 }));
21163 });
21164 self.selection_history.mode = SelectionHistoryMode::Normal;
21165 };
21166 }
21167
21168 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21169 }
21170
21171 fn update_lsp_data(
21172 &mut self,
21173 ignore_cache: bool,
21174 for_buffer: Option<BufferId>,
21175 window: &mut Window,
21176 cx: &mut Context<'_, Self>,
21177 ) {
21178 self.pull_diagnostics(for_buffer, window, cx);
21179 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21180 }
21181}
21182
21183fn vim_enabled(cx: &App) -> bool {
21184 cx.global::<SettingsStore>()
21185 .raw_user_settings()
21186 .get("vim_mode")
21187 == Some(&serde_json::Value::Bool(true))
21188}
21189
21190fn process_completion_for_edit(
21191 completion: &Completion,
21192 intent: CompletionIntent,
21193 buffer: &Entity<Buffer>,
21194 cursor_position: &text::Anchor,
21195 cx: &mut Context<Editor>,
21196) -> CompletionEdit {
21197 let buffer = buffer.read(cx);
21198 let buffer_snapshot = buffer.snapshot();
21199 let (snippet, new_text) = if completion.is_snippet() {
21200 // Workaround for typescript language server issues so that methods don't expand within
21201 // strings and functions with type expressions. The previous point is used because the query
21202 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21203 let mut snippet_source = completion.new_text.clone();
21204 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21205 previous_point.column = previous_point.column.saturating_sub(1);
21206 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21207 && scope.prefers_label_for_snippet_in_completion()
21208 && let Some(label) = completion.label()
21209 && matches!(
21210 completion.kind(),
21211 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21212 )
21213 {
21214 snippet_source = label;
21215 }
21216 match Snippet::parse(&snippet_source).log_err() {
21217 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21218 None => (None, completion.new_text.clone()),
21219 }
21220 } else {
21221 (None, completion.new_text.clone())
21222 };
21223
21224 let mut range_to_replace = {
21225 let replace_range = &completion.replace_range;
21226 if let CompletionSource::Lsp {
21227 insert_range: Some(insert_range),
21228 ..
21229 } = &completion.source
21230 {
21231 debug_assert_eq!(
21232 insert_range.start, replace_range.start,
21233 "insert_range and replace_range should start at the same position"
21234 );
21235 debug_assert!(
21236 insert_range
21237 .start
21238 .cmp(cursor_position, &buffer_snapshot)
21239 .is_le(),
21240 "insert_range should start before or at cursor position"
21241 );
21242 debug_assert!(
21243 replace_range
21244 .start
21245 .cmp(cursor_position, &buffer_snapshot)
21246 .is_le(),
21247 "replace_range should start before or at cursor position"
21248 );
21249
21250 let should_replace = match intent {
21251 CompletionIntent::CompleteWithInsert => false,
21252 CompletionIntent::CompleteWithReplace => true,
21253 CompletionIntent::Complete | CompletionIntent::Compose => {
21254 let insert_mode =
21255 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21256 .completions
21257 .lsp_insert_mode;
21258 match insert_mode {
21259 LspInsertMode::Insert => false,
21260 LspInsertMode::Replace => true,
21261 LspInsertMode::ReplaceSubsequence => {
21262 let mut text_to_replace = buffer.chars_for_range(
21263 buffer.anchor_before(replace_range.start)
21264 ..buffer.anchor_after(replace_range.end),
21265 );
21266 let mut current_needle = text_to_replace.next();
21267 for haystack_ch in completion.label.text.chars() {
21268 if let Some(needle_ch) = current_needle
21269 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21270 {
21271 current_needle = text_to_replace.next();
21272 }
21273 }
21274 current_needle.is_none()
21275 }
21276 LspInsertMode::ReplaceSuffix => {
21277 if replace_range
21278 .end
21279 .cmp(cursor_position, &buffer_snapshot)
21280 .is_gt()
21281 {
21282 let range_after_cursor = *cursor_position..replace_range.end;
21283 let text_after_cursor = buffer
21284 .text_for_range(
21285 buffer.anchor_before(range_after_cursor.start)
21286 ..buffer.anchor_after(range_after_cursor.end),
21287 )
21288 .collect::<String>()
21289 .to_ascii_lowercase();
21290 completion
21291 .label
21292 .text
21293 .to_ascii_lowercase()
21294 .ends_with(&text_after_cursor)
21295 } else {
21296 true
21297 }
21298 }
21299 }
21300 }
21301 };
21302
21303 if should_replace {
21304 replace_range.clone()
21305 } else {
21306 insert_range.clone()
21307 }
21308 } else {
21309 replace_range.clone()
21310 }
21311 };
21312
21313 if range_to_replace
21314 .end
21315 .cmp(cursor_position, &buffer_snapshot)
21316 .is_lt()
21317 {
21318 range_to_replace.end = *cursor_position;
21319 }
21320
21321 CompletionEdit {
21322 new_text,
21323 replace_range: range_to_replace.to_offset(buffer),
21324 snippet,
21325 }
21326}
21327
21328struct CompletionEdit {
21329 new_text: String,
21330 replace_range: Range<usize>,
21331 snippet: Option<Snippet>,
21332}
21333
21334fn insert_extra_newline_brackets(
21335 buffer: &MultiBufferSnapshot,
21336 range: Range<usize>,
21337 language: &language::LanguageScope,
21338) -> bool {
21339 let leading_whitespace_len = buffer
21340 .reversed_chars_at(range.start)
21341 .take_while(|c| c.is_whitespace() && *c != '\n')
21342 .map(|c| c.len_utf8())
21343 .sum::<usize>();
21344 let trailing_whitespace_len = buffer
21345 .chars_at(range.end)
21346 .take_while(|c| c.is_whitespace() && *c != '\n')
21347 .map(|c| c.len_utf8())
21348 .sum::<usize>();
21349 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21350
21351 language.brackets().any(|(pair, enabled)| {
21352 let pair_start = pair.start.trim_end();
21353 let pair_end = pair.end.trim_start();
21354
21355 enabled
21356 && pair.newline
21357 && buffer.contains_str_at(range.end, pair_end)
21358 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21359 })
21360}
21361
21362fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21363 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21364 [(buffer, range, _)] => (*buffer, range.clone()),
21365 _ => return false,
21366 };
21367 let pair = {
21368 let mut result: Option<BracketMatch> = None;
21369
21370 for pair in buffer
21371 .all_bracket_ranges(range.clone())
21372 .filter(move |pair| {
21373 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21374 })
21375 {
21376 let len = pair.close_range.end - pair.open_range.start;
21377
21378 if let Some(existing) = &result {
21379 let existing_len = existing.close_range.end - existing.open_range.start;
21380 if len > existing_len {
21381 continue;
21382 }
21383 }
21384
21385 result = Some(pair);
21386 }
21387
21388 result
21389 };
21390 let Some(pair) = pair else {
21391 return false;
21392 };
21393 pair.newline_only
21394 && buffer
21395 .chars_for_range(pair.open_range.end..range.start)
21396 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21397 .all(|c| c.is_whitespace() && c != '\n')
21398}
21399
21400fn update_uncommitted_diff_for_buffer(
21401 editor: Entity<Editor>,
21402 project: &Entity<Project>,
21403 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21404 buffer: Entity<MultiBuffer>,
21405 cx: &mut App,
21406) -> Task<()> {
21407 let mut tasks = Vec::new();
21408 project.update(cx, |project, cx| {
21409 for buffer in buffers {
21410 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21411 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21412 }
21413 }
21414 });
21415 cx.spawn(async move |cx| {
21416 let diffs = future::join_all(tasks).await;
21417 if editor
21418 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21419 .unwrap_or(false)
21420 {
21421 return;
21422 }
21423
21424 buffer
21425 .update(cx, |buffer, cx| {
21426 for diff in diffs.into_iter().flatten() {
21427 buffer.add_diff(diff, cx);
21428 }
21429 })
21430 .ok();
21431 })
21432}
21433
21434fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21435 let tab_size = tab_size.get() as usize;
21436 let mut width = offset;
21437
21438 for ch in text.chars() {
21439 width += if ch == '\t' {
21440 tab_size - (width % tab_size)
21441 } else {
21442 1
21443 };
21444 }
21445
21446 width - offset
21447}
21448
21449#[cfg(test)]
21450mod tests {
21451 use super::*;
21452
21453 #[test]
21454 fn test_string_size_with_expanded_tabs() {
21455 let nz = |val| NonZeroU32::new(val).unwrap();
21456 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21457 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21458 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21459 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21460 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21461 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21462 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21463 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21464 }
21465}
21466
21467/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21468struct WordBreakingTokenizer<'a> {
21469 input: &'a str,
21470}
21471
21472impl<'a> WordBreakingTokenizer<'a> {
21473 fn new(input: &'a str) -> Self {
21474 Self { input }
21475 }
21476}
21477
21478fn is_char_ideographic(ch: char) -> bool {
21479 use unicode_script::Script::*;
21480 use unicode_script::UnicodeScript;
21481 matches!(ch.script(), Han | Tangut | Yi)
21482}
21483
21484fn is_grapheme_ideographic(text: &str) -> bool {
21485 text.chars().any(is_char_ideographic)
21486}
21487
21488fn is_grapheme_whitespace(text: &str) -> bool {
21489 text.chars().any(|x| x.is_whitespace())
21490}
21491
21492fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21493 text.chars()
21494 .next()
21495 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21496}
21497
21498#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21499enum WordBreakToken<'a> {
21500 Word { token: &'a str, grapheme_len: usize },
21501 InlineWhitespace { token: &'a str, grapheme_len: usize },
21502 Newline,
21503}
21504
21505impl<'a> Iterator for WordBreakingTokenizer<'a> {
21506 /// Yields a span, the count of graphemes in the token, and whether it was
21507 /// whitespace. Note that it also breaks at word boundaries.
21508 type Item = WordBreakToken<'a>;
21509
21510 fn next(&mut self) -> Option<Self::Item> {
21511 use unicode_segmentation::UnicodeSegmentation;
21512 if self.input.is_empty() {
21513 return None;
21514 }
21515
21516 let mut iter = self.input.graphemes(true).peekable();
21517 let mut offset = 0;
21518 let mut grapheme_len = 0;
21519 if let Some(first_grapheme) = iter.next() {
21520 let is_newline = first_grapheme == "\n";
21521 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21522 offset += first_grapheme.len();
21523 grapheme_len += 1;
21524 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21525 if let Some(grapheme) = iter.peek().copied()
21526 && should_stay_with_preceding_ideograph(grapheme)
21527 {
21528 offset += grapheme.len();
21529 grapheme_len += 1;
21530 }
21531 } else {
21532 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21533 let mut next_word_bound = words.peek().copied();
21534 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21535 next_word_bound = words.next();
21536 }
21537 while let Some(grapheme) = iter.peek().copied() {
21538 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21539 break;
21540 };
21541 if is_grapheme_whitespace(grapheme) != is_whitespace
21542 || (grapheme == "\n") != is_newline
21543 {
21544 break;
21545 };
21546 offset += grapheme.len();
21547 grapheme_len += 1;
21548 iter.next();
21549 }
21550 }
21551 let token = &self.input[..offset];
21552 self.input = &self.input[offset..];
21553 if token == "\n" {
21554 Some(WordBreakToken::Newline)
21555 } else if is_whitespace {
21556 Some(WordBreakToken::InlineWhitespace {
21557 token,
21558 grapheme_len,
21559 })
21560 } else {
21561 Some(WordBreakToken::Word {
21562 token,
21563 grapheme_len,
21564 })
21565 }
21566 } else {
21567 None
21568 }
21569 }
21570}
21571
21572#[test]
21573fn test_word_breaking_tokenizer() {
21574 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21575 ("", &[]),
21576 (" ", &[whitespace(" ", 2)]),
21577 ("Ʒ", &[word("Ʒ", 1)]),
21578 ("Ǽ", &[word("Ǽ", 1)]),
21579 ("⋑", &[word("⋑", 1)]),
21580 ("⋑⋑", &[word("⋑⋑", 2)]),
21581 (
21582 "原理,进而",
21583 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21584 ),
21585 (
21586 "hello world",
21587 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21588 ),
21589 (
21590 "hello, world",
21591 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21592 ),
21593 (
21594 " hello world",
21595 &[
21596 whitespace(" ", 2),
21597 word("hello", 5),
21598 whitespace(" ", 1),
21599 word("world", 5),
21600 ],
21601 ),
21602 (
21603 "这是什么 \n 钢笔",
21604 &[
21605 word("这", 1),
21606 word("是", 1),
21607 word("什", 1),
21608 word("么", 1),
21609 whitespace(" ", 1),
21610 newline(),
21611 whitespace(" ", 1),
21612 word("钢", 1),
21613 word("笔", 1),
21614 ],
21615 ),
21616 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21617 ];
21618
21619 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21620 WordBreakToken::Word {
21621 token,
21622 grapheme_len,
21623 }
21624 }
21625
21626 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21627 WordBreakToken::InlineWhitespace {
21628 token,
21629 grapheme_len,
21630 }
21631 }
21632
21633 fn newline() -> WordBreakToken<'static> {
21634 WordBreakToken::Newline
21635 }
21636
21637 for (input, result) in tests {
21638 assert_eq!(
21639 WordBreakingTokenizer::new(input)
21640 .collect::<Vec<_>>()
21641 .as_slice(),
21642 *result,
21643 );
21644 }
21645}
21646
21647fn wrap_with_prefix(
21648 first_line_prefix: String,
21649 subsequent_lines_prefix: String,
21650 unwrapped_text: String,
21651 wrap_column: usize,
21652 tab_size: NonZeroU32,
21653 preserve_existing_whitespace: bool,
21654) -> String {
21655 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21656 let subsequent_lines_prefix_len =
21657 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21658 let mut wrapped_text = String::new();
21659 let mut current_line = first_line_prefix;
21660 let mut is_first_line = true;
21661
21662 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21663 let mut current_line_len = first_line_prefix_len;
21664 let mut in_whitespace = false;
21665 for token in tokenizer {
21666 let have_preceding_whitespace = in_whitespace;
21667 match token {
21668 WordBreakToken::Word {
21669 token,
21670 grapheme_len,
21671 } => {
21672 in_whitespace = false;
21673 let current_prefix_len = if is_first_line {
21674 first_line_prefix_len
21675 } else {
21676 subsequent_lines_prefix_len
21677 };
21678 if current_line_len + grapheme_len > wrap_column
21679 && current_line_len != current_prefix_len
21680 {
21681 wrapped_text.push_str(current_line.trim_end());
21682 wrapped_text.push('\n');
21683 is_first_line = false;
21684 current_line = subsequent_lines_prefix.clone();
21685 current_line_len = subsequent_lines_prefix_len;
21686 }
21687 current_line.push_str(token);
21688 current_line_len += grapheme_len;
21689 }
21690 WordBreakToken::InlineWhitespace {
21691 mut token,
21692 mut grapheme_len,
21693 } => {
21694 in_whitespace = true;
21695 if have_preceding_whitespace && !preserve_existing_whitespace {
21696 continue;
21697 }
21698 if !preserve_existing_whitespace {
21699 token = " ";
21700 grapheme_len = 1;
21701 }
21702 let current_prefix_len = if is_first_line {
21703 first_line_prefix_len
21704 } else {
21705 subsequent_lines_prefix_len
21706 };
21707 if current_line_len + grapheme_len > wrap_column {
21708 wrapped_text.push_str(current_line.trim_end());
21709 wrapped_text.push('\n');
21710 is_first_line = false;
21711 current_line = subsequent_lines_prefix.clone();
21712 current_line_len = subsequent_lines_prefix_len;
21713 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21714 current_line.push_str(token);
21715 current_line_len += grapheme_len;
21716 }
21717 }
21718 WordBreakToken::Newline => {
21719 in_whitespace = true;
21720 let current_prefix_len = if is_first_line {
21721 first_line_prefix_len
21722 } else {
21723 subsequent_lines_prefix_len
21724 };
21725 if preserve_existing_whitespace {
21726 wrapped_text.push_str(current_line.trim_end());
21727 wrapped_text.push('\n');
21728 is_first_line = false;
21729 current_line = subsequent_lines_prefix.clone();
21730 current_line_len = subsequent_lines_prefix_len;
21731 } else if have_preceding_whitespace {
21732 continue;
21733 } else if current_line_len + 1 > wrap_column
21734 && current_line_len != current_prefix_len
21735 {
21736 wrapped_text.push_str(current_line.trim_end());
21737 wrapped_text.push('\n');
21738 is_first_line = false;
21739 current_line = subsequent_lines_prefix.clone();
21740 current_line_len = subsequent_lines_prefix_len;
21741 } else if current_line_len != current_prefix_len {
21742 current_line.push(' ');
21743 current_line_len += 1;
21744 }
21745 }
21746 }
21747 }
21748
21749 if !current_line.is_empty() {
21750 wrapped_text.push_str(¤t_line);
21751 }
21752 wrapped_text
21753}
21754
21755#[test]
21756fn test_wrap_with_prefix() {
21757 assert_eq!(
21758 wrap_with_prefix(
21759 "# ".to_string(),
21760 "# ".to_string(),
21761 "abcdefg".to_string(),
21762 4,
21763 NonZeroU32::new(4).unwrap(),
21764 false,
21765 ),
21766 "# abcdefg"
21767 );
21768 assert_eq!(
21769 wrap_with_prefix(
21770 "".to_string(),
21771 "".to_string(),
21772 "\thello world".to_string(),
21773 8,
21774 NonZeroU32::new(4).unwrap(),
21775 false,
21776 ),
21777 "hello\nworld"
21778 );
21779 assert_eq!(
21780 wrap_with_prefix(
21781 "// ".to_string(),
21782 "// ".to_string(),
21783 "xx \nyy zz aa bb cc".to_string(),
21784 12,
21785 NonZeroU32::new(4).unwrap(),
21786 false,
21787 ),
21788 "// xx yy zz\n// aa bb cc"
21789 );
21790 assert_eq!(
21791 wrap_with_prefix(
21792 String::new(),
21793 String::new(),
21794 "这是什么 \n 钢笔".to_string(),
21795 3,
21796 NonZeroU32::new(4).unwrap(),
21797 false,
21798 ),
21799 "这是什\n么 钢\n笔"
21800 );
21801}
21802
21803pub trait CollaborationHub {
21804 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21805 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21806 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21807}
21808
21809impl CollaborationHub for Entity<Project> {
21810 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21811 self.read(cx).collaborators()
21812 }
21813
21814 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21815 self.read(cx).user_store().read(cx).participant_indices()
21816 }
21817
21818 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21819 let this = self.read(cx);
21820 let user_ids = this.collaborators().values().map(|c| c.user_id);
21821 this.user_store().read(cx).participant_names(user_ids, cx)
21822 }
21823}
21824
21825pub trait SemanticsProvider {
21826 fn hover(
21827 &self,
21828 buffer: &Entity<Buffer>,
21829 position: text::Anchor,
21830 cx: &mut App,
21831 ) -> Option<Task<Option<Vec<project::Hover>>>>;
21832
21833 fn inline_values(
21834 &self,
21835 buffer_handle: Entity<Buffer>,
21836 range: Range<text::Anchor>,
21837 cx: &mut App,
21838 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21839
21840 fn inlay_hints(
21841 &self,
21842 buffer_handle: Entity<Buffer>,
21843 range: Range<text::Anchor>,
21844 cx: &mut App,
21845 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21846
21847 fn resolve_inlay_hint(
21848 &self,
21849 hint: InlayHint,
21850 buffer_handle: Entity<Buffer>,
21851 server_id: LanguageServerId,
21852 cx: &mut App,
21853 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21854
21855 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21856
21857 fn document_highlights(
21858 &self,
21859 buffer: &Entity<Buffer>,
21860 position: text::Anchor,
21861 cx: &mut App,
21862 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21863
21864 fn definitions(
21865 &self,
21866 buffer: &Entity<Buffer>,
21867 position: text::Anchor,
21868 kind: GotoDefinitionKind,
21869 cx: &mut App,
21870 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
21871
21872 fn range_for_rename(
21873 &self,
21874 buffer: &Entity<Buffer>,
21875 position: text::Anchor,
21876 cx: &mut App,
21877 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21878
21879 fn perform_rename(
21880 &self,
21881 buffer: &Entity<Buffer>,
21882 position: text::Anchor,
21883 new_name: String,
21884 cx: &mut App,
21885 ) -> Option<Task<Result<ProjectTransaction>>>;
21886}
21887
21888pub trait CompletionProvider {
21889 fn completions(
21890 &self,
21891 excerpt_id: ExcerptId,
21892 buffer: &Entity<Buffer>,
21893 buffer_position: text::Anchor,
21894 trigger: CompletionContext,
21895 window: &mut Window,
21896 cx: &mut Context<Editor>,
21897 ) -> Task<Result<Vec<CompletionResponse>>>;
21898
21899 fn resolve_completions(
21900 &self,
21901 _buffer: Entity<Buffer>,
21902 _completion_indices: Vec<usize>,
21903 _completions: Rc<RefCell<Box<[Completion]>>>,
21904 _cx: &mut Context<Editor>,
21905 ) -> Task<Result<bool>> {
21906 Task::ready(Ok(false))
21907 }
21908
21909 fn apply_additional_edits_for_completion(
21910 &self,
21911 _buffer: Entity<Buffer>,
21912 _completions: Rc<RefCell<Box<[Completion]>>>,
21913 _completion_index: usize,
21914 _push_to_history: bool,
21915 _cx: &mut Context<Editor>,
21916 ) -> Task<Result<Option<language::Transaction>>> {
21917 Task::ready(Ok(None))
21918 }
21919
21920 fn is_completion_trigger(
21921 &self,
21922 buffer: &Entity<Buffer>,
21923 position: language::Anchor,
21924 text: &str,
21925 trigger_in_words: bool,
21926 menu_is_open: bool,
21927 cx: &mut Context<Editor>,
21928 ) -> bool;
21929
21930 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21931
21932 fn sort_completions(&self) -> bool {
21933 true
21934 }
21935
21936 fn filter_completions(&self) -> bool {
21937 true
21938 }
21939}
21940
21941pub trait CodeActionProvider {
21942 fn id(&self) -> Arc<str>;
21943
21944 fn code_actions(
21945 &self,
21946 buffer: &Entity<Buffer>,
21947 range: Range<text::Anchor>,
21948 window: &mut Window,
21949 cx: &mut App,
21950 ) -> Task<Result<Vec<CodeAction>>>;
21951
21952 fn apply_code_action(
21953 &self,
21954 buffer_handle: Entity<Buffer>,
21955 action: CodeAction,
21956 excerpt_id: ExcerptId,
21957 push_to_history: bool,
21958 window: &mut Window,
21959 cx: &mut App,
21960 ) -> Task<Result<ProjectTransaction>>;
21961}
21962
21963impl CodeActionProvider for Entity<Project> {
21964 fn id(&self) -> Arc<str> {
21965 "project".into()
21966 }
21967
21968 fn code_actions(
21969 &self,
21970 buffer: &Entity<Buffer>,
21971 range: Range<text::Anchor>,
21972 _window: &mut Window,
21973 cx: &mut App,
21974 ) -> Task<Result<Vec<CodeAction>>> {
21975 self.update(cx, |project, cx| {
21976 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21977 let code_actions = project.code_actions(buffer, range, None, cx);
21978 cx.background_spawn(async move {
21979 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21980 Ok(code_lens_actions
21981 .context("code lens fetch")?
21982 .into_iter()
21983 .flatten()
21984 .chain(
21985 code_actions
21986 .context("code action fetch")?
21987 .into_iter()
21988 .flatten(),
21989 )
21990 .collect())
21991 })
21992 })
21993 }
21994
21995 fn apply_code_action(
21996 &self,
21997 buffer_handle: Entity<Buffer>,
21998 action: CodeAction,
21999 _excerpt_id: ExcerptId,
22000 push_to_history: bool,
22001 _window: &mut Window,
22002 cx: &mut App,
22003 ) -> Task<Result<ProjectTransaction>> {
22004 self.update(cx, |project, cx| {
22005 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22006 })
22007 }
22008}
22009
22010fn snippet_completions(
22011 project: &Project,
22012 buffer: &Entity<Buffer>,
22013 buffer_position: text::Anchor,
22014 cx: &mut App,
22015) -> Task<Result<CompletionResponse>> {
22016 let languages = buffer.read(cx).languages_at(buffer_position);
22017 let snippet_store = project.snippets().read(cx);
22018
22019 let scopes: Vec<_> = languages
22020 .iter()
22021 .filter_map(|language| {
22022 let language_name = language.lsp_id();
22023 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22024
22025 if snippets.is_empty() {
22026 None
22027 } else {
22028 Some((language.default_scope(), snippets))
22029 }
22030 })
22031 .collect();
22032
22033 if scopes.is_empty() {
22034 return Task::ready(Ok(CompletionResponse {
22035 completions: vec![],
22036 is_incomplete: false,
22037 }));
22038 }
22039
22040 let snapshot = buffer.read(cx).text_snapshot();
22041 let chars: String = snapshot
22042 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22043 .collect();
22044 let executor = cx.background_executor().clone();
22045
22046 cx.background_spawn(async move {
22047 let mut is_incomplete = false;
22048 let mut completions: Vec<Completion> = Vec::new();
22049 for (scope, snippets) in scopes.into_iter() {
22050 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22051 let mut last_word = chars
22052 .chars()
22053 .take_while(|c| classifier.is_word(*c))
22054 .collect::<String>();
22055 last_word = last_word.chars().rev().collect();
22056
22057 if last_word.is_empty() {
22058 return Ok(CompletionResponse {
22059 completions: vec![],
22060 is_incomplete: true,
22061 });
22062 }
22063
22064 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22065 let to_lsp = |point: &text::Anchor| {
22066 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22067 point_to_lsp(end)
22068 };
22069 let lsp_end = to_lsp(&buffer_position);
22070
22071 let candidates = snippets
22072 .iter()
22073 .enumerate()
22074 .flat_map(|(ix, snippet)| {
22075 snippet
22076 .prefix
22077 .iter()
22078 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22079 })
22080 .collect::<Vec<StringMatchCandidate>>();
22081
22082 const MAX_RESULTS: usize = 100;
22083 let mut matches = fuzzy::match_strings(
22084 &candidates,
22085 &last_word,
22086 last_word.chars().any(|c| c.is_uppercase()),
22087 true,
22088 MAX_RESULTS,
22089 &Default::default(),
22090 executor.clone(),
22091 )
22092 .await;
22093
22094 if matches.len() >= MAX_RESULTS {
22095 is_incomplete = true;
22096 }
22097
22098 // Remove all candidates where the query's start does not match the start of any word in the candidate
22099 if let Some(query_start) = last_word.chars().next() {
22100 matches.retain(|string_match| {
22101 split_words(&string_match.string).any(|word| {
22102 // Check that the first codepoint of the word as lowercase matches the first
22103 // codepoint of the query as lowercase
22104 word.chars()
22105 .flat_map(|codepoint| codepoint.to_lowercase())
22106 .zip(query_start.to_lowercase())
22107 .all(|(word_cp, query_cp)| word_cp == query_cp)
22108 })
22109 });
22110 }
22111
22112 let matched_strings = matches
22113 .into_iter()
22114 .map(|m| m.string)
22115 .collect::<HashSet<_>>();
22116
22117 completions.extend(snippets.iter().filter_map(|snippet| {
22118 let matching_prefix = snippet
22119 .prefix
22120 .iter()
22121 .find(|prefix| matched_strings.contains(*prefix))?;
22122 let start = as_offset - last_word.len();
22123 let start = snapshot.anchor_before(start);
22124 let range = start..buffer_position;
22125 let lsp_start = to_lsp(&start);
22126 let lsp_range = lsp::Range {
22127 start: lsp_start,
22128 end: lsp_end,
22129 };
22130 Some(Completion {
22131 replace_range: range,
22132 new_text: snippet.body.clone(),
22133 source: CompletionSource::Lsp {
22134 insert_range: None,
22135 server_id: LanguageServerId(usize::MAX),
22136 resolved: true,
22137 lsp_completion: Box::new(lsp::CompletionItem {
22138 label: snippet.prefix.first().unwrap().clone(),
22139 kind: Some(CompletionItemKind::SNIPPET),
22140 label_details: snippet.description.as_ref().map(|description| {
22141 lsp::CompletionItemLabelDetails {
22142 detail: Some(description.clone()),
22143 description: None,
22144 }
22145 }),
22146 insert_text_format: Some(InsertTextFormat::SNIPPET),
22147 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22148 lsp::InsertReplaceEdit {
22149 new_text: snippet.body.clone(),
22150 insert: lsp_range,
22151 replace: lsp_range,
22152 },
22153 )),
22154 filter_text: Some(snippet.body.clone()),
22155 sort_text: Some(char::MAX.to_string()),
22156 ..lsp::CompletionItem::default()
22157 }),
22158 lsp_defaults: None,
22159 },
22160 label: CodeLabel {
22161 text: matching_prefix.clone(),
22162 runs: Vec::new(),
22163 filter_range: 0..matching_prefix.len(),
22164 },
22165 icon_path: None,
22166 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22167 single_line: snippet.name.clone().into(),
22168 plain_text: snippet
22169 .description
22170 .clone()
22171 .map(|description| description.into()),
22172 }),
22173 insert_text_mode: None,
22174 confirm: None,
22175 })
22176 }))
22177 }
22178
22179 Ok(CompletionResponse {
22180 completions,
22181 is_incomplete,
22182 })
22183 })
22184}
22185
22186impl CompletionProvider for Entity<Project> {
22187 fn completions(
22188 &self,
22189 _excerpt_id: ExcerptId,
22190 buffer: &Entity<Buffer>,
22191 buffer_position: text::Anchor,
22192 options: CompletionContext,
22193 _window: &mut Window,
22194 cx: &mut Context<Editor>,
22195 ) -> Task<Result<Vec<CompletionResponse>>> {
22196 self.update(cx, |project, cx| {
22197 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22198 let project_completions = project.completions(buffer, buffer_position, options, cx);
22199 cx.background_spawn(async move {
22200 let mut responses = project_completions.await?;
22201 let snippets = snippets.await?;
22202 if !snippets.completions.is_empty() {
22203 responses.push(snippets);
22204 }
22205 Ok(responses)
22206 })
22207 })
22208 }
22209
22210 fn resolve_completions(
22211 &self,
22212 buffer: Entity<Buffer>,
22213 completion_indices: Vec<usize>,
22214 completions: Rc<RefCell<Box<[Completion]>>>,
22215 cx: &mut Context<Editor>,
22216 ) -> Task<Result<bool>> {
22217 self.update(cx, |project, cx| {
22218 project.lsp_store().update(cx, |lsp_store, cx| {
22219 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22220 })
22221 })
22222 }
22223
22224 fn apply_additional_edits_for_completion(
22225 &self,
22226 buffer: Entity<Buffer>,
22227 completions: Rc<RefCell<Box<[Completion]>>>,
22228 completion_index: usize,
22229 push_to_history: bool,
22230 cx: &mut Context<Editor>,
22231 ) -> Task<Result<Option<language::Transaction>>> {
22232 self.update(cx, |project, cx| {
22233 project.lsp_store().update(cx, |lsp_store, cx| {
22234 lsp_store.apply_additional_edits_for_completion(
22235 buffer,
22236 completions,
22237 completion_index,
22238 push_to_history,
22239 cx,
22240 )
22241 })
22242 })
22243 }
22244
22245 fn is_completion_trigger(
22246 &self,
22247 buffer: &Entity<Buffer>,
22248 position: language::Anchor,
22249 text: &str,
22250 trigger_in_words: bool,
22251 menu_is_open: bool,
22252 cx: &mut Context<Editor>,
22253 ) -> bool {
22254 let mut chars = text.chars();
22255 let char = if let Some(char) = chars.next() {
22256 char
22257 } else {
22258 return false;
22259 };
22260 if chars.next().is_some() {
22261 return false;
22262 }
22263
22264 let buffer = buffer.read(cx);
22265 let snapshot = buffer.snapshot();
22266 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22267 return false;
22268 }
22269 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22270 if trigger_in_words && classifier.is_word(char) {
22271 return true;
22272 }
22273
22274 buffer.completion_triggers().contains(text)
22275 }
22276}
22277
22278impl SemanticsProvider for Entity<Project> {
22279 fn hover(
22280 &self,
22281 buffer: &Entity<Buffer>,
22282 position: text::Anchor,
22283 cx: &mut App,
22284 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22285 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22286 }
22287
22288 fn document_highlights(
22289 &self,
22290 buffer: &Entity<Buffer>,
22291 position: text::Anchor,
22292 cx: &mut App,
22293 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22294 Some(self.update(cx, |project, cx| {
22295 project.document_highlights(buffer, position, cx)
22296 }))
22297 }
22298
22299 fn definitions(
22300 &self,
22301 buffer: &Entity<Buffer>,
22302 position: text::Anchor,
22303 kind: GotoDefinitionKind,
22304 cx: &mut App,
22305 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22306 Some(self.update(cx, |project, cx| match kind {
22307 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22308 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22309 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22310 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22311 }))
22312 }
22313
22314 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22315 self.update(cx, |project, cx| {
22316 if project
22317 .active_debug_session(cx)
22318 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22319 {
22320 return true;
22321 }
22322
22323 buffer.update(cx, |buffer, cx| {
22324 project.any_language_server_supports_inlay_hints(buffer, cx)
22325 })
22326 })
22327 }
22328
22329 fn inline_values(
22330 &self,
22331 buffer_handle: Entity<Buffer>,
22332 range: Range<text::Anchor>,
22333 cx: &mut App,
22334 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22335 self.update(cx, |project, cx| {
22336 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22337
22338 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22339 })
22340 }
22341
22342 fn inlay_hints(
22343 &self,
22344 buffer_handle: Entity<Buffer>,
22345 range: Range<text::Anchor>,
22346 cx: &mut App,
22347 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22348 Some(self.update(cx, |project, cx| {
22349 project.inlay_hints(buffer_handle, range, cx)
22350 }))
22351 }
22352
22353 fn resolve_inlay_hint(
22354 &self,
22355 hint: InlayHint,
22356 buffer_handle: Entity<Buffer>,
22357 server_id: LanguageServerId,
22358 cx: &mut App,
22359 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22360 Some(self.update(cx, |project, cx| {
22361 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22362 }))
22363 }
22364
22365 fn range_for_rename(
22366 &self,
22367 buffer: &Entity<Buffer>,
22368 position: text::Anchor,
22369 cx: &mut App,
22370 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22371 Some(self.update(cx, |project, cx| {
22372 let buffer = buffer.clone();
22373 let task = project.prepare_rename(buffer.clone(), position, cx);
22374 cx.spawn(async move |_, cx| {
22375 Ok(match task.await? {
22376 PrepareRenameResponse::Success(range) => Some(range),
22377 PrepareRenameResponse::InvalidPosition => None,
22378 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22379 // Fallback on using TreeSitter info to determine identifier range
22380 buffer.read_with(cx, |buffer, _| {
22381 let snapshot = buffer.snapshot();
22382 let (range, kind) = snapshot.surrounding_word(position, false);
22383 if kind != Some(CharKind::Word) {
22384 return None;
22385 }
22386 Some(
22387 snapshot.anchor_before(range.start)
22388 ..snapshot.anchor_after(range.end),
22389 )
22390 })?
22391 }
22392 })
22393 })
22394 }))
22395 }
22396
22397 fn perform_rename(
22398 &self,
22399 buffer: &Entity<Buffer>,
22400 position: text::Anchor,
22401 new_name: String,
22402 cx: &mut App,
22403 ) -> Option<Task<Result<ProjectTransaction>>> {
22404 Some(self.update(cx, |project, cx| {
22405 project.perform_rename(buffer.clone(), position, new_name, cx)
22406 }))
22407 }
22408}
22409
22410fn inlay_hint_settings(
22411 location: Anchor,
22412 snapshot: &MultiBufferSnapshot,
22413 cx: &mut Context<Editor>,
22414) -> InlayHintSettings {
22415 let file = snapshot.file_at(location);
22416 let language = snapshot.language_at(location).map(|l| l.name());
22417 language_settings(language, file, cx).inlay_hints
22418}
22419
22420fn consume_contiguous_rows(
22421 contiguous_row_selections: &mut Vec<Selection<Point>>,
22422 selection: &Selection<Point>,
22423 display_map: &DisplaySnapshot,
22424 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22425) -> (MultiBufferRow, MultiBufferRow) {
22426 contiguous_row_selections.push(selection.clone());
22427 let start_row = starting_row(selection, display_map);
22428 let mut end_row = ending_row(selection, display_map);
22429
22430 while let Some(next_selection) = selections.peek() {
22431 if next_selection.start.row <= end_row.0 {
22432 end_row = ending_row(next_selection, display_map);
22433 contiguous_row_selections.push(selections.next().unwrap().clone());
22434 } else {
22435 break;
22436 }
22437 }
22438 (start_row, end_row)
22439}
22440
22441fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22442 if selection.start.column > 0 {
22443 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22444 } else {
22445 MultiBufferRow(selection.start.row)
22446 }
22447}
22448
22449fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22450 if next_selection.end.column > 0 || next_selection.is_empty() {
22451 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22452 } else {
22453 MultiBufferRow(next_selection.end.row)
22454 }
22455}
22456
22457impl EditorSnapshot {
22458 pub fn remote_selections_in_range<'a>(
22459 &'a self,
22460 range: &'a Range<Anchor>,
22461 collaboration_hub: &dyn CollaborationHub,
22462 cx: &'a App,
22463 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22464 let participant_names = collaboration_hub.user_names(cx);
22465 let participant_indices = collaboration_hub.user_participant_indices(cx);
22466 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22467 let collaborators_by_replica_id = collaborators_by_peer_id
22468 .values()
22469 .map(|collaborator| (collaborator.replica_id, collaborator))
22470 .collect::<HashMap<_, _>>();
22471 self.buffer_snapshot
22472 .selections_in_range(range, false)
22473 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22474 if replica_id == AGENT_REPLICA_ID {
22475 Some(RemoteSelection {
22476 replica_id,
22477 selection,
22478 cursor_shape,
22479 line_mode,
22480 collaborator_id: CollaboratorId::Agent,
22481 user_name: Some("Agent".into()),
22482 color: cx.theme().players().agent(),
22483 })
22484 } else {
22485 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22486 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22487 let user_name = participant_names.get(&collaborator.user_id).cloned();
22488 Some(RemoteSelection {
22489 replica_id,
22490 selection,
22491 cursor_shape,
22492 line_mode,
22493 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22494 user_name,
22495 color: if let Some(index) = participant_index {
22496 cx.theme().players().color_for_participant(index.0)
22497 } else {
22498 cx.theme().players().absent()
22499 },
22500 })
22501 }
22502 })
22503 }
22504
22505 pub fn hunks_for_ranges(
22506 &self,
22507 ranges: impl IntoIterator<Item = Range<Point>>,
22508 ) -> Vec<MultiBufferDiffHunk> {
22509 let mut hunks = Vec::new();
22510 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22511 HashMap::default();
22512 for query_range in ranges {
22513 let query_rows =
22514 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22515 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22516 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22517 ) {
22518 // Include deleted hunks that are adjacent to the query range, because
22519 // otherwise they would be missed.
22520 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22521 if hunk.status().is_deleted() {
22522 intersects_range |= hunk.row_range.start == query_rows.end;
22523 intersects_range |= hunk.row_range.end == query_rows.start;
22524 }
22525 if intersects_range {
22526 if !processed_buffer_rows
22527 .entry(hunk.buffer_id)
22528 .or_default()
22529 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22530 {
22531 continue;
22532 }
22533 hunks.push(hunk);
22534 }
22535 }
22536 }
22537
22538 hunks
22539 }
22540
22541 fn display_diff_hunks_for_rows<'a>(
22542 &'a self,
22543 display_rows: Range<DisplayRow>,
22544 folded_buffers: &'a HashSet<BufferId>,
22545 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22546 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22547 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22548
22549 self.buffer_snapshot
22550 .diff_hunks_in_range(buffer_start..buffer_end)
22551 .filter_map(|hunk| {
22552 if folded_buffers.contains(&hunk.buffer_id) {
22553 return None;
22554 }
22555
22556 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22557 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22558
22559 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22560 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22561
22562 let display_hunk = if hunk_display_start.column() != 0 {
22563 DisplayDiffHunk::Folded {
22564 display_row: hunk_display_start.row(),
22565 }
22566 } else {
22567 let mut end_row = hunk_display_end.row();
22568 if hunk_display_end.column() > 0 {
22569 end_row.0 += 1;
22570 }
22571 let is_created_file = hunk.is_created_file();
22572 DisplayDiffHunk::Unfolded {
22573 status: hunk.status(),
22574 diff_base_byte_range: hunk.diff_base_byte_range,
22575 display_row_range: hunk_display_start.row()..end_row,
22576 multi_buffer_range: Anchor::range_in_buffer(
22577 hunk.excerpt_id,
22578 hunk.buffer_id,
22579 hunk.buffer_range,
22580 ),
22581 is_created_file,
22582 }
22583 };
22584
22585 Some(display_hunk)
22586 })
22587 }
22588
22589 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22590 self.display_snapshot.buffer_snapshot.language_at(position)
22591 }
22592
22593 pub fn is_focused(&self) -> bool {
22594 self.is_focused
22595 }
22596
22597 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22598 self.placeholder_text.as_ref()
22599 }
22600
22601 pub fn scroll_position(&self) -> gpui::Point<f32> {
22602 self.scroll_anchor.scroll_position(&self.display_snapshot)
22603 }
22604
22605 fn gutter_dimensions(
22606 &self,
22607 font_id: FontId,
22608 font_size: Pixels,
22609 max_line_number_width: Pixels,
22610 cx: &App,
22611 ) -> Option<GutterDimensions> {
22612 if !self.show_gutter {
22613 return None;
22614 }
22615
22616 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22617 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22618
22619 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22620 matches!(
22621 ProjectSettings::get_global(cx).git.git_gutter,
22622 Some(GitGutterSetting::TrackedFiles)
22623 )
22624 });
22625 let gutter_settings = EditorSettings::get_global(cx).gutter;
22626 let show_line_numbers = self
22627 .show_line_numbers
22628 .unwrap_or(gutter_settings.line_numbers);
22629 let line_gutter_width = if show_line_numbers {
22630 // Avoid flicker-like gutter resizes when the line number gains another digit by
22631 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22632 let min_width_for_number_on_gutter =
22633 ch_advance * gutter_settings.min_line_number_digits as f32;
22634 max_line_number_width.max(min_width_for_number_on_gutter)
22635 } else {
22636 0.0.into()
22637 };
22638
22639 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22640 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22641
22642 let git_blame_entries_width =
22643 self.git_blame_gutter_max_author_length
22644 .map(|max_author_length| {
22645 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22646 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22647
22648 /// The number of characters to dedicate to gaps and margins.
22649 const SPACING_WIDTH: usize = 4;
22650
22651 let max_char_count = max_author_length.min(renderer.max_author_length())
22652 + ::git::SHORT_SHA_LENGTH
22653 + MAX_RELATIVE_TIMESTAMP.len()
22654 + SPACING_WIDTH;
22655
22656 ch_advance * max_char_count
22657 });
22658
22659 let is_singleton = self.buffer_snapshot.is_singleton();
22660
22661 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22662 left_padding += if !is_singleton {
22663 ch_width * 4.0
22664 } else if show_runnables || show_breakpoints {
22665 ch_width * 3.0
22666 } else if show_git_gutter && show_line_numbers {
22667 ch_width * 2.0
22668 } else if show_git_gutter || show_line_numbers {
22669 ch_width
22670 } else {
22671 px(0.)
22672 };
22673
22674 let shows_folds = is_singleton && gutter_settings.folds;
22675
22676 let right_padding = if shows_folds && show_line_numbers {
22677 ch_width * 4.0
22678 } else if shows_folds || (!is_singleton && show_line_numbers) {
22679 ch_width * 3.0
22680 } else if show_line_numbers {
22681 ch_width
22682 } else {
22683 px(0.)
22684 };
22685
22686 Some(GutterDimensions {
22687 left_padding,
22688 right_padding,
22689 width: line_gutter_width + left_padding + right_padding,
22690 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22691 git_blame_entries_width,
22692 })
22693 }
22694
22695 pub fn render_crease_toggle(
22696 &self,
22697 buffer_row: MultiBufferRow,
22698 row_contains_cursor: bool,
22699 editor: Entity<Editor>,
22700 window: &mut Window,
22701 cx: &mut App,
22702 ) -> Option<AnyElement> {
22703 let folded = self.is_line_folded(buffer_row);
22704 let mut is_foldable = false;
22705
22706 if let Some(crease) = self
22707 .crease_snapshot
22708 .query_row(buffer_row, &self.buffer_snapshot)
22709 {
22710 is_foldable = true;
22711 match crease {
22712 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22713 if let Some(render_toggle) = render_toggle {
22714 let toggle_callback =
22715 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22716 if folded {
22717 editor.update(cx, |editor, cx| {
22718 editor.fold_at(buffer_row, window, cx)
22719 });
22720 } else {
22721 editor.update(cx, |editor, cx| {
22722 editor.unfold_at(buffer_row, window, cx)
22723 });
22724 }
22725 });
22726 return Some((render_toggle)(
22727 buffer_row,
22728 folded,
22729 toggle_callback,
22730 window,
22731 cx,
22732 ));
22733 }
22734 }
22735 }
22736 }
22737
22738 is_foldable |= self.starts_indent(buffer_row);
22739
22740 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22741 Some(
22742 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22743 .toggle_state(folded)
22744 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22745 if folded {
22746 this.unfold_at(buffer_row, window, cx);
22747 } else {
22748 this.fold_at(buffer_row, window, cx);
22749 }
22750 }))
22751 .into_any_element(),
22752 )
22753 } else {
22754 None
22755 }
22756 }
22757
22758 pub fn render_crease_trailer(
22759 &self,
22760 buffer_row: MultiBufferRow,
22761 window: &mut Window,
22762 cx: &mut App,
22763 ) -> Option<AnyElement> {
22764 let folded = self.is_line_folded(buffer_row);
22765 if let Crease::Inline { render_trailer, .. } = self
22766 .crease_snapshot
22767 .query_row(buffer_row, &self.buffer_snapshot)?
22768 {
22769 let render_trailer = render_trailer.as_ref()?;
22770 Some(render_trailer(buffer_row, folded, window, cx))
22771 } else {
22772 None
22773 }
22774 }
22775}
22776
22777impl Deref for EditorSnapshot {
22778 type Target = DisplaySnapshot;
22779
22780 fn deref(&self) -> &Self::Target {
22781 &self.display_snapshot
22782 }
22783}
22784
22785#[derive(Clone, Debug, PartialEq, Eq)]
22786pub enum EditorEvent {
22787 InputIgnored {
22788 text: Arc<str>,
22789 },
22790 InputHandled {
22791 utf16_range_to_replace: Option<Range<isize>>,
22792 text: Arc<str>,
22793 },
22794 ExcerptsAdded {
22795 buffer: Entity<Buffer>,
22796 predecessor: ExcerptId,
22797 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22798 },
22799 ExcerptsRemoved {
22800 ids: Vec<ExcerptId>,
22801 removed_buffer_ids: Vec<BufferId>,
22802 },
22803 BufferFoldToggled {
22804 ids: Vec<ExcerptId>,
22805 folded: bool,
22806 },
22807 ExcerptsEdited {
22808 ids: Vec<ExcerptId>,
22809 },
22810 ExcerptsExpanded {
22811 ids: Vec<ExcerptId>,
22812 },
22813 BufferEdited,
22814 Edited {
22815 transaction_id: clock::Lamport,
22816 },
22817 Reparsed(BufferId),
22818 Focused,
22819 FocusedIn,
22820 Blurred,
22821 DirtyChanged,
22822 Saved,
22823 TitleChanged,
22824 DiffBaseChanged,
22825 SelectionsChanged {
22826 local: bool,
22827 },
22828 ScrollPositionChanged {
22829 local: bool,
22830 autoscroll: bool,
22831 },
22832 Closed,
22833 TransactionUndone {
22834 transaction_id: clock::Lamport,
22835 },
22836 TransactionBegun {
22837 transaction_id: clock::Lamport,
22838 },
22839 Reloaded,
22840 CursorShapeChanged,
22841 BreadcrumbsChanged,
22842 PushedToNavHistory {
22843 anchor: Anchor,
22844 is_deactivate: bool,
22845 },
22846}
22847
22848impl EventEmitter<EditorEvent> for Editor {}
22849
22850impl Focusable for Editor {
22851 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22852 self.focus_handle.clone()
22853 }
22854}
22855
22856impl Render for Editor {
22857 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22858 let settings = ThemeSettings::get_global(cx);
22859
22860 let mut text_style = match self.mode {
22861 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
22862 color: cx.theme().colors().editor_foreground,
22863 font_family: settings.ui_font.family.clone(),
22864 font_features: settings.ui_font.features.clone(),
22865 font_fallbacks: settings.ui_font.fallbacks.clone(),
22866 font_size: rems(0.875).into(),
22867 font_weight: settings.ui_font.weight,
22868 line_height: relative(settings.buffer_line_height.value()),
22869 ..Default::default()
22870 },
22871 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22872 color: cx.theme().colors().editor_foreground,
22873 font_family: settings.buffer_font.family.clone(),
22874 font_features: settings.buffer_font.features.clone(),
22875 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22876 font_size: settings.buffer_font_size(cx).into(),
22877 font_weight: settings.buffer_font.weight,
22878 line_height: relative(settings.buffer_line_height.value()),
22879 ..Default::default()
22880 },
22881 };
22882 if let Some(text_style_refinement) = &self.text_style_refinement {
22883 text_style.refine(text_style_refinement)
22884 }
22885
22886 let background = match self.mode {
22887 EditorMode::SingleLine => cx.theme().system().transparent,
22888 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22889 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22890 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22891 };
22892
22893 EditorElement::new(
22894 &cx.entity(),
22895 EditorStyle {
22896 background,
22897 border: cx.theme().colors().border,
22898 local_player: cx.theme().players().local(),
22899 text: text_style,
22900 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22901 syntax: cx.theme().syntax().clone(),
22902 status: cx.theme().status().clone(),
22903 inlay_hints_style: make_inlay_hints_style(cx),
22904 edit_prediction_styles: make_suggestion_styles(cx),
22905 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22906 show_underlines: self.diagnostics_enabled(),
22907 },
22908 )
22909 }
22910}
22911
22912impl EntityInputHandler for Editor {
22913 fn text_for_range(
22914 &mut self,
22915 range_utf16: Range<usize>,
22916 adjusted_range: &mut Option<Range<usize>>,
22917 _: &mut Window,
22918 cx: &mut Context<Self>,
22919 ) -> Option<String> {
22920 let snapshot = self.buffer.read(cx).read(cx);
22921 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22922 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22923 if (start.0..end.0) != range_utf16 {
22924 adjusted_range.replace(start.0..end.0);
22925 }
22926 Some(snapshot.text_for_range(start..end).collect())
22927 }
22928
22929 fn selected_text_range(
22930 &mut self,
22931 ignore_disabled_input: bool,
22932 _: &mut Window,
22933 cx: &mut Context<Self>,
22934 ) -> Option<UTF16Selection> {
22935 // Prevent the IME menu from appearing when holding down an alphabetic key
22936 // while input is disabled.
22937 if !ignore_disabled_input && !self.input_enabled {
22938 return None;
22939 }
22940
22941 let selection = self.selections.newest::<OffsetUtf16>(cx);
22942 let range = selection.range();
22943
22944 Some(UTF16Selection {
22945 range: range.start.0..range.end.0,
22946 reversed: selection.reversed,
22947 })
22948 }
22949
22950 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22951 let snapshot = self.buffer.read(cx).read(cx);
22952 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22953 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22954 }
22955
22956 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22957 self.clear_highlights::<InputComposition>(cx);
22958 self.ime_transaction.take();
22959 }
22960
22961 fn replace_text_in_range(
22962 &mut self,
22963 range_utf16: Option<Range<usize>>,
22964 text: &str,
22965 window: &mut Window,
22966 cx: &mut Context<Self>,
22967 ) {
22968 if !self.input_enabled {
22969 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22970 return;
22971 }
22972
22973 self.transact(window, cx, |this, window, cx| {
22974 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22975 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22976 Some(this.selection_replacement_ranges(range_utf16, cx))
22977 } else {
22978 this.marked_text_ranges(cx)
22979 };
22980
22981 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22982 let newest_selection_id = this.selections.newest_anchor().id;
22983 this.selections
22984 .all::<OffsetUtf16>(cx)
22985 .iter()
22986 .zip(ranges_to_replace.iter())
22987 .find_map(|(selection, range)| {
22988 if selection.id == newest_selection_id {
22989 Some(
22990 (range.start.0 as isize - selection.head().0 as isize)
22991 ..(range.end.0 as isize - selection.head().0 as isize),
22992 )
22993 } else {
22994 None
22995 }
22996 })
22997 });
22998
22999 cx.emit(EditorEvent::InputHandled {
23000 utf16_range_to_replace: range_to_replace,
23001 text: text.into(),
23002 });
23003
23004 if let Some(new_selected_ranges) = new_selected_ranges {
23005 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23006 selections.select_ranges(new_selected_ranges)
23007 });
23008 this.backspace(&Default::default(), window, cx);
23009 }
23010
23011 this.handle_input(text, window, cx);
23012 });
23013
23014 if let Some(transaction) = self.ime_transaction {
23015 self.buffer.update(cx, |buffer, cx| {
23016 buffer.group_until_transaction(transaction, cx);
23017 });
23018 }
23019
23020 self.unmark_text(window, cx);
23021 }
23022
23023 fn replace_and_mark_text_in_range(
23024 &mut self,
23025 range_utf16: Option<Range<usize>>,
23026 text: &str,
23027 new_selected_range_utf16: Option<Range<usize>>,
23028 window: &mut Window,
23029 cx: &mut Context<Self>,
23030 ) {
23031 if !self.input_enabled {
23032 return;
23033 }
23034
23035 let transaction = self.transact(window, cx, |this, window, cx| {
23036 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23037 let snapshot = this.buffer.read(cx).read(cx);
23038 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23039 for marked_range in &mut marked_ranges {
23040 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23041 marked_range.start.0 += relative_range_utf16.start;
23042 marked_range.start =
23043 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23044 marked_range.end =
23045 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23046 }
23047 }
23048 Some(marked_ranges)
23049 } else if let Some(range_utf16) = range_utf16 {
23050 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23051 Some(this.selection_replacement_ranges(range_utf16, cx))
23052 } else {
23053 None
23054 };
23055
23056 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23057 let newest_selection_id = this.selections.newest_anchor().id;
23058 this.selections
23059 .all::<OffsetUtf16>(cx)
23060 .iter()
23061 .zip(ranges_to_replace.iter())
23062 .find_map(|(selection, range)| {
23063 if selection.id == newest_selection_id {
23064 Some(
23065 (range.start.0 as isize - selection.head().0 as isize)
23066 ..(range.end.0 as isize - selection.head().0 as isize),
23067 )
23068 } else {
23069 None
23070 }
23071 })
23072 });
23073
23074 cx.emit(EditorEvent::InputHandled {
23075 utf16_range_to_replace: range_to_replace,
23076 text: text.into(),
23077 });
23078
23079 if let Some(ranges) = ranges_to_replace {
23080 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23081 s.select_ranges(ranges)
23082 });
23083 }
23084
23085 let marked_ranges = {
23086 let snapshot = this.buffer.read(cx).read(cx);
23087 this.selections
23088 .disjoint_anchors()
23089 .iter()
23090 .map(|selection| {
23091 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23092 })
23093 .collect::<Vec<_>>()
23094 };
23095
23096 if text.is_empty() {
23097 this.unmark_text(window, cx);
23098 } else {
23099 this.highlight_text::<InputComposition>(
23100 marked_ranges.clone(),
23101 HighlightStyle {
23102 underline: Some(UnderlineStyle {
23103 thickness: px(1.),
23104 color: None,
23105 wavy: false,
23106 }),
23107 ..Default::default()
23108 },
23109 cx,
23110 );
23111 }
23112
23113 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23114 let use_autoclose = this.use_autoclose;
23115 let use_auto_surround = this.use_auto_surround;
23116 this.set_use_autoclose(false);
23117 this.set_use_auto_surround(false);
23118 this.handle_input(text, window, cx);
23119 this.set_use_autoclose(use_autoclose);
23120 this.set_use_auto_surround(use_auto_surround);
23121
23122 if let Some(new_selected_range) = new_selected_range_utf16 {
23123 let snapshot = this.buffer.read(cx).read(cx);
23124 let new_selected_ranges = marked_ranges
23125 .into_iter()
23126 .map(|marked_range| {
23127 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23128 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23129 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23130 snapshot.clip_offset_utf16(new_start, Bias::Left)
23131 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23132 })
23133 .collect::<Vec<_>>();
23134
23135 drop(snapshot);
23136 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23137 selections.select_ranges(new_selected_ranges)
23138 });
23139 }
23140 });
23141
23142 self.ime_transaction = self.ime_transaction.or(transaction);
23143 if let Some(transaction) = self.ime_transaction {
23144 self.buffer.update(cx, |buffer, cx| {
23145 buffer.group_until_transaction(transaction, cx);
23146 });
23147 }
23148
23149 if self.text_highlights::<InputComposition>(cx).is_none() {
23150 self.ime_transaction.take();
23151 }
23152 }
23153
23154 fn bounds_for_range(
23155 &mut self,
23156 range_utf16: Range<usize>,
23157 element_bounds: gpui::Bounds<Pixels>,
23158 window: &mut Window,
23159 cx: &mut Context<Self>,
23160 ) -> Option<gpui::Bounds<Pixels>> {
23161 let text_layout_details = self.text_layout_details(window);
23162 let CharacterDimensions {
23163 em_width,
23164 em_advance,
23165 line_height,
23166 } = self.character_dimensions(window);
23167
23168 let snapshot = self.snapshot(window, cx);
23169 let scroll_position = snapshot.scroll_position();
23170 let scroll_left = scroll_position.x * em_advance;
23171
23172 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23173 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23174 + self.gutter_dimensions.full_width();
23175 let y = line_height * (start.row().as_f32() - scroll_position.y);
23176
23177 Some(Bounds {
23178 origin: element_bounds.origin + point(x, y),
23179 size: size(em_width, line_height),
23180 })
23181 }
23182
23183 fn character_index_for_point(
23184 &mut self,
23185 point: gpui::Point<Pixels>,
23186 _window: &mut Window,
23187 _cx: &mut Context<Self>,
23188 ) -> Option<usize> {
23189 let position_map = self.last_position_map.as_ref()?;
23190 if !position_map.text_hitbox.contains(&point) {
23191 return None;
23192 }
23193 let display_point = position_map.point_for_position(point).previous_valid;
23194 let anchor = position_map
23195 .snapshot
23196 .display_point_to_anchor(display_point, Bias::Left);
23197 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23198 Some(utf16_offset.0)
23199 }
23200}
23201
23202trait SelectionExt {
23203 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23204 fn spanned_rows(
23205 &self,
23206 include_end_if_at_line_start: bool,
23207 map: &DisplaySnapshot,
23208 ) -> Range<MultiBufferRow>;
23209}
23210
23211impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23212 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23213 let start = self
23214 .start
23215 .to_point(&map.buffer_snapshot)
23216 .to_display_point(map);
23217 let end = self
23218 .end
23219 .to_point(&map.buffer_snapshot)
23220 .to_display_point(map);
23221 if self.reversed {
23222 end..start
23223 } else {
23224 start..end
23225 }
23226 }
23227
23228 fn spanned_rows(
23229 &self,
23230 include_end_if_at_line_start: bool,
23231 map: &DisplaySnapshot,
23232 ) -> Range<MultiBufferRow> {
23233 let start = self.start.to_point(&map.buffer_snapshot);
23234 let mut end = self.end.to_point(&map.buffer_snapshot);
23235 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23236 end.row -= 1;
23237 }
23238
23239 let buffer_start = map.prev_line_boundary(start).0;
23240 let buffer_end = map.next_line_boundary(end).0;
23241 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23242 }
23243}
23244
23245impl<T: InvalidationRegion> InvalidationStack<T> {
23246 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23247 where
23248 S: Clone + ToOffset,
23249 {
23250 while let Some(region) = self.last() {
23251 let all_selections_inside_invalidation_ranges =
23252 if selections.len() == region.ranges().len() {
23253 selections
23254 .iter()
23255 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23256 .all(|(selection, invalidation_range)| {
23257 let head = selection.head().to_offset(buffer);
23258 invalidation_range.start <= head && invalidation_range.end >= head
23259 })
23260 } else {
23261 false
23262 };
23263
23264 if all_selections_inside_invalidation_ranges {
23265 break;
23266 } else {
23267 self.pop();
23268 }
23269 }
23270 }
23271}
23272
23273impl<T> Default for InvalidationStack<T> {
23274 fn default() -> Self {
23275 Self(Default::default())
23276 }
23277}
23278
23279impl<T> Deref for InvalidationStack<T> {
23280 type Target = Vec<T>;
23281
23282 fn deref(&self) -> &Self::Target {
23283 &self.0
23284 }
23285}
23286
23287impl<T> DerefMut for InvalidationStack<T> {
23288 fn deref_mut(&mut self) -> &mut Self::Target {
23289 &mut self.0
23290 }
23291}
23292
23293impl InvalidationRegion for SnippetState {
23294 fn ranges(&self) -> &[Range<Anchor>] {
23295 &self.ranges[self.active_index]
23296 }
23297}
23298
23299fn edit_prediction_edit_text(
23300 current_snapshot: &BufferSnapshot,
23301 edits: &[(Range<Anchor>, String)],
23302 edit_preview: &EditPreview,
23303 include_deletions: bool,
23304 cx: &App,
23305) -> HighlightedText {
23306 let edits = edits
23307 .iter()
23308 .map(|(anchor, text)| {
23309 (
23310 anchor.start.text_anchor..anchor.end.text_anchor,
23311 text.clone(),
23312 )
23313 })
23314 .collect::<Vec<_>>();
23315
23316 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23317}
23318
23319fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23320 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23321 // Just show the raw edit text with basic styling
23322 let mut text = String::new();
23323 let mut highlights = Vec::new();
23324
23325 let insertion_highlight_style = HighlightStyle {
23326 color: Some(cx.theme().colors().text),
23327 ..Default::default()
23328 };
23329
23330 for (_, edit_text) in edits {
23331 let start_offset = text.len();
23332 text.push_str(edit_text);
23333 let end_offset = text.len();
23334
23335 if start_offset < end_offset {
23336 highlights.push((start_offset..end_offset, insertion_highlight_style));
23337 }
23338 }
23339
23340 HighlightedText {
23341 text: text.into(),
23342 highlights,
23343 }
23344}
23345
23346pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23347 match severity {
23348 lsp::DiagnosticSeverity::ERROR => colors.error,
23349 lsp::DiagnosticSeverity::WARNING => colors.warning,
23350 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23351 lsp::DiagnosticSeverity::HINT => colors.info,
23352 _ => colors.ignored,
23353 }
23354}
23355
23356pub fn styled_runs_for_code_label<'a>(
23357 label: &'a CodeLabel,
23358 syntax_theme: &'a theme::SyntaxTheme,
23359) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23360 let fade_out = HighlightStyle {
23361 fade_out: Some(0.35),
23362 ..Default::default()
23363 };
23364
23365 let mut prev_end = label.filter_range.end;
23366 label
23367 .runs
23368 .iter()
23369 .enumerate()
23370 .flat_map(move |(ix, (range, highlight_id))| {
23371 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23372 style
23373 } else {
23374 return Default::default();
23375 };
23376 let mut muted_style = style;
23377 muted_style.highlight(fade_out);
23378
23379 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23380 if range.start >= label.filter_range.end {
23381 if range.start > prev_end {
23382 runs.push((prev_end..range.start, fade_out));
23383 }
23384 runs.push((range.clone(), muted_style));
23385 } else if range.end <= label.filter_range.end {
23386 runs.push((range.clone(), style));
23387 } else {
23388 runs.push((range.start..label.filter_range.end, style));
23389 runs.push((label.filter_range.end..range.end, muted_style));
23390 }
23391 prev_end = cmp::max(prev_end, range.end);
23392
23393 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23394 runs.push((prev_end..label.text.len(), fade_out));
23395 }
23396
23397 runs
23398 })
23399}
23400
23401pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23402 let mut prev_index = 0;
23403 let mut prev_codepoint: Option<char> = None;
23404 text.char_indices()
23405 .chain([(text.len(), '\0')])
23406 .filter_map(move |(index, codepoint)| {
23407 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23408 let is_boundary = index == text.len()
23409 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23410 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23411 if is_boundary {
23412 let chunk = &text[prev_index..index];
23413 prev_index = index;
23414 Some(chunk)
23415 } else {
23416 None
23417 }
23418 })
23419}
23420
23421pub trait RangeToAnchorExt: Sized {
23422 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23423
23424 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23425 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23426 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23427 }
23428}
23429
23430impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23431 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23432 let start_offset = self.start.to_offset(snapshot);
23433 let end_offset = self.end.to_offset(snapshot);
23434 if start_offset == end_offset {
23435 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23436 } else {
23437 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23438 }
23439 }
23440}
23441
23442pub trait RowExt {
23443 fn as_f32(&self) -> f32;
23444
23445 fn next_row(&self) -> Self;
23446
23447 fn previous_row(&self) -> Self;
23448
23449 fn minus(&self, other: Self) -> u32;
23450}
23451
23452impl RowExt for DisplayRow {
23453 fn as_f32(&self) -> f32 {
23454 self.0 as f32
23455 }
23456
23457 fn next_row(&self) -> Self {
23458 Self(self.0 + 1)
23459 }
23460
23461 fn previous_row(&self) -> Self {
23462 Self(self.0.saturating_sub(1))
23463 }
23464
23465 fn minus(&self, other: Self) -> u32 {
23466 self.0 - other.0
23467 }
23468}
23469
23470impl RowExt for MultiBufferRow {
23471 fn as_f32(&self) -> f32 {
23472 self.0 as f32
23473 }
23474
23475 fn next_row(&self) -> Self {
23476 Self(self.0 + 1)
23477 }
23478
23479 fn previous_row(&self) -> Self {
23480 Self(self.0.saturating_sub(1))
23481 }
23482
23483 fn minus(&self, other: Self) -> u32 {
23484 self.0 - other.0
23485 }
23486}
23487
23488trait RowRangeExt {
23489 type Row;
23490
23491 fn len(&self) -> usize;
23492
23493 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23494}
23495
23496impl RowRangeExt for Range<MultiBufferRow> {
23497 type Row = MultiBufferRow;
23498
23499 fn len(&self) -> usize {
23500 (self.end.0 - self.start.0) as usize
23501 }
23502
23503 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23504 (self.start.0..self.end.0).map(MultiBufferRow)
23505 }
23506}
23507
23508impl RowRangeExt for Range<DisplayRow> {
23509 type Row = DisplayRow;
23510
23511 fn len(&self) -> usize {
23512 (self.end.0 - self.start.0) as usize
23513 }
23514
23515 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23516 (self.start.0..self.end.0).map(DisplayRow)
23517 }
23518}
23519
23520/// If select range has more than one line, we
23521/// just point the cursor to range.start.
23522fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23523 if range.start.row == range.end.row {
23524 range
23525 } else {
23526 range.start..range.start
23527 }
23528}
23529pub struct KillRing(ClipboardItem);
23530impl Global for KillRing {}
23531
23532const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23533
23534enum BreakpointPromptEditAction {
23535 Log,
23536 Condition,
23537 HitCondition,
23538}
23539
23540struct BreakpointPromptEditor {
23541 pub(crate) prompt: Entity<Editor>,
23542 editor: WeakEntity<Editor>,
23543 breakpoint_anchor: Anchor,
23544 breakpoint: Breakpoint,
23545 edit_action: BreakpointPromptEditAction,
23546 block_ids: HashSet<CustomBlockId>,
23547 editor_margins: Arc<Mutex<EditorMargins>>,
23548 _subscriptions: Vec<Subscription>,
23549}
23550
23551impl BreakpointPromptEditor {
23552 const MAX_LINES: u8 = 4;
23553
23554 fn new(
23555 editor: WeakEntity<Editor>,
23556 breakpoint_anchor: Anchor,
23557 breakpoint: Breakpoint,
23558 edit_action: BreakpointPromptEditAction,
23559 window: &mut Window,
23560 cx: &mut Context<Self>,
23561 ) -> Self {
23562 let base_text = match edit_action {
23563 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23564 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23565 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23566 }
23567 .map(|msg| msg.to_string())
23568 .unwrap_or_default();
23569
23570 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23571 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23572
23573 let prompt = cx.new(|cx| {
23574 let mut prompt = Editor::new(
23575 EditorMode::AutoHeight {
23576 min_lines: 1,
23577 max_lines: Some(Self::MAX_LINES as usize),
23578 },
23579 buffer,
23580 None,
23581 window,
23582 cx,
23583 );
23584 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23585 prompt.set_show_cursor_when_unfocused(false, cx);
23586 prompt.set_placeholder_text(
23587 match edit_action {
23588 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23589 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23590 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23591 },
23592 cx,
23593 );
23594
23595 prompt
23596 });
23597
23598 Self {
23599 prompt,
23600 editor,
23601 breakpoint_anchor,
23602 breakpoint,
23603 edit_action,
23604 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23605 block_ids: Default::default(),
23606 _subscriptions: vec![],
23607 }
23608 }
23609
23610 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23611 self.block_ids.extend(block_ids)
23612 }
23613
23614 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23615 if let Some(editor) = self.editor.upgrade() {
23616 let message = self
23617 .prompt
23618 .read(cx)
23619 .buffer
23620 .read(cx)
23621 .as_singleton()
23622 .expect("A multi buffer in breakpoint prompt isn't possible")
23623 .read(cx)
23624 .as_rope()
23625 .to_string();
23626
23627 editor.update(cx, |editor, cx| {
23628 editor.edit_breakpoint_at_anchor(
23629 self.breakpoint_anchor,
23630 self.breakpoint.clone(),
23631 match self.edit_action {
23632 BreakpointPromptEditAction::Log => {
23633 BreakpointEditAction::EditLogMessage(message.into())
23634 }
23635 BreakpointPromptEditAction::Condition => {
23636 BreakpointEditAction::EditCondition(message.into())
23637 }
23638 BreakpointPromptEditAction::HitCondition => {
23639 BreakpointEditAction::EditHitCondition(message.into())
23640 }
23641 },
23642 cx,
23643 );
23644
23645 editor.remove_blocks(self.block_ids.clone(), None, cx);
23646 cx.focus_self(window);
23647 });
23648 }
23649 }
23650
23651 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23652 self.editor
23653 .update(cx, |editor, cx| {
23654 editor.remove_blocks(self.block_ids.clone(), None, cx);
23655 window.focus(&editor.focus_handle);
23656 })
23657 .log_err();
23658 }
23659
23660 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23661 let settings = ThemeSettings::get_global(cx);
23662 let text_style = TextStyle {
23663 color: if self.prompt.read(cx).read_only(cx) {
23664 cx.theme().colors().text_disabled
23665 } else {
23666 cx.theme().colors().text
23667 },
23668 font_family: settings.buffer_font.family.clone(),
23669 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23670 font_size: settings.buffer_font_size(cx).into(),
23671 font_weight: settings.buffer_font.weight,
23672 line_height: relative(settings.buffer_line_height.value()),
23673 ..Default::default()
23674 };
23675 EditorElement::new(
23676 &self.prompt,
23677 EditorStyle {
23678 background: cx.theme().colors().editor_background,
23679 local_player: cx.theme().players().local(),
23680 text: text_style,
23681 ..Default::default()
23682 },
23683 )
23684 }
23685}
23686
23687impl Render for BreakpointPromptEditor {
23688 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23689 let editor_margins = *self.editor_margins.lock();
23690 let gutter_dimensions = editor_margins.gutter;
23691 h_flex()
23692 .key_context("Editor")
23693 .bg(cx.theme().colors().editor_background)
23694 .border_y_1()
23695 .border_color(cx.theme().status().info_border)
23696 .size_full()
23697 .py(window.line_height() / 2.5)
23698 .on_action(cx.listener(Self::confirm))
23699 .on_action(cx.listener(Self::cancel))
23700 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23701 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23702 }
23703}
23704
23705impl Focusable for BreakpointPromptEditor {
23706 fn focus_handle(&self, cx: &App) -> FocusHandle {
23707 self.prompt.focus_handle(cx)
23708 }
23709}
23710
23711fn all_edits_insertions_or_deletions(
23712 edits: &Vec<(Range<Anchor>, String)>,
23713 snapshot: &MultiBufferSnapshot,
23714) -> bool {
23715 let mut all_insertions = true;
23716 let mut all_deletions = true;
23717
23718 for (range, new_text) in edits.iter() {
23719 let range_is_empty = range.to_offset(snapshot).is_empty();
23720 let text_is_empty = new_text.is_empty();
23721
23722 if range_is_empty != text_is_empty {
23723 if range_is_empty {
23724 all_deletions = false;
23725 } else {
23726 all_insertions = false;
23727 }
23728 } else {
23729 return false;
23730 }
23731
23732 if !all_insertions && !all_deletions {
23733 return false;
23734 }
23735 }
23736 all_insertions || all_deletions
23737}
23738
23739struct MissingEditPredictionKeybindingTooltip;
23740
23741impl Render for MissingEditPredictionKeybindingTooltip {
23742 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23743 ui::tooltip_container(window, cx, |container, _, cx| {
23744 container
23745 .flex_shrink_0()
23746 .max_w_80()
23747 .min_h(rems_from_px(124.))
23748 .justify_between()
23749 .child(
23750 v_flex()
23751 .flex_1()
23752 .text_ui_sm(cx)
23753 .child(Label::new("Conflict with Accept Keybinding"))
23754 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23755 )
23756 .child(
23757 h_flex()
23758 .pb_1()
23759 .gap_1()
23760 .items_end()
23761 .w_full()
23762 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23763 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23764 }))
23765 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23766 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23767 })),
23768 )
23769 })
23770 }
23771}
23772
23773#[derive(Debug, Clone, Copy, PartialEq)]
23774pub struct LineHighlight {
23775 pub background: Background,
23776 pub border: Option<gpui::Hsla>,
23777 pub include_gutter: bool,
23778 pub type_id: Option<TypeId>,
23779}
23780
23781struct LineManipulationResult {
23782 pub new_text: String,
23783 pub line_count_before: usize,
23784 pub line_count_after: usize,
23785}
23786
23787fn render_diff_hunk_controls(
23788 row: u32,
23789 status: &DiffHunkStatus,
23790 hunk_range: Range<Anchor>,
23791 is_created_file: bool,
23792 line_height: Pixels,
23793 editor: &Entity<Editor>,
23794 _window: &mut Window,
23795 cx: &mut App,
23796) -> AnyElement {
23797 h_flex()
23798 .h(line_height)
23799 .mr_1()
23800 .gap_1()
23801 .px_0p5()
23802 .pb_1()
23803 .border_x_1()
23804 .border_b_1()
23805 .border_color(cx.theme().colors().border_variant)
23806 .rounded_b_lg()
23807 .bg(cx.theme().colors().editor_background)
23808 .gap_1()
23809 .block_mouse_except_scroll()
23810 .shadow_md()
23811 .child(if status.has_secondary_hunk() {
23812 Button::new(("stage", row as u64), "Stage")
23813 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23814 .tooltip({
23815 let focus_handle = editor.focus_handle(cx);
23816 move |window, cx| {
23817 Tooltip::for_action_in(
23818 "Stage Hunk",
23819 &::git::ToggleStaged,
23820 &focus_handle,
23821 window,
23822 cx,
23823 )
23824 }
23825 })
23826 .on_click({
23827 let editor = editor.clone();
23828 move |_event, _window, cx| {
23829 editor.update(cx, |editor, cx| {
23830 editor.stage_or_unstage_diff_hunks(
23831 true,
23832 vec![hunk_range.start..hunk_range.start],
23833 cx,
23834 );
23835 });
23836 }
23837 })
23838 } else {
23839 Button::new(("unstage", row as u64), "Unstage")
23840 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23841 .tooltip({
23842 let focus_handle = editor.focus_handle(cx);
23843 move |window, cx| {
23844 Tooltip::for_action_in(
23845 "Unstage Hunk",
23846 &::git::ToggleStaged,
23847 &focus_handle,
23848 window,
23849 cx,
23850 )
23851 }
23852 })
23853 .on_click({
23854 let editor = editor.clone();
23855 move |_event, _window, cx| {
23856 editor.update(cx, |editor, cx| {
23857 editor.stage_or_unstage_diff_hunks(
23858 false,
23859 vec![hunk_range.start..hunk_range.start],
23860 cx,
23861 );
23862 });
23863 }
23864 })
23865 })
23866 .child(
23867 Button::new(("restore", row as u64), "Restore")
23868 .tooltip({
23869 let focus_handle = editor.focus_handle(cx);
23870 move |window, cx| {
23871 Tooltip::for_action_in(
23872 "Restore Hunk",
23873 &::git::Restore,
23874 &focus_handle,
23875 window,
23876 cx,
23877 )
23878 }
23879 })
23880 .on_click({
23881 let editor = editor.clone();
23882 move |_event, window, cx| {
23883 editor.update(cx, |editor, cx| {
23884 let snapshot = editor.snapshot(window, cx);
23885 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23886 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23887 });
23888 }
23889 })
23890 .disabled(is_created_file),
23891 )
23892 .when(
23893 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23894 |el| {
23895 el.child(
23896 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23897 .shape(IconButtonShape::Square)
23898 .icon_size(IconSize::Small)
23899 // .disabled(!has_multiple_hunks)
23900 .tooltip({
23901 let focus_handle = editor.focus_handle(cx);
23902 move |window, cx| {
23903 Tooltip::for_action_in(
23904 "Next Hunk",
23905 &GoToHunk,
23906 &focus_handle,
23907 window,
23908 cx,
23909 )
23910 }
23911 })
23912 .on_click({
23913 let editor = editor.clone();
23914 move |_event, window, cx| {
23915 editor.update(cx, |editor, cx| {
23916 let snapshot = editor.snapshot(window, cx);
23917 let position =
23918 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23919 editor.go_to_hunk_before_or_after_position(
23920 &snapshot,
23921 position,
23922 Direction::Next,
23923 window,
23924 cx,
23925 );
23926 editor.expand_selected_diff_hunks(cx);
23927 });
23928 }
23929 }),
23930 )
23931 .child(
23932 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23933 .shape(IconButtonShape::Square)
23934 .icon_size(IconSize::Small)
23935 // .disabled(!has_multiple_hunks)
23936 .tooltip({
23937 let focus_handle = editor.focus_handle(cx);
23938 move |window, cx| {
23939 Tooltip::for_action_in(
23940 "Previous Hunk",
23941 &GoToPreviousHunk,
23942 &focus_handle,
23943 window,
23944 cx,
23945 )
23946 }
23947 })
23948 .on_click({
23949 let editor = editor.clone();
23950 move |_event, window, cx| {
23951 editor.update(cx, |editor, cx| {
23952 let snapshot = editor.snapshot(window, cx);
23953 let point =
23954 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23955 editor.go_to_hunk_before_or_after_position(
23956 &snapshot,
23957 point,
23958 Direction::Prev,
23959 window,
23960 cx,
23961 );
23962 editor.expand_selected_diff_hunks(cx);
23963 });
23964 }
23965 }),
23966 )
23967 },
23968 )
23969 .into_any_element()
23970}