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 match *self {
786 Self::Disabled => true,
787 _ => false,
788 }
789 }
790
791 fn settings_visibility(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 ..
796 } => setting_configuration,
797 _ => false,
798 }
799 }
800
801 fn visible(&self) -> bool {
802 match *self {
803 Self::Enabled {
804 setting_configuration,
805 toggle_override,
806 } => setting_configuration ^ toggle_override,
807 _ => false,
808 }
809 }
810
811 fn toggle_visibility(&self) -> Self {
812 match *self {
813 Self::Enabled {
814 toggle_override,
815 setting_configuration,
816 } => Self::Enabled {
817 setting_configuration,
818 toggle_override: !toggle_override,
819 },
820 Self::Disabled => Self::Disabled,
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
826struct RunnableTasks {
827 templates: Vec<(TaskSourceKind, TaskTemplate)>,
828 offset: multi_buffer::Anchor,
829 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
830 column: u32,
831 // Values of all named captures, including those starting with '_'
832 extra_variables: HashMap<String, String>,
833 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
834 context_range: Range<BufferOffset>,
835}
836
837impl RunnableTasks {
838 fn resolve<'a>(
839 &'a self,
840 cx: &'a task::TaskContext,
841 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
842 self.templates.iter().filter_map(|(kind, template)| {
843 template
844 .resolve_task(&kind.to_id_base(), cx)
845 .map(|task| (kind.clone(), task))
846 })
847 }
848}
849
850#[derive(Clone)]
851pub struct ResolvedTasks {
852 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
853 position: Anchor,
854}
855
856#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
857struct BufferOffset(usize);
858
859// Addons allow storing per-editor state in other crates (e.g. Vim)
860pub trait Addon: 'static {
861 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
862
863 fn render_buffer_header_controls(
864 &self,
865 _: &ExcerptInfo,
866 _: &Window,
867 _: &App,
868 ) -> Option<AnyElement> {
869 None
870 }
871
872 fn to_any(&self) -> &dyn std::any::Any;
873
874 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
875 None
876 }
877}
878
879struct ChangeLocation {
880 current: Option<Vec<Anchor>>,
881 original: Vec<Anchor>,
882}
883impl ChangeLocation {
884 fn locations(&self) -> &[Anchor] {
885 self.current.as_ref().unwrap_or(&self.original)
886 }
887}
888
889/// A set of caret positions, registered when the editor was edited.
890pub struct ChangeList {
891 changes: Vec<ChangeLocation>,
892 /// Currently "selected" change.
893 position: Option<usize>,
894}
895
896impl ChangeList {
897 pub fn new() -> Self {
898 Self {
899 changes: Vec::new(),
900 position: None,
901 }
902 }
903
904 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
905 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
906 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
907 if self.changes.is_empty() {
908 return None;
909 }
910
911 let prev = self.position.unwrap_or(self.changes.len());
912 let next = if direction == Direction::Prev {
913 prev.saturating_sub(count)
914 } else {
915 (prev + count).min(self.changes.len() - 1)
916 };
917 self.position = Some(next);
918 self.changes.get(next).map(|change| change.locations())
919 }
920
921 /// Adds a new change to the list, resetting the change list position.
922 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
923 self.position.take();
924 if let Some(last) = self.changes.last_mut()
925 && group
926 {
927 last.current = Some(new_positions)
928 } else {
929 self.changes.push(ChangeLocation {
930 original: new_positions,
931 current: None,
932 });
933 }
934 }
935
936 pub fn last(&self) -> Option<&[Anchor]> {
937 self.changes.last().map(|change| change.locations())
938 }
939
940 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
941 self.changes.last().map(|change| change.original.as_slice())
942 }
943
944 pub fn invert_last_group(&mut self) {
945 if let Some(last) = self.changes.last_mut()
946 && let Some(current) = last.current.as_mut()
947 {
948 mem::swap(&mut last.original, current);
949 }
950 }
951}
952
953#[derive(Clone)]
954struct InlineBlamePopoverState {
955 scroll_handle: ScrollHandle,
956 commit_message: Option<ParsedCommitMessage>,
957 markdown: Entity<Markdown>,
958}
959
960struct InlineBlamePopover {
961 position: gpui::Point<Pixels>,
962 hide_task: Option<Task<()>>,
963 popover_bounds: Option<Bounds<Pixels>>,
964 popover_state: InlineBlamePopoverState,
965 keyboard_grace: bool,
966}
967
968enum SelectionDragState {
969 /// State when no drag related activity is detected.
970 None,
971 /// State when the mouse is down on a selection that is about to be dragged.
972 ReadyToDrag {
973 selection: Selection<Anchor>,
974 click_position: gpui::Point<Pixels>,
975 mouse_down_time: Instant,
976 },
977 /// State when the mouse is dragging the selection in the editor.
978 Dragging {
979 selection: Selection<Anchor>,
980 drop_cursor: Selection<Anchor>,
981 hide_drop_cursor: bool,
982 },
983}
984
985enum ColumnarSelectionState {
986 FromMouse {
987 selection_tail: Anchor,
988 display_point: Option<DisplayPoint>,
989 },
990 FromSelection {
991 selection_tail: Anchor,
992 },
993}
994
995/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
996/// a breakpoint on them.
997#[derive(Clone, Copy, Debug, PartialEq, Eq)]
998struct PhantomBreakpointIndicator {
999 display_row: DisplayRow,
1000 /// There's a small debounce between hovering over the line and showing the indicator.
1001 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1002 is_active: bool,
1003 collides_with_existing_breakpoint: bool,
1004}
1005
1006/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1007///
1008/// See the [module level documentation](self) for more information.
1009pub struct Editor {
1010 focus_handle: FocusHandle,
1011 last_focused_descendant: Option<WeakFocusHandle>,
1012 /// The text buffer being edited
1013 buffer: Entity<MultiBuffer>,
1014 /// Map of how text in the buffer should be displayed.
1015 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1016 pub display_map: Entity<DisplayMap>,
1017 pub selections: SelectionsCollection,
1018 pub scroll_manager: ScrollManager,
1019 /// When inline assist editors are linked, they all render cursors because
1020 /// typing enters text into each of them, even the ones that aren't focused.
1021 pub(crate) show_cursor_when_unfocused: bool,
1022 columnar_selection_state: Option<ColumnarSelectionState>,
1023 add_selections_state: Option<AddSelectionsState>,
1024 select_next_state: Option<SelectNextState>,
1025 select_prev_state: Option<SelectNextState>,
1026 selection_history: SelectionHistory,
1027 defer_selection_effects: bool,
1028 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1029 autoclose_regions: Vec<AutocloseRegion>,
1030 snippet_stack: InvalidationStack<SnippetState>,
1031 select_syntax_node_history: SelectSyntaxNodeHistory,
1032 ime_transaction: Option<TransactionId>,
1033 pub diagnostics_max_severity: DiagnosticSeverity,
1034 active_diagnostics: ActiveDiagnostic,
1035 show_inline_diagnostics: bool,
1036 inline_diagnostics_update: Task<()>,
1037 inline_diagnostics_enabled: bool,
1038 diagnostics_enabled: bool,
1039 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1040 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1041 hard_wrap: Option<usize>,
1042 project: Option<Entity<Project>>,
1043 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1044 completion_provider: Option<Rc<dyn CompletionProvider>>,
1045 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1046 blink_manager: Entity<BlinkManager>,
1047 show_cursor_names: bool,
1048 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1049 pub show_local_selections: bool,
1050 mode: EditorMode,
1051 show_breadcrumbs: bool,
1052 show_gutter: bool,
1053 show_scrollbars: ScrollbarAxes,
1054 minimap_visibility: MinimapVisibility,
1055 offset_content: bool,
1056 disable_expand_excerpt_buttons: bool,
1057 show_line_numbers: Option<bool>,
1058 use_relative_line_numbers: Option<bool>,
1059 show_git_diff_gutter: Option<bool>,
1060 show_code_actions: Option<bool>,
1061 show_runnables: Option<bool>,
1062 show_breakpoints: Option<bool>,
1063 show_wrap_guides: Option<bool>,
1064 show_indent_guides: Option<bool>,
1065 placeholder_text: Option<Arc<str>>,
1066 highlight_order: usize,
1067 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1068 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1069 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1070 scrollbar_marker_state: ScrollbarMarkerState,
1071 active_indent_guides_state: ActiveIndentGuidesState,
1072 nav_history: Option<ItemNavHistory>,
1073 context_menu: RefCell<Option<CodeContextMenu>>,
1074 context_menu_options: Option<ContextMenuOptions>,
1075 mouse_context_menu: Option<MouseContextMenu>,
1076 completion_tasks: Vec<(CompletionId, Task<()>)>,
1077 inline_blame_popover: Option<InlineBlamePopover>,
1078 inline_blame_popover_show_task: Option<Task<()>>,
1079 signature_help_state: SignatureHelpState,
1080 auto_signature_help: Option<bool>,
1081 find_all_references_task_sources: Vec<Anchor>,
1082 next_completion_id: CompletionId,
1083 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1084 code_actions_task: Option<Task<Result<()>>>,
1085 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1086 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1087 document_highlights_task: Option<Task<()>>,
1088 linked_editing_range_task: Option<Task<Option<()>>>,
1089 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1090 pending_rename: Option<RenameState>,
1091 searchable: bool,
1092 cursor_shape: CursorShape,
1093 current_line_highlight: Option<CurrentLineHighlight>,
1094 collapse_matches: bool,
1095 autoindent_mode: Option<AutoindentMode>,
1096 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1097 input_enabled: bool,
1098 use_modal_editing: bool,
1099 read_only: bool,
1100 leader_id: Option<CollaboratorId>,
1101 remote_id: Option<ViewId>,
1102 pub hover_state: HoverState,
1103 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1104 gutter_hovered: bool,
1105 hovered_link_state: Option<HoveredLinkState>,
1106 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1107 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1108 active_edit_prediction: Option<EditPredictionState>,
1109 /// Used to prevent flickering as the user types while the menu is open
1110 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1111 edit_prediction_settings: EditPredictionSettings,
1112 edit_predictions_hidden_for_vim_mode: bool,
1113 show_edit_predictions_override: Option<bool>,
1114 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1115 edit_prediction_preview: EditPredictionPreview,
1116 edit_prediction_indent_conflict: bool,
1117 edit_prediction_requires_modifier_in_indent_conflict: bool,
1118 inlay_hint_cache: InlayHintCache,
1119 next_inlay_id: usize,
1120 _subscriptions: Vec<Subscription>,
1121 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1122 gutter_dimensions: GutterDimensions,
1123 style: Option<EditorStyle>,
1124 text_style_refinement: Option<TextStyleRefinement>,
1125 next_editor_action_id: EditorActionId,
1126 editor_actions: Rc<
1127 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1128 >,
1129 use_autoclose: bool,
1130 use_auto_surround: bool,
1131 auto_replace_emoji_shortcode: bool,
1132 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1133 show_git_blame_gutter: bool,
1134 show_git_blame_inline: bool,
1135 show_git_blame_inline_delay_task: Option<Task<()>>,
1136 git_blame_inline_enabled: bool,
1137 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1138 serialize_dirty_buffers: bool,
1139 show_selection_menu: Option<bool>,
1140 blame: Option<Entity<GitBlame>>,
1141 blame_subscription: Option<Subscription>,
1142 custom_context_menu: Option<
1143 Box<
1144 dyn 'static
1145 + Fn(
1146 &mut Self,
1147 DisplayPoint,
1148 &mut Window,
1149 &mut Context<Self>,
1150 ) -> Option<Entity<ui::ContextMenu>>,
1151 >,
1152 >,
1153 last_bounds: Option<Bounds<Pixels>>,
1154 last_position_map: Option<Rc<PositionMap>>,
1155 expect_bounds_change: Option<Bounds<Pixels>>,
1156 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1157 tasks_update_task: Option<Task<()>>,
1158 breakpoint_store: Option<Entity<BreakpointStore>>,
1159 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1160 hovered_diff_hunk_row: Option<DisplayRow>,
1161 pull_diagnostics_task: Task<()>,
1162 in_project_search: bool,
1163 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1164 breadcrumb_header: Option<String>,
1165 focused_block: Option<FocusedBlock>,
1166 next_scroll_position: NextScrollCursorCenterTopBottom,
1167 addons: HashMap<TypeId, Box<dyn Addon>>,
1168 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1169 load_diff_task: Option<Shared<Task<()>>>,
1170 /// Whether we are temporarily displaying a diff other than git's
1171 temporary_diff_override: bool,
1172 selection_mark_mode: bool,
1173 toggle_fold_multiple_buffers: Task<()>,
1174 _scroll_cursor_center_top_bottom_task: Task<()>,
1175 serialize_selections: Task<()>,
1176 serialize_folds: Task<()>,
1177 mouse_cursor_hidden: bool,
1178 minimap: Option<Entity<Self>>,
1179 hide_mouse_mode: HideMouseMode,
1180 pub change_list: ChangeList,
1181 inline_value_cache: InlineValueCache,
1182 selection_drag_state: SelectionDragState,
1183 next_color_inlay_id: usize,
1184 colors: Option<LspColorData>,
1185 folding_newlines: Task<()>,
1186}
1187
1188#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1189enum NextScrollCursorCenterTopBottom {
1190 #[default]
1191 Center,
1192 Top,
1193 Bottom,
1194}
1195
1196impl NextScrollCursorCenterTopBottom {
1197 fn next(&self) -> Self {
1198 match self {
1199 Self::Center => Self::Top,
1200 Self::Top => Self::Bottom,
1201 Self::Bottom => Self::Center,
1202 }
1203 }
1204}
1205
1206#[derive(Clone)]
1207pub struct EditorSnapshot {
1208 pub mode: EditorMode,
1209 show_gutter: bool,
1210 show_line_numbers: Option<bool>,
1211 show_git_diff_gutter: Option<bool>,
1212 show_code_actions: Option<bool>,
1213 show_runnables: Option<bool>,
1214 show_breakpoints: Option<bool>,
1215 git_blame_gutter_max_author_length: Option<usize>,
1216 pub display_snapshot: DisplaySnapshot,
1217 pub placeholder_text: Option<Arc<str>>,
1218 is_focused: bool,
1219 scroll_anchor: ScrollAnchor,
1220 ongoing_scroll: OngoingScroll,
1221 current_line_highlight: CurrentLineHighlight,
1222 gutter_hovered: bool,
1223}
1224
1225#[derive(Default, Debug, Clone, Copy)]
1226pub struct GutterDimensions {
1227 pub left_padding: Pixels,
1228 pub right_padding: Pixels,
1229 pub width: Pixels,
1230 pub margin: Pixels,
1231 pub git_blame_entries_width: Option<Pixels>,
1232}
1233
1234impl GutterDimensions {
1235 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1236 Self {
1237 margin: Self::default_gutter_margin(font_id, font_size, cx),
1238 ..Default::default()
1239 }
1240 }
1241
1242 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1243 -cx.text_system().descent(font_id, font_size)
1244 }
1245 /// The full width of the space taken up by the gutter.
1246 pub fn full_width(&self) -> Pixels {
1247 self.margin + self.width
1248 }
1249
1250 /// The width of the space reserved for the fold indicators,
1251 /// use alongside 'justify_end' and `gutter_width` to
1252 /// right align content with the line numbers
1253 pub fn fold_area_width(&self) -> Pixels {
1254 self.margin + self.right_padding
1255 }
1256}
1257
1258struct CharacterDimensions {
1259 em_width: Pixels,
1260 em_advance: Pixels,
1261 line_height: Pixels,
1262}
1263
1264#[derive(Debug)]
1265pub struct RemoteSelection {
1266 pub replica_id: ReplicaId,
1267 pub selection: Selection<Anchor>,
1268 pub cursor_shape: CursorShape,
1269 pub collaborator_id: CollaboratorId,
1270 pub line_mode: bool,
1271 pub user_name: Option<SharedString>,
1272 pub color: PlayerColor,
1273}
1274
1275#[derive(Clone, Debug)]
1276struct SelectionHistoryEntry {
1277 selections: Arc<[Selection<Anchor>]>,
1278 select_next_state: Option<SelectNextState>,
1279 select_prev_state: Option<SelectNextState>,
1280 add_selections_state: Option<AddSelectionsState>,
1281}
1282
1283#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1284enum SelectionHistoryMode {
1285 Normal,
1286 Undoing,
1287 Redoing,
1288 Skipping,
1289}
1290
1291#[derive(Clone, PartialEq, Eq, Hash)]
1292struct HoveredCursor {
1293 replica_id: u16,
1294 selection_id: usize,
1295}
1296
1297impl Default for SelectionHistoryMode {
1298 fn default() -> Self {
1299 Self::Normal
1300 }
1301}
1302
1303#[derive(Debug)]
1304/// SelectionEffects controls the side-effects of updating the selection.
1305///
1306/// The default behaviour does "what you mostly want":
1307/// - it pushes to the nav history if the cursor moved by >10 lines
1308/// - it re-triggers completion requests
1309/// - it scrolls to fit
1310///
1311/// You might want to modify these behaviours. For example when doing a "jump"
1312/// like go to definition, we always want to add to nav history; but when scrolling
1313/// in vim mode we never do.
1314///
1315/// Similarly, you might want to disable scrolling if you don't want the viewport to
1316/// move.
1317#[derive(Clone)]
1318pub struct SelectionEffects {
1319 nav_history: Option<bool>,
1320 completions: bool,
1321 scroll: Option<Autoscroll>,
1322}
1323
1324impl Default for SelectionEffects {
1325 fn default() -> Self {
1326 Self {
1327 nav_history: None,
1328 completions: true,
1329 scroll: Some(Autoscroll::fit()),
1330 }
1331 }
1332}
1333impl SelectionEffects {
1334 pub fn scroll(scroll: Autoscroll) -> Self {
1335 Self {
1336 scroll: Some(scroll),
1337 ..Default::default()
1338 }
1339 }
1340
1341 pub fn no_scroll() -> Self {
1342 Self {
1343 scroll: None,
1344 ..Default::default()
1345 }
1346 }
1347
1348 pub fn completions(self, completions: bool) -> Self {
1349 Self {
1350 completions,
1351 ..self
1352 }
1353 }
1354
1355 pub fn nav_history(self, nav_history: bool) -> Self {
1356 Self {
1357 nav_history: Some(nav_history),
1358 ..self
1359 }
1360 }
1361}
1362
1363struct DeferredSelectionEffectsState {
1364 changed: bool,
1365 effects: SelectionEffects,
1366 old_cursor_position: Anchor,
1367 history_entry: SelectionHistoryEntry,
1368}
1369
1370#[derive(Default)]
1371struct SelectionHistory {
1372 #[allow(clippy::type_complexity)]
1373 selections_by_transaction:
1374 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1375 mode: SelectionHistoryMode,
1376 undo_stack: VecDeque<SelectionHistoryEntry>,
1377 redo_stack: VecDeque<SelectionHistoryEntry>,
1378}
1379
1380impl SelectionHistory {
1381 #[track_caller]
1382 fn insert_transaction(
1383 &mut self,
1384 transaction_id: TransactionId,
1385 selections: Arc<[Selection<Anchor>]>,
1386 ) {
1387 if selections.is_empty() {
1388 log::error!(
1389 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1390 std::panic::Location::caller()
1391 );
1392 return;
1393 }
1394 self.selections_by_transaction
1395 .insert(transaction_id, (selections, None));
1396 }
1397
1398 #[allow(clippy::type_complexity)]
1399 fn transaction(
1400 &self,
1401 transaction_id: TransactionId,
1402 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1403 self.selections_by_transaction.get(&transaction_id)
1404 }
1405
1406 #[allow(clippy::type_complexity)]
1407 fn transaction_mut(
1408 &mut self,
1409 transaction_id: TransactionId,
1410 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1411 self.selections_by_transaction.get_mut(&transaction_id)
1412 }
1413
1414 fn push(&mut self, entry: SelectionHistoryEntry) {
1415 if !entry.selections.is_empty() {
1416 match self.mode {
1417 SelectionHistoryMode::Normal => {
1418 self.push_undo(entry);
1419 self.redo_stack.clear();
1420 }
1421 SelectionHistoryMode::Undoing => self.push_redo(entry),
1422 SelectionHistoryMode::Redoing => self.push_undo(entry),
1423 SelectionHistoryMode::Skipping => {}
1424 }
1425 }
1426 }
1427
1428 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1429 if self
1430 .undo_stack
1431 .back()
1432 .is_none_or(|e| e.selections != entry.selections)
1433 {
1434 self.undo_stack.push_back(entry);
1435 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1436 self.undo_stack.pop_front();
1437 }
1438 }
1439 }
1440
1441 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1442 if self
1443 .redo_stack
1444 .back()
1445 .is_none_or(|e| e.selections != entry.selections)
1446 {
1447 self.redo_stack.push_back(entry);
1448 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1449 self.redo_stack.pop_front();
1450 }
1451 }
1452 }
1453}
1454
1455#[derive(Clone, Copy)]
1456pub struct RowHighlightOptions {
1457 pub autoscroll: bool,
1458 pub include_gutter: bool,
1459}
1460
1461impl Default for RowHighlightOptions {
1462 fn default() -> Self {
1463 Self {
1464 autoscroll: Default::default(),
1465 include_gutter: true,
1466 }
1467 }
1468}
1469
1470struct RowHighlight {
1471 index: usize,
1472 range: Range<Anchor>,
1473 color: Hsla,
1474 options: RowHighlightOptions,
1475 type_id: TypeId,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsState {
1480 groups: Vec<AddSelectionsGroup>,
1481}
1482
1483#[derive(Clone, Debug)]
1484struct AddSelectionsGroup {
1485 above: bool,
1486 stack: Vec<usize>,
1487}
1488
1489#[derive(Clone)]
1490struct SelectNextState {
1491 query: AhoCorasick,
1492 wordwise: bool,
1493 done: bool,
1494}
1495
1496impl std::fmt::Debug for SelectNextState {
1497 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1498 f.debug_struct(std::any::type_name::<Self>())
1499 .field("wordwise", &self.wordwise)
1500 .field("done", &self.done)
1501 .finish()
1502 }
1503}
1504
1505#[derive(Debug)]
1506struct AutocloseRegion {
1507 selection_id: usize,
1508 range: Range<Anchor>,
1509 pair: BracketPair,
1510}
1511
1512#[derive(Debug)]
1513struct SnippetState {
1514 ranges: Vec<Vec<Range<Anchor>>>,
1515 active_index: usize,
1516 choices: Vec<Option<Vec<String>>>,
1517}
1518
1519#[doc(hidden)]
1520pub struct RenameState {
1521 pub range: Range<Anchor>,
1522 pub old_name: Arc<str>,
1523 pub editor: Entity<Editor>,
1524 block_id: CustomBlockId,
1525}
1526
1527struct InvalidationStack<T>(Vec<T>);
1528
1529struct RegisteredEditPredictionProvider {
1530 provider: Arc<dyn EditPredictionProviderHandle>,
1531 _subscription: Subscription,
1532}
1533
1534#[derive(Debug, PartialEq, Eq)]
1535pub struct ActiveDiagnosticGroup {
1536 pub active_range: Range<Anchor>,
1537 pub active_message: String,
1538 pub group_id: usize,
1539 pub blocks: HashSet<CustomBlockId>,
1540}
1541
1542#[derive(Debug, PartialEq, Eq)]
1543
1544pub(crate) enum ActiveDiagnostic {
1545 None,
1546 All,
1547 Group(ActiveDiagnosticGroup),
1548}
1549
1550#[derive(Serialize, Deserialize, Clone, Debug)]
1551pub struct ClipboardSelection {
1552 /// The number of bytes in this selection.
1553 pub len: usize,
1554 /// Whether this was a full-line selection.
1555 pub is_entire_line: bool,
1556 /// The indentation of the first line when this content was originally copied.
1557 pub first_line_indent: u32,
1558}
1559
1560// selections, scroll behavior, was newest selection reversed
1561type SelectSyntaxNodeHistoryState = (
1562 Box<[Selection<usize>]>,
1563 SelectSyntaxNodeScrollBehavior,
1564 bool,
1565);
1566
1567#[derive(Default)]
1568struct SelectSyntaxNodeHistory {
1569 stack: Vec<SelectSyntaxNodeHistoryState>,
1570 // disable temporarily to allow changing selections without losing the stack
1571 pub disable_clearing: bool,
1572}
1573
1574impl SelectSyntaxNodeHistory {
1575 pub fn try_clear(&mut self) {
1576 if !self.disable_clearing {
1577 self.stack.clear();
1578 }
1579 }
1580
1581 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1582 self.stack.push(selection);
1583 }
1584
1585 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1586 self.stack.pop()
1587 }
1588}
1589
1590enum SelectSyntaxNodeScrollBehavior {
1591 CursorTop,
1592 FitSelection,
1593 CursorBottom,
1594}
1595
1596#[derive(Debug)]
1597pub(crate) struct NavigationData {
1598 cursor_anchor: Anchor,
1599 cursor_position: Point,
1600 scroll_anchor: ScrollAnchor,
1601 scroll_top_row: u32,
1602}
1603
1604#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1605pub enum GotoDefinitionKind {
1606 Symbol,
1607 Declaration,
1608 Type,
1609 Implementation,
1610}
1611
1612#[derive(Debug, Clone)]
1613enum InlayHintRefreshReason {
1614 ModifiersChanged(bool),
1615 Toggle(bool),
1616 SettingsChange(InlayHintSettings),
1617 NewLinesShown,
1618 BufferEdited(HashSet<Arc<Language>>),
1619 RefreshRequested,
1620 ExcerptsRemoved(Vec<ExcerptId>),
1621}
1622
1623impl InlayHintRefreshReason {
1624 fn description(&self) -> &'static str {
1625 match self {
1626 Self::ModifiersChanged(_) => "modifiers changed",
1627 Self::Toggle(_) => "toggle",
1628 Self::SettingsChange(_) => "settings change",
1629 Self::NewLinesShown => "new lines shown",
1630 Self::BufferEdited(_) => "buffer edited",
1631 Self::RefreshRequested => "refresh requested",
1632 Self::ExcerptsRemoved(_) => "excerpts removed",
1633 }
1634 }
1635}
1636
1637pub enum FormatTarget {
1638 Buffers(HashSet<Entity<Buffer>>),
1639 Ranges(Vec<Range<MultiBufferPoint>>),
1640}
1641
1642pub(crate) struct FocusedBlock {
1643 id: BlockId,
1644 focus_handle: WeakFocusHandle,
1645}
1646
1647#[derive(Clone)]
1648enum JumpData {
1649 MultiBufferRow {
1650 row: MultiBufferRow,
1651 line_offset_from_top: u32,
1652 },
1653 MultiBufferPoint {
1654 excerpt_id: ExcerptId,
1655 position: Point,
1656 anchor: text::Anchor,
1657 line_offset_from_top: u32,
1658 },
1659}
1660
1661pub enum MultibufferSelectionMode {
1662 First,
1663 All,
1664}
1665
1666#[derive(Clone, Copy, Debug, Default)]
1667pub struct RewrapOptions {
1668 pub override_language_settings: bool,
1669 pub preserve_existing_whitespace: bool,
1670}
1671
1672impl Editor {
1673 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1677 }
1678
1679 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1680 let buffer = cx.new(|cx| Buffer::local("", cx));
1681 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1682 Self::new(EditorMode::full(), buffer, None, window, cx)
1683 }
1684
1685 pub fn auto_height(
1686 min_lines: usize,
1687 max_lines: usize,
1688 window: &mut Window,
1689 cx: &mut Context<Self>,
1690 ) -> Self {
1691 let buffer = cx.new(|cx| Buffer::local("", cx));
1692 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1693 Self::new(
1694 EditorMode::AutoHeight {
1695 min_lines,
1696 max_lines: Some(max_lines),
1697 },
1698 buffer,
1699 None,
1700 window,
1701 cx,
1702 )
1703 }
1704
1705 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1706 /// The editor grows as tall as needed to fit its content.
1707 pub fn auto_height_unbounded(
1708 min_lines: usize,
1709 window: &mut Window,
1710 cx: &mut Context<Self>,
1711 ) -> Self {
1712 let buffer = cx.new(|cx| Buffer::local("", cx));
1713 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1714 Self::new(
1715 EditorMode::AutoHeight {
1716 min_lines,
1717 max_lines: None,
1718 },
1719 buffer,
1720 None,
1721 window,
1722 cx,
1723 )
1724 }
1725
1726 pub fn for_buffer(
1727 buffer: Entity<Buffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn for_multibuffer(
1737 buffer: Entity<MultiBuffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 Self::new(EditorMode::full(), buffer, project, window, cx)
1743 }
1744
1745 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1746 let mut clone = Self::new(
1747 self.mode.clone(),
1748 self.buffer.clone(),
1749 self.project.clone(),
1750 window,
1751 cx,
1752 );
1753 self.display_map.update(cx, |display_map, cx| {
1754 let snapshot = display_map.snapshot(cx);
1755 clone.display_map.update(cx, |display_map, cx| {
1756 display_map.set_state(&snapshot, cx);
1757 });
1758 });
1759 clone.folds_did_change(cx);
1760 clone.selections.clone_state(&self.selections);
1761 clone.scroll_manager.clone_state(&self.scroll_manager);
1762 clone.searchable = self.searchable;
1763 clone.read_only = self.read_only;
1764 clone
1765 }
1766
1767 pub fn new(
1768 mode: EditorMode,
1769 buffer: Entity<MultiBuffer>,
1770 project: Option<Entity<Project>>,
1771 window: &mut Window,
1772 cx: &mut Context<Self>,
1773 ) -> Self {
1774 Editor::new_internal(mode, buffer, project, None, window, cx)
1775 }
1776
1777 fn new_internal(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 display_map: Option<Entity<DisplayMap>>,
1782 window: &mut Window,
1783 cx: &mut Context<Self>,
1784 ) -> Self {
1785 debug_assert!(
1786 display_map.is_none() || mode.is_minimap(),
1787 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1788 );
1789
1790 let full_mode = mode.is_full();
1791 let is_minimap = mode.is_minimap();
1792 let diagnostics_max_severity = if full_mode {
1793 EditorSettings::get_global(cx)
1794 .diagnostics_max_severity
1795 .unwrap_or(DiagnosticSeverity::Hint)
1796 } else {
1797 DiagnosticSeverity::Off
1798 };
1799 let style = window.text_style();
1800 let font_size = style.font_size.to_pixels(window.rem_size());
1801 let editor = cx.entity().downgrade();
1802 let fold_placeholder = FoldPlaceholder {
1803 constrain_width: true,
1804 render: Arc::new(move |fold_id, fold_range, cx| {
1805 let editor = editor.clone();
1806 div()
1807 .id(fold_id)
1808 .bg(cx.theme().colors().ghost_element_background)
1809 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1810 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1811 .rounded_xs()
1812 .size_full()
1813 .cursor_pointer()
1814 .child("⋯")
1815 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1816 .on_click(move |_, _window, cx| {
1817 editor
1818 .update(cx, |editor, cx| {
1819 editor.unfold_ranges(
1820 &[fold_range.start..fold_range.end],
1821 true,
1822 false,
1823 cx,
1824 );
1825 cx.stop_propagation();
1826 })
1827 .ok();
1828 })
1829 .into_any()
1830 }),
1831 merge_adjacent: true,
1832 ..FoldPlaceholder::default()
1833 };
1834 let display_map = display_map.unwrap_or_else(|| {
1835 cx.new(|cx| {
1836 DisplayMap::new(
1837 buffer.clone(),
1838 style.font(),
1839 font_size,
1840 None,
1841 FILE_HEADER_HEIGHT,
1842 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1843 fold_placeholder,
1844 diagnostics_max_severity,
1845 cx,
1846 )
1847 })
1848 });
1849
1850 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1851
1852 let blink_manager = cx.new(|cx| {
1853 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1854 if is_minimap {
1855 blink_manager.disable(cx);
1856 }
1857 blink_manager
1858 });
1859
1860 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1861 .then(|| language_settings::SoftWrap::None);
1862
1863 let mut project_subscriptions = Vec::new();
1864 if full_mode && let Some(project) = project.as_ref() {
1865 project_subscriptions.push(cx.subscribe_in(
1866 project,
1867 window,
1868 |editor, _, event, window, cx| match event {
1869 project::Event::RefreshCodeLens => {
1870 // we always query lens with actions, without storing them, always refreshing them
1871 }
1872 project::Event::RefreshInlayHints => {
1873 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1874 }
1875 project::Event::LanguageServerAdded(..)
1876 | project::Event::LanguageServerRemoved(..) => {
1877 if editor.tasks_update_task.is_none() {
1878 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1879 }
1880 }
1881 project::Event::SnippetEdit(id, snippet_edits) => {
1882 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1883 let focus_handle = editor.focus_handle(cx);
1884 if focus_handle.is_focused(window) {
1885 let snapshot = buffer.read(cx).snapshot();
1886 for (range, snippet) in snippet_edits {
1887 let editor_range =
1888 language::range_from_lsp(*range).to_offset(&snapshot);
1889 editor
1890 .insert_snippet(
1891 &[editor_range],
1892 snippet.clone(),
1893 window,
1894 cx,
1895 )
1896 .ok();
1897 }
1898 }
1899 }
1900 }
1901 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1902 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1903 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1904 }
1905 }
1906 _ => {}
1907 },
1908 ));
1909 if let Some(task_inventory) = project
1910 .read(cx)
1911 .task_store()
1912 .read(cx)
1913 .task_inventory()
1914 .cloned()
1915 {
1916 project_subscriptions.push(cx.observe_in(
1917 &task_inventory,
1918 window,
1919 |editor, _, window, cx| {
1920 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1921 },
1922 ));
1923 };
1924
1925 project_subscriptions.push(cx.subscribe_in(
1926 &project.read(cx).breakpoint_store(),
1927 window,
1928 |editor, _, event, window, cx| match event {
1929 BreakpointStoreEvent::ClearDebugLines => {
1930 editor.clear_row_highlights::<ActiveDebugLine>();
1931 editor.refresh_inline_values(cx);
1932 }
1933 BreakpointStoreEvent::SetDebugLine => {
1934 if editor.go_to_active_debug_line(window, cx) {
1935 cx.stop_propagation();
1936 }
1937
1938 editor.refresh_inline_values(cx);
1939 }
1940 _ => {}
1941 },
1942 ));
1943 let git_store = project.read(cx).git_store().clone();
1944 let project = project.clone();
1945 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1946 if let GitStoreEvent::RepositoryUpdated(
1947 _,
1948 RepositoryEvent::Updated {
1949 new_instance: true, ..
1950 },
1951 _,
1952 ) = event
1953 {
1954 this.load_diff_task = Some(
1955 update_uncommitted_diff_for_buffer(
1956 cx.entity(),
1957 &project,
1958 this.buffer.read(cx).all_buffers(),
1959 this.buffer.clone(),
1960 cx,
1961 )
1962 .shared(),
1963 );
1964 }
1965 }));
1966 }
1967
1968 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1969
1970 let inlay_hint_settings =
1971 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1972 let focus_handle = cx.focus_handle();
1973 if !is_minimap {
1974 cx.on_focus(&focus_handle, window, Self::handle_focus)
1975 .detach();
1976 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1977 .detach();
1978 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1979 .detach();
1980 cx.on_blur(&focus_handle, window, Self::handle_blur)
1981 .detach();
1982 cx.observe_pending_input(window, Self::observe_pending_input)
1983 .detach();
1984 }
1985
1986 let show_indent_guides = if matches!(
1987 mode,
1988 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1989 ) {
1990 Some(false)
1991 } else {
1992 None
1993 };
1994
1995 let breakpoint_store = match (&mode, project.as_ref()) {
1996 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1997 _ => None,
1998 };
1999
2000 let mut code_action_providers = Vec::new();
2001 let mut load_uncommitted_diff = None;
2002 if let Some(project) = project.clone() {
2003 load_uncommitted_diff = Some(
2004 update_uncommitted_diff_for_buffer(
2005 cx.entity(),
2006 &project,
2007 buffer.read(cx).all_buffers(),
2008 buffer.clone(),
2009 cx,
2010 )
2011 .shared(),
2012 );
2013 code_action_providers.push(Rc::new(project) as Rc<_>);
2014 }
2015
2016 let mut editor = Self {
2017 focus_handle,
2018 show_cursor_when_unfocused: false,
2019 last_focused_descendant: None,
2020 buffer: buffer.clone(),
2021 display_map: display_map.clone(),
2022 selections,
2023 scroll_manager: ScrollManager::new(cx),
2024 columnar_selection_state: None,
2025 add_selections_state: None,
2026 select_next_state: None,
2027 select_prev_state: None,
2028 selection_history: SelectionHistory::default(),
2029 defer_selection_effects: false,
2030 deferred_selection_effects_state: None,
2031 autoclose_regions: Vec::new(),
2032 snippet_stack: InvalidationStack::default(),
2033 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2034 ime_transaction: None,
2035 active_diagnostics: ActiveDiagnostic::None,
2036 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2037 inline_diagnostics_update: Task::ready(()),
2038 inline_diagnostics: Vec::new(),
2039 soft_wrap_mode_override,
2040 diagnostics_max_severity,
2041 hard_wrap: None,
2042 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2043 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2044 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2045 project,
2046 blink_manager: blink_manager.clone(),
2047 show_local_selections: true,
2048 show_scrollbars: ScrollbarAxes {
2049 horizontal: full_mode,
2050 vertical: full_mode,
2051 },
2052 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2053 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2054 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2055 show_gutter: full_mode,
2056 show_line_numbers: (!full_mode).then_some(false),
2057 use_relative_line_numbers: None,
2058 disable_expand_excerpt_buttons: !full_mode,
2059 show_git_diff_gutter: None,
2060 show_code_actions: None,
2061 show_runnables: None,
2062 show_breakpoints: None,
2063 show_wrap_guides: None,
2064 show_indent_guides,
2065 placeholder_text: None,
2066 highlight_order: 0,
2067 highlighted_rows: HashMap::default(),
2068 background_highlights: TreeMap::default(),
2069 gutter_highlights: TreeMap::default(),
2070 scrollbar_marker_state: ScrollbarMarkerState::default(),
2071 active_indent_guides_state: ActiveIndentGuidesState::default(),
2072 nav_history: None,
2073 context_menu: RefCell::new(None),
2074 context_menu_options: None,
2075 mouse_context_menu: None,
2076 completion_tasks: Vec::new(),
2077 inline_blame_popover: None,
2078 inline_blame_popover_show_task: None,
2079 signature_help_state: SignatureHelpState::default(),
2080 auto_signature_help: None,
2081 find_all_references_task_sources: Vec::new(),
2082 next_completion_id: 0,
2083 next_inlay_id: 0,
2084 code_action_providers,
2085 available_code_actions: None,
2086 code_actions_task: None,
2087 quick_selection_highlight_task: None,
2088 debounced_selection_highlight_task: None,
2089 document_highlights_task: None,
2090 linked_editing_range_task: None,
2091 pending_rename: None,
2092 searchable: !is_minimap,
2093 cursor_shape: EditorSettings::get_global(cx)
2094 .cursor_shape
2095 .unwrap_or_default(),
2096 current_line_highlight: None,
2097 autoindent_mode: Some(AutoindentMode::EachLine),
2098 collapse_matches: false,
2099 workspace: None,
2100 input_enabled: !is_minimap,
2101 use_modal_editing: full_mode,
2102 read_only: is_minimap,
2103 use_autoclose: true,
2104 use_auto_surround: true,
2105 auto_replace_emoji_shortcode: false,
2106 jsx_tag_auto_close_enabled_in_any_buffer: false,
2107 leader_id: None,
2108 remote_id: None,
2109 hover_state: HoverState::default(),
2110 pending_mouse_down: None,
2111 hovered_link_state: None,
2112 edit_prediction_provider: None,
2113 active_edit_prediction: None,
2114 stale_edit_prediction_in_menu: None,
2115 edit_prediction_preview: EditPredictionPreview::Inactive {
2116 released_too_fast: false,
2117 },
2118 inline_diagnostics_enabled: full_mode,
2119 diagnostics_enabled: full_mode,
2120 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2121 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2122 gutter_hovered: false,
2123 pixel_position_of_newest_cursor: None,
2124 last_bounds: None,
2125 last_position_map: None,
2126 expect_bounds_change: None,
2127 gutter_dimensions: GutterDimensions::default(),
2128 style: None,
2129 show_cursor_names: false,
2130 hovered_cursors: HashMap::default(),
2131 next_editor_action_id: EditorActionId::default(),
2132 editor_actions: Rc::default(),
2133 edit_predictions_hidden_for_vim_mode: false,
2134 show_edit_predictions_override: None,
2135 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2136 edit_prediction_settings: EditPredictionSettings::Disabled,
2137 edit_prediction_indent_conflict: false,
2138 edit_prediction_requires_modifier_in_indent_conflict: true,
2139 custom_context_menu: None,
2140 show_git_blame_gutter: false,
2141 show_git_blame_inline: false,
2142 show_selection_menu: None,
2143 show_git_blame_inline_delay_task: None,
2144 git_blame_inline_enabled: full_mode
2145 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2146 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2147 serialize_dirty_buffers: !is_minimap
2148 && ProjectSettings::get_global(cx)
2149 .session
2150 .restore_unsaved_buffers,
2151 blame: None,
2152 blame_subscription: None,
2153 tasks: BTreeMap::default(),
2154
2155 breakpoint_store,
2156 gutter_breakpoint_indicator: (None, None),
2157 hovered_diff_hunk_row: None,
2158 _subscriptions: (!is_minimap)
2159 .then(|| {
2160 vec![
2161 cx.observe(&buffer, Self::on_buffer_changed),
2162 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2163 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2164 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2165 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2166 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2167 cx.observe_window_activation(window, |editor, window, cx| {
2168 let active = window.is_window_active();
2169 editor.blink_manager.update(cx, |blink_manager, cx| {
2170 if active {
2171 blink_manager.enable(cx);
2172 } else {
2173 blink_manager.disable(cx);
2174 }
2175 });
2176 if active {
2177 editor.show_mouse_cursor(cx);
2178 }
2179 }),
2180 ]
2181 })
2182 .unwrap_or_default(),
2183 tasks_update_task: None,
2184 pull_diagnostics_task: Task::ready(()),
2185 colors: None,
2186 next_color_inlay_id: 0,
2187 linked_edit_ranges: Default::default(),
2188 in_project_search: false,
2189 previous_search_ranges: None,
2190 breadcrumb_header: None,
2191 focused_block: None,
2192 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2193 addons: HashMap::default(),
2194 registered_buffers: HashMap::default(),
2195 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2196 selection_mark_mode: false,
2197 toggle_fold_multiple_buffers: Task::ready(()),
2198 serialize_selections: Task::ready(()),
2199 serialize_folds: Task::ready(()),
2200 text_style_refinement: None,
2201 load_diff_task: load_uncommitted_diff,
2202 temporary_diff_override: false,
2203 mouse_cursor_hidden: false,
2204 minimap: None,
2205 hide_mouse_mode: EditorSettings::get_global(cx)
2206 .hide_mouse
2207 .unwrap_or_default(),
2208 change_list: ChangeList::new(),
2209 mode,
2210 selection_drag_state: SelectionDragState::None,
2211 folding_newlines: Task::ready(()),
2212 };
2213
2214 if is_minimap {
2215 return editor;
2216 }
2217
2218 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2219 editor
2220 ._subscriptions
2221 .push(cx.observe(breakpoints, |_, _, cx| {
2222 cx.notify();
2223 }));
2224 }
2225 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2226 editor._subscriptions.extend(project_subscriptions);
2227
2228 editor._subscriptions.push(cx.subscribe_in(
2229 &cx.entity(),
2230 window,
2231 |editor, _, e: &EditorEvent, window, cx| match e {
2232 EditorEvent::ScrollPositionChanged { local, .. } => {
2233 if *local {
2234 let new_anchor = editor.scroll_manager.anchor();
2235 let snapshot = editor.snapshot(window, cx);
2236 editor.update_restoration_data(cx, move |data| {
2237 data.scroll_position = (
2238 new_anchor.top_row(&snapshot.buffer_snapshot),
2239 new_anchor.offset,
2240 );
2241 });
2242 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2243 editor.inline_blame_popover.take();
2244 }
2245 }
2246 EditorEvent::Edited { .. } => {
2247 if !vim_enabled(cx) {
2248 let (map, selections) = editor.selections.all_adjusted_display(cx);
2249 let pop_state = editor
2250 .change_list
2251 .last()
2252 .map(|previous| {
2253 previous.len() == selections.len()
2254 && previous.iter().enumerate().all(|(ix, p)| {
2255 p.to_display_point(&map).row()
2256 == selections[ix].head().row()
2257 })
2258 })
2259 .unwrap_or(false);
2260 let new_positions = selections
2261 .into_iter()
2262 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2263 .collect();
2264 editor
2265 .change_list
2266 .push_to_change_list(pop_state, new_positions);
2267 }
2268 }
2269 _ => (),
2270 },
2271 ));
2272
2273 if let Some(dap_store) = editor
2274 .project
2275 .as_ref()
2276 .map(|project| project.read(cx).dap_store())
2277 {
2278 let weak_editor = cx.weak_entity();
2279
2280 editor
2281 ._subscriptions
2282 .push(
2283 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2284 let session_entity = cx.entity();
2285 weak_editor
2286 .update(cx, |editor, cx| {
2287 editor._subscriptions.push(
2288 cx.subscribe(&session_entity, Self::on_debug_session_event),
2289 );
2290 })
2291 .ok();
2292 }),
2293 );
2294
2295 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2296 editor
2297 ._subscriptions
2298 .push(cx.subscribe(&session, Self::on_debug_session_event));
2299 }
2300 }
2301
2302 // skip adding the initial selection to selection history
2303 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2304 editor.end_selection(window, cx);
2305 editor.selection_history.mode = SelectionHistoryMode::Normal;
2306
2307 editor.scroll_manager.show_scrollbars(window, cx);
2308 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2309
2310 if full_mode {
2311 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2312 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2313
2314 if editor.git_blame_inline_enabled {
2315 editor.start_git_blame_inline(false, window, cx);
2316 }
2317
2318 editor.go_to_active_debug_line(window, cx);
2319
2320 if let Some(buffer) = buffer.read(cx).as_singleton()
2321 && let Some(project) = editor.project()
2322 {
2323 let handle = project.update(cx, |project, cx| {
2324 project.register_buffer_with_language_servers(&buffer, cx)
2325 });
2326 editor
2327 .registered_buffers
2328 .insert(buffer.read(cx).remote_id(), handle);
2329 }
2330
2331 editor.minimap =
2332 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2333 editor.colors = Some(LspColorData::new(cx));
2334 editor.update_lsp_data(false, None, window, cx);
2335 }
2336
2337 if editor.mode.is_full() {
2338 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2339 }
2340
2341 editor
2342 }
2343
2344 pub fn deploy_mouse_context_menu(
2345 &mut self,
2346 position: gpui::Point<Pixels>,
2347 context_menu: Entity<ContextMenu>,
2348 window: &mut Window,
2349 cx: &mut Context<Self>,
2350 ) {
2351 self.mouse_context_menu = Some(MouseContextMenu::new(
2352 self,
2353 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2354 context_menu,
2355 window,
2356 cx,
2357 ));
2358 }
2359
2360 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2361 self.mouse_context_menu
2362 .as_ref()
2363 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2364 }
2365
2366 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2367 if self
2368 .selections
2369 .pending
2370 .as_ref()
2371 .is_some_and(|pending_selection| {
2372 let snapshot = self.buffer().read(cx).snapshot(cx);
2373 pending_selection
2374 .selection
2375 .range()
2376 .includes(range, &snapshot)
2377 })
2378 {
2379 return true;
2380 }
2381
2382 self.selections
2383 .disjoint_in_range::<usize>(range.clone(), cx)
2384 .into_iter()
2385 .any(|selection| {
2386 // This is needed to cover a corner case, if we just check for an existing
2387 // selection in the fold range, having a cursor at the start of the fold
2388 // marks it as selected. Non-empty selections don't cause this.
2389 let length = selection.end - selection.start;
2390 length > 0
2391 })
2392 }
2393
2394 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2395 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2396 }
2397
2398 fn key_context_internal(
2399 &self,
2400 has_active_edit_prediction: bool,
2401 window: &Window,
2402 cx: &App,
2403 ) -> KeyContext {
2404 let mut key_context = KeyContext::new_with_defaults();
2405 key_context.add("Editor");
2406 let mode = match self.mode {
2407 EditorMode::SingleLine { .. } => "single_line",
2408 EditorMode::AutoHeight { .. } => "auto_height",
2409 EditorMode::Minimap { .. } => "minimap",
2410 EditorMode::Full { .. } => "full",
2411 };
2412
2413 if EditorSettings::jupyter_enabled(cx) {
2414 key_context.add("jupyter");
2415 }
2416
2417 key_context.set("mode", mode);
2418 if self.pending_rename.is_some() {
2419 key_context.add("renaming");
2420 }
2421
2422 match self.context_menu.borrow().as_ref() {
2423 Some(CodeContextMenu::Completions(menu)) => {
2424 if menu.visible() {
2425 key_context.add("menu");
2426 key_context.add("showing_completions");
2427 }
2428 }
2429 Some(CodeContextMenu::CodeActions(menu)) => {
2430 if menu.visible() {
2431 key_context.add("menu");
2432 key_context.add("showing_code_actions")
2433 }
2434 }
2435 None => {}
2436 }
2437
2438 if self.signature_help_state.has_multiple_signatures() {
2439 key_context.add("showing_signature_help");
2440 }
2441
2442 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2443 if !self.focus_handle(cx).contains_focused(window, cx)
2444 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2445 {
2446 for addon in self.addons.values() {
2447 addon.extend_key_context(&mut key_context, cx)
2448 }
2449 }
2450
2451 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2452 if let Some(extension) = singleton_buffer
2453 .read(cx)
2454 .file()
2455 .and_then(|file| file.path().extension()?.to_str())
2456 {
2457 key_context.set("extension", extension.to_string());
2458 }
2459 } else {
2460 key_context.add("multibuffer");
2461 }
2462
2463 if has_active_edit_prediction {
2464 if self.edit_prediction_in_conflict() {
2465 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2466 } else {
2467 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2468 key_context.add("copilot_suggestion");
2469 }
2470 }
2471
2472 if self.selection_mark_mode {
2473 key_context.add("selection_mode");
2474 }
2475
2476 key_context
2477 }
2478
2479 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2480 if self.mouse_cursor_hidden {
2481 self.mouse_cursor_hidden = false;
2482 cx.notify();
2483 }
2484 }
2485
2486 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2487 let hide_mouse_cursor = match origin {
2488 HideMouseCursorOrigin::TypingAction => {
2489 matches!(
2490 self.hide_mouse_mode,
2491 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2492 )
2493 }
2494 HideMouseCursorOrigin::MovementAction => {
2495 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2496 }
2497 };
2498 if self.mouse_cursor_hidden != hide_mouse_cursor {
2499 self.mouse_cursor_hidden = hide_mouse_cursor;
2500 cx.notify();
2501 }
2502 }
2503
2504 pub fn edit_prediction_in_conflict(&self) -> bool {
2505 if !self.show_edit_predictions_in_menu() {
2506 return false;
2507 }
2508
2509 let showing_completions = self
2510 .context_menu
2511 .borrow()
2512 .as_ref()
2513 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2514
2515 showing_completions
2516 || self.edit_prediction_requires_modifier()
2517 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2518 // bindings to insert tab characters.
2519 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2520 }
2521
2522 pub fn accept_edit_prediction_keybind(
2523 &self,
2524 accept_partial: bool,
2525 window: &Window,
2526 cx: &App,
2527 ) -> AcceptEditPredictionBinding {
2528 let key_context = self.key_context_internal(true, window, cx);
2529 let in_conflict = self.edit_prediction_in_conflict();
2530
2531 let bindings = if accept_partial {
2532 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2533 } else {
2534 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2535 };
2536
2537 // TODO: if the binding contains multiple keystrokes, display all of them, not
2538 // just the first one.
2539 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2540 !in_conflict
2541 || binding
2542 .keystrokes()
2543 .first()
2544 .is_some_and(|keystroke| keystroke.modifiers.modified())
2545 }))
2546 }
2547
2548 pub fn new_file(
2549 workspace: &mut Workspace,
2550 _: &workspace::NewFile,
2551 window: &mut Window,
2552 cx: &mut Context<Workspace>,
2553 ) {
2554 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2555 "Failed to create buffer",
2556 window,
2557 cx,
2558 |e, _, _| match e.error_code() {
2559 ErrorCode::RemoteUpgradeRequired => Some(format!(
2560 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2561 e.error_tag("required").unwrap_or("the latest version")
2562 )),
2563 _ => None,
2564 },
2565 );
2566 }
2567
2568 pub fn new_in_workspace(
2569 workspace: &mut Workspace,
2570 window: &mut Window,
2571 cx: &mut Context<Workspace>,
2572 ) -> Task<Result<Entity<Editor>>> {
2573 let project = workspace.project().clone();
2574 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2575
2576 cx.spawn_in(window, async move |workspace, cx| {
2577 let buffer = create.await?;
2578 workspace.update_in(cx, |workspace, window, cx| {
2579 let editor =
2580 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2581 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2582 editor
2583 })
2584 })
2585 }
2586
2587 fn new_file_vertical(
2588 workspace: &mut Workspace,
2589 _: &workspace::NewFileSplitVertical,
2590 window: &mut Window,
2591 cx: &mut Context<Workspace>,
2592 ) {
2593 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2594 }
2595
2596 fn new_file_horizontal(
2597 workspace: &mut Workspace,
2598 _: &workspace::NewFileSplitHorizontal,
2599 window: &mut Window,
2600 cx: &mut Context<Workspace>,
2601 ) {
2602 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2603 }
2604
2605 fn new_file_in_direction(
2606 workspace: &mut Workspace,
2607 direction: SplitDirection,
2608 window: &mut Window,
2609 cx: &mut Context<Workspace>,
2610 ) {
2611 let project = workspace.project().clone();
2612 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2613
2614 cx.spawn_in(window, async move |workspace, cx| {
2615 let buffer = create.await?;
2616 workspace.update_in(cx, move |workspace, window, cx| {
2617 workspace.split_item(
2618 direction,
2619 Box::new(
2620 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2621 ),
2622 window,
2623 cx,
2624 )
2625 })?;
2626 anyhow::Ok(())
2627 })
2628 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2629 match e.error_code() {
2630 ErrorCode::RemoteUpgradeRequired => Some(format!(
2631 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2632 e.error_tag("required").unwrap_or("the latest version")
2633 )),
2634 _ => None,
2635 }
2636 });
2637 }
2638
2639 pub fn leader_id(&self) -> Option<CollaboratorId> {
2640 self.leader_id
2641 }
2642
2643 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2644 &self.buffer
2645 }
2646
2647 pub fn project(&self) -> Option<&Entity<Project>> {
2648 self.project.as_ref()
2649 }
2650
2651 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2652 self.workspace.as_ref()?.0.upgrade()
2653 }
2654
2655 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2656 self.buffer().read(cx).title(cx)
2657 }
2658
2659 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2660 let git_blame_gutter_max_author_length = self
2661 .render_git_blame_gutter(cx)
2662 .then(|| {
2663 if let Some(blame) = self.blame.as_ref() {
2664 let max_author_length =
2665 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2666 Some(max_author_length)
2667 } else {
2668 None
2669 }
2670 })
2671 .flatten();
2672
2673 EditorSnapshot {
2674 mode: self.mode.clone(),
2675 show_gutter: self.show_gutter,
2676 show_line_numbers: self.show_line_numbers,
2677 show_git_diff_gutter: self.show_git_diff_gutter,
2678 show_code_actions: self.show_code_actions,
2679 show_runnables: self.show_runnables,
2680 show_breakpoints: self.show_breakpoints,
2681 git_blame_gutter_max_author_length,
2682 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2683 scroll_anchor: self.scroll_manager.anchor(),
2684 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2685 placeholder_text: self.placeholder_text.clone(),
2686 is_focused: self.focus_handle.is_focused(window),
2687 current_line_highlight: self
2688 .current_line_highlight
2689 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2690 gutter_hovered: self.gutter_hovered,
2691 }
2692 }
2693
2694 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2695 self.buffer.read(cx).language_at(point, cx)
2696 }
2697
2698 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2699 self.buffer.read(cx).read(cx).file_at(point).cloned()
2700 }
2701
2702 pub fn active_excerpt(
2703 &self,
2704 cx: &App,
2705 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2706 self.buffer
2707 .read(cx)
2708 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2709 }
2710
2711 pub fn mode(&self) -> &EditorMode {
2712 &self.mode
2713 }
2714
2715 pub fn set_mode(&mut self, mode: EditorMode) {
2716 self.mode = mode;
2717 }
2718
2719 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2720 self.collaboration_hub.as_deref()
2721 }
2722
2723 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2724 self.collaboration_hub = Some(hub);
2725 }
2726
2727 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2728 self.in_project_search = in_project_search;
2729 }
2730
2731 pub fn set_custom_context_menu(
2732 &mut self,
2733 f: impl 'static
2734 + Fn(
2735 &mut Self,
2736 DisplayPoint,
2737 &mut Window,
2738 &mut Context<Self>,
2739 ) -> Option<Entity<ui::ContextMenu>>,
2740 ) {
2741 self.custom_context_menu = Some(Box::new(f))
2742 }
2743
2744 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2745 self.completion_provider = provider;
2746 }
2747
2748 #[cfg(any(test, feature = "test-support"))]
2749 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2750 self.completion_provider.clone()
2751 }
2752
2753 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2754 self.semantics_provider.clone()
2755 }
2756
2757 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2758 self.semantics_provider = provider;
2759 }
2760
2761 pub fn set_edit_prediction_provider<T>(
2762 &mut self,
2763 provider: Option<Entity<T>>,
2764 window: &mut Window,
2765 cx: &mut Context<Self>,
2766 ) where
2767 T: EditPredictionProvider,
2768 {
2769 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2770 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2771 if this.focus_handle.is_focused(window) {
2772 this.update_visible_edit_prediction(window, cx);
2773 }
2774 }),
2775 provider: Arc::new(provider),
2776 });
2777 self.update_edit_prediction_settings(cx);
2778 self.refresh_edit_prediction(false, false, window, cx);
2779 }
2780
2781 pub fn placeholder_text(&self) -> Option<&str> {
2782 self.placeholder_text.as_deref()
2783 }
2784
2785 pub fn set_placeholder_text(
2786 &mut self,
2787 placeholder_text: impl Into<Arc<str>>,
2788 cx: &mut Context<Self>,
2789 ) {
2790 let placeholder_text = Some(placeholder_text.into());
2791 if self.placeholder_text != placeholder_text {
2792 self.placeholder_text = placeholder_text;
2793 cx.notify();
2794 }
2795 }
2796
2797 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2798 self.cursor_shape = cursor_shape;
2799
2800 // Disrupt blink for immediate user feedback that the cursor shape has changed
2801 self.blink_manager.update(cx, BlinkManager::show_cursor);
2802
2803 cx.notify();
2804 }
2805
2806 pub fn set_current_line_highlight(
2807 &mut self,
2808 current_line_highlight: Option<CurrentLineHighlight>,
2809 ) {
2810 self.current_line_highlight = current_line_highlight;
2811 }
2812
2813 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2814 self.collapse_matches = collapse_matches;
2815 }
2816
2817 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2818 let buffers = self.buffer.read(cx).all_buffers();
2819 let Some(project) = self.project.as_ref() else {
2820 return;
2821 };
2822 project.update(cx, |project, cx| {
2823 for buffer in buffers {
2824 self.registered_buffers
2825 .entry(buffer.read(cx).remote_id())
2826 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2827 }
2828 })
2829 }
2830
2831 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2832 if self.collapse_matches {
2833 return range.start..range.start;
2834 }
2835 range.clone()
2836 }
2837
2838 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2839 if self.display_map.read(cx).clip_at_line_ends != clip {
2840 self.display_map
2841 .update(cx, |map, _| map.clip_at_line_ends = clip);
2842 }
2843 }
2844
2845 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2846 self.input_enabled = input_enabled;
2847 }
2848
2849 pub fn set_edit_predictions_hidden_for_vim_mode(
2850 &mut self,
2851 hidden: bool,
2852 window: &mut Window,
2853 cx: &mut Context<Self>,
2854 ) {
2855 if hidden != self.edit_predictions_hidden_for_vim_mode {
2856 self.edit_predictions_hidden_for_vim_mode = hidden;
2857 if hidden {
2858 self.update_visible_edit_prediction(window, cx);
2859 } else {
2860 self.refresh_edit_prediction(true, false, window, cx);
2861 }
2862 }
2863 }
2864
2865 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2866 self.menu_edit_predictions_policy = value;
2867 }
2868
2869 pub fn set_autoindent(&mut self, autoindent: bool) {
2870 if autoindent {
2871 self.autoindent_mode = Some(AutoindentMode::EachLine);
2872 } else {
2873 self.autoindent_mode = None;
2874 }
2875 }
2876
2877 pub fn read_only(&self, cx: &App) -> bool {
2878 self.read_only || self.buffer.read(cx).read_only()
2879 }
2880
2881 pub fn set_read_only(&mut self, read_only: bool) {
2882 self.read_only = read_only;
2883 }
2884
2885 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2886 self.use_autoclose = autoclose;
2887 }
2888
2889 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2890 self.use_auto_surround = auto_surround;
2891 }
2892
2893 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2894 self.auto_replace_emoji_shortcode = auto_replace;
2895 }
2896
2897 pub fn toggle_edit_predictions(
2898 &mut self,
2899 _: &ToggleEditPrediction,
2900 window: &mut Window,
2901 cx: &mut Context<Self>,
2902 ) {
2903 if self.show_edit_predictions_override.is_some() {
2904 self.set_show_edit_predictions(None, window, cx);
2905 } else {
2906 let show_edit_predictions = !self.edit_predictions_enabled();
2907 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2908 }
2909 }
2910
2911 pub fn set_show_edit_predictions(
2912 &mut self,
2913 show_edit_predictions: Option<bool>,
2914 window: &mut Window,
2915 cx: &mut Context<Self>,
2916 ) {
2917 self.show_edit_predictions_override = show_edit_predictions;
2918 self.update_edit_prediction_settings(cx);
2919
2920 if let Some(false) = show_edit_predictions {
2921 self.discard_edit_prediction(false, cx);
2922 } else {
2923 self.refresh_edit_prediction(false, true, window, cx);
2924 }
2925 }
2926
2927 fn edit_predictions_disabled_in_scope(
2928 &self,
2929 buffer: &Entity<Buffer>,
2930 buffer_position: language::Anchor,
2931 cx: &App,
2932 ) -> bool {
2933 let snapshot = buffer.read(cx).snapshot();
2934 let settings = snapshot.settings_at(buffer_position, cx);
2935
2936 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2937 return false;
2938 };
2939
2940 scope.override_name().is_some_and(|scope_name| {
2941 settings
2942 .edit_predictions_disabled_in
2943 .iter()
2944 .any(|s| s == scope_name)
2945 })
2946 }
2947
2948 pub fn set_use_modal_editing(&mut self, to: bool) {
2949 self.use_modal_editing = to;
2950 }
2951
2952 pub fn use_modal_editing(&self) -> bool {
2953 self.use_modal_editing
2954 }
2955
2956 fn selections_did_change(
2957 &mut self,
2958 local: bool,
2959 old_cursor_position: &Anchor,
2960 effects: SelectionEffects,
2961 window: &mut Window,
2962 cx: &mut Context<Self>,
2963 ) {
2964 window.invalidate_character_coordinates();
2965
2966 // Copy selections to primary selection buffer
2967 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2968 if local {
2969 let selections = self.selections.all::<usize>(cx);
2970 let buffer_handle = self.buffer.read(cx).read(cx);
2971
2972 let mut text = String::new();
2973 for (index, selection) in selections.iter().enumerate() {
2974 let text_for_selection = buffer_handle
2975 .text_for_range(selection.start..selection.end)
2976 .collect::<String>();
2977
2978 text.push_str(&text_for_selection);
2979 if index != selections.len() - 1 {
2980 text.push('\n');
2981 }
2982 }
2983
2984 if !text.is_empty() {
2985 cx.write_to_primary(ClipboardItem::new_string(text));
2986 }
2987 }
2988
2989 let selection_anchors = self.selections.disjoint_anchors();
2990
2991 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2992 self.buffer.update(cx, |buffer, cx| {
2993 buffer.set_active_selections(
2994 &selection_anchors,
2995 self.selections.line_mode,
2996 self.cursor_shape,
2997 cx,
2998 )
2999 });
3000 }
3001 let display_map = self
3002 .display_map
3003 .update(cx, |display_map, cx| display_map.snapshot(cx));
3004 let buffer = &display_map.buffer_snapshot;
3005 if self.selections.count() == 1 {
3006 self.add_selections_state = None;
3007 }
3008 self.select_next_state = None;
3009 self.select_prev_state = None;
3010 self.select_syntax_node_history.try_clear();
3011 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3012 self.snippet_stack.invalidate(&selection_anchors, buffer);
3013 self.take_rename(false, window, cx);
3014
3015 let newest_selection = self.selections.newest_anchor();
3016 let new_cursor_position = newest_selection.head();
3017 let selection_start = newest_selection.start;
3018
3019 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3020 self.push_to_nav_history(
3021 *old_cursor_position,
3022 Some(new_cursor_position.to_point(buffer)),
3023 false,
3024 effects.nav_history == Some(true),
3025 cx,
3026 );
3027 }
3028
3029 if local {
3030 if let Some(buffer_id) = new_cursor_position.buffer_id
3031 && !self.registered_buffers.contains_key(&buffer_id)
3032 && let Some(project) = self.project.as_ref()
3033 {
3034 project.update(cx, |project, cx| {
3035 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3036 return;
3037 };
3038 self.registered_buffers.insert(
3039 buffer_id,
3040 project.register_buffer_with_language_servers(&buffer, cx),
3041 );
3042 })
3043 }
3044
3045 let mut context_menu = self.context_menu.borrow_mut();
3046 let completion_menu = match context_menu.as_ref() {
3047 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3048 Some(CodeContextMenu::CodeActions(_)) => {
3049 *context_menu = None;
3050 None
3051 }
3052 None => None,
3053 };
3054 let completion_position = completion_menu.map(|menu| menu.initial_position);
3055 drop(context_menu);
3056
3057 if effects.completions
3058 && let Some(completion_position) = completion_position
3059 {
3060 let start_offset = selection_start.to_offset(buffer);
3061 let position_matches = start_offset == completion_position.to_offset(buffer);
3062 let continue_showing = if position_matches {
3063 if self.snippet_stack.is_empty() {
3064 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3065 } else {
3066 // Snippet choices can be shown even when the cursor is in whitespace.
3067 // Dismissing the menu with actions like backspace is handled by
3068 // invalidation regions.
3069 true
3070 }
3071 } else {
3072 false
3073 };
3074
3075 if continue_showing {
3076 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3077 } else {
3078 self.hide_context_menu(window, cx);
3079 }
3080 }
3081
3082 hide_hover(self, cx);
3083
3084 if old_cursor_position.to_display_point(&display_map).row()
3085 != new_cursor_position.to_display_point(&display_map).row()
3086 {
3087 self.available_code_actions.take();
3088 }
3089 self.refresh_code_actions(window, cx);
3090 self.refresh_document_highlights(cx);
3091 self.refresh_selected_text_highlights(false, window, cx);
3092 refresh_matching_bracket_highlights(self, window, cx);
3093 self.update_visible_edit_prediction(window, cx);
3094 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3095 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3096 self.inline_blame_popover.take();
3097 if self.git_blame_inline_enabled {
3098 self.start_inline_blame_timer(window, cx);
3099 }
3100 }
3101
3102 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3103 cx.emit(EditorEvent::SelectionsChanged { local });
3104
3105 let selections = &self.selections.disjoint;
3106 if selections.len() == 1 {
3107 cx.emit(SearchEvent::ActiveMatchChanged)
3108 }
3109 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3110 let inmemory_selections = selections
3111 .iter()
3112 .map(|s| {
3113 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3114 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3115 })
3116 .collect();
3117 self.update_restoration_data(cx, |data| {
3118 data.selections = inmemory_selections;
3119 });
3120
3121 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3122 && let Some(workspace_id) =
3123 self.workspace.as_ref().and_then(|workspace| workspace.1)
3124 {
3125 let snapshot = self.buffer().read(cx).snapshot(cx);
3126 let selections = selections.clone();
3127 let background_executor = cx.background_executor().clone();
3128 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3129 self.serialize_selections = cx.background_spawn(async move {
3130 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3131 let db_selections = selections
3132 .iter()
3133 .map(|selection| {
3134 (
3135 selection.start.to_offset(&snapshot),
3136 selection.end.to_offset(&snapshot),
3137 )
3138 })
3139 .collect();
3140
3141 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3142 .await
3143 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3144 .log_err();
3145 });
3146 }
3147 }
3148
3149 cx.notify();
3150 }
3151
3152 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3153 use text::ToOffset as _;
3154 use text::ToPoint as _;
3155
3156 if self.mode.is_minimap()
3157 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3158 {
3159 return;
3160 }
3161
3162 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3163 return;
3164 };
3165
3166 let snapshot = singleton.read(cx).snapshot();
3167 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3168 let display_snapshot = display_map.snapshot(cx);
3169
3170 display_snapshot
3171 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3172 .map(|fold| {
3173 fold.range.start.text_anchor.to_point(&snapshot)
3174 ..fold.range.end.text_anchor.to_point(&snapshot)
3175 })
3176 .collect()
3177 });
3178 self.update_restoration_data(cx, |data| {
3179 data.folds = inmemory_folds;
3180 });
3181
3182 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3183 return;
3184 };
3185 let background_executor = cx.background_executor().clone();
3186 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3187 let db_folds = self.display_map.update(cx, |display_map, cx| {
3188 display_map
3189 .snapshot(cx)
3190 .folds_in_range(0..snapshot.len())
3191 .map(|fold| {
3192 (
3193 fold.range.start.text_anchor.to_offset(&snapshot),
3194 fold.range.end.text_anchor.to_offset(&snapshot),
3195 )
3196 })
3197 .collect()
3198 });
3199 self.serialize_folds = cx.background_spawn(async move {
3200 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3201 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3202 .await
3203 .with_context(|| {
3204 format!(
3205 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3206 )
3207 })
3208 .log_err();
3209 });
3210 }
3211
3212 pub fn sync_selections(
3213 &mut self,
3214 other: Entity<Editor>,
3215 cx: &mut Context<Self>,
3216 ) -> gpui::Subscription {
3217 let other_selections = other.read(cx).selections.disjoint.to_vec();
3218 self.selections.change_with(cx, |selections| {
3219 selections.select_anchors(other_selections);
3220 });
3221
3222 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3223 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3224 let other_selections = other.read(cx).selections.disjoint.to_vec();
3225 if other_selections.is_empty() {
3226 return;
3227 }
3228 this.selections.change_with(cx, |selections| {
3229 selections.select_anchors(other_selections);
3230 });
3231 }
3232 });
3233
3234 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3235 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3236 let these_selections = this.selections.disjoint.to_vec();
3237 if these_selections.is_empty() {
3238 return;
3239 }
3240 other.update(cx, |other_editor, cx| {
3241 other_editor.selections.change_with(cx, |selections| {
3242 selections.select_anchors(these_selections);
3243 })
3244 });
3245 }
3246 });
3247
3248 Subscription::join(other_subscription, this_subscription)
3249 }
3250
3251 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3252 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3253 /// effects of selection change occur at the end of the transaction.
3254 pub fn change_selections<R>(
3255 &mut self,
3256 effects: SelectionEffects,
3257 window: &mut Window,
3258 cx: &mut Context<Self>,
3259 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3260 ) -> R {
3261 if let Some(state) = &mut self.deferred_selection_effects_state {
3262 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3263 state.effects.completions = effects.completions;
3264 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3265 let (changed, result) = self.selections.change_with(cx, change);
3266 state.changed |= changed;
3267 return result;
3268 }
3269 let mut state = DeferredSelectionEffectsState {
3270 changed: false,
3271 effects,
3272 old_cursor_position: self.selections.newest_anchor().head(),
3273 history_entry: SelectionHistoryEntry {
3274 selections: self.selections.disjoint_anchors(),
3275 select_next_state: self.select_next_state.clone(),
3276 select_prev_state: self.select_prev_state.clone(),
3277 add_selections_state: self.add_selections_state.clone(),
3278 },
3279 };
3280 let (changed, result) = self.selections.change_with(cx, change);
3281 state.changed = state.changed || changed;
3282 if self.defer_selection_effects {
3283 self.deferred_selection_effects_state = Some(state);
3284 } else {
3285 self.apply_selection_effects(state, window, cx);
3286 }
3287 result
3288 }
3289
3290 /// Defers the effects of selection change, so that the effects of multiple calls to
3291 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3292 /// to selection history and the state of popovers based on selection position aren't
3293 /// erroneously updated.
3294 pub fn with_selection_effects_deferred<R>(
3295 &mut self,
3296 window: &mut Window,
3297 cx: &mut Context<Self>,
3298 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3299 ) -> R {
3300 let already_deferred = self.defer_selection_effects;
3301 self.defer_selection_effects = true;
3302 let result = update(self, window, cx);
3303 if !already_deferred {
3304 self.defer_selection_effects = false;
3305 if let Some(state) = self.deferred_selection_effects_state.take() {
3306 self.apply_selection_effects(state, window, cx);
3307 }
3308 }
3309 result
3310 }
3311
3312 fn apply_selection_effects(
3313 &mut self,
3314 state: DeferredSelectionEffectsState,
3315 window: &mut Window,
3316 cx: &mut Context<Self>,
3317 ) {
3318 if state.changed {
3319 self.selection_history.push(state.history_entry);
3320
3321 if let Some(autoscroll) = state.effects.scroll {
3322 self.request_autoscroll(autoscroll, cx);
3323 }
3324
3325 let old_cursor_position = &state.old_cursor_position;
3326
3327 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3328
3329 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3330 self.show_signature_help(&ShowSignatureHelp, window, cx);
3331 }
3332 }
3333 }
3334
3335 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3336 where
3337 I: IntoIterator<Item = (Range<S>, T)>,
3338 S: ToOffset,
3339 T: Into<Arc<str>>,
3340 {
3341 if self.read_only(cx) {
3342 return;
3343 }
3344
3345 self.buffer
3346 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3347 }
3348
3349 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3350 where
3351 I: IntoIterator<Item = (Range<S>, T)>,
3352 S: ToOffset,
3353 T: Into<Arc<str>>,
3354 {
3355 if self.read_only(cx) {
3356 return;
3357 }
3358
3359 self.buffer.update(cx, |buffer, cx| {
3360 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3361 });
3362 }
3363
3364 pub fn edit_with_block_indent<I, S, T>(
3365 &mut self,
3366 edits: I,
3367 original_indent_columns: Vec<Option<u32>>,
3368 cx: &mut Context<Self>,
3369 ) where
3370 I: IntoIterator<Item = (Range<S>, T)>,
3371 S: ToOffset,
3372 T: Into<Arc<str>>,
3373 {
3374 if self.read_only(cx) {
3375 return;
3376 }
3377
3378 self.buffer.update(cx, |buffer, cx| {
3379 buffer.edit(
3380 edits,
3381 Some(AutoindentMode::Block {
3382 original_indent_columns,
3383 }),
3384 cx,
3385 )
3386 });
3387 }
3388
3389 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3390 self.hide_context_menu(window, cx);
3391
3392 match phase {
3393 SelectPhase::Begin {
3394 position,
3395 add,
3396 click_count,
3397 } => self.begin_selection(position, add, click_count, window, cx),
3398 SelectPhase::BeginColumnar {
3399 position,
3400 goal_column,
3401 reset,
3402 mode,
3403 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3404 SelectPhase::Extend {
3405 position,
3406 click_count,
3407 } => self.extend_selection(position, click_count, window, cx),
3408 SelectPhase::Update {
3409 position,
3410 goal_column,
3411 scroll_delta,
3412 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3413 SelectPhase::End => self.end_selection(window, cx),
3414 }
3415 }
3416
3417 fn extend_selection(
3418 &mut self,
3419 position: DisplayPoint,
3420 click_count: usize,
3421 window: &mut Window,
3422 cx: &mut Context<Self>,
3423 ) {
3424 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3425 let tail = self.selections.newest::<usize>(cx).tail();
3426 self.begin_selection(position, false, click_count, window, cx);
3427
3428 let position = position.to_offset(&display_map, Bias::Left);
3429 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3430
3431 let mut pending_selection = self
3432 .selections
3433 .pending_anchor()
3434 .expect("extend_selection not called with pending selection");
3435 if position >= tail {
3436 pending_selection.start = tail_anchor;
3437 } else {
3438 pending_selection.end = tail_anchor;
3439 pending_selection.reversed = true;
3440 }
3441
3442 let mut pending_mode = self.selections.pending_mode().unwrap();
3443 match &mut pending_mode {
3444 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3445 _ => {}
3446 }
3447
3448 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3449 SelectionEffects::scroll(Autoscroll::fit())
3450 } else {
3451 SelectionEffects::no_scroll()
3452 };
3453
3454 self.change_selections(effects, window, cx, |s| {
3455 s.set_pending(pending_selection, pending_mode)
3456 });
3457 }
3458
3459 fn begin_selection(
3460 &mut self,
3461 position: DisplayPoint,
3462 add: bool,
3463 click_count: usize,
3464 window: &mut Window,
3465 cx: &mut Context<Self>,
3466 ) {
3467 if !self.focus_handle.is_focused(window) {
3468 self.last_focused_descendant = None;
3469 window.focus(&self.focus_handle);
3470 }
3471
3472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3473 let buffer = &display_map.buffer_snapshot;
3474 let position = display_map.clip_point(position, Bias::Left);
3475
3476 let start;
3477 let end;
3478 let mode;
3479 let mut auto_scroll;
3480 match click_count {
3481 1 => {
3482 start = buffer.anchor_before(position.to_point(&display_map));
3483 end = start;
3484 mode = SelectMode::Character;
3485 auto_scroll = true;
3486 }
3487 2 => {
3488 let position = display_map
3489 .clip_point(position, Bias::Left)
3490 .to_offset(&display_map, Bias::Left);
3491 let (range, _) = buffer.surrounding_word(position, false);
3492 start = buffer.anchor_before(range.start);
3493 end = buffer.anchor_before(range.end);
3494 mode = SelectMode::Word(start..end);
3495 auto_scroll = true;
3496 }
3497 3 => {
3498 let position = display_map
3499 .clip_point(position, Bias::Left)
3500 .to_point(&display_map);
3501 let line_start = display_map.prev_line_boundary(position).0;
3502 let next_line_start = buffer.clip_point(
3503 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3504 Bias::Left,
3505 );
3506 start = buffer.anchor_before(line_start);
3507 end = buffer.anchor_before(next_line_start);
3508 mode = SelectMode::Line(start..end);
3509 auto_scroll = true;
3510 }
3511 _ => {
3512 start = buffer.anchor_before(0);
3513 end = buffer.anchor_before(buffer.len());
3514 mode = SelectMode::All;
3515 auto_scroll = false;
3516 }
3517 }
3518 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3519
3520 let point_to_delete: Option<usize> = {
3521 let selected_points: Vec<Selection<Point>> =
3522 self.selections.disjoint_in_range(start..end, cx);
3523
3524 if !add || click_count > 1 {
3525 None
3526 } else if !selected_points.is_empty() {
3527 Some(selected_points[0].id)
3528 } else {
3529 let clicked_point_already_selected =
3530 self.selections.disjoint.iter().find(|selection| {
3531 selection.start.to_point(buffer) == start.to_point(buffer)
3532 || selection.end.to_point(buffer) == end.to_point(buffer)
3533 });
3534
3535 clicked_point_already_selected.map(|selection| selection.id)
3536 }
3537 };
3538
3539 let selections_count = self.selections.count();
3540 let effects = if auto_scroll {
3541 SelectionEffects::default()
3542 } else {
3543 SelectionEffects::no_scroll()
3544 };
3545
3546 self.change_selections(effects, window, cx, |s| {
3547 if let Some(point_to_delete) = point_to_delete {
3548 s.delete(point_to_delete);
3549
3550 if selections_count == 1 {
3551 s.set_pending_anchor_range(start..end, mode);
3552 }
3553 } else {
3554 if !add {
3555 s.clear_disjoint();
3556 }
3557
3558 s.set_pending_anchor_range(start..end, mode);
3559 }
3560 });
3561 }
3562
3563 fn begin_columnar_selection(
3564 &mut self,
3565 position: DisplayPoint,
3566 goal_column: u32,
3567 reset: bool,
3568 mode: ColumnarMode,
3569 window: &mut Window,
3570 cx: &mut Context<Self>,
3571 ) {
3572 if !self.focus_handle.is_focused(window) {
3573 self.last_focused_descendant = None;
3574 window.focus(&self.focus_handle);
3575 }
3576
3577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3578
3579 if reset {
3580 let pointer_position = display_map
3581 .buffer_snapshot
3582 .anchor_before(position.to_point(&display_map));
3583
3584 self.change_selections(
3585 SelectionEffects::scroll(Autoscroll::newest()),
3586 window,
3587 cx,
3588 |s| {
3589 s.clear_disjoint();
3590 s.set_pending_anchor_range(
3591 pointer_position..pointer_position,
3592 SelectMode::Character,
3593 );
3594 },
3595 );
3596 };
3597
3598 let tail = self.selections.newest::<Point>(cx).tail();
3599 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3600 self.columnar_selection_state = match mode {
3601 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3602 selection_tail: selection_anchor,
3603 display_point: if reset {
3604 if position.column() != goal_column {
3605 Some(DisplayPoint::new(position.row(), goal_column))
3606 } else {
3607 None
3608 }
3609 } else {
3610 None
3611 },
3612 }),
3613 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3614 selection_tail: selection_anchor,
3615 }),
3616 };
3617
3618 if !reset {
3619 self.select_columns(position, goal_column, &display_map, window, cx);
3620 }
3621 }
3622
3623 fn update_selection(
3624 &mut self,
3625 position: DisplayPoint,
3626 goal_column: u32,
3627 scroll_delta: gpui::Point<f32>,
3628 window: &mut Window,
3629 cx: &mut Context<Self>,
3630 ) {
3631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3632
3633 if self.columnar_selection_state.is_some() {
3634 self.select_columns(position, goal_column, &display_map, window, cx);
3635 } else if let Some(mut pending) = self.selections.pending_anchor() {
3636 let buffer = &display_map.buffer_snapshot;
3637 let head;
3638 let tail;
3639 let mode = self.selections.pending_mode().unwrap();
3640 match &mode {
3641 SelectMode::Character => {
3642 head = position.to_point(&display_map);
3643 tail = pending.tail().to_point(buffer);
3644 }
3645 SelectMode::Word(original_range) => {
3646 let offset = display_map
3647 .clip_point(position, Bias::Left)
3648 .to_offset(&display_map, Bias::Left);
3649 let original_range = original_range.to_offset(buffer);
3650
3651 let head_offset = if buffer.is_inside_word(offset, false)
3652 || original_range.contains(&offset)
3653 {
3654 let (word_range, _) = buffer.surrounding_word(offset, false);
3655 if word_range.start < original_range.start {
3656 word_range.start
3657 } else {
3658 word_range.end
3659 }
3660 } else {
3661 offset
3662 };
3663
3664 head = head_offset.to_point(buffer);
3665 if head_offset <= original_range.start {
3666 tail = original_range.end.to_point(buffer);
3667 } else {
3668 tail = original_range.start.to_point(buffer);
3669 }
3670 }
3671 SelectMode::Line(original_range) => {
3672 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3673
3674 let position = display_map
3675 .clip_point(position, Bias::Left)
3676 .to_point(&display_map);
3677 let line_start = display_map.prev_line_boundary(position).0;
3678 let next_line_start = buffer.clip_point(
3679 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3680 Bias::Left,
3681 );
3682
3683 if line_start < original_range.start {
3684 head = line_start
3685 } else {
3686 head = next_line_start
3687 }
3688
3689 if head <= original_range.start {
3690 tail = original_range.end;
3691 } else {
3692 tail = original_range.start;
3693 }
3694 }
3695 SelectMode::All => {
3696 return;
3697 }
3698 };
3699
3700 if head < tail {
3701 pending.start = buffer.anchor_before(head);
3702 pending.end = buffer.anchor_before(tail);
3703 pending.reversed = true;
3704 } else {
3705 pending.start = buffer.anchor_before(tail);
3706 pending.end = buffer.anchor_before(head);
3707 pending.reversed = false;
3708 }
3709
3710 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3711 s.set_pending(pending, mode);
3712 });
3713 } else {
3714 log::error!("update_selection dispatched with no pending selection");
3715 return;
3716 }
3717
3718 self.apply_scroll_delta(scroll_delta, window, cx);
3719 cx.notify();
3720 }
3721
3722 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3723 self.columnar_selection_state.take();
3724 if self.selections.pending_anchor().is_some() {
3725 let selections = self.selections.all::<usize>(cx);
3726 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3727 s.select(selections);
3728 s.clear_pending();
3729 });
3730 }
3731 }
3732
3733 fn select_columns(
3734 &mut self,
3735 head: DisplayPoint,
3736 goal_column: u32,
3737 display_map: &DisplaySnapshot,
3738 window: &mut Window,
3739 cx: &mut Context<Self>,
3740 ) {
3741 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3742 return;
3743 };
3744
3745 let tail = match columnar_state {
3746 ColumnarSelectionState::FromMouse {
3747 selection_tail,
3748 display_point,
3749 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3750 ColumnarSelectionState::FromSelection { selection_tail } => {
3751 selection_tail.to_display_point(display_map)
3752 }
3753 };
3754
3755 let start_row = cmp::min(tail.row(), head.row());
3756 let end_row = cmp::max(tail.row(), head.row());
3757 let start_column = cmp::min(tail.column(), goal_column);
3758 let end_column = cmp::max(tail.column(), goal_column);
3759 let reversed = start_column < tail.column();
3760
3761 let selection_ranges = (start_row.0..=end_row.0)
3762 .map(DisplayRow)
3763 .filter_map(|row| {
3764 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3765 || start_column <= display_map.line_len(row))
3766 && !display_map.is_block_line(row)
3767 {
3768 let start = display_map
3769 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3770 .to_point(display_map);
3771 let end = display_map
3772 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3773 .to_point(display_map);
3774 if reversed {
3775 Some(end..start)
3776 } else {
3777 Some(start..end)
3778 }
3779 } else {
3780 None
3781 }
3782 })
3783 .collect::<Vec<_>>();
3784
3785 let ranges = match columnar_state {
3786 ColumnarSelectionState::FromMouse { .. } => {
3787 let mut non_empty_ranges = selection_ranges
3788 .iter()
3789 .filter(|selection_range| selection_range.start != selection_range.end)
3790 .peekable();
3791 if non_empty_ranges.peek().is_some() {
3792 non_empty_ranges.cloned().collect()
3793 } else {
3794 selection_ranges
3795 }
3796 }
3797 _ => selection_ranges,
3798 };
3799
3800 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3801 s.select_ranges(ranges);
3802 });
3803 cx.notify();
3804 }
3805
3806 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3807 self.selections
3808 .all_adjusted(cx)
3809 .iter()
3810 .any(|selection| !selection.is_empty())
3811 }
3812
3813 pub fn has_pending_nonempty_selection(&self) -> bool {
3814 let pending_nonempty_selection = match self.selections.pending_anchor() {
3815 Some(Selection { start, end, .. }) => start != end,
3816 None => false,
3817 };
3818
3819 pending_nonempty_selection
3820 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3821 }
3822
3823 pub fn has_pending_selection(&self) -> bool {
3824 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3825 }
3826
3827 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3828 self.selection_mark_mode = false;
3829 self.selection_drag_state = SelectionDragState::None;
3830
3831 if self.clear_expanded_diff_hunks(cx) {
3832 cx.notify();
3833 return;
3834 }
3835 if self.dismiss_menus_and_popups(true, window, cx) {
3836 return;
3837 }
3838
3839 if self.mode.is_full()
3840 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3841 {
3842 return;
3843 }
3844
3845 cx.propagate();
3846 }
3847
3848 pub fn dismiss_menus_and_popups(
3849 &mut self,
3850 is_user_requested: bool,
3851 window: &mut Window,
3852 cx: &mut Context<Self>,
3853 ) -> bool {
3854 if self.take_rename(false, window, cx).is_some() {
3855 return true;
3856 }
3857
3858 if hide_hover(self, cx) {
3859 return true;
3860 }
3861
3862 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3863 return true;
3864 }
3865
3866 if self.hide_context_menu(window, cx).is_some() {
3867 return true;
3868 }
3869
3870 if self.mouse_context_menu.take().is_some() {
3871 return true;
3872 }
3873
3874 if is_user_requested && self.discard_edit_prediction(true, cx) {
3875 return true;
3876 }
3877
3878 if self.snippet_stack.pop().is_some() {
3879 return true;
3880 }
3881
3882 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3883 self.dismiss_diagnostics(cx);
3884 return true;
3885 }
3886
3887 false
3888 }
3889
3890 fn linked_editing_ranges_for(
3891 &self,
3892 selection: Range<text::Anchor>,
3893 cx: &App,
3894 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3895 if self.linked_edit_ranges.is_empty() {
3896 return None;
3897 }
3898 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3899 selection.end.buffer_id.and_then(|end_buffer_id| {
3900 if selection.start.buffer_id != Some(end_buffer_id) {
3901 return None;
3902 }
3903 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3904 let snapshot = buffer.read(cx).snapshot();
3905 self.linked_edit_ranges
3906 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3907 .map(|ranges| (ranges, snapshot, buffer))
3908 })?;
3909 use text::ToOffset as TO;
3910 // find offset from the start of current range to current cursor position
3911 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3912
3913 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3914 let start_difference = start_offset - start_byte_offset;
3915 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3916 let end_difference = end_offset - start_byte_offset;
3917 // Current range has associated linked ranges.
3918 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3919 for range in linked_ranges.iter() {
3920 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3921 let end_offset = start_offset + end_difference;
3922 let start_offset = start_offset + start_difference;
3923 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3924 continue;
3925 }
3926 if self.selections.disjoint_anchor_ranges().any(|s| {
3927 if s.start.buffer_id != selection.start.buffer_id
3928 || s.end.buffer_id != selection.end.buffer_id
3929 {
3930 return false;
3931 }
3932 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3933 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3934 }) {
3935 continue;
3936 }
3937 let start = buffer_snapshot.anchor_after(start_offset);
3938 let end = buffer_snapshot.anchor_after(end_offset);
3939 linked_edits
3940 .entry(buffer.clone())
3941 .or_default()
3942 .push(start..end);
3943 }
3944 Some(linked_edits)
3945 }
3946
3947 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3948 let text: Arc<str> = text.into();
3949
3950 if self.read_only(cx) {
3951 return;
3952 }
3953
3954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3955
3956 let selections = self.selections.all_adjusted(cx);
3957 let mut bracket_inserted = false;
3958 let mut edits = Vec::new();
3959 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3960 let mut new_selections = Vec::with_capacity(selections.len());
3961 let mut new_autoclose_regions = Vec::new();
3962 let snapshot = self.buffer.read(cx).read(cx);
3963 let mut clear_linked_edit_ranges = false;
3964
3965 for (selection, autoclose_region) in
3966 self.selections_with_autoclose_regions(selections, &snapshot)
3967 {
3968 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3969 // Determine if the inserted text matches the opening or closing
3970 // bracket of any of this language's bracket pairs.
3971 let mut bracket_pair = None;
3972 let mut is_bracket_pair_start = false;
3973 let mut is_bracket_pair_end = false;
3974 if !text.is_empty() {
3975 let mut bracket_pair_matching_end = None;
3976 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3977 // and they are removing the character that triggered IME popup.
3978 for (pair, enabled) in scope.brackets() {
3979 if !pair.close && !pair.surround {
3980 continue;
3981 }
3982
3983 if enabled && pair.start.ends_with(text.as_ref()) {
3984 let prefix_len = pair.start.len() - text.len();
3985 let preceding_text_matches_prefix = prefix_len == 0
3986 || (selection.start.column >= (prefix_len as u32)
3987 && snapshot.contains_str_at(
3988 Point::new(
3989 selection.start.row,
3990 selection.start.column - (prefix_len as u32),
3991 ),
3992 &pair.start[..prefix_len],
3993 ));
3994 if preceding_text_matches_prefix {
3995 bracket_pair = Some(pair.clone());
3996 is_bracket_pair_start = true;
3997 break;
3998 }
3999 }
4000 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4001 {
4002 // take first bracket pair matching end, but don't break in case a later bracket
4003 // pair matches start
4004 bracket_pair_matching_end = Some(pair.clone());
4005 }
4006 }
4007 if let Some(end) = bracket_pair_matching_end
4008 && bracket_pair.is_none()
4009 {
4010 bracket_pair = Some(end);
4011 is_bracket_pair_end = true;
4012 }
4013 }
4014
4015 if let Some(bracket_pair) = bracket_pair {
4016 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4017 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4018 let auto_surround =
4019 self.use_auto_surround && snapshot_settings.use_auto_surround;
4020 if selection.is_empty() {
4021 if is_bracket_pair_start {
4022 // If the inserted text is a suffix of an opening bracket and the
4023 // selection is preceded by the rest of the opening bracket, then
4024 // insert the closing bracket.
4025 let following_text_allows_autoclose = snapshot
4026 .chars_at(selection.start)
4027 .next()
4028 .is_none_or(|c| scope.should_autoclose_before(c));
4029
4030 let preceding_text_allows_autoclose = selection.start.column == 0
4031 || snapshot
4032 .reversed_chars_at(selection.start)
4033 .next()
4034 .is_none_or(|c| {
4035 bracket_pair.start != bracket_pair.end
4036 || !snapshot
4037 .char_classifier_at(selection.start)
4038 .is_word(c)
4039 });
4040
4041 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4042 && bracket_pair.start.len() == 1
4043 {
4044 let target = bracket_pair.start.chars().next().unwrap();
4045 let current_line_count = snapshot
4046 .reversed_chars_at(selection.start)
4047 .take_while(|&c| c != '\n')
4048 .filter(|&c| c == target)
4049 .count();
4050 current_line_count % 2 == 1
4051 } else {
4052 false
4053 };
4054
4055 if autoclose
4056 && bracket_pair.close
4057 && following_text_allows_autoclose
4058 && preceding_text_allows_autoclose
4059 && !is_closing_quote
4060 {
4061 let anchor = snapshot.anchor_before(selection.end);
4062 new_selections.push((selection.map(|_| anchor), text.len()));
4063 new_autoclose_regions.push((
4064 anchor,
4065 text.len(),
4066 selection.id,
4067 bracket_pair.clone(),
4068 ));
4069 edits.push((
4070 selection.range(),
4071 format!("{}{}", text, bracket_pair.end).into(),
4072 ));
4073 bracket_inserted = true;
4074 continue;
4075 }
4076 }
4077
4078 if let Some(region) = autoclose_region {
4079 // If the selection is followed by an auto-inserted closing bracket,
4080 // then don't insert that closing bracket again; just move the selection
4081 // past the closing bracket.
4082 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4083 && text.as_ref() == region.pair.end.as_str()
4084 && snapshot.contains_str_at(region.range.end, text.as_ref());
4085 if should_skip {
4086 let anchor = snapshot.anchor_after(selection.end);
4087 new_selections
4088 .push((selection.map(|_| anchor), region.pair.end.len()));
4089 continue;
4090 }
4091 }
4092
4093 let always_treat_brackets_as_autoclosed = snapshot
4094 .language_settings_at(selection.start, cx)
4095 .always_treat_brackets_as_autoclosed;
4096 if always_treat_brackets_as_autoclosed
4097 && is_bracket_pair_end
4098 && snapshot.contains_str_at(selection.end, text.as_ref())
4099 {
4100 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4101 // and the inserted text is a closing bracket and the selection is followed
4102 // by the closing bracket then move the selection past the closing bracket.
4103 let anchor = snapshot.anchor_after(selection.end);
4104 new_selections.push((selection.map(|_| anchor), text.len()));
4105 continue;
4106 }
4107 }
4108 // If an opening bracket is 1 character long and is typed while
4109 // text is selected, then surround that text with the bracket pair.
4110 else if auto_surround
4111 && bracket_pair.surround
4112 && is_bracket_pair_start
4113 && bracket_pair.start.chars().count() == 1
4114 {
4115 edits.push((selection.start..selection.start, text.clone()));
4116 edits.push((
4117 selection.end..selection.end,
4118 bracket_pair.end.as_str().into(),
4119 ));
4120 bracket_inserted = true;
4121 new_selections.push((
4122 Selection {
4123 id: selection.id,
4124 start: snapshot.anchor_after(selection.start),
4125 end: snapshot.anchor_before(selection.end),
4126 reversed: selection.reversed,
4127 goal: selection.goal,
4128 },
4129 0,
4130 ));
4131 continue;
4132 }
4133 }
4134 }
4135
4136 if self.auto_replace_emoji_shortcode
4137 && selection.is_empty()
4138 && text.as_ref().ends_with(':')
4139 && let Some(possible_emoji_short_code) =
4140 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4141 && !possible_emoji_short_code.is_empty()
4142 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4143 {
4144 let emoji_shortcode_start = Point::new(
4145 selection.start.row,
4146 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4147 );
4148
4149 // Remove shortcode from buffer
4150 edits.push((
4151 emoji_shortcode_start..selection.start,
4152 "".to_string().into(),
4153 ));
4154 new_selections.push((
4155 Selection {
4156 id: selection.id,
4157 start: snapshot.anchor_after(emoji_shortcode_start),
4158 end: snapshot.anchor_before(selection.start),
4159 reversed: selection.reversed,
4160 goal: selection.goal,
4161 },
4162 0,
4163 ));
4164
4165 // Insert emoji
4166 let selection_start_anchor = snapshot.anchor_after(selection.start);
4167 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4168 edits.push((selection.start..selection.end, emoji.to_string().into()));
4169
4170 continue;
4171 }
4172
4173 // If not handling any auto-close operation, then just replace the selected
4174 // text with the given input and move the selection to the end of the
4175 // newly inserted text.
4176 let anchor = snapshot.anchor_after(selection.end);
4177 if !self.linked_edit_ranges.is_empty() {
4178 let start_anchor = snapshot.anchor_before(selection.start);
4179
4180 let is_word_char = text.chars().next().is_none_or(|char| {
4181 let classifier = snapshot
4182 .char_classifier_at(start_anchor.to_offset(&snapshot))
4183 .ignore_punctuation(true);
4184 classifier.is_word(char)
4185 });
4186
4187 if is_word_char {
4188 if let Some(ranges) = self
4189 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4190 {
4191 for (buffer, edits) in ranges {
4192 linked_edits
4193 .entry(buffer.clone())
4194 .or_default()
4195 .extend(edits.into_iter().map(|range| (range, text.clone())));
4196 }
4197 }
4198 } else {
4199 clear_linked_edit_ranges = true;
4200 }
4201 }
4202
4203 new_selections.push((selection.map(|_| anchor), 0));
4204 edits.push((selection.start..selection.end, text.clone()));
4205 }
4206
4207 drop(snapshot);
4208
4209 self.transact(window, cx, |this, window, cx| {
4210 if clear_linked_edit_ranges {
4211 this.linked_edit_ranges.clear();
4212 }
4213 let initial_buffer_versions =
4214 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4215
4216 this.buffer.update(cx, |buffer, cx| {
4217 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4218 });
4219 for (buffer, edits) in linked_edits {
4220 buffer.update(cx, |buffer, cx| {
4221 let snapshot = buffer.snapshot();
4222 let edits = edits
4223 .into_iter()
4224 .map(|(range, text)| {
4225 use text::ToPoint as TP;
4226 let end_point = TP::to_point(&range.end, &snapshot);
4227 let start_point = TP::to_point(&range.start, &snapshot);
4228 (start_point..end_point, text)
4229 })
4230 .sorted_by_key(|(range, _)| range.start);
4231 buffer.edit(edits, None, cx);
4232 })
4233 }
4234 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4235 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4236 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4237 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4238 .zip(new_selection_deltas)
4239 .map(|(selection, delta)| Selection {
4240 id: selection.id,
4241 start: selection.start + delta,
4242 end: selection.end + delta,
4243 reversed: selection.reversed,
4244 goal: SelectionGoal::None,
4245 })
4246 .collect::<Vec<_>>();
4247
4248 let mut i = 0;
4249 for (position, delta, selection_id, pair) in new_autoclose_regions {
4250 let position = position.to_offset(&map.buffer_snapshot) + delta;
4251 let start = map.buffer_snapshot.anchor_before(position);
4252 let end = map.buffer_snapshot.anchor_after(position);
4253 while let Some(existing_state) = this.autoclose_regions.get(i) {
4254 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4255 Ordering::Less => i += 1,
4256 Ordering::Greater => break,
4257 Ordering::Equal => {
4258 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4259 Ordering::Less => i += 1,
4260 Ordering::Equal => break,
4261 Ordering::Greater => break,
4262 }
4263 }
4264 }
4265 }
4266 this.autoclose_regions.insert(
4267 i,
4268 AutocloseRegion {
4269 selection_id,
4270 range: start..end,
4271 pair,
4272 },
4273 );
4274 }
4275
4276 let had_active_edit_prediction = this.has_active_edit_prediction();
4277 this.change_selections(
4278 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4279 window,
4280 cx,
4281 |s| s.select(new_selections),
4282 );
4283
4284 if !bracket_inserted
4285 && let Some(on_type_format_task) =
4286 this.trigger_on_type_formatting(text.to_string(), window, cx)
4287 {
4288 on_type_format_task.detach_and_log_err(cx);
4289 }
4290
4291 let editor_settings = EditorSettings::get_global(cx);
4292 if bracket_inserted
4293 && (editor_settings.auto_signature_help
4294 || editor_settings.show_signature_help_after_edits)
4295 {
4296 this.show_signature_help(&ShowSignatureHelp, window, cx);
4297 }
4298
4299 let trigger_in_words =
4300 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4301 if this.hard_wrap.is_some() {
4302 let latest: Range<Point> = this.selections.newest(cx).range();
4303 if latest.is_empty()
4304 && this
4305 .buffer()
4306 .read(cx)
4307 .snapshot(cx)
4308 .line_len(MultiBufferRow(latest.start.row))
4309 == latest.start.column
4310 {
4311 this.rewrap_impl(
4312 RewrapOptions {
4313 override_language_settings: true,
4314 preserve_existing_whitespace: true,
4315 },
4316 cx,
4317 )
4318 }
4319 }
4320 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4321 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4322 this.refresh_edit_prediction(true, false, window, cx);
4323 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4324 });
4325 }
4326
4327 fn find_possible_emoji_shortcode_at_position(
4328 snapshot: &MultiBufferSnapshot,
4329 position: Point,
4330 ) -> Option<String> {
4331 let mut chars = Vec::new();
4332 let mut found_colon = false;
4333 for char in snapshot.reversed_chars_at(position).take(100) {
4334 // Found a possible emoji shortcode in the middle of the buffer
4335 if found_colon {
4336 if char.is_whitespace() {
4337 chars.reverse();
4338 return Some(chars.iter().collect());
4339 }
4340 // If the previous character is not a whitespace, we are in the middle of a word
4341 // and we only want to complete the shortcode if the word is made up of other emojis
4342 let mut containing_word = String::new();
4343 for ch in snapshot
4344 .reversed_chars_at(position)
4345 .skip(chars.len() + 1)
4346 .take(100)
4347 {
4348 if ch.is_whitespace() {
4349 break;
4350 }
4351 containing_word.push(ch);
4352 }
4353 let containing_word = containing_word.chars().rev().collect::<String>();
4354 if util::word_consists_of_emojis(containing_word.as_str()) {
4355 chars.reverse();
4356 return Some(chars.iter().collect());
4357 }
4358 }
4359
4360 if char.is_whitespace() || !char.is_ascii() {
4361 return None;
4362 }
4363 if char == ':' {
4364 found_colon = true;
4365 } else {
4366 chars.push(char);
4367 }
4368 }
4369 // Found a possible emoji shortcode at the beginning of the buffer
4370 chars.reverse();
4371 Some(chars.iter().collect())
4372 }
4373
4374 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4375 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4376 self.transact(window, cx, |this, window, cx| {
4377 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4378 let selections = this.selections.all::<usize>(cx);
4379 let multi_buffer = this.buffer.read(cx);
4380 let buffer = multi_buffer.snapshot(cx);
4381 selections
4382 .iter()
4383 .map(|selection| {
4384 let start_point = selection.start.to_point(&buffer);
4385 let mut existing_indent =
4386 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4387 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4388 let start = selection.start;
4389 let end = selection.end;
4390 let selection_is_empty = start == end;
4391 let language_scope = buffer.language_scope_at(start);
4392 let (
4393 comment_delimiter,
4394 doc_delimiter,
4395 insert_extra_newline,
4396 indent_on_newline,
4397 indent_on_extra_newline,
4398 ) = if let Some(language) = &language_scope {
4399 let mut insert_extra_newline =
4400 insert_extra_newline_brackets(&buffer, start..end, language)
4401 || insert_extra_newline_tree_sitter(&buffer, start..end);
4402
4403 // Comment extension on newline is allowed only for cursor selections
4404 let comment_delimiter = maybe!({
4405 if !selection_is_empty {
4406 return None;
4407 }
4408
4409 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4410 return None;
4411 }
4412
4413 let delimiters = language.line_comment_prefixes();
4414 let max_len_of_delimiter =
4415 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4416 let (snapshot, range) =
4417 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4418
4419 let num_of_whitespaces = snapshot
4420 .chars_for_range(range.clone())
4421 .take_while(|c| c.is_whitespace())
4422 .count();
4423 let comment_candidate = snapshot
4424 .chars_for_range(range.clone())
4425 .skip(num_of_whitespaces)
4426 .take(max_len_of_delimiter)
4427 .collect::<String>();
4428 let (delimiter, trimmed_len) = delimiters
4429 .iter()
4430 .filter_map(|delimiter| {
4431 let prefix = delimiter.trim_end();
4432 if comment_candidate.starts_with(prefix) {
4433 Some((delimiter, prefix.len()))
4434 } else {
4435 None
4436 }
4437 })
4438 .max_by_key(|(_, len)| *len)?;
4439
4440 if let Some(BlockCommentConfig {
4441 start: block_start, ..
4442 }) = language.block_comment()
4443 {
4444 let block_start_trimmed = block_start.trim_end();
4445 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4446 let line_content = snapshot
4447 .chars_for_range(range)
4448 .skip(num_of_whitespaces)
4449 .take(block_start_trimmed.len())
4450 .collect::<String>();
4451
4452 if line_content.starts_with(block_start_trimmed) {
4453 return None;
4454 }
4455 }
4456 }
4457
4458 let cursor_is_placed_after_comment_marker =
4459 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4460 if cursor_is_placed_after_comment_marker {
4461 Some(delimiter.clone())
4462 } else {
4463 None
4464 }
4465 });
4466
4467 let mut indent_on_newline = IndentSize::spaces(0);
4468 let mut indent_on_extra_newline = IndentSize::spaces(0);
4469
4470 let doc_delimiter = maybe!({
4471 if !selection_is_empty {
4472 return None;
4473 }
4474
4475 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4476 return None;
4477 }
4478
4479 let BlockCommentConfig {
4480 start: start_tag,
4481 end: end_tag,
4482 prefix: delimiter,
4483 tab_size: len,
4484 } = language.documentation_comment()?;
4485 let is_within_block_comment = buffer
4486 .language_scope_at(start_point)
4487 .is_some_and(|scope| scope.override_name() == Some("comment"));
4488 if !is_within_block_comment {
4489 return None;
4490 }
4491
4492 let (snapshot, range) =
4493 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4494
4495 let num_of_whitespaces = snapshot
4496 .chars_for_range(range.clone())
4497 .take_while(|c| c.is_whitespace())
4498 .count();
4499
4500 // 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.
4501 let column = start_point.column;
4502 let cursor_is_after_start_tag = {
4503 let start_tag_len = start_tag.len();
4504 let start_tag_line = snapshot
4505 .chars_for_range(range.clone())
4506 .skip(num_of_whitespaces)
4507 .take(start_tag_len)
4508 .collect::<String>();
4509 if start_tag_line.starts_with(start_tag.as_ref()) {
4510 num_of_whitespaces + start_tag_len <= column as usize
4511 } else {
4512 false
4513 }
4514 };
4515
4516 let cursor_is_after_delimiter = {
4517 let delimiter_trim = delimiter.trim_end();
4518 let delimiter_line = snapshot
4519 .chars_for_range(range.clone())
4520 .skip(num_of_whitespaces)
4521 .take(delimiter_trim.len())
4522 .collect::<String>();
4523 if delimiter_line.starts_with(delimiter_trim) {
4524 num_of_whitespaces + delimiter_trim.len() <= column as usize
4525 } else {
4526 false
4527 }
4528 };
4529
4530 let cursor_is_before_end_tag_if_exists = {
4531 let mut char_position = 0u32;
4532 let mut end_tag_offset = None;
4533
4534 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4535 if let Some(byte_pos) = chunk.find(&**end_tag) {
4536 let chars_before_match =
4537 chunk[..byte_pos].chars().count() as u32;
4538 end_tag_offset =
4539 Some(char_position + chars_before_match);
4540 break 'outer;
4541 }
4542 char_position += chunk.chars().count() as u32;
4543 }
4544
4545 if let Some(end_tag_offset) = end_tag_offset {
4546 let cursor_is_before_end_tag = column <= end_tag_offset;
4547 if cursor_is_after_start_tag {
4548 if cursor_is_before_end_tag {
4549 insert_extra_newline = true;
4550 }
4551 let cursor_is_at_start_of_end_tag =
4552 column == end_tag_offset;
4553 if cursor_is_at_start_of_end_tag {
4554 indent_on_extra_newline.len = *len;
4555 }
4556 }
4557 cursor_is_before_end_tag
4558 } else {
4559 true
4560 }
4561 };
4562
4563 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4564 && cursor_is_before_end_tag_if_exists
4565 {
4566 if cursor_is_after_start_tag {
4567 indent_on_newline.len = *len;
4568 }
4569 Some(delimiter.clone())
4570 } else {
4571 None
4572 }
4573 });
4574
4575 (
4576 comment_delimiter,
4577 doc_delimiter,
4578 insert_extra_newline,
4579 indent_on_newline,
4580 indent_on_extra_newline,
4581 )
4582 } else {
4583 (
4584 None,
4585 None,
4586 false,
4587 IndentSize::default(),
4588 IndentSize::default(),
4589 )
4590 };
4591
4592 let prevent_auto_indent = doc_delimiter.is_some();
4593 let delimiter = comment_delimiter.or(doc_delimiter);
4594
4595 let capacity_for_delimiter =
4596 delimiter.as_deref().map(str::len).unwrap_or_default();
4597 let mut new_text = String::with_capacity(
4598 1 + capacity_for_delimiter
4599 + existing_indent.len as usize
4600 + indent_on_newline.len as usize
4601 + indent_on_extra_newline.len as usize,
4602 );
4603 new_text.push('\n');
4604 new_text.extend(existing_indent.chars());
4605 new_text.extend(indent_on_newline.chars());
4606
4607 if let Some(delimiter) = &delimiter {
4608 new_text.push_str(delimiter);
4609 }
4610
4611 if insert_extra_newline {
4612 new_text.push('\n');
4613 new_text.extend(existing_indent.chars());
4614 new_text.extend(indent_on_extra_newline.chars());
4615 }
4616
4617 let anchor = buffer.anchor_after(end);
4618 let new_selection = selection.map(|_| anchor);
4619 (
4620 ((start..end, new_text), prevent_auto_indent),
4621 (insert_extra_newline, new_selection),
4622 )
4623 })
4624 .unzip()
4625 };
4626
4627 let mut auto_indent_edits = Vec::new();
4628 let mut edits = Vec::new();
4629 for (edit, prevent_auto_indent) in edits_with_flags {
4630 if prevent_auto_indent {
4631 edits.push(edit);
4632 } else {
4633 auto_indent_edits.push(edit);
4634 }
4635 }
4636 if !edits.is_empty() {
4637 this.edit(edits, cx);
4638 }
4639 if !auto_indent_edits.is_empty() {
4640 this.edit_with_autoindent(auto_indent_edits, cx);
4641 }
4642
4643 let buffer = this.buffer.read(cx).snapshot(cx);
4644 let new_selections = selection_info
4645 .into_iter()
4646 .map(|(extra_newline_inserted, new_selection)| {
4647 let mut cursor = new_selection.end.to_point(&buffer);
4648 if extra_newline_inserted {
4649 cursor.row -= 1;
4650 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4651 }
4652 new_selection.map(|_| cursor)
4653 })
4654 .collect();
4655
4656 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4657 this.refresh_edit_prediction(true, false, window, cx);
4658 });
4659 }
4660
4661 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4662 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4663
4664 let buffer = self.buffer.read(cx);
4665 let snapshot = buffer.snapshot(cx);
4666
4667 let mut edits = Vec::new();
4668 let mut rows = Vec::new();
4669
4670 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4671 let cursor = selection.head();
4672 let row = cursor.row;
4673
4674 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4675
4676 let newline = "\n".to_string();
4677 edits.push((start_of_line..start_of_line, newline));
4678
4679 rows.push(row + rows_inserted as u32);
4680 }
4681
4682 self.transact(window, cx, |editor, window, cx| {
4683 editor.edit(edits, cx);
4684
4685 editor.change_selections(Default::default(), window, cx, |s| {
4686 let mut index = 0;
4687 s.move_cursors_with(|map, _, _| {
4688 let row = rows[index];
4689 index += 1;
4690
4691 let point = Point::new(row, 0);
4692 let boundary = map.next_line_boundary(point).1;
4693 let clipped = map.clip_point(boundary, Bias::Left);
4694
4695 (clipped, SelectionGoal::None)
4696 });
4697 });
4698
4699 let mut indent_edits = Vec::new();
4700 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4701 for row in rows {
4702 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4703 for (row, indent) in indents {
4704 if indent.len == 0 {
4705 continue;
4706 }
4707
4708 let text = match indent.kind {
4709 IndentKind::Space => " ".repeat(indent.len as usize),
4710 IndentKind::Tab => "\t".repeat(indent.len as usize),
4711 };
4712 let point = Point::new(row.0, 0);
4713 indent_edits.push((point..point, text));
4714 }
4715 }
4716 editor.edit(indent_edits, cx);
4717 });
4718 }
4719
4720 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4722
4723 let buffer = self.buffer.read(cx);
4724 let snapshot = buffer.snapshot(cx);
4725
4726 let mut edits = Vec::new();
4727 let mut rows = Vec::new();
4728 let mut rows_inserted = 0;
4729
4730 for selection in self.selections.all_adjusted(cx) {
4731 let cursor = selection.head();
4732 let row = cursor.row;
4733
4734 let point = Point::new(row + 1, 0);
4735 let start_of_line = snapshot.clip_point(point, Bias::Left);
4736
4737 let newline = "\n".to_string();
4738 edits.push((start_of_line..start_of_line, newline));
4739
4740 rows_inserted += 1;
4741 rows.push(row + rows_inserted);
4742 }
4743
4744 self.transact(window, cx, |editor, window, cx| {
4745 editor.edit(edits, cx);
4746
4747 editor.change_selections(Default::default(), window, cx, |s| {
4748 let mut index = 0;
4749 s.move_cursors_with(|map, _, _| {
4750 let row = rows[index];
4751 index += 1;
4752
4753 let point = Point::new(row, 0);
4754 let boundary = map.next_line_boundary(point).1;
4755 let clipped = map.clip_point(boundary, Bias::Left);
4756
4757 (clipped, SelectionGoal::None)
4758 });
4759 });
4760
4761 let mut indent_edits = Vec::new();
4762 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4763 for row in rows {
4764 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4765 for (row, indent) in indents {
4766 if indent.len == 0 {
4767 continue;
4768 }
4769
4770 let text = match indent.kind {
4771 IndentKind::Space => " ".repeat(indent.len as usize),
4772 IndentKind::Tab => "\t".repeat(indent.len as usize),
4773 };
4774 let point = Point::new(row.0, 0);
4775 indent_edits.push((point..point, text));
4776 }
4777 }
4778 editor.edit(indent_edits, cx);
4779 });
4780 }
4781
4782 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4783 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4784 original_indent_columns: Vec::new(),
4785 });
4786 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4787 }
4788
4789 fn insert_with_autoindent_mode(
4790 &mut self,
4791 text: &str,
4792 autoindent_mode: Option<AutoindentMode>,
4793 window: &mut Window,
4794 cx: &mut Context<Self>,
4795 ) {
4796 if self.read_only(cx) {
4797 return;
4798 }
4799
4800 let text: Arc<str> = text.into();
4801 self.transact(window, cx, |this, window, cx| {
4802 let old_selections = this.selections.all_adjusted(cx);
4803 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4804 let anchors = {
4805 let snapshot = buffer.read(cx);
4806 old_selections
4807 .iter()
4808 .map(|s| {
4809 let anchor = snapshot.anchor_after(s.head());
4810 s.map(|_| anchor)
4811 })
4812 .collect::<Vec<_>>()
4813 };
4814 buffer.edit(
4815 old_selections
4816 .iter()
4817 .map(|s| (s.start..s.end, text.clone())),
4818 autoindent_mode,
4819 cx,
4820 );
4821 anchors
4822 });
4823
4824 this.change_selections(Default::default(), window, cx, |s| {
4825 s.select_anchors(selection_anchors);
4826 });
4827
4828 cx.notify();
4829 });
4830 }
4831
4832 fn trigger_completion_on_input(
4833 &mut self,
4834 text: &str,
4835 trigger_in_words: bool,
4836 window: &mut Window,
4837 cx: &mut Context<Self>,
4838 ) {
4839 let completions_source = self
4840 .context_menu
4841 .borrow()
4842 .as_ref()
4843 .and_then(|menu| match menu {
4844 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4845 CodeContextMenu::CodeActions(_) => None,
4846 });
4847
4848 match completions_source {
4849 Some(CompletionsMenuSource::Words) => {
4850 self.show_word_completions(&ShowWordCompletions, window, cx)
4851 }
4852 Some(CompletionsMenuSource::Normal)
4853 | Some(CompletionsMenuSource::SnippetChoices)
4854 | None
4855 if self.is_completion_trigger(
4856 text,
4857 trigger_in_words,
4858 completions_source.is_some(),
4859 cx,
4860 ) =>
4861 {
4862 self.show_completions(
4863 &ShowCompletions {
4864 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4865 },
4866 window,
4867 cx,
4868 )
4869 }
4870 _ => {
4871 self.hide_context_menu(window, cx);
4872 }
4873 }
4874 }
4875
4876 fn is_completion_trigger(
4877 &self,
4878 text: &str,
4879 trigger_in_words: bool,
4880 menu_is_open: bool,
4881 cx: &mut Context<Self>,
4882 ) -> bool {
4883 let position = self.selections.newest_anchor().head();
4884 let multibuffer = self.buffer.read(cx);
4885 let Some(buffer) = position
4886 .buffer_id
4887 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4888 else {
4889 return false;
4890 };
4891
4892 if let Some(completion_provider) = &self.completion_provider {
4893 completion_provider.is_completion_trigger(
4894 &buffer,
4895 position.text_anchor,
4896 text,
4897 trigger_in_words,
4898 menu_is_open,
4899 cx,
4900 )
4901 } else {
4902 false
4903 }
4904 }
4905
4906 /// If any empty selections is touching the start of its innermost containing autoclose
4907 /// region, expand it to select the brackets.
4908 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4909 let selections = self.selections.all::<usize>(cx);
4910 let buffer = self.buffer.read(cx).read(cx);
4911 let new_selections = self
4912 .selections_with_autoclose_regions(selections, &buffer)
4913 .map(|(mut selection, region)| {
4914 if !selection.is_empty() {
4915 return selection;
4916 }
4917
4918 if let Some(region) = region {
4919 let mut range = region.range.to_offset(&buffer);
4920 if selection.start == range.start && range.start >= region.pair.start.len() {
4921 range.start -= region.pair.start.len();
4922 if buffer.contains_str_at(range.start, ®ion.pair.start)
4923 && buffer.contains_str_at(range.end, ®ion.pair.end)
4924 {
4925 range.end += region.pair.end.len();
4926 selection.start = range.start;
4927 selection.end = range.end;
4928
4929 return selection;
4930 }
4931 }
4932 }
4933
4934 let always_treat_brackets_as_autoclosed = buffer
4935 .language_settings_at(selection.start, cx)
4936 .always_treat_brackets_as_autoclosed;
4937
4938 if !always_treat_brackets_as_autoclosed {
4939 return selection;
4940 }
4941
4942 if let Some(scope) = buffer.language_scope_at(selection.start) {
4943 for (pair, enabled) in scope.brackets() {
4944 if !enabled || !pair.close {
4945 continue;
4946 }
4947
4948 if buffer.contains_str_at(selection.start, &pair.end) {
4949 let pair_start_len = pair.start.len();
4950 if buffer.contains_str_at(
4951 selection.start.saturating_sub(pair_start_len),
4952 &pair.start,
4953 ) {
4954 selection.start -= pair_start_len;
4955 selection.end += pair.end.len();
4956
4957 return selection;
4958 }
4959 }
4960 }
4961 }
4962
4963 selection
4964 })
4965 .collect();
4966
4967 drop(buffer);
4968 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4969 selections.select(new_selections)
4970 });
4971 }
4972
4973 /// Iterate the given selections, and for each one, find the smallest surrounding
4974 /// autoclose region. This uses the ordering of the selections and the autoclose
4975 /// regions to avoid repeated comparisons.
4976 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4977 &'a self,
4978 selections: impl IntoIterator<Item = Selection<D>>,
4979 buffer: &'a MultiBufferSnapshot,
4980 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4981 let mut i = 0;
4982 let mut regions = self.autoclose_regions.as_slice();
4983 selections.into_iter().map(move |selection| {
4984 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4985
4986 let mut enclosing = None;
4987 while let Some(pair_state) = regions.get(i) {
4988 if pair_state.range.end.to_offset(buffer) < range.start {
4989 regions = ®ions[i + 1..];
4990 i = 0;
4991 } else if pair_state.range.start.to_offset(buffer) > range.end {
4992 break;
4993 } else {
4994 if pair_state.selection_id == selection.id {
4995 enclosing = Some(pair_state);
4996 }
4997 i += 1;
4998 }
4999 }
5000
5001 (selection, enclosing)
5002 })
5003 }
5004
5005 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5006 fn invalidate_autoclose_regions(
5007 &mut self,
5008 mut selections: &[Selection<Anchor>],
5009 buffer: &MultiBufferSnapshot,
5010 ) {
5011 self.autoclose_regions.retain(|state| {
5012 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5013 return false;
5014 }
5015
5016 let mut i = 0;
5017 while let Some(selection) = selections.get(i) {
5018 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5019 selections = &selections[1..];
5020 continue;
5021 }
5022 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5023 break;
5024 }
5025 if selection.id == state.selection_id {
5026 return true;
5027 } else {
5028 i += 1;
5029 }
5030 }
5031 false
5032 });
5033 }
5034
5035 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5036 let offset = position.to_offset(buffer);
5037 let (word_range, kind) = buffer.surrounding_word(offset, true);
5038 if offset > word_range.start && kind == Some(CharKind::Word) {
5039 Some(
5040 buffer
5041 .text_for_range(word_range.start..offset)
5042 .collect::<String>(),
5043 )
5044 } else {
5045 None
5046 }
5047 }
5048
5049 pub fn toggle_inline_values(
5050 &mut self,
5051 _: &ToggleInlineValues,
5052 _: &mut Window,
5053 cx: &mut Context<Self>,
5054 ) {
5055 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5056
5057 self.refresh_inline_values(cx);
5058 }
5059
5060 pub fn toggle_inlay_hints(
5061 &mut self,
5062 _: &ToggleInlayHints,
5063 _: &mut Window,
5064 cx: &mut Context<Self>,
5065 ) {
5066 self.refresh_inlay_hints(
5067 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5068 cx,
5069 );
5070 }
5071
5072 pub fn inlay_hints_enabled(&self) -> bool {
5073 self.inlay_hint_cache.enabled
5074 }
5075
5076 pub fn inline_values_enabled(&self) -> bool {
5077 self.inline_value_cache.enabled
5078 }
5079
5080 #[cfg(any(test, feature = "test-support"))]
5081 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5082 self.display_map
5083 .read(cx)
5084 .current_inlays()
5085 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5086 .cloned()
5087 .collect()
5088 }
5089
5090 #[cfg(any(test, feature = "test-support"))]
5091 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5092 self.display_map
5093 .read(cx)
5094 .current_inlays()
5095 .cloned()
5096 .collect()
5097 }
5098
5099 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5100 if self.semantics_provider.is_none() || !self.mode.is_full() {
5101 return;
5102 }
5103
5104 let reason_description = reason.description();
5105 let ignore_debounce = matches!(
5106 reason,
5107 InlayHintRefreshReason::SettingsChange(_)
5108 | InlayHintRefreshReason::Toggle(_)
5109 | InlayHintRefreshReason::ExcerptsRemoved(_)
5110 | InlayHintRefreshReason::ModifiersChanged(_)
5111 );
5112 let (invalidate_cache, required_languages) = match reason {
5113 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5114 match self.inlay_hint_cache.modifiers_override(enabled) {
5115 Some(enabled) => {
5116 if enabled {
5117 (InvalidationStrategy::RefreshRequested, None)
5118 } else {
5119 self.splice_inlays(
5120 &self
5121 .visible_inlay_hints(cx)
5122 .iter()
5123 .map(|inlay| inlay.id)
5124 .collect::<Vec<InlayId>>(),
5125 Vec::new(),
5126 cx,
5127 );
5128 return;
5129 }
5130 }
5131 None => return,
5132 }
5133 }
5134 InlayHintRefreshReason::Toggle(enabled) => {
5135 if self.inlay_hint_cache.toggle(enabled) {
5136 if enabled {
5137 (InvalidationStrategy::RefreshRequested, None)
5138 } else {
5139 self.splice_inlays(
5140 &self
5141 .visible_inlay_hints(cx)
5142 .iter()
5143 .map(|inlay| inlay.id)
5144 .collect::<Vec<InlayId>>(),
5145 Vec::new(),
5146 cx,
5147 );
5148 return;
5149 }
5150 } else {
5151 return;
5152 }
5153 }
5154 InlayHintRefreshReason::SettingsChange(new_settings) => {
5155 match self.inlay_hint_cache.update_settings(
5156 &self.buffer,
5157 new_settings,
5158 self.visible_inlay_hints(cx),
5159 cx,
5160 ) {
5161 ControlFlow::Break(Some(InlaySplice {
5162 to_remove,
5163 to_insert,
5164 })) => {
5165 self.splice_inlays(&to_remove, to_insert, cx);
5166 return;
5167 }
5168 ControlFlow::Break(None) => return,
5169 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5170 }
5171 }
5172 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5173 if let Some(InlaySplice {
5174 to_remove,
5175 to_insert,
5176 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5177 {
5178 self.splice_inlays(&to_remove, to_insert, cx);
5179 }
5180 self.display_map.update(cx, |display_map, _| {
5181 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5182 });
5183 return;
5184 }
5185 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5186 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5187 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5188 }
5189 InlayHintRefreshReason::RefreshRequested => {
5190 (InvalidationStrategy::RefreshRequested, None)
5191 }
5192 };
5193
5194 if let Some(InlaySplice {
5195 to_remove,
5196 to_insert,
5197 }) = self.inlay_hint_cache.spawn_hint_refresh(
5198 reason_description,
5199 self.visible_excerpts(required_languages.as_ref(), cx),
5200 invalidate_cache,
5201 ignore_debounce,
5202 cx,
5203 ) {
5204 self.splice_inlays(&to_remove, to_insert, cx);
5205 }
5206 }
5207
5208 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5209 self.display_map
5210 .read(cx)
5211 .current_inlays()
5212 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5213 .cloned()
5214 .collect()
5215 }
5216
5217 pub fn visible_excerpts(
5218 &self,
5219 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5220 cx: &mut Context<Editor>,
5221 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5222 let Some(project) = self.project() else {
5223 return HashMap::default();
5224 };
5225 let project = project.read(cx);
5226 let multi_buffer = self.buffer().read(cx);
5227 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5228 let multi_buffer_visible_start = self
5229 .scroll_manager
5230 .anchor()
5231 .anchor
5232 .to_point(&multi_buffer_snapshot);
5233 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5234 multi_buffer_visible_start
5235 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5236 Bias::Left,
5237 );
5238 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5239 multi_buffer_snapshot
5240 .range_to_buffer_ranges(multi_buffer_visible_range)
5241 .into_iter()
5242 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5243 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5244 let buffer_file = project::File::from_dyn(buffer.file())?;
5245 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5246 let worktree_entry = buffer_worktree
5247 .read(cx)
5248 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5249 if worktree_entry.is_ignored {
5250 return None;
5251 }
5252
5253 let language = buffer.language()?;
5254 if let Some(restrict_to_languages) = restrict_to_languages
5255 && !restrict_to_languages.contains(language)
5256 {
5257 return None;
5258 }
5259 Some((
5260 excerpt_id,
5261 (
5262 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5263 buffer.version().clone(),
5264 excerpt_visible_range,
5265 ),
5266 ))
5267 })
5268 .collect()
5269 }
5270
5271 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5272 TextLayoutDetails {
5273 text_system: window.text_system().clone(),
5274 editor_style: self.style.clone().unwrap(),
5275 rem_size: window.rem_size(),
5276 scroll_anchor: self.scroll_manager.anchor(),
5277 visible_rows: self.visible_line_count(),
5278 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5279 }
5280 }
5281
5282 pub fn splice_inlays(
5283 &self,
5284 to_remove: &[InlayId],
5285 to_insert: Vec<Inlay>,
5286 cx: &mut Context<Self>,
5287 ) {
5288 self.display_map.update(cx, |display_map, cx| {
5289 display_map.splice_inlays(to_remove, to_insert, cx)
5290 });
5291 cx.notify();
5292 }
5293
5294 fn trigger_on_type_formatting(
5295 &self,
5296 input: String,
5297 window: &mut Window,
5298 cx: &mut Context<Self>,
5299 ) -> Option<Task<Result<()>>> {
5300 if input.len() != 1 {
5301 return None;
5302 }
5303
5304 let project = self.project()?;
5305 let position = self.selections.newest_anchor().head();
5306 let (buffer, buffer_position) = self
5307 .buffer
5308 .read(cx)
5309 .text_anchor_for_position(position, cx)?;
5310
5311 let settings = language_settings::language_settings(
5312 buffer
5313 .read(cx)
5314 .language_at(buffer_position)
5315 .map(|l| l.name()),
5316 buffer.read(cx).file(),
5317 cx,
5318 );
5319 if !settings.use_on_type_format {
5320 return None;
5321 }
5322
5323 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5324 // hence we do LSP request & edit on host side only — add formats to host's history.
5325 let push_to_lsp_host_history = true;
5326 // If this is not the host, append its history with new edits.
5327 let push_to_client_history = project.read(cx).is_via_collab();
5328
5329 let on_type_formatting = project.update(cx, |project, cx| {
5330 project.on_type_format(
5331 buffer.clone(),
5332 buffer_position,
5333 input,
5334 push_to_lsp_host_history,
5335 cx,
5336 )
5337 });
5338 Some(cx.spawn_in(window, async move |editor, cx| {
5339 if let Some(transaction) = on_type_formatting.await? {
5340 if push_to_client_history {
5341 buffer
5342 .update(cx, |buffer, _| {
5343 buffer.push_transaction(transaction, Instant::now());
5344 buffer.finalize_last_transaction();
5345 })
5346 .ok();
5347 }
5348 editor.update(cx, |editor, cx| {
5349 editor.refresh_document_highlights(cx);
5350 })?;
5351 }
5352 Ok(())
5353 }))
5354 }
5355
5356 pub fn show_word_completions(
5357 &mut self,
5358 _: &ShowWordCompletions,
5359 window: &mut Window,
5360 cx: &mut Context<Self>,
5361 ) {
5362 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5363 }
5364
5365 pub fn show_completions(
5366 &mut self,
5367 options: &ShowCompletions,
5368 window: &mut Window,
5369 cx: &mut Context<Self>,
5370 ) {
5371 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5372 }
5373
5374 fn open_or_update_completions_menu(
5375 &mut self,
5376 requested_source: Option<CompletionsMenuSource>,
5377 trigger: Option<&str>,
5378 window: &mut Window,
5379 cx: &mut Context<Self>,
5380 ) {
5381 if self.pending_rename.is_some() {
5382 return;
5383 }
5384
5385 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5386
5387 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5388 // inserted and selected. To handle that case, the start of the selection is used so that
5389 // the menu starts with all choices.
5390 let position = self
5391 .selections
5392 .newest_anchor()
5393 .start
5394 .bias_right(&multibuffer_snapshot);
5395 if position.diff_base_anchor.is_some() {
5396 return;
5397 }
5398 let (buffer, buffer_position) =
5399 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5400 output
5401 } else {
5402 return;
5403 };
5404 let buffer_snapshot = buffer.read(cx).snapshot();
5405
5406 let query: Option<Arc<String>> =
5407 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5408
5409 drop(multibuffer_snapshot);
5410
5411 let provider = match requested_source {
5412 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5413 Some(CompletionsMenuSource::Words) => None,
5414 Some(CompletionsMenuSource::SnippetChoices) => {
5415 log::error!("bug: SnippetChoices requested_source is not handled");
5416 None
5417 }
5418 };
5419
5420 let sort_completions = provider
5421 .as_ref()
5422 .is_some_and(|provider| provider.sort_completions());
5423
5424 let filter_completions = provider
5425 .as_ref()
5426 .is_none_or(|provider| provider.filter_completions());
5427
5428 let trigger_kind = match trigger {
5429 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5430 CompletionTriggerKind::TRIGGER_CHARACTER
5431 }
5432 _ => CompletionTriggerKind::INVOKED,
5433 };
5434 let completion_context = CompletionContext {
5435 trigger_character: trigger.and_then(|trigger| {
5436 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5437 Some(String::from(trigger))
5438 } else {
5439 None
5440 }
5441 }),
5442 trigger_kind,
5443 };
5444
5445 // Hide the current completions menu when a trigger char is typed. Without this, cached
5446 // completions from before the trigger char may be reused (#32774). Snippet choices could
5447 // involve trigger chars, so this is skipped in that case.
5448 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5449 {
5450 let menu_is_open = matches!(
5451 self.context_menu.borrow().as_ref(),
5452 Some(CodeContextMenu::Completions(_))
5453 );
5454 if menu_is_open {
5455 self.hide_context_menu(window, cx);
5456 }
5457 }
5458
5459 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5460 if filter_completions {
5461 menu.filter(query.clone(), provider.clone(), window, cx);
5462 }
5463 // When `is_incomplete` is false, no need to re-query completions when the current query
5464 // is a suffix of the initial query.
5465 if !menu.is_incomplete {
5466 // If the new query is a suffix of the old query (typing more characters) and
5467 // the previous result was complete, the existing completions can be filtered.
5468 //
5469 // Note that this is always true for snippet completions.
5470 let query_matches = match (&menu.initial_query, &query) {
5471 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5472 (None, _) => true,
5473 _ => false,
5474 };
5475 if query_matches {
5476 let position_matches = if menu.initial_position == position {
5477 true
5478 } else {
5479 let snapshot = self.buffer.read(cx).read(cx);
5480 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5481 };
5482 if position_matches {
5483 return;
5484 }
5485 }
5486 }
5487 };
5488
5489 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5490 buffer_snapshot.surrounding_word(buffer_position, false)
5491 {
5492 let word_to_exclude = buffer_snapshot
5493 .text_for_range(word_range.clone())
5494 .collect::<String>();
5495 (
5496 buffer_snapshot.anchor_before(word_range.start)
5497 ..buffer_snapshot.anchor_after(buffer_position),
5498 Some(word_to_exclude),
5499 )
5500 } else {
5501 (buffer_position..buffer_position, None)
5502 };
5503
5504 let language = buffer_snapshot
5505 .language_at(buffer_position)
5506 .map(|language| language.name());
5507
5508 let completion_settings =
5509 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5510
5511 let show_completion_documentation = buffer_snapshot
5512 .settings_at(buffer_position, cx)
5513 .show_completion_documentation;
5514
5515 // The document can be large, so stay in reasonable bounds when searching for words,
5516 // otherwise completion pop-up might be slow to appear.
5517 const WORD_LOOKUP_ROWS: u32 = 5_000;
5518 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5519 let min_word_search = buffer_snapshot.clip_point(
5520 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5521 Bias::Left,
5522 );
5523 let max_word_search = buffer_snapshot.clip_point(
5524 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5525 Bias::Right,
5526 );
5527 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5528 ..buffer_snapshot.point_to_offset(max_word_search);
5529
5530 let skip_digits = query
5531 .as_ref()
5532 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5533
5534 let (mut words, provider_responses) = match &provider {
5535 Some(provider) => {
5536 let provider_responses = provider.completions(
5537 position.excerpt_id,
5538 &buffer,
5539 buffer_position,
5540 completion_context,
5541 window,
5542 cx,
5543 );
5544
5545 let words = match completion_settings.words {
5546 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5547 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5548 .background_spawn(async move {
5549 buffer_snapshot.words_in_range(WordsQuery {
5550 fuzzy_contents: None,
5551 range: word_search_range,
5552 skip_digits,
5553 })
5554 }),
5555 };
5556
5557 (words, provider_responses)
5558 }
5559 None => (
5560 cx.background_spawn(async move {
5561 buffer_snapshot.words_in_range(WordsQuery {
5562 fuzzy_contents: None,
5563 range: word_search_range,
5564 skip_digits,
5565 })
5566 }),
5567 Task::ready(Ok(Vec::new())),
5568 ),
5569 };
5570
5571 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5572
5573 let id = post_inc(&mut self.next_completion_id);
5574 let task = cx.spawn_in(window, async move |editor, cx| {
5575 let Ok(()) = editor.update(cx, |this, _| {
5576 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5577 }) else {
5578 return;
5579 };
5580
5581 // TODO: Ideally completions from different sources would be selectively re-queried, so
5582 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5583 let mut completions = Vec::new();
5584 let mut is_incomplete = false;
5585 if let Some(provider_responses) = provider_responses.await.log_err()
5586 && !provider_responses.is_empty()
5587 {
5588 for response in provider_responses {
5589 completions.extend(response.completions);
5590 is_incomplete = is_incomplete || response.is_incomplete;
5591 }
5592 if completion_settings.words == WordsCompletionMode::Fallback {
5593 words = Task::ready(BTreeMap::default());
5594 }
5595 }
5596
5597 let mut words = words.await;
5598 if let Some(word_to_exclude) = &word_to_exclude {
5599 words.remove(word_to_exclude);
5600 }
5601 for lsp_completion in &completions {
5602 words.remove(&lsp_completion.new_text);
5603 }
5604 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5605 replace_range: word_replace_range.clone(),
5606 new_text: word.clone(),
5607 label: CodeLabel::plain(word, None),
5608 icon_path: None,
5609 documentation: None,
5610 source: CompletionSource::BufferWord {
5611 word_range,
5612 resolved: false,
5613 },
5614 insert_text_mode: Some(InsertTextMode::AS_IS),
5615 confirm: None,
5616 }));
5617
5618 let menu = if completions.is_empty() {
5619 None
5620 } else {
5621 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5622 let languages = editor
5623 .workspace
5624 .as_ref()
5625 .and_then(|(workspace, _)| workspace.upgrade())
5626 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5627 let menu = CompletionsMenu::new(
5628 id,
5629 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5630 sort_completions,
5631 show_completion_documentation,
5632 position,
5633 query.clone(),
5634 is_incomplete,
5635 buffer.clone(),
5636 completions.into(),
5637 snippet_sort_order,
5638 languages,
5639 language,
5640 cx,
5641 );
5642
5643 let query = if filter_completions { query } else { None };
5644 let matches_task = if let Some(query) = query {
5645 menu.do_async_filtering(query, cx)
5646 } else {
5647 Task::ready(menu.unfiltered_matches())
5648 };
5649 (menu, matches_task)
5650 }) else {
5651 return;
5652 };
5653
5654 let matches = matches_task.await;
5655
5656 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5657 // Newer menu already set, so exit.
5658 if let Some(CodeContextMenu::Completions(prev_menu)) =
5659 editor.context_menu.borrow().as_ref()
5660 && prev_menu.id > id
5661 {
5662 return;
5663 };
5664
5665 // Only valid to take prev_menu because it the new menu is immediately set
5666 // below, or the menu is hidden.
5667 if let Some(CodeContextMenu::Completions(prev_menu)) =
5668 editor.context_menu.borrow_mut().take()
5669 {
5670 let position_matches =
5671 if prev_menu.initial_position == menu.initial_position {
5672 true
5673 } else {
5674 let snapshot = editor.buffer.read(cx).read(cx);
5675 prev_menu.initial_position.to_offset(&snapshot)
5676 == menu.initial_position.to_offset(&snapshot)
5677 };
5678 if position_matches {
5679 // Preserve markdown cache before `set_filter_results` because it will
5680 // try to populate the documentation cache.
5681 menu.preserve_markdown_cache(prev_menu);
5682 }
5683 };
5684
5685 menu.set_filter_results(matches, provider, window, cx);
5686 }) else {
5687 return;
5688 };
5689
5690 menu.visible().then_some(menu)
5691 };
5692
5693 editor
5694 .update_in(cx, |editor, window, cx| {
5695 if editor.focus_handle.is_focused(window)
5696 && let Some(menu) = menu
5697 {
5698 *editor.context_menu.borrow_mut() =
5699 Some(CodeContextMenu::Completions(menu));
5700
5701 crate::hover_popover::hide_hover(editor, cx);
5702 if editor.show_edit_predictions_in_menu() {
5703 editor.update_visible_edit_prediction(window, cx);
5704 } else {
5705 editor.discard_edit_prediction(false, cx);
5706 }
5707
5708 cx.notify();
5709 return;
5710 }
5711
5712 if editor.completion_tasks.len() <= 1 {
5713 // If there are no more completion tasks and the last menu was empty, we should hide it.
5714 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5715 // If it was already hidden and we don't show edit predictions in the menu,
5716 // we should also show the edit prediction when available.
5717 if was_hidden && editor.show_edit_predictions_in_menu() {
5718 editor.update_visible_edit_prediction(window, cx);
5719 }
5720 }
5721 })
5722 .ok();
5723 });
5724
5725 self.completion_tasks.push((id, task));
5726 }
5727
5728 #[cfg(feature = "test-support")]
5729 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5730 let menu = self.context_menu.borrow();
5731 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5732 let completions = menu.completions.borrow();
5733 Some(completions.to_vec())
5734 } else {
5735 None
5736 }
5737 }
5738
5739 pub fn with_completions_menu_matching_id<R>(
5740 &self,
5741 id: CompletionId,
5742 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5743 ) -> R {
5744 let mut context_menu = self.context_menu.borrow_mut();
5745 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5746 return f(None);
5747 };
5748 if completions_menu.id != id {
5749 return f(None);
5750 }
5751 f(Some(completions_menu))
5752 }
5753
5754 pub fn confirm_completion(
5755 &mut self,
5756 action: &ConfirmCompletion,
5757 window: &mut Window,
5758 cx: &mut Context<Self>,
5759 ) -> Option<Task<Result<()>>> {
5760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5761 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5762 }
5763
5764 pub fn confirm_completion_insert(
5765 &mut self,
5766 _: &ConfirmCompletionInsert,
5767 window: &mut Window,
5768 cx: &mut Context<Self>,
5769 ) -> Option<Task<Result<()>>> {
5770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5771 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5772 }
5773
5774 pub fn confirm_completion_replace(
5775 &mut self,
5776 _: &ConfirmCompletionReplace,
5777 window: &mut Window,
5778 cx: &mut Context<Self>,
5779 ) -> Option<Task<Result<()>>> {
5780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5781 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5782 }
5783
5784 pub fn compose_completion(
5785 &mut self,
5786 action: &ComposeCompletion,
5787 window: &mut Window,
5788 cx: &mut Context<Self>,
5789 ) -> Option<Task<Result<()>>> {
5790 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5791 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5792 }
5793
5794 fn do_completion(
5795 &mut self,
5796 item_ix: Option<usize>,
5797 intent: CompletionIntent,
5798 window: &mut Window,
5799 cx: &mut Context<Editor>,
5800 ) -> Option<Task<Result<()>>> {
5801 use language::ToOffset as _;
5802
5803 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5804 else {
5805 return None;
5806 };
5807
5808 let candidate_id = {
5809 let entries = completions_menu.entries.borrow();
5810 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5811 if self.show_edit_predictions_in_menu() {
5812 self.discard_edit_prediction(true, cx);
5813 }
5814 mat.candidate_id
5815 };
5816
5817 let completion = completions_menu
5818 .completions
5819 .borrow()
5820 .get(candidate_id)?
5821 .clone();
5822 cx.stop_propagation();
5823
5824 let buffer_handle = completions_menu.buffer.clone();
5825
5826 let CompletionEdit {
5827 new_text,
5828 snippet,
5829 replace_range,
5830 } = process_completion_for_edit(
5831 &completion,
5832 intent,
5833 &buffer_handle,
5834 &completions_menu.initial_position.text_anchor,
5835 cx,
5836 );
5837
5838 let buffer = buffer_handle.read(cx);
5839 let snapshot = self.buffer.read(cx).snapshot(cx);
5840 let newest_anchor = self.selections.newest_anchor();
5841 let replace_range_multibuffer = {
5842 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5843 let multibuffer_anchor = snapshot
5844 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5845 .unwrap()
5846 ..snapshot
5847 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5848 .unwrap();
5849 multibuffer_anchor.start.to_offset(&snapshot)
5850 ..multibuffer_anchor.end.to_offset(&snapshot)
5851 };
5852 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5853 return None;
5854 }
5855
5856 let old_text = buffer
5857 .text_for_range(replace_range.clone())
5858 .collect::<String>();
5859 let lookbehind = newest_anchor
5860 .start
5861 .text_anchor
5862 .to_offset(buffer)
5863 .saturating_sub(replace_range.start);
5864 let lookahead = replace_range
5865 .end
5866 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5867 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5868 let suffix = &old_text[lookbehind.min(old_text.len())..];
5869
5870 let selections = self.selections.all::<usize>(cx);
5871 let mut ranges = Vec::new();
5872 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5873
5874 for selection in &selections {
5875 let range = if selection.id == newest_anchor.id {
5876 replace_range_multibuffer.clone()
5877 } else {
5878 let mut range = selection.range();
5879
5880 // if prefix is present, don't duplicate it
5881 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5882 range.start = range.start.saturating_sub(lookbehind);
5883
5884 // if suffix is also present, mimic the newest cursor and replace it
5885 if selection.id != newest_anchor.id
5886 && snapshot.contains_str_at(range.end, suffix)
5887 {
5888 range.end += lookahead;
5889 }
5890 }
5891 range
5892 };
5893
5894 ranges.push(range.clone());
5895
5896 if !self.linked_edit_ranges.is_empty() {
5897 let start_anchor = snapshot.anchor_before(range.start);
5898 let end_anchor = snapshot.anchor_after(range.end);
5899 if let Some(ranges) = self
5900 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5901 {
5902 for (buffer, edits) in ranges {
5903 linked_edits
5904 .entry(buffer.clone())
5905 .or_default()
5906 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5907 }
5908 }
5909 }
5910 }
5911
5912 let common_prefix_len = old_text
5913 .chars()
5914 .zip(new_text.chars())
5915 .take_while(|(a, b)| a == b)
5916 .map(|(a, _)| a.len_utf8())
5917 .sum::<usize>();
5918
5919 cx.emit(EditorEvent::InputHandled {
5920 utf16_range_to_replace: None,
5921 text: new_text[common_prefix_len..].into(),
5922 });
5923
5924 self.transact(window, cx, |editor, window, cx| {
5925 if let Some(mut snippet) = snippet {
5926 snippet.text = new_text.to_string();
5927 editor
5928 .insert_snippet(&ranges, snippet, window, cx)
5929 .log_err();
5930 } else {
5931 editor.buffer.update(cx, |multi_buffer, cx| {
5932 let auto_indent = match completion.insert_text_mode {
5933 Some(InsertTextMode::AS_IS) => None,
5934 _ => editor.autoindent_mode.clone(),
5935 };
5936 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5937 multi_buffer.edit(edits, auto_indent, cx);
5938 });
5939 }
5940 for (buffer, edits) in linked_edits {
5941 buffer.update(cx, |buffer, cx| {
5942 let snapshot = buffer.snapshot();
5943 let edits = edits
5944 .into_iter()
5945 .map(|(range, text)| {
5946 use text::ToPoint as TP;
5947 let end_point = TP::to_point(&range.end, &snapshot);
5948 let start_point = TP::to_point(&range.start, &snapshot);
5949 (start_point..end_point, text)
5950 })
5951 .sorted_by_key(|(range, _)| range.start);
5952 buffer.edit(edits, None, cx);
5953 })
5954 }
5955
5956 editor.refresh_edit_prediction(true, false, window, cx);
5957 });
5958 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5959
5960 let show_new_completions_on_confirm = completion
5961 .confirm
5962 .as_ref()
5963 .is_some_and(|confirm| confirm(intent, window, cx));
5964 if show_new_completions_on_confirm {
5965 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5966 }
5967
5968 let provider = self.completion_provider.as_ref()?;
5969 drop(completion);
5970 let apply_edits = provider.apply_additional_edits_for_completion(
5971 buffer_handle,
5972 completions_menu.completions.clone(),
5973 candidate_id,
5974 true,
5975 cx,
5976 );
5977
5978 let editor_settings = EditorSettings::get_global(cx);
5979 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5980 // After the code completion is finished, users often want to know what signatures are needed.
5981 // so we should automatically call signature_help
5982 self.show_signature_help(&ShowSignatureHelp, window, cx);
5983 }
5984
5985 Some(cx.foreground_executor().spawn(async move {
5986 apply_edits.await?;
5987 Ok(())
5988 }))
5989 }
5990
5991 pub fn toggle_code_actions(
5992 &mut self,
5993 action: &ToggleCodeActions,
5994 window: &mut Window,
5995 cx: &mut Context<Self>,
5996 ) {
5997 let quick_launch = action.quick_launch;
5998 let mut context_menu = self.context_menu.borrow_mut();
5999 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6000 if code_actions.deployed_from == action.deployed_from {
6001 // Toggle if we're selecting the same one
6002 *context_menu = None;
6003 cx.notify();
6004 return;
6005 } else {
6006 // Otherwise, clear it and start a new one
6007 *context_menu = None;
6008 cx.notify();
6009 }
6010 }
6011 drop(context_menu);
6012 let snapshot = self.snapshot(window, cx);
6013 let deployed_from = action.deployed_from.clone();
6014 let action = action.clone();
6015 self.completion_tasks.clear();
6016 self.discard_edit_prediction(false, cx);
6017
6018 let multibuffer_point = match &action.deployed_from {
6019 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6020 DisplayPoint::new(*row, 0).to_point(&snapshot)
6021 }
6022 _ => self.selections.newest::<Point>(cx).head(),
6023 };
6024 let Some((buffer, buffer_row)) = snapshot
6025 .buffer_snapshot
6026 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6027 .and_then(|(buffer_snapshot, range)| {
6028 self.buffer()
6029 .read(cx)
6030 .buffer(buffer_snapshot.remote_id())
6031 .map(|buffer| (buffer, range.start.row))
6032 })
6033 else {
6034 return;
6035 };
6036 let buffer_id = buffer.read(cx).remote_id();
6037 let tasks = self
6038 .tasks
6039 .get(&(buffer_id, buffer_row))
6040 .map(|t| Arc::new(t.to_owned()));
6041
6042 if !self.focus_handle.is_focused(window) {
6043 return;
6044 }
6045 let project = self.project.clone();
6046
6047 let code_actions_task = match deployed_from {
6048 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6049 _ => self.code_actions(buffer_row, window, cx),
6050 };
6051
6052 let runnable_task = match deployed_from {
6053 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6054 _ => {
6055 let mut task_context_task = Task::ready(None);
6056 if let Some(tasks) = &tasks
6057 && let Some(project) = project
6058 {
6059 task_context_task =
6060 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6061 }
6062
6063 cx.spawn_in(window, {
6064 let buffer = buffer.clone();
6065 async move |editor, cx| {
6066 let task_context = task_context_task.await;
6067
6068 let resolved_tasks =
6069 tasks
6070 .zip(task_context.clone())
6071 .map(|(tasks, task_context)| ResolvedTasks {
6072 templates: tasks.resolve(&task_context).collect(),
6073 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6074 multibuffer_point.row,
6075 tasks.column,
6076 )),
6077 });
6078 let debug_scenarios = editor
6079 .update(cx, |editor, cx| {
6080 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6081 })?
6082 .await;
6083 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6084 }
6085 })
6086 }
6087 };
6088
6089 cx.spawn_in(window, async move |editor, cx| {
6090 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6091 let code_actions = code_actions_task.await;
6092 let spawn_straight_away = quick_launch
6093 && resolved_tasks
6094 .as_ref()
6095 .is_some_and(|tasks| tasks.templates.len() == 1)
6096 && code_actions
6097 .as_ref()
6098 .is_none_or(|actions| actions.is_empty())
6099 && debug_scenarios.is_empty();
6100
6101 editor.update_in(cx, |editor, window, cx| {
6102 crate::hover_popover::hide_hover(editor, cx);
6103 let actions = CodeActionContents::new(
6104 resolved_tasks,
6105 code_actions,
6106 debug_scenarios,
6107 task_context.unwrap_or_default(),
6108 );
6109
6110 // Don't show the menu if there are no actions available
6111 if actions.is_empty() {
6112 cx.notify();
6113 return Task::ready(Ok(()));
6114 }
6115
6116 *editor.context_menu.borrow_mut() =
6117 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6118 buffer,
6119 actions,
6120 selected_item: Default::default(),
6121 scroll_handle: UniformListScrollHandle::default(),
6122 deployed_from,
6123 }));
6124 cx.notify();
6125 if spawn_straight_away
6126 && let Some(task) = editor.confirm_code_action(
6127 &ConfirmCodeAction { item_ix: Some(0) },
6128 window,
6129 cx,
6130 )
6131 {
6132 return task;
6133 }
6134
6135 Task::ready(Ok(()))
6136 })
6137 })
6138 .detach_and_log_err(cx);
6139 }
6140
6141 fn debug_scenarios(
6142 &mut self,
6143 resolved_tasks: &Option<ResolvedTasks>,
6144 buffer: &Entity<Buffer>,
6145 cx: &mut App,
6146 ) -> Task<Vec<task::DebugScenario>> {
6147 maybe!({
6148 let project = self.project()?;
6149 let dap_store = project.read(cx).dap_store();
6150 let mut scenarios = vec![];
6151 let resolved_tasks = resolved_tasks.as_ref()?;
6152 let buffer = buffer.read(cx);
6153 let language = buffer.language()?;
6154 let file = buffer.file();
6155 let debug_adapter = language_settings(language.name().into(), file, cx)
6156 .debuggers
6157 .first()
6158 .map(SharedString::from)
6159 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6160
6161 dap_store.update(cx, |dap_store, cx| {
6162 for (_, task) in &resolved_tasks.templates {
6163 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6164 task.original_task().clone(),
6165 debug_adapter.clone().into(),
6166 task.display_label().to_owned().into(),
6167 cx,
6168 );
6169 scenarios.push(maybe_scenario);
6170 }
6171 });
6172 Some(cx.background_spawn(async move {
6173 futures::future::join_all(scenarios)
6174 .await
6175 .into_iter()
6176 .flatten()
6177 .collect::<Vec<_>>()
6178 }))
6179 })
6180 .unwrap_or_else(|| Task::ready(vec![]))
6181 }
6182
6183 fn code_actions(
6184 &mut self,
6185 buffer_row: u32,
6186 window: &mut Window,
6187 cx: &mut Context<Self>,
6188 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6189 let mut task = self.code_actions_task.take();
6190 cx.spawn_in(window, async move |editor, cx| {
6191 while let Some(prev_task) = task {
6192 prev_task.await.log_err();
6193 task = editor
6194 .update(cx, |this, _| this.code_actions_task.take())
6195 .ok()?;
6196 }
6197
6198 editor
6199 .update(cx, |editor, cx| {
6200 editor
6201 .available_code_actions
6202 .clone()
6203 .and_then(|(location, code_actions)| {
6204 let snapshot = location.buffer.read(cx).snapshot();
6205 let point_range = location.range.to_point(&snapshot);
6206 let point_range = point_range.start.row..=point_range.end.row;
6207 if point_range.contains(&buffer_row) {
6208 Some(code_actions)
6209 } else {
6210 None
6211 }
6212 })
6213 })
6214 .ok()
6215 .flatten()
6216 })
6217 }
6218
6219 pub fn confirm_code_action(
6220 &mut self,
6221 action: &ConfirmCodeAction,
6222 window: &mut Window,
6223 cx: &mut Context<Self>,
6224 ) -> Option<Task<Result<()>>> {
6225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6226
6227 let actions_menu =
6228 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6229 menu
6230 } else {
6231 return None;
6232 };
6233
6234 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6235 let action = actions_menu.actions.get(action_ix)?;
6236 let title = action.label();
6237 let buffer = actions_menu.buffer;
6238 let workspace = self.workspace()?;
6239
6240 match action {
6241 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6242 workspace.update(cx, |workspace, cx| {
6243 workspace.schedule_resolved_task(
6244 task_source_kind,
6245 resolved_task,
6246 false,
6247 window,
6248 cx,
6249 );
6250
6251 Some(Task::ready(Ok(())))
6252 })
6253 }
6254 CodeActionsItem::CodeAction {
6255 excerpt_id,
6256 action,
6257 provider,
6258 } => {
6259 let apply_code_action =
6260 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6261 let workspace = workspace.downgrade();
6262 Some(cx.spawn_in(window, async move |editor, cx| {
6263 let project_transaction = apply_code_action.await?;
6264 Self::open_project_transaction(
6265 &editor,
6266 workspace,
6267 project_transaction,
6268 title,
6269 cx,
6270 )
6271 .await
6272 }))
6273 }
6274 CodeActionsItem::DebugScenario(scenario) => {
6275 let context = actions_menu.actions.context.clone();
6276
6277 workspace.update(cx, |workspace, cx| {
6278 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6279 workspace.start_debug_session(
6280 scenario,
6281 context,
6282 Some(buffer),
6283 None,
6284 window,
6285 cx,
6286 );
6287 });
6288 Some(Task::ready(Ok(())))
6289 }
6290 }
6291 }
6292
6293 pub async fn open_project_transaction(
6294 this: &WeakEntity<Editor>,
6295 workspace: WeakEntity<Workspace>,
6296 transaction: ProjectTransaction,
6297 title: String,
6298 cx: &mut AsyncWindowContext,
6299 ) -> Result<()> {
6300 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6301 cx.update(|_, cx| {
6302 entries.sort_unstable_by_key(|(buffer, _)| {
6303 buffer.read(cx).file().map(|f| f.path().clone())
6304 });
6305 })?;
6306
6307 // If the project transaction's edits are all contained within this editor, then
6308 // avoid opening a new editor to display them.
6309
6310 if let Some((buffer, transaction)) = entries.first() {
6311 if entries.len() == 1 {
6312 let excerpt = this.update(cx, |editor, cx| {
6313 editor
6314 .buffer()
6315 .read(cx)
6316 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6317 })?;
6318 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6319 && excerpted_buffer == *buffer
6320 {
6321 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6322 let excerpt_range = excerpt_range.to_offset(buffer);
6323 buffer
6324 .edited_ranges_for_transaction::<usize>(transaction)
6325 .all(|range| {
6326 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6327 })
6328 })?;
6329
6330 if all_edits_within_excerpt {
6331 return Ok(());
6332 }
6333 }
6334 }
6335 } else {
6336 return Ok(());
6337 }
6338
6339 let mut ranges_to_highlight = Vec::new();
6340 let excerpt_buffer = cx.new(|cx| {
6341 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6342 for (buffer_handle, transaction) in &entries {
6343 let edited_ranges = buffer_handle
6344 .read(cx)
6345 .edited_ranges_for_transaction::<Point>(transaction)
6346 .collect::<Vec<_>>();
6347 let (ranges, _) = multibuffer.set_excerpts_for_path(
6348 PathKey::for_buffer(buffer_handle, cx),
6349 buffer_handle.clone(),
6350 edited_ranges,
6351 DEFAULT_MULTIBUFFER_CONTEXT,
6352 cx,
6353 );
6354
6355 ranges_to_highlight.extend(ranges);
6356 }
6357 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6358 multibuffer
6359 })?;
6360
6361 workspace.update_in(cx, |workspace, window, cx| {
6362 let project = workspace.project().clone();
6363 let editor =
6364 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6365 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6366 editor.update(cx, |editor, cx| {
6367 editor.highlight_background::<Self>(
6368 &ranges_to_highlight,
6369 |theme| theme.colors().editor_highlighted_line_background,
6370 cx,
6371 );
6372 });
6373 })?;
6374
6375 Ok(())
6376 }
6377
6378 pub fn clear_code_action_providers(&mut self) {
6379 self.code_action_providers.clear();
6380 self.available_code_actions.take();
6381 }
6382
6383 pub fn add_code_action_provider(
6384 &mut self,
6385 provider: Rc<dyn CodeActionProvider>,
6386 window: &mut Window,
6387 cx: &mut Context<Self>,
6388 ) {
6389 if self
6390 .code_action_providers
6391 .iter()
6392 .any(|existing_provider| existing_provider.id() == provider.id())
6393 {
6394 return;
6395 }
6396
6397 self.code_action_providers.push(provider);
6398 self.refresh_code_actions(window, cx);
6399 }
6400
6401 pub fn remove_code_action_provider(
6402 &mut self,
6403 id: Arc<str>,
6404 window: &mut Window,
6405 cx: &mut Context<Self>,
6406 ) {
6407 self.code_action_providers
6408 .retain(|provider| provider.id() != id);
6409 self.refresh_code_actions(window, cx);
6410 }
6411
6412 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6413 !self.code_action_providers.is_empty()
6414 && EditorSettings::get_global(cx).toolbar.code_actions
6415 }
6416
6417 pub fn has_available_code_actions(&self) -> bool {
6418 self.available_code_actions
6419 .as_ref()
6420 .is_some_and(|(_, actions)| !actions.is_empty())
6421 }
6422
6423 fn render_inline_code_actions(
6424 &self,
6425 icon_size: ui::IconSize,
6426 display_row: DisplayRow,
6427 is_active: bool,
6428 cx: &mut Context<Self>,
6429 ) -> AnyElement {
6430 let show_tooltip = !self.context_menu_visible();
6431 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6432 .icon_size(icon_size)
6433 .shape(ui::IconButtonShape::Square)
6434 .icon_color(ui::Color::Hidden)
6435 .toggle_state(is_active)
6436 .when(show_tooltip, |this| {
6437 this.tooltip({
6438 let focus_handle = self.focus_handle.clone();
6439 move |window, cx| {
6440 Tooltip::for_action_in(
6441 "Toggle Code Actions",
6442 &ToggleCodeActions {
6443 deployed_from: None,
6444 quick_launch: false,
6445 },
6446 &focus_handle,
6447 window,
6448 cx,
6449 )
6450 }
6451 })
6452 })
6453 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6454 window.focus(&editor.focus_handle(cx));
6455 editor.toggle_code_actions(
6456 &crate::actions::ToggleCodeActions {
6457 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6458 display_row,
6459 )),
6460 quick_launch: false,
6461 },
6462 window,
6463 cx,
6464 );
6465 }))
6466 .into_any_element()
6467 }
6468
6469 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6470 &self.context_menu
6471 }
6472
6473 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6474 let newest_selection = self.selections.newest_anchor().clone();
6475 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6476 let buffer = self.buffer.read(cx);
6477 if newest_selection.head().diff_base_anchor.is_some() {
6478 return None;
6479 }
6480 let (start_buffer, start) =
6481 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6482 let (end_buffer, end) =
6483 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6484 if start_buffer != end_buffer {
6485 return None;
6486 }
6487
6488 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6489 cx.background_executor()
6490 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6491 .await;
6492
6493 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6494 let providers = this.code_action_providers.clone();
6495 let tasks = this
6496 .code_action_providers
6497 .iter()
6498 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6499 .collect::<Vec<_>>();
6500 (providers, tasks)
6501 })?;
6502
6503 let mut actions = Vec::new();
6504 for (provider, provider_actions) in
6505 providers.into_iter().zip(future::join_all(tasks).await)
6506 {
6507 if let Some(provider_actions) = provider_actions.log_err() {
6508 actions.extend(provider_actions.into_iter().map(|action| {
6509 AvailableCodeAction {
6510 excerpt_id: newest_selection.start.excerpt_id,
6511 action,
6512 provider: provider.clone(),
6513 }
6514 }));
6515 }
6516 }
6517
6518 this.update(cx, |this, cx| {
6519 this.available_code_actions = if actions.is_empty() {
6520 None
6521 } else {
6522 Some((
6523 Location {
6524 buffer: start_buffer,
6525 range: start..end,
6526 },
6527 actions.into(),
6528 ))
6529 };
6530 cx.notify();
6531 })
6532 }));
6533 None
6534 }
6535
6536 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6537 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6538 self.show_git_blame_inline = false;
6539
6540 self.show_git_blame_inline_delay_task =
6541 Some(cx.spawn_in(window, async move |this, cx| {
6542 cx.background_executor().timer(delay).await;
6543
6544 this.update(cx, |this, cx| {
6545 this.show_git_blame_inline = true;
6546 cx.notify();
6547 })
6548 .log_err();
6549 }));
6550 }
6551 }
6552
6553 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6554 let snapshot = self.snapshot(window, cx);
6555 let cursor = self.selections.newest::<Point>(cx).head();
6556 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6557 else {
6558 return;
6559 };
6560
6561 let Some(blame) = self.blame.as_ref() else {
6562 return;
6563 };
6564
6565 let row_info = RowInfo {
6566 buffer_id: Some(buffer.remote_id()),
6567 buffer_row: Some(point.row),
6568 ..Default::default()
6569 };
6570 let Some(blame_entry) = blame
6571 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6572 .flatten()
6573 else {
6574 return;
6575 };
6576
6577 let anchor = self.selections.newest_anchor().head();
6578 let position = self.to_pixel_point(anchor, &snapshot, window);
6579 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6580 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6581 };
6582 }
6583
6584 fn show_blame_popover(
6585 &mut self,
6586 blame_entry: &BlameEntry,
6587 position: gpui::Point<Pixels>,
6588 ignore_timeout: bool,
6589 cx: &mut Context<Self>,
6590 ) {
6591 if let Some(state) = &mut self.inline_blame_popover {
6592 state.hide_task.take();
6593 } else {
6594 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6595 let blame_entry = blame_entry.clone();
6596 let show_task = cx.spawn(async move |editor, cx| {
6597 if !ignore_timeout {
6598 cx.background_executor()
6599 .timer(std::time::Duration::from_millis(blame_popover_delay))
6600 .await;
6601 }
6602 editor
6603 .update(cx, |editor, cx| {
6604 editor.inline_blame_popover_show_task.take();
6605 let Some(blame) = editor.blame.as_ref() else {
6606 return;
6607 };
6608 let blame = blame.read(cx);
6609 let details = blame.details_for_entry(&blame_entry);
6610 let markdown = cx.new(|cx| {
6611 Markdown::new(
6612 details
6613 .as_ref()
6614 .map(|message| message.message.clone())
6615 .unwrap_or_default(),
6616 None,
6617 None,
6618 cx,
6619 )
6620 });
6621 editor.inline_blame_popover = Some(InlineBlamePopover {
6622 position,
6623 hide_task: None,
6624 popover_bounds: None,
6625 popover_state: InlineBlamePopoverState {
6626 scroll_handle: ScrollHandle::new(),
6627 commit_message: details,
6628 markdown,
6629 },
6630 keyboard_grace: ignore_timeout,
6631 });
6632 cx.notify();
6633 })
6634 .ok();
6635 });
6636 self.inline_blame_popover_show_task = Some(show_task);
6637 }
6638 }
6639
6640 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6641 self.inline_blame_popover_show_task.take();
6642 if let Some(state) = &mut self.inline_blame_popover {
6643 let hide_task = cx.spawn(async move |editor, cx| {
6644 cx.background_executor()
6645 .timer(std::time::Duration::from_millis(100))
6646 .await;
6647 editor
6648 .update(cx, |editor, cx| {
6649 editor.inline_blame_popover.take();
6650 cx.notify();
6651 })
6652 .ok();
6653 });
6654 state.hide_task = Some(hide_task);
6655 }
6656 }
6657
6658 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6659 if self.pending_rename.is_some() {
6660 return None;
6661 }
6662
6663 let provider = self.semantics_provider.clone()?;
6664 let buffer = self.buffer.read(cx);
6665 let newest_selection = self.selections.newest_anchor().clone();
6666 let cursor_position = newest_selection.head();
6667 let (cursor_buffer, cursor_buffer_position) =
6668 buffer.text_anchor_for_position(cursor_position, cx)?;
6669 let (tail_buffer, tail_buffer_position) =
6670 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6671 if cursor_buffer != tail_buffer {
6672 return None;
6673 }
6674
6675 let snapshot = cursor_buffer.read(cx).snapshot();
6676 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6677 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6678 if start_word_range != end_word_range {
6679 self.document_highlights_task.take();
6680 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6681 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6682 return None;
6683 }
6684
6685 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6686 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6687 cx.background_executor()
6688 .timer(Duration::from_millis(debounce))
6689 .await;
6690
6691 let highlights = if let Some(highlights) = cx
6692 .update(|cx| {
6693 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6694 })
6695 .ok()
6696 .flatten()
6697 {
6698 highlights.await.log_err()
6699 } else {
6700 None
6701 };
6702
6703 if let Some(highlights) = highlights {
6704 this.update(cx, |this, cx| {
6705 if this.pending_rename.is_some() {
6706 return;
6707 }
6708
6709 let buffer_id = cursor_position.buffer_id;
6710 let buffer = this.buffer.read(cx);
6711 if buffer
6712 .text_anchor_for_position(cursor_position, cx)
6713 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6714 {
6715 return;
6716 }
6717
6718 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6719 let mut write_ranges = Vec::new();
6720 let mut read_ranges = Vec::new();
6721 for highlight in highlights {
6722 for (excerpt_id, excerpt_range) in
6723 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6724 {
6725 let start = highlight
6726 .range
6727 .start
6728 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6729 let end = highlight
6730 .range
6731 .end
6732 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6733 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6734 continue;
6735 }
6736
6737 let range = Anchor {
6738 buffer_id,
6739 excerpt_id,
6740 text_anchor: start,
6741 diff_base_anchor: None,
6742 }..Anchor {
6743 buffer_id,
6744 excerpt_id,
6745 text_anchor: end,
6746 diff_base_anchor: None,
6747 };
6748 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6749 write_ranges.push(range);
6750 } else {
6751 read_ranges.push(range);
6752 }
6753 }
6754 }
6755
6756 this.highlight_background::<DocumentHighlightRead>(
6757 &read_ranges,
6758 |theme| theme.colors().editor_document_highlight_read_background,
6759 cx,
6760 );
6761 this.highlight_background::<DocumentHighlightWrite>(
6762 &write_ranges,
6763 |theme| theme.colors().editor_document_highlight_write_background,
6764 cx,
6765 );
6766 cx.notify();
6767 })
6768 .log_err();
6769 }
6770 }));
6771 None
6772 }
6773
6774 fn prepare_highlight_query_from_selection(
6775 &mut self,
6776 cx: &mut Context<Editor>,
6777 ) -> Option<(String, Range<Anchor>)> {
6778 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6779 return None;
6780 }
6781 if !EditorSettings::get_global(cx).selection_highlight {
6782 return None;
6783 }
6784 if self.selections.count() != 1 || self.selections.line_mode {
6785 return None;
6786 }
6787 let selection = self.selections.newest::<Point>(cx);
6788 if selection.is_empty() || selection.start.row != selection.end.row {
6789 return None;
6790 }
6791 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6792 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6793 let query = multi_buffer_snapshot
6794 .text_for_range(selection_anchor_range.clone())
6795 .collect::<String>();
6796 if query.trim().is_empty() {
6797 return None;
6798 }
6799 Some((query, selection_anchor_range))
6800 }
6801
6802 fn update_selection_occurrence_highlights(
6803 &mut self,
6804 query_text: String,
6805 query_range: Range<Anchor>,
6806 multi_buffer_range_to_query: Range<Point>,
6807 use_debounce: bool,
6808 window: &mut Window,
6809 cx: &mut Context<Editor>,
6810 ) -> Task<()> {
6811 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6812 cx.spawn_in(window, async move |editor, cx| {
6813 if use_debounce {
6814 cx.background_executor()
6815 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6816 .await;
6817 }
6818 let match_task = cx.background_spawn(async move {
6819 let buffer_ranges = multi_buffer_snapshot
6820 .range_to_buffer_ranges(multi_buffer_range_to_query)
6821 .into_iter()
6822 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6823 let mut match_ranges = Vec::new();
6824 let Ok(regex) = project::search::SearchQuery::text(
6825 query_text.clone(),
6826 false,
6827 false,
6828 false,
6829 Default::default(),
6830 Default::default(),
6831 false,
6832 None,
6833 ) else {
6834 return Vec::default();
6835 };
6836 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6837 match_ranges.extend(
6838 regex
6839 .search(buffer_snapshot, Some(search_range.clone()))
6840 .await
6841 .into_iter()
6842 .filter_map(|match_range| {
6843 let match_start = buffer_snapshot
6844 .anchor_after(search_range.start + match_range.start);
6845 let match_end = buffer_snapshot
6846 .anchor_before(search_range.start + match_range.end);
6847 let match_anchor_range = Anchor::range_in_buffer(
6848 excerpt_id,
6849 buffer_snapshot.remote_id(),
6850 match_start..match_end,
6851 );
6852 (match_anchor_range != query_range).then_some(match_anchor_range)
6853 }),
6854 );
6855 }
6856 match_ranges
6857 });
6858 let match_ranges = match_task.await;
6859 editor
6860 .update_in(cx, |editor, _, cx| {
6861 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6862 if !match_ranges.is_empty() {
6863 editor.highlight_background::<SelectedTextHighlight>(
6864 &match_ranges,
6865 |theme| theme.colors().editor_document_highlight_bracket_background,
6866 cx,
6867 )
6868 }
6869 })
6870 .log_err();
6871 })
6872 }
6873
6874 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6875 struct NewlineFold;
6876 let type_id = std::any::TypeId::of::<NewlineFold>();
6877 if !self.mode.is_single_line() {
6878 return;
6879 }
6880 let snapshot = self.snapshot(window, cx);
6881 if snapshot.buffer_snapshot.max_point().row == 0 {
6882 return;
6883 }
6884 let task = cx.background_spawn(async move {
6885 let new_newlines = snapshot
6886 .buffer_chars_at(0)
6887 .filter_map(|(c, i)| {
6888 if c == '\n' {
6889 Some(
6890 snapshot.buffer_snapshot.anchor_after(i)
6891 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6892 )
6893 } else {
6894 None
6895 }
6896 })
6897 .collect::<Vec<_>>();
6898 let existing_newlines = snapshot
6899 .folds_in_range(0..snapshot.buffer_snapshot.len())
6900 .filter_map(|fold| {
6901 if fold.placeholder.type_tag == Some(type_id) {
6902 Some(fold.range.start..fold.range.end)
6903 } else {
6904 None
6905 }
6906 })
6907 .collect::<Vec<_>>();
6908
6909 (new_newlines, existing_newlines)
6910 });
6911 self.folding_newlines = cx.spawn(async move |this, cx| {
6912 let (new_newlines, existing_newlines) = task.await;
6913 if new_newlines == existing_newlines {
6914 return;
6915 }
6916 let placeholder = FoldPlaceholder {
6917 render: Arc::new(move |_, _, cx| {
6918 div()
6919 .bg(cx.theme().status().hint_background)
6920 .border_b_1()
6921 .size_full()
6922 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6923 .border_color(cx.theme().status().hint)
6924 .child("\\n")
6925 .into_any()
6926 }),
6927 constrain_width: false,
6928 merge_adjacent: false,
6929 type_tag: Some(type_id),
6930 };
6931 let creases = new_newlines
6932 .into_iter()
6933 .map(|range| Crease::simple(range, placeholder.clone()))
6934 .collect();
6935 this.update(cx, |this, cx| {
6936 this.display_map.update(cx, |display_map, cx| {
6937 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6938 display_map.fold(creases, cx);
6939 });
6940 })
6941 .ok();
6942 });
6943 }
6944
6945 fn refresh_selected_text_highlights(
6946 &mut self,
6947 on_buffer_edit: bool,
6948 window: &mut Window,
6949 cx: &mut Context<Editor>,
6950 ) {
6951 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6952 else {
6953 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6954 self.quick_selection_highlight_task.take();
6955 self.debounced_selection_highlight_task.take();
6956 return;
6957 };
6958 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6959 if on_buffer_edit
6960 || self
6961 .quick_selection_highlight_task
6962 .as_ref()
6963 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6964 {
6965 let multi_buffer_visible_start = self
6966 .scroll_manager
6967 .anchor()
6968 .anchor
6969 .to_point(&multi_buffer_snapshot);
6970 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6971 multi_buffer_visible_start
6972 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6973 Bias::Left,
6974 );
6975 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6976 self.quick_selection_highlight_task = Some((
6977 query_range.clone(),
6978 self.update_selection_occurrence_highlights(
6979 query_text.clone(),
6980 query_range.clone(),
6981 multi_buffer_visible_range,
6982 false,
6983 window,
6984 cx,
6985 ),
6986 ));
6987 }
6988 if on_buffer_edit
6989 || self
6990 .debounced_selection_highlight_task
6991 .as_ref()
6992 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6993 {
6994 let multi_buffer_start = multi_buffer_snapshot
6995 .anchor_before(0)
6996 .to_point(&multi_buffer_snapshot);
6997 let multi_buffer_end = multi_buffer_snapshot
6998 .anchor_after(multi_buffer_snapshot.len())
6999 .to_point(&multi_buffer_snapshot);
7000 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7001 self.debounced_selection_highlight_task = Some((
7002 query_range.clone(),
7003 self.update_selection_occurrence_highlights(
7004 query_text,
7005 query_range,
7006 multi_buffer_full_range,
7007 true,
7008 window,
7009 cx,
7010 ),
7011 ));
7012 }
7013 }
7014
7015 pub fn refresh_edit_prediction(
7016 &mut self,
7017 debounce: bool,
7018 user_requested: bool,
7019 window: &mut Window,
7020 cx: &mut Context<Self>,
7021 ) -> Option<()> {
7022 if DisableAiSettings::get_global(cx).disable_ai {
7023 return None;
7024 }
7025
7026 let provider = self.edit_prediction_provider()?;
7027 let cursor = self.selections.newest_anchor().head();
7028 let (buffer, cursor_buffer_position) =
7029 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7030
7031 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7032 self.discard_edit_prediction(false, cx);
7033 return None;
7034 }
7035
7036 if !user_requested
7037 && (!self.should_show_edit_predictions()
7038 || !self.is_focused(window)
7039 || buffer.read(cx).is_empty())
7040 {
7041 self.discard_edit_prediction(false, cx);
7042 return None;
7043 }
7044
7045 self.update_visible_edit_prediction(window, cx);
7046 provider.refresh(
7047 self.project.clone(),
7048 buffer,
7049 cursor_buffer_position,
7050 debounce,
7051 cx,
7052 );
7053 Some(())
7054 }
7055
7056 fn show_edit_predictions_in_menu(&self) -> bool {
7057 match self.edit_prediction_settings {
7058 EditPredictionSettings::Disabled => false,
7059 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7060 }
7061 }
7062
7063 pub fn edit_predictions_enabled(&self) -> bool {
7064 match self.edit_prediction_settings {
7065 EditPredictionSettings::Disabled => false,
7066 EditPredictionSettings::Enabled { .. } => true,
7067 }
7068 }
7069
7070 fn edit_prediction_requires_modifier(&self) -> bool {
7071 match self.edit_prediction_settings {
7072 EditPredictionSettings::Disabled => false,
7073 EditPredictionSettings::Enabled {
7074 preview_requires_modifier,
7075 ..
7076 } => preview_requires_modifier,
7077 }
7078 }
7079
7080 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7081 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7082 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7083 self.discard_edit_prediction(false, cx);
7084 } else {
7085 let selection = self.selections.newest_anchor();
7086 let cursor = selection.head();
7087
7088 if let Some((buffer, cursor_buffer_position)) =
7089 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7090 {
7091 self.edit_prediction_settings =
7092 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7093 }
7094 }
7095 }
7096
7097 fn edit_prediction_settings_at_position(
7098 &self,
7099 buffer: &Entity<Buffer>,
7100 buffer_position: language::Anchor,
7101 cx: &App,
7102 ) -> EditPredictionSettings {
7103 if !self.mode.is_full()
7104 || !self.show_edit_predictions_override.unwrap_or(true)
7105 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7106 {
7107 return EditPredictionSettings::Disabled;
7108 }
7109
7110 let buffer = buffer.read(cx);
7111
7112 let file = buffer.file();
7113
7114 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7115 return EditPredictionSettings::Disabled;
7116 };
7117
7118 let by_provider = matches!(
7119 self.menu_edit_predictions_policy,
7120 MenuEditPredictionsPolicy::ByProvider
7121 );
7122
7123 let show_in_menu = by_provider
7124 && self
7125 .edit_prediction_provider
7126 .as_ref()
7127 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7128
7129 let preview_requires_modifier =
7130 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7131
7132 EditPredictionSettings::Enabled {
7133 show_in_menu,
7134 preview_requires_modifier,
7135 }
7136 }
7137
7138 fn should_show_edit_predictions(&self) -> bool {
7139 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7140 }
7141
7142 pub fn edit_prediction_preview_is_active(&self) -> bool {
7143 matches!(
7144 self.edit_prediction_preview,
7145 EditPredictionPreview::Active { .. }
7146 )
7147 }
7148
7149 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7150 let cursor = self.selections.newest_anchor().head();
7151 if let Some((buffer, cursor_position)) =
7152 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7153 {
7154 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7155 } else {
7156 false
7157 }
7158 }
7159
7160 pub fn supports_minimap(&self, cx: &App) -> bool {
7161 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7162 }
7163
7164 fn edit_predictions_enabled_in_buffer(
7165 &self,
7166 buffer: &Entity<Buffer>,
7167 buffer_position: language::Anchor,
7168 cx: &App,
7169 ) -> bool {
7170 maybe!({
7171 if self.read_only(cx) {
7172 return Some(false);
7173 }
7174 let provider = self.edit_prediction_provider()?;
7175 if !provider.is_enabled(buffer, buffer_position, cx) {
7176 return Some(false);
7177 }
7178 let buffer = buffer.read(cx);
7179 let Some(file) = buffer.file() else {
7180 return Some(true);
7181 };
7182 let settings = all_language_settings(Some(file), cx);
7183 Some(settings.edit_predictions_enabled_for_file(file, cx))
7184 })
7185 .unwrap_or(false)
7186 }
7187
7188 fn cycle_edit_prediction(
7189 &mut self,
7190 direction: Direction,
7191 window: &mut Window,
7192 cx: &mut Context<Self>,
7193 ) -> Option<()> {
7194 let provider = self.edit_prediction_provider()?;
7195 let cursor = self.selections.newest_anchor().head();
7196 let (buffer, cursor_buffer_position) =
7197 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7198 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7199 return None;
7200 }
7201
7202 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7203 self.update_visible_edit_prediction(window, cx);
7204
7205 Some(())
7206 }
7207
7208 pub fn show_edit_prediction(
7209 &mut self,
7210 _: &ShowEditPrediction,
7211 window: &mut Window,
7212 cx: &mut Context<Self>,
7213 ) {
7214 if !self.has_active_edit_prediction() {
7215 self.refresh_edit_prediction(false, true, window, cx);
7216 return;
7217 }
7218
7219 self.update_visible_edit_prediction(window, cx);
7220 }
7221
7222 pub fn display_cursor_names(
7223 &mut self,
7224 _: &DisplayCursorNames,
7225 window: &mut Window,
7226 cx: &mut Context<Self>,
7227 ) {
7228 self.show_cursor_names(window, cx);
7229 }
7230
7231 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7232 self.show_cursor_names = true;
7233 cx.notify();
7234 cx.spawn_in(window, async move |this, cx| {
7235 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7236 this.update(cx, |this, cx| {
7237 this.show_cursor_names = false;
7238 cx.notify()
7239 })
7240 .ok()
7241 })
7242 .detach();
7243 }
7244
7245 pub fn next_edit_prediction(
7246 &mut self,
7247 _: &NextEditPrediction,
7248 window: &mut Window,
7249 cx: &mut Context<Self>,
7250 ) {
7251 if self.has_active_edit_prediction() {
7252 self.cycle_edit_prediction(Direction::Next, window, cx);
7253 } else {
7254 let is_copilot_disabled = self
7255 .refresh_edit_prediction(false, true, window, cx)
7256 .is_none();
7257 if is_copilot_disabled {
7258 cx.propagate();
7259 }
7260 }
7261 }
7262
7263 pub fn previous_edit_prediction(
7264 &mut self,
7265 _: &PreviousEditPrediction,
7266 window: &mut Window,
7267 cx: &mut Context<Self>,
7268 ) {
7269 if self.has_active_edit_prediction() {
7270 self.cycle_edit_prediction(Direction::Prev, window, cx);
7271 } else {
7272 let is_copilot_disabled = self
7273 .refresh_edit_prediction(false, true, window, cx)
7274 .is_none();
7275 if is_copilot_disabled {
7276 cx.propagate();
7277 }
7278 }
7279 }
7280
7281 pub fn accept_edit_prediction(
7282 &mut self,
7283 _: &AcceptEditPrediction,
7284 window: &mut Window,
7285 cx: &mut Context<Self>,
7286 ) {
7287 if self.show_edit_predictions_in_menu() {
7288 self.hide_context_menu(window, cx);
7289 }
7290
7291 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7292 return;
7293 };
7294
7295 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7296
7297 match &active_edit_prediction.completion {
7298 EditPrediction::Move { target, .. } => {
7299 let target = *target;
7300
7301 if let Some(position_map) = &self.last_position_map {
7302 if position_map
7303 .visible_row_range
7304 .contains(&target.to_display_point(&position_map.snapshot).row())
7305 || !self.edit_prediction_requires_modifier()
7306 {
7307 self.unfold_ranges(&[target..target], true, false, cx);
7308 // Note that this is also done in vim's handler of the Tab action.
7309 self.change_selections(
7310 SelectionEffects::scroll(Autoscroll::newest()),
7311 window,
7312 cx,
7313 |selections| {
7314 selections.select_anchor_ranges([target..target]);
7315 },
7316 );
7317 self.clear_row_highlights::<EditPredictionPreview>();
7318
7319 self.edit_prediction_preview
7320 .set_previous_scroll_position(None);
7321 } else {
7322 self.edit_prediction_preview
7323 .set_previous_scroll_position(Some(
7324 position_map.snapshot.scroll_anchor,
7325 ));
7326
7327 self.highlight_rows::<EditPredictionPreview>(
7328 target..target,
7329 cx.theme().colors().editor_highlighted_line_background,
7330 RowHighlightOptions {
7331 autoscroll: true,
7332 ..Default::default()
7333 },
7334 cx,
7335 );
7336 self.request_autoscroll(Autoscroll::fit(), cx);
7337 }
7338 }
7339 }
7340 EditPrediction::Edit { edits, .. } => {
7341 if let Some(provider) = self.edit_prediction_provider() {
7342 provider.accept(cx);
7343 }
7344
7345 // Store the transaction ID and selections before applying the edit
7346 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7347
7348 let snapshot = self.buffer.read(cx).snapshot(cx);
7349 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7350
7351 self.buffer.update(cx, |buffer, cx| {
7352 buffer.edit(edits.iter().cloned(), None, cx)
7353 });
7354
7355 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7356 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7357 });
7358
7359 let selections = self.selections.disjoint_anchors();
7360 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7361 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7362 if has_new_transaction {
7363 self.selection_history
7364 .insert_transaction(transaction_id_now, selections);
7365 }
7366 }
7367
7368 self.update_visible_edit_prediction(window, cx);
7369 if self.active_edit_prediction.is_none() {
7370 self.refresh_edit_prediction(true, true, window, cx);
7371 }
7372
7373 cx.notify();
7374 }
7375 }
7376
7377 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7378 }
7379
7380 pub fn accept_partial_edit_prediction(
7381 &mut self,
7382 _: &AcceptPartialEditPrediction,
7383 window: &mut Window,
7384 cx: &mut Context<Self>,
7385 ) {
7386 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7387 return;
7388 };
7389 if self.selections.count() != 1 {
7390 return;
7391 }
7392
7393 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7394
7395 match &active_edit_prediction.completion {
7396 EditPrediction::Move { target, .. } => {
7397 let target = *target;
7398 self.change_selections(
7399 SelectionEffects::scroll(Autoscroll::newest()),
7400 window,
7401 cx,
7402 |selections| {
7403 selections.select_anchor_ranges([target..target]);
7404 },
7405 );
7406 }
7407 EditPrediction::Edit { edits, .. } => {
7408 // Find an insertion that starts at the cursor position.
7409 let snapshot = self.buffer.read(cx).snapshot(cx);
7410 let cursor_offset = self.selections.newest::<usize>(cx).head();
7411 let insertion = edits.iter().find_map(|(range, text)| {
7412 let range = range.to_offset(&snapshot);
7413 if range.is_empty() && range.start == cursor_offset {
7414 Some(text)
7415 } else {
7416 None
7417 }
7418 });
7419
7420 if let Some(text) = insertion {
7421 let mut partial_completion = text
7422 .chars()
7423 .by_ref()
7424 .take_while(|c| c.is_alphabetic())
7425 .collect::<String>();
7426 if partial_completion.is_empty() {
7427 partial_completion = text
7428 .chars()
7429 .by_ref()
7430 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7431 .collect::<String>();
7432 }
7433
7434 cx.emit(EditorEvent::InputHandled {
7435 utf16_range_to_replace: None,
7436 text: partial_completion.clone().into(),
7437 });
7438
7439 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7440
7441 self.refresh_edit_prediction(true, true, window, cx);
7442 cx.notify();
7443 } else {
7444 self.accept_edit_prediction(&Default::default(), window, cx);
7445 }
7446 }
7447 }
7448 }
7449
7450 fn discard_edit_prediction(
7451 &mut self,
7452 should_report_edit_prediction_event: bool,
7453 cx: &mut Context<Self>,
7454 ) -> bool {
7455 if should_report_edit_prediction_event {
7456 let completion_id = self
7457 .active_edit_prediction
7458 .as_ref()
7459 .and_then(|active_completion| active_completion.completion_id.clone());
7460
7461 self.report_edit_prediction_event(completion_id, false, cx);
7462 }
7463
7464 if let Some(provider) = self.edit_prediction_provider() {
7465 provider.discard(cx);
7466 }
7467
7468 self.take_active_edit_prediction(cx)
7469 }
7470
7471 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7472 let Some(provider) = self.edit_prediction_provider() else {
7473 return;
7474 };
7475
7476 let Some((_, buffer, _)) = self
7477 .buffer
7478 .read(cx)
7479 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7480 else {
7481 return;
7482 };
7483
7484 let extension = buffer
7485 .read(cx)
7486 .file()
7487 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7488
7489 let event_type = match accepted {
7490 true => "Edit Prediction Accepted",
7491 false => "Edit Prediction Discarded",
7492 };
7493 telemetry::event!(
7494 event_type,
7495 provider = provider.name(),
7496 prediction_id = id,
7497 suggestion_accepted = accepted,
7498 file_extension = extension,
7499 );
7500 }
7501
7502 pub fn has_active_edit_prediction(&self) -> bool {
7503 self.active_edit_prediction.is_some()
7504 }
7505
7506 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7507 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7508 return false;
7509 };
7510
7511 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7512 self.clear_highlights::<EditPredictionHighlight>(cx);
7513 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7514 true
7515 }
7516
7517 /// Returns true when we're displaying the edit prediction popover below the cursor
7518 /// like we are not previewing and the LSP autocomplete menu is visible
7519 /// or we are in `when_holding_modifier` mode.
7520 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7521 if self.edit_prediction_preview_is_active()
7522 || !self.show_edit_predictions_in_menu()
7523 || !self.edit_predictions_enabled()
7524 {
7525 return false;
7526 }
7527
7528 if self.has_visible_completions_menu() {
7529 return true;
7530 }
7531
7532 has_completion && self.edit_prediction_requires_modifier()
7533 }
7534
7535 fn handle_modifiers_changed(
7536 &mut self,
7537 modifiers: Modifiers,
7538 position_map: &PositionMap,
7539 window: &mut Window,
7540 cx: &mut Context<Self>,
7541 ) {
7542 if self.show_edit_predictions_in_menu() {
7543 self.update_edit_prediction_preview(&modifiers, window, cx);
7544 }
7545
7546 self.update_selection_mode(&modifiers, position_map, window, cx);
7547
7548 let mouse_position = window.mouse_position();
7549 if !position_map.text_hitbox.is_hovered(window) {
7550 return;
7551 }
7552
7553 self.update_hovered_link(
7554 position_map.point_for_position(mouse_position),
7555 &position_map.snapshot,
7556 modifiers,
7557 window,
7558 cx,
7559 )
7560 }
7561
7562 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7563 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7564 if invert {
7565 match multi_cursor_setting {
7566 MultiCursorModifier::Alt => modifiers.alt,
7567 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7568 }
7569 } else {
7570 match multi_cursor_setting {
7571 MultiCursorModifier::Alt => modifiers.secondary(),
7572 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7573 }
7574 }
7575 }
7576
7577 fn columnar_selection_mode(
7578 modifiers: &Modifiers,
7579 cx: &mut Context<Self>,
7580 ) -> Option<ColumnarMode> {
7581 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7582 if Self::multi_cursor_modifier(false, modifiers, cx) {
7583 Some(ColumnarMode::FromMouse)
7584 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7585 Some(ColumnarMode::FromSelection)
7586 } else {
7587 None
7588 }
7589 } else {
7590 None
7591 }
7592 }
7593
7594 fn update_selection_mode(
7595 &mut self,
7596 modifiers: &Modifiers,
7597 position_map: &PositionMap,
7598 window: &mut Window,
7599 cx: &mut Context<Self>,
7600 ) {
7601 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7602 return;
7603 };
7604 if self.selections.pending.is_none() {
7605 return;
7606 }
7607
7608 let mouse_position = window.mouse_position();
7609 let point_for_position = position_map.point_for_position(mouse_position);
7610 let position = point_for_position.previous_valid;
7611
7612 self.select(
7613 SelectPhase::BeginColumnar {
7614 position,
7615 reset: false,
7616 mode,
7617 goal_column: point_for_position.exact_unclipped.column(),
7618 },
7619 window,
7620 cx,
7621 );
7622 }
7623
7624 fn update_edit_prediction_preview(
7625 &mut self,
7626 modifiers: &Modifiers,
7627 window: &mut Window,
7628 cx: &mut Context<Self>,
7629 ) {
7630 let mut modifiers_held = false;
7631 if let Some(accept_keystroke) = self
7632 .accept_edit_prediction_keybind(false, window, cx)
7633 .keystroke()
7634 {
7635 modifiers_held = modifiers_held
7636 || (&accept_keystroke.modifiers == modifiers
7637 && accept_keystroke.modifiers.modified());
7638 };
7639 if let Some(accept_partial_keystroke) = self
7640 .accept_edit_prediction_keybind(true, window, cx)
7641 .keystroke()
7642 {
7643 modifiers_held = modifiers_held
7644 || (&accept_partial_keystroke.modifiers == modifiers
7645 && accept_partial_keystroke.modifiers.modified());
7646 }
7647
7648 if modifiers_held {
7649 if matches!(
7650 self.edit_prediction_preview,
7651 EditPredictionPreview::Inactive { .. }
7652 ) {
7653 self.edit_prediction_preview = EditPredictionPreview::Active {
7654 previous_scroll_position: None,
7655 since: Instant::now(),
7656 };
7657
7658 self.update_visible_edit_prediction(window, cx);
7659 cx.notify();
7660 }
7661 } else if let EditPredictionPreview::Active {
7662 previous_scroll_position,
7663 since,
7664 } = self.edit_prediction_preview
7665 {
7666 if let (Some(previous_scroll_position), Some(position_map)) =
7667 (previous_scroll_position, self.last_position_map.as_ref())
7668 {
7669 self.set_scroll_position(
7670 previous_scroll_position
7671 .scroll_position(&position_map.snapshot.display_snapshot),
7672 window,
7673 cx,
7674 );
7675 }
7676
7677 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7678 released_too_fast: since.elapsed() < Duration::from_millis(200),
7679 };
7680 self.clear_row_highlights::<EditPredictionPreview>();
7681 self.update_visible_edit_prediction(window, cx);
7682 cx.notify();
7683 }
7684 }
7685
7686 fn update_visible_edit_prediction(
7687 &mut self,
7688 _window: &mut Window,
7689 cx: &mut Context<Self>,
7690 ) -> Option<()> {
7691 if DisableAiSettings::get_global(cx).disable_ai {
7692 return None;
7693 }
7694
7695 let selection = self.selections.newest_anchor();
7696 let cursor = selection.head();
7697 let multibuffer = self.buffer.read(cx).snapshot(cx);
7698 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7699 let excerpt_id = cursor.excerpt_id;
7700
7701 let show_in_menu = self.show_edit_predictions_in_menu();
7702 let completions_menu_has_precedence = !show_in_menu
7703 && (self.context_menu.borrow().is_some()
7704 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7705
7706 if completions_menu_has_precedence
7707 || !offset_selection.is_empty()
7708 || self
7709 .active_edit_prediction
7710 .as_ref()
7711 .is_some_and(|completion| {
7712 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7713 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7714 !invalidation_range.contains(&offset_selection.head())
7715 })
7716 {
7717 self.discard_edit_prediction(false, cx);
7718 return None;
7719 }
7720
7721 self.take_active_edit_prediction(cx);
7722 let Some(provider) = self.edit_prediction_provider() else {
7723 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7724 return None;
7725 };
7726
7727 let (buffer, cursor_buffer_position) =
7728 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7729
7730 self.edit_prediction_settings =
7731 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7732
7733 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7734 self.discard_edit_prediction(false, cx);
7735 return None;
7736 };
7737
7738 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7739
7740 if self.edit_prediction_indent_conflict {
7741 let cursor_point = cursor.to_point(&multibuffer);
7742
7743 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7744
7745 if let Some((_, indent)) = indents.iter().next()
7746 && indent.len == cursor_point.column
7747 {
7748 self.edit_prediction_indent_conflict = false;
7749 }
7750 }
7751
7752 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7753 let edits = edit_prediction
7754 .edits
7755 .into_iter()
7756 .flat_map(|(range, new_text)| {
7757 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7758 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7759 Some((start..end, new_text))
7760 })
7761 .collect::<Vec<_>>();
7762 if edits.is_empty() {
7763 return None;
7764 }
7765
7766 let first_edit_start = edits.first().unwrap().0.start;
7767 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7768 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7769
7770 let last_edit_end = edits.last().unwrap().0.end;
7771 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7772 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7773
7774 let cursor_row = cursor.to_point(&multibuffer).row;
7775
7776 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7777
7778 let mut inlay_ids = Vec::new();
7779 let invalidation_row_range;
7780 let move_invalidation_row_range = if cursor_row < edit_start_row {
7781 Some(cursor_row..edit_end_row)
7782 } else if cursor_row > edit_end_row {
7783 Some(edit_start_row..cursor_row)
7784 } else {
7785 None
7786 };
7787 let supports_jump = self
7788 .edit_prediction_provider
7789 .as_ref()
7790 .map(|provider| provider.provider.supports_jump_to_edit())
7791 .unwrap_or(true);
7792
7793 let is_move = supports_jump
7794 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7795 let completion = if is_move {
7796 invalidation_row_range =
7797 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7798 let target = first_edit_start;
7799 EditPrediction::Move { target, snapshot }
7800 } else {
7801 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7802 && !self.edit_predictions_hidden_for_vim_mode;
7803
7804 if show_completions_in_buffer {
7805 if edits
7806 .iter()
7807 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7808 {
7809 let mut inlays = Vec::new();
7810 for (range, new_text) in &edits {
7811 let inlay = Inlay::edit_prediction(
7812 post_inc(&mut self.next_inlay_id),
7813 range.start,
7814 new_text.as_str(),
7815 );
7816 inlay_ids.push(inlay.id);
7817 inlays.push(inlay);
7818 }
7819
7820 self.splice_inlays(&[], inlays, cx);
7821 } else {
7822 let background_color = cx.theme().status().deleted_background;
7823 self.highlight_text::<EditPredictionHighlight>(
7824 edits.iter().map(|(range, _)| range.clone()).collect(),
7825 HighlightStyle {
7826 background_color: Some(background_color),
7827 ..Default::default()
7828 },
7829 cx,
7830 );
7831 }
7832 }
7833
7834 invalidation_row_range = edit_start_row..edit_end_row;
7835
7836 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7837 if provider.show_tab_accept_marker() {
7838 EditDisplayMode::TabAccept
7839 } else {
7840 EditDisplayMode::Inline
7841 }
7842 } else {
7843 EditDisplayMode::DiffPopover
7844 };
7845
7846 EditPrediction::Edit {
7847 edits,
7848 edit_preview: edit_prediction.edit_preview,
7849 display_mode,
7850 snapshot,
7851 }
7852 };
7853
7854 let invalidation_range = multibuffer
7855 .anchor_before(Point::new(invalidation_row_range.start, 0))
7856 ..multibuffer.anchor_after(Point::new(
7857 invalidation_row_range.end,
7858 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7859 ));
7860
7861 self.stale_edit_prediction_in_menu = None;
7862 self.active_edit_prediction = Some(EditPredictionState {
7863 inlay_ids,
7864 completion,
7865 completion_id: edit_prediction.id,
7866 invalidation_range,
7867 });
7868
7869 cx.notify();
7870
7871 Some(())
7872 }
7873
7874 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7875 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7876 }
7877
7878 fn clear_tasks(&mut self) {
7879 self.tasks.clear()
7880 }
7881
7882 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7883 if self.tasks.insert(key, value).is_some() {
7884 // This case should hopefully be rare, but just in case...
7885 log::error!(
7886 "multiple different run targets found on a single line, only the last target will be rendered"
7887 )
7888 }
7889 }
7890
7891 /// Get all display points of breakpoints that will be rendered within editor
7892 ///
7893 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7894 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7895 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7896 fn active_breakpoints(
7897 &self,
7898 range: Range<DisplayRow>,
7899 window: &mut Window,
7900 cx: &mut Context<Self>,
7901 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7902 let mut breakpoint_display_points = HashMap::default();
7903
7904 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7905 return breakpoint_display_points;
7906 };
7907
7908 let snapshot = self.snapshot(window, cx);
7909
7910 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7911 let Some(project) = self.project() else {
7912 return breakpoint_display_points;
7913 };
7914
7915 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7916 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7917
7918 for (buffer_snapshot, range, excerpt_id) in
7919 multi_buffer_snapshot.range_to_buffer_ranges(range)
7920 {
7921 let Some(buffer) = project
7922 .read(cx)
7923 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7924 else {
7925 continue;
7926 };
7927 let breakpoints = breakpoint_store.read(cx).breakpoints(
7928 &buffer,
7929 Some(
7930 buffer_snapshot.anchor_before(range.start)
7931 ..buffer_snapshot.anchor_after(range.end),
7932 ),
7933 buffer_snapshot,
7934 cx,
7935 );
7936 for (breakpoint, state) in breakpoints {
7937 let multi_buffer_anchor =
7938 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7939 let position = multi_buffer_anchor
7940 .to_point(multi_buffer_snapshot)
7941 .to_display_point(&snapshot);
7942
7943 breakpoint_display_points.insert(
7944 position.row(),
7945 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7946 );
7947 }
7948 }
7949
7950 breakpoint_display_points
7951 }
7952
7953 fn breakpoint_context_menu(
7954 &self,
7955 anchor: Anchor,
7956 window: &mut Window,
7957 cx: &mut Context<Self>,
7958 ) -> Entity<ui::ContextMenu> {
7959 let weak_editor = cx.weak_entity();
7960 let focus_handle = self.focus_handle(cx);
7961
7962 let row = self
7963 .buffer
7964 .read(cx)
7965 .snapshot(cx)
7966 .summary_for_anchor::<Point>(&anchor)
7967 .row;
7968
7969 let breakpoint = self
7970 .breakpoint_at_row(row, window, cx)
7971 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7972
7973 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7974 "Edit Log Breakpoint"
7975 } else {
7976 "Set Log Breakpoint"
7977 };
7978
7979 let condition_breakpoint_msg = if breakpoint
7980 .as_ref()
7981 .is_some_and(|bp| bp.1.condition.is_some())
7982 {
7983 "Edit Condition Breakpoint"
7984 } else {
7985 "Set Condition Breakpoint"
7986 };
7987
7988 let hit_condition_breakpoint_msg = if breakpoint
7989 .as_ref()
7990 .is_some_and(|bp| bp.1.hit_condition.is_some())
7991 {
7992 "Edit Hit Condition Breakpoint"
7993 } else {
7994 "Set Hit Condition Breakpoint"
7995 };
7996
7997 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7998 "Unset Breakpoint"
7999 } else {
8000 "Set Breakpoint"
8001 };
8002
8003 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8004
8005 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8006 BreakpointState::Enabled => Some("Disable"),
8007 BreakpointState::Disabled => Some("Enable"),
8008 });
8009
8010 let (anchor, breakpoint) =
8011 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8012
8013 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8014 menu.on_blur_subscription(Subscription::new(|| {}))
8015 .context(focus_handle)
8016 .when(run_to_cursor, |this| {
8017 let weak_editor = weak_editor.clone();
8018 this.entry("Run to cursor", None, move |window, cx| {
8019 weak_editor
8020 .update(cx, |editor, cx| {
8021 editor.change_selections(
8022 SelectionEffects::no_scroll(),
8023 window,
8024 cx,
8025 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8026 );
8027 })
8028 .ok();
8029
8030 window.dispatch_action(Box::new(RunToCursor), cx);
8031 })
8032 .separator()
8033 })
8034 .when_some(toggle_state_msg, |this, msg| {
8035 this.entry(msg, None, {
8036 let weak_editor = weak_editor.clone();
8037 let breakpoint = breakpoint.clone();
8038 move |_window, cx| {
8039 weak_editor
8040 .update(cx, |this, cx| {
8041 this.edit_breakpoint_at_anchor(
8042 anchor,
8043 breakpoint.as_ref().clone(),
8044 BreakpointEditAction::InvertState,
8045 cx,
8046 );
8047 })
8048 .log_err();
8049 }
8050 })
8051 })
8052 .entry(set_breakpoint_msg, None, {
8053 let weak_editor = weak_editor.clone();
8054 let breakpoint = breakpoint.clone();
8055 move |_window, cx| {
8056 weak_editor
8057 .update(cx, |this, cx| {
8058 this.edit_breakpoint_at_anchor(
8059 anchor,
8060 breakpoint.as_ref().clone(),
8061 BreakpointEditAction::Toggle,
8062 cx,
8063 );
8064 })
8065 .log_err();
8066 }
8067 })
8068 .entry(log_breakpoint_msg, None, {
8069 let breakpoint = breakpoint.clone();
8070 let weak_editor = weak_editor.clone();
8071 move |window, cx| {
8072 weak_editor
8073 .update(cx, |this, cx| {
8074 this.add_edit_breakpoint_block(
8075 anchor,
8076 breakpoint.as_ref(),
8077 BreakpointPromptEditAction::Log,
8078 window,
8079 cx,
8080 );
8081 })
8082 .log_err();
8083 }
8084 })
8085 .entry(condition_breakpoint_msg, None, {
8086 let breakpoint = breakpoint.clone();
8087 let weak_editor = weak_editor.clone();
8088 move |window, cx| {
8089 weak_editor
8090 .update(cx, |this, cx| {
8091 this.add_edit_breakpoint_block(
8092 anchor,
8093 breakpoint.as_ref(),
8094 BreakpointPromptEditAction::Condition,
8095 window,
8096 cx,
8097 );
8098 })
8099 .log_err();
8100 }
8101 })
8102 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8103 weak_editor
8104 .update(cx, |this, cx| {
8105 this.add_edit_breakpoint_block(
8106 anchor,
8107 breakpoint.as_ref(),
8108 BreakpointPromptEditAction::HitCondition,
8109 window,
8110 cx,
8111 );
8112 })
8113 .log_err();
8114 })
8115 })
8116 }
8117
8118 fn render_breakpoint(
8119 &self,
8120 position: Anchor,
8121 row: DisplayRow,
8122 breakpoint: &Breakpoint,
8123 state: Option<BreakpointSessionState>,
8124 cx: &mut Context<Self>,
8125 ) -> IconButton {
8126 let is_rejected = state.is_some_and(|s| !s.verified);
8127 // Is it a breakpoint that shows up when hovering over gutter?
8128 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8129 (false, false),
8130 |PhantomBreakpointIndicator {
8131 is_active,
8132 display_row,
8133 collides_with_existing_breakpoint,
8134 }| {
8135 (
8136 is_active && display_row == row,
8137 collides_with_existing_breakpoint,
8138 )
8139 },
8140 );
8141
8142 let (color, icon) = {
8143 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8144 (false, false) => ui::IconName::DebugBreakpoint,
8145 (true, false) => ui::IconName::DebugLogBreakpoint,
8146 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8147 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8148 };
8149
8150 let color = if is_phantom {
8151 Color::Hint
8152 } else if is_rejected {
8153 Color::Disabled
8154 } else {
8155 Color::Debugger
8156 };
8157
8158 (color, icon)
8159 };
8160
8161 let breakpoint = Arc::from(breakpoint.clone());
8162
8163 let alt_as_text = gpui::Keystroke {
8164 modifiers: Modifiers::secondary_key(),
8165 ..Default::default()
8166 };
8167 let primary_action_text = if breakpoint.is_disabled() {
8168 "Enable breakpoint"
8169 } else if is_phantom && !collides_with_existing {
8170 "Set breakpoint"
8171 } else {
8172 "Unset breakpoint"
8173 };
8174 let focus_handle = self.focus_handle.clone();
8175
8176 let meta = if is_rejected {
8177 SharedString::from("No executable code is associated with this line.")
8178 } else if collides_with_existing && !breakpoint.is_disabled() {
8179 SharedString::from(format!(
8180 "{alt_as_text}-click to disable,\nright-click for more options."
8181 ))
8182 } else {
8183 SharedString::from("Right-click for more options.")
8184 };
8185 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8186 .icon_size(IconSize::XSmall)
8187 .size(ui::ButtonSize::None)
8188 .when(is_rejected, |this| {
8189 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8190 })
8191 .icon_color(color)
8192 .style(ButtonStyle::Transparent)
8193 .on_click(cx.listener({
8194 let breakpoint = breakpoint.clone();
8195
8196 move |editor, event: &ClickEvent, window, cx| {
8197 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8198 BreakpointEditAction::InvertState
8199 } else {
8200 BreakpointEditAction::Toggle
8201 };
8202
8203 window.focus(&editor.focus_handle(cx));
8204 editor.edit_breakpoint_at_anchor(
8205 position,
8206 breakpoint.as_ref().clone(),
8207 edit_action,
8208 cx,
8209 );
8210 }
8211 }))
8212 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8213 editor.set_breakpoint_context_menu(
8214 row,
8215 Some(position),
8216 event.position(),
8217 window,
8218 cx,
8219 );
8220 }))
8221 .tooltip(move |window, cx| {
8222 Tooltip::with_meta_in(
8223 primary_action_text,
8224 Some(&ToggleBreakpoint),
8225 meta.clone(),
8226 &focus_handle,
8227 window,
8228 cx,
8229 )
8230 })
8231 }
8232
8233 fn build_tasks_context(
8234 project: &Entity<Project>,
8235 buffer: &Entity<Buffer>,
8236 buffer_row: u32,
8237 tasks: &Arc<RunnableTasks>,
8238 cx: &mut Context<Self>,
8239 ) -> Task<Option<task::TaskContext>> {
8240 let position = Point::new(buffer_row, tasks.column);
8241 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8242 let location = Location {
8243 buffer: buffer.clone(),
8244 range: range_start..range_start,
8245 };
8246 // Fill in the environmental variables from the tree-sitter captures
8247 let mut captured_task_variables = TaskVariables::default();
8248 for (capture_name, value) in tasks.extra_variables.clone() {
8249 captured_task_variables.insert(
8250 task::VariableName::Custom(capture_name.into()),
8251 value.clone(),
8252 );
8253 }
8254 project.update(cx, |project, cx| {
8255 project.task_store().update(cx, |task_store, cx| {
8256 task_store.task_context_for_location(captured_task_variables, location, cx)
8257 })
8258 })
8259 }
8260
8261 pub fn spawn_nearest_task(
8262 &mut self,
8263 action: &SpawnNearestTask,
8264 window: &mut Window,
8265 cx: &mut Context<Self>,
8266 ) {
8267 let Some((workspace, _)) = self.workspace.clone() else {
8268 return;
8269 };
8270 let Some(project) = self.project.clone() else {
8271 return;
8272 };
8273
8274 // Try to find a closest, enclosing node using tree-sitter that has a task
8275 let Some((buffer, buffer_row, tasks)) = self
8276 .find_enclosing_node_task(cx)
8277 // Or find the task that's closest in row-distance.
8278 .or_else(|| self.find_closest_task(cx))
8279 else {
8280 return;
8281 };
8282
8283 let reveal_strategy = action.reveal;
8284 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8285 cx.spawn_in(window, async move |_, cx| {
8286 let context = task_context.await?;
8287 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8288
8289 let resolved = &mut resolved_task.resolved;
8290 resolved.reveal = reveal_strategy;
8291
8292 workspace
8293 .update_in(cx, |workspace, window, cx| {
8294 workspace.schedule_resolved_task(
8295 task_source_kind,
8296 resolved_task,
8297 false,
8298 window,
8299 cx,
8300 );
8301 })
8302 .ok()
8303 })
8304 .detach();
8305 }
8306
8307 fn find_closest_task(
8308 &mut self,
8309 cx: &mut Context<Self>,
8310 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8311 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8312
8313 let ((buffer_id, row), tasks) = self
8314 .tasks
8315 .iter()
8316 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8317
8318 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8319 let tasks = Arc::new(tasks.to_owned());
8320 Some((buffer, *row, tasks))
8321 }
8322
8323 fn find_enclosing_node_task(
8324 &mut self,
8325 cx: &mut Context<Self>,
8326 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8327 let snapshot = self.buffer.read(cx).snapshot(cx);
8328 let offset = self.selections.newest::<usize>(cx).head();
8329 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8330 let buffer_id = excerpt.buffer().remote_id();
8331
8332 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8333 let mut cursor = layer.node().walk();
8334
8335 while cursor.goto_first_child_for_byte(offset).is_some() {
8336 if cursor.node().end_byte() == offset {
8337 cursor.goto_next_sibling();
8338 }
8339 }
8340
8341 // Ascend to the smallest ancestor that contains the range and has a task.
8342 loop {
8343 let node = cursor.node();
8344 let node_range = node.byte_range();
8345 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8346
8347 // Check if this node contains our offset
8348 if node_range.start <= offset && node_range.end >= offset {
8349 // If it contains offset, check for task
8350 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8351 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8352 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8353 }
8354 }
8355
8356 if !cursor.goto_parent() {
8357 break;
8358 }
8359 }
8360 None
8361 }
8362
8363 fn render_run_indicator(
8364 &self,
8365 _style: &EditorStyle,
8366 is_active: bool,
8367 row: DisplayRow,
8368 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8369 cx: &mut Context<Self>,
8370 ) -> IconButton {
8371 let color = Color::Muted;
8372 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8373
8374 IconButton::new(
8375 ("run_indicator", row.0 as usize),
8376 ui::IconName::PlayOutlined,
8377 )
8378 .shape(ui::IconButtonShape::Square)
8379 .icon_size(IconSize::XSmall)
8380 .icon_color(color)
8381 .toggle_state(is_active)
8382 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8383 let quick_launch = match e {
8384 ClickEvent::Keyboard(_) => true,
8385 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8386 };
8387
8388 window.focus(&editor.focus_handle(cx));
8389 editor.toggle_code_actions(
8390 &ToggleCodeActions {
8391 deployed_from: Some(CodeActionSource::RunMenu(row)),
8392 quick_launch,
8393 },
8394 window,
8395 cx,
8396 );
8397 }))
8398 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8399 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8400 }))
8401 }
8402
8403 pub fn context_menu_visible(&self) -> bool {
8404 !self.edit_prediction_preview_is_active()
8405 && self
8406 .context_menu
8407 .borrow()
8408 .as_ref()
8409 .is_some_and(|menu| menu.visible())
8410 }
8411
8412 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8413 self.context_menu
8414 .borrow()
8415 .as_ref()
8416 .map(|menu| menu.origin())
8417 }
8418
8419 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8420 self.context_menu_options = Some(options);
8421 }
8422
8423 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8424 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8425
8426 fn render_edit_prediction_popover(
8427 &mut self,
8428 text_bounds: &Bounds<Pixels>,
8429 content_origin: gpui::Point<Pixels>,
8430 right_margin: Pixels,
8431 editor_snapshot: &EditorSnapshot,
8432 visible_row_range: Range<DisplayRow>,
8433 scroll_top: f32,
8434 scroll_bottom: f32,
8435 line_layouts: &[LineWithInvisibles],
8436 line_height: Pixels,
8437 scroll_pixel_position: gpui::Point<Pixels>,
8438 newest_selection_head: Option<DisplayPoint>,
8439 editor_width: Pixels,
8440 style: &EditorStyle,
8441 window: &mut Window,
8442 cx: &mut App,
8443 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8444 if self.mode().is_minimap() {
8445 return None;
8446 }
8447 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8448
8449 if self.edit_prediction_visible_in_cursor_popover(true) {
8450 return None;
8451 }
8452
8453 match &active_edit_prediction.completion {
8454 EditPrediction::Move { target, .. } => {
8455 let target_display_point = target.to_display_point(editor_snapshot);
8456
8457 if self.edit_prediction_requires_modifier() {
8458 if !self.edit_prediction_preview_is_active() {
8459 return None;
8460 }
8461
8462 self.render_edit_prediction_modifier_jump_popover(
8463 text_bounds,
8464 content_origin,
8465 visible_row_range,
8466 line_layouts,
8467 line_height,
8468 scroll_pixel_position,
8469 newest_selection_head,
8470 target_display_point,
8471 window,
8472 cx,
8473 )
8474 } else {
8475 self.render_edit_prediction_eager_jump_popover(
8476 text_bounds,
8477 content_origin,
8478 editor_snapshot,
8479 visible_row_range,
8480 scroll_top,
8481 scroll_bottom,
8482 line_height,
8483 scroll_pixel_position,
8484 target_display_point,
8485 editor_width,
8486 window,
8487 cx,
8488 )
8489 }
8490 }
8491 EditPrediction::Edit {
8492 display_mode: EditDisplayMode::Inline,
8493 ..
8494 } => None,
8495 EditPrediction::Edit {
8496 display_mode: EditDisplayMode::TabAccept,
8497 edits,
8498 ..
8499 } => {
8500 let range = &edits.first()?.0;
8501 let target_display_point = range.end.to_display_point(editor_snapshot);
8502
8503 self.render_edit_prediction_end_of_line_popover(
8504 "Accept",
8505 editor_snapshot,
8506 visible_row_range,
8507 target_display_point,
8508 line_height,
8509 scroll_pixel_position,
8510 content_origin,
8511 editor_width,
8512 window,
8513 cx,
8514 )
8515 }
8516 EditPrediction::Edit {
8517 edits,
8518 edit_preview,
8519 display_mode: EditDisplayMode::DiffPopover,
8520 snapshot,
8521 } => self.render_edit_prediction_diff_popover(
8522 text_bounds,
8523 content_origin,
8524 right_margin,
8525 editor_snapshot,
8526 visible_row_range,
8527 line_layouts,
8528 line_height,
8529 scroll_pixel_position,
8530 newest_selection_head,
8531 editor_width,
8532 style,
8533 edits,
8534 edit_preview,
8535 snapshot,
8536 window,
8537 cx,
8538 ),
8539 }
8540 }
8541
8542 fn render_edit_prediction_modifier_jump_popover(
8543 &mut self,
8544 text_bounds: &Bounds<Pixels>,
8545 content_origin: gpui::Point<Pixels>,
8546 visible_row_range: Range<DisplayRow>,
8547 line_layouts: &[LineWithInvisibles],
8548 line_height: Pixels,
8549 scroll_pixel_position: gpui::Point<Pixels>,
8550 newest_selection_head: Option<DisplayPoint>,
8551 target_display_point: DisplayPoint,
8552 window: &mut Window,
8553 cx: &mut App,
8554 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8555 let scrolled_content_origin =
8556 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8557
8558 const SCROLL_PADDING_Y: Pixels = px(12.);
8559
8560 if target_display_point.row() < visible_row_range.start {
8561 return self.render_edit_prediction_scroll_popover(
8562 |_| SCROLL_PADDING_Y,
8563 IconName::ArrowUp,
8564 visible_row_range,
8565 line_layouts,
8566 newest_selection_head,
8567 scrolled_content_origin,
8568 window,
8569 cx,
8570 );
8571 } else if target_display_point.row() >= visible_row_range.end {
8572 return self.render_edit_prediction_scroll_popover(
8573 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8574 IconName::ArrowDown,
8575 visible_row_range,
8576 line_layouts,
8577 newest_selection_head,
8578 scrolled_content_origin,
8579 window,
8580 cx,
8581 );
8582 }
8583
8584 const POLE_WIDTH: Pixels = px(2.);
8585
8586 let line_layout =
8587 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8588 let target_column = target_display_point.column() as usize;
8589
8590 let target_x = line_layout.x_for_index(target_column);
8591 let target_y =
8592 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8593
8594 let flag_on_right = target_x < text_bounds.size.width / 2.;
8595
8596 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8597 border_color.l += 0.001;
8598
8599 let mut element = v_flex()
8600 .items_end()
8601 .when(flag_on_right, |el| el.items_start())
8602 .child(if flag_on_right {
8603 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8604 .rounded_bl(px(0.))
8605 .rounded_tl(px(0.))
8606 .border_l_2()
8607 .border_color(border_color)
8608 } else {
8609 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8610 .rounded_br(px(0.))
8611 .rounded_tr(px(0.))
8612 .border_r_2()
8613 .border_color(border_color)
8614 })
8615 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8616 .into_any();
8617
8618 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8619
8620 let mut origin = scrolled_content_origin + point(target_x, target_y)
8621 - point(
8622 if flag_on_right {
8623 POLE_WIDTH
8624 } else {
8625 size.width - POLE_WIDTH
8626 },
8627 size.height - line_height,
8628 );
8629
8630 origin.x = origin.x.max(content_origin.x);
8631
8632 element.prepaint_at(origin, window, cx);
8633
8634 Some((element, origin))
8635 }
8636
8637 fn render_edit_prediction_scroll_popover(
8638 &mut self,
8639 to_y: impl Fn(Size<Pixels>) -> Pixels,
8640 scroll_icon: IconName,
8641 visible_row_range: Range<DisplayRow>,
8642 line_layouts: &[LineWithInvisibles],
8643 newest_selection_head: Option<DisplayPoint>,
8644 scrolled_content_origin: gpui::Point<Pixels>,
8645 window: &mut Window,
8646 cx: &mut App,
8647 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8648 let mut element = self
8649 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8650 .into_any();
8651
8652 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8653
8654 let cursor = newest_selection_head?;
8655 let cursor_row_layout =
8656 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8657 let cursor_column = cursor.column() as usize;
8658
8659 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8660
8661 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8662
8663 element.prepaint_at(origin, window, cx);
8664 Some((element, origin))
8665 }
8666
8667 fn render_edit_prediction_eager_jump_popover(
8668 &mut self,
8669 text_bounds: &Bounds<Pixels>,
8670 content_origin: gpui::Point<Pixels>,
8671 editor_snapshot: &EditorSnapshot,
8672 visible_row_range: Range<DisplayRow>,
8673 scroll_top: f32,
8674 scroll_bottom: f32,
8675 line_height: Pixels,
8676 scroll_pixel_position: gpui::Point<Pixels>,
8677 target_display_point: DisplayPoint,
8678 editor_width: Pixels,
8679 window: &mut Window,
8680 cx: &mut App,
8681 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8682 if target_display_point.row().as_f32() < scroll_top {
8683 let mut element = self
8684 .render_edit_prediction_line_popover(
8685 "Jump to Edit",
8686 Some(IconName::ArrowUp),
8687 window,
8688 cx,
8689 )?
8690 .into_any();
8691
8692 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8693 let offset = point(
8694 (text_bounds.size.width - size.width) / 2.,
8695 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8696 );
8697
8698 let origin = text_bounds.origin + offset;
8699 element.prepaint_at(origin, window, cx);
8700 Some((element, origin))
8701 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8702 let mut element = self
8703 .render_edit_prediction_line_popover(
8704 "Jump to Edit",
8705 Some(IconName::ArrowDown),
8706 window,
8707 cx,
8708 )?
8709 .into_any();
8710
8711 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8712 let offset = point(
8713 (text_bounds.size.width - size.width) / 2.,
8714 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8715 );
8716
8717 let origin = text_bounds.origin + offset;
8718 element.prepaint_at(origin, window, cx);
8719 Some((element, origin))
8720 } else {
8721 self.render_edit_prediction_end_of_line_popover(
8722 "Jump to Edit",
8723 editor_snapshot,
8724 visible_row_range,
8725 target_display_point,
8726 line_height,
8727 scroll_pixel_position,
8728 content_origin,
8729 editor_width,
8730 window,
8731 cx,
8732 )
8733 }
8734 }
8735
8736 fn render_edit_prediction_end_of_line_popover(
8737 self: &mut Editor,
8738 label: &'static str,
8739 editor_snapshot: &EditorSnapshot,
8740 visible_row_range: Range<DisplayRow>,
8741 target_display_point: DisplayPoint,
8742 line_height: Pixels,
8743 scroll_pixel_position: gpui::Point<Pixels>,
8744 content_origin: gpui::Point<Pixels>,
8745 editor_width: Pixels,
8746 window: &mut Window,
8747 cx: &mut App,
8748 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8749 let target_line_end = DisplayPoint::new(
8750 target_display_point.row(),
8751 editor_snapshot.line_len(target_display_point.row()),
8752 );
8753
8754 let mut element = self
8755 .render_edit_prediction_line_popover(label, None, window, cx)?
8756 .into_any();
8757
8758 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8759
8760 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8761
8762 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8763 let mut origin = start_point
8764 + line_origin
8765 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8766 origin.x = origin.x.max(content_origin.x);
8767
8768 let max_x = content_origin.x + editor_width - size.width;
8769
8770 if origin.x > max_x {
8771 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8772
8773 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8774 origin.y += offset;
8775 IconName::ArrowUp
8776 } else {
8777 origin.y -= offset;
8778 IconName::ArrowDown
8779 };
8780
8781 element = self
8782 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8783 .into_any();
8784
8785 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8786
8787 origin.x = content_origin.x + editor_width - size.width - px(2.);
8788 }
8789
8790 element.prepaint_at(origin, window, cx);
8791 Some((element, origin))
8792 }
8793
8794 fn render_edit_prediction_diff_popover(
8795 self: &Editor,
8796 text_bounds: &Bounds<Pixels>,
8797 content_origin: gpui::Point<Pixels>,
8798 right_margin: Pixels,
8799 editor_snapshot: &EditorSnapshot,
8800 visible_row_range: Range<DisplayRow>,
8801 line_layouts: &[LineWithInvisibles],
8802 line_height: Pixels,
8803 scroll_pixel_position: gpui::Point<Pixels>,
8804 newest_selection_head: Option<DisplayPoint>,
8805 editor_width: Pixels,
8806 style: &EditorStyle,
8807 edits: &Vec<(Range<Anchor>, String)>,
8808 edit_preview: &Option<language::EditPreview>,
8809 snapshot: &language::BufferSnapshot,
8810 window: &mut Window,
8811 cx: &mut App,
8812 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8813 let edit_start = edits
8814 .first()
8815 .unwrap()
8816 .0
8817 .start
8818 .to_display_point(editor_snapshot);
8819 let edit_end = edits
8820 .last()
8821 .unwrap()
8822 .0
8823 .end
8824 .to_display_point(editor_snapshot);
8825
8826 let is_visible = visible_row_range.contains(&edit_start.row())
8827 || visible_row_range.contains(&edit_end.row());
8828 if !is_visible {
8829 return None;
8830 }
8831
8832 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8833 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8834 } else {
8835 // Fallback for providers without edit_preview
8836 crate::edit_prediction_fallback_text(edits, cx)
8837 };
8838
8839 let styled_text = highlighted_edits.to_styled_text(&style.text);
8840 let line_count = highlighted_edits.text.lines().count();
8841
8842 const BORDER_WIDTH: Pixels = px(1.);
8843
8844 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8845 let has_keybind = keybind.is_some();
8846
8847 let mut element = h_flex()
8848 .items_start()
8849 .child(
8850 h_flex()
8851 .bg(cx.theme().colors().editor_background)
8852 .border(BORDER_WIDTH)
8853 .shadow_xs()
8854 .border_color(cx.theme().colors().border)
8855 .rounded_l_lg()
8856 .when(line_count > 1, |el| el.rounded_br_lg())
8857 .pr_1()
8858 .child(styled_text),
8859 )
8860 .child(
8861 h_flex()
8862 .h(line_height + BORDER_WIDTH * 2.)
8863 .px_1p5()
8864 .gap_1()
8865 // Workaround: For some reason, there's a gap if we don't do this
8866 .ml(-BORDER_WIDTH)
8867 .shadow(vec![gpui::BoxShadow {
8868 color: gpui::black().opacity(0.05),
8869 offset: point(px(1.), px(1.)),
8870 blur_radius: px(2.),
8871 spread_radius: px(0.),
8872 }])
8873 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8874 .border(BORDER_WIDTH)
8875 .border_color(cx.theme().colors().border)
8876 .rounded_r_lg()
8877 .id("edit_prediction_diff_popover_keybind")
8878 .when(!has_keybind, |el| {
8879 let status_colors = cx.theme().status();
8880
8881 el.bg(status_colors.error_background)
8882 .border_color(status_colors.error.opacity(0.6))
8883 .child(Icon::new(IconName::Info).color(Color::Error))
8884 .cursor_default()
8885 .hoverable_tooltip(move |_window, cx| {
8886 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8887 })
8888 })
8889 .children(keybind),
8890 )
8891 .into_any();
8892
8893 let longest_row =
8894 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8895 let longest_line_width = if visible_row_range.contains(&longest_row) {
8896 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8897 } else {
8898 layout_line(
8899 longest_row,
8900 editor_snapshot,
8901 style,
8902 editor_width,
8903 |_| false,
8904 window,
8905 cx,
8906 )
8907 .width
8908 };
8909
8910 let viewport_bounds =
8911 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8912 right: -right_margin,
8913 ..Default::default()
8914 });
8915
8916 let x_after_longest =
8917 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8918 - scroll_pixel_position.x;
8919
8920 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8921
8922 // Fully visible if it can be displayed within the window (allow overlapping other
8923 // panes). However, this is only allowed if the popover starts within text_bounds.
8924 let can_position_to_the_right = x_after_longest < text_bounds.right()
8925 && x_after_longest + element_bounds.width < viewport_bounds.right();
8926
8927 let mut origin = if can_position_to_the_right {
8928 point(
8929 x_after_longest,
8930 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8931 - scroll_pixel_position.y,
8932 )
8933 } else {
8934 let cursor_row = newest_selection_head.map(|head| head.row());
8935 let above_edit = edit_start
8936 .row()
8937 .0
8938 .checked_sub(line_count as u32)
8939 .map(DisplayRow);
8940 let below_edit = Some(edit_end.row() + 1);
8941 let above_cursor =
8942 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8943 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8944
8945 // Place the edit popover adjacent to the edit if there is a location
8946 // available that is onscreen and does not obscure the cursor. Otherwise,
8947 // place it adjacent to the cursor.
8948 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8949 .into_iter()
8950 .flatten()
8951 .find(|&start_row| {
8952 let end_row = start_row + line_count as u32;
8953 visible_row_range.contains(&start_row)
8954 && visible_row_range.contains(&end_row)
8955 && cursor_row
8956 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
8957 })?;
8958
8959 content_origin
8960 + point(
8961 -scroll_pixel_position.x,
8962 row_target.as_f32() * line_height - scroll_pixel_position.y,
8963 )
8964 };
8965
8966 origin.x -= BORDER_WIDTH;
8967
8968 window.defer_draw(element, origin, 1);
8969
8970 // Do not return an element, since it will already be drawn due to defer_draw.
8971 None
8972 }
8973
8974 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8975 px(30.)
8976 }
8977
8978 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8979 if self.read_only(cx) {
8980 cx.theme().players().read_only()
8981 } else {
8982 self.style.as_ref().unwrap().local_player
8983 }
8984 }
8985
8986 fn render_edit_prediction_accept_keybind(
8987 &self,
8988 window: &mut Window,
8989 cx: &App,
8990 ) -> Option<AnyElement> {
8991 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8992 let accept_keystroke = accept_binding.keystroke()?;
8993
8994 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8995
8996 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8997 Color::Accent
8998 } else {
8999 Color::Muted
9000 };
9001
9002 h_flex()
9003 .px_0p5()
9004 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9005 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9006 .text_size(TextSize::XSmall.rems(cx))
9007 .child(h_flex().children(ui::render_modifiers(
9008 &accept_keystroke.modifiers,
9009 PlatformStyle::platform(),
9010 Some(modifiers_color),
9011 Some(IconSize::XSmall.rems().into()),
9012 true,
9013 )))
9014 .when(is_platform_style_mac, |parent| {
9015 parent.child(accept_keystroke.key.clone())
9016 })
9017 .when(!is_platform_style_mac, |parent| {
9018 parent.child(
9019 Key::new(
9020 util::capitalize(&accept_keystroke.key),
9021 Some(Color::Default),
9022 )
9023 .size(Some(IconSize::XSmall.rems().into())),
9024 )
9025 })
9026 .into_any()
9027 .into()
9028 }
9029
9030 fn render_edit_prediction_line_popover(
9031 &self,
9032 label: impl Into<SharedString>,
9033 icon: Option<IconName>,
9034 window: &mut Window,
9035 cx: &App,
9036 ) -> Option<Stateful<Div>> {
9037 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9038
9039 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9040 let has_keybind = keybind.is_some();
9041
9042 let result = h_flex()
9043 .id("ep-line-popover")
9044 .py_0p5()
9045 .pl_1()
9046 .pr(padding_right)
9047 .gap_1()
9048 .rounded_md()
9049 .border_1()
9050 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9051 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9052 .shadow_xs()
9053 .when(!has_keybind, |el| {
9054 let status_colors = cx.theme().status();
9055
9056 el.bg(status_colors.error_background)
9057 .border_color(status_colors.error.opacity(0.6))
9058 .pl_2()
9059 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9060 .cursor_default()
9061 .hoverable_tooltip(move |_window, cx| {
9062 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9063 })
9064 })
9065 .children(keybind)
9066 .child(
9067 Label::new(label)
9068 .size(LabelSize::Small)
9069 .when(!has_keybind, |el| {
9070 el.color(cx.theme().status().error.into()).strikethrough()
9071 }),
9072 )
9073 .when(!has_keybind, |el| {
9074 el.child(
9075 h_flex().ml_1().child(
9076 Icon::new(IconName::Info)
9077 .size(IconSize::Small)
9078 .color(cx.theme().status().error.into()),
9079 ),
9080 )
9081 })
9082 .when_some(icon, |element, icon| {
9083 element.child(
9084 div()
9085 .mt(px(1.5))
9086 .child(Icon::new(icon).size(IconSize::Small)),
9087 )
9088 });
9089
9090 Some(result)
9091 }
9092
9093 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9094 let accent_color = cx.theme().colors().text_accent;
9095 let editor_bg_color = cx.theme().colors().editor_background;
9096 editor_bg_color.blend(accent_color.opacity(0.1))
9097 }
9098
9099 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9100 let accent_color = cx.theme().colors().text_accent;
9101 let editor_bg_color = cx.theme().colors().editor_background;
9102 editor_bg_color.blend(accent_color.opacity(0.6))
9103 }
9104 fn get_prediction_provider_icon_name(
9105 provider: &Option<RegisteredEditPredictionProvider>,
9106 ) -> IconName {
9107 match provider {
9108 Some(provider) => match provider.provider.name() {
9109 "copilot" => IconName::Copilot,
9110 "supermaven" => IconName::Supermaven,
9111 _ => IconName::ZedPredict,
9112 },
9113 None => IconName::ZedPredict,
9114 }
9115 }
9116
9117 fn render_edit_prediction_cursor_popover(
9118 &self,
9119 min_width: Pixels,
9120 max_width: Pixels,
9121 cursor_point: Point,
9122 style: &EditorStyle,
9123 accept_keystroke: Option<&gpui::Keystroke>,
9124 _window: &Window,
9125 cx: &mut Context<Editor>,
9126 ) -> Option<AnyElement> {
9127 let provider = self.edit_prediction_provider.as_ref()?;
9128 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9129
9130 if provider.provider.needs_terms_acceptance(cx) {
9131 return Some(
9132 h_flex()
9133 .min_w(min_width)
9134 .flex_1()
9135 .px_2()
9136 .py_1()
9137 .gap_3()
9138 .elevation_2(cx)
9139 .hover(|style| style.bg(cx.theme().colors().element_hover))
9140 .id("accept-terms")
9141 .cursor_pointer()
9142 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9143 .on_click(cx.listener(|this, _event, window, cx| {
9144 cx.stop_propagation();
9145 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9146 window.dispatch_action(
9147 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9148 cx,
9149 );
9150 }))
9151 .child(
9152 h_flex()
9153 .flex_1()
9154 .gap_2()
9155 .child(Icon::new(provider_icon))
9156 .child(Label::new("Accept Terms of Service"))
9157 .child(div().w_full())
9158 .child(
9159 Icon::new(IconName::ArrowUpRight)
9160 .color(Color::Muted)
9161 .size(IconSize::Small),
9162 )
9163 .into_any_element(),
9164 )
9165 .into_any(),
9166 );
9167 }
9168
9169 let is_refreshing = provider.provider.is_refreshing(cx);
9170
9171 fn pending_completion_container(icon: IconName) -> Div {
9172 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9173 }
9174
9175 let completion = match &self.active_edit_prediction {
9176 Some(prediction) => {
9177 if !self.has_visible_completions_menu() {
9178 const RADIUS: Pixels = px(6.);
9179 const BORDER_WIDTH: Pixels = px(1.);
9180
9181 return Some(
9182 h_flex()
9183 .elevation_2(cx)
9184 .border(BORDER_WIDTH)
9185 .border_color(cx.theme().colors().border)
9186 .when(accept_keystroke.is_none(), |el| {
9187 el.border_color(cx.theme().status().error)
9188 })
9189 .rounded(RADIUS)
9190 .rounded_tl(px(0.))
9191 .overflow_hidden()
9192 .child(div().px_1p5().child(match &prediction.completion {
9193 EditPrediction::Move { target, snapshot } => {
9194 use text::ToPoint as _;
9195 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9196 {
9197 Icon::new(IconName::ZedPredictDown)
9198 } else {
9199 Icon::new(IconName::ZedPredictUp)
9200 }
9201 }
9202 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9203 }))
9204 .child(
9205 h_flex()
9206 .gap_1()
9207 .py_1()
9208 .px_2()
9209 .rounded_r(RADIUS - BORDER_WIDTH)
9210 .border_l_1()
9211 .border_color(cx.theme().colors().border)
9212 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9213 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9214 el.child(
9215 Label::new("Hold")
9216 .size(LabelSize::Small)
9217 .when(accept_keystroke.is_none(), |el| {
9218 el.strikethrough()
9219 })
9220 .line_height_style(LineHeightStyle::UiLabel),
9221 )
9222 })
9223 .id("edit_prediction_cursor_popover_keybind")
9224 .when(accept_keystroke.is_none(), |el| {
9225 let status_colors = cx.theme().status();
9226
9227 el.bg(status_colors.error_background)
9228 .border_color(status_colors.error.opacity(0.6))
9229 .child(Icon::new(IconName::Info).color(Color::Error))
9230 .cursor_default()
9231 .hoverable_tooltip(move |_window, cx| {
9232 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9233 .into()
9234 })
9235 })
9236 .when_some(
9237 accept_keystroke.as_ref(),
9238 |el, accept_keystroke| {
9239 el.child(h_flex().children(ui::render_modifiers(
9240 &accept_keystroke.modifiers,
9241 PlatformStyle::platform(),
9242 Some(Color::Default),
9243 Some(IconSize::XSmall.rems().into()),
9244 false,
9245 )))
9246 },
9247 ),
9248 )
9249 .into_any(),
9250 );
9251 }
9252
9253 self.render_edit_prediction_cursor_popover_preview(
9254 prediction,
9255 cursor_point,
9256 style,
9257 cx,
9258 )?
9259 }
9260
9261 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9262 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9263 stale_completion,
9264 cursor_point,
9265 style,
9266 cx,
9267 )?,
9268
9269 None => pending_completion_container(provider_icon)
9270 .child(Label::new("...").size(LabelSize::Small)),
9271 },
9272
9273 None => pending_completion_container(provider_icon)
9274 .child(Label::new("...").size(LabelSize::Small)),
9275 };
9276
9277 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9278 completion
9279 .with_animation(
9280 "loading-completion",
9281 Animation::new(Duration::from_secs(2))
9282 .repeat()
9283 .with_easing(pulsating_between(0.4, 0.8)),
9284 |label, delta| label.opacity(delta),
9285 )
9286 .into_any_element()
9287 } else {
9288 completion.into_any_element()
9289 };
9290
9291 let has_completion = self.active_edit_prediction.is_some();
9292
9293 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9294 Some(
9295 h_flex()
9296 .min_w(min_width)
9297 .max_w(max_width)
9298 .flex_1()
9299 .elevation_2(cx)
9300 .border_color(cx.theme().colors().border)
9301 .child(
9302 div()
9303 .flex_1()
9304 .py_1()
9305 .px_2()
9306 .overflow_hidden()
9307 .child(completion),
9308 )
9309 .when_some(accept_keystroke, |el, accept_keystroke| {
9310 if !accept_keystroke.modifiers.modified() {
9311 return el;
9312 }
9313
9314 el.child(
9315 h_flex()
9316 .h_full()
9317 .border_l_1()
9318 .rounded_r_lg()
9319 .border_color(cx.theme().colors().border)
9320 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9321 .gap_1()
9322 .py_1()
9323 .px_2()
9324 .child(
9325 h_flex()
9326 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9327 .when(is_platform_style_mac, |parent| parent.gap_1())
9328 .child(h_flex().children(ui::render_modifiers(
9329 &accept_keystroke.modifiers,
9330 PlatformStyle::platform(),
9331 Some(if !has_completion {
9332 Color::Muted
9333 } else {
9334 Color::Default
9335 }),
9336 None,
9337 false,
9338 ))),
9339 )
9340 .child(Label::new("Preview").into_any_element())
9341 .opacity(if has_completion { 1.0 } else { 0.4 }),
9342 )
9343 })
9344 .into_any(),
9345 )
9346 }
9347
9348 fn render_edit_prediction_cursor_popover_preview(
9349 &self,
9350 completion: &EditPredictionState,
9351 cursor_point: Point,
9352 style: &EditorStyle,
9353 cx: &mut Context<Editor>,
9354 ) -> Option<Div> {
9355 use text::ToPoint as _;
9356
9357 fn render_relative_row_jump(
9358 prefix: impl Into<String>,
9359 current_row: u32,
9360 target_row: u32,
9361 ) -> Div {
9362 let (row_diff, arrow) = if target_row < current_row {
9363 (current_row - target_row, IconName::ArrowUp)
9364 } else {
9365 (target_row - current_row, IconName::ArrowDown)
9366 };
9367
9368 h_flex()
9369 .child(
9370 Label::new(format!("{}{}", prefix.into(), row_diff))
9371 .color(Color::Muted)
9372 .size(LabelSize::Small),
9373 )
9374 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9375 }
9376
9377 let supports_jump = self
9378 .edit_prediction_provider
9379 .as_ref()
9380 .map(|provider| provider.provider.supports_jump_to_edit())
9381 .unwrap_or(true);
9382
9383 match &completion.completion {
9384 EditPrediction::Move {
9385 target, snapshot, ..
9386 } => {
9387 if !supports_jump {
9388 return None;
9389 }
9390
9391 Some(
9392 h_flex()
9393 .px_2()
9394 .gap_2()
9395 .flex_1()
9396 .child(
9397 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9398 Icon::new(IconName::ZedPredictDown)
9399 } else {
9400 Icon::new(IconName::ZedPredictUp)
9401 },
9402 )
9403 .child(Label::new("Jump to Edit")),
9404 )
9405 }
9406
9407 EditPrediction::Edit {
9408 edits,
9409 edit_preview,
9410 snapshot,
9411 display_mode: _,
9412 } => {
9413 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9414
9415 let (highlighted_edits, has_more_lines) =
9416 if let Some(edit_preview) = edit_preview.as_ref() {
9417 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9418 .first_line_preview()
9419 } else {
9420 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9421 };
9422
9423 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9424 .with_default_highlights(&style.text, highlighted_edits.highlights);
9425
9426 let preview = h_flex()
9427 .gap_1()
9428 .min_w_16()
9429 .child(styled_text)
9430 .when(has_more_lines, |parent| parent.child("…"));
9431
9432 let left = if supports_jump && first_edit_row != cursor_point.row {
9433 render_relative_row_jump("", cursor_point.row, first_edit_row)
9434 .into_any_element()
9435 } else {
9436 let icon_name =
9437 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9438 Icon::new(icon_name).into_any_element()
9439 };
9440
9441 Some(
9442 h_flex()
9443 .h_full()
9444 .flex_1()
9445 .gap_2()
9446 .pr_1()
9447 .overflow_x_hidden()
9448 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9449 .child(left)
9450 .child(preview),
9451 )
9452 }
9453 }
9454 }
9455
9456 pub fn render_context_menu(
9457 &self,
9458 style: &EditorStyle,
9459 max_height_in_lines: u32,
9460 window: &mut Window,
9461 cx: &mut Context<Editor>,
9462 ) -> Option<AnyElement> {
9463 let menu = self.context_menu.borrow();
9464 let menu = menu.as_ref()?;
9465 if !menu.visible() {
9466 return None;
9467 };
9468 Some(menu.render(style, max_height_in_lines, window, cx))
9469 }
9470
9471 fn render_context_menu_aside(
9472 &mut self,
9473 max_size: Size<Pixels>,
9474 window: &mut Window,
9475 cx: &mut Context<Editor>,
9476 ) -> Option<AnyElement> {
9477 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9478 if menu.visible() {
9479 menu.render_aside(max_size, window, cx)
9480 } else {
9481 None
9482 }
9483 })
9484 }
9485
9486 fn hide_context_menu(
9487 &mut self,
9488 window: &mut Window,
9489 cx: &mut Context<Self>,
9490 ) -> Option<CodeContextMenu> {
9491 cx.notify();
9492 self.completion_tasks.clear();
9493 let context_menu = self.context_menu.borrow_mut().take();
9494 self.stale_edit_prediction_in_menu.take();
9495 self.update_visible_edit_prediction(window, cx);
9496 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9497 && let Some(completion_provider) = &self.completion_provider
9498 {
9499 completion_provider.selection_changed(None, window, cx);
9500 }
9501 context_menu
9502 }
9503
9504 fn show_snippet_choices(
9505 &mut self,
9506 choices: &Vec<String>,
9507 selection: Range<Anchor>,
9508 cx: &mut Context<Self>,
9509 ) {
9510 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9511 (Some(a), Some(b)) if a == b => a,
9512 _ => {
9513 log::error!("expected anchor range to have matching buffer IDs");
9514 return;
9515 }
9516 };
9517 let multi_buffer = self.buffer().read(cx);
9518 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9519 return;
9520 };
9521
9522 let id = post_inc(&mut self.next_completion_id);
9523 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9524 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9525 CompletionsMenu::new_snippet_choices(
9526 id,
9527 true,
9528 choices,
9529 selection,
9530 buffer,
9531 snippet_sort_order,
9532 ),
9533 ));
9534 }
9535
9536 pub fn insert_snippet(
9537 &mut self,
9538 insertion_ranges: &[Range<usize>],
9539 snippet: Snippet,
9540 window: &mut Window,
9541 cx: &mut Context<Self>,
9542 ) -> Result<()> {
9543 struct Tabstop<T> {
9544 is_end_tabstop: bool,
9545 ranges: Vec<Range<T>>,
9546 choices: Option<Vec<String>>,
9547 }
9548
9549 let tabstops = self.buffer.update(cx, |buffer, cx| {
9550 let snippet_text: Arc<str> = snippet.text.clone().into();
9551 let edits = insertion_ranges
9552 .iter()
9553 .cloned()
9554 .map(|range| (range, snippet_text.clone()));
9555 let autoindent_mode = AutoindentMode::Block {
9556 original_indent_columns: Vec::new(),
9557 };
9558 buffer.edit(edits, Some(autoindent_mode), cx);
9559
9560 let snapshot = &*buffer.read(cx);
9561 let snippet = &snippet;
9562 snippet
9563 .tabstops
9564 .iter()
9565 .map(|tabstop| {
9566 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9567 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9568 });
9569 let mut tabstop_ranges = tabstop
9570 .ranges
9571 .iter()
9572 .flat_map(|tabstop_range| {
9573 let mut delta = 0_isize;
9574 insertion_ranges.iter().map(move |insertion_range| {
9575 let insertion_start = insertion_range.start as isize + delta;
9576 delta +=
9577 snippet.text.len() as isize - insertion_range.len() as isize;
9578
9579 let start = ((insertion_start + tabstop_range.start) as usize)
9580 .min(snapshot.len());
9581 let end = ((insertion_start + tabstop_range.end) as usize)
9582 .min(snapshot.len());
9583 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9584 })
9585 })
9586 .collect::<Vec<_>>();
9587 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9588
9589 Tabstop {
9590 is_end_tabstop,
9591 ranges: tabstop_ranges,
9592 choices: tabstop.choices.clone(),
9593 }
9594 })
9595 .collect::<Vec<_>>()
9596 });
9597 if let Some(tabstop) = tabstops.first() {
9598 self.change_selections(Default::default(), window, cx, |s| {
9599 // Reverse order so that the first range is the newest created selection.
9600 // Completions will use it and autoscroll will prioritize it.
9601 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9602 });
9603
9604 if let Some(choices) = &tabstop.choices
9605 && let Some(selection) = tabstop.ranges.first()
9606 {
9607 self.show_snippet_choices(choices, selection.clone(), cx)
9608 }
9609
9610 // If we're already at the last tabstop and it's at the end of the snippet,
9611 // we're done, we don't need to keep the state around.
9612 if !tabstop.is_end_tabstop {
9613 let choices = tabstops
9614 .iter()
9615 .map(|tabstop| tabstop.choices.clone())
9616 .collect();
9617
9618 let ranges = tabstops
9619 .into_iter()
9620 .map(|tabstop| tabstop.ranges)
9621 .collect::<Vec<_>>();
9622
9623 self.snippet_stack.push(SnippetState {
9624 active_index: 0,
9625 ranges,
9626 choices,
9627 });
9628 }
9629
9630 // Check whether the just-entered snippet ends with an auto-closable bracket.
9631 if self.autoclose_regions.is_empty() {
9632 let snapshot = self.buffer.read(cx).snapshot(cx);
9633 let mut all_selections = self.selections.all::<Point>(cx);
9634 for selection in &mut all_selections {
9635 let selection_head = selection.head();
9636 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9637 continue;
9638 };
9639
9640 let mut bracket_pair = None;
9641 let max_lookup_length = scope
9642 .brackets()
9643 .map(|(pair, _)| {
9644 pair.start
9645 .as_str()
9646 .chars()
9647 .count()
9648 .max(pair.end.as_str().chars().count())
9649 })
9650 .max();
9651 if let Some(max_lookup_length) = max_lookup_length {
9652 let next_text = snapshot
9653 .chars_at(selection_head)
9654 .take(max_lookup_length)
9655 .collect::<String>();
9656 let prev_text = snapshot
9657 .reversed_chars_at(selection_head)
9658 .take(max_lookup_length)
9659 .collect::<String>();
9660
9661 for (pair, enabled) in scope.brackets() {
9662 if enabled
9663 && pair.close
9664 && prev_text.starts_with(pair.start.as_str())
9665 && next_text.starts_with(pair.end.as_str())
9666 {
9667 bracket_pair = Some(pair.clone());
9668 break;
9669 }
9670 }
9671 }
9672
9673 if let Some(pair) = bracket_pair {
9674 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9675 let autoclose_enabled =
9676 self.use_autoclose && snapshot_settings.use_autoclose;
9677 if autoclose_enabled {
9678 let start = snapshot.anchor_after(selection_head);
9679 let end = snapshot.anchor_after(selection_head);
9680 self.autoclose_regions.push(AutocloseRegion {
9681 selection_id: selection.id,
9682 range: start..end,
9683 pair,
9684 });
9685 }
9686 }
9687 }
9688 }
9689 }
9690 Ok(())
9691 }
9692
9693 pub fn move_to_next_snippet_tabstop(
9694 &mut self,
9695 window: &mut Window,
9696 cx: &mut Context<Self>,
9697 ) -> bool {
9698 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9699 }
9700
9701 pub fn move_to_prev_snippet_tabstop(
9702 &mut self,
9703 window: &mut Window,
9704 cx: &mut Context<Self>,
9705 ) -> bool {
9706 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9707 }
9708
9709 pub fn move_to_snippet_tabstop(
9710 &mut self,
9711 bias: Bias,
9712 window: &mut Window,
9713 cx: &mut Context<Self>,
9714 ) -> bool {
9715 if let Some(mut snippet) = self.snippet_stack.pop() {
9716 match bias {
9717 Bias::Left => {
9718 if snippet.active_index > 0 {
9719 snippet.active_index -= 1;
9720 } else {
9721 self.snippet_stack.push(snippet);
9722 return false;
9723 }
9724 }
9725 Bias::Right => {
9726 if snippet.active_index + 1 < snippet.ranges.len() {
9727 snippet.active_index += 1;
9728 } else {
9729 self.snippet_stack.push(snippet);
9730 return false;
9731 }
9732 }
9733 }
9734 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9735 self.change_selections(Default::default(), window, cx, |s| {
9736 // Reverse order so that the first range is the newest created selection.
9737 // Completions will use it and autoscroll will prioritize it.
9738 s.select_ranges(current_ranges.iter().rev().cloned())
9739 });
9740
9741 if let Some(choices) = &snippet.choices[snippet.active_index]
9742 && let Some(selection) = current_ranges.first()
9743 {
9744 self.show_snippet_choices(choices, selection.clone(), cx);
9745 }
9746
9747 // If snippet state is not at the last tabstop, push it back on the stack
9748 if snippet.active_index + 1 < snippet.ranges.len() {
9749 self.snippet_stack.push(snippet);
9750 }
9751 return true;
9752 }
9753 }
9754
9755 false
9756 }
9757
9758 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9759 self.transact(window, cx, |this, window, cx| {
9760 this.select_all(&SelectAll, window, cx);
9761 this.insert("", window, cx);
9762 });
9763 }
9764
9765 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9767 self.transact(window, cx, |this, window, cx| {
9768 this.select_autoclose_pair(window, cx);
9769 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9770 if !this.linked_edit_ranges.is_empty() {
9771 let selections = this.selections.all::<MultiBufferPoint>(cx);
9772 let snapshot = this.buffer.read(cx).snapshot(cx);
9773
9774 for selection in selections.iter() {
9775 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9776 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9777 if selection_start.buffer_id != selection_end.buffer_id {
9778 continue;
9779 }
9780 if let Some(ranges) =
9781 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9782 {
9783 for (buffer, entries) in ranges {
9784 linked_ranges.entry(buffer).or_default().extend(entries);
9785 }
9786 }
9787 }
9788 }
9789
9790 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9791 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9792 for selection in &mut selections {
9793 if selection.is_empty() {
9794 let old_head = selection.head();
9795 let mut new_head =
9796 movement::left(&display_map, old_head.to_display_point(&display_map))
9797 .to_point(&display_map);
9798 if let Some((buffer, line_buffer_range)) = display_map
9799 .buffer_snapshot
9800 .buffer_line_for_row(MultiBufferRow(old_head.row))
9801 {
9802 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9803 let indent_len = match indent_size.kind {
9804 IndentKind::Space => {
9805 buffer.settings_at(line_buffer_range.start, cx).tab_size
9806 }
9807 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9808 };
9809 if old_head.column <= indent_size.len && old_head.column > 0 {
9810 let indent_len = indent_len.get();
9811 new_head = cmp::min(
9812 new_head,
9813 MultiBufferPoint::new(
9814 old_head.row,
9815 ((old_head.column - 1) / indent_len) * indent_len,
9816 ),
9817 );
9818 }
9819 }
9820
9821 selection.set_head(new_head, SelectionGoal::None);
9822 }
9823 }
9824
9825 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9826 this.insert("", window, cx);
9827 let empty_str: Arc<str> = Arc::from("");
9828 for (buffer, edits) in linked_ranges {
9829 let snapshot = buffer.read(cx).snapshot();
9830 use text::ToPoint as TP;
9831
9832 let edits = edits
9833 .into_iter()
9834 .map(|range| {
9835 let end_point = TP::to_point(&range.end, &snapshot);
9836 let mut start_point = TP::to_point(&range.start, &snapshot);
9837
9838 if end_point == start_point {
9839 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9840 .saturating_sub(1);
9841 start_point =
9842 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9843 };
9844
9845 (start_point..end_point, empty_str.clone())
9846 })
9847 .sorted_by_key(|(range, _)| range.start)
9848 .collect::<Vec<_>>();
9849 buffer.update(cx, |this, cx| {
9850 this.edit(edits, None, cx);
9851 })
9852 }
9853 this.refresh_edit_prediction(true, false, window, cx);
9854 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9855 });
9856 }
9857
9858 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9860 self.transact(window, cx, |this, window, cx| {
9861 this.change_selections(Default::default(), window, cx, |s| {
9862 s.move_with(|map, selection| {
9863 if selection.is_empty() {
9864 let cursor = movement::right(map, selection.head());
9865 selection.end = cursor;
9866 selection.reversed = true;
9867 selection.goal = SelectionGoal::None;
9868 }
9869 })
9870 });
9871 this.insert("", window, cx);
9872 this.refresh_edit_prediction(true, false, window, cx);
9873 });
9874 }
9875
9876 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9877 if self.mode.is_single_line() {
9878 cx.propagate();
9879 return;
9880 }
9881
9882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9883 if self.move_to_prev_snippet_tabstop(window, cx) {
9884 return;
9885 }
9886 self.outdent(&Outdent, window, cx);
9887 }
9888
9889 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9890 if self.mode.is_single_line() {
9891 cx.propagate();
9892 return;
9893 }
9894
9895 if self.move_to_next_snippet_tabstop(window, cx) {
9896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9897 return;
9898 }
9899 if self.read_only(cx) {
9900 return;
9901 }
9902 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9903 let mut selections = self.selections.all_adjusted(cx);
9904 let buffer = self.buffer.read(cx);
9905 let snapshot = buffer.snapshot(cx);
9906 let rows_iter = selections.iter().map(|s| s.head().row);
9907 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9908
9909 let has_some_cursor_in_whitespace = selections
9910 .iter()
9911 .filter(|selection| selection.is_empty())
9912 .any(|selection| {
9913 let cursor = selection.head();
9914 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9915 cursor.column < current_indent.len
9916 });
9917
9918 let mut edits = Vec::new();
9919 let mut prev_edited_row = 0;
9920 let mut row_delta = 0;
9921 for selection in &mut selections {
9922 if selection.start.row != prev_edited_row {
9923 row_delta = 0;
9924 }
9925 prev_edited_row = selection.end.row;
9926
9927 // If the selection is non-empty, then increase the indentation of the selected lines.
9928 if !selection.is_empty() {
9929 row_delta =
9930 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9931 continue;
9932 }
9933
9934 let cursor = selection.head();
9935 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9936 if let Some(suggested_indent) =
9937 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9938 {
9939 // Don't do anything if already at suggested indent
9940 // and there is any other cursor which is not
9941 if has_some_cursor_in_whitespace
9942 && cursor.column == current_indent.len
9943 && current_indent.len == suggested_indent.len
9944 {
9945 continue;
9946 }
9947
9948 // Adjust line and move cursor to suggested indent
9949 // if cursor is not at suggested indent
9950 if cursor.column < suggested_indent.len
9951 && cursor.column <= current_indent.len
9952 && current_indent.len <= suggested_indent.len
9953 {
9954 selection.start = Point::new(cursor.row, suggested_indent.len);
9955 selection.end = selection.start;
9956 if row_delta == 0 {
9957 edits.extend(Buffer::edit_for_indent_size_adjustment(
9958 cursor.row,
9959 current_indent,
9960 suggested_indent,
9961 ));
9962 row_delta = suggested_indent.len - current_indent.len;
9963 }
9964 continue;
9965 }
9966
9967 // If current indent is more than suggested indent
9968 // only move cursor to current indent and skip indent
9969 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9970 selection.start = Point::new(cursor.row, current_indent.len);
9971 selection.end = selection.start;
9972 continue;
9973 }
9974 }
9975
9976 // Otherwise, insert a hard or soft tab.
9977 let settings = buffer.language_settings_at(cursor, cx);
9978 let tab_size = if settings.hard_tabs {
9979 IndentSize::tab()
9980 } else {
9981 let tab_size = settings.tab_size.get();
9982 let indent_remainder = snapshot
9983 .text_for_range(Point::new(cursor.row, 0)..cursor)
9984 .flat_map(str::chars)
9985 .fold(row_delta % tab_size, |counter: u32, c| {
9986 if c == '\t' {
9987 0
9988 } else {
9989 (counter + 1) % tab_size
9990 }
9991 });
9992
9993 let chars_to_next_tab_stop = tab_size - indent_remainder;
9994 IndentSize::spaces(chars_to_next_tab_stop)
9995 };
9996 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9997 selection.end = selection.start;
9998 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9999 row_delta += tab_size.len;
10000 }
10001
10002 self.transact(window, cx, |this, window, cx| {
10003 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10004 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10005 this.refresh_edit_prediction(true, false, window, cx);
10006 });
10007 }
10008
10009 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10010 if self.read_only(cx) {
10011 return;
10012 }
10013 if self.mode.is_single_line() {
10014 cx.propagate();
10015 return;
10016 }
10017
10018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10019 let mut selections = self.selections.all::<Point>(cx);
10020 let mut prev_edited_row = 0;
10021 let mut row_delta = 0;
10022 let mut edits = Vec::new();
10023 let buffer = self.buffer.read(cx);
10024 let snapshot = buffer.snapshot(cx);
10025 for selection in &mut selections {
10026 if selection.start.row != prev_edited_row {
10027 row_delta = 0;
10028 }
10029 prev_edited_row = selection.end.row;
10030
10031 row_delta =
10032 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10033 }
10034
10035 self.transact(window, cx, |this, window, cx| {
10036 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10037 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10038 });
10039 }
10040
10041 fn indent_selection(
10042 buffer: &MultiBuffer,
10043 snapshot: &MultiBufferSnapshot,
10044 selection: &mut Selection<Point>,
10045 edits: &mut Vec<(Range<Point>, String)>,
10046 delta_for_start_row: u32,
10047 cx: &App,
10048 ) -> u32 {
10049 let settings = buffer.language_settings_at(selection.start, cx);
10050 let tab_size = settings.tab_size.get();
10051 let indent_kind = if settings.hard_tabs {
10052 IndentKind::Tab
10053 } else {
10054 IndentKind::Space
10055 };
10056 let mut start_row = selection.start.row;
10057 let mut end_row = selection.end.row + 1;
10058
10059 // If a selection ends at the beginning of a line, don't indent
10060 // that last line.
10061 if selection.end.column == 0 && selection.end.row > selection.start.row {
10062 end_row -= 1;
10063 }
10064
10065 // Avoid re-indenting a row that has already been indented by a
10066 // previous selection, but still update this selection's column
10067 // to reflect that indentation.
10068 if delta_for_start_row > 0 {
10069 start_row += 1;
10070 selection.start.column += delta_for_start_row;
10071 if selection.end.row == selection.start.row {
10072 selection.end.column += delta_for_start_row;
10073 }
10074 }
10075
10076 let mut delta_for_end_row = 0;
10077 let has_multiple_rows = start_row + 1 != end_row;
10078 for row in start_row..end_row {
10079 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10080 let indent_delta = match (current_indent.kind, indent_kind) {
10081 (IndentKind::Space, IndentKind::Space) => {
10082 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10083 IndentSize::spaces(columns_to_next_tab_stop)
10084 }
10085 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10086 (_, IndentKind::Tab) => IndentSize::tab(),
10087 };
10088
10089 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10090 0
10091 } else {
10092 selection.start.column
10093 };
10094 let row_start = Point::new(row, start);
10095 edits.push((
10096 row_start..row_start,
10097 indent_delta.chars().collect::<String>(),
10098 ));
10099
10100 // Update this selection's endpoints to reflect the indentation.
10101 if row == selection.start.row {
10102 selection.start.column += indent_delta.len;
10103 }
10104 if row == selection.end.row {
10105 selection.end.column += indent_delta.len;
10106 delta_for_end_row = indent_delta.len;
10107 }
10108 }
10109
10110 if selection.start.row == selection.end.row {
10111 delta_for_start_row + delta_for_end_row
10112 } else {
10113 delta_for_end_row
10114 }
10115 }
10116
10117 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10118 if self.read_only(cx) {
10119 return;
10120 }
10121 if self.mode.is_single_line() {
10122 cx.propagate();
10123 return;
10124 }
10125
10126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10128 let selections = self.selections.all::<Point>(cx);
10129 let mut deletion_ranges = Vec::new();
10130 let mut last_outdent = None;
10131 {
10132 let buffer = self.buffer.read(cx);
10133 let snapshot = buffer.snapshot(cx);
10134 for selection in &selections {
10135 let settings = buffer.language_settings_at(selection.start, cx);
10136 let tab_size = settings.tab_size.get();
10137 let mut rows = selection.spanned_rows(false, &display_map);
10138
10139 // Avoid re-outdenting a row that has already been outdented by a
10140 // previous selection.
10141 if let Some(last_row) = last_outdent
10142 && last_row == rows.start
10143 {
10144 rows.start = rows.start.next_row();
10145 }
10146 let has_multiple_rows = rows.len() > 1;
10147 for row in rows.iter_rows() {
10148 let indent_size = snapshot.indent_size_for_line(row);
10149 if indent_size.len > 0 {
10150 let deletion_len = match indent_size.kind {
10151 IndentKind::Space => {
10152 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10153 if columns_to_prev_tab_stop == 0 {
10154 tab_size
10155 } else {
10156 columns_to_prev_tab_stop
10157 }
10158 }
10159 IndentKind::Tab => 1,
10160 };
10161 let start = if has_multiple_rows
10162 || deletion_len > selection.start.column
10163 || indent_size.len < selection.start.column
10164 {
10165 0
10166 } else {
10167 selection.start.column - deletion_len
10168 };
10169 deletion_ranges.push(
10170 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10171 );
10172 last_outdent = Some(row);
10173 }
10174 }
10175 }
10176 }
10177
10178 self.transact(window, cx, |this, window, cx| {
10179 this.buffer.update(cx, |buffer, cx| {
10180 let empty_str: Arc<str> = Arc::default();
10181 buffer.edit(
10182 deletion_ranges
10183 .into_iter()
10184 .map(|range| (range, empty_str.clone())),
10185 None,
10186 cx,
10187 );
10188 });
10189 let selections = this.selections.all::<usize>(cx);
10190 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10191 });
10192 }
10193
10194 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10195 if self.read_only(cx) {
10196 return;
10197 }
10198 if self.mode.is_single_line() {
10199 cx.propagate();
10200 return;
10201 }
10202
10203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10204 let selections = self
10205 .selections
10206 .all::<usize>(cx)
10207 .into_iter()
10208 .map(|s| s.range());
10209
10210 self.transact(window, cx, |this, window, cx| {
10211 this.buffer.update(cx, |buffer, cx| {
10212 buffer.autoindent_ranges(selections, cx);
10213 });
10214 let selections = this.selections.all::<usize>(cx);
10215 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10216 });
10217 }
10218
10219 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10222 let selections = self.selections.all::<Point>(cx);
10223
10224 let mut new_cursors = Vec::new();
10225 let mut edit_ranges = Vec::new();
10226 let mut selections = selections.iter().peekable();
10227 while let Some(selection) = selections.next() {
10228 let mut rows = selection.spanned_rows(false, &display_map);
10229 let goal_display_column = selection.head().to_display_point(&display_map).column();
10230
10231 // Accumulate contiguous regions of rows that we want to delete.
10232 while let Some(next_selection) = selections.peek() {
10233 let next_rows = next_selection.spanned_rows(false, &display_map);
10234 if next_rows.start <= rows.end {
10235 rows.end = next_rows.end;
10236 selections.next().unwrap();
10237 } else {
10238 break;
10239 }
10240 }
10241
10242 let buffer = &display_map.buffer_snapshot;
10243 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10244 let edit_end;
10245 let cursor_buffer_row;
10246 if buffer.max_point().row >= rows.end.0 {
10247 // If there's a line after the range, delete the \n from the end of the row range
10248 // and position the cursor on the next line.
10249 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10250 cursor_buffer_row = rows.end;
10251 } else {
10252 // If there isn't a line after the range, delete the \n from the line before the
10253 // start of the row range and position the cursor there.
10254 edit_start = edit_start.saturating_sub(1);
10255 edit_end = buffer.len();
10256 cursor_buffer_row = rows.start.previous_row();
10257 }
10258
10259 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10260 *cursor.column_mut() =
10261 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10262
10263 new_cursors.push((
10264 selection.id,
10265 buffer.anchor_after(cursor.to_point(&display_map)),
10266 ));
10267 edit_ranges.push(edit_start..edit_end);
10268 }
10269
10270 self.transact(window, cx, |this, window, cx| {
10271 let buffer = this.buffer.update(cx, |buffer, cx| {
10272 let empty_str: Arc<str> = Arc::default();
10273 buffer.edit(
10274 edit_ranges
10275 .into_iter()
10276 .map(|range| (range, empty_str.clone())),
10277 None,
10278 cx,
10279 );
10280 buffer.snapshot(cx)
10281 });
10282 let new_selections = new_cursors
10283 .into_iter()
10284 .map(|(id, cursor)| {
10285 let cursor = cursor.to_point(&buffer);
10286 Selection {
10287 id,
10288 start: cursor,
10289 end: cursor,
10290 reversed: false,
10291 goal: SelectionGoal::None,
10292 }
10293 })
10294 .collect();
10295
10296 this.change_selections(Default::default(), window, cx, |s| {
10297 s.select(new_selections);
10298 });
10299 });
10300 }
10301
10302 pub fn join_lines_impl(
10303 &mut self,
10304 insert_whitespace: bool,
10305 window: &mut Window,
10306 cx: &mut Context<Self>,
10307 ) {
10308 if self.read_only(cx) {
10309 return;
10310 }
10311 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10312 for selection in self.selections.all::<Point>(cx) {
10313 let start = MultiBufferRow(selection.start.row);
10314 // Treat single line selections as if they include the next line. Otherwise this action
10315 // would do nothing for single line selections individual cursors.
10316 let end = if selection.start.row == selection.end.row {
10317 MultiBufferRow(selection.start.row + 1)
10318 } else {
10319 MultiBufferRow(selection.end.row)
10320 };
10321
10322 if let Some(last_row_range) = row_ranges.last_mut()
10323 && start <= last_row_range.end
10324 {
10325 last_row_range.end = end;
10326 continue;
10327 }
10328 row_ranges.push(start..end);
10329 }
10330
10331 let snapshot = self.buffer.read(cx).snapshot(cx);
10332 let mut cursor_positions = Vec::new();
10333 for row_range in &row_ranges {
10334 let anchor = snapshot.anchor_before(Point::new(
10335 row_range.end.previous_row().0,
10336 snapshot.line_len(row_range.end.previous_row()),
10337 ));
10338 cursor_positions.push(anchor..anchor);
10339 }
10340
10341 self.transact(window, cx, |this, window, cx| {
10342 for row_range in row_ranges.into_iter().rev() {
10343 for row in row_range.iter_rows().rev() {
10344 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10345 let next_line_row = row.next_row();
10346 let indent = snapshot.indent_size_for_line(next_line_row);
10347 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10348
10349 let replace =
10350 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10351 " "
10352 } else {
10353 ""
10354 };
10355
10356 this.buffer.update(cx, |buffer, cx| {
10357 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10358 });
10359 }
10360 }
10361
10362 this.change_selections(Default::default(), window, cx, |s| {
10363 s.select_anchor_ranges(cursor_positions)
10364 });
10365 });
10366 }
10367
10368 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10370 self.join_lines_impl(true, window, cx);
10371 }
10372
10373 pub fn sort_lines_case_sensitive(
10374 &mut self,
10375 _: &SortLinesCaseSensitive,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10380 }
10381
10382 pub fn sort_lines_by_length(
10383 &mut self,
10384 _: &SortLinesByLength,
10385 window: &mut Window,
10386 cx: &mut Context<Self>,
10387 ) {
10388 self.manipulate_immutable_lines(window, cx, |lines| {
10389 lines.sort_by_key(|&line| line.chars().count())
10390 })
10391 }
10392
10393 pub fn sort_lines_case_insensitive(
10394 &mut self,
10395 _: &SortLinesCaseInsensitive,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) {
10399 self.manipulate_immutable_lines(window, cx, |lines| {
10400 lines.sort_by_key(|line| line.to_lowercase())
10401 })
10402 }
10403
10404 pub fn unique_lines_case_insensitive(
10405 &mut self,
10406 _: &UniqueLinesCaseInsensitive,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) {
10410 self.manipulate_immutable_lines(window, cx, |lines| {
10411 let mut seen = HashSet::default();
10412 lines.retain(|line| seen.insert(line.to_lowercase()));
10413 })
10414 }
10415
10416 pub fn unique_lines_case_sensitive(
10417 &mut self,
10418 _: &UniqueLinesCaseSensitive,
10419 window: &mut Window,
10420 cx: &mut Context<Self>,
10421 ) {
10422 self.manipulate_immutable_lines(window, cx, |lines| {
10423 let mut seen = HashSet::default();
10424 lines.retain(|line| seen.insert(*line));
10425 })
10426 }
10427
10428 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10429 let Some(project) = self.project.clone() else {
10430 return;
10431 };
10432 self.reload(project, window, cx)
10433 .detach_and_notify_err(window, cx);
10434 }
10435
10436 pub fn restore_file(
10437 &mut self,
10438 _: &::git::RestoreFile,
10439 window: &mut Window,
10440 cx: &mut Context<Self>,
10441 ) {
10442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10443 let mut buffer_ids = HashSet::default();
10444 let snapshot = self.buffer().read(cx).snapshot(cx);
10445 for selection in self.selections.all::<usize>(cx) {
10446 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10447 }
10448
10449 let buffer = self.buffer().read(cx);
10450 let ranges = buffer_ids
10451 .into_iter()
10452 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10453 .collect::<Vec<_>>();
10454
10455 self.restore_hunks_in_ranges(ranges, window, cx);
10456 }
10457
10458 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10460 let selections = self
10461 .selections
10462 .all(cx)
10463 .into_iter()
10464 .map(|s| s.range())
10465 .collect();
10466 self.restore_hunks_in_ranges(selections, window, cx);
10467 }
10468
10469 pub fn restore_hunks_in_ranges(
10470 &mut self,
10471 ranges: Vec<Range<Point>>,
10472 window: &mut Window,
10473 cx: &mut Context<Editor>,
10474 ) {
10475 let mut revert_changes = HashMap::default();
10476 let chunk_by = self
10477 .snapshot(window, cx)
10478 .hunks_for_ranges(ranges)
10479 .into_iter()
10480 .chunk_by(|hunk| hunk.buffer_id);
10481 for (buffer_id, hunks) in &chunk_by {
10482 let hunks = hunks.collect::<Vec<_>>();
10483 for hunk in &hunks {
10484 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10485 }
10486 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10487 }
10488 drop(chunk_by);
10489 if !revert_changes.is_empty() {
10490 self.transact(window, cx, |editor, window, cx| {
10491 editor.restore(revert_changes, window, cx);
10492 });
10493 }
10494 }
10495
10496 pub fn open_active_item_in_terminal(
10497 &mut self,
10498 _: &OpenInTerminal,
10499 window: &mut Window,
10500 cx: &mut Context<Self>,
10501 ) {
10502 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10503 let project_path = buffer.read(cx).project_path(cx)?;
10504 let project = self.project()?.read(cx);
10505 let entry = project.entry_for_path(&project_path, cx)?;
10506 let parent = match &entry.canonical_path {
10507 Some(canonical_path) => canonical_path.to_path_buf(),
10508 None => project.absolute_path(&project_path, cx)?,
10509 }
10510 .parent()?
10511 .to_path_buf();
10512 Some(parent)
10513 }) {
10514 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10515 }
10516 }
10517
10518 fn set_breakpoint_context_menu(
10519 &mut self,
10520 display_row: DisplayRow,
10521 position: Option<Anchor>,
10522 clicked_point: gpui::Point<Pixels>,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) {
10526 let source = self
10527 .buffer
10528 .read(cx)
10529 .snapshot(cx)
10530 .anchor_before(Point::new(display_row.0, 0u32));
10531
10532 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10533
10534 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10535 self,
10536 source,
10537 clicked_point,
10538 context_menu,
10539 window,
10540 cx,
10541 );
10542 }
10543
10544 fn add_edit_breakpoint_block(
10545 &mut self,
10546 anchor: Anchor,
10547 breakpoint: &Breakpoint,
10548 edit_action: BreakpointPromptEditAction,
10549 window: &mut Window,
10550 cx: &mut Context<Self>,
10551 ) {
10552 let weak_editor = cx.weak_entity();
10553 let bp_prompt = cx.new(|cx| {
10554 BreakpointPromptEditor::new(
10555 weak_editor,
10556 anchor,
10557 breakpoint.clone(),
10558 edit_action,
10559 window,
10560 cx,
10561 )
10562 });
10563
10564 let height = bp_prompt.update(cx, |this, cx| {
10565 this.prompt
10566 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10567 });
10568 let cloned_prompt = bp_prompt.clone();
10569 let blocks = vec![BlockProperties {
10570 style: BlockStyle::Sticky,
10571 placement: BlockPlacement::Above(anchor),
10572 height: Some(height),
10573 render: Arc::new(move |cx| {
10574 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10575 cloned_prompt.clone().into_any_element()
10576 }),
10577 priority: 0,
10578 }];
10579
10580 let focus_handle = bp_prompt.focus_handle(cx);
10581 window.focus(&focus_handle);
10582
10583 let block_ids = self.insert_blocks(blocks, None, cx);
10584 bp_prompt.update(cx, |prompt, _| {
10585 prompt.add_block_ids(block_ids);
10586 });
10587 }
10588
10589 pub(crate) fn breakpoint_at_row(
10590 &self,
10591 row: u32,
10592 window: &mut Window,
10593 cx: &mut Context<Self>,
10594 ) -> Option<(Anchor, Breakpoint)> {
10595 let snapshot = self.snapshot(window, cx);
10596 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10597
10598 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10599 }
10600
10601 pub(crate) fn breakpoint_at_anchor(
10602 &self,
10603 breakpoint_position: Anchor,
10604 snapshot: &EditorSnapshot,
10605 cx: &mut Context<Self>,
10606 ) -> Option<(Anchor, Breakpoint)> {
10607 let project = self.project.clone()?;
10608
10609 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10610 snapshot
10611 .buffer_snapshot
10612 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10613 })?;
10614
10615 let enclosing_excerpt = breakpoint_position.excerpt_id;
10616 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10617 let buffer_snapshot = buffer.read(cx).snapshot();
10618
10619 let row = buffer_snapshot
10620 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10621 .row;
10622
10623 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10624 let anchor_end = snapshot
10625 .buffer_snapshot
10626 .anchor_after(Point::new(row, line_len));
10627
10628 self.breakpoint_store
10629 .as_ref()?
10630 .read_with(cx, |breakpoint_store, cx| {
10631 breakpoint_store
10632 .breakpoints(
10633 &buffer,
10634 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10635 &buffer_snapshot,
10636 cx,
10637 )
10638 .next()
10639 .and_then(|(bp, _)| {
10640 let breakpoint_row = buffer_snapshot
10641 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10642 .row;
10643
10644 if breakpoint_row == row {
10645 snapshot
10646 .buffer_snapshot
10647 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10648 .map(|position| (position, bp.bp.clone()))
10649 } else {
10650 None
10651 }
10652 })
10653 })
10654 }
10655
10656 pub fn edit_log_breakpoint(
10657 &mut self,
10658 _: &EditLogBreakpoint,
10659 window: &mut Window,
10660 cx: &mut Context<Self>,
10661 ) {
10662 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10663 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10664 message: None,
10665 state: BreakpointState::Enabled,
10666 condition: None,
10667 hit_condition: None,
10668 });
10669
10670 self.add_edit_breakpoint_block(
10671 anchor,
10672 &breakpoint,
10673 BreakpointPromptEditAction::Log,
10674 window,
10675 cx,
10676 );
10677 }
10678 }
10679
10680 fn breakpoints_at_cursors(
10681 &self,
10682 window: &mut Window,
10683 cx: &mut Context<Self>,
10684 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10685 let snapshot = self.snapshot(window, cx);
10686 let cursors = self
10687 .selections
10688 .disjoint_anchors()
10689 .iter()
10690 .map(|selection| {
10691 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10692
10693 let breakpoint_position = self
10694 .breakpoint_at_row(cursor_position.row, window, cx)
10695 .map(|bp| bp.0)
10696 .unwrap_or_else(|| {
10697 snapshot
10698 .display_snapshot
10699 .buffer_snapshot
10700 .anchor_after(Point::new(cursor_position.row, 0))
10701 });
10702
10703 let breakpoint = self
10704 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10705 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10706
10707 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10708 })
10709 // 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.
10710 .collect::<HashMap<Anchor, _>>();
10711
10712 cursors.into_iter().collect()
10713 }
10714
10715 pub fn enable_breakpoint(
10716 &mut self,
10717 _: &crate::actions::EnableBreakpoint,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) {
10721 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10722 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10723 continue;
10724 };
10725 self.edit_breakpoint_at_anchor(
10726 anchor,
10727 breakpoint,
10728 BreakpointEditAction::InvertState,
10729 cx,
10730 );
10731 }
10732 }
10733
10734 pub fn disable_breakpoint(
10735 &mut self,
10736 _: &crate::actions::DisableBreakpoint,
10737 window: &mut Window,
10738 cx: &mut Context<Self>,
10739 ) {
10740 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10741 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10742 continue;
10743 };
10744 self.edit_breakpoint_at_anchor(
10745 anchor,
10746 breakpoint,
10747 BreakpointEditAction::InvertState,
10748 cx,
10749 );
10750 }
10751 }
10752
10753 pub fn toggle_breakpoint(
10754 &mut self,
10755 _: &crate::actions::ToggleBreakpoint,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10760 if let Some(breakpoint) = breakpoint {
10761 self.edit_breakpoint_at_anchor(
10762 anchor,
10763 breakpoint,
10764 BreakpointEditAction::Toggle,
10765 cx,
10766 );
10767 } else {
10768 self.edit_breakpoint_at_anchor(
10769 anchor,
10770 Breakpoint::new_standard(),
10771 BreakpointEditAction::Toggle,
10772 cx,
10773 );
10774 }
10775 }
10776 }
10777
10778 pub fn edit_breakpoint_at_anchor(
10779 &mut self,
10780 breakpoint_position: Anchor,
10781 breakpoint: Breakpoint,
10782 edit_action: BreakpointEditAction,
10783 cx: &mut Context<Self>,
10784 ) {
10785 let Some(breakpoint_store) = &self.breakpoint_store else {
10786 return;
10787 };
10788
10789 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10790 if breakpoint_position == Anchor::min() {
10791 self.buffer()
10792 .read(cx)
10793 .excerpt_buffer_ids()
10794 .into_iter()
10795 .next()
10796 } else {
10797 None
10798 }
10799 }) else {
10800 return;
10801 };
10802
10803 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10804 return;
10805 };
10806
10807 breakpoint_store.update(cx, |breakpoint_store, cx| {
10808 breakpoint_store.toggle_breakpoint(
10809 buffer,
10810 BreakpointWithPosition {
10811 position: breakpoint_position.text_anchor,
10812 bp: breakpoint,
10813 },
10814 edit_action,
10815 cx,
10816 );
10817 });
10818
10819 cx.notify();
10820 }
10821
10822 #[cfg(any(test, feature = "test-support"))]
10823 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10824 self.breakpoint_store.clone()
10825 }
10826
10827 pub fn prepare_restore_change(
10828 &self,
10829 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10830 hunk: &MultiBufferDiffHunk,
10831 cx: &mut App,
10832 ) -> Option<()> {
10833 if hunk.is_created_file() {
10834 return None;
10835 }
10836 let buffer = self.buffer.read(cx);
10837 let diff = buffer.diff_for(hunk.buffer_id)?;
10838 let buffer = buffer.buffer(hunk.buffer_id)?;
10839 let buffer = buffer.read(cx);
10840 let original_text = diff
10841 .read(cx)
10842 .base_text()
10843 .as_rope()
10844 .slice(hunk.diff_base_byte_range.clone());
10845 let buffer_snapshot = buffer.snapshot();
10846 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10847 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10848 probe
10849 .0
10850 .start
10851 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10852 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10853 }) {
10854 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10855 Some(())
10856 } else {
10857 None
10858 }
10859 }
10860
10861 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10862 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10863 }
10864
10865 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10866 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10867 }
10868
10869 fn manipulate_lines<M>(
10870 &mut self,
10871 window: &mut Window,
10872 cx: &mut Context<Self>,
10873 mut manipulate: M,
10874 ) where
10875 M: FnMut(&str) -> LineManipulationResult,
10876 {
10877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10878
10879 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10880 let buffer = self.buffer.read(cx).snapshot(cx);
10881
10882 let mut edits = Vec::new();
10883
10884 let selections = self.selections.all::<Point>(cx);
10885 let mut selections = selections.iter().peekable();
10886 let mut contiguous_row_selections = Vec::new();
10887 let mut new_selections = Vec::new();
10888 let mut added_lines = 0;
10889 let mut removed_lines = 0;
10890
10891 while let Some(selection) = selections.next() {
10892 let (start_row, end_row) = consume_contiguous_rows(
10893 &mut contiguous_row_selections,
10894 selection,
10895 &display_map,
10896 &mut selections,
10897 );
10898
10899 let start_point = Point::new(start_row.0, 0);
10900 let end_point = Point::new(
10901 end_row.previous_row().0,
10902 buffer.line_len(end_row.previous_row()),
10903 );
10904 let text = buffer
10905 .text_for_range(start_point..end_point)
10906 .collect::<String>();
10907
10908 let LineManipulationResult {
10909 new_text,
10910 line_count_before,
10911 line_count_after,
10912 } = manipulate(&text);
10913
10914 edits.push((start_point..end_point, new_text));
10915
10916 // Selections must change based on added and removed line count
10917 let start_row =
10918 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10919 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10920 new_selections.push(Selection {
10921 id: selection.id,
10922 start: start_row,
10923 end: end_row,
10924 goal: SelectionGoal::None,
10925 reversed: selection.reversed,
10926 });
10927
10928 if line_count_after > line_count_before {
10929 added_lines += line_count_after - line_count_before;
10930 } else if line_count_before > line_count_after {
10931 removed_lines += line_count_before - line_count_after;
10932 }
10933 }
10934
10935 self.transact(window, cx, |this, window, cx| {
10936 let buffer = this.buffer.update(cx, |buffer, cx| {
10937 buffer.edit(edits, None, cx);
10938 buffer.snapshot(cx)
10939 });
10940
10941 // Recalculate offsets on newly edited buffer
10942 let new_selections = new_selections
10943 .iter()
10944 .map(|s| {
10945 let start_point = Point::new(s.start.0, 0);
10946 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10947 Selection {
10948 id: s.id,
10949 start: buffer.point_to_offset(start_point),
10950 end: buffer.point_to_offset(end_point),
10951 goal: s.goal,
10952 reversed: s.reversed,
10953 }
10954 })
10955 .collect();
10956
10957 this.change_selections(Default::default(), window, cx, |s| {
10958 s.select(new_selections);
10959 });
10960
10961 this.request_autoscroll(Autoscroll::fit(), cx);
10962 });
10963 }
10964
10965 fn manipulate_immutable_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<&str>),
10972 {
10973 self.manipulate_lines(window, cx, |text| {
10974 let mut lines: Vec<&str> = text.split('\n').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 fn manipulate_mutable_lines<Fn>(
10988 &mut self,
10989 window: &mut Window,
10990 cx: &mut Context<Self>,
10991 mut callback: Fn,
10992 ) where
10993 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10994 {
10995 self.manipulate_lines(window, cx, |text| {
10996 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10997 let line_count_before = lines.len();
10998
10999 callback(&mut lines);
11000
11001 LineManipulationResult {
11002 new_text: lines.join("\n"),
11003 line_count_before,
11004 line_count_after: lines.len(),
11005 }
11006 });
11007 }
11008
11009 pub fn convert_indentation_to_spaces(
11010 &mut self,
11011 _: &ConvertIndentationToSpaces,
11012 window: &mut Window,
11013 cx: &mut Context<Self>,
11014 ) {
11015 let settings = self.buffer.read(cx).language_settings(cx);
11016 let tab_size = settings.tab_size.get() as usize;
11017
11018 self.manipulate_mutable_lines(window, cx, |lines| {
11019 // Allocates a reasonably sized scratch buffer once for the whole loop
11020 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11021 // Avoids recomputing spaces that could be inserted many times
11022 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11023 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11024 .collect();
11025
11026 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11027 let mut chars = line.as_ref().chars();
11028 let mut col = 0;
11029 let mut changed = false;
11030
11031 while let Some(ch) = chars.next() {
11032 match ch {
11033 ' ' => {
11034 reindented_line.push(' ');
11035 col += 1;
11036 }
11037 '\t' => {
11038 // \t are converted to spaces depending on the current column
11039 let spaces_len = tab_size - (col % tab_size);
11040 reindented_line.extend(&space_cache[spaces_len - 1]);
11041 col += spaces_len;
11042 changed = true;
11043 }
11044 _ => {
11045 // If we dont append before break, the character is consumed
11046 reindented_line.push(ch);
11047 break;
11048 }
11049 }
11050 }
11051
11052 if !changed {
11053 reindented_line.clear();
11054 continue;
11055 }
11056 // Append the rest of the line and replace old reference with new one
11057 reindented_line.extend(chars);
11058 *line = Cow::Owned(reindented_line.clone());
11059 reindented_line.clear();
11060 }
11061 });
11062 }
11063
11064 pub fn convert_indentation_to_tabs(
11065 &mut self,
11066 _: &ConvertIndentationToTabs,
11067 window: &mut Window,
11068 cx: &mut Context<Self>,
11069 ) {
11070 let settings = self.buffer.read(cx).language_settings(cx);
11071 let tab_size = settings.tab_size.get() as usize;
11072
11073 self.manipulate_mutable_lines(window, cx, |lines| {
11074 // Allocates a reasonably sized buffer once for the whole loop
11075 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11076 // Avoids recomputing spaces that could be inserted many times
11077 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11078 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11079 .collect();
11080
11081 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11082 let mut chars = line.chars();
11083 let mut spaces_count = 0;
11084 let mut first_non_indent_char = None;
11085 let mut changed = false;
11086
11087 while let Some(ch) = chars.next() {
11088 match ch {
11089 ' ' => {
11090 // Keep track of spaces. Append \t when we reach tab_size
11091 spaces_count += 1;
11092 changed = true;
11093 if spaces_count == tab_size {
11094 reindented_line.push('\t');
11095 spaces_count = 0;
11096 }
11097 }
11098 '\t' => {
11099 reindented_line.push('\t');
11100 spaces_count = 0;
11101 }
11102 _ => {
11103 // Dont append it yet, we might have remaining spaces
11104 first_non_indent_char = Some(ch);
11105 break;
11106 }
11107 }
11108 }
11109
11110 if !changed {
11111 reindented_line.clear();
11112 continue;
11113 }
11114 // Remaining spaces that didn't make a full tab stop
11115 if spaces_count > 0 {
11116 reindented_line.extend(&space_cache[spaces_count - 1]);
11117 }
11118 // If we consume an extra character that was not indentation, add it back
11119 if let Some(extra_char) = first_non_indent_char {
11120 reindented_line.push(extra_char);
11121 }
11122 // Append the rest of the line and replace old reference with new one
11123 reindented_line.extend(chars);
11124 *line = Cow::Owned(reindented_line.clone());
11125 reindented_line.clear();
11126 }
11127 });
11128 }
11129
11130 pub fn convert_to_upper_case(
11131 &mut self,
11132 _: &ConvertToUpperCase,
11133 window: &mut Window,
11134 cx: &mut Context<Self>,
11135 ) {
11136 self.manipulate_text(window, cx, |text| text.to_uppercase())
11137 }
11138
11139 pub fn convert_to_lower_case(
11140 &mut self,
11141 _: &ConvertToLowerCase,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 ) {
11145 self.manipulate_text(window, cx, |text| text.to_lowercase())
11146 }
11147
11148 pub fn convert_to_title_case(
11149 &mut self,
11150 _: &ConvertToTitleCase,
11151 window: &mut Window,
11152 cx: &mut Context<Self>,
11153 ) {
11154 self.manipulate_text(window, cx, |text| {
11155 text.split('\n')
11156 .map(|line| line.to_case(Case::Title))
11157 .join("\n")
11158 })
11159 }
11160
11161 pub fn convert_to_snake_case(
11162 &mut self,
11163 _: &ConvertToSnakeCase,
11164 window: &mut Window,
11165 cx: &mut Context<Self>,
11166 ) {
11167 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11168 }
11169
11170 pub fn convert_to_kebab_case(
11171 &mut self,
11172 _: &ConvertToKebabCase,
11173 window: &mut Window,
11174 cx: &mut Context<Self>,
11175 ) {
11176 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11177 }
11178
11179 pub fn convert_to_upper_camel_case(
11180 &mut self,
11181 _: &ConvertToUpperCamelCase,
11182 window: &mut Window,
11183 cx: &mut Context<Self>,
11184 ) {
11185 self.manipulate_text(window, cx, |text| {
11186 text.split('\n')
11187 .map(|line| line.to_case(Case::UpperCamel))
11188 .join("\n")
11189 })
11190 }
11191
11192 pub fn convert_to_lower_camel_case(
11193 &mut self,
11194 _: &ConvertToLowerCamelCase,
11195 window: &mut Window,
11196 cx: &mut Context<Self>,
11197 ) {
11198 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11199 }
11200
11201 pub fn convert_to_opposite_case(
11202 &mut self,
11203 _: &ConvertToOppositeCase,
11204 window: &mut Window,
11205 cx: &mut Context<Self>,
11206 ) {
11207 self.manipulate_text(window, cx, |text| {
11208 text.chars()
11209 .fold(String::with_capacity(text.len()), |mut t, c| {
11210 if c.is_uppercase() {
11211 t.extend(c.to_lowercase());
11212 } else {
11213 t.extend(c.to_uppercase());
11214 }
11215 t
11216 })
11217 })
11218 }
11219
11220 pub fn convert_to_sentence_case(
11221 &mut self,
11222 _: &ConvertToSentenceCase,
11223 window: &mut Window,
11224 cx: &mut Context<Self>,
11225 ) {
11226 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11227 }
11228
11229 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11230 self.manipulate_text(window, cx, |text| {
11231 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11232 if has_upper_case_characters {
11233 text.to_lowercase()
11234 } else {
11235 text.to_uppercase()
11236 }
11237 })
11238 }
11239
11240 pub fn convert_to_rot13(
11241 &mut self,
11242 _: &ConvertToRot13,
11243 window: &mut Window,
11244 cx: &mut Context<Self>,
11245 ) {
11246 self.manipulate_text(window, cx, |text| {
11247 text.chars()
11248 .map(|c| match c {
11249 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11250 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11251 _ => c,
11252 })
11253 .collect()
11254 })
11255 }
11256
11257 pub fn convert_to_rot47(
11258 &mut self,
11259 _: &ConvertToRot47,
11260 window: &mut Window,
11261 cx: &mut Context<Self>,
11262 ) {
11263 self.manipulate_text(window, cx, |text| {
11264 text.chars()
11265 .map(|c| {
11266 let code_point = c as u32;
11267 if code_point >= 33 && code_point <= 126 {
11268 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11269 }
11270 c
11271 })
11272 .collect()
11273 })
11274 }
11275
11276 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11277 where
11278 Fn: FnMut(&str) -> String,
11279 {
11280 let buffer = self.buffer.read(cx).snapshot(cx);
11281
11282 let mut new_selections = Vec::new();
11283 let mut edits = Vec::new();
11284 let mut selection_adjustment = 0i32;
11285
11286 for selection in self.selections.all::<usize>(cx) {
11287 let selection_is_empty = selection.is_empty();
11288
11289 let (start, end) = if selection_is_empty {
11290 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11291 (word_range.start, word_range.end)
11292 } else {
11293 (selection.start, selection.end)
11294 };
11295
11296 let text = buffer.text_for_range(start..end).collect::<String>();
11297 let old_length = text.len() as i32;
11298 let text = callback(&text);
11299
11300 new_selections.push(Selection {
11301 start: (start as i32 - selection_adjustment) as usize,
11302 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11303 goal: SelectionGoal::None,
11304 ..selection
11305 });
11306
11307 selection_adjustment += old_length - text.len() as i32;
11308
11309 edits.push((start..end, text));
11310 }
11311
11312 self.transact(window, cx, |this, window, cx| {
11313 this.buffer.update(cx, |buffer, cx| {
11314 buffer.edit(edits, None, cx);
11315 });
11316
11317 this.change_selections(Default::default(), window, cx, |s| {
11318 s.select(new_selections);
11319 });
11320
11321 this.request_autoscroll(Autoscroll::fit(), cx);
11322 });
11323 }
11324
11325 pub fn move_selection_on_drop(
11326 &mut self,
11327 selection: &Selection<Anchor>,
11328 target: DisplayPoint,
11329 is_cut: bool,
11330 window: &mut Window,
11331 cx: &mut Context<Self>,
11332 ) {
11333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11334 let buffer = &display_map.buffer_snapshot;
11335 let mut edits = Vec::new();
11336 let insert_point = display_map
11337 .clip_point(target, Bias::Left)
11338 .to_point(&display_map);
11339 let text = buffer
11340 .text_for_range(selection.start..selection.end)
11341 .collect::<String>();
11342 if is_cut {
11343 edits.push(((selection.start..selection.end), String::new()));
11344 }
11345 let insert_anchor = buffer.anchor_before(insert_point);
11346 edits.push(((insert_anchor..insert_anchor), text));
11347 let last_edit_start = insert_anchor.bias_left(buffer);
11348 let last_edit_end = insert_anchor.bias_right(buffer);
11349 self.transact(window, cx, |this, window, cx| {
11350 this.buffer.update(cx, |buffer, cx| {
11351 buffer.edit(edits, None, cx);
11352 });
11353 this.change_selections(Default::default(), window, cx, |s| {
11354 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11355 });
11356 });
11357 }
11358
11359 pub fn clear_selection_drag_state(&mut self) {
11360 self.selection_drag_state = SelectionDragState::None;
11361 }
11362
11363 pub fn duplicate(
11364 &mut self,
11365 upwards: bool,
11366 whole_lines: bool,
11367 window: &mut Window,
11368 cx: &mut Context<Self>,
11369 ) {
11370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11371
11372 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11373 let buffer = &display_map.buffer_snapshot;
11374 let selections = self.selections.all::<Point>(cx);
11375
11376 let mut edits = Vec::new();
11377 let mut selections_iter = selections.iter().peekable();
11378 while let Some(selection) = selections_iter.next() {
11379 let mut rows = selection.spanned_rows(false, &display_map);
11380 // duplicate line-wise
11381 if whole_lines || selection.start == selection.end {
11382 // Avoid duplicating the same lines twice.
11383 while let Some(next_selection) = selections_iter.peek() {
11384 let next_rows = next_selection.spanned_rows(false, &display_map);
11385 if next_rows.start < rows.end {
11386 rows.end = next_rows.end;
11387 selections_iter.next().unwrap();
11388 } else {
11389 break;
11390 }
11391 }
11392
11393 // Copy the text from the selected row region and splice it either at the start
11394 // or end of the region.
11395 let start = Point::new(rows.start.0, 0);
11396 let end = Point::new(
11397 rows.end.previous_row().0,
11398 buffer.line_len(rows.end.previous_row()),
11399 );
11400 let text = buffer
11401 .text_for_range(start..end)
11402 .chain(Some("\n"))
11403 .collect::<String>();
11404 let insert_location = if upwards {
11405 Point::new(rows.end.0, 0)
11406 } else {
11407 start
11408 };
11409 edits.push((insert_location..insert_location, text));
11410 } else {
11411 // duplicate character-wise
11412 let start = selection.start;
11413 let end = selection.end;
11414 let text = buffer.text_for_range(start..end).collect::<String>();
11415 edits.push((selection.end..selection.end, text));
11416 }
11417 }
11418
11419 self.transact(window, cx, |this, _, cx| {
11420 this.buffer.update(cx, |buffer, cx| {
11421 buffer.edit(edits, None, cx);
11422 });
11423
11424 this.request_autoscroll(Autoscroll::fit(), cx);
11425 });
11426 }
11427
11428 pub fn duplicate_line_up(
11429 &mut self,
11430 _: &DuplicateLineUp,
11431 window: &mut Window,
11432 cx: &mut Context<Self>,
11433 ) {
11434 self.duplicate(true, true, window, cx);
11435 }
11436
11437 pub fn duplicate_line_down(
11438 &mut self,
11439 _: &DuplicateLineDown,
11440 window: &mut Window,
11441 cx: &mut Context<Self>,
11442 ) {
11443 self.duplicate(false, true, window, cx);
11444 }
11445
11446 pub fn duplicate_selection(
11447 &mut self,
11448 _: &DuplicateSelection,
11449 window: &mut Window,
11450 cx: &mut Context<Self>,
11451 ) {
11452 self.duplicate(false, false, window, cx);
11453 }
11454
11455 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11456 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11457 if self.mode.is_single_line() {
11458 cx.propagate();
11459 return;
11460 }
11461
11462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11463 let buffer = self.buffer.read(cx).snapshot(cx);
11464
11465 let mut edits = Vec::new();
11466 let mut unfold_ranges = Vec::new();
11467 let mut refold_creases = Vec::new();
11468
11469 let selections = self.selections.all::<Point>(cx);
11470 let mut selections = selections.iter().peekable();
11471 let mut contiguous_row_selections = Vec::new();
11472 let mut new_selections = Vec::new();
11473
11474 while let Some(selection) = selections.next() {
11475 // Find all the selections that span a contiguous row range
11476 let (start_row, end_row) = consume_contiguous_rows(
11477 &mut contiguous_row_selections,
11478 selection,
11479 &display_map,
11480 &mut selections,
11481 );
11482
11483 // Move the text spanned by the row range to be before the line preceding the row range
11484 if start_row.0 > 0 {
11485 let range_to_move = Point::new(
11486 start_row.previous_row().0,
11487 buffer.line_len(start_row.previous_row()),
11488 )
11489 ..Point::new(
11490 end_row.previous_row().0,
11491 buffer.line_len(end_row.previous_row()),
11492 );
11493 let insertion_point = display_map
11494 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11495 .0;
11496
11497 // Don't move lines across excerpts
11498 if buffer
11499 .excerpt_containing(insertion_point..range_to_move.end)
11500 .is_some()
11501 {
11502 let text = buffer
11503 .text_for_range(range_to_move.clone())
11504 .flat_map(|s| s.chars())
11505 .skip(1)
11506 .chain(['\n'])
11507 .collect::<String>();
11508
11509 edits.push((
11510 buffer.anchor_after(range_to_move.start)
11511 ..buffer.anchor_before(range_to_move.end),
11512 String::new(),
11513 ));
11514 let insertion_anchor = buffer.anchor_after(insertion_point);
11515 edits.push((insertion_anchor..insertion_anchor, text));
11516
11517 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11518
11519 // Move selections up
11520 new_selections.extend(contiguous_row_selections.drain(..).map(
11521 |mut selection| {
11522 selection.start.row -= row_delta;
11523 selection.end.row -= row_delta;
11524 selection
11525 },
11526 ));
11527
11528 // Move folds up
11529 unfold_ranges.push(range_to_move.clone());
11530 for fold in display_map.folds_in_range(
11531 buffer.anchor_before(range_to_move.start)
11532 ..buffer.anchor_after(range_to_move.end),
11533 ) {
11534 let mut start = fold.range.start.to_point(&buffer);
11535 let mut end = fold.range.end.to_point(&buffer);
11536 start.row -= row_delta;
11537 end.row -= row_delta;
11538 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11539 }
11540 }
11541 }
11542
11543 // If we didn't move line(s), preserve the existing selections
11544 new_selections.append(&mut contiguous_row_selections);
11545 }
11546
11547 self.transact(window, cx, |this, window, cx| {
11548 this.unfold_ranges(&unfold_ranges, true, true, cx);
11549 this.buffer.update(cx, |buffer, cx| {
11550 for (range, text) in edits {
11551 buffer.edit([(range, text)], None, cx);
11552 }
11553 });
11554 this.fold_creases(refold_creases, true, window, cx);
11555 this.change_selections(Default::default(), window, cx, |s| {
11556 s.select(new_selections);
11557 })
11558 });
11559 }
11560
11561 pub fn move_line_down(
11562 &mut self,
11563 _: &MoveLineDown,
11564 window: &mut Window,
11565 cx: &mut Context<Self>,
11566 ) {
11567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11568 if self.mode.is_single_line() {
11569 cx.propagate();
11570 return;
11571 }
11572
11573 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11574 let buffer = self.buffer.read(cx).snapshot(cx);
11575
11576 let mut edits = Vec::new();
11577 let mut unfold_ranges = Vec::new();
11578 let mut refold_creases = Vec::new();
11579
11580 let selections = self.selections.all::<Point>(cx);
11581 let mut selections = selections.iter().peekable();
11582 let mut contiguous_row_selections = Vec::new();
11583 let mut new_selections = Vec::new();
11584
11585 while let Some(selection) = selections.next() {
11586 // Find all the selections that span a contiguous row range
11587 let (start_row, end_row) = consume_contiguous_rows(
11588 &mut contiguous_row_selections,
11589 selection,
11590 &display_map,
11591 &mut selections,
11592 );
11593
11594 // Move the text spanned by the row range to be after the last line of the row range
11595 if end_row.0 <= buffer.max_point().row {
11596 let range_to_move =
11597 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11598 let insertion_point = display_map
11599 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11600 .0;
11601
11602 // Don't move lines across excerpt boundaries
11603 if buffer
11604 .excerpt_containing(range_to_move.start..insertion_point)
11605 .is_some()
11606 {
11607 let mut text = String::from("\n");
11608 text.extend(buffer.text_for_range(range_to_move.clone()));
11609 text.pop(); // Drop trailing newline
11610 edits.push((
11611 buffer.anchor_after(range_to_move.start)
11612 ..buffer.anchor_before(range_to_move.end),
11613 String::new(),
11614 ));
11615 let insertion_anchor = buffer.anchor_after(insertion_point);
11616 edits.push((insertion_anchor..insertion_anchor, text));
11617
11618 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11619
11620 // Move selections down
11621 new_selections.extend(contiguous_row_selections.drain(..).map(
11622 |mut selection| {
11623 selection.start.row += row_delta;
11624 selection.end.row += row_delta;
11625 selection
11626 },
11627 ));
11628
11629 // Move folds down
11630 unfold_ranges.push(range_to_move.clone());
11631 for fold in display_map.folds_in_range(
11632 buffer.anchor_before(range_to_move.start)
11633 ..buffer.anchor_after(range_to_move.end),
11634 ) {
11635 let mut start = fold.range.start.to_point(&buffer);
11636 let mut end = fold.range.end.to_point(&buffer);
11637 start.row += row_delta;
11638 end.row += row_delta;
11639 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11640 }
11641 }
11642 }
11643
11644 // If we didn't move line(s), preserve the existing selections
11645 new_selections.append(&mut contiguous_row_selections);
11646 }
11647
11648 self.transact(window, cx, |this, window, cx| {
11649 this.unfold_ranges(&unfold_ranges, true, true, cx);
11650 this.buffer.update(cx, |buffer, cx| {
11651 for (range, text) in edits {
11652 buffer.edit([(range, text)], None, cx);
11653 }
11654 });
11655 this.fold_creases(refold_creases, true, window, cx);
11656 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11657 });
11658 }
11659
11660 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11661 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11662 let text_layout_details = &self.text_layout_details(window);
11663 self.transact(window, cx, |this, window, cx| {
11664 let edits = this.change_selections(Default::default(), window, cx, |s| {
11665 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11666 s.move_with(|display_map, selection| {
11667 if !selection.is_empty() {
11668 return;
11669 }
11670
11671 let mut head = selection.head();
11672 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11673 if head.column() == display_map.line_len(head.row()) {
11674 transpose_offset = display_map
11675 .buffer_snapshot
11676 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11677 }
11678
11679 if transpose_offset == 0 {
11680 return;
11681 }
11682
11683 *head.column_mut() += 1;
11684 head = display_map.clip_point(head, Bias::Right);
11685 let goal = SelectionGoal::HorizontalPosition(
11686 display_map
11687 .x_for_display_point(head, text_layout_details)
11688 .into(),
11689 );
11690 selection.collapse_to(head, goal);
11691
11692 let transpose_start = display_map
11693 .buffer_snapshot
11694 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11695 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11696 let transpose_end = display_map
11697 .buffer_snapshot
11698 .clip_offset(transpose_offset + 1, Bias::Right);
11699 if let Some(ch) =
11700 display_map.buffer_snapshot.chars_at(transpose_start).next()
11701 {
11702 edits.push((transpose_start..transpose_offset, String::new()));
11703 edits.push((transpose_end..transpose_end, ch.to_string()));
11704 }
11705 }
11706 });
11707 edits
11708 });
11709 this.buffer
11710 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11711 let selections = this.selections.all::<usize>(cx);
11712 this.change_selections(Default::default(), window, cx, |s| {
11713 s.select(selections);
11714 });
11715 });
11716 }
11717
11718 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11719 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11720 if self.mode.is_single_line() {
11721 cx.propagate();
11722 return;
11723 }
11724
11725 self.rewrap_impl(RewrapOptions::default(), cx)
11726 }
11727
11728 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11729 let buffer = self.buffer.read(cx).snapshot(cx);
11730 let selections = self.selections.all::<Point>(cx);
11731
11732 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11733 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11734 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11735 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11736 .peekable();
11737
11738 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11739 row
11740 } else {
11741 return Vec::new();
11742 };
11743
11744 let language_settings = buffer.language_settings_at(selection.head(), cx);
11745 let language_scope = buffer.language_scope_at(selection.head());
11746
11747 let indent_and_prefix_for_row =
11748 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11749 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11750 let (comment_prefix, rewrap_prefix) =
11751 if let Some(language_scope) = &language_scope {
11752 let indent_end = Point::new(row, indent.len);
11753 let comment_prefix = language_scope
11754 .line_comment_prefixes()
11755 .iter()
11756 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11757 .map(|prefix| prefix.to_string());
11758 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11759 let line_text_after_indent = buffer
11760 .text_for_range(indent_end..line_end)
11761 .collect::<String>();
11762 let rewrap_prefix = language_scope
11763 .rewrap_prefixes()
11764 .iter()
11765 .find_map(|prefix_regex| {
11766 prefix_regex.find(&line_text_after_indent).map(|mat| {
11767 if mat.start() == 0 {
11768 Some(mat.as_str().to_string())
11769 } else {
11770 None
11771 }
11772 })
11773 })
11774 .flatten();
11775 (comment_prefix, rewrap_prefix)
11776 } else {
11777 (None, None)
11778 };
11779 (indent, comment_prefix, rewrap_prefix)
11780 };
11781
11782 let mut ranges = Vec::new();
11783 let from_empty_selection = selection.is_empty();
11784
11785 let mut current_range_start = first_row;
11786 let mut prev_row = first_row;
11787 let (
11788 mut current_range_indent,
11789 mut current_range_comment_prefix,
11790 mut current_range_rewrap_prefix,
11791 ) = indent_and_prefix_for_row(first_row);
11792
11793 for row in non_blank_rows_iter.skip(1) {
11794 let has_paragraph_break = row > prev_row + 1;
11795
11796 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11797 indent_and_prefix_for_row(row);
11798
11799 let has_indent_change = row_indent != current_range_indent;
11800 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11801
11802 let has_boundary_change = has_comment_change
11803 || row_rewrap_prefix.is_some()
11804 || (has_indent_change && current_range_comment_prefix.is_some());
11805
11806 if has_paragraph_break || has_boundary_change {
11807 ranges.push((
11808 language_settings.clone(),
11809 Point::new(current_range_start, 0)
11810 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11811 current_range_indent,
11812 current_range_comment_prefix.clone(),
11813 current_range_rewrap_prefix.clone(),
11814 from_empty_selection,
11815 ));
11816 current_range_start = row;
11817 current_range_indent = row_indent;
11818 current_range_comment_prefix = row_comment_prefix;
11819 current_range_rewrap_prefix = row_rewrap_prefix;
11820 }
11821 prev_row = row;
11822 }
11823
11824 ranges.push((
11825 language_settings.clone(),
11826 Point::new(current_range_start, 0)
11827 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11828 current_range_indent,
11829 current_range_comment_prefix,
11830 current_range_rewrap_prefix,
11831 from_empty_selection,
11832 ));
11833
11834 ranges
11835 });
11836
11837 let mut edits = Vec::new();
11838 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11839
11840 for (
11841 language_settings,
11842 wrap_range,
11843 indent_size,
11844 comment_prefix,
11845 rewrap_prefix,
11846 from_empty_selection,
11847 ) in wrap_ranges
11848 {
11849 let mut start_row = wrap_range.start.row;
11850 let mut end_row = wrap_range.end.row;
11851
11852 // Skip selections that overlap with a range that has already been rewrapped.
11853 let selection_range = start_row..end_row;
11854 if rewrapped_row_ranges
11855 .iter()
11856 .any(|range| range.overlaps(&selection_range))
11857 {
11858 continue;
11859 }
11860
11861 let tab_size = language_settings.tab_size;
11862
11863 let indent_prefix = indent_size.chars().collect::<String>();
11864 let mut line_prefix = indent_prefix.clone();
11865 let mut inside_comment = false;
11866 if let Some(prefix) = &comment_prefix {
11867 line_prefix.push_str(prefix);
11868 inside_comment = true;
11869 }
11870 if let Some(prefix) = &rewrap_prefix {
11871 line_prefix.push_str(prefix);
11872 }
11873
11874 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11875 RewrapBehavior::InComments => inside_comment,
11876 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11877 RewrapBehavior::Anywhere => true,
11878 };
11879
11880 let should_rewrap = options.override_language_settings
11881 || allow_rewrap_based_on_language
11882 || self.hard_wrap.is_some();
11883 if !should_rewrap {
11884 continue;
11885 }
11886
11887 if from_empty_selection {
11888 'expand_upwards: while start_row > 0 {
11889 let prev_row = start_row - 1;
11890 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11891 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11892 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11893 {
11894 start_row = prev_row;
11895 } else {
11896 break 'expand_upwards;
11897 }
11898 }
11899
11900 'expand_downwards: while end_row < buffer.max_point().row {
11901 let next_row = end_row + 1;
11902 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11903 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11904 && !buffer.is_line_blank(MultiBufferRow(next_row))
11905 {
11906 end_row = next_row;
11907 } else {
11908 break 'expand_downwards;
11909 }
11910 }
11911 }
11912
11913 let start = Point::new(start_row, 0);
11914 let start_offset = start.to_offset(&buffer);
11915 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11916 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11917 let Some(lines_without_prefixes) = selection_text
11918 .lines()
11919 .enumerate()
11920 .map(|(ix, line)| {
11921 let line_trimmed = line.trim_start();
11922 if rewrap_prefix.is_some() && ix > 0 {
11923 Ok(line_trimmed)
11924 } else {
11925 line_trimmed
11926 .strip_prefix(&line_prefix.trim_start())
11927 .with_context(|| {
11928 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11929 })
11930 }
11931 })
11932 .collect::<Result<Vec<_>, _>>()
11933 .log_err()
11934 else {
11935 continue;
11936 };
11937
11938 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11939 buffer
11940 .language_settings_at(Point::new(start_row, 0), cx)
11941 .preferred_line_length as usize
11942 });
11943
11944 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11945 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11946 } else {
11947 line_prefix.clone()
11948 };
11949
11950 let wrapped_text = wrap_with_prefix(
11951 line_prefix,
11952 subsequent_lines_prefix,
11953 lines_without_prefixes.join("\n"),
11954 wrap_column,
11955 tab_size,
11956 options.preserve_existing_whitespace,
11957 );
11958
11959 // TODO: should always use char-based diff while still supporting cursor behavior that
11960 // matches vim.
11961 let mut diff_options = DiffOptions::default();
11962 if options.override_language_settings {
11963 diff_options.max_word_diff_len = 0;
11964 diff_options.max_word_diff_line_count = 0;
11965 } else {
11966 diff_options.max_word_diff_len = usize::MAX;
11967 diff_options.max_word_diff_line_count = usize::MAX;
11968 }
11969
11970 for (old_range, new_text) in
11971 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11972 {
11973 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11974 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11975 edits.push((edit_start..edit_end, new_text));
11976 }
11977
11978 rewrapped_row_ranges.push(start_row..=end_row);
11979 }
11980
11981 self.buffer
11982 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11983 }
11984
11985 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11986 let mut text = String::new();
11987 let buffer = self.buffer.read(cx).snapshot(cx);
11988 let mut selections = self.selections.all::<Point>(cx);
11989 let mut clipboard_selections = Vec::with_capacity(selections.len());
11990 {
11991 let max_point = buffer.max_point();
11992 let mut is_first = true;
11993 for selection in &mut selections {
11994 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11995 if is_entire_line {
11996 selection.start = Point::new(selection.start.row, 0);
11997 if !selection.is_empty() && selection.end.column == 0 {
11998 selection.end = cmp::min(max_point, selection.end);
11999 } else {
12000 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12001 }
12002 selection.goal = SelectionGoal::None;
12003 }
12004 if is_first {
12005 is_first = false;
12006 } else {
12007 text += "\n";
12008 }
12009 let mut len = 0;
12010 for chunk in buffer.text_for_range(selection.start..selection.end) {
12011 text.push_str(chunk);
12012 len += chunk.len();
12013 }
12014 clipboard_selections.push(ClipboardSelection {
12015 len,
12016 is_entire_line,
12017 first_line_indent: buffer
12018 .indent_size_for_line(MultiBufferRow(selection.start.row))
12019 .len,
12020 });
12021 }
12022 }
12023
12024 self.transact(window, cx, |this, window, cx| {
12025 this.change_selections(Default::default(), window, cx, |s| {
12026 s.select(selections);
12027 });
12028 this.insert("", window, cx);
12029 });
12030 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12031 }
12032
12033 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12035 let item = self.cut_common(window, cx);
12036 cx.write_to_clipboard(item);
12037 }
12038
12039 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12041 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12042 s.move_with(|snapshot, sel| {
12043 if sel.is_empty() {
12044 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12045 }
12046 });
12047 });
12048 let item = self.cut_common(window, cx);
12049 cx.set_global(KillRing(item))
12050 }
12051
12052 pub fn kill_ring_yank(
12053 &mut self,
12054 _: &KillRingYank,
12055 window: &mut Window,
12056 cx: &mut Context<Self>,
12057 ) {
12058 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12059 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12060 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12061 (kill_ring.text().to_string(), kill_ring.metadata_json())
12062 } else {
12063 return;
12064 }
12065 } else {
12066 return;
12067 };
12068 self.do_paste(&text, metadata, false, window, cx);
12069 }
12070
12071 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12072 self.do_copy(true, cx);
12073 }
12074
12075 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12076 self.do_copy(false, cx);
12077 }
12078
12079 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12080 let selections = self.selections.all::<Point>(cx);
12081 let buffer = self.buffer.read(cx).read(cx);
12082 let mut text = String::new();
12083
12084 let mut clipboard_selections = Vec::with_capacity(selections.len());
12085 {
12086 let max_point = buffer.max_point();
12087 let mut is_first = true;
12088 for selection in &selections {
12089 let mut start = selection.start;
12090 let mut end = selection.end;
12091 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12092 if is_entire_line {
12093 start = Point::new(start.row, 0);
12094 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12095 }
12096
12097 let mut trimmed_selections = Vec::new();
12098 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12099 let row = MultiBufferRow(start.row);
12100 let first_indent = buffer.indent_size_for_line(row);
12101 if first_indent.len == 0 || start.column > first_indent.len {
12102 trimmed_selections.push(start..end);
12103 } else {
12104 trimmed_selections.push(
12105 Point::new(row.0, first_indent.len)
12106 ..Point::new(row.0, buffer.line_len(row)),
12107 );
12108 for row in start.row + 1..=end.row {
12109 let mut line_len = buffer.line_len(MultiBufferRow(row));
12110 if row == end.row {
12111 line_len = end.column;
12112 }
12113 if line_len == 0 {
12114 trimmed_selections
12115 .push(Point::new(row, 0)..Point::new(row, line_len));
12116 continue;
12117 }
12118 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12119 if row_indent_size.len >= first_indent.len {
12120 trimmed_selections.push(
12121 Point::new(row, first_indent.len)..Point::new(row, line_len),
12122 );
12123 } else {
12124 trimmed_selections.clear();
12125 trimmed_selections.push(start..end);
12126 break;
12127 }
12128 }
12129 }
12130 } else {
12131 trimmed_selections.push(start..end);
12132 }
12133
12134 for trimmed_range in trimmed_selections {
12135 if is_first {
12136 is_first = false;
12137 } else {
12138 text += "\n";
12139 }
12140 let mut len = 0;
12141 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12142 text.push_str(chunk);
12143 len += chunk.len();
12144 }
12145 clipboard_selections.push(ClipboardSelection {
12146 len,
12147 is_entire_line,
12148 first_line_indent: buffer
12149 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12150 .len,
12151 });
12152 }
12153 }
12154 }
12155
12156 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12157 text,
12158 clipboard_selections,
12159 ));
12160 }
12161
12162 pub fn do_paste(
12163 &mut self,
12164 text: &String,
12165 clipboard_selections: Option<Vec<ClipboardSelection>>,
12166 handle_entire_lines: bool,
12167 window: &mut Window,
12168 cx: &mut Context<Self>,
12169 ) {
12170 if self.read_only(cx) {
12171 return;
12172 }
12173
12174 let clipboard_text = Cow::Borrowed(text);
12175
12176 self.transact(window, cx, |this, window, cx| {
12177 let had_active_edit_prediction = this.has_active_edit_prediction();
12178
12179 if let Some(mut clipboard_selections) = clipboard_selections {
12180 let old_selections = this.selections.all::<usize>(cx);
12181 let all_selections_were_entire_line =
12182 clipboard_selections.iter().all(|s| s.is_entire_line);
12183 let first_selection_indent_column =
12184 clipboard_selections.first().map(|s| s.first_line_indent);
12185 if clipboard_selections.len() != old_selections.len() {
12186 clipboard_selections.drain(..);
12187 }
12188 let cursor_offset = this.selections.last::<usize>(cx).head();
12189 let mut auto_indent_on_paste = true;
12190
12191 this.buffer.update(cx, |buffer, cx| {
12192 let snapshot = buffer.read(cx);
12193 auto_indent_on_paste = snapshot
12194 .language_settings_at(cursor_offset, cx)
12195 .auto_indent_on_paste;
12196
12197 let mut start_offset = 0;
12198 let mut edits = Vec::new();
12199 let mut original_indent_columns = Vec::new();
12200 for (ix, selection) in old_selections.iter().enumerate() {
12201 let to_insert;
12202 let entire_line;
12203 let original_indent_column;
12204 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12205 let end_offset = start_offset + clipboard_selection.len;
12206 to_insert = &clipboard_text[start_offset..end_offset];
12207 entire_line = clipboard_selection.is_entire_line;
12208 start_offset = end_offset + 1;
12209 original_indent_column = Some(clipboard_selection.first_line_indent);
12210 } else {
12211 to_insert = clipboard_text.as_str();
12212 entire_line = all_selections_were_entire_line;
12213 original_indent_column = first_selection_indent_column
12214 }
12215
12216 // If the corresponding selection was empty when this slice of the
12217 // clipboard text was written, then the entire line containing the
12218 // selection was copied. If this selection is also currently empty,
12219 // then paste the line before the current line of the buffer.
12220 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12221 let column = selection.start.to_point(&snapshot).column as usize;
12222 let line_start = selection.start - column;
12223 line_start..line_start
12224 } else {
12225 selection.range()
12226 };
12227
12228 edits.push((range, to_insert));
12229 original_indent_columns.push(original_indent_column);
12230 }
12231 drop(snapshot);
12232
12233 buffer.edit(
12234 edits,
12235 if auto_indent_on_paste {
12236 Some(AutoindentMode::Block {
12237 original_indent_columns,
12238 })
12239 } else {
12240 None
12241 },
12242 cx,
12243 );
12244 });
12245
12246 let selections = this.selections.all::<usize>(cx);
12247 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12248 } else {
12249 this.insert(&clipboard_text, window, cx);
12250 }
12251
12252 let trigger_in_words =
12253 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12254
12255 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12256 });
12257 }
12258
12259 pub fn diff_clipboard_with_selection(
12260 &mut self,
12261 _: &DiffClipboardWithSelection,
12262 window: &mut Window,
12263 cx: &mut Context<Self>,
12264 ) {
12265 let selections = self.selections.all::<usize>(cx);
12266
12267 if selections.is_empty() {
12268 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12269 return;
12270 };
12271
12272 let clipboard_text = match cx.read_from_clipboard() {
12273 Some(item) => match item.entries().first() {
12274 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12275 _ => None,
12276 },
12277 None => None,
12278 };
12279
12280 let Some(clipboard_text) = clipboard_text else {
12281 log::warn!("Clipboard doesn't contain text.");
12282 return;
12283 };
12284
12285 window.dispatch_action(
12286 Box::new(DiffClipboardWithSelectionData {
12287 clipboard_text,
12288 editor: cx.entity(),
12289 }),
12290 cx,
12291 );
12292 }
12293
12294 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12295 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12296 if let Some(item) = cx.read_from_clipboard() {
12297 let entries = item.entries();
12298
12299 match entries.first() {
12300 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12301 // of all the pasted entries.
12302 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12303 .do_paste(
12304 clipboard_string.text(),
12305 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12306 true,
12307 window,
12308 cx,
12309 ),
12310 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12311 }
12312 }
12313 }
12314
12315 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12316 if self.read_only(cx) {
12317 return;
12318 }
12319
12320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12321
12322 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12323 if let Some((selections, _)) =
12324 self.selection_history.transaction(transaction_id).cloned()
12325 {
12326 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12327 s.select_anchors(selections.to_vec());
12328 });
12329 } else {
12330 log::error!(
12331 "No entry in selection_history found for undo. \
12332 This may correspond to a bug where undo does not update the selection. \
12333 If this is occurring, please add details to \
12334 https://github.com/zed-industries/zed/issues/22692"
12335 );
12336 }
12337 self.request_autoscroll(Autoscroll::fit(), cx);
12338 self.unmark_text(window, cx);
12339 self.refresh_edit_prediction(true, false, window, cx);
12340 cx.emit(EditorEvent::Edited { transaction_id });
12341 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12342 }
12343 }
12344
12345 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12346 if self.read_only(cx) {
12347 return;
12348 }
12349
12350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12351
12352 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12353 if let Some((_, Some(selections))) =
12354 self.selection_history.transaction(transaction_id).cloned()
12355 {
12356 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12357 s.select_anchors(selections.to_vec());
12358 });
12359 } else {
12360 log::error!(
12361 "No entry in selection_history found for redo. \
12362 This may correspond to a bug where undo does not update the selection. \
12363 If this is occurring, please add details to \
12364 https://github.com/zed-industries/zed/issues/22692"
12365 );
12366 }
12367 self.request_autoscroll(Autoscroll::fit(), cx);
12368 self.unmark_text(window, cx);
12369 self.refresh_edit_prediction(true, false, window, cx);
12370 cx.emit(EditorEvent::Edited { transaction_id });
12371 }
12372 }
12373
12374 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12375 self.buffer
12376 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12377 }
12378
12379 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12380 self.buffer
12381 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12382 }
12383
12384 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12386 self.change_selections(Default::default(), window, cx, |s| {
12387 s.move_with(|map, selection| {
12388 let cursor = if selection.is_empty() {
12389 movement::left(map, selection.start)
12390 } else {
12391 selection.start
12392 };
12393 selection.collapse_to(cursor, SelectionGoal::None);
12394 });
12395 })
12396 }
12397
12398 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12400 self.change_selections(Default::default(), window, cx, |s| {
12401 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12402 })
12403 }
12404
12405 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12407 self.change_selections(Default::default(), window, cx, |s| {
12408 s.move_with(|map, selection| {
12409 let cursor = if selection.is_empty() {
12410 movement::right(map, selection.end)
12411 } else {
12412 selection.end
12413 };
12414 selection.collapse_to(cursor, SelectionGoal::None)
12415 });
12416 })
12417 }
12418
12419 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12420 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12421 self.change_selections(Default::default(), window, cx, |s| {
12422 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12423 })
12424 }
12425
12426 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12427 if self.take_rename(true, window, cx).is_some() {
12428 return;
12429 }
12430
12431 if self.mode.is_single_line() {
12432 cx.propagate();
12433 return;
12434 }
12435
12436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12437
12438 let text_layout_details = &self.text_layout_details(window);
12439 let selection_count = self.selections.count();
12440 let first_selection = self.selections.first_anchor();
12441
12442 self.change_selections(Default::default(), window, cx, |s| {
12443 s.move_with(|map, selection| {
12444 if !selection.is_empty() {
12445 selection.goal = SelectionGoal::None;
12446 }
12447 let (cursor, goal) = movement::up(
12448 map,
12449 selection.start,
12450 selection.goal,
12451 false,
12452 text_layout_details,
12453 );
12454 selection.collapse_to(cursor, goal);
12455 });
12456 });
12457
12458 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12459 {
12460 cx.propagate();
12461 }
12462 }
12463
12464 pub fn move_up_by_lines(
12465 &mut self,
12466 action: &MoveUpByLines,
12467 window: &mut Window,
12468 cx: &mut Context<Self>,
12469 ) {
12470 if self.take_rename(true, window, cx).is_some() {
12471 return;
12472 }
12473
12474 if self.mode.is_single_line() {
12475 cx.propagate();
12476 return;
12477 }
12478
12479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12480
12481 let text_layout_details = &self.text_layout_details(window);
12482
12483 self.change_selections(Default::default(), window, cx, |s| {
12484 s.move_with(|map, selection| {
12485 if !selection.is_empty() {
12486 selection.goal = SelectionGoal::None;
12487 }
12488 let (cursor, goal) = movement::up_by_rows(
12489 map,
12490 selection.start,
12491 action.lines,
12492 selection.goal,
12493 false,
12494 text_layout_details,
12495 );
12496 selection.collapse_to(cursor, goal);
12497 });
12498 })
12499 }
12500
12501 pub fn move_down_by_lines(
12502 &mut self,
12503 action: &MoveDownByLines,
12504 window: &mut Window,
12505 cx: &mut Context<Self>,
12506 ) {
12507 if self.take_rename(true, window, cx).is_some() {
12508 return;
12509 }
12510
12511 if self.mode.is_single_line() {
12512 cx.propagate();
12513 return;
12514 }
12515
12516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12517
12518 let text_layout_details = &self.text_layout_details(window);
12519
12520 self.change_selections(Default::default(), window, cx, |s| {
12521 s.move_with(|map, selection| {
12522 if !selection.is_empty() {
12523 selection.goal = SelectionGoal::None;
12524 }
12525 let (cursor, goal) = movement::down_by_rows(
12526 map,
12527 selection.start,
12528 action.lines,
12529 selection.goal,
12530 false,
12531 text_layout_details,
12532 );
12533 selection.collapse_to(cursor, goal);
12534 });
12535 })
12536 }
12537
12538 pub fn select_down_by_lines(
12539 &mut self,
12540 action: &SelectDownByLines,
12541 window: &mut Window,
12542 cx: &mut Context<Self>,
12543 ) {
12544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12545 let text_layout_details = &self.text_layout_details(window);
12546 self.change_selections(Default::default(), window, cx, |s| {
12547 s.move_heads_with(|map, head, goal| {
12548 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12549 })
12550 })
12551 }
12552
12553 pub fn select_up_by_lines(
12554 &mut self,
12555 action: &SelectUpByLines,
12556 window: &mut Window,
12557 cx: &mut Context<Self>,
12558 ) {
12559 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12560 let text_layout_details = &self.text_layout_details(window);
12561 self.change_selections(Default::default(), window, cx, |s| {
12562 s.move_heads_with(|map, head, goal| {
12563 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12564 })
12565 })
12566 }
12567
12568 pub fn select_page_up(
12569 &mut self,
12570 _: &SelectPageUp,
12571 window: &mut Window,
12572 cx: &mut Context<Self>,
12573 ) {
12574 let Some(row_count) = self.visible_row_count() else {
12575 return;
12576 };
12577
12578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12579
12580 let text_layout_details = &self.text_layout_details(window);
12581
12582 self.change_selections(Default::default(), window, cx, |s| {
12583 s.move_heads_with(|map, head, goal| {
12584 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12585 })
12586 })
12587 }
12588
12589 pub fn move_page_up(
12590 &mut self,
12591 action: &MovePageUp,
12592 window: &mut Window,
12593 cx: &mut Context<Self>,
12594 ) {
12595 if self.take_rename(true, window, cx).is_some() {
12596 return;
12597 }
12598
12599 if self
12600 .context_menu
12601 .borrow_mut()
12602 .as_mut()
12603 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12604 .unwrap_or(false)
12605 {
12606 return;
12607 }
12608
12609 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12610 cx.propagate();
12611 return;
12612 }
12613
12614 let Some(row_count) = self.visible_row_count() else {
12615 return;
12616 };
12617
12618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12619
12620 let effects = if action.center_cursor {
12621 SelectionEffects::scroll(Autoscroll::center())
12622 } else {
12623 SelectionEffects::default()
12624 };
12625
12626 let text_layout_details = &self.text_layout_details(window);
12627
12628 self.change_selections(effects, window, cx, |s| {
12629 s.move_with(|map, selection| {
12630 if !selection.is_empty() {
12631 selection.goal = SelectionGoal::None;
12632 }
12633 let (cursor, goal) = movement::up_by_rows(
12634 map,
12635 selection.end,
12636 row_count,
12637 selection.goal,
12638 false,
12639 text_layout_details,
12640 );
12641 selection.collapse_to(cursor, goal);
12642 });
12643 });
12644 }
12645
12646 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12648 let text_layout_details = &self.text_layout_details(window);
12649 self.change_selections(Default::default(), window, cx, |s| {
12650 s.move_heads_with(|map, head, goal| {
12651 movement::up(map, head, goal, false, text_layout_details)
12652 })
12653 })
12654 }
12655
12656 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12657 self.take_rename(true, window, cx);
12658
12659 if self.mode.is_single_line() {
12660 cx.propagate();
12661 return;
12662 }
12663
12664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12665
12666 let text_layout_details = &self.text_layout_details(window);
12667 let selection_count = self.selections.count();
12668 let first_selection = self.selections.first_anchor();
12669
12670 self.change_selections(Default::default(), window, cx, |s| {
12671 s.move_with(|map, selection| {
12672 if !selection.is_empty() {
12673 selection.goal = SelectionGoal::None;
12674 }
12675 let (cursor, goal) = movement::down(
12676 map,
12677 selection.end,
12678 selection.goal,
12679 false,
12680 text_layout_details,
12681 );
12682 selection.collapse_to(cursor, goal);
12683 });
12684 });
12685
12686 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12687 {
12688 cx.propagate();
12689 }
12690 }
12691
12692 pub fn select_page_down(
12693 &mut self,
12694 _: &SelectPageDown,
12695 window: &mut Window,
12696 cx: &mut Context<Self>,
12697 ) {
12698 let Some(row_count) = self.visible_row_count() else {
12699 return;
12700 };
12701
12702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12703
12704 let text_layout_details = &self.text_layout_details(window);
12705
12706 self.change_selections(Default::default(), window, cx, |s| {
12707 s.move_heads_with(|map, head, goal| {
12708 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12709 })
12710 })
12711 }
12712
12713 pub fn move_page_down(
12714 &mut self,
12715 action: &MovePageDown,
12716 window: &mut Window,
12717 cx: &mut Context<Self>,
12718 ) {
12719 if self.take_rename(true, window, cx).is_some() {
12720 return;
12721 }
12722
12723 if self
12724 .context_menu
12725 .borrow_mut()
12726 .as_mut()
12727 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12728 .unwrap_or(false)
12729 {
12730 return;
12731 }
12732
12733 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12734 cx.propagate();
12735 return;
12736 }
12737
12738 let Some(row_count) = self.visible_row_count() else {
12739 return;
12740 };
12741
12742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12743
12744 let effects = if action.center_cursor {
12745 SelectionEffects::scroll(Autoscroll::center())
12746 } else {
12747 SelectionEffects::default()
12748 };
12749
12750 let text_layout_details = &self.text_layout_details(window);
12751 self.change_selections(effects, window, cx, |s| {
12752 s.move_with(|map, selection| {
12753 if !selection.is_empty() {
12754 selection.goal = SelectionGoal::None;
12755 }
12756 let (cursor, goal) = movement::down_by_rows(
12757 map,
12758 selection.end,
12759 row_count,
12760 selection.goal,
12761 false,
12762 text_layout_details,
12763 );
12764 selection.collapse_to(cursor, goal);
12765 });
12766 });
12767 }
12768
12769 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12770 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12771 let text_layout_details = &self.text_layout_details(window);
12772 self.change_selections(Default::default(), window, cx, |s| {
12773 s.move_heads_with(|map, head, goal| {
12774 movement::down(map, head, goal, false, text_layout_details)
12775 })
12776 });
12777 }
12778
12779 pub fn context_menu_first(
12780 &mut self,
12781 _: &ContextMenuFirst,
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_first(self.completion_provider.as_deref(), window, cx);
12787 }
12788 }
12789
12790 pub fn context_menu_prev(
12791 &mut self,
12792 _: &ContextMenuPrevious,
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_prev(self.completion_provider.as_deref(), window, cx);
12798 }
12799 }
12800
12801 pub fn context_menu_next(
12802 &mut self,
12803 _: &ContextMenuNext,
12804 window: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12808 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12809 }
12810 }
12811
12812 pub fn context_menu_last(
12813 &mut self,
12814 _: &ContextMenuLast,
12815 window: &mut Window,
12816 cx: &mut Context<Self>,
12817 ) {
12818 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12819 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12820 }
12821 }
12822
12823 pub fn signature_help_prev(
12824 &mut self,
12825 _: &SignatureHelpPrevious,
12826 _: &mut Window,
12827 cx: &mut Context<Self>,
12828 ) {
12829 if let Some(popover) = self.signature_help_state.popover_mut() {
12830 if popover.current_signature == 0 {
12831 popover.current_signature = popover.signatures.len() - 1;
12832 } else {
12833 popover.current_signature -= 1;
12834 }
12835 cx.notify();
12836 }
12837 }
12838
12839 pub fn signature_help_next(
12840 &mut self,
12841 _: &SignatureHelpNext,
12842 _: &mut Window,
12843 cx: &mut Context<Self>,
12844 ) {
12845 if let Some(popover) = self.signature_help_state.popover_mut() {
12846 if popover.current_signature + 1 == popover.signatures.len() {
12847 popover.current_signature = 0;
12848 } else {
12849 popover.current_signature += 1;
12850 }
12851 cx.notify();
12852 }
12853 }
12854
12855 pub fn move_to_previous_word_start(
12856 &mut self,
12857 _: &MoveToPreviousWordStart,
12858 window: &mut Window,
12859 cx: &mut Context<Self>,
12860 ) {
12861 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12862 self.change_selections(Default::default(), window, cx, |s| {
12863 s.move_cursors_with(|map, head, _| {
12864 (
12865 movement::previous_word_start(map, head),
12866 SelectionGoal::None,
12867 )
12868 });
12869 })
12870 }
12871
12872 pub fn move_to_previous_subword_start(
12873 &mut self,
12874 _: &MoveToPreviousSubwordStart,
12875 window: &mut Window,
12876 cx: &mut Context<Self>,
12877 ) {
12878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12879 self.change_selections(Default::default(), window, cx, |s| {
12880 s.move_cursors_with(|map, head, _| {
12881 (
12882 movement::previous_subword_start(map, head),
12883 SelectionGoal::None,
12884 )
12885 });
12886 })
12887 }
12888
12889 pub fn select_to_previous_word_start(
12890 &mut self,
12891 _: &SelectToPreviousWordStart,
12892 window: &mut Window,
12893 cx: &mut Context<Self>,
12894 ) {
12895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12896 self.change_selections(Default::default(), window, cx, |s| {
12897 s.move_heads_with(|map, head, _| {
12898 (
12899 movement::previous_word_start(map, head),
12900 SelectionGoal::None,
12901 )
12902 });
12903 })
12904 }
12905
12906 pub fn select_to_previous_subword_start(
12907 &mut self,
12908 _: &SelectToPreviousSubwordStart,
12909 window: &mut Window,
12910 cx: &mut Context<Self>,
12911 ) {
12912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12913 self.change_selections(Default::default(), window, cx, |s| {
12914 s.move_heads_with(|map, head, _| {
12915 (
12916 movement::previous_subword_start(map, head),
12917 SelectionGoal::None,
12918 )
12919 });
12920 })
12921 }
12922
12923 pub fn delete_to_previous_word_start(
12924 &mut self,
12925 action: &DeleteToPreviousWordStart,
12926 window: &mut Window,
12927 cx: &mut Context<Self>,
12928 ) {
12929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12930 self.transact(window, cx, |this, window, cx| {
12931 this.select_autoclose_pair(window, cx);
12932 this.change_selections(Default::default(), window, cx, |s| {
12933 s.move_with(|map, selection| {
12934 if selection.is_empty() {
12935 let cursor = if action.ignore_newlines {
12936 movement::previous_word_start(map, selection.head())
12937 } else {
12938 movement::previous_word_start_or_newline(map, selection.head())
12939 };
12940 selection.set_head(cursor, SelectionGoal::None);
12941 }
12942 });
12943 });
12944 this.insert("", window, cx);
12945 });
12946 }
12947
12948 pub fn delete_to_previous_subword_start(
12949 &mut self,
12950 _: &DeleteToPreviousSubwordStart,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12955 self.transact(window, cx, |this, window, cx| {
12956 this.select_autoclose_pair(window, cx);
12957 this.change_selections(Default::default(), window, cx, |s| {
12958 s.move_with(|map, selection| {
12959 if selection.is_empty() {
12960 let cursor = movement::previous_subword_start(map, selection.head());
12961 selection.set_head(cursor, SelectionGoal::None);
12962 }
12963 });
12964 });
12965 this.insert("", window, cx);
12966 });
12967 }
12968
12969 pub fn move_to_next_word_end(
12970 &mut self,
12971 _: &MoveToNextWordEnd,
12972 window: &mut Window,
12973 cx: &mut Context<Self>,
12974 ) {
12975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12976 self.change_selections(Default::default(), window, cx, |s| {
12977 s.move_cursors_with(|map, head, _| {
12978 (movement::next_word_end(map, head), SelectionGoal::None)
12979 });
12980 })
12981 }
12982
12983 pub fn move_to_next_subword_end(
12984 &mut self,
12985 _: &MoveToNextSubwordEnd,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) {
12989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12990 self.change_selections(Default::default(), window, cx, |s| {
12991 s.move_cursors_with(|map, head, _| {
12992 (movement::next_subword_end(map, head), SelectionGoal::None)
12993 });
12994 })
12995 }
12996
12997 pub fn select_to_next_word_end(
12998 &mut self,
12999 _: &SelectToNextWordEnd,
13000 window: &mut Window,
13001 cx: &mut Context<Self>,
13002 ) {
13003 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13004 self.change_selections(Default::default(), window, cx, |s| {
13005 s.move_heads_with(|map, head, _| {
13006 (movement::next_word_end(map, head), SelectionGoal::None)
13007 });
13008 })
13009 }
13010
13011 pub fn select_to_next_subword_end(
13012 &mut self,
13013 _: &SelectToNextSubwordEnd,
13014 window: &mut Window,
13015 cx: &mut Context<Self>,
13016 ) {
13017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13018 self.change_selections(Default::default(), window, cx, |s| {
13019 s.move_heads_with(|map, head, _| {
13020 (movement::next_subword_end(map, head), SelectionGoal::None)
13021 });
13022 })
13023 }
13024
13025 pub fn delete_to_next_word_end(
13026 &mut self,
13027 action: &DeleteToNextWordEnd,
13028 window: &mut Window,
13029 cx: &mut Context<Self>,
13030 ) {
13031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13032 self.transact(window, cx, |this, window, cx| {
13033 this.change_selections(Default::default(), window, cx, |s| {
13034 s.move_with(|map, selection| {
13035 if selection.is_empty() {
13036 let cursor = if action.ignore_newlines {
13037 movement::next_word_end(map, selection.head())
13038 } else {
13039 movement::next_word_end_or_newline(map, selection.head())
13040 };
13041 selection.set_head(cursor, SelectionGoal::None);
13042 }
13043 });
13044 });
13045 this.insert("", window, cx);
13046 });
13047 }
13048
13049 pub fn delete_to_next_subword_end(
13050 &mut self,
13051 _: &DeleteToNextSubwordEnd,
13052 window: &mut Window,
13053 cx: &mut Context<Self>,
13054 ) {
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13056 self.transact(window, cx, |this, window, cx| {
13057 this.change_selections(Default::default(), window, cx, |s| {
13058 s.move_with(|map, selection| {
13059 if selection.is_empty() {
13060 let cursor = movement::next_subword_end(map, selection.head());
13061 selection.set_head(cursor, SelectionGoal::None);
13062 }
13063 });
13064 });
13065 this.insert("", window, cx);
13066 });
13067 }
13068
13069 pub fn move_to_beginning_of_line(
13070 &mut self,
13071 action: &MoveToBeginningOfLine,
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_cursors_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 select_to_beginning_of_line(
13092 &mut self,
13093 action: &SelectToBeginningOfLine,
13094 window: &mut Window,
13095 cx: &mut Context<Self>,
13096 ) {
13097 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13098 self.change_selections(Default::default(), window, cx, |s| {
13099 s.move_heads_with(|map, head, _| {
13100 (
13101 movement::indented_line_beginning(
13102 map,
13103 head,
13104 action.stop_at_soft_wraps,
13105 action.stop_at_indent,
13106 ),
13107 SelectionGoal::None,
13108 )
13109 });
13110 });
13111 }
13112
13113 pub fn delete_to_beginning_of_line(
13114 &mut self,
13115 action: &DeleteToBeginningOfLine,
13116 window: &mut Window,
13117 cx: &mut Context<Self>,
13118 ) {
13119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13120 self.transact(window, cx, |this, window, cx| {
13121 this.change_selections(Default::default(), window, cx, |s| {
13122 s.move_with(|_, selection| {
13123 selection.reversed = true;
13124 });
13125 });
13126
13127 this.select_to_beginning_of_line(
13128 &SelectToBeginningOfLine {
13129 stop_at_soft_wraps: false,
13130 stop_at_indent: action.stop_at_indent,
13131 },
13132 window,
13133 cx,
13134 );
13135 this.backspace(&Backspace, window, cx);
13136 });
13137 }
13138
13139 pub fn move_to_end_of_line(
13140 &mut self,
13141 action: &MoveToEndOfLine,
13142 window: &mut Window,
13143 cx: &mut Context<Self>,
13144 ) {
13145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13146 self.change_selections(Default::default(), window, cx, |s| {
13147 s.move_cursors_with(|map, head, _| {
13148 (
13149 movement::line_end(map, head, action.stop_at_soft_wraps),
13150 SelectionGoal::None,
13151 )
13152 });
13153 })
13154 }
13155
13156 pub fn select_to_end_of_line(
13157 &mut self,
13158 action: &SelectToEndOfLine,
13159 window: &mut Window,
13160 cx: &mut Context<Self>,
13161 ) {
13162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13163 self.change_selections(Default::default(), window, cx, |s| {
13164 s.move_heads_with(|map, head, _| {
13165 (
13166 movement::line_end(map, head, action.stop_at_soft_wraps),
13167 SelectionGoal::None,
13168 )
13169 });
13170 })
13171 }
13172
13173 pub fn delete_to_end_of_line(
13174 &mut self,
13175 _: &DeleteToEndOfLine,
13176 window: &mut Window,
13177 cx: &mut Context<Self>,
13178 ) {
13179 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13180 self.transact(window, cx, |this, window, cx| {
13181 this.select_to_end_of_line(
13182 &SelectToEndOfLine {
13183 stop_at_soft_wraps: false,
13184 },
13185 window,
13186 cx,
13187 );
13188 this.delete(&Delete, window, cx);
13189 });
13190 }
13191
13192 pub fn cut_to_end_of_line(
13193 &mut self,
13194 _: &CutToEndOfLine,
13195 window: &mut Window,
13196 cx: &mut Context<Self>,
13197 ) {
13198 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13199 self.transact(window, cx, |this, window, cx| {
13200 this.select_to_end_of_line(
13201 &SelectToEndOfLine {
13202 stop_at_soft_wraps: false,
13203 },
13204 window,
13205 cx,
13206 );
13207 this.cut(&Cut, window, cx);
13208 });
13209 }
13210
13211 pub fn move_to_start_of_paragraph(
13212 &mut self,
13213 _: &MoveToStartOfParagraph,
13214 window: &mut Window,
13215 cx: &mut Context<Self>,
13216 ) {
13217 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13218 cx.propagate();
13219 return;
13220 }
13221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13222 self.change_selections(Default::default(), window, cx, |s| {
13223 s.move_with(|map, selection| {
13224 selection.collapse_to(
13225 movement::start_of_paragraph(map, selection.head(), 1),
13226 SelectionGoal::None,
13227 )
13228 });
13229 })
13230 }
13231
13232 pub fn move_to_end_of_paragraph(
13233 &mut self,
13234 _: &MoveToEndOfParagraph,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) {
13238 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13239 cx.propagate();
13240 return;
13241 }
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243 self.change_selections(Default::default(), window, cx, |s| {
13244 s.move_with(|map, selection| {
13245 selection.collapse_to(
13246 movement::end_of_paragraph(map, selection.head(), 1),
13247 SelectionGoal::None,
13248 )
13249 });
13250 })
13251 }
13252
13253 pub fn select_to_start_of_paragraph(
13254 &mut self,
13255 _: &SelectToStartOfParagraph,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) {
13259 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13260 cx.propagate();
13261 return;
13262 }
13263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13264 self.change_selections(Default::default(), window, cx, |s| {
13265 s.move_heads_with(|map, head, _| {
13266 (
13267 movement::start_of_paragraph(map, head, 1),
13268 SelectionGoal::None,
13269 )
13270 });
13271 })
13272 }
13273
13274 pub fn select_to_end_of_paragraph(
13275 &mut self,
13276 _: &SelectToEndOfParagraph,
13277 window: &mut Window,
13278 cx: &mut Context<Self>,
13279 ) {
13280 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13281 cx.propagate();
13282 return;
13283 }
13284 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13285 self.change_selections(Default::default(), window, cx, |s| {
13286 s.move_heads_with(|map, head, _| {
13287 (
13288 movement::end_of_paragraph(map, head, 1),
13289 SelectionGoal::None,
13290 )
13291 });
13292 })
13293 }
13294
13295 pub fn move_to_start_of_excerpt(
13296 &mut self,
13297 _: &MoveToStartOfExcerpt,
13298 window: &mut Window,
13299 cx: &mut Context<Self>,
13300 ) {
13301 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13302 cx.propagate();
13303 return;
13304 }
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13306 self.change_selections(Default::default(), window, cx, |s| {
13307 s.move_with(|map, selection| {
13308 selection.collapse_to(
13309 movement::start_of_excerpt(
13310 map,
13311 selection.head(),
13312 workspace::searchable::Direction::Prev,
13313 ),
13314 SelectionGoal::None,
13315 )
13316 });
13317 })
13318 }
13319
13320 pub fn move_to_start_of_next_excerpt(
13321 &mut self,
13322 _: &MoveToStartOfNextExcerpt,
13323 window: &mut Window,
13324 cx: &mut Context<Self>,
13325 ) {
13326 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13327 cx.propagate();
13328 return;
13329 }
13330
13331 self.change_selections(Default::default(), window, cx, |s| {
13332 s.move_with(|map, selection| {
13333 selection.collapse_to(
13334 movement::start_of_excerpt(
13335 map,
13336 selection.head(),
13337 workspace::searchable::Direction::Next,
13338 ),
13339 SelectionGoal::None,
13340 )
13341 });
13342 })
13343 }
13344
13345 pub fn move_to_end_of_excerpt(
13346 &mut self,
13347 _: &MoveToEndOfExcerpt,
13348 window: &mut Window,
13349 cx: &mut Context<Self>,
13350 ) {
13351 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13352 cx.propagate();
13353 return;
13354 }
13355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13356 self.change_selections(Default::default(), window, cx, |s| {
13357 s.move_with(|map, selection| {
13358 selection.collapse_to(
13359 movement::end_of_excerpt(
13360 map,
13361 selection.head(),
13362 workspace::searchable::Direction::Next,
13363 ),
13364 SelectionGoal::None,
13365 )
13366 });
13367 })
13368 }
13369
13370 pub fn move_to_end_of_previous_excerpt(
13371 &mut self,
13372 _: &MoveToEndOfPreviousExcerpt,
13373 window: &mut Window,
13374 cx: &mut Context<Self>,
13375 ) {
13376 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13377 cx.propagate();
13378 return;
13379 }
13380 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13381 self.change_selections(Default::default(), window, cx, |s| {
13382 s.move_with(|map, selection| {
13383 selection.collapse_to(
13384 movement::end_of_excerpt(
13385 map,
13386 selection.head(),
13387 workspace::searchable::Direction::Prev,
13388 ),
13389 SelectionGoal::None,
13390 )
13391 });
13392 })
13393 }
13394
13395 pub fn select_to_start_of_excerpt(
13396 &mut self,
13397 _: &SelectToStartOfExcerpt,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13402 cx.propagate();
13403 return;
13404 }
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13406 self.change_selections(Default::default(), window, cx, |s| {
13407 s.move_heads_with(|map, head, _| {
13408 (
13409 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13410 SelectionGoal::None,
13411 )
13412 });
13413 })
13414 }
13415
13416 pub fn select_to_start_of_next_excerpt(
13417 &mut self,
13418 _: &SelectToStartOfNextExcerpt,
13419 window: &mut Window,
13420 cx: &mut Context<Self>,
13421 ) {
13422 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13423 cx.propagate();
13424 return;
13425 }
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13427 self.change_selections(Default::default(), window, cx, |s| {
13428 s.move_heads_with(|map, head, _| {
13429 (
13430 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13431 SelectionGoal::None,
13432 )
13433 });
13434 })
13435 }
13436
13437 pub fn select_to_end_of_excerpt(
13438 &mut self,
13439 _: &SelectToEndOfExcerpt,
13440 window: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13444 cx.propagate();
13445 return;
13446 }
13447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13448 self.change_selections(Default::default(), window, cx, |s| {
13449 s.move_heads_with(|map, head, _| {
13450 (
13451 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13452 SelectionGoal::None,
13453 )
13454 });
13455 })
13456 }
13457
13458 pub fn select_to_end_of_previous_excerpt(
13459 &mut self,
13460 _: &SelectToEndOfPreviousExcerpt,
13461 window: &mut Window,
13462 cx: &mut Context<Self>,
13463 ) {
13464 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13465 cx.propagate();
13466 return;
13467 }
13468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13469 self.change_selections(Default::default(), window, cx, |s| {
13470 s.move_heads_with(|map, head, _| {
13471 (
13472 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13473 SelectionGoal::None,
13474 )
13475 });
13476 })
13477 }
13478
13479 pub fn move_to_beginning(
13480 &mut self,
13481 _: &MoveToBeginning,
13482 window: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13486 cx.propagate();
13487 return;
13488 }
13489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13490 self.change_selections(Default::default(), window, cx, |s| {
13491 s.select_ranges(vec![0..0]);
13492 });
13493 }
13494
13495 pub fn select_to_beginning(
13496 &mut self,
13497 _: &SelectToBeginning,
13498 window: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 let mut selection = self.selections.last::<Point>(cx);
13502 selection.set_head(Point::zero(), SelectionGoal::None);
13503 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13504 self.change_selections(Default::default(), window, cx, |s| {
13505 s.select(vec![selection]);
13506 });
13507 }
13508
13509 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13510 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13511 cx.propagate();
13512 return;
13513 }
13514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13515 let cursor = self.buffer.read(cx).read(cx).len();
13516 self.change_selections(Default::default(), window, cx, |s| {
13517 s.select_ranges(vec![cursor..cursor])
13518 });
13519 }
13520
13521 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13522 self.nav_history = nav_history;
13523 }
13524
13525 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13526 self.nav_history.as_ref()
13527 }
13528
13529 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13530 self.push_to_nav_history(
13531 self.selections.newest_anchor().head(),
13532 None,
13533 false,
13534 true,
13535 cx,
13536 );
13537 }
13538
13539 fn push_to_nav_history(
13540 &mut self,
13541 cursor_anchor: Anchor,
13542 new_position: Option<Point>,
13543 is_deactivate: bool,
13544 always: bool,
13545 cx: &mut Context<Self>,
13546 ) {
13547 if let Some(nav_history) = self.nav_history.as_mut() {
13548 let buffer = self.buffer.read(cx).read(cx);
13549 let cursor_position = cursor_anchor.to_point(&buffer);
13550 let scroll_state = self.scroll_manager.anchor();
13551 let scroll_top_row = scroll_state.top_row(&buffer);
13552 drop(buffer);
13553
13554 if let Some(new_position) = new_position {
13555 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13556 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13557 return;
13558 }
13559 }
13560
13561 nav_history.push(
13562 Some(NavigationData {
13563 cursor_anchor,
13564 cursor_position,
13565 scroll_anchor: scroll_state,
13566 scroll_top_row,
13567 }),
13568 cx,
13569 );
13570 cx.emit(EditorEvent::PushedToNavHistory {
13571 anchor: cursor_anchor,
13572 is_deactivate,
13573 })
13574 }
13575 }
13576
13577 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13579 let buffer = self.buffer.read(cx).snapshot(cx);
13580 let mut selection = self.selections.first::<usize>(cx);
13581 selection.set_head(buffer.len(), SelectionGoal::None);
13582 self.change_selections(Default::default(), window, cx, |s| {
13583 s.select(vec![selection]);
13584 });
13585 }
13586
13587 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13589 let end = self.buffer.read(cx).read(cx).len();
13590 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13591 s.select_ranges(vec![0..end]);
13592 });
13593 }
13594
13595 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13598 let mut selections = self.selections.all::<Point>(cx);
13599 let max_point = display_map.buffer_snapshot.max_point();
13600 for selection in &mut selections {
13601 let rows = selection.spanned_rows(true, &display_map);
13602 selection.start = Point::new(rows.start.0, 0);
13603 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13604 selection.reversed = false;
13605 }
13606 self.change_selections(Default::default(), window, cx, |s| {
13607 s.select(selections);
13608 });
13609 }
13610
13611 pub fn split_selection_into_lines(
13612 &mut self,
13613 action: &SplitSelectionIntoLines,
13614 window: &mut Window,
13615 cx: &mut Context<Self>,
13616 ) {
13617 let selections = self
13618 .selections
13619 .all::<Point>(cx)
13620 .into_iter()
13621 .map(|selection| selection.start..selection.end)
13622 .collect::<Vec<_>>();
13623 self.unfold_ranges(&selections, true, true, cx);
13624
13625 let mut new_selection_ranges = Vec::new();
13626 {
13627 let buffer = self.buffer.read(cx).read(cx);
13628 for selection in selections {
13629 for row in selection.start.row..selection.end.row {
13630 let line_start = Point::new(row, 0);
13631 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13632
13633 if action.keep_selections {
13634 // Keep the selection range for each line
13635 let selection_start = if row == selection.start.row {
13636 selection.start
13637 } else {
13638 line_start
13639 };
13640 new_selection_ranges.push(selection_start..line_end);
13641 } else {
13642 // Collapse to cursor at end of line
13643 new_selection_ranges.push(line_end..line_end);
13644 }
13645 }
13646
13647 let is_multiline_selection = selection.start.row != selection.end.row;
13648 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13649 // so this action feels more ergonomic when paired with other selection operations
13650 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13651 if !should_skip_last {
13652 if action.keep_selections {
13653 if is_multiline_selection {
13654 let line_start = Point::new(selection.end.row, 0);
13655 new_selection_ranges.push(line_start..selection.end);
13656 } else {
13657 new_selection_ranges.push(selection.start..selection.end);
13658 }
13659 } else {
13660 new_selection_ranges.push(selection.end..selection.end);
13661 }
13662 }
13663 }
13664 }
13665 self.change_selections(Default::default(), window, cx, |s| {
13666 s.select_ranges(new_selection_ranges);
13667 });
13668 }
13669
13670 pub fn add_selection_above(
13671 &mut self,
13672 _: &AddSelectionAbove,
13673 window: &mut Window,
13674 cx: &mut Context<Self>,
13675 ) {
13676 self.add_selection(true, window, cx);
13677 }
13678
13679 pub fn add_selection_below(
13680 &mut self,
13681 _: &AddSelectionBelow,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 self.add_selection(false, window, cx);
13686 }
13687
13688 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13690
13691 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13692 let all_selections = self.selections.all::<Point>(cx);
13693 let text_layout_details = self.text_layout_details(window);
13694
13695 let (mut columnar_selections, new_selections_to_columnarize) = {
13696 if let Some(state) = self.add_selections_state.as_ref() {
13697 let columnar_selection_ids: HashSet<_> = state
13698 .groups
13699 .iter()
13700 .flat_map(|group| group.stack.iter())
13701 .copied()
13702 .collect();
13703
13704 all_selections
13705 .into_iter()
13706 .partition(|s| columnar_selection_ids.contains(&s.id))
13707 } else {
13708 (Vec::new(), all_selections)
13709 }
13710 };
13711
13712 let mut state = self
13713 .add_selections_state
13714 .take()
13715 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13716
13717 for selection in new_selections_to_columnarize {
13718 let range = selection.display_range(&display_map).sorted();
13719 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13720 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13721 let positions = start_x.min(end_x)..start_x.max(end_x);
13722 let mut stack = Vec::new();
13723 for row in range.start.row().0..=range.end.row().0 {
13724 if let Some(selection) = self.selections.build_columnar_selection(
13725 &display_map,
13726 DisplayRow(row),
13727 &positions,
13728 selection.reversed,
13729 &text_layout_details,
13730 ) {
13731 stack.push(selection.id);
13732 columnar_selections.push(selection);
13733 }
13734 }
13735 if !stack.is_empty() {
13736 if above {
13737 stack.reverse();
13738 }
13739 state.groups.push(AddSelectionsGroup { above, stack });
13740 }
13741 }
13742
13743 let mut final_selections = Vec::new();
13744 let end_row = if above {
13745 DisplayRow(0)
13746 } else {
13747 display_map.max_point().row()
13748 };
13749
13750 let mut last_added_item_per_group = HashMap::default();
13751 for group in state.groups.iter_mut() {
13752 if let Some(last_id) = group.stack.last() {
13753 last_added_item_per_group.insert(*last_id, group);
13754 }
13755 }
13756
13757 for selection in columnar_selections {
13758 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13759 if above == group.above {
13760 let range = selection.display_range(&display_map).sorted();
13761 debug_assert_eq!(range.start.row(), range.end.row());
13762 let mut row = range.start.row();
13763 let positions =
13764 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13765 px(start)..px(end)
13766 } else {
13767 let start_x =
13768 display_map.x_for_display_point(range.start, &text_layout_details);
13769 let end_x =
13770 display_map.x_for_display_point(range.end, &text_layout_details);
13771 start_x.min(end_x)..start_x.max(end_x)
13772 };
13773
13774 let mut maybe_new_selection = None;
13775 while row != end_row {
13776 if above {
13777 row.0 -= 1;
13778 } else {
13779 row.0 += 1;
13780 }
13781 if let Some(new_selection) = self.selections.build_columnar_selection(
13782 &display_map,
13783 row,
13784 &positions,
13785 selection.reversed,
13786 &text_layout_details,
13787 ) {
13788 maybe_new_selection = Some(new_selection);
13789 break;
13790 }
13791 }
13792
13793 if let Some(new_selection) = maybe_new_selection {
13794 group.stack.push(new_selection.id);
13795 if above {
13796 final_selections.push(new_selection);
13797 final_selections.push(selection);
13798 } else {
13799 final_selections.push(selection);
13800 final_selections.push(new_selection);
13801 }
13802 } else {
13803 final_selections.push(selection);
13804 }
13805 } else {
13806 group.stack.pop();
13807 }
13808 } else {
13809 final_selections.push(selection);
13810 }
13811 }
13812
13813 self.change_selections(Default::default(), window, cx, |s| {
13814 s.select(final_selections);
13815 });
13816
13817 let final_selection_ids: HashSet<_> = self
13818 .selections
13819 .all::<Point>(cx)
13820 .iter()
13821 .map(|s| s.id)
13822 .collect();
13823 state.groups.retain_mut(|group| {
13824 // selections might get merged above so we remove invalid items from stacks
13825 group.stack.retain(|id| final_selection_ids.contains(id));
13826
13827 // single selection in stack can be treated as initial state
13828 group.stack.len() > 1
13829 });
13830
13831 if !state.groups.is_empty() {
13832 self.add_selections_state = Some(state);
13833 }
13834 }
13835
13836 fn select_match_ranges(
13837 &mut self,
13838 range: Range<usize>,
13839 reversed: bool,
13840 replace_newest: bool,
13841 auto_scroll: Option<Autoscroll>,
13842 window: &mut Window,
13843 cx: &mut Context<Editor>,
13844 ) {
13845 self.unfold_ranges(
13846 std::slice::from_ref(&range),
13847 false,
13848 auto_scroll.is_some(),
13849 cx,
13850 );
13851 let effects = if let Some(scroll) = auto_scroll {
13852 SelectionEffects::scroll(scroll)
13853 } else {
13854 SelectionEffects::no_scroll()
13855 };
13856 self.change_selections(effects, window, cx, |s| {
13857 if replace_newest {
13858 s.delete(s.newest_anchor().id);
13859 }
13860 if reversed {
13861 s.insert_range(range.end..range.start);
13862 } else {
13863 s.insert_range(range);
13864 }
13865 });
13866 }
13867
13868 pub fn select_next_match_internal(
13869 &mut self,
13870 display_map: &DisplaySnapshot,
13871 replace_newest: bool,
13872 autoscroll: Option<Autoscroll>,
13873 window: &mut Window,
13874 cx: &mut Context<Self>,
13875 ) -> Result<()> {
13876 let buffer = &display_map.buffer_snapshot;
13877 let mut selections = self.selections.all::<usize>(cx);
13878 if let Some(mut select_next_state) = self.select_next_state.take() {
13879 let query = &select_next_state.query;
13880 if !select_next_state.done {
13881 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13882 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13883 let mut next_selected_range = None;
13884
13885 let bytes_after_last_selection =
13886 buffer.bytes_in_range(last_selection.end..buffer.len());
13887 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13888 let query_matches = query
13889 .stream_find_iter(bytes_after_last_selection)
13890 .map(|result| (last_selection.end, result))
13891 .chain(
13892 query
13893 .stream_find_iter(bytes_before_first_selection)
13894 .map(|result| (0, result)),
13895 );
13896
13897 for (start_offset, query_match) in query_matches {
13898 let query_match = query_match.unwrap(); // can only fail due to I/O
13899 let offset_range =
13900 start_offset + query_match.start()..start_offset + query_match.end();
13901
13902 if !select_next_state.wordwise
13903 || (!buffer.is_inside_word(offset_range.start, false)
13904 && !buffer.is_inside_word(offset_range.end, false))
13905 {
13906 // TODO: This is n^2, because we might check all the selections
13907 if !selections
13908 .iter()
13909 .any(|selection| selection.range().overlaps(&offset_range))
13910 {
13911 next_selected_range = Some(offset_range);
13912 break;
13913 }
13914 }
13915 }
13916
13917 if let Some(next_selected_range) = next_selected_range {
13918 self.select_match_ranges(
13919 next_selected_range,
13920 last_selection.reversed,
13921 replace_newest,
13922 autoscroll,
13923 window,
13924 cx,
13925 );
13926 } else {
13927 select_next_state.done = true;
13928 }
13929 }
13930
13931 self.select_next_state = Some(select_next_state);
13932 } else {
13933 let mut only_carets = true;
13934 let mut same_text_selected = true;
13935 let mut selected_text = None;
13936
13937 let mut selections_iter = selections.iter().peekable();
13938 while let Some(selection) = selections_iter.next() {
13939 if selection.start != selection.end {
13940 only_carets = false;
13941 }
13942
13943 if same_text_selected {
13944 if selected_text.is_none() {
13945 selected_text =
13946 Some(buffer.text_for_range(selection.range()).collect::<String>());
13947 }
13948
13949 if let Some(next_selection) = selections_iter.peek() {
13950 if next_selection.range().len() == selection.range().len() {
13951 let next_selected_text = buffer
13952 .text_for_range(next_selection.range())
13953 .collect::<String>();
13954 if Some(next_selected_text) != selected_text {
13955 same_text_selected = false;
13956 selected_text = None;
13957 }
13958 } else {
13959 same_text_selected = false;
13960 selected_text = None;
13961 }
13962 }
13963 }
13964 }
13965
13966 if only_carets {
13967 for selection in &mut selections {
13968 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13969 selection.start = word_range.start;
13970 selection.end = word_range.end;
13971 selection.goal = SelectionGoal::None;
13972 selection.reversed = false;
13973 self.select_match_ranges(
13974 selection.start..selection.end,
13975 selection.reversed,
13976 replace_newest,
13977 autoscroll,
13978 window,
13979 cx,
13980 );
13981 }
13982
13983 if selections.len() == 1 {
13984 let selection = selections
13985 .last()
13986 .expect("ensured that there's only one selection");
13987 let query = buffer
13988 .text_for_range(selection.start..selection.end)
13989 .collect::<String>();
13990 let is_empty = query.is_empty();
13991 let select_state = SelectNextState {
13992 query: AhoCorasick::new(&[query])?,
13993 wordwise: true,
13994 done: is_empty,
13995 };
13996 self.select_next_state = Some(select_state);
13997 } else {
13998 self.select_next_state = None;
13999 }
14000 } else if let Some(selected_text) = selected_text {
14001 self.select_next_state = Some(SelectNextState {
14002 query: AhoCorasick::new(&[selected_text])?,
14003 wordwise: false,
14004 done: false,
14005 });
14006 self.select_next_match_internal(
14007 display_map,
14008 replace_newest,
14009 autoscroll,
14010 window,
14011 cx,
14012 )?;
14013 }
14014 }
14015 Ok(())
14016 }
14017
14018 pub fn select_all_matches(
14019 &mut self,
14020 _action: &SelectAllMatches,
14021 window: &mut Window,
14022 cx: &mut Context<Self>,
14023 ) -> Result<()> {
14024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14025
14026 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14027
14028 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14029 let Some(select_next_state) = self.select_next_state.as_mut() else {
14030 return Ok(());
14031 };
14032 if select_next_state.done {
14033 return Ok(());
14034 }
14035
14036 let mut new_selections = Vec::new();
14037
14038 let reversed = self.selections.oldest::<usize>(cx).reversed;
14039 let buffer = &display_map.buffer_snapshot;
14040 let query_matches = select_next_state
14041 .query
14042 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14043
14044 for query_match in query_matches.into_iter() {
14045 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14046 let offset_range = if reversed {
14047 query_match.end()..query_match.start()
14048 } else {
14049 query_match.start()..query_match.end()
14050 };
14051
14052 if !select_next_state.wordwise
14053 || (!buffer.is_inside_word(offset_range.start, false)
14054 && !buffer.is_inside_word(offset_range.end, false))
14055 {
14056 new_selections.push(offset_range.start..offset_range.end);
14057 }
14058 }
14059
14060 select_next_state.done = true;
14061
14062 if new_selections.is_empty() {
14063 log::error!("bug: new_selections is empty in select_all_matches");
14064 return Ok(());
14065 }
14066
14067 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14069 selections.select_ranges(new_selections)
14070 });
14071
14072 Ok(())
14073 }
14074
14075 pub fn select_next(
14076 &mut self,
14077 action: &SelectNext,
14078 window: &mut Window,
14079 cx: &mut Context<Self>,
14080 ) -> Result<()> {
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14082 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14083 self.select_next_match_internal(
14084 &display_map,
14085 action.replace_newest,
14086 Some(Autoscroll::newest()),
14087 window,
14088 cx,
14089 )?;
14090 Ok(())
14091 }
14092
14093 pub fn select_previous(
14094 &mut self,
14095 action: &SelectPrevious,
14096 window: &mut Window,
14097 cx: &mut Context<Self>,
14098 ) -> Result<()> {
14099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14100 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14101 let buffer = &display_map.buffer_snapshot;
14102 let mut selections = self.selections.all::<usize>(cx);
14103 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14104 let query = &select_prev_state.query;
14105 if !select_prev_state.done {
14106 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14107 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14108 let mut next_selected_range = None;
14109 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14110 let bytes_before_last_selection =
14111 buffer.reversed_bytes_in_range(0..last_selection.start);
14112 let bytes_after_first_selection =
14113 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14114 let query_matches = query
14115 .stream_find_iter(bytes_before_last_selection)
14116 .map(|result| (last_selection.start, result))
14117 .chain(
14118 query
14119 .stream_find_iter(bytes_after_first_selection)
14120 .map(|result| (buffer.len(), result)),
14121 );
14122 for (end_offset, query_match) in query_matches {
14123 let query_match = query_match.unwrap(); // can only fail due to I/O
14124 let offset_range =
14125 end_offset - query_match.end()..end_offset - query_match.start();
14126
14127 if !select_prev_state.wordwise
14128 || (!buffer.is_inside_word(offset_range.start, false)
14129 && !buffer.is_inside_word(offset_range.end, false))
14130 {
14131 next_selected_range = Some(offset_range);
14132 break;
14133 }
14134 }
14135
14136 if let Some(next_selected_range) = next_selected_range {
14137 self.select_match_ranges(
14138 next_selected_range,
14139 last_selection.reversed,
14140 action.replace_newest,
14141 Some(Autoscroll::newest()),
14142 window,
14143 cx,
14144 );
14145 } else {
14146 select_prev_state.done = true;
14147 }
14148 }
14149
14150 self.select_prev_state = Some(select_prev_state);
14151 } else {
14152 let mut only_carets = true;
14153 let mut same_text_selected = true;
14154 let mut selected_text = None;
14155
14156 let mut selections_iter = selections.iter().peekable();
14157 while let Some(selection) = selections_iter.next() {
14158 if selection.start != selection.end {
14159 only_carets = false;
14160 }
14161
14162 if same_text_selected {
14163 if selected_text.is_none() {
14164 selected_text =
14165 Some(buffer.text_for_range(selection.range()).collect::<String>());
14166 }
14167
14168 if let Some(next_selection) = selections_iter.peek() {
14169 if next_selection.range().len() == selection.range().len() {
14170 let next_selected_text = buffer
14171 .text_for_range(next_selection.range())
14172 .collect::<String>();
14173 if Some(next_selected_text) != selected_text {
14174 same_text_selected = false;
14175 selected_text = None;
14176 }
14177 } else {
14178 same_text_selected = false;
14179 selected_text = None;
14180 }
14181 }
14182 }
14183 }
14184
14185 if only_carets {
14186 for selection in &mut selections {
14187 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14188 selection.start = word_range.start;
14189 selection.end = word_range.end;
14190 selection.goal = SelectionGoal::None;
14191 selection.reversed = false;
14192 self.select_match_ranges(
14193 selection.start..selection.end,
14194 selection.reversed,
14195 action.replace_newest,
14196 Some(Autoscroll::newest()),
14197 window,
14198 cx,
14199 );
14200 }
14201 if selections.len() == 1 {
14202 let selection = selections
14203 .last()
14204 .expect("ensured that there's only one selection");
14205 let query = buffer
14206 .text_for_range(selection.start..selection.end)
14207 .collect::<String>();
14208 let is_empty = query.is_empty();
14209 let select_state = SelectNextState {
14210 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14211 wordwise: true,
14212 done: is_empty,
14213 };
14214 self.select_prev_state = Some(select_state);
14215 } else {
14216 self.select_prev_state = None;
14217 }
14218 } else if let Some(selected_text) = selected_text {
14219 self.select_prev_state = Some(SelectNextState {
14220 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14221 wordwise: false,
14222 done: false,
14223 });
14224 self.select_previous(action, window, cx)?;
14225 }
14226 }
14227 Ok(())
14228 }
14229
14230 pub fn find_next_match(
14231 &mut self,
14232 _: &FindNextMatch,
14233 window: &mut Window,
14234 cx: &mut Context<Self>,
14235 ) -> Result<()> {
14236 let selections = self.selections.disjoint_anchors();
14237 match selections.first() {
14238 Some(first) if selections.len() >= 2 => {
14239 self.change_selections(Default::default(), window, cx, |s| {
14240 s.select_ranges([first.range()]);
14241 });
14242 }
14243 _ => self.select_next(
14244 &SelectNext {
14245 replace_newest: true,
14246 },
14247 window,
14248 cx,
14249 )?,
14250 }
14251 Ok(())
14252 }
14253
14254 pub fn find_previous_match(
14255 &mut self,
14256 _: &FindPreviousMatch,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) -> Result<()> {
14260 let selections = self.selections.disjoint_anchors();
14261 match selections.last() {
14262 Some(last) if selections.len() >= 2 => {
14263 self.change_selections(Default::default(), window, cx, |s| {
14264 s.select_ranges([last.range()]);
14265 });
14266 }
14267 _ => self.select_previous(
14268 &SelectPrevious {
14269 replace_newest: true,
14270 },
14271 window,
14272 cx,
14273 )?,
14274 }
14275 Ok(())
14276 }
14277
14278 pub fn toggle_comments(
14279 &mut self,
14280 action: &ToggleComments,
14281 window: &mut Window,
14282 cx: &mut Context<Self>,
14283 ) {
14284 if self.read_only(cx) {
14285 return;
14286 }
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14288 let text_layout_details = &self.text_layout_details(window);
14289 self.transact(window, cx, |this, window, cx| {
14290 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14291 let mut edits = Vec::new();
14292 let mut selection_edit_ranges = Vec::new();
14293 let mut last_toggled_row = None;
14294 let snapshot = this.buffer.read(cx).read(cx);
14295 let empty_str: Arc<str> = Arc::default();
14296 let mut suffixes_inserted = Vec::new();
14297 let ignore_indent = action.ignore_indent;
14298
14299 fn comment_prefix_range(
14300 snapshot: &MultiBufferSnapshot,
14301 row: MultiBufferRow,
14302 comment_prefix: &str,
14303 comment_prefix_whitespace: &str,
14304 ignore_indent: bool,
14305 ) -> Range<Point> {
14306 let indent_size = if ignore_indent {
14307 0
14308 } else {
14309 snapshot.indent_size_for_line(row).len
14310 };
14311
14312 let start = Point::new(row.0, indent_size);
14313
14314 let mut line_bytes = snapshot
14315 .bytes_in_range(start..snapshot.max_point())
14316 .flatten()
14317 .copied();
14318
14319 // If this line currently begins with the line comment prefix, then record
14320 // the range containing the prefix.
14321 if line_bytes
14322 .by_ref()
14323 .take(comment_prefix.len())
14324 .eq(comment_prefix.bytes())
14325 {
14326 // Include any whitespace that matches the comment prefix.
14327 let matching_whitespace_len = line_bytes
14328 .zip(comment_prefix_whitespace.bytes())
14329 .take_while(|(a, b)| a == b)
14330 .count() as u32;
14331 let end = Point::new(
14332 start.row,
14333 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14334 );
14335 start..end
14336 } else {
14337 start..start
14338 }
14339 }
14340
14341 fn comment_suffix_range(
14342 snapshot: &MultiBufferSnapshot,
14343 row: MultiBufferRow,
14344 comment_suffix: &str,
14345 comment_suffix_has_leading_space: bool,
14346 ) -> Range<Point> {
14347 let end = Point::new(row.0, snapshot.line_len(row));
14348 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14349
14350 let mut line_end_bytes = snapshot
14351 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14352 .flatten()
14353 .copied();
14354
14355 let leading_space_len = if suffix_start_column > 0
14356 && line_end_bytes.next() == Some(b' ')
14357 && comment_suffix_has_leading_space
14358 {
14359 1
14360 } else {
14361 0
14362 };
14363
14364 // If this line currently begins with the line comment prefix, then record
14365 // the range containing the prefix.
14366 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14367 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14368 start..end
14369 } else {
14370 end..end
14371 }
14372 }
14373
14374 // TODO: Handle selections that cross excerpts
14375 for selection in &mut selections {
14376 let start_column = snapshot
14377 .indent_size_for_line(MultiBufferRow(selection.start.row))
14378 .len;
14379 let language = if let Some(language) =
14380 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14381 {
14382 language
14383 } else {
14384 continue;
14385 };
14386
14387 selection_edit_ranges.clear();
14388
14389 // If multiple selections contain a given row, avoid processing that
14390 // row more than once.
14391 let mut start_row = MultiBufferRow(selection.start.row);
14392 if last_toggled_row == Some(start_row) {
14393 start_row = start_row.next_row();
14394 }
14395 let end_row =
14396 if selection.end.row > selection.start.row && selection.end.column == 0 {
14397 MultiBufferRow(selection.end.row - 1)
14398 } else {
14399 MultiBufferRow(selection.end.row)
14400 };
14401 last_toggled_row = Some(end_row);
14402
14403 if start_row > end_row {
14404 continue;
14405 }
14406
14407 // If the language has line comments, toggle those.
14408 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14409
14410 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14411 if ignore_indent {
14412 full_comment_prefixes = full_comment_prefixes
14413 .into_iter()
14414 .map(|s| Arc::from(s.trim_end()))
14415 .collect();
14416 }
14417
14418 if !full_comment_prefixes.is_empty() {
14419 let first_prefix = full_comment_prefixes
14420 .first()
14421 .expect("prefixes is non-empty");
14422 let prefix_trimmed_lengths = full_comment_prefixes
14423 .iter()
14424 .map(|p| p.trim_end_matches(' ').len())
14425 .collect::<SmallVec<[usize; 4]>>();
14426
14427 let mut all_selection_lines_are_comments = true;
14428
14429 for row in start_row.0..=end_row.0 {
14430 let row = MultiBufferRow(row);
14431 if start_row < end_row && snapshot.is_line_blank(row) {
14432 continue;
14433 }
14434
14435 let prefix_range = full_comment_prefixes
14436 .iter()
14437 .zip(prefix_trimmed_lengths.iter().copied())
14438 .map(|(prefix, trimmed_prefix_len)| {
14439 comment_prefix_range(
14440 snapshot.deref(),
14441 row,
14442 &prefix[..trimmed_prefix_len],
14443 &prefix[trimmed_prefix_len..],
14444 ignore_indent,
14445 )
14446 })
14447 .max_by_key(|range| range.end.column - range.start.column)
14448 .expect("prefixes is non-empty");
14449
14450 if prefix_range.is_empty() {
14451 all_selection_lines_are_comments = false;
14452 }
14453
14454 selection_edit_ranges.push(prefix_range);
14455 }
14456
14457 if all_selection_lines_are_comments {
14458 edits.extend(
14459 selection_edit_ranges
14460 .iter()
14461 .cloned()
14462 .map(|range| (range, empty_str.clone())),
14463 );
14464 } else {
14465 let min_column = selection_edit_ranges
14466 .iter()
14467 .map(|range| range.start.column)
14468 .min()
14469 .unwrap_or(0);
14470 edits.extend(selection_edit_ranges.iter().map(|range| {
14471 let position = Point::new(range.start.row, min_column);
14472 (position..position, first_prefix.clone())
14473 }));
14474 }
14475 } else if let Some(BlockCommentConfig {
14476 start: full_comment_prefix,
14477 end: comment_suffix,
14478 ..
14479 }) = language.block_comment()
14480 {
14481 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14482 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14483 let prefix_range = comment_prefix_range(
14484 snapshot.deref(),
14485 start_row,
14486 comment_prefix,
14487 comment_prefix_whitespace,
14488 ignore_indent,
14489 );
14490 let suffix_range = comment_suffix_range(
14491 snapshot.deref(),
14492 end_row,
14493 comment_suffix.trim_start_matches(' '),
14494 comment_suffix.starts_with(' '),
14495 );
14496
14497 if prefix_range.is_empty() || suffix_range.is_empty() {
14498 edits.push((
14499 prefix_range.start..prefix_range.start,
14500 full_comment_prefix.clone(),
14501 ));
14502 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14503 suffixes_inserted.push((end_row, comment_suffix.len()));
14504 } else {
14505 edits.push((prefix_range, empty_str.clone()));
14506 edits.push((suffix_range, empty_str.clone()));
14507 }
14508 } else {
14509 continue;
14510 }
14511 }
14512
14513 drop(snapshot);
14514 this.buffer.update(cx, |buffer, cx| {
14515 buffer.edit(edits, None, cx);
14516 });
14517
14518 // Adjust selections so that they end before any comment suffixes that
14519 // were inserted.
14520 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14521 let mut selections = this.selections.all::<Point>(cx);
14522 let snapshot = this.buffer.read(cx).read(cx);
14523 for selection in &mut selections {
14524 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14525 match row.cmp(&MultiBufferRow(selection.end.row)) {
14526 Ordering::Less => {
14527 suffixes_inserted.next();
14528 continue;
14529 }
14530 Ordering::Greater => break,
14531 Ordering::Equal => {
14532 if selection.end.column == snapshot.line_len(row) {
14533 if selection.is_empty() {
14534 selection.start.column -= suffix_len as u32;
14535 }
14536 selection.end.column -= suffix_len as u32;
14537 }
14538 break;
14539 }
14540 }
14541 }
14542 }
14543
14544 drop(snapshot);
14545 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14546
14547 let selections = this.selections.all::<Point>(cx);
14548 let selections_on_single_row = selections.windows(2).all(|selections| {
14549 selections[0].start.row == selections[1].start.row
14550 && selections[0].end.row == selections[1].end.row
14551 && selections[0].start.row == selections[0].end.row
14552 });
14553 let selections_selecting = selections
14554 .iter()
14555 .any(|selection| selection.start != selection.end);
14556 let advance_downwards = action.advance_downwards
14557 && selections_on_single_row
14558 && !selections_selecting
14559 && !matches!(this.mode, EditorMode::SingleLine { .. });
14560
14561 if advance_downwards {
14562 let snapshot = this.buffer.read(cx).snapshot(cx);
14563
14564 this.change_selections(Default::default(), window, cx, |s| {
14565 s.move_cursors_with(|display_snapshot, display_point, _| {
14566 let mut point = display_point.to_point(display_snapshot);
14567 point.row += 1;
14568 point = snapshot.clip_point(point, Bias::Left);
14569 let display_point = point.to_display_point(display_snapshot);
14570 let goal = SelectionGoal::HorizontalPosition(
14571 display_snapshot
14572 .x_for_display_point(display_point, text_layout_details)
14573 .into(),
14574 );
14575 (display_point, goal)
14576 })
14577 });
14578 }
14579 });
14580 }
14581
14582 pub fn select_enclosing_symbol(
14583 &mut self,
14584 _: &SelectEnclosingSymbol,
14585 window: &mut Window,
14586 cx: &mut Context<Self>,
14587 ) {
14588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14589
14590 let buffer = self.buffer.read(cx).snapshot(cx);
14591 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14592
14593 fn update_selection(
14594 selection: &Selection<usize>,
14595 buffer_snap: &MultiBufferSnapshot,
14596 ) -> Option<Selection<usize>> {
14597 let cursor = selection.head();
14598 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14599 for symbol in symbols.iter().rev() {
14600 let start = symbol.range.start.to_offset(buffer_snap);
14601 let end = symbol.range.end.to_offset(buffer_snap);
14602 let new_range = start..end;
14603 if start < selection.start || end > selection.end {
14604 return Some(Selection {
14605 id: selection.id,
14606 start: new_range.start,
14607 end: new_range.end,
14608 goal: SelectionGoal::None,
14609 reversed: selection.reversed,
14610 });
14611 }
14612 }
14613 None
14614 }
14615
14616 let mut selected_larger_symbol = false;
14617 let new_selections = old_selections
14618 .iter()
14619 .map(|selection| match update_selection(selection, &buffer) {
14620 Some(new_selection) => {
14621 if new_selection.range() != selection.range() {
14622 selected_larger_symbol = true;
14623 }
14624 new_selection
14625 }
14626 None => selection.clone(),
14627 })
14628 .collect::<Vec<_>>();
14629
14630 if selected_larger_symbol {
14631 self.change_selections(Default::default(), window, cx, |s| {
14632 s.select(new_selections);
14633 });
14634 }
14635 }
14636
14637 pub fn select_larger_syntax_node(
14638 &mut self,
14639 _: &SelectLargerSyntaxNode,
14640 window: &mut Window,
14641 cx: &mut Context<Self>,
14642 ) {
14643 let Some(visible_row_count) = self.visible_row_count() else {
14644 return;
14645 };
14646 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14647 if old_selections.is_empty() {
14648 return;
14649 }
14650
14651 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14652
14653 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14654 let buffer = self.buffer.read(cx).snapshot(cx);
14655
14656 let mut selected_larger_node = false;
14657 let mut new_selections = old_selections
14658 .iter()
14659 .map(|selection| {
14660 let old_range = selection.start..selection.end;
14661
14662 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14663 // manually select word at selection
14664 if ["string_content", "inline"].contains(&node.kind()) {
14665 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14666 // ignore if word is already selected
14667 if !word_range.is_empty() && old_range != word_range {
14668 let (last_word_range, _) =
14669 buffer.surrounding_word(old_range.end, false);
14670 // only select word if start and end point belongs to same word
14671 if word_range == last_word_range {
14672 selected_larger_node = true;
14673 return Selection {
14674 id: selection.id,
14675 start: word_range.start,
14676 end: word_range.end,
14677 goal: SelectionGoal::None,
14678 reversed: selection.reversed,
14679 };
14680 }
14681 }
14682 }
14683 }
14684
14685 let mut new_range = old_range.clone();
14686 while let Some((_node, containing_range)) =
14687 buffer.syntax_ancestor(new_range.clone())
14688 {
14689 new_range = match containing_range {
14690 MultiOrSingleBufferOffsetRange::Single(_) => break,
14691 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14692 };
14693 if !display_map.intersects_fold(new_range.start)
14694 && !display_map.intersects_fold(new_range.end)
14695 {
14696 break;
14697 }
14698 }
14699
14700 selected_larger_node |= new_range != old_range;
14701 Selection {
14702 id: selection.id,
14703 start: new_range.start,
14704 end: new_range.end,
14705 goal: SelectionGoal::None,
14706 reversed: selection.reversed,
14707 }
14708 })
14709 .collect::<Vec<_>>();
14710
14711 if !selected_larger_node {
14712 return; // don't put this call in the history
14713 }
14714
14715 // scroll based on transformation done to the last selection created by the user
14716 let (last_old, last_new) = old_selections
14717 .last()
14718 .zip(new_selections.last().cloned())
14719 .expect("old_selections isn't empty");
14720
14721 // revert selection
14722 let is_selection_reversed = {
14723 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14724 new_selections.last_mut().expect("checked above").reversed =
14725 should_newest_selection_be_reversed;
14726 should_newest_selection_be_reversed
14727 };
14728
14729 if selected_larger_node {
14730 self.select_syntax_node_history.disable_clearing = true;
14731 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14732 s.select(new_selections.clone());
14733 });
14734 self.select_syntax_node_history.disable_clearing = false;
14735 }
14736
14737 let start_row = last_new.start.to_display_point(&display_map).row().0;
14738 let end_row = last_new.end.to_display_point(&display_map).row().0;
14739 let selection_height = end_row - start_row + 1;
14740 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14741
14742 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14743 let scroll_behavior = if fits_on_the_screen {
14744 self.request_autoscroll(Autoscroll::fit(), cx);
14745 SelectSyntaxNodeScrollBehavior::FitSelection
14746 } else if is_selection_reversed {
14747 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14748 SelectSyntaxNodeScrollBehavior::CursorTop
14749 } else {
14750 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14751 SelectSyntaxNodeScrollBehavior::CursorBottom
14752 };
14753
14754 self.select_syntax_node_history.push((
14755 old_selections,
14756 scroll_behavior,
14757 is_selection_reversed,
14758 ));
14759 }
14760
14761 pub fn select_smaller_syntax_node(
14762 &mut self,
14763 _: &SelectSmallerSyntaxNode,
14764 window: &mut Window,
14765 cx: &mut Context<Self>,
14766 ) {
14767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14768
14769 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14770 self.select_syntax_node_history.pop()
14771 {
14772 if let Some(selection) = selections.last_mut() {
14773 selection.reversed = is_selection_reversed;
14774 }
14775
14776 self.select_syntax_node_history.disable_clearing = true;
14777 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14778 s.select(selections.to_vec());
14779 });
14780 self.select_syntax_node_history.disable_clearing = false;
14781
14782 match scroll_behavior {
14783 SelectSyntaxNodeScrollBehavior::CursorTop => {
14784 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14785 }
14786 SelectSyntaxNodeScrollBehavior::FitSelection => {
14787 self.request_autoscroll(Autoscroll::fit(), cx);
14788 }
14789 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14790 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14791 }
14792 }
14793 }
14794 }
14795
14796 pub fn unwrap_syntax_node(
14797 &mut self,
14798 _: &UnwrapSyntaxNode,
14799 window: &mut Window,
14800 cx: &mut Context<Self>,
14801 ) {
14802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14803
14804 let buffer = self.buffer.read(cx).snapshot(cx);
14805 let selections = self
14806 .selections
14807 .all::<usize>(cx)
14808 .into_iter()
14809 // subtracting the offset requires sorting
14810 .sorted_by_key(|i| i.start);
14811
14812 let full_edits = selections
14813 .into_iter()
14814 .filter_map(|selection| {
14815 // Only requires two branches once if-let-chains stabilize (#53667)
14816 let child = if !selection.is_empty() {
14817 selection.range()
14818 } else if let Some((_, ancestor_range)) =
14819 buffer.syntax_ancestor(selection.start..selection.end)
14820 {
14821 match ancestor_range {
14822 MultiOrSingleBufferOffsetRange::Single(range) => range,
14823 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14824 }
14825 } else {
14826 selection.range()
14827 };
14828
14829 let mut parent = child.clone();
14830 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14831 parent = match ancestor_range {
14832 MultiOrSingleBufferOffsetRange::Single(range) => range,
14833 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14834 };
14835 if parent.start < child.start || parent.end > child.end {
14836 break;
14837 }
14838 }
14839
14840 if parent == child {
14841 return None;
14842 }
14843 let text = buffer.text_for_range(child.clone()).collect::<String>();
14844 Some((selection.id, parent, text))
14845 })
14846 .collect::<Vec<_>>();
14847
14848 self.transact(window, cx, |this, window, cx| {
14849 this.buffer.update(cx, |buffer, cx| {
14850 buffer.edit(
14851 full_edits
14852 .iter()
14853 .map(|(_, p, t)| (p.clone(), t.clone()))
14854 .collect::<Vec<_>>(),
14855 None,
14856 cx,
14857 );
14858 });
14859 this.change_selections(Default::default(), window, cx, |s| {
14860 let mut offset = 0;
14861 let mut selections = vec![];
14862 for (id, parent, text) in full_edits {
14863 let start = parent.start - offset;
14864 offset += parent.len() - text.len();
14865 selections.push(Selection {
14866 id,
14867 start,
14868 end: start + text.len(),
14869 reversed: false,
14870 goal: Default::default(),
14871 });
14872 }
14873 s.select(selections);
14874 });
14875 });
14876 }
14877
14878 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14879 if !EditorSettings::get_global(cx).gutter.runnables {
14880 self.clear_tasks();
14881 return Task::ready(());
14882 }
14883 let project = self.project().map(Entity::downgrade);
14884 let task_sources = self.lsp_task_sources(cx);
14885 let multi_buffer = self.buffer.downgrade();
14886 cx.spawn_in(window, async move |editor, cx| {
14887 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14888 let Some(project) = project.and_then(|p| p.upgrade()) else {
14889 return;
14890 };
14891 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14892 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14893 }) else {
14894 return;
14895 };
14896
14897 let hide_runnables = project
14898 .update(cx, |project, cx| {
14899 // Do not display any test indicators in non-dev server remote projects.
14900 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14901 })
14902 .unwrap_or(true);
14903 if hide_runnables {
14904 return;
14905 }
14906 let new_rows =
14907 cx.background_spawn({
14908 let snapshot = display_snapshot.clone();
14909 async move {
14910 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14911 }
14912 })
14913 .await;
14914 let Ok(lsp_tasks) =
14915 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14916 else {
14917 return;
14918 };
14919 let lsp_tasks = lsp_tasks.await;
14920
14921 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14922 lsp_tasks
14923 .into_iter()
14924 .flat_map(|(kind, tasks)| {
14925 tasks.into_iter().filter_map(move |(location, task)| {
14926 Some((kind.clone(), location?, task))
14927 })
14928 })
14929 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14930 let buffer = location.target.buffer;
14931 let buffer_snapshot = buffer.read(cx).snapshot();
14932 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14933 |(excerpt_id, snapshot, _)| {
14934 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14935 display_snapshot
14936 .buffer_snapshot
14937 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14938 } else {
14939 None
14940 }
14941 },
14942 );
14943 if let Some(offset) = offset {
14944 let task_buffer_range =
14945 location.target.range.to_point(&buffer_snapshot);
14946 let context_buffer_range =
14947 task_buffer_range.to_offset(&buffer_snapshot);
14948 let context_range = BufferOffset(context_buffer_range.start)
14949 ..BufferOffset(context_buffer_range.end);
14950
14951 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14952 .or_insert_with(|| RunnableTasks {
14953 templates: Vec::new(),
14954 offset,
14955 column: task_buffer_range.start.column,
14956 extra_variables: HashMap::default(),
14957 context_range,
14958 })
14959 .templates
14960 .push((kind, task.original_task().clone()));
14961 }
14962
14963 acc
14964 })
14965 }) else {
14966 return;
14967 };
14968
14969 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14970 buffer.language_settings(cx).tasks.prefer_lsp
14971 }) else {
14972 return;
14973 };
14974
14975 let rows = Self::runnable_rows(
14976 project,
14977 display_snapshot,
14978 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14979 new_rows,
14980 cx.clone(),
14981 )
14982 .await;
14983 editor
14984 .update(cx, |editor, _| {
14985 editor.clear_tasks();
14986 for (key, mut value) in rows {
14987 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14988 value.templates.extend(lsp_tasks.templates);
14989 }
14990
14991 editor.insert_tasks(key, value);
14992 }
14993 for (key, value) in lsp_tasks_by_rows {
14994 editor.insert_tasks(key, value);
14995 }
14996 })
14997 .ok();
14998 })
14999 }
15000 fn fetch_runnable_ranges(
15001 snapshot: &DisplaySnapshot,
15002 range: Range<Anchor>,
15003 ) -> Vec<language::RunnableRange> {
15004 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15005 }
15006
15007 fn runnable_rows(
15008 project: Entity<Project>,
15009 snapshot: DisplaySnapshot,
15010 prefer_lsp: bool,
15011 runnable_ranges: Vec<RunnableRange>,
15012 cx: AsyncWindowContext,
15013 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15014 cx.spawn(async move |cx| {
15015 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15016 for mut runnable in runnable_ranges {
15017 let Some(tasks) = cx
15018 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15019 .ok()
15020 else {
15021 continue;
15022 };
15023 let mut tasks = tasks.await;
15024
15025 if prefer_lsp {
15026 tasks.retain(|(task_kind, _)| {
15027 !matches!(task_kind, TaskSourceKind::Language { .. })
15028 });
15029 }
15030 if tasks.is_empty() {
15031 continue;
15032 }
15033
15034 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15035 let Some(row) = snapshot
15036 .buffer_snapshot
15037 .buffer_line_for_row(MultiBufferRow(point.row))
15038 .map(|(_, range)| range.start.row)
15039 else {
15040 continue;
15041 };
15042
15043 let context_range =
15044 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15045 runnable_rows.push((
15046 (runnable.buffer_id, row),
15047 RunnableTasks {
15048 templates: tasks,
15049 offset: snapshot
15050 .buffer_snapshot
15051 .anchor_before(runnable.run_range.start),
15052 context_range,
15053 column: point.column,
15054 extra_variables: runnable.extra_captures,
15055 },
15056 ));
15057 }
15058 runnable_rows
15059 })
15060 }
15061
15062 fn templates_with_tags(
15063 project: &Entity<Project>,
15064 runnable: &mut Runnable,
15065 cx: &mut App,
15066 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15067 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15068 let (worktree_id, file) = project
15069 .buffer_for_id(runnable.buffer, cx)
15070 .and_then(|buffer| buffer.read(cx).file())
15071 .map(|file| (file.worktree_id(cx), file.clone()))
15072 .unzip();
15073
15074 (
15075 project.task_store().read(cx).task_inventory().cloned(),
15076 worktree_id,
15077 file,
15078 )
15079 });
15080
15081 let tags = mem::take(&mut runnable.tags);
15082 let language = runnable.language.clone();
15083 cx.spawn(async move |cx| {
15084 let mut templates_with_tags = Vec::new();
15085 if let Some(inventory) = inventory {
15086 for RunnableTag(tag) in tags {
15087 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15088 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15089 }) else {
15090 return templates_with_tags;
15091 };
15092 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15093 move |(_, template)| {
15094 template.tags.iter().any(|source_tag| source_tag == &tag)
15095 },
15096 ));
15097 }
15098 }
15099 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15100
15101 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15102 // Strongest source wins; if we have worktree tag binding, prefer that to
15103 // global and language bindings;
15104 // if we have a global binding, prefer that to language binding.
15105 let first_mismatch = templates_with_tags
15106 .iter()
15107 .position(|(tag_source, _)| tag_source != leading_tag_source);
15108 if let Some(index) = first_mismatch {
15109 templates_with_tags.truncate(index);
15110 }
15111 }
15112
15113 templates_with_tags
15114 })
15115 }
15116
15117 pub fn move_to_enclosing_bracket(
15118 &mut self,
15119 _: &MoveToEnclosingBracket,
15120 window: &mut Window,
15121 cx: &mut Context<Self>,
15122 ) {
15123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15124 self.change_selections(Default::default(), window, cx, |s| {
15125 s.move_offsets_with(|snapshot, selection| {
15126 let Some(enclosing_bracket_ranges) =
15127 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15128 else {
15129 return;
15130 };
15131
15132 let mut best_length = usize::MAX;
15133 let mut best_inside = false;
15134 let mut best_in_bracket_range = false;
15135 let mut best_destination = None;
15136 for (open, close) in enclosing_bracket_ranges {
15137 let close = close.to_inclusive();
15138 let length = close.end() - open.start;
15139 let inside = selection.start >= open.end && selection.end <= *close.start();
15140 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15141 || close.contains(&selection.head());
15142
15143 // If best is next to a bracket and current isn't, skip
15144 if !in_bracket_range && best_in_bracket_range {
15145 continue;
15146 }
15147
15148 // Prefer smaller lengths unless best is inside and current isn't
15149 if length > best_length && (best_inside || !inside) {
15150 continue;
15151 }
15152
15153 best_length = length;
15154 best_inside = inside;
15155 best_in_bracket_range = in_bracket_range;
15156 best_destination = Some(
15157 if close.contains(&selection.start) && close.contains(&selection.end) {
15158 if inside { open.end } else { open.start }
15159 } else if inside {
15160 *close.start()
15161 } else {
15162 *close.end()
15163 },
15164 );
15165 }
15166
15167 if let Some(destination) = best_destination {
15168 selection.collapse_to(destination, SelectionGoal::None);
15169 }
15170 })
15171 });
15172 }
15173
15174 pub fn undo_selection(
15175 &mut self,
15176 _: &UndoSelection,
15177 window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) {
15180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15181 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15182 self.selection_history.mode = SelectionHistoryMode::Undoing;
15183 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15184 this.end_selection(window, cx);
15185 this.change_selections(
15186 SelectionEffects::scroll(Autoscroll::newest()),
15187 window,
15188 cx,
15189 |s| s.select_anchors(entry.selections.to_vec()),
15190 );
15191 });
15192 self.selection_history.mode = SelectionHistoryMode::Normal;
15193
15194 self.select_next_state = entry.select_next_state;
15195 self.select_prev_state = entry.select_prev_state;
15196 self.add_selections_state = entry.add_selections_state;
15197 }
15198 }
15199
15200 pub fn redo_selection(
15201 &mut self,
15202 _: &RedoSelection,
15203 window: &mut Window,
15204 cx: &mut Context<Self>,
15205 ) {
15206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15207 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15208 self.selection_history.mode = SelectionHistoryMode::Redoing;
15209 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15210 this.end_selection(window, cx);
15211 this.change_selections(
15212 SelectionEffects::scroll(Autoscroll::newest()),
15213 window,
15214 cx,
15215 |s| s.select_anchors(entry.selections.to_vec()),
15216 );
15217 });
15218 self.selection_history.mode = SelectionHistoryMode::Normal;
15219
15220 self.select_next_state = entry.select_next_state;
15221 self.select_prev_state = entry.select_prev_state;
15222 self.add_selections_state = entry.add_selections_state;
15223 }
15224 }
15225
15226 pub fn expand_excerpts(
15227 &mut self,
15228 action: &ExpandExcerpts,
15229 _: &mut Window,
15230 cx: &mut Context<Self>,
15231 ) {
15232 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15233 }
15234
15235 pub fn expand_excerpts_down(
15236 &mut self,
15237 action: &ExpandExcerptsDown,
15238 _: &mut Window,
15239 cx: &mut Context<Self>,
15240 ) {
15241 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15242 }
15243
15244 pub fn expand_excerpts_up(
15245 &mut self,
15246 action: &ExpandExcerptsUp,
15247 _: &mut Window,
15248 cx: &mut Context<Self>,
15249 ) {
15250 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15251 }
15252
15253 pub fn expand_excerpts_for_direction(
15254 &mut self,
15255 lines: u32,
15256 direction: ExpandExcerptDirection,
15257
15258 cx: &mut Context<Self>,
15259 ) {
15260 let selections = self.selections.disjoint_anchors();
15261
15262 let lines = if lines == 0 {
15263 EditorSettings::get_global(cx).expand_excerpt_lines
15264 } else {
15265 lines
15266 };
15267
15268 self.buffer.update(cx, |buffer, cx| {
15269 let snapshot = buffer.snapshot(cx);
15270 let mut excerpt_ids = selections
15271 .iter()
15272 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15273 .collect::<Vec<_>>();
15274 excerpt_ids.sort();
15275 excerpt_ids.dedup();
15276 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15277 })
15278 }
15279
15280 pub fn expand_excerpt(
15281 &mut self,
15282 excerpt: ExcerptId,
15283 direction: ExpandExcerptDirection,
15284 window: &mut Window,
15285 cx: &mut Context<Self>,
15286 ) {
15287 let current_scroll_position = self.scroll_position(cx);
15288 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15289 let mut should_scroll_up = false;
15290
15291 if direction == ExpandExcerptDirection::Down {
15292 let multi_buffer = self.buffer.read(cx);
15293 let snapshot = multi_buffer.snapshot(cx);
15294 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15295 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15296 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15297 {
15298 let buffer_snapshot = buffer.read(cx).snapshot();
15299 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15300 let last_row = buffer_snapshot.max_point().row;
15301 let lines_below = last_row.saturating_sub(excerpt_end_row);
15302 should_scroll_up = lines_below >= lines_to_expand;
15303 }
15304 }
15305
15306 self.buffer.update(cx, |buffer, cx| {
15307 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15308 });
15309
15310 if should_scroll_up {
15311 let new_scroll_position =
15312 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15313 self.set_scroll_position(new_scroll_position, window, cx);
15314 }
15315 }
15316
15317 pub fn go_to_singleton_buffer_point(
15318 &mut self,
15319 point: Point,
15320 window: &mut Window,
15321 cx: &mut Context<Self>,
15322 ) {
15323 self.go_to_singleton_buffer_range(point..point, window, cx);
15324 }
15325
15326 pub fn go_to_singleton_buffer_range(
15327 &mut self,
15328 range: Range<Point>,
15329 window: &mut Window,
15330 cx: &mut Context<Self>,
15331 ) {
15332 let multibuffer = self.buffer().read(cx);
15333 let Some(buffer) = multibuffer.as_singleton() else {
15334 return;
15335 };
15336 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15337 return;
15338 };
15339 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15340 return;
15341 };
15342 self.change_selections(
15343 SelectionEffects::default().nav_history(true),
15344 window,
15345 cx,
15346 |s| s.select_anchor_ranges([start..end]),
15347 );
15348 }
15349
15350 pub fn go_to_diagnostic(
15351 &mut self,
15352 action: &GoToDiagnostic,
15353 window: &mut Window,
15354 cx: &mut Context<Self>,
15355 ) {
15356 if !self.diagnostics_enabled() {
15357 return;
15358 }
15359 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15360 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15361 }
15362
15363 pub fn go_to_prev_diagnostic(
15364 &mut self,
15365 action: &GoToPreviousDiagnostic,
15366 window: &mut Window,
15367 cx: &mut Context<Self>,
15368 ) {
15369 if !self.diagnostics_enabled() {
15370 return;
15371 }
15372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15373 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15374 }
15375
15376 pub fn go_to_diagnostic_impl(
15377 &mut self,
15378 direction: Direction,
15379 severity: GoToDiagnosticSeverityFilter,
15380 window: &mut Window,
15381 cx: &mut Context<Self>,
15382 ) {
15383 let buffer = self.buffer.read(cx).snapshot(cx);
15384 let selection = self.selections.newest::<usize>(cx);
15385
15386 let mut active_group_id = None;
15387 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15388 && active_group.active_range.start.to_offset(&buffer) == selection.start
15389 {
15390 active_group_id = Some(active_group.group_id);
15391 }
15392
15393 fn filtered(
15394 snapshot: EditorSnapshot,
15395 severity: GoToDiagnosticSeverityFilter,
15396 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15397 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15398 diagnostics
15399 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15400 .filter(|entry| entry.range.start != entry.range.end)
15401 .filter(|entry| !entry.diagnostic.is_unnecessary)
15402 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15403 }
15404
15405 let snapshot = self.snapshot(window, cx);
15406 let before = filtered(
15407 snapshot.clone(),
15408 severity,
15409 buffer
15410 .diagnostics_in_range(0..selection.start)
15411 .filter(|entry| entry.range.start <= selection.start),
15412 );
15413 let after = filtered(
15414 snapshot,
15415 severity,
15416 buffer
15417 .diagnostics_in_range(selection.start..buffer.len())
15418 .filter(|entry| entry.range.start >= selection.start),
15419 );
15420
15421 let mut found: Option<DiagnosticEntry<usize>> = None;
15422 if direction == Direction::Prev {
15423 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15424 {
15425 for diagnostic in prev_diagnostics.into_iter().rev() {
15426 if diagnostic.range.start != selection.start
15427 || active_group_id
15428 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15429 {
15430 found = Some(diagnostic);
15431 break 'outer;
15432 }
15433 }
15434 }
15435 } else {
15436 for diagnostic in after.chain(before) {
15437 if diagnostic.range.start != selection.start
15438 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15439 {
15440 found = Some(diagnostic);
15441 break;
15442 }
15443 }
15444 }
15445 let Some(next_diagnostic) = found else {
15446 return;
15447 };
15448
15449 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15450 return;
15451 };
15452 self.change_selections(Default::default(), window, cx, |s| {
15453 s.select_ranges(vec![
15454 next_diagnostic.range.start..next_diagnostic.range.start,
15455 ])
15456 });
15457 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15458 self.refresh_edit_prediction(false, true, window, cx);
15459 }
15460
15461 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15462 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15463 let snapshot = self.snapshot(window, cx);
15464 let selection = self.selections.newest::<Point>(cx);
15465 self.go_to_hunk_before_or_after_position(
15466 &snapshot,
15467 selection.head(),
15468 Direction::Next,
15469 window,
15470 cx,
15471 );
15472 }
15473
15474 pub fn go_to_hunk_before_or_after_position(
15475 &mut self,
15476 snapshot: &EditorSnapshot,
15477 position: Point,
15478 direction: Direction,
15479 window: &mut Window,
15480 cx: &mut Context<Editor>,
15481 ) {
15482 let row = if direction == Direction::Next {
15483 self.hunk_after_position(snapshot, position)
15484 .map(|hunk| hunk.row_range.start)
15485 } else {
15486 self.hunk_before_position(snapshot, position)
15487 };
15488
15489 if let Some(row) = row {
15490 let destination = Point::new(row.0, 0);
15491 let autoscroll = Autoscroll::center();
15492
15493 self.unfold_ranges(&[destination..destination], false, false, cx);
15494 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15495 s.select_ranges([destination..destination]);
15496 });
15497 }
15498 }
15499
15500 fn hunk_after_position(
15501 &mut self,
15502 snapshot: &EditorSnapshot,
15503 position: Point,
15504 ) -> Option<MultiBufferDiffHunk> {
15505 snapshot
15506 .buffer_snapshot
15507 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15508 .find(|hunk| hunk.row_range.start.0 > position.row)
15509 .or_else(|| {
15510 snapshot
15511 .buffer_snapshot
15512 .diff_hunks_in_range(Point::zero()..position)
15513 .find(|hunk| hunk.row_range.end.0 < position.row)
15514 })
15515 }
15516
15517 fn go_to_prev_hunk(
15518 &mut self,
15519 _: &GoToPreviousHunk,
15520 window: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) {
15523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15524 let snapshot = self.snapshot(window, cx);
15525 let selection = self.selections.newest::<Point>(cx);
15526 self.go_to_hunk_before_or_after_position(
15527 &snapshot,
15528 selection.head(),
15529 Direction::Prev,
15530 window,
15531 cx,
15532 );
15533 }
15534
15535 fn hunk_before_position(
15536 &mut self,
15537 snapshot: &EditorSnapshot,
15538 position: Point,
15539 ) -> Option<MultiBufferRow> {
15540 snapshot
15541 .buffer_snapshot
15542 .diff_hunk_before(position)
15543 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15544 }
15545
15546 fn go_to_next_change(
15547 &mut self,
15548 _: &GoToNextChange,
15549 window: &mut Window,
15550 cx: &mut Context<Self>,
15551 ) {
15552 if let Some(selections) = self
15553 .change_list
15554 .next_change(1, Direction::Next)
15555 .map(|s| s.to_vec())
15556 {
15557 self.change_selections(Default::default(), window, cx, |s| {
15558 let map = s.display_map();
15559 s.select_display_ranges(selections.iter().map(|a| {
15560 let point = a.to_display_point(&map);
15561 point..point
15562 }))
15563 })
15564 }
15565 }
15566
15567 fn go_to_previous_change(
15568 &mut self,
15569 _: &GoToPreviousChange,
15570 window: &mut Window,
15571 cx: &mut Context<Self>,
15572 ) {
15573 if let Some(selections) = self
15574 .change_list
15575 .next_change(1, Direction::Prev)
15576 .map(|s| s.to_vec())
15577 {
15578 self.change_selections(Default::default(), window, cx, |s| {
15579 let map = s.display_map();
15580 s.select_display_ranges(selections.iter().map(|a| {
15581 let point = a.to_display_point(&map);
15582 point..point
15583 }))
15584 })
15585 }
15586 }
15587
15588 fn go_to_line<T: 'static>(
15589 &mut self,
15590 position: Anchor,
15591 highlight_color: Option<Hsla>,
15592 window: &mut Window,
15593 cx: &mut Context<Self>,
15594 ) {
15595 let snapshot = self.snapshot(window, cx).display_snapshot;
15596 let position = position.to_point(&snapshot.buffer_snapshot);
15597 let start = snapshot
15598 .buffer_snapshot
15599 .clip_point(Point::new(position.row, 0), Bias::Left);
15600 let end = start + Point::new(1, 0);
15601 let start = snapshot.buffer_snapshot.anchor_before(start);
15602 let end = snapshot.buffer_snapshot.anchor_before(end);
15603
15604 self.highlight_rows::<T>(
15605 start..end,
15606 highlight_color
15607 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15608 Default::default(),
15609 cx,
15610 );
15611
15612 if self.buffer.read(cx).is_singleton() {
15613 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15614 }
15615 }
15616
15617 pub fn go_to_definition(
15618 &mut self,
15619 _: &GoToDefinition,
15620 window: &mut Window,
15621 cx: &mut Context<Self>,
15622 ) -> Task<Result<Navigated>> {
15623 let definition =
15624 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15625 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15626 cx.spawn_in(window, async move |editor, cx| {
15627 if definition.await? == Navigated::Yes {
15628 return Ok(Navigated::Yes);
15629 }
15630 match fallback_strategy {
15631 GoToDefinitionFallback::None => Ok(Navigated::No),
15632 GoToDefinitionFallback::FindAllReferences => {
15633 match editor.update_in(cx, |editor, window, cx| {
15634 editor.find_all_references(&FindAllReferences, window, cx)
15635 })? {
15636 Some(references) => references.await,
15637 None => Ok(Navigated::No),
15638 }
15639 }
15640 }
15641 })
15642 }
15643
15644 pub fn go_to_declaration(
15645 &mut self,
15646 _: &GoToDeclaration,
15647 window: &mut Window,
15648 cx: &mut Context<Self>,
15649 ) -> Task<Result<Navigated>> {
15650 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15651 }
15652
15653 pub fn go_to_declaration_split(
15654 &mut self,
15655 _: &GoToDeclaration,
15656 window: &mut Window,
15657 cx: &mut Context<Self>,
15658 ) -> Task<Result<Navigated>> {
15659 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15660 }
15661
15662 pub fn go_to_implementation(
15663 &mut self,
15664 _: &GoToImplementation,
15665 window: &mut Window,
15666 cx: &mut Context<Self>,
15667 ) -> Task<Result<Navigated>> {
15668 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15669 }
15670
15671 pub fn go_to_implementation_split(
15672 &mut self,
15673 _: &GoToImplementationSplit,
15674 window: &mut Window,
15675 cx: &mut Context<Self>,
15676 ) -> Task<Result<Navigated>> {
15677 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15678 }
15679
15680 pub fn go_to_type_definition(
15681 &mut self,
15682 _: &GoToTypeDefinition,
15683 window: &mut Window,
15684 cx: &mut Context<Self>,
15685 ) -> Task<Result<Navigated>> {
15686 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15687 }
15688
15689 pub fn go_to_definition_split(
15690 &mut self,
15691 _: &GoToDefinitionSplit,
15692 window: &mut Window,
15693 cx: &mut Context<Self>,
15694 ) -> Task<Result<Navigated>> {
15695 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15696 }
15697
15698 pub fn go_to_type_definition_split(
15699 &mut self,
15700 _: &GoToTypeDefinitionSplit,
15701 window: &mut Window,
15702 cx: &mut Context<Self>,
15703 ) -> Task<Result<Navigated>> {
15704 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15705 }
15706
15707 fn go_to_definition_of_kind(
15708 &mut self,
15709 kind: GotoDefinitionKind,
15710 split: bool,
15711 window: &mut Window,
15712 cx: &mut Context<Self>,
15713 ) -> Task<Result<Navigated>> {
15714 let Some(provider) = self.semantics_provider.clone() else {
15715 return Task::ready(Ok(Navigated::No));
15716 };
15717 let head = self.selections.newest::<usize>(cx).head();
15718 let buffer = self.buffer.read(cx);
15719 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15720 return Task::ready(Ok(Navigated::No));
15721 };
15722 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15723 return Task::ready(Ok(Navigated::No));
15724 };
15725
15726 cx.spawn_in(window, async move |editor, cx| {
15727 let definitions = definitions.await?;
15728 let navigated = editor
15729 .update_in(cx, |editor, window, cx| {
15730 editor.navigate_to_hover_links(
15731 Some(kind),
15732 definitions
15733 .into_iter()
15734 .filter(|location| {
15735 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15736 })
15737 .map(HoverLink::Text)
15738 .collect::<Vec<_>>(),
15739 split,
15740 window,
15741 cx,
15742 )
15743 })?
15744 .await?;
15745 anyhow::Ok(navigated)
15746 })
15747 }
15748
15749 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15750 let selection = self.selections.newest_anchor();
15751 let head = selection.head();
15752 let tail = selection.tail();
15753
15754 let Some((buffer, start_position)) =
15755 self.buffer.read(cx).text_anchor_for_position(head, cx)
15756 else {
15757 return;
15758 };
15759
15760 let end_position = if head != tail {
15761 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15762 return;
15763 };
15764 Some(pos)
15765 } else {
15766 None
15767 };
15768
15769 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15770 let url = if let Some(end_pos) = end_position {
15771 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15772 } else {
15773 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15774 };
15775
15776 if let Some(url) = url {
15777 editor.update(cx, |_, cx| {
15778 cx.open_url(&url);
15779 })
15780 } else {
15781 Ok(())
15782 }
15783 });
15784
15785 url_finder.detach();
15786 }
15787
15788 pub fn open_selected_filename(
15789 &mut self,
15790 _: &OpenSelectedFilename,
15791 window: &mut Window,
15792 cx: &mut Context<Self>,
15793 ) {
15794 let Some(workspace) = self.workspace() else {
15795 return;
15796 };
15797
15798 let position = self.selections.newest_anchor().head();
15799
15800 let Some((buffer, buffer_position)) =
15801 self.buffer.read(cx).text_anchor_for_position(position, cx)
15802 else {
15803 return;
15804 };
15805
15806 let project = self.project.clone();
15807
15808 cx.spawn_in(window, async move |_, cx| {
15809 let result = find_file(&buffer, project, buffer_position, cx).await;
15810
15811 if let Some((_, path)) = result {
15812 workspace
15813 .update_in(cx, |workspace, window, cx| {
15814 workspace.open_resolved_path(path, window, cx)
15815 })?
15816 .await?;
15817 }
15818 anyhow::Ok(())
15819 })
15820 .detach();
15821 }
15822
15823 pub(crate) fn navigate_to_hover_links(
15824 &mut self,
15825 kind: Option<GotoDefinitionKind>,
15826 definitions: Vec<HoverLink>,
15827 split: bool,
15828 window: &mut Window,
15829 cx: &mut Context<Editor>,
15830 ) -> Task<Result<Navigated>> {
15831 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15832 let mut first_url_or_file = None;
15833 let definitions: Vec<_> = definitions
15834 .into_iter()
15835 .filter_map(|def| match def {
15836 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15837 HoverLink::InlayHint(lsp_location, server_id) => {
15838 let computation =
15839 self.compute_target_location(lsp_location, server_id, window, cx);
15840 Some(cx.background_spawn(computation))
15841 }
15842 HoverLink::Url(url) => {
15843 first_url_or_file = Some(Either::Left(url));
15844 None
15845 }
15846 HoverLink::File(path) => {
15847 first_url_or_file = Some(Either::Right(path));
15848 None
15849 }
15850 })
15851 .collect();
15852
15853 let workspace = self.workspace();
15854
15855 cx.spawn_in(window, async move |editor, acx| {
15856 let mut locations: Vec<Location> = future::join_all(definitions)
15857 .await
15858 .into_iter()
15859 .filter_map(|location| location.transpose())
15860 .collect::<Result<_>>()
15861 .context("location tasks")?;
15862
15863 if locations.len() > 1 {
15864 let Some(workspace) = workspace else {
15865 return Ok(Navigated::No);
15866 };
15867
15868 let tab_kind = match kind {
15869 Some(GotoDefinitionKind::Implementation) => "Implementations",
15870 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15871 Some(GotoDefinitionKind::Declaration) => "Declarations",
15872 Some(GotoDefinitionKind::Type) => "Types",
15873 };
15874 let title = editor
15875 .update_in(acx, |_, _, cx| {
15876 let target = locations
15877 .iter()
15878 .map(|location| {
15879 location
15880 .buffer
15881 .read(cx)
15882 .text_for_range(location.range.clone())
15883 .collect::<String>()
15884 })
15885 .filter(|text| !text.contains('\n'))
15886 .unique()
15887 .take(3)
15888 .join(", ");
15889 if target.is_empty() {
15890 tab_kind.to_owned()
15891 } else {
15892 format!("{tab_kind} for {target}")
15893 }
15894 })
15895 .context("buffer title")?;
15896
15897 let opened = workspace
15898 .update_in(acx, |workspace, window, cx| {
15899 Self::open_locations_in_multibuffer(
15900 workspace,
15901 locations,
15902 title,
15903 split,
15904 MultibufferSelectionMode::First,
15905 window,
15906 cx,
15907 )
15908 })
15909 .is_ok();
15910
15911 anyhow::Ok(Navigated::from_bool(opened))
15912 } else if locations.is_empty() {
15913 // If there is one definition, just open it directly
15914 match first_url_or_file {
15915 Some(Either::Left(url)) => {
15916 acx.update(|_, cx| cx.open_url(&url))?;
15917 Ok(Navigated::Yes)
15918 }
15919 Some(Either::Right(path)) => {
15920 let Some(workspace) = workspace else {
15921 return Ok(Navigated::No);
15922 };
15923
15924 workspace
15925 .update_in(acx, |workspace, window, cx| {
15926 workspace.open_resolved_path(path, window, cx)
15927 })?
15928 .await?;
15929 Ok(Navigated::Yes)
15930 }
15931 None => Ok(Navigated::No),
15932 }
15933 } else {
15934 let Some(workspace) = workspace else {
15935 return Ok(Navigated::No);
15936 };
15937
15938 let target = locations.pop().unwrap();
15939 editor.update_in(acx, |editor, window, cx| {
15940 let pane = workspace.read(cx).active_pane().clone();
15941
15942 let range = target.range.to_point(target.buffer.read(cx));
15943 let range = editor.range_for_match(&range);
15944 let range = collapse_multiline_range(range);
15945
15946 if !split
15947 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15948 {
15949 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15950 } else {
15951 window.defer(cx, move |window, cx| {
15952 let target_editor: Entity<Self> =
15953 workspace.update(cx, |workspace, cx| {
15954 let pane = if split {
15955 workspace.adjacent_pane(window, cx)
15956 } else {
15957 workspace.active_pane().clone()
15958 };
15959
15960 workspace.open_project_item(
15961 pane,
15962 target.buffer.clone(),
15963 true,
15964 true,
15965 window,
15966 cx,
15967 )
15968 });
15969 target_editor.update(cx, |target_editor, cx| {
15970 // When selecting a definition in a different buffer, disable the nav history
15971 // to avoid creating a history entry at the previous cursor location.
15972 pane.update(cx, |pane, _| pane.disable_history());
15973 target_editor.go_to_singleton_buffer_range(range, window, cx);
15974 pane.update(cx, |pane, _| pane.enable_history());
15975 });
15976 });
15977 }
15978 Navigated::Yes
15979 })
15980 }
15981 })
15982 }
15983
15984 fn compute_target_location(
15985 &self,
15986 lsp_location: lsp::Location,
15987 server_id: LanguageServerId,
15988 window: &mut Window,
15989 cx: &mut Context<Self>,
15990 ) -> Task<anyhow::Result<Option<Location>>> {
15991 let Some(project) = self.project.clone() else {
15992 return Task::ready(Ok(None));
15993 };
15994
15995 cx.spawn_in(window, async move |editor, cx| {
15996 let location_task = editor.update(cx, |_, cx| {
15997 project.update(cx, |project, cx| {
15998 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
15999 })
16000 })?;
16001 let location = Some({
16002 let target_buffer_handle = location_task.await.context("open local buffer")?;
16003 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16004 let target_start = target_buffer
16005 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16006 let target_end = target_buffer
16007 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16008 target_buffer.anchor_after(target_start)
16009 ..target_buffer.anchor_before(target_end)
16010 })?;
16011 Location {
16012 buffer: target_buffer_handle,
16013 range,
16014 }
16015 });
16016 Ok(location)
16017 })
16018 }
16019
16020 pub fn find_all_references(
16021 &mut self,
16022 _: &FindAllReferences,
16023 window: &mut Window,
16024 cx: &mut Context<Self>,
16025 ) -> Option<Task<Result<Navigated>>> {
16026 let selection = self.selections.newest::<usize>(cx);
16027 let multi_buffer = self.buffer.read(cx);
16028 let head = selection.head();
16029
16030 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16031 let head_anchor = multi_buffer_snapshot.anchor_at(
16032 head,
16033 if head < selection.tail() {
16034 Bias::Right
16035 } else {
16036 Bias::Left
16037 },
16038 );
16039
16040 match self
16041 .find_all_references_task_sources
16042 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16043 {
16044 Ok(_) => {
16045 log::info!(
16046 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16047 );
16048 return None;
16049 }
16050 Err(i) => {
16051 self.find_all_references_task_sources.insert(i, head_anchor);
16052 }
16053 }
16054
16055 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16056 let workspace = self.workspace()?;
16057 let project = workspace.read(cx).project().clone();
16058 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16059 Some(cx.spawn_in(window, async move |editor, cx| {
16060 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16061 if let Ok(i) = editor
16062 .find_all_references_task_sources
16063 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16064 {
16065 editor.find_all_references_task_sources.remove(i);
16066 }
16067 });
16068
16069 let locations = references.await?;
16070 if locations.is_empty() {
16071 return anyhow::Ok(Navigated::No);
16072 }
16073
16074 workspace.update_in(cx, |workspace, window, cx| {
16075 let target = locations
16076 .iter()
16077 .map(|location| {
16078 location
16079 .buffer
16080 .read(cx)
16081 .text_for_range(location.range.clone())
16082 .collect::<String>()
16083 })
16084 .filter(|text| !text.contains('\n'))
16085 .unique()
16086 .take(3)
16087 .join(", ");
16088 let title = if target.is_empty() {
16089 "References".to_owned()
16090 } else {
16091 format!("References to {target}")
16092 };
16093 Self::open_locations_in_multibuffer(
16094 workspace,
16095 locations,
16096 title,
16097 false,
16098 MultibufferSelectionMode::First,
16099 window,
16100 cx,
16101 );
16102 Navigated::Yes
16103 })
16104 }))
16105 }
16106
16107 /// Opens a multibuffer with the given project locations in it
16108 pub fn open_locations_in_multibuffer(
16109 workspace: &mut Workspace,
16110 mut locations: Vec<Location>,
16111 title: String,
16112 split: bool,
16113 multibuffer_selection_mode: MultibufferSelectionMode,
16114 window: &mut Window,
16115 cx: &mut Context<Workspace>,
16116 ) {
16117 if locations.is_empty() {
16118 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16119 return;
16120 }
16121
16122 // If there are multiple definitions, open them in a multibuffer
16123 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16124 let mut locations = locations.into_iter().peekable();
16125 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16126 let capability = workspace.project().read(cx).capability();
16127
16128 let excerpt_buffer = cx.new(|cx| {
16129 let mut multibuffer = MultiBuffer::new(capability);
16130 while let Some(location) = locations.next() {
16131 let buffer = location.buffer.read(cx);
16132 let mut ranges_for_buffer = Vec::new();
16133 let range = location.range.to_point(buffer);
16134 ranges_for_buffer.push(range.clone());
16135
16136 while let Some(next_location) = locations.peek() {
16137 if next_location.buffer == location.buffer {
16138 ranges_for_buffer.push(next_location.range.to_point(buffer));
16139 locations.next();
16140 } else {
16141 break;
16142 }
16143 }
16144
16145 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16146 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16147 PathKey::for_buffer(&location.buffer, cx),
16148 location.buffer.clone(),
16149 ranges_for_buffer,
16150 DEFAULT_MULTIBUFFER_CONTEXT,
16151 cx,
16152 );
16153 ranges.extend(new_ranges)
16154 }
16155
16156 multibuffer.with_title(title)
16157 });
16158
16159 let editor = cx.new(|cx| {
16160 Editor::for_multibuffer(
16161 excerpt_buffer,
16162 Some(workspace.project().clone()),
16163 window,
16164 cx,
16165 )
16166 });
16167 editor.update(cx, |editor, cx| {
16168 match multibuffer_selection_mode {
16169 MultibufferSelectionMode::First => {
16170 if let Some(first_range) = ranges.first() {
16171 editor.change_selections(
16172 SelectionEffects::no_scroll(),
16173 window,
16174 cx,
16175 |selections| {
16176 selections.clear_disjoint();
16177 selections
16178 .select_anchor_ranges(std::iter::once(first_range.clone()));
16179 },
16180 );
16181 }
16182 editor.highlight_background::<Self>(
16183 &ranges,
16184 |theme| theme.colors().editor_highlighted_line_background,
16185 cx,
16186 );
16187 }
16188 MultibufferSelectionMode::All => {
16189 editor.change_selections(
16190 SelectionEffects::no_scroll(),
16191 window,
16192 cx,
16193 |selections| {
16194 selections.clear_disjoint();
16195 selections.select_anchor_ranges(ranges);
16196 },
16197 );
16198 }
16199 }
16200 editor.register_buffers_with_language_servers(cx);
16201 });
16202
16203 let item = Box::new(editor);
16204 let item_id = item.item_id();
16205
16206 if split {
16207 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16208 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16209 let (preview_item_id, preview_item_idx) =
16210 workspace.active_pane().read_with(cx, |pane, _| {
16211 (pane.preview_item_id(), pane.preview_item_idx())
16212 });
16213
16214 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16215
16216 if let Some(preview_item_id) = preview_item_id {
16217 workspace.active_pane().update(cx, |pane, cx| {
16218 pane.remove_item(preview_item_id, false, false, window, cx);
16219 });
16220 }
16221 } else {
16222 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16223 }
16224 workspace.active_pane().update(cx, |pane, cx| {
16225 pane.set_preview_item_id(Some(item_id), cx);
16226 });
16227 }
16228
16229 pub fn rename(
16230 &mut self,
16231 _: &Rename,
16232 window: &mut Window,
16233 cx: &mut Context<Self>,
16234 ) -> Option<Task<Result<()>>> {
16235 use language::ToOffset as _;
16236
16237 let provider = self.semantics_provider.clone()?;
16238 let selection = self.selections.newest_anchor().clone();
16239 let (cursor_buffer, cursor_buffer_position) = self
16240 .buffer
16241 .read(cx)
16242 .text_anchor_for_position(selection.head(), cx)?;
16243 let (tail_buffer, cursor_buffer_position_end) = self
16244 .buffer
16245 .read(cx)
16246 .text_anchor_for_position(selection.tail(), cx)?;
16247 if tail_buffer != cursor_buffer {
16248 return None;
16249 }
16250
16251 let snapshot = cursor_buffer.read(cx).snapshot();
16252 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16253 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16254 let prepare_rename = provider
16255 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16256 .unwrap_or_else(|| Task::ready(Ok(None)));
16257 drop(snapshot);
16258
16259 Some(cx.spawn_in(window, async move |this, cx| {
16260 let rename_range = if let Some(range) = prepare_rename.await? {
16261 Some(range)
16262 } else {
16263 this.update(cx, |this, cx| {
16264 let buffer = this.buffer.read(cx).snapshot(cx);
16265 let mut buffer_highlights = this
16266 .document_highlights_for_position(selection.head(), &buffer)
16267 .filter(|highlight| {
16268 highlight.start.excerpt_id == selection.head().excerpt_id
16269 && highlight.end.excerpt_id == selection.head().excerpt_id
16270 });
16271 buffer_highlights
16272 .next()
16273 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16274 })?
16275 };
16276 if let Some(rename_range) = rename_range {
16277 this.update_in(cx, |this, window, cx| {
16278 let snapshot = cursor_buffer.read(cx).snapshot();
16279 let rename_buffer_range = rename_range.to_offset(&snapshot);
16280 let cursor_offset_in_rename_range =
16281 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16282 let cursor_offset_in_rename_range_end =
16283 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16284
16285 this.take_rename(false, window, cx);
16286 let buffer = this.buffer.read(cx).read(cx);
16287 let cursor_offset = selection.head().to_offset(&buffer);
16288 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16289 let rename_end = rename_start + rename_buffer_range.len();
16290 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16291 let mut old_highlight_id = None;
16292 let old_name: Arc<str> = buffer
16293 .chunks(rename_start..rename_end, true)
16294 .map(|chunk| {
16295 if old_highlight_id.is_none() {
16296 old_highlight_id = chunk.syntax_highlight_id;
16297 }
16298 chunk.text
16299 })
16300 .collect::<String>()
16301 .into();
16302
16303 drop(buffer);
16304
16305 // Position the selection in the rename editor so that it matches the current selection.
16306 this.show_local_selections = false;
16307 let rename_editor = cx.new(|cx| {
16308 let mut editor = Editor::single_line(window, cx);
16309 editor.buffer.update(cx, |buffer, cx| {
16310 buffer.edit([(0..0, old_name.clone())], None, cx)
16311 });
16312 let rename_selection_range = match cursor_offset_in_rename_range
16313 .cmp(&cursor_offset_in_rename_range_end)
16314 {
16315 Ordering::Equal => {
16316 editor.select_all(&SelectAll, window, cx);
16317 return editor;
16318 }
16319 Ordering::Less => {
16320 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16321 }
16322 Ordering::Greater => {
16323 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16324 }
16325 };
16326 if rename_selection_range.end > old_name.len() {
16327 editor.select_all(&SelectAll, window, cx);
16328 } else {
16329 editor.change_selections(Default::default(), window, cx, |s| {
16330 s.select_ranges([rename_selection_range]);
16331 });
16332 }
16333 editor
16334 });
16335 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16336 if e == &EditorEvent::Focused {
16337 cx.emit(EditorEvent::FocusedIn)
16338 }
16339 })
16340 .detach();
16341
16342 let write_highlights =
16343 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16344 let read_highlights =
16345 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16346 let ranges = write_highlights
16347 .iter()
16348 .flat_map(|(_, ranges)| ranges.iter())
16349 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16350 .cloned()
16351 .collect();
16352
16353 this.highlight_text::<Rename>(
16354 ranges,
16355 HighlightStyle {
16356 fade_out: Some(0.6),
16357 ..Default::default()
16358 },
16359 cx,
16360 );
16361 let rename_focus_handle = rename_editor.focus_handle(cx);
16362 window.focus(&rename_focus_handle);
16363 let block_id = this.insert_blocks(
16364 [BlockProperties {
16365 style: BlockStyle::Flex,
16366 placement: BlockPlacement::Below(range.start),
16367 height: Some(1),
16368 render: Arc::new({
16369 let rename_editor = rename_editor.clone();
16370 move |cx: &mut BlockContext| {
16371 let mut text_style = cx.editor_style.text.clone();
16372 if let Some(highlight_style) = old_highlight_id
16373 .and_then(|h| h.style(&cx.editor_style.syntax))
16374 {
16375 text_style = text_style.highlight(highlight_style);
16376 }
16377 div()
16378 .block_mouse_except_scroll()
16379 .pl(cx.anchor_x)
16380 .child(EditorElement::new(
16381 &rename_editor,
16382 EditorStyle {
16383 background: cx.theme().system().transparent,
16384 local_player: cx.editor_style.local_player,
16385 text: text_style,
16386 scrollbar_width: cx.editor_style.scrollbar_width,
16387 syntax: cx.editor_style.syntax.clone(),
16388 status: cx.editor_style.status.clone(),
16389 inlay_hints_style: HighlightStyle {
16390 font_weight: Some(FontWeight::BOLD),
16391 ..make_inlay_hints_style(cx.app)
16392 },
16393 edit_prediction_styles: make_suggestion_styles(
16394 cx.app,
16395 ),
16396 ..EditorStyle::default()
16397 },
16398 ))
16399 .into_any_element()
16400 }
16401 }),
16402 priority: 0,
16403 }],
16404 Some(Autoscroll::fit()),
16405 cx,
16406 )[0];
16407 this.pending_rename = Some(RenameState {
16408 range,
16409 old_name,
16410 editor: rename_editor,
16411 block_id,
16412 });
16413 })?;
16414 }
16415
16416 Ok(())
16417 }))
16418 }
16419
16420 pub fn confirm_rename(
16421 &mut self,
16422 _: &ConfirmRename,
16423 window: &mut Window,
16424 cx: &mut Context<Self>,
16425 ) -> Option<Task<Result<()>>> {
16426 let rename = self.take_rename(false, window, cx)?;
16427 let workspace = self.workspace()?.downgrade();
16428 let (buffer, start) = self
16429 .buffer
16430 .read(cx)
16431 .text_anchor_for_position(rename.range.start, cx)?;
16432 let (end_buffer, _) = self
16433 .buffer
16434 .read(cx)
16435 .text_anchor_for_position(rename.range.end, cx)?;
16436 if buffer != end_buffer {
16437 return None;
16438 }
16439
16440 let old_name = rename.old_name;
16441 let new_name = rename.editor.read(cx).text(cx);
16442
16443 let rename = self.semantics_provider.as_ref()?.perform_rename(
16444 &buffer,
16445 start,
16446 new_name.clone(),
16447 cx,
16448 )?;
16449
16450 Some(cx.spawn_in(window, async move |editor, cx| {
16451 let project_transaction = rename.await?;
16452 Self::open_project_transaction(
16453 &editor,
16454 workspace,
16455 project_transaction,
16456 format!("Rename: {} → {}", old_name, new_name),
16457 cx,
16458 )
16459 .await?;
16460
16461 editor.update(cx, |editor, cx| {
16462 editor.refresh_document_highlights(cx);
16463 })?;
16464 Ok(())
16465 }))
16466 }
16467
16468 fn take_rename(
16469 &mut self,
16470 moving_cursor: bool,
16471 window: &mut Window,
16472 cx: &mut Context<Self>,
16473 ) -> Option<RenameState> {
16474 let rename = self.pending_rename.take()?;
16475 if rename.editor.focus_handle(cx).is_focused(window) {
16476 window.focus(&self.focus_handle);
16477 }
16478
16479 self.remove_blocks(
16480 [rename.block_id].into_iter().collect(),
16481 Some(Autoscroll::fit()),
16482 cx,
16483 );
16484 self.clear_highlights::<Rename>(cx);
16485 self.show_local_selections = true;
16486
16487 if moving_cursor {
16488 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16489 editor.selections.newest::<usize>(cx).head()
16490 });
16491
16492 // Update the selection to match the position of the selection inside
16493 // the rename editor.
16494 let snapshot = self.buffer.read(cx).read(cx);
16495 let rename_range = rename.range.to_offset(&snapshot);
16496 let cursor_in_editor = snapshot
16497 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16498 .min(rename_range.end);
16499 drop(snapshot);
16500
16501 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16502 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16503 });
16504 } else {
16505 self.refresh_document_highlights(cx);
16506 }
16507
16508 Some(rename)
16509 }
16510
16511 pub fn pending_rename(&self) -> Option<&RenameState> {
16512 self.pending_rename.as_ref()
16513 }
16514
16515 fn format(
16516 &mut self,
16517 _: &Format,
16518 window: &mut Window,
16519 cx: &mut Context<Self>,
16520 ) -> Option<Task<Result<()>>> {
16521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16522
16523 let project = match &self.project {
16524 Some(project) => project.clone(),
16525 None => return None,
16526 };
16527
16528 Some(self.perform_format(
16529 project,
16530 FormatTrigger::Manual,
16531 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16532 window,
16533 cx,
16534 ))
16535 }
16536
16537 fn format_selections(
16538 &mut self,
16539 _: &FormatSelections,
16540 window: &mut Window,
16541 cx: &mut Context<Self>,
16542 ) -> Option<Task<Result<()>>> {
16543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16544
16545 let project = match &self.project {
16546 Some(project) => project.clone(),
16547 None => return None,
16548 };
16549
16550 let ranges = self
16551 .selections
16552 .all_adjusted(cx)
16553 .into_iter()
16554 .map(|selection| selection.range())
16555 .collect_vec();
16556
16557 Some(self.perform_format(
16558 project,
16559 FormatTrigger::Manual,
16560 FormatTarget::Ranges(ranges),
16561 window,
16562 cx,
16563 ))
16564 }
16565
16566 fn perform_format(
16567 &mut self,
16568 project: Entity<Project>,
16569 trigger: FormatTrigger,
16570 target: FormatTarget,
16571 window: &mut Window,
16572 cx: &mut Context<Self>,
16573 ) -> Task<Result<()>> {
16574 let buffer = self.buffer.clone();
16575 let (buffers, target) = match target {
16576 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16577 FormatTarget::Ranges(selection_ranges) => {
16578 let multi_buffer = buffer.read(cx);
16579 let snapshot = multi_buffer.read(cx);
16580 let mut buffers = HashSet::default();
16581 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16582 BTreeMap::new();
16583 for selection_range in selection_ranges {
16584 for (buffer, buffer_range, _) in
16585 snapshot.range_to_buffer_ranges(selection_range)
16586 {
16587 let buffer_id = buffer.remote_id();
16588 let start = buffer.anchor_before(buffer_range.start);
16589 let end = buffer.anchor_after(buffer_range.end);
16590 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16591 buffer_id_to_ranges
16592 .entry(buffer_id)
16593 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16594 .or_insert_with(|| vec![start..end]);
16595 }
16596 }
16597 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16598 }
16599 };
16600
16601 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16602 let selections_prev = transaction_id_prev
16603 .and_then(|transaction_id_prev| {
16604 // default to selections as they were after the last edit, if we have them,
16605 // instead of how they are now.
16606 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16607 // will take you back to where you made the last edit, instead of staying where you scrolled
16608 self.selection_history
16609 .transaction(transaction_id_prev)
16610 .map(|t| t.0.clone())
16611 })
16612 .unwrap_or_else(|| {
16613 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16614 self.selections.disjoint_anchors()
16615 });
16616
16617 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16618 let format = project.update(cx, |project, cx| {
16619 project.format(buffers, target, true, trigger, cx)
16620 });
16621
16622 cx.spawn_in(window, async move |editor, cx| {
16623 let transaction = futures::select_biased! {
16624 transaction = format.log_err().fuse() => transaction,
16625 () = timeout => {
16626 log::warn!("timed out waiting for formatting");
16627 None
16628 }
16629 };
16630
16631 buffer
16632 .update(cx, |buffer, cx| {
16633 if let Some(transaction) = transaction
16634 && !buffer.is_singleton()
16635 {
16636 buffer.push_transaction(&transaction.0, cx);
16637 }
16638 cx.notify();
16639 })
16640 .ok();
16641
16642 if let Some(transaction_id_now) =
16643 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16644 {
16645 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16646 if has_new_transaction {
16647 _ = editor.update(cx, |editor, _| {
16648 editor
16649 .selection_history
16650 .insert_transaction(transaction_id_now, selections_prev);
16651 });
16652 }
16653 }
16654
16655 Ok(())
16656 })
16657 }
16658
16659 fn organize_imports(
16660 &mut self,
16661 _: &OrganizeImports,
16662 window: &mut Window,
16663 cx: &mut Context<Self>,
16664 ) -> Option<Task<Result<()>>> {
16665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16666 let project = match &self.project {
16667 Some(project) => project.clone(),
16668 None => return None,
16669 };
16670 Some(self.perform_code_action_kind(
16671 project,
16672 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16673 window,
16674 cx,
16675 ))
16676 }
16677
16678 fn perform_code_action_kind(
16679 &mut self,
16680 project: Entity<Project>,
16681 kind: CodeActionKind,
16682 window: &mut Window,
16683 cx: &mut Context<Self>,
16684 ) -> Task<Result<()>> {
16685 let buffer = self.buffer.clone();
16686 let buffers = buffer.read(cx).all_buffers();
16687 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16688 let apply_action = project.update(cx, |project, cx| {
16689 project.apply_code_action_kind(buffers, kind, true, cx)
16690 });
16691 cx.spawn_in(window, async move |_, cx| {
16692 let transaction = futures::select_biased! {
16693 () = timeout => {
16694 log::warn!("timed out waiting for executing code action");
16695 None
16696 }
16697 transaction = apply_action.log_err().fuse() => transaction,
16698 };
16699 buffer
16700 .update(cx, |buffer, cx| {
16701 // check if we need this
16702 if let Some(transaction) = transaction
16703 && !buffer.is_singleton()
16704 {
16705 buffer.push_transaction(&transaction.0, cx);
16706 }
16707 cx.notify();
16708 })
16709 .ok();
16710 Ok(())
16711 })
16712 }
16713
16714 pub fn restart_language_server(
16715 &mut self,
16716 _: &RestartLanguageServer,
16717 _: &mut Window,
16718 cx: &mut Context<Self>,
16719 ) {
16720 if let Some(project) = self.project.clone() {
16721 self.buffer.update(cx, |multi_buffer, cx| {
16722 project.update(cx, |project, cx| {
16723 project.restart_language_servers_for_buffers(
16724 multi_buffer.all_buffers().into_iter().collect(),
16725 HashSet::default(),
16726 cx,
16727 );
16728 });
16729 })
16730 }
16731 }
16732
16733 pub fn stop_language_server(
16734 &mut self,
16735 _: &StopLanguageServer,
16736 _: &mut Window,
16737 cx: &mut Context<Self>,
16738 ) {
16739 if let Some(project) = self.project.clone() {
16740 self.buffer.update(cx, |multi_buffer, cx| {
16741 project.update(cx, |project, cx| {
16742 project.stop_language_servers_for_buffers(
16743 multi_buffer.all_buffers().into_iter().collect(),
16744 HashSet::default(),
16745 cx,
16746 );
16747 cx.emit(project::Event::RefreshInlayHints);
16748 });
16749 });
16750 }
16751 }
16752
16753 fn cancel_language_server_work(
16754 workspace: &mut Workspace,
16755 _: &actions::CancelLanguageServerWork,
16756 _: &mut Window,
16757 cx: &mut Context<Workspace>,
16758 ) {
16759 let project = workspace.project();
16760 let buffers = workspace
16761 .active_item(cx)
16762 .and_then(|item| item.act_as::<Editor>(cx))
16763 .map_or(HashSet::default(), |editor| {
16764 editor.read(cx).buffer.read(cx).all_buffers()
16765 });
16766 project.update(cx, |project, cx| {
16767 project.cancel_language_server_work_for_buffers(buffers, cx);
16768 });
16769 }
16770
16771 fn show_character_palette(
16772 &mut self,
16773 _: &ShowCharacterPalette,
16774 window: &mut Window,
16775 _: &mut Context<Self>,
16776 ) {
16777 window.show_character_palette();
16778 }
16779
16780 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16781 if !self.diagnostics_enabled() {
16782 return;
16783 }
16784
16785 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16786 let buffer = self.buffer.read(cx).snapshot(cx);
16787 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16788 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16789 let is_valid = buffer
16790 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16791 .any(|entry| {
16792 entry.diagnostic.is_primary
16793 && !entry.range.is_empty()
16794 && entry.range.start == primary_range_start
16795 && entry.diagnostic.message == active_diagnostics.active_message
16796 });
16797
16798 if !is_valid {
16799 self.dismiss_diagnostics(cx);
16800 }
16801 }
16802 }
16803
16804 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16805 match &self.active_diagnostics {
16806 ActiveDiagnostic::Group(group) => Some(group),
16807 _ => None,
16808 }
16809 }
16810
16811 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16812 if !self.diagnostics_enabled() {
16813 return;
16814 }
16815 self.dismiss_diagnostics(cx);
16816 self.active_diagnostics = ActiveDiagnostic::All;
16817 }
16818
16819 fn activate_diagnostics(
16820 &mut self,
16821 buffer_id: BufferId,
16822 diagnostic: DiagnosticEntry<usize>,
16823 window: &mut Window,
16824 cx: &mut Context<Self>,
16825 ) {
16826 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16827 return;
16828 }
16829 self.dismiss_diagnostics(cx);
16830 let snapshot = self.snapshot(window, cx);
16831 let buffer = self.buffer.read(cx).snapshot(cx);
16832 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16833 return;
16834 };
16835
16836 let diagnostic_group = buffer
16837 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16838 .collect::<Vec<_>>();
16839
16840 let blocks =
16841 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16842
16843 let blocks = self.display_map.update(cx, |display_map, cx| {
16844 display_map.insert_blocks(blocks, cx).into_iter().collect()
16845 });
16846 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16847 active_range: buffer.anchor_before(diagnostic.range.start)
16848 ..buffer.anchor_after(diagnostic.range.end),
16849 active_message: diagnostic.diagnostic.message.clone(),
16850 group_id: diagnostic.diagnostic.group_id,
16851 blocks,
16852 });
16853 cx.notify();
16854 }
16855
16856 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16857 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16858 return;
16859 };
16860
16861 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16862 if let ActiveDiagnostic::Group(group) = prev {
16863 self.display_map.update(cx, |display_map, cx| {
16864 display_map.remove_blocks(group.blocks, cx);
16865 });
16866 cx.notify();
16867 }
16868 }
16869
16870 /// Disable inline diagnostics rendering for this editor.
16871 pub fn disable_inline_diagnostics(&mut self) {
16872 self.inline_diagnostics_enabled = false;
16873 self.inline_diagnostics_update = Task::ready(());
16874 self.inline_diagnostics.clear();
16875 }
16876
16877 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16878 self.diagnostics_enabled = false;
16879 self.dismiss_diagnostics(cx);
16880 self.inline_diagnostics_update = Task::ready(());
16881 self.inline_diagnostics.clear();
16882 }
16883
16884 pub fn diagnostics_enabled(&self) -> bool {
16885 self.diagnostics_enabled && self.mode.is_full()
16886 }
16887
16888 pub fn inline_diagnostics_enabled(&self) -> bool {
16889 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16890 }
16891
16892 pub fn show_inline_diagnostics(&self) -> bool {
16893 self.show_inline_diagnostics
16894 }
16895
16896 pub fn toggle_inline_diagnostics(
16897 &mut self,
16898 _: &ToggleInlineDiagnostics,
16899 window: &mut Window,
16900 cx: &mut Context<Editor>,
16901 ) {
16902 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16903 self.refresh_inline_diagnostics(false, window, cx);
16904 }
16905
16906 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16907 self.diagnostics_max_severity = severity;
16908 self.display_map.update(cx, |display_map, _| {
16909 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16910 });
16911 }
16912
16913 pub fn toggle_diagnostics(
16914 &mut self,
16915 _: &ToggleDiagnostics,
16916 window: &mut Window,
16917 cx: &mut Context<Editor>,
16918 ) {
16919 if !self.diagnostics_enabled() {
16920 return;
16921 }
16922
16923 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16924 EditorSettings::get_global(cx)
16925 .diagnostics_max_severity
16926 .filter(|severity| severity != &DiagnosticSeverity::Off)
16927 .unwrap_or(DiagnosticSeverity::Hint)
16928 } else {
16929 DiagnosticSeverity::Off
16930 };
16931 self.set_max_diagnostics_severity(new_severity, cx);
16932 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16933 self.active_diagnostics = ActiveDiagnostic::None;
16934 self.inline_diagnostics_update = Task::ready(());
16935 self.inline_diagnostics.clear();
16936 } else {
16937 self.refresh_inline_diagnostics(false, window, cx);
16938 }
16939
16940 cx.notify();
16941 }
16942
16943 pub fn toggle_minimap(
16944 &mut self,
16945 _: &ToggleMinimap,
16946 window: &mut Window,
16947 cx: &mut Context<Editor>,
16948 ) {
16949 if self.supports_minimap(cx) {
16950 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16951 }
16952 }
16953
16954 fn refresh_inline_diagnostics(
16955 &mut self,
16956 debounce: bool,
16957 window: &mut Window,
16958 cx: &mut Context<Self>,
16959 ) {
16960 let max_severity = ProjectSettings::get_global(cx)
16961 .diagnostics
16962 .inline
16963 .max_severity
16964 .unwrap_or(self.diagnostics_max_severity);
16965
16966 if !self.inline_diagnostics_enabled()
16967 || !self.show_inline_diagnostics
16968 || max_severity == DiagnosticSeverity::Off
16969 {
16970 self.inline_diagnostics_update = Task::ready(());
16971 self.inline_diagnostics.clear();
16972 return;
16973 }
16974
16975 let debounce_ms = ProjectSettings::get_global(cx)
16976 .diagnostics
16977 .inline
16978 .update_debounce_ms;
16979 let debounce = if debounce && debounce_ms > 0 {
16980 Some(Duration::from_millis(debounce_ms))
16981 } else {
16982 None
16983 };
16984 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16985 if let Some(debounce) = debounce {
16986 cx.background_executor().timer(debounce).await;
16987 }
16988 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16989 editor
16990 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16991 .ok()
16992 }) else {
16993 return;
16994 };
16995
16996 let new_inline_diagnostics = cx
16997 .background_spawn(async move {
16998 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16999 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17000 let message = diagnostic_entry
17001 .diagnostic
17002 .message
17003 .split_once('\n')
17004 .map(|(line, _)| line)
17005 .map(SharedString::new)
17006 .unwrap_or_else(|| {
17007 SharedString::from(diagnostic_entry.diagnostic.message)
17008 });
17009 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17010 let (Ok(i) | Err(i)) = inline_diagnostics
17011 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17012 inline_diagnostics.insert(
17013 i,
17014 (
17015 start_anchor,
17016 InlineDiagnostic {
17017 message,
17018 group_id: diagnostic_entry.diagnostic.group_id,
17019 start: diagnostic_entry.range.start.to_point(&snapshot),
17020 is_primary: diagnostic_entry.diagnostic.is_primary,
17021 severity: diagnostic_entry.diagnostic.severity,
17022 },
17023 ),
17024 );
17025 }
17026 inline_diagnostics
17027 })
17028 .await;
17029
17030 editor
17031 .update(cx, |editor, cx| {
17032 editor.inline_diagnostics = new_inline_diagnostics;
17033 cx.notify();
17034 })
17035 .ok();
17036 });
17037 }
17038
17039 fn pull_diagnostics(
17040 &mut self,
17041 buffer_id: Option<BufferId>,
17042 window: &Window,
17043 cx: &mut Context<Self>,
17044 ) -> Option<()> {
17045 if !self.mode().is_full() {
17046 return None;
17047 }
17048 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17049 .diagnostics
17050 .lsp_pull_diagnostics;
17051 if !pull_diagnostics_settings.enabled {
17052 return None;
17053 }
17054 let project = self.project()?.downgrade();
17055 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17056 let mut buffers = self.buffer.read(cx).all_buffers();
17057 if let Some(buffer_id) = buffer_id {
17058 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17059 }
17060
17061 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17062 cx.background_executor().timer(debounce).await;
17063
17064 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17065 buffers
17066 .into_iter()
17067 .filter_map(|buffer| {
17068 project
17069 .update(cx, |project, cx| {
17070 project.lsp_store().update(cx, |lsp_store, cx| {
17071 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17072 })
17073 })
17074 .ok()
17075 })
17076 .collect::<FuturesUnordered<_>>()
17077 }) else {
17078 return;
17079 };
17080
17081 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17082 match pull_task {
17083 Ok(()) => {
17084 if editor
17085 .update_in(cx, |editor, window, cx| {
17086 editor.update_diagnostics_state(window, cx);
17087 })
17088 .is_err()
17089 {
17090 return;
17091 }
17092 }
17093 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17094 }
17095 }
17096 });
17097
17098 Some(())
17099 }
17100
17101 pub fn set_selections_from_remote(
17102 &mut self,
17103 selections: Vec<Selection<Anchor>>,
17104 pending_selection: Option<Selection<Anchor>>,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) {
17108 let old_cursor_position = self.selections.newest_anchor().head();
17109 self.selections.change_with(cx, |s| {
17110 s.select_anchors(selections);
17111 if let Some(pending_selection) = pending_selection {
17112 s.set_pending(pending_selection, SelectMode::Character);
17113 } else {
17114 s.clear_pending();
17115 }
17116 });
17117 self.selections_did_change(
17118 false,
17119 &old_cursor_position,
17120 SelectionEffects::default(),
17121 window,
17122 cx,
17123 );
17124 }
17125
17126 pub fn transact(
17127 &mut self,
17128 window: &mut Window,
17129 cx: &mut Context<Self>,
17130 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17131 ) -> Option<TransactionId> {
17132 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17133 this.start_transaction_at(Instant::now(), window, cx);
17134 update(this, window, cx);
17135 this.end_transaction_at(Instant::now(), cx)
17136 })
17137 }
17138
17139 pub fn start_transaction_at(
17140 &mut self,
17141 now: Instant,
17142 window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) -> Option<TransactionId> {
17145 self.end_selection(window, cx);
17146 if let Some(tx_id) = self
17147 .buffer
17148 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17149 {
17150 self.selection_history
17151 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17152 cx.emit(EditorEvent::TransactionBegun {
17153 transaction_id: tx_id,
17154 });
17155 Some(tx_id)
17156 } else {
17157 None
17158 }
17159 }
17160
17161 pub fn end_transaction_at(
17162 &mut self,
17163 now: Instant,
17164 cx: &mut Context<Self>,
17165 ) -> Option<TransactionId> {
17166 if let Some(transaction_id) = self
17167 .buffer
17168 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17169 {
17170 if let Some((_, end_selections)) =
17171 self.selection_history.transaction_mut(transaction_id)
17172 {
17173 *end_selections = Some(self.selections.disjoint_anchors());
17174 } else {
17175 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17176 }
17177
17178 cx.emit(EditorEvent::Edited { transaction_id });
17179 Some(transaction_id)
17180 } else {
17181 None
17182 }
17183 }
17184
17185 pub fn modify_transaction_selection_history(
17186 &mut self,
17187 transaction_id: TransactionId,
17188 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17189 ) -> bool {
17190 self.selection_history
17191 .transaction_mut(transaction_id)
17192 .map(modify)
17193 .is_some()
17194 }
17195
17196 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17197 if self.selection_mark_mode {
17198 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17199 s.move_with(|_, sel| {
17200 sel.collapse_to(sel.head(), SelectionGoal::None);
17201 });
17202 })
17203 }
17204 self.selection_mark_mode = true;
17205 cx.notify();
17206 }
17207
17208 pub fn swap_selection_ends(
17209 &mut self,
17210 _: &actions::SwapSelectionEnds,
17211 window: &mut Window,
17212 cx: &mut Context<Self>,
17213 ) {
17214 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17215 s.move_with(|_, sel| {
17216 if sel.start != sel.end {
17217 sel.reversed = !sel.reversed
17218 }
17219 });
17220 });
17221 self.request_autoscroll(Autoscroll::newest(), cx);
17222 cx.notify();
17223 }
17224
17225 pub fn toggle_focus(
17226 workspace: &mut Workspace,
17227 _: &actions::ToggleFocus,
17228 window: &mut Window,
17229 cx: &mut Context<Workspace>,
17230 ) {
17231 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17232 return;
17233 };
17234 workspace.activate_item(&item, true, true, window, cx);
17235 }
17236
17237 pub fn toggle_fold(
17238 &mut self,
17239 _: &actions::ToggleFold,
17240 window: &mut Window,
17241 cx: &mut Context<Self>,
17242 ) {
17243 if self.is_singleton(cx) {
17244 let selection = self.selections.newest::<Point>(cx);
17245
17246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17247 let range = if selection.is_empty() {
17248 let point = selection.head().to_display_point(&display_map);
17249 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17250 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17251 .to_point(&display_map);
17252 start..end
17253 } else {
17254 selection.range()
17255 };
17256 if display_map.folds_in_range(range).next().is_some() {
17257 self.unfold_lines(&Default::default(), window, cx)
17258 } else {
17259 self.fold(&Default::default(), window, cx)
17260 }
17261 } else {
17262 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17263 let buffer_ids: HashSet<_> = self
17264 .selections
17265 .disjoint_anchor_ranges()
17266 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17267 .collect();
17268
17269 let should_unfold = buffer_ids
17270 .iter()
17271 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17272
17273 for buffer_id in buffer_ids {
17274 if should_unfold {
17275 self.unfold_buffer(buffer_id, cx);
17276 } else {
17277 self.fold_buffer(buffer_id, cx);
17278 }
17279 }
17280 }
17281 }
17282
17283 pub fn toggle_fold_recursive(
17284 &mut self,
17285 _: &actions::ToggleFoldRecursive,
17286 window: &mut Window,
17287 cx: &mut Context<Self>,
17288 ) {
17289 let selection = self.selections.newest::<Point>(cx);
17290
17291 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17292 let range = if selection.is_empty() {
17293 let point = selection.head().to_display_point(&display_map);
17294 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17295 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17296 .to_point(&display_map);
17297 start..end
17298 } else {
17299 selection.range()
17300 };
17301 if display_map.folds_in_range(range).next().is_some() {
17302 self.unfold_recursive(&Default::default(), window, cx)
17303 } else {
17304 self.fold_recursive(&Default::default(), window, cx)
17305 }
17306 }
17307
17308 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17309 if self.is_singleton(cx) {
17310 let mut to_fold = Vec::new();
17311 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17312 let selections = self.selections.all_adjusted(cx);
17313
17314 for selection in selections {
17315 let range = selection.range().sorted();
17316 let buffer_start_row = range.start.row;
17317
17318 if range.start.row != range.end.row {
17319 let mut found = false;
17320 let mut row = range.start.row;
17321 while row <= range.end.row {
17322 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17323 {
17324 found = true;
17325 row = crease.range().end.row + 1;
17326 to_fold.push(crease);
17327 } else {
17328 row += 1
17329 }
17330 }
17331 if found {
17332 continue;
17333 }
17334 }
17335
17336 for row in (0..=range.start.row).rev() {
17337 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17338 && crease.range().end.row >= buffer_start_row
17339 {
17340 to_fold.push(crease);
17341 if row <= range.start.row {
17342 break;
17343 }
17344 }
17345 }
17346 }
17347
17348 self.fold_creases(to_fold, true, window, cx);
17349 } else {
17350 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17351 let buffer_ids = self
17352 .selections
17353 .disjoint_anchor_ranges()
17354 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17355 .collect::<HashSet<_>>();
17356 for buffer_id in buffer_ids {
17357 self.fold_buffer(buffer_id, cx);
17358 }
17359 }
17360 }
17361
17362 pub fn toggle_fold_all(
17363 &mut self,
17364 _: &actions::ToggleFoldAll,
17365 window: &mut Window,
17366 cx: &mut Context<Self>,
17367 ) {
17368 if self.buffer.read(cx).is_singleton() {
17369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17370 let has_folds = display_map
17371 .folds_in_range(0..display_map.buffer_snapshot.len())
17372 .next()
17373 .is_some();
17374
17375 if has_folds {
17376 self.unfold_all(&actions::UnfoldAll, window, cx);
17377 } else {
17378 self.fold_all(&actions::FoldAll, window, cx);
17379 }
17380 } else {
17381 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17382 let should_unfold = buffer_ids
17383 .iter()
17384 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17385
17386 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17387 editor
17388 .update_in(cx, |editor, _, cx| {
17389 for buffer_id in buffer_ids {
17390 if should_unfold {
17391 editor.unfold_buffer(buffer_id, cx);
17392 } else {
17393 editor.fold_buffer(buffer_id, cx);
17394 }
17395 }
17396 })
17397 .ok();
17398 });
17399 }
17400 }
17401
17402 fn fold_at_level(
17403 &mut self,
17404 fold_at: &FoldAtLevel,
17405 window: &mut Window,
17406 cx: &mut Context<Self>,
17407 ) {
17408 if !self.buffer.read(cx).is_singleton() {
17409 return;
17410 }
17411
17412 let fold_at_level = fold_at.0;
17413 let snapshot = self.buffer.read(cx).snapshot(cx);
17414 let mut to_fold = Vec::new();
17415 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17416
17417 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17418 while start_row < end_row {
17419 match self
17420 .snapshot(window, cx)
17421 .crease_for_buffer_row(MultiBufferRow(start_row))
17422 {
17423 Some(crease) => {
17424 let nested_start_row = crease.range().start.row + 1;
17425 let nested_end_row = crease.range().end.row;
17426
17427 if current_level < fold_at_level {
17428 stack.push((nested_start_row, nested_end_row, current_level + 1));
17429 } else if current_level == fold_at_level {
17430 to_fold.push(crease);
17431 }
17432
17433 start_row = nested_end_row + 1;
17434 }
17435 None => start_row += 1,
17436 }
17437 }
17438 }
17439
17440 self.fold_creases(to_fold, true, window, cx);
17441 }
17442
17443 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17444 if self.buffer.read(cx).is_singleton() {
17445 let mut fold_ranges = Vec::new();
17446 let snapshot = self.buffer.read(cx).snapshot(cx);
17447
17448 for row in 0..snapshot.max_row().0 {
17449 if let Some(foldable_range) = self
17450 .snapshot(window, cx)
17451 .crease_for_buffer_row(MultiBufferRow(row))
17452 {
17453 fold_ranges.push(foldable_range);
17454 }
17455 }
17456
17457 self.fold_creases(fold_ranges, true, window, cx);
17458 } else {
17459 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17460 editor
17461 .update_in(cx, |editor, _, cx| {
17462 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17463 editor.fold_buffer(buffer_id, cx);
17464 }
17465 })
17466 .ok();
17467 });
17468 }
17469 }
17470
17471 pub fn fold_function_bodies(
17472 &mut self,
17473 _: &actions::FoldFunctionBodies,
17474 window: &mut Window,
17475 cx: &mut Context<Self>,
17476 ) {
17477 let snapshot = self.buffer.read(cx).snapshot(cx);
17478
17479 let ranges = snapshot
17480 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17481 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17482 .collect::<Vec<_>>();
17483
17484 let creases = ranges
17485 .into_iter()
17486 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17487 .collect();
17488
17489 self.fold_creases(creases, true, window, cx);
17490 }
17491
17492 pub fn fold_recursive(
17493 &mut self,
17494 _: &actions::FoldRecursive,
17495 window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) {
17498 let mut to_fold = Vec::new();
17499 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17500 let selections = self.selections.all_adjusted(cx);
17501
17502 for selection in selections {
17503 let range = selection.range().sorted();
17504 let buffer_start_row = range.start.row;
17505
17506 if range.start.row != range.end.row {
17507 let mut found = false;
17508 for row in range.start.row..=range.end.row {
17509 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17510 found = true;
17511 to_fold.push(crease);
17512 }
17513 }
17514 if found {
17515 continue;
17516 }
17517 }
17518
17519 for row in (0..=range.start.row).rev() {
17520 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17521 if crease.range().end.row >= buffer_start_row {
17522 to_fold.push(crease);
17523 } else {
17524 break;
17525 }
17526 }
17527 }
17528 }
17529
17530 self.fold_creases(to_fold, true, window, cx);
17531 }
17532
17533 pub fn fold_at(
17534 &mut self,
17535 buffer_row: MultiBufferRow,
17536 window: &mut Window,
17537 cx: &mut Context<Self>,
17538 ) {
17539 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17540
17541 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17542 let autoscroll = self
17543 .selections
17544 .all::<Point>(cx)
17545 .iter()
17546 .any(|selection| crease.range().overlaps(&selection.range()));
17547
17548 self.fold_creases(vec![crease], autoscroll, window, cx);
17549 }
17550 }
17551
17552 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17553 if self.is_singleton(cx) {
17554 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17555 let buffer = &display_map.buffer_snapshot;
17556 let selections = self.selections.all::<Point>(cx);
17557 let ranges = selections
17558 .iter()
17559 .map(|s| {
17560 let range = s.display_range(&display_map).sorted();
17561 let mut start = range.start.to_point(&display_map);
17562 let mut end = range.end.to_point(&display_map);
17563 start.column = 0;
17564 end.column = buffer.line_len(MultiBufferRow(end.row));
17565 start..end
17566 })
17567 .collect::<Vec<_>>();
17568
17569 self.unfold_ranges(&ranges, true, true, cx);
17570 } else {
17571 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17572 let buffer_ids = self
17573 .selections
17574 .disjoint_anchor_ranges()
17575 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17576 .collect::<HashSet<_>>();
17577 for buffer_id in buffer_ids {
17578 self.unfold_buffer(buffer_id, cx);
17579 }
17580 }
17581 }
17582
17583 pub fn unfold_recursive(
17584 &mut self,
17585 _: &UnfoldRecursive,
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 let selections = self.selections.all::<Point>(cx);
17591 let ranges = selections
17592 .iter()
17593 .map(|s| {
17594 let mut range = s.display_range(&display_map).sorted();
17595 *range.start.column_mut() = 0;
17596 *range.end.column_mut() = display_map.line_len(range.end.row());
17597 let start = range.start.to_point(&display_map);
17598 let end = range.end.to_point(&display_map);
17599 start..end
17600 })
17601 .collect::<Vec<_>>();
17602
17603 self.unfold_ranges(&ranges, true, true, cx);
17604 }
17605
17606 pub fn unfold_at(
17607 &mut self,
17608 buffer_row: MultiBufferRow,
17609 _window: &mut Window,
17610 cx: &mut Context<Self>,
17611 ) {
17612 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17613
17614 let intersection_range = Point::new(buffer_row.0, 0)
17615 ..Point::new(
17616 buffer_row.0,
17617 display_map.buffer_snapshot.line_len(buffer_row),
17618 );
17619
17620 let autoscroll = self
17621 .selections
17622 .all::<Point>(cx)
17623 .iter()
17624 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17625
17626 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17627 }
17628
17629 pub fn unfold_all(
17630 &mut self,
17631 _: &actions::UnfoldAll,
17632 _window: &mut Window,
17633 cx: &mut Context<Self>,
17634 ) {
17635 if self.buffer.read(cx).is_singleton() {
17636 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17637 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17638 } else {
17639 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17640 editor
17641 .update(cx, |editor, cx| {
17642 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17643 editor.unfold_buffer(buffer_id, cx);
17644 }
17645 })
17646 .ok();
17647 });
17648 }
17649 }
17650
17651 pub fn fold_selected_ranges(
17652 &mut self,
17653 _: &FoldSelectedRanges,
17654 window: &mut Window,
17655 cx: &mut Context<Self>,
17656 ) {
17657 let selections = self.selections.all_adjusted(cx);
17658 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17659 let ranges = selections
17660 .into_iter()
17661 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17662 .collect::<Vec<_>>();
17663 self.fold_creases(ranges, true, window, cx);
17664 }
17665
17666 pub fn fold_ranges<T: ToOffset + Clone>(
17667 &mut self,
17668 ranges: Vec<Range<T>>,
17669 auto_scroll: bool,
17670 window: &mut Window,
17671 cx: &mut Context<Self>,
17672 ) {
17673 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17674 let ranges = ranges
17675 .into_iter()
17676 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17677 .collect::<Vec<_>>();
17678 self.fold_creases(ranges, auto_scroll, window, cx);
17679 }
17680
17681 pub fn fold_creases<T: ToOffset + Clone>(
17682 &mut self,
17683 creases: Vec<Crease<T>>,
17684 auto_scroll: bool,
17685 _window: &mut Window,
17686 cx: &mut Context<Self>,
17687 ) {
17688 if creases.is_empty() {
17689 return;
17690 }
17691
17692 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17693
17694 if auto_scroll {
17695 self.request_autoscroll(Autoscroll::fit(), cx);
17696 }
17697
17698 cx.notify();
17699
17700 self.scrollbar_marker_state.dirty = true;
17701 self.folds_did_change(cx);
17702 }
17703
17704 /// Removes any folds whose ranges intersect any of the given ranges.
17705 pub fn unfold_ranges<T: ToOffset + Clone>(
17706 &mut self,
17707 ranges: &[Range<T>],
17708 inclusive: bool,
17709 auto_scroll: bool,
17710 cx: &mut Context<Self>,
17711 ) {
17712 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17713 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17714 });
17715 self.folds_did_change(cx);
17716 }
17717
17718 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17719 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17720 return;
17721 }
17722 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17723 self.display_map.update(cx, |display_map, cx| {
17724 display_map.fold_buffers([buffer_id], cx)
17725 });
17726 cx.emit(EditorEvent::BufferFoldToggled {
17727 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17728 folded: true,
17729 });
17730 cx.notify();
17731 }
17732
17733 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17734 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17735 return;
17736 }
17737 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17738 self.display_map.update(cx, |display_map, cx| {
17739 display_map.unfold_buffers([buffer_id], cx);
17740 });
17741 cx.emit(EditorEvent::BufferFoldToggled {
17742 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17743 folded: false,
17744 });
17745 cx.notify();
17746 }
17747
17748 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17749 self.display_map.read(cx).is_buffer_folded(buffer)
17750 }
17751
17752 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17753 self.display_map.read(cx).folded_buffers()
17754 }
17755
17756 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17757 self.display_map.update(cx, |display_map, cx| {
17758 display_map.disable_header_for_buffer(buffer_id, cx);
17759 });
17760 cx.notify();
17761 }
17762
17763 /// Removes any folds with the given ranges.
17764 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17765 &mut self,
17766 ranges: &[Range<T>],
17767 type_id: TypeId,
17768 auto_scroll: bool,
17769 cx: &mut Context<Self>,
17770 ) {
17771 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17772 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17773 });
17774 self.folds_did_change(cx);
17775 }
17776
17777 fn remove_folds_with<T: ToOffset + Clone>(
17778 &mut self,
17779 ranges: &[Range<T>],
17780 auto_scroll: bool,
17781 cx: &mut Context<Self>,
17782 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17783 ) {
17784 if ranges.is_empty() {
17785 return;
17786 }
17787
17788 let mut buffers_affected = HashSet::default();
17789 let multi_buffer = self.buffer().read(cx);
17790 for range in ranges {
17791 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17792 buffers_affected.insert(buffer.read(cx).remote_id());
17793 };
17794 }
17795
17796 self.display_map.update(cx, update);
17797
17798 if auto_scroll {
17799 self.request_autoscroll(Autoscroll::fit(), cx);
17800 }
17801
17802 cx.notify();
17803 self.scrollbar_marker_state.dirty = true;
17804 self.active_indent_guides_state.dirty = true;
17805 }
17806
17807 pub fn update_renderer_widths(
17808 &mut self,
17809 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17810 cx: &mut Context<Self>,
17811 ) -> bool {
17812 self.display_map
17813 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17814 }
17815
17816 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17817 self.display_map.read(cx).fold_placeholder.clone()
17818 }
17819
17820 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17821 self.buffer.update(cx, |buffer, cx| {
17822 buffer.set_all_diff_hunks_expanded(cx);
17823 });
17824 }
17825
17826 pub fn expand_all_diff_hunks(
17827 &mut self,
17828 _: &ExpandAllDiffHunks,
17829 _window: &mut Window,
17830 cx: &mut Context<Self>,
17831 ) {
17832 self.buffer.update(cx, |buffer, cx| {
17833 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17834 });
17835 }
17836
17837 pub fn toggle_selected_diff_hunks(
17838 &mut self,
17839 _: &ToggleSelectedDiffHunks,
17840 _window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17844 self.toggle_diff_hunks_in_ranges(ranges, cx);
17845 }
17846
17847 pub fn diff_hunks_in_ranges<'a>(
17848 &'a self,
17849 ranges: &'a [Range<Anchor>],
17850 buffer: &'a MultiBufferSnapshot,
17851 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17852 ranges.iter().flat_map(move |range| {
17853 let end_excerpt_id = range.end.excerpt_id;
17854 let range = range.to_point(buffer);
17855 let mut peek_end = range.end;
17856 if range.end.row < buffer.max_row().0 {
17857 peek_end = Point::new(range.end.row + 1, 0);
17858 }
17859 buffer
17860 .diff_hunks_in_range(range.start..peek_end)
17861 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17862 })
17863 }
17864
17865 pub fn has_stageable_diff_hunks_in_ranges(
17866 &self,
17867 ranges: &[Range<Anchor>],
17868 snapshot: &MultiBufferSnapshot,
17869 ) -> bool {
17870 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17871 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17872 }
17873
17874 pub fn toggle_staged_selected_diff_hunks(
17875 &mut self,
17876 _: &::git::ToggleStaged,
17877 _: &mut Window,
17878 cx: &mut Context<Self>,
17879 ) {
17880 let snapshot = self.buffer.read(cx).snapshot(cx);
17881 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17882 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17883 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17884 }
17885
17886 pub fn set_render_diff_hunk_controls(
17887 &mut self,
17888 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17889 cx: &mut Context<Self>,
17890 ) {
17891 self.render_diff_hunk_controls = render_diff_hunk_controls;
17892 cx.notify();
17893 }
17894
17895 pub fn stage_and_next(
17896 &mut self,
17897 _: &::git::StageAndNext,
17898 window: &mut Window,
17899 cx: &mut Context<Self>,
17900 ) {
17901 self.do_stage_or_unstage_and_next(true, window, cx);
17902 }
17903
17904 pub fn unstage_and_next(
17905 &mut self,
17906 _: &::git::UnstageAndNext,
17907 window: &mut Window,
17908 cx: &mut Context<Self>,
17909 ) {
17910 self.do_stage_or_unstage_and_next(false, window, cx);
17911 }
17912
17913 pub fn stage_or_unstage_diff_hunks(
17914 &mut self,
17915 stage: bool,
17916 ranges: Vec<Range<Anchor>>,
17917 cx: &mut Context<Self>,
17918 ) {
17919 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17920 cx.spawn(async move |this, cx| {
17921 task.await?;
17922 this.update(cx, |this, cx| {
17923 let snapshot = this.buffer.read(cx).snapshot(cx);
17924 let chunk_by = this
17925 .diff_hunks_in_ranges(&ranges, &snapshot)
17926 .chunk_by(|hunk| hunk.buffer_id);
17927 for (buffer_id, hunks) in &chunk_by {
17928 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17929 }
17930 })
17931 })
17932 .detach_and_log_err(cx);
17933 }
17934
17935 fn save_buffers_for_ranges_if_needed(
17936 &mut self,
17937 ranges: &[Range<Anchor>],
17938 cx: &mut Context<Editor>,
17939 ) -> Task<Result<()>> {
17940 let multibuffer = self.buffer.read(cx);
17941 let snapshot = multibuffer.read(cx);
17942 let buffer_ids: HashSet<_> = ranges
17943 .iter()
17944 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17945 .collect();
17946 drop(snapshot);
17947
17948 let mut buffers = HashSet::default();
17949 for buffer_id in buffer_ids {
17950 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17951 let buffer = buffer_entity.read(cx);
17952 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17953 {
17954 buffers.insert(buffer_entity);
17955 }
17956 }
17957 }
17958
17959 if let Some(project) = &self.project {
17960 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17961 } else {
17962 Task::ready(Ok(()))
17963 }
17964 }
17965
17966 fn do_stage_or_unstage_and_next(
17967 &mut self,
17968 stage: bool,
17969 window: &mut Window,
17970 cx: &mut Context<Self>,
17971 ) {
17972 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17973
17974 if ranges.iter().any(|range| range.start != range.end) {
17975 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17976 return;
17977 }
17978
17979 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17980 let snapshot = self.snapshot(window, cx);
17981 let position = self.selections.newest::<Point>(cx).head();
17982 let mut row = snapshot
17983 .buffer_snapshot
17984 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17985 .find(|hunk| hunk.row_range.start.0 > position.row)
17986 .map(|hunk| hunk.row_range.start);
17987
17988 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17989 // Outside of the project diff editor, wrap around to the beginning.
17990 if !all_diff_hunks_expanded {
17991 row = row.or_else(|| {
17992 snapshot
17993 .buffer_snapshot
17994 .diff_hunks_in_range(Point::zero()..position)
17995 .find(|hunk| hunk.row_range.end.0 < position.row)
17996 .map(|hunk| hunk.row_range.start)
17997 });
17998 }
17999
18000 if let Some(row) = row {
18001 let destination = Point::new(row.0, 0);
18002 let autoscroll = Autoscroll::center();
18003
18004 self.unfold_ranges(&[destination..destination], false, false, cx);
18005 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18006 s.select_ranges([destination..destination]);
18007 });
18008 }
18009 }
18010
18011 fn do_stage_or_unstage(
18012 &self,
18013 stage: bool,
18014 buffer_id: BufferId,
18015 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18016 cx: &mut App,
18017 ) -> Option<()> {
18018 let project = self.project()?;
18019 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18020 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18021 let buffer_snapshot = buffer.read(cx).snapshot();
18022 let file_exists = buffer_snapshot
18023 .file()
18024 .is_some_and(|file| file.disk_state().exists());
18025 diff.update(cx, |diff, cx| {
18026 diff.stage_or_unstage_hunks(
18027 stage,
18028 &hunks
18029 .map(|hunk| buffer_diff::DiffHunk {
18030 buffer_range: hunk.buffer_range,
18031 diff_base_byte_range: hunk.diff_base_byte_range,
18032 secondary_status: hunk.secondary_status,
18033 range: Point::zero()..Point::zero(), // unused
18034 })
18035 .collect::<Vec<_>>(),
18036 &buffer_snapshot,
18037 file_exists,
18038 cx,
18039 )
18040 });
18041 None
18042 }
18043
18044 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18045 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18046 self.buffer
18047 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18048 }
18049
18050 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18051 self.buffer.update(cx, |buffer, cx| {
18052 let ranges = vec![Anchor::min()..Anchor::max()];
18053 if !buffer.all_diff_hunks_expanded()
18054 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18055 {
18056 buffer.collapse_diff_hunks(ranges, cx);
18057 true
18058 } else {
18059 false
18060 }
18061 })
18062 }
18063
18064 fn toggle_diff_hunks_in_ranges(
18065 &mut self,
18066 ranges: Vec<Range<Anchor>>,
18067 cx: &mut Context<Editor>,
18068 ) {
18069 self.buffer.update(cx, |buffer, cx| {
18070 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18071 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18072 })
18073 }
18074
18075 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18076 self.buffer.update(cx, |buffer, cx| {
18077 let snapshot = buffer.snapshot(cx);
18078 let excerpt_id = range.end.excerpt_id;
18079 let point_range = range.to_point(&snapshot);
18080 let expand = !buffer.single_hunk_is_expanded(range, cx);
18081 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18082 })
18083 }
18084
18085 pub(crate) fn apply_all_diff_hunks(
18086 &mut self,
18087 _: &ApplyAllDiffHunks,
18088 window: &mut Window,
18089 cx: &mut Context<Self>,
18090 ) {
18091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18092
18093 let buffers = self.buffer.read(cx).all_buffers();
18094 for branch_buffer in buffers {
18095 branch_buffer.update(cx, |branch_buffer, cx| {
18096 branch_buffer.merge_into_base(Vec::new(), cx);
18097 });
18098 }
18099
18100 if let Some(project) = self.project.clone() {
18101 self.save(
18102 SaveOptions {
18103 format: true,
18104 autosave: false,
18105 },
18106 project,
18107 window,
18108 cx,
18109 )
18110 .detach_and_log_err(cx);
18111 }
18112 }
18113
18114 pub(crate) fn apply_selected_diff_hunks(
18115 &mut self,
18116 _: &ApplyDiffHunk,
18117 window: &mut Window,
18118 cx: &mut Context<Self>,
18119 ) {
18120 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18121 let snapshot = self.snapshot(window, cx);
18122 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18123 let mut ranges_by_buffer = HashMap::default();
18124 self.transact(window, cx, |editor, _window, cx| {
18125 for hunk in hunks {
18126 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18127 ranges_by_buffer
18128 .entry(buffer.clone())
18129 .or_insert_with(Vec::new)
18130 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18131 }
18132 }
18133
18134 for (buffer, ranges) in ranges_by_buffer {
18135 buffer.update(cx, |buffer, cx| {
18136 buffer.merge_into_base(ranges, cx);
18137 });
18138 }
18139 });
18140
18141 if let Some(project) = self.project.clone() {
18142 self.save(
18143 SaveOptions {
18144 format: true,
18145 autosave: false,
18146 },
18147 project,
18148 window,
18149 cx,
18150 )
18151 .detach_and_log_err(cx);
18152 }
18153 }
18154
18155 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18156 if hovered != self.gutter_hovered {
18157 self.gutter_hovered = hovered;
18158 cx.notify();
18159 }
18160 }
18161
18162 pub fn insert_blocks(
18163 &mut self,
18164 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18165 autoscroll: Option<Autoscroll>,
18166 cx: &mut Context<Self>,
18167 ) -> Vec<CustomBlockId> {
18168 let blocks = self
18169 .display_map
18170 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18171 if let Some(autoscroll) = autoscroll {
18172 self.request_autoscroll(autoscroll, cx);
18173 }
18174 cx.notify();
18175 blocks
18176 }
18177
18178 pub fn resize_blocks(
18179 &mut self,
18180 heights: HashMap<CustomBlockId, u32>,
18181 autoscroll: Option<Autoscroll>,
18182 cx: &mut Context<Self>,
18183 ) {
18184 self.display_map
18185 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18186 if let Some(autoscroll) = autoscroll {
18187 self.request_autoscroll(autoscroll, cx);
18188 }
18189 cx.notify();
18190 }
18191
18192 pub fn replace_blocks(
18193 &mut self,
18194 renderers: HashMap<CustomBlockId, RenderBlock>,
18195 autoscroll: Option<Autoscroll>,
18196 cx: &mut Context<Self>,
18197 ) {
18198 self.display_map
18199 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18200 if let Some(autoscroll) = autoscroll {
18201 self.request_autoscroll(autoscroll, cx);
18202 }
18203 cx.notify();
18204 }
18205
18206 pub fn remove_blocks(
18207 &mut self,
18208 block_ids: HashSet<CustomBlockId>,
18209 autoscroll: Option<Autoscroll>,
18210 cx: &mut Context<Self>,
18211 ) {
18212 self.display_map.update(cx, |display_map, cx| {
18213 display_map.remove_blocks(block_ids, cx)
18214 });
18215 if let Some(autoscroll) = autoscroll {
18216 self.request_autoscroll(autoscroll, cx);
18217 }
18218 cx.notify();
18219 }
18220
18221 pub fn row_for_block(
18222 &self,
18223 block_id: CustomBlockId,
18224 cx: &mut Context<Self>,
18225 ) -> Option<DisplayRow> {
18226 self.display_map
18227 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18228 }
18229
18230 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18231 self.focused_block = Some(focused_block);
18232 }
18233
18234 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18235 self.focused_block.take()
18236 }
18237
18238 pub fn insert_creases(
18239 &mut self,
18240 creases: impl IntoIterator<Item = Crease<Anchor>>,
18241 cx: &mut Context<Self>,
18242 ) -> Vec<CreaseId> {
18243 self.display_map
18244 .update(cx, |map, cx| map.insert_creases(creases, cx))
18245 }
18246
18247 pub fn remove_creases(
18248 &mut self,
18249 ids: impl IntoIterator<Item = CreaseId>,
18250 cx: &mut Context<Self>,
18251 ) -> Vec<(CreaseId, Range<Anchor>)> {
18252 self.display_map
18253 .update(cx, |map, cx| map.remove_creases(ids, cx))
18254 }
18255
18256 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18257 self.display_map
18258 .update(cx, |map, cx| map.snapshot(cx))
18259 .longest_row()
18260 }
18261
18262 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18263 self.display_map
18264 .update(cx, |map, cx| map.snapshot(cx))
18265 .max_point()
18266 }
18267
18268 pub fn text(&self, cx: &App) -> String {
18269 self.buffer.read(cx).read(cx).text()
18270 }
18271
18272 pub fn is_empty(&self, cx: &App) -> bool {
18273 self.buffer.read(cx).read(cx).is_empty()
18274 }
18275
18276 pub fn text_option(&self, cx: &App) -> Option<String> {
18277 let text = self.text(cx);
18278 let text = text.trim();
18279
18280 if text.is_empty() {
18281 return None;
18282 }
18283
18284 Some(text.to_string())
18285 }
18286
18287 pub fn set_text(
18288 &mut self,
18289 text: impl Into<Arc<str>>,
18290 window: &mut Window,
18291 cx: &mut Context<Self>,
18292 ) {
18293 self.transact(window, cx, |this, _, cx| {
18294 this.buffer
18295 .read(cx)
18296 .as_singleton()
18297 .expect("you can only call set_text on editors for singleton buffers")
18298 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18299 });
18300 }
18301
18302 pub fn display_text(&self, cx: &mut App) -> String {
18303 self.display_map
18304 .update(cx, |map, cx| map.snapshot(cx))
18305 .text()
18306 }
18307
18308 fn create_minimap(
18309 &self,
18310 minimap_settings: MinimapSettings,
18311 window: &mut Window,
18312 cx: &mut Context<Self>,
18313 ) -> Option<Entity<Self>> {
18314 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18315 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18316 }
18317
18318 fn initialize_new_minimap(
18319 &self,
18320 minimap_settings: MinimapSettings,
18321 window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) -> Entity<Self> {
18324 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18325
18326 let mut minimap = Editor::new_internal(
18327 EditorMode::Minimap {
18328 parent: cx.weak_entity(),
18329 },
18330 self.buffer.clone(),
18331 None,
18332 Some(self.display_map.clone()),
18333 window,
18334 cx,
18335 );
18336 minimap.scroll_manager.clone_state(&self.scroll_manager);
18337 minimap.set_text_style_refinement(TextStyleRefinement {
18338 font_size: Some(MINIMAP_FONT_SIZE),
18339 font_weight: Some(MINIMAP_FONT_WEIGHT),
18340 ..Default::default()
18341 });
18342 minimap.update_minimap_configuration(minimap_settings, cx);
18343 cx.new(|_| minimap)
18344 }
18345
18346 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18347 let current_line_highlight = minimap_settings
18348 .current_line_highlight
18349 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18350 self.set_current_line_highlight(Some(current_line_highlight));
18351 }
18352
18353 pub fn minimap(&self) -> Option<&Entity<Self>> {
18354 self.minimap
18355 .as_ref()
18356 .filter(|_| self.minimap_visibility.visible())
18357 }
18358
18359 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18360 let mut wrap_guides = smallvec![];
18361
18362 if self.show_wrap_guides == Some(false) {
18363 return wrap_guides;
18364 }
18365
18366 let settings = self.buffer.read(cx).language_settings(cx);
18367 if settings.show_wrap_guides {
18368 match self.soft_wrap_mode(cx) {
18369 SoftWrap::Column(soft_wrap) => {
18370 wrap_guides.push((soft_wrap as usize, true));
18371 }
18372 SoftWrap::Bounded(soft_wrap) => {
18373 wrap_guides.push((soft_wrap as usize, true));
18374 }
18375 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18376 }
18377 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18378 }
18379
18380 wrap_guides
18381 }
18382
18383 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18384 let settings = self.buffer.read(cx).language_settings(cx);
18385 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18386 match mode {
18387 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18388 SoftWrap::None
18389 }
18390 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18391 language_settings::SoftWrap::PreferredLineLength => {
18392 SoftWrap::Column(settings.preferred_line_length)
18393 }
18394 language_settings::SoftWrap::Bounded => {
18395 SoftWrap::Bounded(settings.preferred_line_length)
18396 }
18397 }
18398 }
18399
18400 pub fn set_soft_wrap_mode(
18401 &mut self,
18402 mode: language_settings::SoftWrap,
18403
18404 cx: &mut Context<Self>,
18405 ) {
18406 self.soft_wrap_mode_override = Some(mode);
18407 cx.notify();
18408 }
18409
18410 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18411 self.hard_wrap = hard_wrap;
18412 cx.notify();
18413 }
18414
18415 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18416 self.text_style_refinement = Some(style);
18417 }
18418
18419 /// called by the Element so we know what style we were most recently rendered with.
18420 pub(crate) fn set_style(
18421 &mut self,
18422 style: EditorStyle,
18423 window: &mut Window,
18424 cx: &mut Context<Self>,
18425 ) {
18426 // We intentionally do not inform the display map about the minimap style
18427 // so that wrapping is not recalculated and stays consistent for the editor
18428 // and its linked minimap.
18429 if !self.mode.is_minimap() {
18430 let rem_size = window.rem_size();
18431 self.display_map.update(cx, |map, cx| {
18432 map.set_font(
18433 style.text.font(),
18434 style.text.font_size.to_pixels(rem_size),
18435 cx,
18436 )
18437 });
18438 }
18439 self.style = Some(style);
18440 }
18441
18442 pub fn style(&self) -> Option<&EditorStyle> {
18443 self.style.as_ref()
18444 }
18445
18446 // Called by the element. This method is not designed to be called outside of the editor
18447 // element's layout code because it does not notify when rewrapping is computed synchronously.
18448 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18449 self.display_map
18450 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18451 }
18452
18453 pub fn set_soft_wrap(&mut self) {
18454 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18455 }
18456
18457 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18458 if self.soft_wrap_mode_override.is_some() {
18459 self.soft_wrap_mode_override.take();
18460 } else {
18461 let soft_wrap = match self.soft_wrap_mode(cx) {
18462 SoftWrap::GitDiff => return,
18463 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18464 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18465 language_settings::SoftWrap::None
18466 }
18467 };
18468 self.soft_wrap_mode_override = Some(soft_wrap);
18469 }
18470 cx.notify();
18471 }
18472
18473 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18474 let Some(workspace) = self.workspace() else {
18475 return;
18476 };
18477 let fs = workspace.read(cx).app_state().fs.clone();
18478 let current_show = TabBarSettings::get_global(cx).show;
18479 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18480 setting.show = Some(!current_show);
18481 });
18482 }
18483
18484 pub fn toggle_indent_guides(
18485 &mut self,
18486 _: &ToggleIndentGuides,
18487 _: &mut Window,
18488 cx: &mut Context<Self>,
18489 ) {
18490 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18491 self.buffer
18492 .read(cx)
18493 .language_settings(cx)
18494 .indent_guides
18495 .enabled
18496 });
18497 self.show_indent_guides = Some(!currently_enabled);
18498 cx.notify();
18499 }
18500
18501 fn should_show_indent_guides(&self) -> Option<bool> {
18502 self.show_indent_guides
18503 }
18504
18505 pub fn toggle_line_numbers(
18506 &mut self,
18507 _: &ToggleLineNumbers,
18508 _: &mut Window,
18509 cx: &mut Context<Self>,
18510 ) {
18511 let mut editor_settings = EditorSettings::get_global(cx).clone();
18512 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18513 EditorSettings::override_global(editor_settings, cx);
18514 }
18515
18516 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18517 if let Some(show_line_numbers) = self.show_line_numbers {
18518 return show_line_numbers;
18519 }
18520 EditorSettings::get_global(cx).gutter.line_numbers
18521 }
18522
18523 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18524 self.use_relative_line_numbers
18525 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18526 }
18527
18528 pub fn toggle_relative_line_numbers(
18529 &mut self,
18530 _: &ToggleRelativeLineNumbers,
18531 _: &mut Window,
18532 cx: &mut Context<Self>,
18533 ) {
18534 let is_relative = self.should_use_relative_line_numbers(cx);
18535 self.set_relative_line_number(Some(!is_relative), cx)
18536 }
18537
18538 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18539 self.use_relative_line_numbers = is_relative;
18540 cx.notify();
18541 }
18542
18543 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18544 self.show_gutter = show_gutter;
18545 cx.notify();
18546 }
18547
18548 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18549 self.show_scrollbars = ScrollbarAxes {
18550 horizontal: show,
18551 vertical: show,
18552 };
18553 cx.notify();
18554 }
18555
18556 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18557 self.show_scrollbars.vertical = show;
18558 cx.notify();
18559 }
18560
18561 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18562 self.show_scrollbars.horizontal = show;
18563 cx.notify();
18564 }
18565
18566 pub fn set_minimap_visibility(
18567 &mut self,
18568 minimap_visibility: MinimapVisibility,
18569 window: &mut Window,
18570 cx: &mut Context<Self>,
18571 ) {
18572 if self.minimap_visibility != minimap_visibility {
18573 if minimap_visibility.visible() && self.minimap.is_none() {
18574 let minimap_settings = EditorSettings::get_global(cx).minimap;
18575 self.minimap =
18576 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18577 }
18578 self.minimap_visibility = minimap_visibility;
18579 cx.notify();
18580 }
18581 }
18582
18583 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18584 self.set_show_scrollbars(false, cx);
18585 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18586 }
18587
18588 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18589 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18590 }
18591
18592 /// Normally the text in full mode and auto height editors is padded on the
18593 /// left side by roughly half a character width for improved hit testing.
18594 ///
18595 /// Use this method to disable this for cases where this is not wanted (e.g.
18596 /// if you want to align the editor text with some other text above or below)
18597 /// or if you want to add this padding to single-line editors.
18598 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18599 self.offset_content = offset_content;
18600 cx.notify();
18601 }
18602
18603 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18604 self.show_line_numbers = Some(show_line_numbers);
18605 cx.notify();
18606 }
18607
18608 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18609 self.disable_expand_excerpt_buttons = true;
18610 cx.notify();
18611 }
18612
18613 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18614 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18615 cx.notify();
18616 }
18617
18618 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18619 self.show_code_actions = Some(show_code_actions);
18620 cx.notify();
18621 }
18622
18623 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18624 self.show_runnables = Some(show_runnables);
18625 cx.notify();
18626 }
18627
18628 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18629 self.show_breakpoints = Some(show_breakpoints);
18630 cx.notify();
18631 }
18632
18633 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18634 if self.display_map.read(cx).masked != masked {
18635 self.display_map.update(cx, |map, _| map.masked = masked);
18636 }
18637 cx.notify()
18638 }
18639
18640 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18641 self.show_wrap_guides = Some(show_wrap_guides);
18642 cx.notify();
18643 }
18644
18645 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18646 self.show_indent_guides = Some(show_indent_guides);
18647 cx.notify();
18648 }
18649
18650 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18651 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18652 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18653 && let Some(dir) = file.abs_path(cx).parent()
18654 {
18655 return Some(dir.to_owned());
18656 }
18657
18658 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18659 return Some(project_path.path.to_path_buf());
18660 }
18661 }
18662
18663 None
18664 }
18665
18666 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18667 self.active_excerpt(cx)?
18668 .1
18669 .read(cx)
18670 .file()
18671 .and_then(|f| f.as_local())
18672 }
18673
18674 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18675 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18676 let buffer = buffer.read(cx);
18677 if let Some(project_path) = buffer.project_path(cx) {
18678 let project = self.project()?.read(cx);
18679 project.absolute_path(&project_path, cx)
18680 } else {
18681 buffer
18682 .file()
18683 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18684 }
18685 })
18686 }
18687
18688 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18689 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18690 let project_path = buffer.read(cx).project_path(cx)?;
18691 let project = self.project()?.read(cx);
18692 let entry = project.entry_for_path(&project_path, cx)?;
18693 let path = entry.path.to_path_buf();
18694 Some(path)
18695 })
18696 }
18697
18698 pub fn reveal_in_finder(
18699 &mut self,
18700 _: &RevealInFileManager,
18701 _window: &mut Window,
18702 cx: &mut Context<Self>,
18703 ) {
18704 if let Some(target) = self.target_file(cx) {
18705 cx.reveal_path(&target.abs_path(cx));
18706 }
18707 }
18708
18709 pub fn copy_path(
18710 &mut self,
18711 _: &zed_actions::workspace::CopyPath,
18712 _window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) {
18715 if let Some(path) = self.target_file_abs_path(cx)
18716 && let Some(path) = path.to_str()
18717 {
18718 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18719 }
18720 }
18721
18722 pub fn copy_relative_path(
18723 &mut self,
18724 _: &zed_actions::workspace::CopyRelativePath,
18725 _window: &mut Window,
18726 cx: &mut Context<Self>,
18727 ) {
18728 if let Some(path) = self.target_file_path(cx)
18729 && let Some(path) = path.to_str()
18730 {
18731 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18732 }
18733 }
18734
18735 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18736 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18737 buffer.read(cx).project_path(cx)
18738 } else {
18739 None
18740 }
18741 }
18742
18743 // Returns true if the editor handled a go-to-line request
18744 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18745 maybe!({
18746 let breakpoint_store = self.breakpoint_store.as_ref()?;
18747
18748 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18749 else {
18750 self.clear_row_highlights::<ActiveDebugLine>();
18751 return None;
18752 };
18753
18754 let position = active_stack_frame.position;
18755 let buffer_id = position.buffer_id?;
18756 let snapshot = self
18757 .project
18758 .as_ref()?
18759 .read(cx)
18760 .buffer_for_id(buffer_id, cx)?
18761 .read(cx)
18762 .snapshot();
18763
18764 let mut handled = false;
18765 for (id, ExcerptRange { context, .. }) in
18766 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18767 {
18768 if context.start.cmp(&position, &snapshot).is_ge()
18769 || context.end.cmp(&position, &snapshot).is_lt()
18770 {
18771 continue;
18772 }
18773 let snapshot = self.buffer.read(cx).snapshot(cx);
18774 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18775
18776 handled = true;
18777 self.clear_row_highlights::<ActiveDebugLine>();
18778
18779 self.go_to_line::<ActiveDebugLine>(
18780 multibuffer_anchor,
18781 Some(cx.theme().colors().editor_debugger_active_line_background),
18782 window,
18783 cx,
18784 );
18785
18786 cx.notify();
18787 }
18788
18789 handled.then_some(())
18790 })
18791 .is_some()
18792 }
18793
18794 pub fn copy_file_name_without_extension(
18795 &mut self,
18796 _: &CopyFileNameWithoutExtension,
18797 _: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 if let Some(file) = self.target_file(cx)
18801 && let Some(file_stem) = file.path().file_stem()
18802 && let Some(name) = file_stem.to_str()
18803 {
18804 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18805 }
18806 }
18807
18808 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18809 if let Some(file) = self.target_file(cx)
18810 && let Some(file_name) = file.path().file_name()
18811 && let Some(name) = file_name.to_str()
18812 {
18813 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18814 }
18815 }
18816
18817 pub fn toggle_git_blame(
18818 &mut self,
18819 _: &::git::Blame,
18820 window: &mut Window,
18821 cx: &mut Context<Self>,
18822 ) {
18823 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18824
18825 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18826 self.start_git_blame(true, window, cx);
18827 }
18828
18829 cx.notify();
18830 }
18831
18832 pub fn toggle_git_blame_inline(
18833 &mut self,
18834 _: &ToggleGitBlameInline,
18835 window: &mut Window,
18836 cx: &mut Context<Self>,
18837 ) {
18838 self.toggle_git_blame_inline_internal(true, window, cx);
18839 cx.notify();
18840 }
18841
18842 pub fn open_git_blame_commit(
18843 &mut self,
18844 _: &OpenGitBlameCommit,
18845 window: &mut Window,
18846 cx: &mut Context<Self>,
18847 ) {
18848 self.open_git_blame_commit_internal(window, cx);
18849 }
18850
18851 fn open_git_blame_commit_internal(
18852 &mut self,
18853 window: &mut Window,
18854 cx: &mut Context<Self>,
18855 ) -> Option<()> {
18856 let blame = self.blame.as_ref()?;
18857 let snapshot = self.snapshot(window, cx);
18858 let cursor = self.selections.newest::<Point>(cx).head();
18859 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18860 let blame_entry = blame
18861 .update(cx, |blame, cx| {
18862 blame
18863 .blame_for_rows(
18864 &[RowInfo {
18865 buffer_id: Some(buffer.remote_id()),
18866 buffer_row: Some(point.row),
18867 ..Default::default()
18868 }],
18869 cx,
18870 )
18871 .next()
18872 })
18873 .flatten()?;
18874 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18875 let repo = blame.read(cx).repository(cx)?;
18876 let workspace = self.workspace()?.downgrade();
18877 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18878 None
18879 }
18880
18881 pub fn git_blame_inline_enabled(&self) -> bool {
18882 self.git_blame_inline_enabled
18883 }
18884
18885 pub fn toggle_selection_menu(
18886 &mut self,
18887 _: &ToggleSelectionMenu,
18888 _: &mut Window,
18889 cx: &mut Context<Self>,
18890 ) {
18891 self.show_selection_menu = self
18892 .show_selection_menu
18893 .map(|show_selections_menu| !show_selections_menu)
18894 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18895
18896 cx.notify();
18897 }
18898
18899 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18900 self.show_selection_menu
18901 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18902 }
18903
18904 fn start_git_blame(
18905 &mut self,
18906 user_triggered: bool,
18907 window: &mut Window,
18908 cx: &mut Context<Self>,
18909 ) {
18910 if let Some(project) = self.project() {
18911 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18912 return;
18913 };
18914
18915 if buffer.read(cx).file().is_none() {
18916 return;
18917 }
18918
18919 let focused = self.focus_handle(cx).contains_focused(window, cx);
18920
18921 let project = project.clone();
18922 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18923 self.blame_subscription =
18924 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18925 self.blame = Some(blame);
18926 }
18927 }
18928
18929 fn toggle_git_blame_inline_internal(
18930 &mut self,
18931 user_triggered: bool,
18932 window: &mut Window,
18933 cx: &mut Context<Self>,
18934 ) {
18935 if self.git_blame_inline_enabled {
18936 self.git_blame_inline_enabled = false;
18937 self.show_git_blame_inline = false;
18938 self.show_git_blame_inline_delay_task.take();
18939 } else {
18940 self.git_blame_inline_enabled = true;
18941 self.start_git_blame_inline(user_triggered, window, cx);
18942 }
18943
18944 cx.notify();
18945 }
18946
18947 fn start_git_blame_inline(
18948 &mut self,
18949 user_triggered: bool,
18950 window: &mut Window,
18951 cx: &mut Context<Self>,
18952 ) {
18953 self.start_git_blame(user_triggered, window, cx);
18954
18955 if ProjectSettings::get_global(cx)
18956 .git
18957 .inline_blame_delay()
18958 .is_some()
18959 {
18960 self.start_inline_blame_timer(window, cx);
18961 } else {
18962 self.show_git_blame_inline = true
18963 }
18964 }
18965
18966 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18967 self.blame.as_ref()
18968 }
18969
18970 pub fn show_git_blame_gutter(&self) -> bool {
18971 self.show_git_blame_gutter
18972 }
18973
18974 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18975 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18976 }
18977
18978 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18979 self.show_git_blame_inline
18980 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18981 && !self.newest_selection_head_on_empty_line(cx)
18982 && self.has_blame_entries(cx)
18983 }
18984
18985 fn has_blame_entries(&self, cx: &App) -> bool {
18986 self.blame()
18987 .is_some_and(|blame| blame.read(cx).has_generated_entries())
18988 }
18989
18990 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18991 let cursor_anchor = self.selections.newest_anchor().head();
18992
18993 let snapshot = self.buffer.read(cx).snapshot(cx);
18994 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18995
18996 snapshot.line_len(buffer_row) == 0
18997 }
18998
18999 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19000 let buffer_and_selection = maybe!({
19001 let selection = self.selections.newest::<Point>(cx);
19002 let selection_range = selection.range();
19003
19004 let multi_buffer = self.buffer().read(cx);
19005 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19006 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19007
19008 let (buffer, range, _) = if selection.reversed {
19009 buffer_ranges.first()
19010 } else {
19011 buffer_ranges.last()
19012 }?;
19013
19014 let selection = text::ToPoint::to_point(&range.start, buffer).row
19015 ..text::ToPoint::to_point(&range.end, buffer).row;
19016 Some((
19017 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
19018 selection,
19019 ))
19020 });
19021
19022 let Some((buffer, selection)) = buffer_and_selection else {
19023 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19024 };
19025
19026 let Some(project) = self.project() else {
19027 return Task::ready(Err(anyhow!("editor does not have project")));
19028 };
19029
19030 project.update(cx, |project, cx| {
19031 project.get_permalink_to_line(&buffer, selection, cx)
19032 })
19033 }
19034
19035 pub fn copy_permalink_to_line(
19036 &mut self,
19037 _: &CopyPermalinkToLine,
19038 window: &mut Window,
19039 cx: &mut Context<Self>,
19040 ) {
19041 let permalink_task = self.get_permalink_to_line(cx);
19042 let workspace = self.workspace();
19043
19044 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19045 Ok(permalink) => {
19046 cx.update(|_, cx| {
19047 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19048 })
19049 .ok();
19050 }
19051 Err(err) => {
19052 let message = format!("Failed to copy permalink: {err}");
19053
19054 anyhow::Result::<()>::Err(err).log_err();
19055
19056 if let Some(workspace) = workspace {
19057 workspace
19058 .update_in(cx, |workspace, _, cx| {
19059 struct CopyPermalinkToLine;
19060
19061 workspace.show_toast(
19062 Toast::new(
19063 NotificationId::unique::<CopyPermalinkToLine>(),
19064 message,
19065 ),
19066 cx,
19067 )
19068 })
19069 .ok();
19070 }
19071 }
19072 })
19073 .detach();
19074 }
19075
19076 pub fn copy_file_location(
19077 &mut self,
19078 _: &CopyFileLocation,
19079 _: &mut Window,
19080 cx: &mut Context<Self>,
19081 ) {
19082 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19083 if let Some(file) = self.target_file(cx)
19084 && let Some(path) = file.path().to_str()
19085 {
19086 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19087 }
19088 }
19089
19090 pub fn open_permalink_to_line(
19091 &mut self,
19092 _: &OpenPermalinkToLine,
19093 window: &mut Window,
19094 cx: &mut Context<Self>,
19095 ) {
19096 let permalink_task = self.get_permalink_to_line(cx);
19097 let workspace = self.workspace();
19098
19099 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19100 Ok(permalink) => {
19101 cx.update(|_, cx| {
19102 cx.open_url(permalink.as_ref());
19103 })
19104 .ok();
19105 }
19106 Err(err) => {
19107 let message = format!("Failed to open permalink: {err}");
19108
19109 anyhow::Result::<()>::Err(err).log_err();
19110
19111 if let Some(workspace) = workspace {
19112 workspace
19113 .update(cx, |workspace, cx| {
19114 struct OpenPermalinkToLine;
19115
19116 workspace.show_toast(
19117 Toast::new(
19118 NotificationId::unique::<OpenPermalinkToLine>(),
19119 message,
19120 ),
19121 cx,
19122 )
19123 })
19124 .ok();
19125 }
19126 }
19127 })
19128 .detach();
19129 }
19130
19131 pub fn insert_uuid_v4(
19132 &mut self,
19133 _: &InsertUuidV4,
19134 window: &mut Window,
19135 cx: &mut Context<Self>,
19136 ) {
19137 self.insert_uuid(UuidVersion::V4, window, cx);
19138 }
19139
19140 pub fn insert_uuid_v7(
19141 &mut self,
19142 _: &InsertUuidV7,
19143 window: &mut Window,
19144 cx: &mut Context<Self>,
19145 ) {
19146 self.insert_uuid(UuidVersion::V7, window, cx);
19147 }
19148
19149 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19151 self.transact(window, cx, |this, window, cx| {
19152 let edits = this
19153 .selections
19154 .all::<Point>(cx)
19155 .into_iter()
19156 .map(|selection| {
19157 let uuid = match version {
19158 UuidVersion::V4 => uuid::Uuid::new_v4(),
19159 UuidVersion::V7 => uuid::Uuid::now_v7(),
19160 };
19161
19162 (selection.range(), uuid.to_string())
19163 });
19164 this.edit(edits, cx);
19165 this.refresh_edit_prediction(true, false, window, cx);
19166 });
19167 }
19168
19169 pub fn open_selections_in_multibuffer(
19170 &mut self,
19171 _: &OpenSelectionsInMultibuffer,
19172 window: &mut Window,
19173 cx: &mut Context<Self>,
19174 ) {
19175 let multibuffer = self.buffer.read(cx);
19176
19177 let Some(buffer) = multibuffer.as_singleton() else {
19178 return;
19179 };
19180
19181 let Some(workspace) = self.workspace() else {
19182 return;
19183 };
19184
19185 let title = multibuffer.title(cx).to_string();
19186
19187 let locations = self
19188 .selections
19189 .all_anchors(cx)
19190 .iter()
19191 .map(|selection| Location {
19192 buffer: buffer.clone(),
19193 range: selection.start.text_anchor..selection.end.text_anchor,
19194 })
19195 .collect::<Vec<_>>();
19196
19197 cx.spawn_in(window, async move |_, cx| {
19198 workspace.update_in(cx, |workspace, window, cx| {
19199 Self::open_locations_in_multibuffer(
19200 workspace,
19201 locations,
19202 format!("Selections for '{title}'"),
19203 false,
19204 MultibufferSelectionMode::All,
19205 window,
19206 cx,
19207 );
19208 })
19209 })
19210 .detach();
19211 }
19212
19213 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19214 /// last highlight added will be used.
19215 ///
19216 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19217 pub fn highlight_rows<T: 'static>(
19218 &mut self,
19219 range: Range<Anchor>,
19220 color: Hsla,
19221 options: RowHighlightOptions,
19222 cx: &mut Context<Self>,
19223 ) {
19224 let snapshot = self.buffer().read(cx).snapshot(cx);
19225 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19226 let ix = row_highlights.binary_search_by(|highlight| {
19227 Ordering::Equal
19228 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19229 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19230 });
19231
19232 if let Err(mut ix) = ix {
19233 let index = post_inc(&mut self.highlight_order);
19234
19235 // If this range intersects with the preceding highlight, then merge it with
19236 // the preceding highlight. Otherwise insert a new highlight.
19237 let mut merged = false;
19238 if ix > 0 {
19239 let prev_highlight = &mut row_highlights[ix - 1];
19240 if prev_highlight
19241 .range
19242 .end
19243 .cmp(&range.start, &snapshot)
19244 .is_ge()
19245 {
19246 ix -= 1;
19247 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19248 prev_highlight.range.end = range.end;
19249 }
19250 merged = true;
19251 prev_highlight.index = index;
19252 prev_highlight.color = color;
19253 prev_highlight.options = options;
19254 }
19255 }
19256
19257 if !merged {
19258 row_highlights.insert(
19259 ix,
19260 RowHighlight {
19261 range: range.clone(),
19262 index,
19263 color,
19264 options,
19265 type_id: TypeId::of::<T>(),
19266 },
19267 );
19268 }
19269
19270 // If any of the following highlights intersect with this one, merge them.
19271 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19272 let highlight = &row_highlights[ix];
19273 if next_highlight
19274 .range
19275 .start
19276 .cmp(&highlight.range.end, &snapshot)
19277 .is_le()
19278 {
19279 if next_highlight
19280 .range
19281 .end
19282 .cmp(&highlight.range.end, &snapshot)
19283 .is_gt()
19284 {
19285 row_highlights[ix].range.end = next_highlight.range.end;
19286 }
19287 row_highlights.remove(ix + 1);
19288 } else {
19289 break;
19290 }
19291 }
19292 }
19293 }
19294
19295 /// Remove any highlighted row ranges of the given type that intersect the
19296 /// given ranges.
19297 pub fn remove_highlighted_rows<T: 'static>(
19298 &mut self,
19299 ranges_to_remove: Vec<Range<Anchor>>,
19300 cx: &mut Context<Self>,
19301 ) {
19302 let snapshot = self.buffer().read(cx).snapshot(cx);
19303 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19304 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19305 row_highlights.retain(|highlight| {
19306 while let Some(range_to_remove) = ranges_to_remove.peek() {
19307 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19308 Ordering::Less | Ordering::Equal => {
19309 ranges_to_remove.next();
19310 }
19311 Ordering::Greater => {
19312 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19313 Ordering::Less | Ordering::Equal => {
19314 return false;
19315 }
19316 Ordering::Greater => break,
19317 }
19318 }
19319 }
19320 }
19321
19322 true
19323 })
19324 }
19325
19326 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19327 pub fn clear_row_highlights<T: 'static>(&mut self) {
19328 self.highlighted_rows.remove(&TypeId::of::<T>());
19329 }
19330
19331 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19332 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19333 self.highlighted_rows
19334 .get(&TypeId::of::<T>())
19335 .map_or(&[] as &[_], |vec| vec.as_slice())
19336 .iter()
19337 .map(|highlight| (highlight.range.clone(), highlight.color))
19338 }
19339
19340 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19341 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19342 /// Allows to ignore certain kinds of highlights.
19343 pub fn highlighted_display_rows(
19344 &self,
19345 window: &mut Window,
19346 cx: &mut App,
19347 ) -> BTreeMap<DisplayRow, LineHighlight> {
19348 let snapshot = self.snapshot(window, cx);
19349 let mut used_highlight_orders = HashMap::default();
19350 self.highlighted_rows
19351 .iter()
19352 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19353 .fold(
19354 BTreeMap::<DisplayRow, LineHighlight>::new(),
19355 |mut unique_rows, highlight| {
19356 let start = highlight.range.start.to_display_point(&snapshot);
19357 let end = highlight.range.end.to_display_point(&snapshot);
19358 let start_row = start.row().0;
19359 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19360 && end.column() == 0
19361 {
19362 end.row().0.saturating_sub(1)
19363 } else {
19364 end.row().0
19365 };
19366 for row in start_row..=end_row {
19367 let used_index =
19368 used_highlight_orders.entry(row).or_insert(highlight.index);
19369 if highlight.index >= *used_index {
19370 *used_index = highlight.index;
19371 unique_rows.insert(
19372 DisplayRow(row),
19373 LineHighlight {
19374 include_gutter: highlight.options.include_gutter,
19375 border: None,
19376 background: highlight.color.into(),
19377 type_id: Some(highlight.type_id),
19378 },
19379 );
19380 }
19381 }
19382 unique_rows
19383 },
19384 )
19385 }
19386
19387 pub fn highlighted_display_row_for_autoscroll(
19388 &self,
19389 snapshot: &DisplaySnapshot,
19390 ) -> Option<DisplayRow> {
19391 self.highlighted_rows
19392 .values()
19393 .flat_map(|highlighted_rows| highlighted_rows.iter())
19394 .filter_map(|highlight| {
19395 if highlight.options.autoscroll {
19396 Some(highlight.range.start.to_display_point(snapshot).row())
19397 } else {
19398 None
19399 }
19400 })
19401 .min()
19402 }
19403
19404 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19405 self.highlight_background::<SearchWithinRange>(
19406 ranges,
19407 |colors| colors.colors().editor_document_highlight_read_background,
19408 cx,
19409 )
19410 }
19411
19412 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19413 self.breadcrumb_header = Some(new_header);
19414 }
19415
19416 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19417 self.clear_background_highlights::<SearchWithinRange>(cx);
19418 }
19419
19420 pub fn highlight_background<T: 'static>(
19421 &mut self,
19422 ranges: &[Range<Anchor>],
19423 color_fetcher: fn(&Theme) -> Hsla,
19424 cx: &mut Context<Self>,
19425 ) {
19426 self.background_highlights.insert(
19427 HighlightKey::Type(TypeId::of::<T>()),
19428 (color_fetcher, Arc::from(ranges)),
19429 );
19430 self.scrollbar_marker_state.dirty = true;
19431 cx.notify();
19432 }
19433
19434 pub fn highlight_background_key<T: 'static>(
19435 &mut self,
19436 key: usize,
19437 ranges: &[Range<Anchor>],
19438 color_fetcher: fn(&Theme) -> Hsla,
19439 cx: &mut Context<Self>,
19440 ) {
19441 self.background_highlights.insert(
19442 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19443 (color_fetcher, Arc::from(ranges)),
19444 );
19445 self.scrollbar_marker_state.dirty = true;
19446 cx.notify();
19447 }
19448
19449 pub fn clear_background_highlights<T: 'static>(
19450 &mut self,
19451 cx: &mut Context<Self>,
19452 ) -> Option<BackgroundHighlight> {
19453 let text_highlights = self
19454 .background_highlights
19455 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19456 if !text_highlights.1.is_empty() {
19457 self.scrollbar_marker_state.dirty = true;
19458 cx.notify();
19459 }
19460 Some(text_highlights)
19461 }
19462
19463 pub fn highlight_gutter<T: 'static>(
19464 &mut self,
19465 ranges: impl Into<Vec<Range<Anchor>>>,
19466 color_fetcher: fn(&App) -> Hsla,
19467 cx: &mut Context<Self>,
19468 ) {
19469 self.gutter_highlights
19470 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19471 cx.notify();
19472 }
19473
19474 pub fn clear_gutter_highlights<T: 'static>(
19475 &mut self,
19476 cx: &mut Context<Self>,
19477 ) -> Option<GutterHighlight> {
19478 cx.notify();
19479 self.gutter_highlights.remove(&TypeId::of::<T>())
19480 }
19481
19482 pub fn insert_gutter_highlight<T: 'static>(
19483 &mut self,
19484 range: Range<Anchor>,
19485 color_fetcher: fn(&App) -> Hsla,
19486 cx: &mut Context<Self>,
19487 ) {
19488 let snapshot = self.buffer().read(cx).snapshot(cx);
19489 let mut highlights = self
19490 .gutter_highlights
19491 .remove(&TypeId::of::<T>())
19492 .map(|(_, highlights)| highlights)
19493 .unwrap_or_default();
19494 let ix = highlights.binary_search_by(|highlight| {
19495 Ordering::Equal
19496 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19497 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19498 });
19499 if let Err(ix) = ix {
19500 highlights.insert(ix, range);
19501 }
19502 self.gutter_highlights
19503 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19504 }
19505
19506 pub fn remove_gutter_highlights<T: 'static>(
19507 &mut self,
19508 ranges_to_remove: Vec<Range<Anchor>>,
19509 cx: &mut Context<Self>,
19510 ) {
19511 let snapshot = self.buffer().read(cx).snapshot(cx);
19512 let Some((color_fetcher, mut gutter_highlights)) =
19513 self.gutter_highlights.remove(&TypeId::of::<T>())
19514 else {
19515 return;
19516 };
19517 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19518 gutter_highlights.retain(|highlight| {
19519 while let Some(range_to_remove) = ranges_to_remove.peek() {
19520 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19521 Ordering::Less | Ordering::Equal => {
19522 ranges_to_remove.next();
19523 }
19524 Ordering::Greater => {
19525 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19526 Ordering::Less | Ordering::Equal => {
19527 return false;
19528 }
19529 Ordering::Greater => break,
19530 }
19531 }
19532 }
19533 }
19534
19535 true
19536 });
19537 self.gutter_highlights
19538 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19539 }
19540
19541 #[cfg(feature = "test-support")]
19542 pub fn all_text_highlights(
19543 &self,
19544 window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19547 let snapshot = self.snapshot(window, cx);
19548 self.display_map.update(cx, |display_map, _| {
19549 display_map
19550 .all_text_highlights()
19551 .map(|highlight| {
19552 let (style, ranges) = highlight.as_ref();
19553 (
19554 *style,
19555 ranges
19556 .iter()
19557 .map(|range| range.clone().to_display_points(&snapshot))
19558 .collect(),
19559 )
19560 })
19561 .collect()
19562 })
19563 }
19564
19565 #[cfg(feature = "test-support")]
19566 pub fn all_text_background_highlights(
19567 &self,
19568 window: &mut Window,
19569 cx: &mut Context<Self>,
19570 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19571 let snapshot = self.snapshot(window, cx);
19572 let buffer = &snapshot.buffer_snapshot;
19573 let start = buffer.anchor_before(0);
19574 let end = buffer.anchor_after(buffer.len());
19575 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19576 }
19577
19578 #[cfg(feature = "test-support")]
19579 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19580 let snapshot = self.buffer().read(cx).snapshot(cx);
19581
19582 let highlights = self
19583 .background_highlights
19584 .get(&HighlightKey::Type(TypeId::of::<
19585 items::BufferSearchHighlights,
19586 >()));
19587
19588 if let Some((_color, ranges)) = highlights {
19589 ranges
19590 .iter()
19591 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19592 .collect_vec()
19593 } else {
19594 vec![]
19595 }
19596 }
19597
19598 fn document_highlights_for_position<'a>(
19599 &'a self,
19600 position: Anchor,
19601 buffer: &'a MultiBufferSnapshot,
19602 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19603 let read_highlights = self
19604 .background_highlights
19605 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19606 .map(|h| &h.1);
19607 let write_highlights = self
19608 .background_highlights
19609 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19610 .map(|h| &h.1);
19611 let left_position = position.bias_left(buffer);
19612 let right_position = position.bias_right(buffer);
19613 read_highlights
19614 .into_iter()
19615 .chain(write_highlights)
19616 .flat_map(move |ranges| {
19617 let start_ix = match ranges.binary_search_by(|probe| {
19618 let cmp = probe.end.cmp(&left_position, buffer);
19619 if cmp.is_ge() {
19620 Ordering::Greater
19621 } else {
19622 Ordering::Less
19623 }
19624 }) {
19625 Ok(i) | Err(i) => i,
19626 };
19627
19628 ranges[start_ix..]
19629 .iter()
19630 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19631 })
19632 }
19633
19634 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19635 self.background_highlights
19636 .get(&HighlightKey::Type(TypeId::of::<T>()))
19637 .is_some_and(|(_, highlights)| !highlights.is_empty())
19638 }
19639
19640 pub fn background_highlights_in_range(
19641 &self,
19642 search_range: Range<Anchor>,
19643 display_snapshot: &DisplaySnapshot,
19644 theme: &Theme,
19645 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19646 let mut results = Vec::new();
19647 for (color_fetcher, ranges) in self.background_highlights.values() {
19648 let color = color_fetcher(theme);
19649 let start_ix = match ranges.binary_search_by(|probe| {
19650 let cmp = probe
19651 .end
19652 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19653 if cmp.is_gt() {
19654 Ordering::Greater
19655 } else {
19656 Ordering::Less
19657 }
19658 }) {
19659 Ok(i) | Err(i) => i,
19660 };
19661 for range in &ranges[start_ix..] {
19662 if range
19663 .start
19664 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19665 .is_ge()
19666 {
19667 break;
19668 }
19669
19670 let start = range.start.to_display_point(display_snapshot);
19671 let end = range.end.to_display_point(display_snapshot);
19672 results.push((start..end, color))
19673 }
19674 }
19675 results
19676 }
19677
19678 pub fn background_highlight_row_ranges<T: 'static>(
19679 &self,
19680 search_range: Range<Anchor>,
19681 display_snapshot: &DisplaySnapshot,
19682 count: usize,
19683 ) -> Vec<RangeInclusive<DisplayPoint>> {
19684 let mut results = Vec::new();
19685 let Some((_, ranges)) = self
19686 .background_highlights
19687 .get(&HighlightKey::Type(TypeId::of::<T>()))
19688 else {
19689 return vec![];
19690 };
19691
19692 let start_ix = match ranges.binary_search_by(|probe| {
19693 let cmp = probe
19694 .end
19695 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19696 if cmp.is_gt() {
19697 Ordering::Greater
19698 } else {
19699 Ordering::Less
19700 }
19701 }) {
19702 Ok(i) | Err(i) => i,
19703 };
19704 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19705 if let (Some(start_display), Some(end_display)) = (start, end) {
19706 results.push(
19707 start_display.to_display_point(display_snapshot)
19708 ..=end_display.to_display_point(display_snapshot),
19709 );
19710 }
19711 };
19712 let mut start_row: Option<Point> = None;
19713 let mut end_row: Option<Point> = None;
19714 if ranges.len() > count {
19715 return Vec::new();
19716 }
19717 for range in &ranges[start_ix..] {
19718 if range
19719 .start
19720 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19721 .is_ge()
19722 {
19723 break;
19724 }
19725 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19726 if let Some(current_row) = &end_row
19727 && end.row == current_row.row
19728 {
19729 continue;
19730 }
19731 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19732 if start_row.is_none() {
19733 assert_eq!(end_row, None);
19734 start_row = Some(start);
19735 end_row = Some(end);
19736 continue;
19737 }
19738 if let Some(current_end) = end_row.as_mut() {
19739 if start.row > current_end.row + 1 {
19740 push_region(start_row, end_row);
19741 start_row = Some(start);
19742 end_row = Some(end);
19743 } else {
19744 // Merge two hunks.
19745 *current_end = end;
19746 }
19747 } else {
19748 unreachable!();
19749 }
19750 }
19751 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19752 push_region(start_row, end_row);
19753 results
19754 }
19755
19756 pub fn gutter_highlights_in_range(
19757 &self,
19758 search_range: Range<Anchor>,
19759 display_snapshot: &DisplaySnapshot,
19760 cx: &App,
19761 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19762 let mut results = Vec::new();
19763 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19764 let color = color_fetcher(cx);
19765 let start_ix = match ranges.binary_search_by(|probe| {
19766 let cmp = probe
19767 .end
19768 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19769 if cmp.is_gt() {
19770 Ordering::Greater
19771 } else {
19772 Ordering::Less
19773 }
19774 }) {
19775 Ok(i) | Err(i) => i,
19776 };
19777 for range in &ranges[start_ix..] {
19778 if range
19779 .start
19780 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19781 .is_ge()
19782 {
19783 break;
19784 }
19785
19786 let start = range.start.to_display_point(display_snapshot);
19787 let end = range.end.to_display_point(display_snapshot);
19788 results.push((start..end, color))
19789 }
19790 }
19791 results
19792 }
19793
19794 /// Get the text ranges corresponding to the redaction query
19795 pub fn redacted_ranges(
19796 &self,
19797 search_range: Range<Anchor>,
19798 display_snapshot: &DisplaySnapshot,
19799 cx: &App,
19800 ) -> Vec<Range<DisplayPoint>> {
19801 display_snapshot
19802 .buffer_snapshot
19803 .redacted_ranges(search_range, |file| {
19804 if let Some(file) = file {
19805 file.is_private()
19806 && EditorSettings::get(
19807 Some(SettingsLocation {
19808 worktree_id: file.worktree_id(cx),
19809 path: file.path().as_ref(),
19810 }),
19811 cx,
19812 )
19813 .redact_private_values
19814 } else {
19815 false
19816 }
19817 })
19818 .map(|range| {
19819 range.start.to_display_point(display_snapshot)
19820 ..range.end.to_display_point(display_snapshot)
19821 })
19822 .collect()
19823 }
19824
19825 pub fn highlight_text_key<T: 'static>(
19826 &mut self,
19827 key: usize,
19828 ranges: Vec<Range<Anchor>>,
19829 style: HighlightStyle,
19830 cx: &mut Context<Self>,
19831 ) {
19832 self.display_map.update(cx, |map, _| {
19833 map.highlight_text(
19834 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19835 ranges,
19836 style,
19837 );
19838 });
19839 cx.notify();
19840 }
19841
19842 pub fn highlight_text<T: 'static>(
19843 &mut self,
19844 ranges: Vec<Range<Anchor>>,
19845 style: HighlightStyle,
19846 cx: &mut Context<Self>,
19847 ) {
19848 self.display_map.update(cx, |map, _| {
19849 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19850 });
19851 cx.notify();
19852 }
19853
19854 pub(crate) fn highlight_inlays<T: 'static>(
19855 &mut self,
19856 highlights: Vec<InlayHighlight>,
19857 style: HighlightStyle,
19858 cx: &mut Context<Self>,
19859 ) {
19860 self.display_map.update(cx, |map, _| {
19861 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19862 });
19863 cx.notify();
19864 }
19865
19866 pub fn text_highlights<'a, T: 'static>(
19867 &'a self,
19868 cx: &'a App,
19869 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19870 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19871 }
19872
19873 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19874 let cleared = self
19875 .display_map
19876 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19877 if cleared {
19878 cx.notify();
19879 }
19880 }
19881
19882 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19883 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19884 && self.focus_handle.is_focused(window)
19885 }
19886
19887 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19888 self.show_cursor_when_unfocused = is_enabled;
19889 cx.notify();
19890 }
19891
19892 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19893 cx.notify();
19894 }
19895
19896 fn on_debug_session_event(
19897 &mut self,
19898 _session: Entity<Session>,
19899 event: &SessionEvent,
19900 cx: &mut Context<Self>,
19901 ) {
19902 if let SessionEvent::InvalidateInlineValue = event {
19903 self.refresh_inline_values(cx);
19904 }
19905 }
19906
19907 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19908 let Some(project) = self.project.clone() else {
19909 return;
19910 };
19911
19912 if !self.inline_value_cache.enabled {
19913 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19914 self.splice_inlays(&inlays, Vec::new(), cx);
19915 return;
19916 }
19917
19918 let current_execution_position = self
19919 .highlighted_rows
19920 .get(&TypeId::of::<ActiveDebugLine>())
19921 .and_then(|lines| lines.last().map(|line| line.range.end));
19922
19923 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19924 let inline_values = editor
19925 .update(cx, |editor, cx| {
19926 let Some(current_execution_position) = current_execution_position else {
19927 return Some(Task::ready(Ok(Vec::new())));
19928 };
19929
19930 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19931 let snapshot = buffer.snapshot(cx);
19932
19933 let excerpt = snapshot.excerpt_containing(
19934 current_execution_position..current_execution_position,
19935 )?;
19936
19937 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19938 })?;
19939
19940 let range =
19941 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19942
19943 project.inline_values(buffer, range, cx)
19944 })
19945 .ok()
19946 .flatten()?
19947 .await
19948 .context("refreshing debugger inlays")
19949 .log_err()?;
19950
19951 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19952
19953 for (buffer_id, inline_value) in inline_values
19954 .into_iter()
19955 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19956 {
19957 buffer_inline_values
19958 .entry(buffer_id)
19959 .or_default()
19960 .push(inline_value);
19961 }
19962
19963 editor
19964 .update(cx, |editor, cx| {
19965 let snapshot = editor.buffer.read(cx).snapshot(cx);
19966 let mut new_inlays = Vec::default();
19967
19968 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19969 let buffer_id = buffer_snapshot.remote_id();
19970 buffer_inline_values
19971 .get(&buffer_id)
19972 .into_iter()
19973 .flatten()
19974 .for_each(|hint| {
19975 let inlay = Inlay::debugger(
19976 post_inc(&mut editor.next_inlay_id),
19977 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19978 hint.text(),
19979 );
19980 if !inlay.text.chars().contains(&'\n') {
19981 new_inlays.push(inlay);
19982 }
19983 });
19984 }
19985
19986 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19987 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19988
19989 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19990 })
19991 .ok()?;
19992 Some(())
19993 });
19994 }
19995
19996 fn on_buffer_event(
19997 &mut self,
19998 multibuffer: &Entity<MultiBuffer>,
19999 event: &multi_buffer::Event,
20000 window: &mut Window,
20001 cx: &mut Context<Self>,
20002 ) {
20003 match event {
20004 multi_buffer::Event::Edited {
20005 singleton_buffer_edited,
20006 edited_buffer,
20007 } => {
20008 self.scrollbar_marker_state.dirty = true;
20009 self.active_indent_guides_state.dirty = true;
20010 self.refresh_active_diagnostics(cx);
20011 self.refresh_code_actions(window, cx);
20012 self.refresh_selected_text_highlights(true, window, cx);
20013 self.refresh_single_line_folds(window, cx);
20014 refresh_matching_bracket_highlights(self, window, cx);
20015 if self.has_active_edit_prediction() {
20016 self.update_visible_edit_prediction(window, cx);
20017 }
20018 if let Some(project) = self.project.as_ref()
20019 && let Some(edited_buffer) = edited_buffer
20020 {
20021 project.update(cx, |project, cx| {
20022 self.registered_buffers
20023 .entry(edited_buffer.read(cx).remote_id())
20024 .or_insert_with(|| {
20025 project.register_buffer_with_language_servers(edited_buffer, cx)
20026 });
20027 });
20028 }
20029 cx.emit(EditorEvent::BufferEdited);
20030 cx.emit(SearchEvent::MatchesInvalidated);
20031
20032 if let Some(buffer) = edited_buffer {
20033 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20034 }
20035
20036 if *singleton_buffer_edited {
20037 if let Some(buffer) = edited_buffer
20038 && buffer.read(cx).file().is_none()
20039 {
20040 cx.emit(EditorEvent::TitleChanged);
20041 }
20042 if let Some(project) = &self.project {
20043 #[allow(clippy::mutable_key_type)]
20044 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20045 multibuffer
20046 .all_buffers()
20047 .into_iter()
20048 .filter_map(|buffer| {
20049 buffer.update(cx, |buffer, cx| {
20050 let language = buffer.language()?;
20051 let should_discard = project.update(cx, |project, cx| {
20052 project.is_local()
20053 && !project.has_language_servers_for(buffer, cx)
20054 });
20055 should_discard.not().then_some(language.clone())
20056 })
20057 })
20058 .collect::<HashSet<_>>()
20059 });
20060 if !languages_affected.is_empty() {
20061 self.refresh_inlay_hints(
20062 InlayHintRefreshReason::BufferEdited(languages_affected),
20063 cx,
20064 );
20065 }
20066 }
20067 }
20068
20069 let Some(project) = &self.project else { return };
20070 let (telemetry, is_via_ssh) = {
20071 let project = project.read(cx);
20072 let telemetry = project.client().telemetry().clone();
20073 let is_via_ssh = project.is_via_ssh();
20074 (telemetry, is_via_ssh)
20075 };
20076 refresh_linked_ranges(self, window, cx);
20077 telemetry.log_edit_event("editor", is_via_ssh);
20078 }
20079 multi_buffer::Event::ExcerptsAdded {
20080 buffer,
20081 predecessor,
20082 excerpts,
20083 } => {
20084 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20085 let buffer_id = buffer.read(cx).remote_id();
20086 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20087 && let Some(project) = &self.project
20088 {
20089 update_uncommitted_diff_for_buffer(
20090 cx.entity(),
20091 project,
20092 [buffer.clone()],
20093 self.buffer.clone(),
20094 cx,
20095 )
20096 .detach();
20097 }
20098 self.update_lsp_data(false, Some(buffer_id), window, cx);
20099 cx.emit(EditorEvent::ExcerptsAdded {
20100 buffer: buffer.clone(),
20101 predecessor: *predecessor,
20102 excerpts: excerpts.clone(),
20103 });
20104 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20105 }
20106 multi_buffer::Event::ExcerptsRemoved {
20107 ids,
20108 removed_buffer_ids,
20109 } => {
20110 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20111 let buffer = self.buffer.read(cx);
20112 self.registered_buffers
20113 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20114 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20115 cx.emit(EditorEvent::ExcerptsRemoved {
20116 ids: ids.clone(),
20117 removed_buffer_ids: removed_buffer_ids.clone(),
20118 });
20119 }
20120 multi_buffer::Event::ExcerptsEdited {
20121 excerpt_ids,
20122 buffer_ids,
20123 } => {
20124 self.display_map.update(cx, |map, cx| {
20125 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20126 });
20127 cx.emit(EditorEvent::ExcerptsEdited {
20128 ids: excerpt_ids.clone(),
20129 });
20130 }
20131 multi_buffer::Event::ExcerptsExpanded { ids } => {
20132 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20133 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20134 }
20135 multi_buffer::Event::Reparsed(buffer_id) => {
20136 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20137 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20138
20139 cx.emit(EditorEvent::Reparsed(*buffer_id));
20140 }
20141 multi_buffer::Event::DiffHunksToggled => {
20142 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20143 }
20144 multi_buffer::Event::LanguageChanged(buffer_id) => {
20145 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20146 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20147 cx.emit(EditorEvent::Reparsed(*buffer_id));
20148 cx.notify();
20149 }
20150 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20151 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20152 multi_buffer::Event::FileHandleChanged
20153 | multi_buffer::Event::Reloaded
20154 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20155 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20156 multi_buffer::Event::DiagnosticsUpdated => {
20157 self.update_diagnostics_state(window, cx);
20158 }
20159 _ => {}
20160 };
20161 }
20162
20163 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20164 if !self.diagnostics_enabled() {
20165 return;
20166 }
20167 self.refresh_active_diagnostics(cx);
20168 self.refresh_inline_diagnostics(true, window, cx);
20169 self.scrollbar_marker_state.dirty = true;
20170 cx.notify();
20171 }
20172
20173 pub fn start_temporary_diff_override(&mut self) {
20174 self.load_diff_task.take();
20175 self.temporary_diff_override = true;
20176 }
20177
20178 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20179 self.temporary_diff_override = false;
20180 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20181 self.buffer.update(cx, |buffer, cx| {
20182 buffer.set_all_diff_hunks_collapsed(cx);
20183 });
20184
20185 if let Some(project) = self.project.clone() {
20186 self.load_diff_task = Some(
20187 update_uncommitted_diff_for_buffer(
20188 cx.entity(),
20189 &project,
20190 self.buffer.read(cx).all_buffers(),
20191 self.buffer.clone(),
20192 cx,
20193 )
20194 .shared(),
20195 );
20196 }
20197 }
20198
20199 fn on_display_map_changed(
20200 &mut self,
20201 _: Entity<DisplayMap>,
20202 _: &mut Window,
20203 cx: &mut Context<Self>,
20204 ) {
20205 cx.notify();
20206 }
20207
20208 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20209 if self.diagnostics_enabled() {
20210 let new_severity = EditorSettings::get_global(cx)
20211 .diagnostics_max_severity
20212 .unwrap_or(DiagnosticSeverity::Hint);
20213 self.set_max_diagnostics_severity(new_severity, cx);
20214 }
20215 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20216 self.update_edit_prediction_settings(cx);
20217 self.refresh_edit_prediction(true, false, window, cx);
20218 self.refresh_inline_values(cx);
20219 self.refresh_inlay_hints(
20220 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20221 self.selections.newest_anchor().head(),
20222 &self.buffer.read(cx).snapshot(cx),
20223 cx,
20224 )),
20225 cx,
20226 );
20227
20228 let old_cursor_shape = self.cursor_shape;
20229 let old_show_breadcrumbs = self.show_breadcrumbs;
20230
20231 {
20232 let editor_settings = EditorSettings::get_global(cx);
20233 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20234 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20235 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20236 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20237 }
20238
20239 if old_cursor_shape != self.cursor_shape {
20240 cx.emit(EditorEvent::CursorShapeChanged);
20241 }
20242
20243 if old_show_breadcrumbs != self.show_breadcrumbs {
20244 cx.emit(EditorEvent::BreadcrumbsChanged);
20245 }
20246
20247 let project_settings = ProjectSettings::get_global(cx);
20248 self.serialize_dirty_buffers =
20249 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20250
20251 if self.mode.is_full() {
20252 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20253 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20254 if self.show_inline_diagnostics != show_inline_diagnostics {
20255 self.show_inline_diagnostics = show_inline_diagnostics;
20256 self.refresh_inline_diagnostics(false, window, cx);
20257 }
20258
20259 if self.git_blame_inline_enabled != inline_blame_enabled {
20260 self.toggle_git_blame_inline_internal(false, window, cx);
20261 }
20262
20263 let minimap_settings = EditorSettings::get_global(cx).minimap;
20264 if self.minimap_visibility != MinimapVisibility::Disabled {
20265 if self.minimap_visibility.settings_visibility()
20266 != minimap_settings.minimap_enabled()
20267 {
20268 self.set_minimap_visibility(
20269 MinimapVisibility::for_mode(self.mode(), cx),
20270 window,
20271 cx,
20272 );
20273 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20274 minimap_entity.update(cx, |minimap_editor, cx| {
20275 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20276 })
20277 }
20278 }
20279 }
20280
20281 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20282 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20283 }) {
20284 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20285 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20286 }
20287 self.refresh_colors(false, None, window, cx);
20288 }
20289
20290 cx.notify();
20291 }
20292
20293 pub fn set_searchable(&mut self, searchable: bool) {
20294 self.searchable = searchable;
20295 }
20296
20297 pub fn searchable(&self) -> bool {
20298 self.searchable
20299 }
20300
20301 fn open_proposed_changes_editor(
20302 &mut self,
20303 _: &OpenProposedChangesEditor,
20304 window: &mut Window,
20305 cx: &mut Context<Self>,
20306 ) {
20307 let Some(workspace) = self.workspace() else {
20308 cx.propagate();
20309 return;
20310 };
20311
20312 let selections = self.selections.all::<usize>(cx);
20313 let multi_buffer = self.buffer.read(cx);
20314 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20315 let mut new_selections_by_buffer = HashMap::default();
20316 for selection in selections {
20317 for (buffer, range, _) in
20318 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20319 {
20320 let mut range = range.to_point(buffer);
20321 range.start.column = 0;
20322 range.end.column = buffer.line_len(range.end.row);
20323 new_selections_by_buffer
20324 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20325 .or_insert(Vec::new())
20326 .push(range)
20327 }
20328 }
20329
20330 let proposed_changes_buffers = new_selections_by_buffer
20331 .into_iter()
20332 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20333 .collect::<Vec<_>>();
20334 let proposed_changes_editor = cx.new(|cx| {
20335 ProposedChangesEditor::new(
20336 "Proposed changes",
20337 proposed_changes_buffers,
20338 self.project.clone(),
20339 window,
20340 cx,
20341 )
20342 });
20343
20344 window.defer(cx, move |window, cx| {
20345 workspace.update(cx, |workspace, cx| {
20346 workspace.active_pane().update(cx, |pane, cx| {
20347 pane.add_item(
20348 Box::new(proposed_changes_editor),
20349 true,
20350 true,
20351 None,
20352 window,
20353 cx,
20354 );
20355 });
20356 });
20357 });
20358 }
20359
20360 pub fn open_excerpts_in_split(
20361 &mut self,
20362 _: &OpenExcerptsSplit,
20363 window: &mut Window,
20364 cx: &mut Context<Self>,
20365 ) {
20366 self.open_excerpts_common(None, true, window, cx)
20367 }
20368
20369 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20370 self.open_excerpts_common(None, false, window, cx)
20371 }
20372
20373 fn open_excerpts_common(
20374 &mut self,
20375 jump_data: Option<JumpData>,
20376 split: bool,
20377 window: &mut Window,
20378 cx: &mut Context<Self>,
20379 ) {
20380 let Some(workspace) = self.workspace() else {
20381 cx.propagate();
20382 return;
20383 };
20384
20385 if self.buffer.read(cx).is_singleton() {
20386 cx.propagate();
20387 return;
20388 }
20389
20390 let mut new_selections_by_buffer = HashMap::default();
20391 match &jump_data {
20392 Some(JumpData::MultiBufferPoint {
20393 excerpt_id,
20394 position,
20395 anchor,
20396 line_offset_from_top,
20397 }) => {
20398 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20399 if let Some(buffer) = multi_buffer_snapshot
20400 .buffer_id_for_excerpt(*excerpt_id)
20401 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20402 {
20403 let buffer_snapshot = buffer.read(cx).snapshot();
20404 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20405 language::ToPoint::to_point(anchor, &buffer_snapshot)
20406 } else {
20407 buffer_snapshot.clip_point(*position, Bias::Left)
20408 };
20409 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20410 new_selections_by_buffer.insert(
20411 buffer,
20412 (
20413 vec![jump_to_offset..jump_to_offset],
20414 Some(*line_offset_from_top),
20415 ),
20416 );
20417 }
20418 }
20419 Some(JumpData::MultiBufferRow {
20420 row,
20421 line_offset_from_top,
20422 }) => {
20423 let point = MultiBufferPoint::new(row.0, 0);
20424 if let Some((buffer, buffer_point, _)) =
20425 self.buffer.read(cx).point_to_buffer_point(point, cx)
20426 {
20427 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20428 new_selections_by_buffer
20429 .entry(buffer)
20430 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20431 .0
20432 .push(buffer_offset..buffer_offset)
20433 }
20434 }
20435 None => {
20436 let selections = self.selections.all::<usize>(cx);
20437 let multi_buffer = self.buffer.read(cx);
20438 for selection in selections {
20439 for (snapshot, range, _, anchor) in multi_buffer
20440 .snapshot(cx)
20441 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20442 {
20443 if let Some(anchor) = anchor {
20444 // selection is in a deleted hunk
20445 let Some(buffer_id) = anchor.buffer_id else {
20446 continue;
20447 };
20448 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20449 continue;
20450 };
20451 let offset = text::ToOffset::to_offset(
20452 &anchor.text_anchor,
20453 &buffer_handle.read(cx).snapshot(),
20454 );
20455 let range = offset..offset;
20456 new_selections_by_buffer
20457 .entry(buffer_handle)
20458 .or_insert((Vec::new(), None))
20459 .0
20460 .push(range)
20461 } else {
20462 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20463 else {
20464 continue;
20465 };
20466 new_selections_by_buffer
20467 .entry(buffer_handle)
20468 .or_insert((Vec::new(), None))
20469 .0
20470 .push(range)
20471 }
20472 }
20473 }
20474 }
20475 }
20476
20477 new_selections_by_buffer
20478 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20479
20480 if new_selections_by_buffer.is_empty() {
20481 return;
20482 }
20483
20484 // We defer the pane interaction because we ourselves are a workspace item
20485 // and activating a new item causes the pane to call a method on us reentrantly,
20486 // which panics if we're on the stack.
20487 window.defer(cx, move |window, cx| {
20488 workspace.update(cx, |workspace, cx| {
20489 let pane = if split {
20490 workspace.adjacent_pane(window, cx)
20491 } else {
20492 workspace.active_pane().clone()
20493 };
20494
20495 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20496 let editor = buffer
20497 .read(cx)
20498 .file()
20499 .is_none()
20500 .then(|| {
20501 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20502 // so `workspace.open_project_item` will never find them, always opening a new editor.
20503 // Instead, we try to activate the existing editor in the pane first.
20504 let (editor, pane_item_index) =
20505 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20506 let editor = item.downcast::<Editor>()?;
20507 let singleton_buffer =
20508 editor.read(cx).buffer().read(cx).as_singleton()?;
20509 if singleton_buffer == buffer {
20510 Some((editor, i))
20511 } else {
20512 None
20513 }
20514 })?;
20515 pane.update(cx, |pane, cx| {
20516 pane.activate_item(pane_item_index, true, true, window, cx)
20517 });
20518 Some(editor)
20519 })
20520 .flatten()
20521 .unwrap_or_else(|| {
20522 workspace.open_project_item::<Self>(
20523 pane.clone(),
20524 buffer,
20525 true,
20526 true,
20527 window,
20528 cx,
20529 )
20530 });
20531
20532 editor.update(cx, |editor, cx| {
20533 let autoscroll = match scroll_offset {
20534 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20535 None => Autoscroll::newest(),
20536 };
20537 let nav_history = editor.nav_history.take();
20538 editor.change_selections(
20539 SelectionEffects::scroll(autoscroll),
20540 window,
20541 cx,
20542 |s| {
20543 s.select_ranges(ranges);
20544 },
20545 );
20546 editor.nav_history = nav_history;
20547 });
20548 }
20549 })
20550 });
20551 }
20552
20553 // For now, don't allow opening excerpts in buffers that aren't backed by
20554 // regular project files.
20555 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20556 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20557 }
20558
20559 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20560 let snapshot = self.buffer.read(cx).read(cx);
20561 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20562 Some(
20563 ranges
20564 .iter()
20565 .map(move |range| {
20566 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20567 })
20568 .collect(),
20569 )
20570 }
20571
20572 fn selection_replacement_ranges(
20573 &self,
20574 range: Range<OffsetUtf16>,
20575 cx: &mut App,
20576 ) -> Vec<Range<OffsetUtf16>> {
20577 let selections = self.selections.all::<OffsetUtf16>(cx);
20578 let newest_selection = selections
20579 .iter()
20580 .max_by_key(|selection| selection.id)
20581 .unwrap();
20582 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20583 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20584 let snapshot = self.buffer.read(cx).read(cx);
20585 selections
20586 .into_iter()
20587 .map(|mut selection| {
20588 selection.start.0 =
20589 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20590 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20591 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20592 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20593 })
20594 .collect()
20595 }
20596
20597 fn report_editor_event(
20598 &self,
20599 reported_event: ReportEditorEvent,
20600 file_extension: Option<String>,
20601 cx: &App,
20602 ) {
20603 if cfg!(any(test, feature = "test-support")) {
20604 return;
20605 }
20606
20607 let Some(project) = &self.project else { return };
20608
20609 // If None, we are in a file without an extension
20610 let file = self
20611 .buffer
20612 .read(cx)
20613 .as_singleton()
20614 .and_then(|b| b.read(cx).file());
20615 let file_extension = file_extension.or(file
20616 .as_ref()
20617 .and_then(|file| Path::new(file.file_name(cx)).extension())
20618 .and_then(|e| e.to_str())
20619 .map(|a| a.to_string()));
20620
20621 let vim_mode = vim_enabled(cx);
20622
20623 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20624 let copilot_enabled = edit_predictions_provider
20625 == language::language_settings::EditPredictionProvider::Copilot;
20626 let copilot_enabled_for_language = self
20627 .buffer
20628 .read(cx)
20629 .language_settings(cx)
20630 .show_edit_predictions;
20631
20632 let project = project.read(cx);
20633 let event_type = reported_event.event_type();
20634
20635 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20636 telemetry::event!(
20637 event_type,
20638 type = if auto_saved {"autosave"} else {"manual"},
20639 file_extension,
20640 vim_mode,
20641 copilot_enabled,
20642 copilot_enabled_for_language,
20643 edit_predictions_provider,
20644 is_via_ssh = project.is_via_ssh(),
20645 );
20646 } else {
20647 telemetry::event!(
20648 event_type,
20649 file_extension,
20650 vim_mode,
20651 copilot_enabled,
20652 copilot_enabled_for_language,
20653 edit_predictions_provider,
20654 is_via_ssh = project.is_via_ssh(),
20655 );
20656 };
20657 }
20658
20659 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20660 /// with each line being an array of {text, highlight} objects.
20661 fn copy_highlight_json(
20662 &mut self,
20663 _: &CopyHighlightJson,
20664 window: &mut Window,
20665 cx: &mut Context<Self>,
20666 ) {
20667 #[derive(Serialize)]
20668 struct Chunk<'a> {
20669 text: String,
20670 highlight: Option<&'a str>,
20671 }
20672
20673 let snapshot = self.buffer.read(cx).snapshot(cx);
20674 let range = self
20675 .selected_text_range(false, window, cx)
20676 .and_then(|selection| {
20677 if selection.range.is_empty() {
20678 None
20679 } else {
20680 Some(selection.range)
20681 }
20682 })
20683 .unwrap_or_else(|| 0..snapshot.len());
20684
20685 let chunks = snapshot.chunks(range, true);
20686 let mut lines = Vec::new();
20687 let mut line: VecDeque<Chunk> = VecDeque::new();
20688
20689 let Some(style) = self.style.as_ref() else {
20690 return;
20691 };
20692
20693 for chunk in chunks {
20694 let highlight = chunk
20695 .syntax_highlight_id
20696 .and_then(|id| id.name(&style.syntax));
20697 let mut chunk_lines = chunk.text.split('\n').peekable();
20698 while let Some(text) = chunk_lines.next() {
20699 let mut merged_with_last_token = false;
20700 if let Some(last_token) = line.back_mut()
20701 && last_token.highlight == highlight
20702 {
20703 last_token.text.push_str(text);
20704 merged_with_last_token = true;
20705 }
20706
20707 if !merged_with_last_token {
20708 line.push_back(Chunk {
20709 text: text.into(),
20710 highlight,
20711 });
20712 }
20713
20714 if chunk_lines.peek().is_some() {
20715 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20716 line.pop_front();
20717 }
20718 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20719 line.pop_back();
20720 }
20721
20722 lines.push(mem::take(&mut line));
20723 }
20724 }
20725 }
20726
20727 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20728 return;
20729 };
20730 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20731 }
20732
20733 pub fn open_context_menu(
20734 &mut self,
20735 _: &OpenContextMenu,
20736 window: &mut Window,
20737 cx: &mut Context<Self>,
20738 ) {
20739 self.request_autoscroll(Autoscroll::newest(), cx);
20740 let position = self.selections.newest_display(cx).start;
20741 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20742 }
20743
20744 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20745 &self.inlay_hint_cache
20746 }
20747
20748 pub fn replay_insert_event(
20749 &mut self,
20750 text: &str,
20751 relative_utf16_range: Option<Range<isize>>,
20752 window: &mut Window,
20753 cx: &mut Context<Self>,
20754 ) {
20755 if !self.input_enabled {
20756 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20757 return;
20758 }
20759 if let Some(relative_utf16_range) = relative_utf16_range {
20760 let selections = self.selections.all::<OffsetUtf16>(cx);
20761 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20762 let new_ranges = selections.into_iter().map(|range| {
20763 let start = OffsetUtf16(
20764 range
20765 .head()
20766 .0
20767 .saturating_add_signed(relative_utf16_range.start),
20768 );
20769 let end = OffsetUtf16(
20770 range
20771 .head()
20772 .0
20773 .saturating_add_signed(relative_utf16_range.end),
20774 );
20775 start..end
20776 });
20777 s.select_ranges(new_ranges);
20778 });
20779 }
20780
20781 self.handle_input(text, window, cx);
20782 }
20783
20784 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20785 let Some(provider) = self.semantics_provider.as_ref() else {
20786 return false;
20787 };
20788
20789 let mut supports = false;
20790 self.buffer().update(cx, |this, cx| {
20791 this.for_each_buffer(|buffer| {
20792 supports |= provider.supports_inlay_hints(buffer, cx);
20793 });
20794 });
20795
20796 supports
20797 }
20798
20799 pub fn is_focused(&self, window: &Window) -> bool {
20800 self.focus_handle.is_focused(window)
20801 }
20802
20803 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20804 cx.emit(EditorEvent::Focused);
20805
20806 if let Some(descendant) = self
20807 .last_focused_descendant
20808 .take()
20809 .and_then(|descendant| descendant.upgrade())
20810 {
20811 window.focus(&descendant);
20812 } else {
20813 if let Some(blame) = self.blame.as_ref() {
20814 blame.update(cx, GitBlame::focus)
20815 }
20816
20817 self.blink_manager.update(cx, BlinkManager::enable);
20818 self.show_cursor_names(window, cx);
20819 self.buffer.update(cx, |buffer, cx| {
20820 buffer.finalize_last_transaction(cx);
20821 if self.leader_id.is_none() {
20822 buffer.set_active_selections(
20823 &self.selections.disjoint_anchors(),
20824 self.selections.line_mode,
20825 self.cursor_shape,
20826 cx,
20827 );
20828 }
20829 });
20830 }
20831 }
20832
20833 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20834 cx.emit(EditorEvent::FocusedIn)
20835 }
20836
20837 fn handle_focus_out(
20838 &mut self,
20839 event: FocusOutEvent,
20840 _window: &mut Window,
20841 cx: &mut Context<Self>,
20842 ) {
20843 if event.blurred != self.focus_handle {
20844 self.last_focused_descendant = Some(event.blurred);
20845 }
20846 self.selection_drag_state = SelectionDragState::None;
20847 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20848 }
20849
20850 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20851 self.blink_manager.update(cx, BlinkManager::disable);
20852 self.buffer
20853 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20854
20855 if let Some(blame) = self.blame.as_ref() {
20856 blame.update(cx, GitBlame::blur)
20857 }
20858 if !self.hover_state.focused(window, cx) {
20859 hide_hover(self, cx);
20860 }
20861 if !self
20862 .context_menu
20863 .borrow()
20864 .as_ref()
20865 .is_some_and(|context_menu| context_menu.focused(window, cx))
20866 {
20867 self.hide_context_menu(window, cx);
20868 }
20869 self.discard_edit_prediction(false, cx);
20870 cx.emit(EditorEvent::Blurred);
20871 cx.notify();
20872 }
20873
20874 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20875 let mut pending: String = window
20876 .pending_input_keystrokes()
20877 .into_iter()
20878 .flatten()
20879 .filter_map(|keystroke| {
20880 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20881 keystroke.key_char.clone()
20882 } else {
20883 None
20884 }
20885 })
20886 .collect();
20887
20888 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20889 pending = "".to_string();
20890 }
20891
20892 let existing_pending = self
20893 .text_highlights::<PendingInput>(cx)
20894 .map(|(_, ranges)| ranges.to_vec());
20895 if existing_pending.is_none() && pending.is_empty() {
20896 return;
20897 }
20898 let transaction =
20899 self.transact(window, cx, |this, window, cx| {
20900 let selections = this.selections.all::<usize>(cx);
20901 let edits = selections
20902 .iter()
20903 .map(|selection| (selection.end..selection.end, pending.clone()));
20904 this.edit(edits, cx);
20905 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20906 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20907 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20908 }));
20909 });
20910 if let Some(existing_ranges) = existing_pending {
20911 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20912 this.edit(edits, cx);
20913 }
20914 });
20915
20916 let snapshot = self.snapshot(window, cx);
20917 let ranges = self
20918 .selections
20919 .all::<usize>(cx)
20920 .into_iter()
20921 .map(|selection| {
20922 snapshot.buffer_snapshot.anchor_after(selection.end)
20923 ..snapshot
20924 .buffer_snapshot
20925 .anchor_before(selection.end + pending.len())
20926 })
20927 .collect();
20928
20929 if pending.is_empty() {
20930 self.clear_highlights::<PendingInput>(cx);
20931 } else {
20932 self.highlight_text::<PendingInput>(
20933 ranges,
20934 HighlightStyle {
20935 underline: Some(UnderlineStyle {
20936 thickness: px(1.),
20937 color: None,
20938 wavy: false,
20939 }),
20940 ..Default::default()
20941 },
20942 cx,
20943 );
20944 }
20945
20946 self.ime_transaction = self.ime_transaction.or(transaction);
20947 if let Some(transaction) = self.ime_transaction {
20948 self.buffer.update(cx, |buffer, cx| {
20949 buffer.group_until_transaction(transaction, cx);
20950 });
20951 }
20952
20953 if self.text_highlights::<PendingInput>(cx).is_none() {
20954 self.ime_transaction.take();
20955 }
20956 }
20957
20958 pub fn register_action_renderer(
20959 &mut self,
20960 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20961 ) -> Subscription {
20962 let id = self.next_editor_action_id.post_inc();
20963 self.editor_actions
20964 .borrow_mut()
20965 .insert(id, Box::new(listener));
20966
20967 let editor_actions = self.editor_actions.clone();
20968 Subscription::new(move || {
20969 editor_actions.borrow_mut().remove(&id);
20970 })
20971 }
20972
20973 pub fn register_action<A: Action>(
20974 &mut self,
20975 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20976 ) -> Subscription {
20977 let id = self.next_editor_action_id.post_inc();
20978 let listener = Arc::new(listener);
20979 self.editor_actions.borrow_mut().insert(
20980 id,
20981 Box::new(move |_, window, _| {
20982 let listener = listener.clone();
20983 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20984 let action = action.downcast_ref().unwrap();
20985 if phase == DispatchPhase::Bubble {
20986 listener(action, window, cx)
20987 }
20988 })
20989 }),
20990 );
20991
20992 let editor_actions = self.editor_actions.clone();
20993 Subscription::new(move || {
20994 editor_actions.borrow_mut().remove(&id);
20995 })
20996 }
20997
20998 pub fn file_header_size(&self) -> u32 {
20999 FILE_HEADER_HEIGHT
21000 }
21001
21002 pub fn restore(
21003 &mut self,
21004 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21005 window: &mut Window,
21006 cx: &mut Context<Self>,
21007 ) {
21008 let workspace = self.workspace();
21009 let project = self.project();
21010 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21011 let mut tasks = Vec::new();
21012 for (buffer_id, changes) in revert_changes {
21013 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21014 buffer.update(cx, |buffer, cx| {
21015 buffer.edit(
21016 changes
21017 .into_iter()
21018 .map(|(range, text)| (range, text.to_string())),
21019 None,
21020 cx,
21021 );
21022 });
21023
21024 if let Some(project) =
21025 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21026 {
21027 project.update(cx, |project, cx| {
21028 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21029 })
21030 }
21031 }
21032 }
21033 tasks
21034 });
21035 cx.spawn_in(window, async move |_, cx| {
21036 for (buffer, task) in save_tasks {
21037 let result = task.await;
21038 if result.is_err() {
21039 let Some(path) = buffer
21040 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21041 .ok()
21042 else {
21043 continue;
21044 };
21045 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21046 let Some(task) = cx
21047 .update_window_entity(workspace, |workspace, window, cx| {
21048 workspace
21049 .open_path_preview(path, None, false, false, false, window, cx)
21050 })
21051 .ok()
21052 else {
21053 continue;
21054 };
21055 task.await.log_err();
21056 }
21057 }
21058 }
21059 })
21060 .detach();
21061 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21062 selections.refresh()
21063 });
21064 }
21065
21066 pub fn to_pixel_point(
21067 &self,
21068 source: multi_buffer::Anchor,
21069 editor_snapshot: &EditorSnapshot,
21070 window: &mut Window,
21071 ) -> Option<gpui::Point<Pixels>> {
21072 let source_point = source.to_display_point(editor_snapshot);
21073 self.display_to_pixel_point(source_point, editor_snapshot, window)
21074 }
21075
21076 pub fn display_to_pixel_point(
21077 &self,
21078 source: DisplayPoint,
21079 editor_snapshot: &EditorSnapshot,
21080 window: &mut Window,
21081 ) -> Option<gpui::Point<Pixels>> {
21082 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21083 let text_layout_details = self.text_layout_details(window);
21084 let scroll_top = text_layout_details
21085 .scroll_anchor
21086 .scroll_position(editor_snapshot)
21087 .y;
21088
21089 if source.row().as_f32() < scroll_top.floor() {
21090 return None;
21091 }
21092 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21093 let source_y = line_height * (source.row().as_f32() - scroll_top);
21094 Some(gpui::Point::new(source_x, source_y))
21095 }
21096
21097 pub fn has_visible_completions_menu(&self) -> bool {
21098 !self.edit_prediction_preview_is_active()
21099 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21100 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21101 })
21102 }
21103
21104 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21105 if self.mode.is_minimap() {
21106 return;
21107 }
21108 self.addons
21109 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21110 }
21111
21112 pub fn unregister_addon<T: Addon>(&mut self) {
21113 self.addons.remove(&std::any::TypeId::of::<T>());
21114 }
21115
21116 pub fn addon<T: Addon>(&self) -> Option<&T> {
21117 let type_id = std::any::TypeId::of::<T>();
21118 self.addons
21119 .get(&type_id)
21120 .and_then(|item| item.to_any().downcast_ref::<T>())
21121 }
21122
21123 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21124 let type_id = std::any::TypeId::of::<T>();
21125 self.addons
21126 .get_mut(&type_id)
21127 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21128 }
21129
21130 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21131 let text_layout_details = self.text_layout_details(window);
21132 let style = &text_layout_details.editor_style;
21133 let font_id = window.text_system().resolve_font(&style.text.font());
21134 let font_size = style.text.font_size.to_pixels(window.rem_size());
21135 let line_height = style.text.line_height_in_pixels(window.rem_size());
21136 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21137 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21138
21139 CharacterDimensions {
21140 em_width,
21141 em_advance,
21142 line_height,
21143 }
21144 }
21145
21146 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21147 self.load_diff_task.clone()
21148 }
21149
21150 fn read_metadata_from_db(
21151 &mut self,
21152 item_id: u64,
21153 workspace_id: WorkspaceId,
21154 window: &mut Window,
21155 cx: &mut Context<Editor>,
21156 ) {
21157 if self.is_singleton(cx)
21158 && !self.mode.is_minimap()
21159 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21160 {
21161 let buffer_snapshot = OnceCell::new();
21162
21163 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21164 && !folds.is_empty()
21165 {
21166 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21167 self.fold_ranges(
21168 folds
21169 .into_iter()
21170 .map(|(start, end)| {
21171 snapshot.clip_offset(start, Bias::Left)
21172 ..snapshot.clip_offset(end, Bias::Right)
21173 })
21174 .collect(),
21175 false,
21176 window,
21177 cx,
21178 );
21179 }
21180
21181 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21182 && !selections.is_empty()
21183 {
21184 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21185 // skip adding the initial selection to selection history
21186 self.selection_history.mode = SelectionHistoryMode::Skipping;
21187 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21188 s.select_ranges(selections.into_iter().map(|(start, end)| {
21189 snapshot.clip_offset(start, Bias::Left)
21190 ..snapshot.clip_offset(end, Bias::Right)
21191 }));
21192 });
21193 self.selection_history.mode = SelectionHistoryMode::Normal;
21194 };
21195 }
21196
21197 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21198 }
21199
21200 fn update_lsp_data(
21201 &mut self,
21202 ignore_cache: bool,
21203 for_buffer: Option<BufferId>,
21204 window: &mut Window,
21205 cx: &mut Context<'_, Self>,
21206 ) {
21207 self.pull_diagnostics(for_buffer, window, cx);
21208 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21209 }
21210}
21211
21212fn vim_enabled(cx: &App) -> bool {
21213 cx.global::<SettingsStore>()
21214 .raw_user_settings()
21215 .get("vim_mode")
21216 == Some(&serde_json::Value::Bool(true))
21217}
21218
21219fn process_completion_for_edit(
21220 completion: &Completion,
21221 intent: CompletionIntent,
21222 buffer: &Entity<Buffer>,
21223 cursor_position: &text::Anchor,
21224 cx: &mut Context<Editor>,
21225) -> CompletionEdit {
21226 let buffer = buffer.read(cx);
21227 let buffer_snapshot = buffer.snapshot();
21228 let (snippet, new_text) = if completion.is_snippet() {
21229 // Workaround for typescript language server issues so that methods don't expand within
21230 // strings and functions with type expressions. The previous point is used because the query
21231 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21232 let mut snippet_source = completion.new_text.clone();
21233 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21234 previous_point.column = previous_point.column.saturating_sub(1);
21235 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21236 && scope.prefers_label_for_snippet_in_completion()
21237 && let Some(label) = completion.label()
21238 && matches!(
21239 completion.kind(),
21240 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21241 )
21242 {
21243 snippet_source = label;
21244 }
21245 match Snippet::parse(&snippet_source).log_err() {
21246 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21247 None => (None, completion.new_text.clone()),
21248 }
21249 } else {
21250 (None, completion.new_text.clone())
21251 };
21252
21253 let mut range_to_replace = {
21254 let replace_range = &completion.replace_range;
21255 if let CompletionSource::Lsp {
21256 insert_range: Some(insert_range),
21257 ..
21258 } = &completion.source
21259 {
21260 debug_assert_eq!(
21261 insert_range.start, replace_range.start,
21262 "insert_range and replace_range should start at the same position"
21263 );
21264 debug_assert!(
21265 insert_range
21266 .start
21267 .cmp(cursor_position, &buffer_snapshot)
21268 .is_le(),
21269 "insert_range should start before or at cursor position"
21270 );
21271 debug_assert!(
21272 replace_range
21273 .start
21274 .cmp(cursor_position, &buffer_snapshot)
21275 .is_le(),
21276 "replace_range should start before or at cursor position"
21277 );
21278
21279 let should_replace = match intent {
21280 CompletionIntent::CompleteWithInsert => false,
21281 CompletionIntent::CompleteWithReplace => true,
21282 CompletionIntent::Complete | CompletionIntent::Compose => {
21283 let insert_mode =
21284 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21285 .completions
21286 .lsp_insert_mode;
21287 match insert_mode {
21288 LspInsertMode::Insert => false,
21289 LspInsertMode::Replace => true,
21290 LspInsertMode::ReplaceSubsequence => {
21291 let mut text_to_replace = buffer.chars_for_range(
21292 buffer.anchor_before(replace_range.start)
21293 ..buffer.anchor_after(replace_range.end),
21294 );
21295 let mut current_needle = text_to_replace.next();
21296 for haystack_ch in completion.label.text.chars() {
21297 if let Some(needle_ch) = current_needle
21298 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21299 {
21300 current_needle = text_to_replace.next();
21301 }
21302 }
21303 current_needle.is_none()
21304 }
21305 LspInsertMode::ReplaceSuffix => {
21306 if replace_range
21307 .end
21308 .cmp(cursor_position, &buffer_snapshot)
21309 .is_gt()
21310 {
21311 let range_after_cursor = *cursor_position..replace_range.end;
21312 let text_after_cursor = buffer
21313 .text_for_range(
21314 buffer.anchor_before(range_after_cursor.start)
21315 ..buffer.anchor_after(range_after_cursor.end),
21316 )
21317 .collect::<String>()
21318 .to_ascii_lowercase();
21319 completion
21320 .label
21321 .text
21322 .to_ascii_lowercase()
21323 .ends_with(&text_after_cursor)
21324 } else {
21325 true
21326 }
21327 }
21328 }
21329 }
21330 };
21331
21332 if should_replace {
21333 replace_range.clone()
21334 } else {
21335 insert_range.clone()
21336 }
21337 } else {
21338 replace_range.clone()
21339 }
21340 };
21341
21342 if range_to_replace
21343 .end
21344 .cmp(cursor_position, &buffer_snapshot)
21345 .is_lt()
21346 {
21347 range_to_replace.end = *cursor_position;
21348 }
21349
21350 CompletionEdit {
21351 new_text,
21352 replace_range: range_to_replace.to_offset(buffer),
21353 snippet,
21354 }
21355}
21356
21357struct CompletionEdit {
21358 new_text: String,
21359 replace_range: Range<usize>,
21360 snippet: Option<Snippet>,
21361}
21362
21363fn insert_extra_newline_brackets(
21364 buffer: &MultiBufferSnapshot,
21365 range: Range<usize>,
21366 language: &language::LanguageScope,
21367) -> bool {
21368 let leading_whitespace_len = buffer
21369 .reversed_chars_at(range.start)
21370 .take_while(|c| c.is_whitespace() && *c != '\n')
21371 .map(|c| c.len_utf8())
21372 .sum::<usize>();
21373 let trailing_whitespace_len = buffer
21374 .chars_at(range.end)
21375 .take_while(|c| c.is_whitespace() && *c != '\n')
21376 .map(|c| c.len_utf8())
21377 .sum::<usize>();
21378 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21379
21380 language.brackets().any(|(pair, enabled)| {
21381 let pair_start = pair.start.trim_end();
21382 let pair_end = pair.end.trim_start();
21383
21384 enabled
21385 && pair.newline
21386 && buffer.contains_str_at(range.end, pair_end)
21387 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21388 })
21389}
21390
21391fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21392 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21393 [(buffer, range, _)] => (*buffer, range.clone()),
21394 _ => return false,
21395 };
21396 let pair = {
21397 let mut result: Option<BracketMatch> = None;
21398
21399 for pair in buffer
21400 .all_bracket_ranges(range.clone())
21401 .filter(move |pair| {
21402 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21403 })
21404 {
21405 let len = pair.close_range.end - pair.open_range.start;
21406
21407 if let Some(existing) = &result {
21408 let existing_len = existing.close_range.end - existing.open_range.start;
21409 if len > existing_len {
21410 continue;
21411 }
21412 }
21413
21414 result = Some(pair);
21415 }
21416
21417 result
21418 };
21419 let Some(pair) = pair else {
21420 return false;
21421 };
21422 pair.newline_only
21423 && buffer
21424 .chars_for_range(pair.open_range.end..range.start)
21425 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21426 .all(|c| c.is_whitespace() && c != '\n')
21427}
21428
21429fn update_uncommitted_diff_for_buffer(
21430 editor: Entity<Editor>,
21431 project: &Entity<Project>,
21432 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21433 buffer: Entity<MultiBuffer>,
21434 cx: &mut App,
21435) -> Task<()> {
21436 let mut tasks = Vec::new();
21437 project.update(cx, |project, cx| {
21438 for buffer in buffers {
21439 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21440 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21441 }
21442 }
21443 });
21444 cx.spawn(async move |cx| {
21445 let diffs = future::join_all(tasks).await;
21446 if editor
21447 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21448 .unwrap_or(false)
21449 {
21450 return;
21451 }
21452
21453 buffer
21454 .update(cx, |buffer, cx| {
21455 for diff in diffs.into_iter().flatten() {
21456 buffer.add_diff(diff, cx);
21457 }
21458 })
21459 .ok();
21460 })
21461}
21462
21463fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21464 let tab_size = tab_size.get() as usize;
21465 let mut width = offset;
21466
21467 for ch in text.chars() {
21468 width += if ch == '\t' {
21469 tab_size - (width % tab_size)
21470 } else {
21471 1
21472 };
21473 }
21474
21475 width - offset
21476}
21477
21478#[cfg(test)]
21479mod tests {
21480 use super::*;
21481
21482 #[test]
21483 fn test_string_size_with_expanded_tabs() {
21484 let nz = |val| NonZeroU32::new(val).unwrap();
21485 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21486 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21487 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21488 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21489 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21490 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21491 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21492 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21493 }
21494}
21495
21496/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21497struct WordBreakingTokenizer<'a> {
21498 input: &'a str,
21499}
21500
21501impl<'a> WordBreakingTokenizer<'a> {
21502 fn new(input: &'a str) -> Self {
21503 Self { input }
21504 }
21505}
21506
21507fn is_char_ideographic(ch: char) -> bool {
21508 use unicode_script::Script::*;
21509 use unicode_script::UnicodeScript;
21510 matches!(ch.script(), Han | Tangut | Yi)
21511}
21512
21513fn is_grapheme_ideographic(text: &str) -> bool {
21514 text.chars().any(is_char_ideographic)
21515}
21516
21517fn is_grapheme_whitespace(text: &str) -> bool {
21518 text.chars().any(|x| x.is_whitespace())
21519}
21520
21521fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21522 text.chars()
21523 .next()
21524 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21525}
21526
21527#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21528enum WordBreakToken<'a> {
21529 Word { token: &'a str, grapheme_len: usize },
21530 InlineWhitespace { token: &'a str, grapheme_len: usize },
21531 Newline,
21532}
21533
21534impl<'a> Iterator for WordBreakingTokenizer<'a> {
21535 /// Yields a span, the count of graphemes in the token, and whether it was
21536 /// whitespace. Note that it also breaks at word boundaries.
21537 type Item = WordBreakToken<'a>;
21538
21539 fn next(&mut self) -> Option<Self::Item> {
21540 use unicode_segmentation::UnicodeSegmentation;
21541 if self.input.is_empty() {
21542 return None;
21543 }
21544
21545 let mut iter = self.input.graphemes(true).peekable();
21546 let mut offset = 0;
21547 let mut grapheme_len = 0;
21548 if let Some(first_grapheme) = iter.next() {
21549 let is_newline = first_grapheme == "\n";
21550 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21551 offset += first_grapheme.len();
21552 grapheme_len += 1;
21553 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21554 if let Some(grapheme) = iter.peek().copied()
21555 && should_stay_with_preceding_ideograph(grapheme)
21556 {
21557 offset += grapheme.len();
21558 grapheme_len += 1;
21559 }
21560 } else {
21561 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21562 let mut next_word_bound = words.peek().copied();
21563 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21564 next_word_bound = words.next();
21565 }
21566 while let Some(grapheme) = iter.peek().copied() {
21567 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21568 break;
21569 };
21570 if is_grapheme_whitespace(grapheme) != is_whitespace
21571 || (grapheme == "\n") != is_newline
21572 {
21573 break;
21574 };
21575 offset += grapheme.len();
21576 grapheme_len += 1;
21577 iter.next();
21578 }
21579 }
21580 let token = &self.input[..offset];
21581 self.input = &self.input[offset..];
21582 if token == "\n" {
21583 Some(WordBreakToken::Newline)
21584 } else if is_whitespace {
21585 Some(WordBreakToken::InlineWhitespace {
21586 token,
21587 grapheme_len,
21588 })
21589 } else {
21590 Some(WordBreakToken::Word {
21591 token,
21592 grapheme_len,
21593 })
21594 }
21595 } else {
21596 None
21597 }
21598 }
21599}
21600
21601#[test]
21602fn test_word_breaking_tokenizer() {
21603 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21604 ("", &[]),
21605 (" ", &[whitespace(" ", 2)]),
21606 ("Ʒ", &[word("Ʒ", 1)]),
21607 ("Ǽ", &[word("Ǽ", 1)]),
21608 ("⋑", &[word("⋑", 1)]),
21609 ("⋑⋑", &[word("⋑⋑", 2)]),
21610 (
21611 "原理,进而",
21612 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21613 ),
21614 (
21615 "hello world",
21616 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21617 ),
21618 (
21619 "hello, world",
21620 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21621 ),
21622 (
21623 " hello world",
21624 &[
21625 whitespace(" ", 2),
21626 word("hello", 5),
21627 whitespace(" ", 1),
21628 word("world", 5),
21629 ],
21630 ),
21631 (
21632 "这是什么 \n 钢笔",
21633 &[
21634 word("这", 1),
21635 word("是", 1),
21636 word("什", 1),
21637 word("么", 1),
21638 whitespace(" ", 1),
21639 newline(),
21640 whitespace(" ", 1),
21641 word("钢", 1),
21642 word("笔", 1),
21643 ],
21644 ),
21645 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21646 ];
21647
21648 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21649 WordBreakToken::Word {
21650 token,
21651 grapheme_len,
21652 }
21653 }
21654
21655 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21656 WordBreakToken::InlineWhitespace {
21657 token,
21658 grapheme_len,
21659 }
21660 }
21661
21662 fn newline() -> WordBreakToken<'static> {
21663 WordBreakToken::Newline
21664 }
21665
21666 for (input, result) in tests {
21667 assert_eq!(
21668 WordBreakingTokenizer::new(input)
21669 .collect::<Vec<_>>()
21670 .as_slice(),
21671 *result,
21672 );
21673 }
21674}
21675
21676fn wrap_with_prefix(
21677 first_line_prefix: String,
21678 subsequent_lines_prefix: String,
21679 unwrapped_text: String,
21680 wrap_column: usize,
21681 tab_size: NonZeroU32,
21682 preserve_existing_whitespace: bool,
21683) -> String {
21684 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21685 let subsequent_lines_prefix_len =
21686 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21687 let mut wrapped_text = String::new();
21688 let mut current_line = first_line_prefix.clone();
21689 let mut is_first_line = true;
21690
21691 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21692 let mut current_line_len = first_line_prefix_len;
21693 let mut in_whitespace = false;
21694 for token in tokenizer {
21695 let have_preceding_whitespace = in_whitespace;
21696 match token {
21697 WordBreakToken::Word {
21698 token,
21699 grapheme_len,
21700 } => {
21701 in_whitespace = false;
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 && current_line_len != current_prefix_len
21709 {
21710 wrapped_text.push_str(current_line.trim_end());
21711 wrapped_text.push('\n');
21712 is_first_line = false;
21713 current_line = subsequent_lines_prefix.clone();
21714 current_line_len = subsequent_lines_prefix_len;
21715 }
21716 current_line.push_str(token);
21717 current_line_len += grapheme_len;
21718 }
21719 WordBreakToken::InlineWhitespace {
21720 mut token,
21721 mut grapheme_len,
21722 } => {
21723 in_whitespace = true;
21724 if have_preceding_whitespace && !preserve_existing_whitespace {
21725 continue;
21726 }
21727 if !preserve_existing_whitespace {
21728 token = " ";
21729 grapheme_len = 1;
21730 }
21731 let current_prefix_len = if is_first_line {
21732 first_line_prefix_len
21733 } else {
21734 subsequent_lines_prefix_len
21735 };
21736 if current_line_len + grapheme_len > wrap_column {
21737 wrapped_text.push_str(current_line.trim_end());
21738 wrapped_text.push('\n');
21739 is_first_line = false;
21740 current_line = subsequent_lines_prefix.clone();
21741 current_line_len = subsequent_lines_prefix_len;
21742 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21743 current_line.push_str(token);
21744 current_line_len += grapheme_len;
21745 }
21746 }
21747 WordBreakToken::Newline => {
21748 in_whitespace = true;
21749 let current_prefix_len = if is_first_line {
21750 first_line_prefix_len
21751 } else {
21752 subsequent_lines_prefix_len
21753 };
21754 if preserve_existing_whitespace {
21755 wrapped_text.push_str(current_line.trim_end());
21756 wrapped_text.push('\n');
21757 is_first_line = false;
21758 current_line = subsequent_lines_prefix.clone();
21759 current_line_len = subsequent_lines_prefix_len;
21760 } else if have_preceding_whitespace {
21761 continue;
21762 } else if current_line_len + 1 > wrap_column
21763 && current_line_len != current_prefix_len
21764 {
21765 wrapped_text.push_str(current_line.trim_end());
21766 wrapped_text.push('\n');
21767 is_first_line = false;
21768 current_line = subsequent_lines_prefix.clone();
21769 current_line_len = subsequent_lines_prefix_len;
21770 } else if current_line_len != current_prefix_len {
21771 current_line.push(' ');
21772 current_line_len += 1;
21773 }
21774 }
21775 }
21776 }
21777
21778 if !current_line.is_empty() {
21779 wrapped_text.push_str(¤t_line);
21780 }
21781 wrapped_text
21782}
21783
21784#[test]
21785fn test_wrap_with_prefix() {
21786 assert_eq!(
21787 wrap_with_prefix(
21788 "# ".to_string(),
21789 "# ".to_string(),
21790 "abcdefg".to_string(),
21791 4,
21792 NonZeroU32::new(4).unwrap(),
21793 false,
21794 ),
21795 "# abcdefg"
21796 );
21797 assert_eq!(
21798 wrap_with_prefix(
21799 "".to_string(),
21800 "".to_string(),
21801 "\thello world".to_string(),
21802 8,
21803 NonZeroU32::new(4).unwrap(),
21804 false,
21805 ),
21806 "hello\nworld"
21807 );
21808 assert_eq!(
21809 wrap_with_prefix(
21810 "// ".to_string(),
21811 "// ".to_string(),
21812 "xx \nyy zz aa bb cc".to_string(),
21813 12,
21814 NonZeroU32::new(4).unwrap(),
21815 false,
21816 ),
21817 "// xx yy zz\n// aa bb cc"
21818 );
21819 assert_eq!(
21820 wrap_with_prefix(
21821 String::new(),
21822 String::new(),
21823 "这是什么 \n 钢笔".to_string(),
21824 3,
21825 NonZeroU32::new(4).unwrap(),
21826 false,
21827 ),
21828 "这是什\n么 钢\n笔"
21829 );
21830}
21831
21832pub trait CollaborationHub {
21833 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21834 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21835 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21836}
21837
21838impl CollaborationHub for Entity<Project> {
21839 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21840 self.read(cx).collaborators()
21841 }
21842
21843 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21844 self.read(cx).user_store().read(cx).participant_indices()
21845 }
21846
21847 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21848 let this = self.read(cx);
21849 let user_ids = this.collaborators().values().map(|c| c.user_id);
21850 this.user_store().read(cx).participant_names(user_ids, cx)
21851 }
21852}
21853
21854pub trait SemanticsProvider {
21855 fn hover(
21856 &self,
21857 buffer: &Entity<Buffer>,
21858 position: text::Anchor,
21859 cx: &mut App,
21860 ) -> Option<Task<Vec<project::Hover>>>;
21861
21862 fn inline_values(
21863 &self,
21864 buffer_handle: Entity<Buffer>,
21865 range: Range<text::Anchor>,
21866 cx: &mut App,
21867 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21868
21869 fn inlay_hints(
21870 &self,
21871 buffer_handle: Entity<Buffer>,
21872 range: Range<text::Anchor>,
21873 cx: &mut App,
21874 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21875
21876 fn resolve_inlay_hint(
21877 &self,
21878 hint: InlayHint,
21879 buffer_handle: Entity<Buffer>,
21880 server_id: LanguageServerId,
21881 cx: &mut App,
21882 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21883
21884 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21885
21886 fn document_highlights(
21887 &self,
21888 buffer: &Entity<Buffer>,
21889 position: text::Anchor,
21890 cx: &mut App,
21891 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21892
21893 fn definitions(
21894 &self,
21895 buffer: &Entity<Buffer>,
21896 position: text::Anchor,
21897 kind: GotoDefinitionKind,
21898 cx: &mut App,
21899 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21900
21901 fn range_for_rename(
21902 &self,
21903 buffer: &Entity<Buffer>,
21904 position: text::Anchor,
21905 cx: &mut App,
21906 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21907
21908 fn perform_rename(
21909 &self,
21910 buffer: &Entity<Buffer>,
21911 position: text::Anchor,
21912 new_name: String,
21913 cx: &mut App,
21914 ) -> Option<Task<Result<ProjectTransaction>>>;
21915}
21916
21917pub trait CompletionProvider {
21918 fn completions(
21919 &self,
21920 excerpt_id: ExcerptId,
21921 buffer: &Entity<Buffer>,
21922 buffer_position: text::Anchor,
21923 trigger: CompletionContext,
21924 window: &mut Window,
21925 cx: &mut Context<Editor>,
21926 ) -> Task<Result<Vec<CompletionResponse>>>;
21927
21928 fn resolve_completions(
21929 &self,
21930 _buffer: Entity<Buffer>,
21931 _completion_indices: Vec<usize>,
21932 _completions: Rc<RefCell<Box<[Completion]>>>,
21933 _cx: &mut Context<Editor>,
21934 ) -> Task<Result<bool>> {
21935 Task::ready(Ok(false))
21936 }
21937
21938 fn apply_additional_edits_for_completion(
21939 &self,
21940 _buffer: Entity<Buffer>,
21941 _completions: Rc<RefCell<Box<[Completion]>>>,
21942 _completion_index: usize,
21943 _push_to_history: bool,
21944 _cx: &mut Context<Editor>,
21945 ) -> Task<Result<Option<language::Transaction>>> {
21946 Task::ready(Ok(None))
21947 }
21948
21949 fn is_completion_trigger(
21950 &self,
21951 buffer: &Entity<Buffer>,
21952 position: language::Anchor,
21953 text: &str,
21954 trigger_in_words: bool,
21955 menu_is_open: bool,
21956 cx: &mut Context<Editor>,
21957 ) -> bool;
21958
21959 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21960
21961 fn sort_completions(&self) -> bool {
21962 true
21963 }
21964
21965 fn filter_completions(&self) -> bool {
21966 true
21967 }
21968}
21969
21970pub trait CodeActionProvider {
21971 fn id(&self) -> Arc<str>;
21972
21973 fn code_actions(
21974 &self,
21975 buffer: &Entity<Buffer>,
21976 range: Range<text::Anchor>,
21977 window: &mut Window,
21978 cx: &mut App,
21979 ) -> Task<Result<Vec<CodeAction>>>;
21980
21981 fn apply_code_action(
21982 &self,
21983 buffer_handle: Entity<Buffer>,
21984 action: CodeAction,
21985 excerpt_id: ExcerptId,
21986 push_to_history: bool,
21987 window: &mut Window,
21988 cx: &mut App,
21989 ) -> Task<Result<ProjectTransaction>>;
21990}
21991
21992impl CodeActionProvider for Entity<Project> {
21993 fn id(&self) -> Arc<str> {
21994 "project".into()
21995 }
21996
21997 fn code_actions(
21998 &self,
21999 buffer: &Entity<Buffer>,
22000 range: Range<text::Anchor>,
22001 _window: &mut Window,
22002 cx: &mut App,
22003 ) -> Task<Result<Vec<CodeAction>>> {
22004 self.update(cx, |project, cx| {
22005 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22006 let code_actions = project.code_actions(buffer, range, None, cx);
22007 cx.background_spawn(async move {
22008 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22009 Ok(code_lens_actions
22010 .context("code lens fetch")?
22011 .into_iter()
22012 .chain(code_actions.context("code action fetch")?)
22013 .collect())
22014 })
22015 })
22016 }
22017
22018 fn apply_code_action(
22019 &self,
22020 buffer_handle: Entity<Buffer>,
22021 action: CodeAction,
22022 _excerpt_id: ExcerptId,
22023 push_to_history: bool,
22024 _window: &mut Window,
22025 cx: &mut App,
22026 ) -> Task<Result<ProjectTransaction>> {
22027 self.update(cx, |project, cx| {
22028 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22029 })
22030 }
22031}
22032
22033fn snippet_completions(
22034 project: &Project,
22035 buffer: &Entity<Buffer>,
22036 buffer_position: text::Anchor,
22037 cx: &mut App,
22038) -> Task<Result<CompletionResponse>> {
22039 let languages = buffer.read(cx).languages_at(buffer_position);
22040 let snippet_store = project.snippets().read(cx);
22041
22042 let scopes: Vec<_> = languages
22043 .iter()
22044 .filter_map(|language| {
22045 let language_name = language.lsp_id();
22046 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22047
22048 if snippets.is_empty() {
22049 None
22050 } else {
22051 Some((language.default_scope(), snippets))
22052 }
22053 })
22054 .collect();
22055
22056 if scopes.is_empty() {
22057 return Task::ready(Ok(CompletionResponse {
22058 completions: vec![],
22059 is_incomplete: false,
22060 }));
22061 }
22062
22063 let snapshot = buffer.read(cx).text_snapshot();
22064 let chars: String = snapshot
22065 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22066 .collect();
22067 let executor = cx.background_executor().clone();
22068
22069 cx.background_spawn(async move {
22070 let mut is_incomplete = false;
22071 let mut completions: Vec<Completion> = Vec::new();
22072 for (scope, snippets) in scopes.into_iter() {
22073 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22074 let mut last_word = chars
22075 .chars()
22076 .take_while(|c| classifier.is_word(*c))
22077 .collect::<String>();
22078 last_word = last_word.chars().rev().collect();
22079
22080 if last_word.is_empty() {
22081 return Ok(CompletionResponse {
22082 completions: vec![],
22083 is_incomplete: true,
22084 });
22085 }
22086
22087 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22088 let to_lsp = |point: &text::Anchor| {
22089 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22090 point_to_lsp(end)
22091 };
22092 let lsp_end = to_lsp(&buffer_position);
22093
22094 let candidates = snippets
22095 .iter()
22096 .enumerate()
22097 .flat_map(|(ix, snippet)| {
22098 snippet
22099 .prefix
22100 .iter()
22101 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22102 })
22103 .collect::<Vec<StringMatchCandidate>>();
22104
22105 const MAX_RESULTS: usize = 100;
22106 let mut matches = fuzzy::match_strings(
22107 &candidates,
22108 &last_word,
22109 last_word.chars().any(|c| c.is_uppercase()),
22110 true,
22111 MAX_RESULTS,
22112 &Default::default(),
22113 executor.clone(),
22114 )
22115 .await;
22116
22117 if matches.len() >= MAX_RESULTS {
22118 is_incomplete = true;
22119 }
22120
22121 // Remove all candidates where the query's start does not match the start of any word in the candidate
22122 if let Some(query_start) = last_word.chars().next() {
22123 matches.retain(|string_match| {
22124 split_words(&string_match.string).any(|word| {
22125 // Check that the first codepoint of the word as lowercase matches the first
22126 // codepoint of the query as lowercase
22127 word.chars()
22128 .flat_map(|codepoint| codepoint.to_lowercase())
22129 .zip(query_start.to_lowercase())
22130 .all(|(word_cp, query_cp)| word_cp == query_cp)
22131 })
22132 });
22133 }
22134
22135 let matched_strings = matches
22136 .into_iter()
22137 .map(|m| m.string)
22138 .collect::<HashSet<_>>();
22139
22140 completions.extend(snippets.iter().filter_map(|snippet| {
22141 let matching_prefix = snippet
22142 .prefix
22143 .iter()
22144 .find(|prefix| matched_strings.contains(*prefix))?;
22145 let start = as_offset - last_word.len();
22146 let start = snapshot.anchor_before(start);
22147 let range = start..buffer_position;
22148 let lsp_start = to_lsp(&start);
22149 let lsp_range = lsp::Range {
22150 start: lsp_start,
22151 end: lsp_end,
22152 };
22153 Some(Completion {
22154 replace_range: range,
22155 new_text: snippet.body.clone(),
22156 source: CompletionSource::Lsp {
22157 insert_range: None,
22158 server_id: LanguageServerId(usize::MAX),
22159 resolved: true,
22160 lsp_completion: Box::new(lsp::CompletionItem {
22161 label: snippet.prefix.first().unwrap().clone(),
22162 kind: Some(CompletionItemKind::SNIPPET),
22163 label_details: snippet.description.as_ref().map(|description| {
22164 lsp::CompletionItemLabelDetails {
22165 detail: Some(description.clone()),
22166 description: None,
22167 }
22168 }),
22169 insert_text_format: Some(InsertTextFormat::SNIPPET),
22170 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22171 lsp::InsertReplaceEdit {
22172 new_text: snippet.body.clone(),
22173 insert: lsp_range,
22174 replace: lsp_range,
22175 },
22176 )),
22177 filter_text: Some(snippet.body.clone()),
22178 sort_text: Some(char::MAX.to_string()),
22179 ..lsp::CompletionItem::default()
22180 }),
22181 lsp_defaults: None,
22182 },
22183 label: CodeLabel {
22184 text: matching_prefix.clone(),
22185 runs: Vec::new(),
22186 filter_range: 0..matching_prefix.len(),
22187 },
22188 icon_path: None,
22189 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22190 single_line: snippet.name.clone().into(),
22191 plain_text: snippet
22192 .description
22193 .clone()
22194 .map(|description| description.into()),
22195 }),
22196 insert_text_mode: None,
22197 confirm: None,
22198 })
22199 }))
22200 }
22201
22202 Ok(CompletionResponse {
22203 completions,
22204 is_incomplete,
22205 })
22206 })
22207}
22208
22209impl CompletionProvider for Entity<Project> {
22210 fn completions(
22211 &self,
22212 _excerpt_id: ExcerptId,
22213 buffer: &Entity<Buffer>,
22214 buffer_position: text::Anchor,
22215 options: CompletionContext,
22216 _window: &mut Window,
22217 cx: &mut Context<Editor>,
22218 ) -> Task<Result<Vec<CompletionResponse>>> {
22219 self.update(cx, |project, cx| {
22220 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22221 let project_completions = project.completions(buffer, buffer_position, options, cx);
22222 cx.background_spawn(async move {
22223 let mut responses = project_completions.await?;
22224 let snippets = snippets.await?;
22225 if !snippets.completions.is_empty() {
22226 responses.push(snippets);
22227 }
22228 Ok(responses)
22229 })
22230 })
22231 }
22232
22233 fn resolve_completions(
22234 &self,
22235 buffer: Entity<Buffer>,
22236 completion_indices: Vec<usize>,
22237 completions: Rc<RefCell<Box<[Completion]>>>,
22238 cx: &mut Context<Editor>,
22239 ) -> Task<Result<bool>> {
22240 self.update(cx, |project, cx| {
22241 project.lsp_store().update(cx, |lsp_store, cx| {
22242 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22243 })
22244 })
22245 }
22246
22247 fn apply_additional_edits_for_completion(
22248 &self,
22249 buffer: Entity<Buffer>,
22250 completions: Rc<RefCell<Box<[Completion]>>>,
22251 completion_index: usize,
22252 push_to_history: bool,
22253 cx: &mut Context<Editor>,
22254 ) -> Task<Result<Option<language::Transaction>>> {
22255 self.update(cx, |project, cx| {
22256 project.lsp_store().update(cx, |lsp_store, cx| {
22257 lsp_store.apply_additional_edits_for_completion(
22258 buffer,
22259 completions,
22260 completion_index,
22261 push_to_history,
22262 cx,
22263 )
22264 })
22265 })
22266 }
22267
22268 fn is_completion_trigger(
22269 &self,
22270 buffer: &Entity<Buffer>,
22271 position: language::Anchor,
22272 text: &str,
22273 trigger_in_words: bool,
22274 menu_is_open: bool,
22275 cx: &mut Context<Editor>,
22276 ) -> bool {
22277 let mut chars = text.chars();
22278 let char = if let Some(char) = chars.next() {
22279 char
22280 } else {
22281 return false;
22282 };
22283 if chars.next().is_some() {
22284 return false;
22285 }
22286
22287 let buffer = buffer.read(cx);
22288 let snapshot = buffer.snapshot();
22289 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22290 return false;
22291 }
22292 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22293 if trigger_in_words && classifier.is_word(char) {
22294 return true;
22295 }
22296
22297 buffer.completion_triggers().contains(text)
22298 }
22299}
22300
22301impl SemanticsProvider for Entity<Project> {
22302 fn hover(
22303 &self,
22304 buffer: &Entity<Buffer>,
22305 position: text::Anchor,
22306 cx: &mut App,
22307 ) -> Option<Task<Vec<project::Hover>>> {
22308 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22309 }
22310
22311 fn document_highlights(
22312 &self,
22313 buffer: &Entity<Buffer>,
22314 position: text::Anchor,
22315 cx: &mut App,
22316 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22317 Some(self.update(cx, |project, cx| {
22318 project.document_highlights(buffer, position, cx)
22319 }))
22320 }
22321
22322 fn definitions(
22323 &self,
22324 buffer: &Entity<Buffer>,
22325 position: text::Anchor,
22326 kind: GotoDefinitionKind,
22327 cx: &mut App,
22328 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22329 Some(self.update(cx, |project, cx| match kind {
22330 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22331 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22332 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22333 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22334 }))
22335 }
22336
22337 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22338 self.update(cx, |project, cx| {
22339 if project
22340 .active_debug_session(cx)
22341 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22342 {
22343 return true;
22344 }
22345
22346 buffer.update(cx, |buffer, cx| {
22347 project.any_language_server_supports_inlay_hints(buffer, cx)
22348 })
22349 })
22350 }
22351
22352 fn inline_values(
22353 &self,
22354 buffer_handle: Entity<Buffer>,
22355 range: Range<text::Anchor>,
22356 cx: &mut App,
22357 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22358 self.update(cx, |project, cx| {
22359 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22360
22361 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22362 })
22363 }
22364
22365 fn inlay_hints(
22366 &self,
22367 buffer_handle: Entity<Buffer>,
22368 range: Range<text::Anchor>,
22369 cx: &mut App,
22370 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22371 Some(self.update(cx, |project, cx| {
22372 project.inlay_hints(buffer_handle, range, cx)
22373 }))
22374 }
22375
22376 fn resolve_inlay_hint(
22377 &self,
22378 hint: InlayHint,
22379 buffer_handle: Entity<Buffer>,
22380 server_id: LanguageServerId,
22381 cx: &mut App,
22382 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22383 Some(self.update(cx, |project, cx| {
22384 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22385 }))
22386 }
22387
22388 fn range_for_rename(
22389 &self,
22390 buffer: &Entity<Buffer>,
22391 position: text::Anchor,
22392 cx: &mut App,
22393 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22394 Some(self.update(cx, |project, cx| {
22395 let buffer = buffer.clone();
22396 let task = project.prepare_rename(buffer.clone(), position, cx);
22397 cx.spawn(async move |_, cx| {
22398 Ok(match task.await? {
22399 PrepareRenameResponse::Success(range) => Some(range),
22400 PrepareRenameResponse::InvalidPosition => None,
22401 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22402 // Fallback on using TreeSitter info to determine identifier range
22403 buffer.read_with(cx, |buffer, _| {
22404 let snapshot = buffer.snapshot();
22405 let (range, kind) = snapshot.surrounding_word(position, false);
22406 if kind != Some(CharKind::Word) {
22407 return None;
22408 }
22409 Some(
22410 snapshot.anchor_before(range.start)
22411 ..snapshot.anchor_after(range.end),
22412 )
22413 })?
22414 }
22415 })
22416 })
22417 }))
22418 }
22419
22420 fn perform_rename(
22421 &self,
22422 buffer: &Entity<Buffer>,
22423 position: text::Anchor,
22424 new_name: String,
22425 cx: &mut App,
22426 ) -> Option<Task<Result<ProjectTransaction>>> {
22427 Some(self.update(cx, |project, cx| {
22428 project.perform_rename(buffer.clone(), position, new_name, cx)
22429 }))
22430 }
22431}
22432
22433fn inlay_hint_settings(
22434 location: Anchor,
22435 snapshot: &MultiBufferSnapshot,
22436 cx: &mut Context<Editor>,
22437) -> InlayHintSettings {
22438 let file = snapshot.file_at(location);
22439 let language = snapshot.language_at(location).map(|l| l.name());
22440 language_settings(language, file, cx).inlay_hints
22441}
22442
22443fn consume_contiguous_rows(
22444 contiguous_row_selections: &mut Vec<Selection<Point>>,
22445 selection: &Selection<Point>,
22446 display_map: &DisplaySnapshot,
22447 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22448) -> (MultiBufferRow, MultiBufferRow) {
22449 contiguous_row_selections.push(selection.clone());
22450 let start_row = starting_row(selection, display_map);
22451 let mut end_row = ending_row(selection, display_map);
22452
22453 while let Some(next_selection) = selections.peek() {
22454 if next_selection.start.row <= end_row.0 {
22455 end_row = ending_row(next_selection, display_map);
22456 contiguous_row_selections.push(selections.next().unwrap().clone());
22457 } else {
22458 break;
22459 }
22460 }
22461 (start_row, end_row)
22462}
22463
22464fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22465 if selection.start.column > 0 {
22466 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22467 } else {
22468 MultiBufferRow(selection.start.row)
22469 }
22470}
22471
22472fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22473 if next_selection.end.column > 0 || next_selection.is_empty() {
22474 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22475 } else {
22476 MultiBufferRow(next_selection.end.row)
22477 }
22478}
22479
22480impl EditorSnapshot {
22481 pub fn remote_selections_in_range<'a>(
22482 &'a self,
22483 range: &'a Range<Anchor>,
22484 collaboration_hub: &dyn CollaborationHub,
22485 cx: &'a App,
22486 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22487 let participant_names = collaboration_hub.user_names(cx);
22488 let participant_indices = collaboration_hub.user_participant_indices(cx);
22489 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22490 let collaborators_by_replica_id = collaborators_by_peer_id
22491 .values()
22492 .map(|collaborator| (collaborator.replica_id, collaborator))
22493 .collect::<HashMap<_, _>>();
22494 self.buffer_snapshot
22495 .selections_in_range(range, false)
22496 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22497 if replica_id == AGENT_REPLICA_ID {
22498 Some(RemoteSelection {
22499 replica_id,
22500 selection,
22501 cursor_shape,
22502 line_mode,
22503 collaborator_id: CollaboratorId::Agent,
22504 user_name: Some("Agent".into()),
22505 color: cx.theme().players().agent(),
22506 })
22507 } else {
22508 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22509 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22510 let user_name = participant_names.get(&collaborator.user_id).cloned();
22511 Some(RemoteSelection {
22512 replica_id,
22513 selection,
22514 cursor_shape,
22515 line_mode,
22516 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22517 user_name,
22518 color: if let Some(index) = participant_index {
22519 cx.theme().players().color_for_participant(index.0)
22520 } else {
22521 cx.theme().players().absent()
22522 },
22523 })
22524 }
22525 })
22526 }
22527
22528 pub fn hunks_for_ranges(
22529 &self,
22530 ranges: impl IntoIterator<Item = Range<Point>>,
22531 ) -> Vec<MultiBufferDiffHunk> {
22532 let mut hunks = Vec::new();
22533 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22534 HashMap::default();
22535 for query_range in ranges {
22536 let query_rows =
22537 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22538 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22539 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22540 ) {
22541 // Include deleted hunks that are adjacent to the query range, because
22542 // otherwise they would be missed.
22543 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22544 if hunk.status().is_deleted() {
22545 intersects_range |= hunk.row_range.start == query_rows.end;
22546 intersects_range |= hunk.row_range.end == query_rows.start;
22547 }
22548 if intersects_range {
22549 if !processed_buffer_rows
22550 .entry(hunk.buffer_id)
22551 .or_default()
22552 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22553 {
22554 continue;
22555 }
22556 hunks.push(hunk);
22557 }
22558 }
22559 }
22560
22561 hunks
22562 }
22563
22564 fn display_diff_hunks_for_rows<'a>(
22565 &'a self,
22566 display_rows: Range<DisplayRow>,
22567 folded_buffers: &'a HashSet<BufferId>,
22568 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22569 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22570 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22571
22572 self.buffer_snapshot
22573 .diff_hunks_in_range(buffer_start..buffer_end)
22574 .filter_map(|hunk| {
22575 if folded_buffers.contains(&hunk.buffer_id) {
22576 return None;
22577 }
22578
22579 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22580 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22581
22582 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22583 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22584
22585 let display_hunk = if hunk_display_start.column() != 0 {
22586 DisplayDiffHunk::Folded {
22587 display_row: hunk_display_start.row(),
22588 }
22589 } else {
22590 let mut end_row = hunk_display_end.row();
22591 if hunk_display_end.column() > 0 {
22592 end_row.0 += 1;
22593 }
22594 let is_created_file = hunk.is_created_file();
22595 DisplayDiffHunk::Unfolded {
22596 status: hunk.status(),
22597 diff_base_byte_range: hunk.diff_base_byte_range,
22598 display_row_range: hunk_display_start.row()..end_row,
22599 multi_buffer_range: Anchor::range_in_buffer(
22600 hunk.excerpt_id,
22601 hunk.buffer_id,
22602 hunk.buffer_range,
22603 ),
22604 is_created_file,
22605 }
22606 };
22607
22608 Some(display_hunk)
22609 })
22610 }
22611
22612 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22613 self.display_snapshot.buffer_snapshot.language_at(position)
22614 }
22615
22616 pub fn is_focused(&self) -> bool {
22617 self.is_focused
22618 }
22619
22620 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22621 self.placeholder_text.as_ref()
22622 }
22623
22624 pub fn scroll_position(&self) -> gpui::Point<f32> {
22625 self.scroll_anchor.scroll_position(&self.display_snapshot)
22626 }
22627
22628 fn gutter_dimensions(
22629 &self,
22630 font_id: FontId,
22631 font_size: Pixels,
22632 max_line_number_width: Pixels,
22633 cx: &App,
22634 ) -> Option<GutterDimensions> {
22635 if !self.show_gutter {
22636 return None;
22637 }
22638
22639 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22640 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22641
22642 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22643 matches!(
22644 ProjectSettings::get_global(cx).git.git_gutter,
22645 Some(GitGutterSetting::TrackedFiles)
22646 )
22647 });
22648 let gutter_settings = EditorSettings::get_global(cx).gutter;
22649 let show_line_numbers = self
22650 .show_line_numbers
22651 .unwrap_or(gutter_settings.line_numbers);
22652 let line_gutter_width = if show_line_numbers {
22653 // Avoid flicker-like gutter resizes when the line number gains another digit by
22654 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22655 let min_width_for_number_on_gutter =
22656 ch_advance * gutter_settings.min_line_number_digits as f32;
22657 max_line_number_width.max(min_width_for_number_on_gutter)
22658 } else {
22659 0.0.into()
22660 };
22661
22662 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22663 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22664
22665 let git_blame_entries_width =
22666 self.git_blame_gutter_max_author_length
22667 .map(|max_author_length| {
22668 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22669 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22670
22671 /// The number of characters to dedicate to gaps and margins.
22672 const SPACING_WIDTH: usize = 4;
22673
22674 let max_char_count = max_author_length.min(renderer.max_author_length())
22675 + ::git::SHORT_SHA_LENGTH
22676 + MAX_RELATIVE_TIMESTAMP.len()
22677 + SPACING_WIDTH;
22678
22679 ch_advance * max_char_count
22680 });
22681
22682 let is_singleton = self.buffer_snapshot.is_singleton();
22683
22684 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22685 left_padding += if !is_singleton {
22686 ch_width * 4.0
22687 } else if show_runnables || show_breakpoints {
22688 ch_width * 3.0
22689 } else if show_git_gutter && show_line_numbers {
22690 ch_width * 2.0
22691 } else if show_git_gutter || show_line_numbers {
22692 ch_width
22693 } else {
22694 px(0.)
22695 };
22696
22697 let shows_folds = is_singleton && gutter_settings.folds;
22698
22699 let right_padding = if shows_folds && show_line_numbers {
22700 ch_width * 4.0
22701 } else if shows_folds || (!is_singleton && show_line_numbers) {
22702 ch_width * 3.0
22703 } else if show_line_numbers {
22704 ch_width
22705 } else {
22706 px(0.)
22707 };
22708
22709 Some(GutterDimensions {
22710 left_padding,
22711 right_padding,
22712 width: line_gutter_width + left_padding + right_padding,
22713 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22714 git_blame_entries_width,
22715 })
22716 }
22717
22718 pub fn render_crease_toggle(
22719 &self,
22720 buffer_row: MultiBufferRow,
22721 row_contains_cursor: bool,
22722 editor: Entity<Editor>,
22723 window: &mut Window,
22724 cx: &mut App,
22725 ) -> Option<AnyElement> {
22726 let folded = self.is_line_folded(buffer_row);
22727 let mut is_foldable = false;
22728
22729 if let Some(crease) = self
22730 .crease_snapshot
22731 .query_row(buffer_row, &self.buffer_snapshot)
22732 {
22733 is_foldable = true;
22734 match crease {
22735 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22736 if let Some(render_toggle) = render_toggle {
22737 let toggle_callback =
22738 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22739 if folded {
22740 editor.update(cx, |editor, cx| {
22741 editor.fold_at(buffer_row, window, cx)
22742 });
22743 } else {
22744 editor.update(cx, |editor, cx| {
22745 editor.unfold_at(buffer_row, window, cx)
22746 });
22747 }
22748 });
22749 return Some((render_toggle)(
22750 buffer_row,
22751 folded,
22752 toggle_callback,
22753 window,
22754 cx,
22755 ));
22756 }
22757 }
22758 }
22759 }
22760
22761 is_foldable |= self.starts_indent(buffer_row);
22762
22763 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22764 Some(
22765 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22766 .toggle_state(folded)
22767 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22768 if folded {
22769 this.unfold_at(buffer_row, window, cx);
22770 } else {
22771 this.fold_at(buffer_row, window, cx);
22772 }
22773 }))
22774 .into_any_element(),
22775 )
22776 } else {
22777 None
22778 }
22779 }
22780
22781 pub fn render_crease_trailer(
22782 &self,
22783 buffer_row: MultiBufferRow,
22784 window: &mut Window,
22785 cx: &mut App,
22786 ) -> Option<AnyElement> {
22787 let folded = self.is_line_folded(buffer_row);
22788 if let Crease::Inline { render_trailer, .. } = self
22789 .crease_snapshot
22790 .query_row(buffer_row, &self.buffer_snapshot)?
22791 {
22792 let render_trailer = render_trailer.as_ref()?;
22793 Some(render_trailer(buffer_row, folded, window, cx))
22794 } else {
22795 None
22796 }
22797 }
22798}
22799
22800impl Deref for EditorSnapshot {
22801 type Target = DisplaySnapshot;
22802
22803 fn deref(&self) -> &Self::Target {
22804 &self.display_snapshot
22805 }
22806}
22807
22808#[derive(Clone, Debug, PartialEq, Eq)]
22809pub enum EditorEvent {
22810 InputIgnored {
22811 text: Arc<str>,
22812 },
22813 InputHandled {
22814 utf16_range_to_replace: Option<Range<isize>>,
22815 text: Arc<str>,
22816 },
22817 ExcerptsAdded {
22818 buffer: Entity<Buffer>,
22819 predecessor: ExcerptId,
22820 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22821 },
22822 ExcerptsRemoved {
22823 ids: Vec<ExcerptId>,
22824 removed_buffer_ids: Vec<BufferId>,
22825 },
22826 BufferFoldToggled {
22827 ids: Vec<ExcerptId>,
22828 folded: bool,
22829 },
22830 ExcerptsEdited {
22831 ids: Vec<ExcerptId>,
22832 },
22833 ExcerptsExpanded {
22834 ids: Vec<ExcerptId>,
22835 },
22836 BufferEdited,
22837 Edited {
22838 transaction_id: clock::Lamport,
22839 },
22840 Reparsed(BufferId),
22841 Focused,
22842 FocusedIn,
22843 Blurred,
22844 DirtyChanged,
22845 Saved,
22846 TitleChanged,
22847 DiffBaseChanged,
22848 SelectionsChanged {
22849 local: bool,
22850 },
22851 ScrollPositionChanged {
22852 local: bool,
22853 autoscroll: bool,
22854 },
22855 Closed,
22856 TransactionUndone {
22857 transaction_id: clock::Lamport,
22858 },
22859 TransactionBegun {
22860 transaction_id: clock::Lamport,
22861 },
22862 Reloaded,
22863 CursorShapeChanged,
22864 BreadcrumbsChanged,
22865 PushedToNavHistory {
22866 anchor: Anchor,
22867 is_deactivate: bool,
22868 },
22869}
22870
22871impl EventEmitter<EditorEvent> for Editor {}
22872
22873impl Focusable for Editor {
22874 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22875 self.focus_handle.clone()
22876 }
22877}
22878
22879impl Render for Editor {
22880 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22881 let settings = ThemeSettings::get_global(cx);
22882
22883 let mut text_style = match self.mode {
22884 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22885 color: cx.theme().colors().editor_foreground,
22886 font_family: settings.ui_font.family.clone(),
22887 font_features: settings.ui_font.features.clone(),
22888 font_fallbacks: settings.ui_font.fallbacks.clone(),
22889 font_size: rems(0.875).into(),
22890 font_weight: settings.ui_font.weight,
22891 line_height: relative(settings.buffer_line_height.value()),
22892 ..Default::default()
22893 },
22894 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22895 color: cx.theme().colors().editor_foreground,
22896 font_family: settings.buffer_font.family.clone(),
22897 font_features: settings.buffer_font.features.clone(),
22898 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22899 font_size: settings.buffer_font_size(cx).into(),
22900 font_weight: settings.buffer_font.weight,
22901 line_height: relative(settings.buffer_line_height.value()),
22902 ..Default::default()
22903 },
22904 };
22905 if let Some(text_style_refinement) = &self.text_style_refinement {
22906 text_style.refine(text_style_refinement)
22907 }
22908
22909 let background = match self.mode {
22910 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22911 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22912 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22913 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22914 };
22915
22916 EditorElement::new(
22917 &cx.entity(),
22918 EditorStyle {
22919 background,
22920 border: cx.theme().colors().border,
22921 local_player: cx.theme().players().local(),
22922 text: text_style,
22923 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22924 syntax: cx.theme().syntax().clone(),
22925 status: cx.theme().status().clone(),
22926 inlay_hints_style: make_inlay_hints_style(cx),
22927 edit_prediction_styles: make_suggestion_styles(cx),
22928 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22929 show_underlines: self.diagnostics_enabled(),
22930 },
22931 )
22932 }
22933}
22934
22935impl EntityInputHandler for Editor {
22936 fn text_for_range(
22937 &mut self,
22938 range_utf16: Range<usize>,
22939 adjusted_range: &mut Option<Range<usize>>,
22940 _: &mut Window,
22941 cx: &mut Context<Self>,
22942 ) -> Option<String> {
22943 let snapshot = self.buffer.read(cx).read(cx);
22944 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22945 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22946 if (start.0..end.0) != range_utf16 {
22947 adjusted_range.replace(start.0..end.0);
22948 }
22949 Some(snapshot.text_for_range(start..end).collect())
22950 }
22951
22952 fn selected_text_range(
22953 &mut self,
22954 ignore_disabled_input: bool,
22955 _: &mut Window,
22956 cx: &mut Context<Self>,
22957 ) -> Option<UTF16Selection> {
22958 // Prevent the IME menu from appearing when holding down an alphabetic key
22959 // while input is disabled.
22960 if !ignore_disabled_input && !self.input_enabled {
22961 return None;
22962 }
22963
22964 let selection = self.selections.newest::<OffsetUtf16>(cx);
22965 let range = selection.range();
22966
22967 Some(UTF16Selection {
22968 range: range.start.0..range.end.0,
22969 reversed: selection.reversed,
22970 })
22971 }
22972
22973 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22974 let snapshot = self.buffer.read(cx).read(cx);
22975 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22976 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22977 }
22978
22979 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22980 self.clear_highlights::<InputComposition>(cx);
22981 self.ime_transaction.take();
22982 }
22983
22984 fn replace_text_in_range(
22985 &mut self,
22986 range_utf16: Option<Range<usize>>,
22987 text: &str,
22988 window: &mut Window,
22989 cx: &mut Context<Self>,
22990 ) {
22991 if !self.input_enabled {
22992 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22993 return;
22994 }
22995
22996 self.transact(window, cx, |this, window, cx| {
22997 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22998 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22999 Some(this.selection_replacement_ranges(range_utf16, cx))
23000 } else {
23001 this.marked_text_ranges(cx)
23002 };
23003
23004 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23005 let newest_selection_id = this.selections.newest_anchor().id;
23006 this.selections
23007 .all::<OffsetUtf16>(cx)
23008 .iter()
23009 .zip(ranges_to_replace.iter())
23010 .find_map(|(selection, range)| {
23011 if selection.id == newest_selection_id {
23012 Some(
23013 (range.start.0 as isize - selection.head().0 as isize)
23014 ..(range.end.0 as isize - selection.head().0 as isize),
23015 )
23016 } else {
23017 None
23018 }
23019 })
23020 });
23021
23022 cx.emit(EditorEvent::InputHandled {
23023 utf16_range_to_replace: range_to_replace,
23024 text: text.into(),
23025 });
23026
23027 if let Some(new_selected_ranges) = new_selected_ranges {
23028 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23029 selections.select_ranges(new_selected_ranges)
23030 });
23031 this.backspace(&Default::default(), window, cx);
23032 }
23033
23034 this.handle_input(text, window, cx);
23035 });
23036
23037 if let Some(transaction) = self.ime_transaction {
23038 self.buffer.update(cx, |buffer, cx| {
23039 buffer.group_until_transaction(transaction, cx);
23040 });
23041 }
23042
23043 self.unmark_text(window, cx);
23044 }
23045
23046 fn replace_and_mark_text_in_range(
23047 &mut self,
23048 range_utf16: Option<Range<usize>>,
23049 text: &str,
23050 new_selected_range_utf16: Option<Range<usize>>,
23051 window: &mut Window,
23052 cx: &mut Context<Self>,
23053 ) {
23054 if !self.input_enabled {
23055 return;
23056 }
23057
23058 let transaction = self.transact(window, cx, |this, window, cx| {
23059 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23060 let snapshot = this.buffer.read(cx).read(cx);
23061 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23062 for marked_range in &mut marked_ranges {
23063 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23064 marked_range.start.0 += relative_range_utf16.start;
23065 marked_range.start =
23066 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23067 marked_range.end =
23068 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23069 }
23070 }
23071 Some(marked_ranges)
23072 } else if let Some(range_utf16) = range_utf16 {
23073 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23074 Some(this.selection_replacement_ranges(range_utf16, cx))
23075 } else {
23076 None
23077 };
23078
23079 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23080 let newest_selection_id = this.selections.newest_anchor().id;
23081 this.selections
23082 .all::<OffsetUtf16>(cx)
23083 .iter()
23084 .zip(ranges_to_replace.iter())
23085 .find_map(|(selection, range)| {
23086 if selection.id == newest_selection_id {
23087 Some(
23088 (range.start.0 as isize - selection.head().0 as isize)
23089 ..(range.end.0 as isize - selection.head().0 as isize),
23090 )
23091 } else {
23092 None
23093 }
23094 })
23095 });
23096
23097 cx.emit(EditorEvent::InputHandled {
23098 utf16_range_to_replace: range_to_replace,
23099 text: text.into(),
23100 });
23101
23102 if let Some(ranges) = ranges_to_replace {
23103 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23104 s.select_ranges(ranges)
23105 });
23106 }
23107
23108 let marked_ranges = {
23109 let snapshot = this.buffer.read(cx).read(cx);
23110 this.selections
23111 .disjoint_anchors()
23112 .iter()
23113 .map(|selection| {
23114 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23115 })
23116 .collect::<Vec<_>>()
23117 };
23118
23119 if text.is_empty() {
23120 this.unmark_text(window, cx);
23121 } else {
23122 this.highlight_text::<InputComposition>(
23123 marked_ranges.clone(),
23124 HighlightStyle {
23125 underline: Some(UnderlineStyle {
23126 thickness: px(1.),
23127 color: None,
23128 wavy: false,
23129 }),
23130 ..Default::default()
23131 },
23132 cx,
23133 );
23134 }
23135
23136 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23137 let use_autoclose = this.use_autoclose;
23138 let use_auto_surround = this.use_auto_surround;
23139 this.set_use_autoclose(false);
23140 this.set_use_auto_surround(false);
23141 this.handle_input(text, window, cx);
23142 this.set_use_autoclose(use_autoclose);
23143 this.set_use_auto_surround(use_auto_surround);
23144
23145 if let Some(new_selected_range) = new_selected_range_utf16 {
23146 let snapshot = this.buffer.read(cx).read(cx);
23147 let new_selected_ranges = marked_ranges
23148 .into_iter()
23149 .map(|marked_range| {
23150 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23151 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23152 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23153 snapshot.clip_offset_utf16(new_start, Bias::Left)
23154 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23155 })
23156 .collect::<Vec<_>>();
23157
23158 drop(snapshot);
23159 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23160 selections.select_ranges(new_selected_ranges)
23161 });
23162 }
23163 });
23164
23165 self.ime_transaction = self.ime_transaction.or(transaction);
23166 if let Some(transaction) = self.ime_transaction {
23167 self.buffer.update(cx, |buffer, cx| {
23168 buffer.group_until_transaction(transaction, cx);
23169 });
23170 }
23171
23172 if self.text_highlights::<InputComposition>(cx).is_none() {
23173 self.ime_transaction.take();
23174 }
23175 }
23176
23177 fn bounds_for_range(
23178 &mut self,
23179 range_utf16: Range<usize>,
23180 element_bounds: gpui::Bounds<Pixels>,
23181 window: &mut Window,
23182 cx: &mut Context<Self>,
23183 ) -> Option<gpui::Bounds<Pixels>> {
23184 let text_layout_details = self.text_layout_details(window);
23185 let CharacterDimensions {
23186 em_width,
23187 em_advance,
23188 line_height,
23189 } = self.character_dimensions(window);
23190
23191 let snapshot = self.snapshot(window, cx);
23192 let scroll_position = snapshot.scroll_position();
23193 let scroll_left = scroll_position.x * em_advance;
23194
23195 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23196 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23197 + self.gutter_dimensions.full_width();
23198 let y = line_height * (start.row().as_f32() - scroll_position.y);
23199
23200 Some(Bounds {
23201 origin: element_bounds.origin + point(x, y),
23202 size: size(em_width, line_height),
23203 })
23204 }
23205
23206 fn character_index_for_point(
23207 &mut self,
23208 point: gpui::Point<Pixels>,
23209 _window: &mut Window,
23210 _cx: &mut Context<Self>,
23211 ) -> Option<usize> {
23212 let position_map = self.last_position_map.as_ref()?;
23213 if !position_map.text_hitbox.contains(&point) {
23214 return None;
23215 }
23216 let display_point = position_map.point_for_position(point).previous_valid;
23217 let anchor = position_map
23218 .snapshot
23219 .display_point_to_anchor(display_point, Bias::Left);
23220 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23221 Some(utf16_offset.0)
23222 }
23223}
23224
23225trait SelectionExt {
23226 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23227 fn spanned_rows(
23228 &self,
23229 include_end_if_at_line_start: bool,
23230 map: &DisplaySnapshot,
23231 ) -> Range<MultiBufferRow>;
23232}
23233
23234impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23235 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23236 let start = self
23237 .start
23238 .to_point(&map.buffer_snapshot)
23239 .to_display_point(map);
23240 let end = self
23241 .end
23242 .to_point(&map.buffer_snapshot)
23243 .to_display_point(map);
23244 if self.reversed {
23245 end..start
23246 } else {
23247 start..end
23248 }
23249 }
23250
23251 fn spanned_rows(
23252 &self,
23253 include_end_if_at_line_start: bool,
23254 map: &DisplaySnapshot,
23255 ) -> Range<MultiBufferRow> {
23256 let start = self.start.to_point(&map.buffer_snapshot);
23257 let mut end = self.end.to_point(&map.buffer_snapshot);
23258 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23259 end.row -= 1;
23260 }
23261
23262 let buffer_start = map.prev_line_boundary(start).0;
23263 let buffer_end = map.next_line_boundary(end).0;
23264 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23265 }
23266}
23267
23268impl<T: InvalidationRegion> InvalidationStack<T> {
23269 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23270 where
23271 S: Clone + ToOffset,
23272 {
23273 while let Some(region) = self.last() {
23274 let all_selections_inside_invalidation_ranges =
23275 if selections.len() == region.ranges().len() {
23276 selections
23277 .iter()
23278 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23279 .all(|(selection, invalidation_range)| {
23280 let head = selection.head().to_offset(buffer);
23281 invalidation_range.start <= head && invalidation_range.end >= head
23282 })
23283 } else {
23284 false
23285 };
23286
23287 if all_selections_inside_invalidation_ranges {
23288 break;
23289 } else {
23290 self.pop();
23291 }
23292 }
23293 }
23294}
23295
23296impl<T> Default for InvalidationStack<T> {
23297 fn default() -> Self {
23298 Self(Default::default())
23299 }
23300}
23301
23302impl<T> Deref for InvalidationStack<T> {
23303 type Target = Vec<T>;
23304
23305 fn deref(&self) -> &Self::Target {
23306 &self.0
23307 }
23308}
23309
23310impl<T> DerefMut for InvalidationStack<T> {
23311 fn deref_mut(&mut self) -> &mut Self::Target {
23312 &mut self.0
23313 }
23314}
23315
23316impl InvalidationRegion for SnippetState {
23317 fn ranges(&self) -> &[Range<Anchor>] {
23318 &self.ranges[self.active_index]
23319 }
23320}
23321
23322fn edit_prediction_edit_text(
23323 current_snapshot: &BufferSnapshot,
23324 edits: &[(Range<Anchor>, String)],
23325 edit_preview: &EditPreview,
23326 include_deletions: bool,
23327 cx: &App,
23328) -> HighlightedText {
23329 let edits = edits
23330 .iter()
23331 .map(|(anchor, text)| {
23332 (
23333 anchor.start.text_anchor..anchor.end.text_anchor,
23334 text.clone(),
23335 )
23336 })
23337 .collect::<Vec<_>>();
23338
23339 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23340}
23341
23342fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23343 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23344 // Just show the raw edit text with basic styling
23345 let mut text = String::new();
23346 let mut highlights = Vec::new();
23347
23348 let insertion_highlight_style = HighlightStyle {
23349 color: Some(cx.theme().colors().text),
23350 ..Default::default()
23351 };
23352
23353 for (_, edit_text) in edits {
23354 let start_offset = text.len();
23355 text.push_str(edit_text);
23356 let end_offset = text.len();
23357
23358 if start_offset < end_offset {
23359 highlights.push((start_offset..end_offset, insertion_highlight_style));
23360 }
23361 }
23362
23363 HighlightedText {
23364 text: text.into(),
23365 highlights,
23366 }
23367}
23368
23369pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23370 match severity {
23371 lsp::DiagnosticSeverity::ERROR => colors.error,
23372 lsp::DiagnosticSeverity::WARNING => colors.warning,
23373 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23374 lsp::DiagnosticSeverity::HINT => colors.info,
23375 _ => colors.ignored,
23376 }
23377}
23378
23379pub fn styled_runs_for_code_label<'a>(
23380 label: &'a CodeLabel,
23381 syntax_theme: &'a theme::SyntaxTheme,
23382) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23383 let fade_out = HighlightStyle {
23384 fade_out: Some(0.35),
23385 ..Default::default()
23386 };
23387
23388 let mut prev_end = label.filter_range.end;
23389 label
23390 .runs
23391 .iter()
23392 .enumerate()
23393 .flat_map(move |(ix, (range, highlight_id))| {
23394 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23395 style
23396 } else {
23397 return Default::default();
23398 };
23399 let mut muted_style = style;
23400 muted_style.highlight(fade_out);
23401
23402 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23403 if range.start >= label.filter_range.end {
23404 if range.start > prev_end {
23405 runs.push((prev_end..range.start, fade_out));
23406 }
23407 runs.push((range.clone(), muted_style));
23408 } else if range.end <= label.filter_range.end {
23409 runs.push((range.clone(), style));
23410 } else {
23411 runs.push((range.start..label.filter_range.end, style));
23412 runs.push((label.filter_range.end..range.end, muted_style));
23413 }
23414 prev_end = cmp::max(prev_end, range.end);
23415
23416 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23417 runs.push((prev_end..label.text.len(), fade_out));
23418 }
23419
23420 runs
23421 })
23422}
23423
23424pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23425 let mut prev_index = 0;
23426 let mut prev_codepoint: Option<char> = None;
23427 text.char_indices()
23428 .chain([(text.len(), '\0')])
23429 .filter_map(move |(index, codepoint)| {
23430 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23431 let is_boundary = index == text.len()
23432 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23433 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23434 if is_boundary {
23435 let chunk = &text[prev_index..index];
23436 prev_index = index;
23437 Some(chunk)
23438 } else {
23439 None
23440 }
23441 })
23442}
23443
23444pub trait RangeToAnchorExt: Sized {
23445 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23446
23447 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23448 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23449 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23450 }
23451}
23452
23453impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23454 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23455 let start_offset = self.start.to_offset(snapshot);
23456 let end_offset = self.end.to_offset(snapshot);
23457 if start_offset == end_offset {
23458 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23459 } else {
23460 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23461 }
23462 }
23463}
23464
23465pub trait RowExt {
23466 fn as_f32(&self) -> f32;
23467
23468 fn next_row(&self) -> Self;
23469
23470 fn previous_row(&self) -> Self;
23471
23472 fn minus(&self, other: Self) -> u32;
23473}
23474
23475impl RowExt for DisplayRow {
23476 fn as_f32(&self) -> f32 {
23477 self.0 as f32
23478 }
23479
23480 fn next_row(&self) -> Self {
23481 Self(self.0 + 1)
23482 }
23483
23484 fn previous_row(&self) -> Self {
23485 Self(self.0.saturating_sub(1))
23486 }
23487
23488 fn minus(&self, other: Self) -> u32 {
23489 self.0 - other.0
23490 }
23491}
23492
23493impl RowExt for MultiBufferRow {
23494 fn as_f32(&self) -> f32 {
23495 self.0 as f32
23496 }
23497
23498 fn next_row(&self) -> Self {
23499 Self(self.0 + 1)
23500 }
23501
23502 fn previous_row(&self) -> Self {
23503 Self(self.0.saturating_sub(1))
23504 }
23505
23506 fn minus(&self, other: Self) -> u32 {
23507 self.0 - other.0
23508 }
23509}
23510
23511trait RowRangeExt {
23512 type Row;
23513
23514 fn len(&self) -> usize;
23515
23516 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23517}
23518
23519impl RowRangeExt for Range<MultiBufferRow> {
23520 type Row = MultiBufferRow;
23521
23522 fn len(&self) -> usize {
23523 (self.end.0 - self.start.0) as usize
23524 }
23525
23526 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23527 (self.start.0..self.end.0).map(MultiBufferRow)
23528 }
23529}
23530
23531impl RowRangeExt for Range<DisplayRow> {
23532 type Row = DisplayRow;
23533
23534 fn len(&self) -> usize {
23535 (self.end.0 - self.start.0) as usize
23536 }
23537
23538 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23539 (self.start.0..self.end.0).map(DisplayRow)
23540 }
23541}
23542
23543/// If select range has more than one line, we
23544/// just point the cursor to range.start.
23545fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23546 if range.start.row == range.end.row {
23547 range
23548 } else {
23549 range.start..range.start
23550 }
23551}
23552pub struct KillRing(ClipboardItem);
23553impl Global for KillRing {}
23554
23555const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23556
23557enum BreakpointPromptEditAction {
23558 Log,
23559 Condition,
23560 HitCondition,
23561}
23562
23563struct BreakpointPromptEditor {
23564 pub(crate) prompt: Entity<Editor>,
23565 editor: WeakEntity<Editor>,
23566 breakpoint_anchor: Anchor,
23567 breakpoint: Breakpoint,
23568 edit_action: BreakpointPromptEditAction,
23569 block_ids: HashSet<CustomBlockId>,
23570 editor_margins: Arc<Mutex<EditorMargins>>,
23571 _subscriptions: Vec<Subscription>,
23572}
23573
23574impl BreakpointPromptEditor {
23575 const MAX_LINES: u8 = 4;
23576
23577 fn new(
23578 editor: WeakEntity<Editor>,
23579 breakpoint_anchor: Anchor,
23580 breakpoint: Breakpoint,
23581 edit_action: BreakpointPromptEditAction,
23582 window: &mut Window,
23583 cx: &mut Context<Self>,
23584 ) -> Self {
23585 let base_text = match edit_action {
23586 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23587 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23588 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23589 }
23590 .map(|msg| msg.to_string())
23591 .unwrap_or_default();
23592
23593 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23594 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23595
23596 let prompt = cx.new(|cx| {
23597 let mut prompt = Editor::new(
23598 EditorMode::AutoHeight {
23599 min_lines: 1,
23600 max_lines: Some(Self::MAX_LINES as usize),
23601 },
23602 buffer,
23603 None,
23604 window,
23605 cx,
23606 );
23607 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23608 prompt.set_show_cursor_when_unfocused(false, cx);
23609 prompt.set_placeholder_text(
23610 match edit_action {
23611 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23612 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23613 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23614 },
23615 cx,
23616 );
23617
23618 prompt
23619 });
23620
23621 Self {
23622 prompt,
23623 editor,
23624 breakpoint_anchor,
23625 breakpoint,
23626 edit_action,
23627 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23628 block_ids: Default::default(),
23629 _subscriptions: vec![],
23630 }
23631 }
23632
23633 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23634 self.block_ids.extend(block_ids)
23635 }
23636
23637 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23638 if let Some(editor) = self.editor.upgrade() {
23639 let message = self
23640 .prompt
23641 .read(cx)
23642 .buffer
23643 .read(cx)
23644 .as_singleton()
23645 .expect("A multi buffer in breakpoint prompt isn't possible")
23646 .read(cx)
23647 .as_rope()
23648 .to_string();
23649
23650 editor.update(cx, |editor, cx| {
23651 editor.edit_breakpoint_at_anchor(
23652 self.breakpoint_anchor,
23653 self.breakpoint.clone(),
23654 match self.edit_action {
23655 BreakpointPromptEditAction::Log => {
23656 BreakpointEditAction::EditLogMessage(message.into())
23657 }
23658 BreakpointPromptEditAction::Condition => {
23659 BreakpointEditAction::EditCondition(message.into())
23660 }
23661 BreakpointPromptEditAction::HitCondition => {
23662 BreakpointEditAction::EditHitCondition(message.into())
23663 }
23664 },
23665 cx,
23666 );
23667
23668 editor.remove_blocks(self.block_ids.clone(), None, cx);
23669 cx.focus_self(window);
23670 });
23671 }
23672 }
23673
23674 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23675 self.editor
23676 .update(cx, |editor, cx| {
23677 editor.remove_blocks(self.block_ids.clone(), None, cx);
23678 window.focus(&editor.focus_handle);
23679 })
23680 .log_err();
23681 }
23682
23683 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23684 let settings = ThemeSettings::get_global(cx);
23685 let text_style = TextStyle {
23686 color: if self.prompt.read(cx).read_only(cx) {
23687 cx.theme().colors().text_disabled
23688 } else {
23689 cx.theme().colors().text
23690 },
23691 font_family: settings.buffer_font.family.clone(),
23692 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23693 font_size: settings.buffer_font_size(cx).into(),
23694 font_weight: settings.buffer_font.weight,
23695 line_height: relative(settings.buffer_line_height.value()),
23696 ..Default::default()
23697 };
23698 EditorElement::new(
23699 &self.prompt,
23700 EditorStyle {
23701 background: cx.theme().colors().editor_background,
23702 local_player: cx.theme().players().local(),
23703 text: text_style,
23704 ..Default::default()
23705 },
23706 )
23707 }
23708}
23709
23710impl Render for BreakpointPromptEditor {
23711 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23712 let editor_margins = *self.editor_margins.lock();
23713 let gutter_dimensions = editor_margins.gutter;
23714 h_flex()
23715 .key_context("Editor")
23716 .bg(cx.theme().colors().editor_background)
23717 .border_y_1()
23718 .border_color(cx.theme().status().info_border)
23719 .size_full()
23720 .py(window.line_height() / 2.5)
23721 .on_action(cx.listener(Self::confirm))
23722 .on_action(cx.listener(Self::cancel))
23723 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23724 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23725 }
23726}
23727
23728impl Focusable for BreakpointPromptEditor {
23729 fn focus_handle(&self, cx: &App) -> FocusHandle {
23730 self.prompt.focus_handle(cx)
23731 }
23732}
23733
23734fn all_edits_insertions_or_deletions(
23735 edits: &Vec<(Range<Anchor>, String)>,
23736 snapshot: &MultiBufferSnapshot,
23737) -> bool {
23738 let mut all_insertions = true;
23739 let mut all_deletions = true;
23740
23741 for (range, new_text) in edits.iter() {
23742 let range_is_empty = range.to_offset(snapshot).is_empty();
23743 let text_is_empty = new_text.is_empty();
23744
23745 if range_is_empty != text_is_empty {
23746 if range_is_empty {
23747 all_deletions = false;
23748 } else {
23749 all_insertions = false;
23750 }
23751 } else {
23752 return false;
23753 }
23754
23755 if !all_insertions && !all_deletions {
23756 return false;
23757 }
23758 }
23759 all_insertions || all_deletions
23760}
23761
23762struct MissingEditPredictionKeybindingTooltip;
23763
23764impl Render for MissingEditPredictionKeybindingTooltip {
23765 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23766 ui::tooltip_container(window, cx, |container, _, cx| {
23767 container
23768 .flex_shrink_0()
23769 .max_w_80()
23770 .min_h(rems_from_px(124.))
23771 .justify_between()
23772 .child(
23773 v_flex()
23774 .flex_1()
23775 .text_ui_sm(cx)
23776 .child(Label::new("Conflict with Accept Keybinding"))
23777 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23778 )
23779 .child(
23780 h_flex()
23781 .pb_1()
23782 .gap_1()
23783 .items_end()
23784 .w_full()
23785 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23786 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23787 }))
23788 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23789 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23790 })),
23791 )
23792 })
23793 }
23794}
23795
23796#[derive(Debug, Clone, Copy, PartialEq)]
23797pub struct LineHighlight {
23798 pub background: Background,
23799 pub border: Option<gpui::Hsla>,
23800 pub include_gutter: bool,
23801 pub type_id: Option<TypeId>,
23802}
23803
23804struct LineManipulationResult {
23805 pub new_text: String,
23806 pub line_count_before: usize,
23807 pub line_count_after: usize,
23808}
23809
23810fn render_diff_hunk_controls(
23811 row: u32,
23812 status: &DiffHunkStatus,
23813 hunk_range: Range<Anchor>,
23814 is_created_file: bool,
23815 line_height: Pixels,
23816 editor: &Entity<Editor>,
23817 _window: &mut Window,
23818 cx: &mut App,
23819) -> AnyElement {
23820 h_flex()
23821 .h(line_height)
23822 .mr_1()
23823 .gap_1()
23824 .px_0p5()
23825 .pb_1()
23826 .border_x_1()
23827 .border_b_1()
23828 .border_color(cx.theme().colors().border_variant)
23829 .rounded_b_lg()
23830 .bg(cx.theme().colors().editor_background)
23831 .gap_1()
23832 .block_mouse_except_scroll()
23833 .shadow_md()
23834 .child(if status.has_secondary_hunk() {
23835 Button::new(("stage", row as u64), "Stage")
23836 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23837 .tooltip({
23838 let focus_handle = editor.focus_handle(cx);
23839 move |window, cx| {
23840 Tooltip::for_action_in(
23841 "Stage Hunk",
23842 &::git::ToggleStaged,
23843 &focus_handle,
23844 window,
23845 cx,
23846 )
23847 }
23848 })
23849 .on_click({
23850 let editor = editor.clone();
23851 move |_event, _window, cx| {
23852 editor.update(cx, |editor, cx| {
23853 editor.stage_or_unstage_diff_hunks(
23854 true,
23855 vec![hunk_range.start..hunk_range.start],
23856 cx,
23857 );
23858 });
23859 }
23860 })
23861 } else {
23862 Button::new(("unstage", row as u64), "Unstage")
23863 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23864 .tooltip({
23865 let focus_handle = editor.focus_handle(cx);
23866 move |window, cx| {
23867 Tooltip::for_action_in(
23868 "Unstage Hunk",
23869 &::git::ToggleStaged,
23870 &focus_handle,
23871 window,
23872 cx,
23873 )
23874 }
23875 })
23876 .on_click({
23877 let editor = editor.clone();
23878 move |_event, _window, cx| {
23879 editor.update(cx, |editor, cx| {
23880 editor.stage_or_unstage_diff_hunks(
23881 false,
23882 vec![hunk_range.start..hunk_range.start],
23883 cx,
23884 );
23885 });
23886 }
23887 })
23888 })
23889 .child(
23890 Button::new(("restore", row as u64), "Restore")
23891 .tooltip({
23892 let focus_handle = editor.focus_handle(cx);
23893 move |window, cx| {
23894 Tooltip::for_action_in(
23895 "Restore Hunk",
23896 &::git::Restore,
23897 &focus_handle,
23898 window,
23899 cx,
23900 )
23901 }
23902 })
23903 .on_click({
23904 let editor = editor.clone();
23905 move |_event, window, cx| {
23906 editor.update(cx, |editor, cx| {
23907 let snapshot = editor.snapshot(window, cx);
23908 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23909 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23910 });
23911 }
23912 })
23913 .disabled(is_created_file),
23914 )
23915 .when(
23916 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23917 |el| {
23918 el.child(
23919 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23920 .shape(IconButtonShape::Square)
23921 .icon_size(IconSize::Small)
23922 // .disabled(!has_multiple_hunks)
23923 .tooltip({
23924 let focus_handle = editor.focus_handle(cx);
23925 move |window, cx| {
23926 Tooltip::for_action_in(
23927 "Next Hunk",
23928 &GoToHunk,
23929 &focus_handle,
23930 window,
23931 cx,
23932 )
23933 }
23934 })
23935 .on_click({
23936 let editor = editor.clone();
23937 move |_event, window, cx| {
23938 editor.update(cx, |editor, cx| {
23939 let snapshot = editor.snapshot(window, cx);
23940 let position =
23941 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23942 editor.go_to_hunk_before_or_after_position(
23943 &snapshot,
23944 position,
23945 Direction::Next,
23946 window,
23947 cx,
23948 );
23949 editor.expand_selected_diff_hunks(cx);
23950 });
23951 }
23952 }),
23953 )
23954 .child(
23955 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23956 .shape(IconButtonShape::Square)
23957 .icon_size(IconSize::Small)
23958 // .disabled(!has_multiple_hunks)
23959 .tooltip({
23960 let focus_handle = editor.focus_handle(cx);
23961 move |window, cx| {
23962 Tooltip::for_action_in(
23963 "Previous Hunk",
23964 &GoToPreviousHunk,
23965 &focus_handle,
23966 window,
23967 cx,
23968 )
23969 }
23970 })
23971 .on_click({
23972 let editor = editor.clone();
23973 move |_event, window, cx| {
23974 editor.update(cx, |editor, cx| {
23975 let snapshot = editor.snapshot(window, cx);
23976 let point =
23977 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23978 editor.go_to_hunk_before_or_after_position(
23979 &snapshot,
23980 point,
23981 Direction::Prev,
23982 window,
23983 cx,
23984 );
23985 editor.expand_selected_diff_hunks(cx);
23986 });
23987 }
23988 }),
23989 )
23990 },
23991 )
23992 .into_any_element()
23993}