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, _| project.is_via_collab())
14899 .unwrap_or(true);
14900 if hide_runnables {
14901 return;
14902 }
14903 let new_rows =
14904 cx.background_spawn({
14905 let snapshot = display_snapshot.clone();
14906 async move {
14907 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14908 }
14909 })
14910 .await;
14911 let Ok(lsp_tasks) =
14912 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14913 else {
14914 return;
14915 };
14916 let lsp_tasks = lsp_tasks.await;
14917
14918 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14919 lsp_tasks
14920 .into_iter()
14921 .flat_map(|(kind, tasks)| {
14922 tasks.into_iter().filter_map(move |(location, task)| {
14923 Some((kind.clone(), location?, task))
14924 })
14925 })
14926 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14927 let buffer = location.target.buffer;
14928 let buffer_snapshot = buffer.read(cx).snapshot();
14929 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14930 |(excerpt_id, snapshot, _)| {
14931 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14932 display_snapshot
14933 .buffer_snapshot
14934 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14935 } else {
14936 None
14937 }
14938 },
14939 );
14940 if let Some(offset) = offset {
14941 let task_buffer_range =
14942 location.target.range.to_point(&buffer_snapshot);
14943 let context_buffer_range =
14944 task_buffer_range.to_offset(&buffer_snapshot);
14945 let context_range = BufferOffset(context_buffer_range.start)
14946 ..BufferOffset(context_buffer_range.end);
14947
14948 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14949 .or_insert_with(|| RunnableTasks {
14950 templates: Vec::new(),
14951 offset,
14952 column: task_buffer_range.start.column,
14953 extra_variables: HashMap::default(),
14954 context_range,
14955 })
14956 .templates
14957 .push((kind, task.original_task().clone()));
14958 }
14959
14960 acc
14961 })
14962 }) else {
14963 return;
14964 };
14965
14966 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14967 buffer.language_settings(cx).tasks.prefer_lsp
14968 }) else {
14969 return;
14970 };
14971
14972 let rows = Self::runnable_rows(
14973 project,
14974 display_snapshot,
14975 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14976 new_rows,
14977 cx.clone(),
14978 )
14979 .await;
14980 editor
14981 .update(cx, |editor, _| {
14982 editor.clear_tasks();
14983 for (key, mut value) in rows {
14984 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14985 value.templates.extend(lsp_tasks.templates);
14986 }
14987
14988 editor.insert_tasks(key, value);
14989 }
14990 for (key, value) in lsp_tasks_by_rows {
14991 editor.insert_tasks(key, value);
14992 }
14993 })
14994 .ok();
14995 })
14996 }
14997 fn fetch_runnable_ranges(
14998 snapshot: &DisplaySnapshot,
14999 range: Range<Anchor>,
15000 ) -> Vec<language::RunnableRange> {
15001 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15002 }
15003
15004 fn runnable_rows(
15005 project: Entity<Project>,
15006 snapshot: DisplaySnapshot,
15007 prefer_lsp: bool,
15008 runnable_ranges: Vec<RunnableRange>,
15009 cx: AsyncWindowContext,
15010 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15011 cx.spawn(async move |cx| {
15012 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15013 for mut runnable in runnable_ranges {
15014 let Some(tasks) = cx
15015 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15016 .ok()
15017 else {
15018 continue;
15019 };
15020 let mut tasks = tasks.await;
15021
15022 if prefer_lsp {
15023 tasks.retain(|(task_kind, _)| {
15024 !matches!(task_kind, TaskSourceKind::Language { .. })
15025 });
15026 }
15027 if tasks.is_empty() {
15028 continue;
15029 }
15030
15031 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15032 let Some(row) = snapshot
15033 .buffer_snapshot
15034 .buffer_line_for_row(MultiBufferRow(point.row))
15035 .map(|(_, range)| range.start.row)
15036 else {
15037 continue;
15038 };
15039
15040 let context_range =
15041 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15042 runnable_rows.push((
15043 (runnable.buffer_id, row),
15044 RunnableTasks {
15045 templates: tasks,
15046 offset: snapshot
15047 .buffer_snapshot
15048 .anchor_before(runnable.run_range.start),
15049 context_range,
15050 column: point.column,
15051 extra_variables: runnable.extra_captures,
15052 },
15053 ));
15054 }
15055 runnable_rows
15056 })
15057 }
15058
15059 fn templates_with_tags(
15060 project: &Entity<Project>,
15061 runnable: &mut Runnable,
15062 cx: &mut App,
15063 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15064 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15065 let (worktree_id, file) = project
15066 .buffer_for_id(runnable.buffer, cx)
15067 .and_then(|buffer| buffer.read(cx).file())
15068 .map(|file| (file.worktree_id(cx), file.clone()))
15069 .unzip();
15070
15071 (
15072 project.task_store().read(cx).task_inventory().cloned(),
15073 worktree_id,
15074 file,
15075 )
15076 });
15077
15078 let tags = mem::take(&mut runnable.tags);
15079 let language = runnable.language.clone();
15080 cx.spawn(async move |cx| {
15081 let mut templates_with_tags = Vec::new();
15082 if let Some(inventory) = inventory {
15083 for RunnableTag(tag) in tags {
15084 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15085 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15086 }) else {
15087 return templates_with_tags;
15088 };
15089 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15090 move |(_, template)| {
15091 template.tags.iter().any(|source_tag| source_tag == &tag)
15092 },
15093 ));
15094 }
15095 }
15096 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15097
15098 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15099 // Strongest source wins; if we have worktree tag binding, prefer that to
15100 // global and language bindings;
15101 // if we have a global binding, prefer that to language binding.
15102 let first_mismatch = templates_with_tags
15103 .iter()
15104 .position(|(tag_source, _)| tag_source != leading_tag_source);
15105 if let Some(index) = first_mismatch {
15106 templates_with_tags.truncate(index);
15107 }
15108 }
15109
15110 templates_with_tags
15111 })
15112 }
15113
15114 pub fn move_to_enclosing_bracket(
15115 &mut self,
15116 _: &MoveToEnclosingBracket,
15117 window: &mut Window,
15118 cx: &mut Context<Self>,
15119 ) {
15120 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15121 self.change_selections(Default::default(), window, cx, |s| {
15122 s.move_offsets_with(|snapshot, selection| {
15123 let Some(enclosing_bracket_ranges) =
15124 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15125 else {
15126 return;
15127 };
15128
15129 let mut best_length = usize::MAX;
15130 let mut best_inside = false;
15131 let mut best_in_bracket_range = false;
15132 let mut best_destination = None;
15133 for (open, close) in enclosing_bracket_ranges {
15134 let close = close.to_inclusive();
15135 let length = close.end() - open.start;
15136 let inside = selection.start >= open.end && selection.end <= *close.start();
15137 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15138 || close.contains(&selection.head());
15139
15140 // If best is next to a bracket and current isn't, skip
15141 if !in_bracket_range && best_in_bracket_range {
15142 continue;
15143 }
15144
15145 // Prefer smaller lengths unless best is inside and current isn't
15146 if length > best_length && (best_inside || !inside) {
15147 continue;
15148 }
15149
15150 best_length = length;
15151 best_inside = inside;
15152 best_in_bracket_range = in_bracket_range;
15153 best_destination = Some(
15154 if close.contains(&selection.start) && close.contains(&selection.end) {
15155 if inside { open.end } else { open.start }
15156 } else if inside {
15157 *close.start()
15158 } else {
15159 *close.end()
15160 },
15161 );
15162 }
15163
15164 if let Some(destination) = best_destination {
15165 selection.collapse_to(destination, SelectionGoal::None);
15166 }
15167 })
15168 });
15169 }
15170
15171 pub fn undo_selection(
15172 &mut self,
15173 _: &UndoSelection,
15174 window: &mut Window,
15175 cx: &mut Context<Self>,
15176 ) {
15177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15178 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15179 self.selection_history.mode = SelectionHistoryMode::Undoing;
15180 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15181 this.end_selection(window, cx);
15182 this.change_selections(
15183 SelectionEffects::scroll(Autoscroll::newest()),
15184 window,
15185 cx,
15186 |s| s.select_anchors(entry.selections.to_vec()),
15187 );
15188 });
15189 self.selection_history.mode = SelectionHistoryMode::Normal;
15190
15191 self.select_next_state = entry.select_next_state;
15192 self.select_prev_state = entry.select_prev_state;
15193 self.add_selections_state = entry.add_selections_state;
15194 }
15195 }
15196
15197 pub fn redo_selection(
15198 &mut self,
15199 _: &RedoSelection,
15200 window: &mut Window,
15201 cx: &mut Context<Self>,
15202 ) {
15203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15204 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15205 self.selection_history.mode = SelectionHistoryMode::Redoing;
15206 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15207 this.end_selection(window, cx);
15208 this.change_selections(
15209 SelectionEffects::scroll(Autoscroll::newest()),
15210 window,
15211 cx,
15212 |s| s.select_anchors(entry.selections.to_vec()),
15213 );
15214 });
15215 self.selection_history.mode = SelectionHistoryMode::Normal;
15216
15217 self.select_next_state = entry.select_next_state;
15218 self.select_prev_state = entry.select_prev_state;
15219 self.add_selections_state = entry.add_selections_state;
15220 }
15221 }
15222
15223 pub fn expand_excerpts(
15224 &mut self,
15225 action: &ExpandExcerpts,
15226 _: &mut Window,
15227 cx: &mut Context<Self>,
15228 ) {
15229 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15230 }
15231
15232 pub fn expand_excerpts_down(
15233 &mut self,
15234 action: &ExpandExcerptsDown,
15235 _: &mut Window,
15236 cx: &mut Context<Self>,
15237 ) {
15238 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15239 }
15240
15241 pub fn expand_excerpts_up(
15242 &mut self,
15243 action: &ExpandExcerptsUp,
15244 _: &mut Window,
15245 cx: &mut Context<Self>,
15246 ) {
15247 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15248 }
15249
15250 pub fn expand_excerpts_for_direction(
15251 &mut self,
15252 lines: u32,
15253 direction: ExpandExcerptDirection,
15254
15255 cx: &mut Context<Self>,
15256 ) {
15257 let selections = self.selections.disjoint_anchors();
15258
15259 let lines = if lines == 0 {
15260 EditorSettings::get_global(cx).expand_excerpt_lines
15261 } else {
15262 lines
15263 };
15264
15265 self.buffer.update(cx, |buffer, cx| {
15266 let snapshot = buffer.snapshot(cx);
15267 let mut excerpt_ids = selections
15268 .iter()
15269 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15270 .collect::<Vec<_>>();
15271 excerpt_ids.sort();
15272 excerpt_ids.dedup();
15273 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15274 })
15275 }
15276
15277 pub fn expand_excerpt(
15278 &mut self,
15279 excerpt: ExcerptId,
15280 direction: ExpandExcerptDirection,
15281 window: &mut Window,
15282 cx: &mut Context<Self>,
15283 ) {
15284 let current_scroll_position = self.scroll_position(cx);
15285 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15286 let mut should_scroll_up = false;
15287
15288 if direction == ExpandExcerptDirection::Down {
15289 let multi_buffer = self.buffer.read(cx);
15290 let snapshot = multi_buffer.snapshot(cx);
15291 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15292 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15293 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15294 {
15295 let buffer_snapshot = buffer.read(cx).snapshot();
15296 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15297 let last_row = buffer_snapshot.max_point().row;
15298 let lines_below = last_row.saturating_sub(excerpt_end_row);
15299 should_scroll_up = lines_below >= lines_to_expand;
15300 }
15301 }
15302
15303 self.buffer.update(cx, |buffer, cx| {
15304 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15305 });
15306
15307 if should_scroll_up {
15308 let new_scroll_position =
15309 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15310 self.set_scroll_position(new_scroll_position, window, cx);
15311 }
15312 }
15313
15314 pub fn go_to_singleton_buffer_point(
15315 &mut self,
15316 point: Point,
15317 window: &mut Window,
15318 cx: &mut Context<Self>,
15319 ) {
15320 self.go_to_singleton_buffer_range(point..point, window, cx);
15321 }
15322
15323 pub fn go_to_singleton_buffer_range(
15324 &mut self,
15325 range: Range<Point>,
15326 window: &mut Window,
15327 cx: &mut Context<Self>,
15328 ) {
15329 let multibuffer = self.buffer().read(cx);
15330 let Some(buffer) = multibuffer.as_singleton() else {
15331 return;
15332 };
15333 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15334 return;
15335 };
15336 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15337 return;
15338 };
15339 self.change_selections(
15340 SelectionEffects::default().nav_history(true),
15341 window,
15342 cx,
15343 |s| s.select_anchor_ranges([start..end]),
15344 );
15345 }
15346
15347 pub fn go_to_diagnostic(
15348 &mut self,
15349 action: &GoToDiagnostic,
15350 window: &mut Window,
15351 cx: &mut Context<Self>,
15352 ) {
15353 if !self.diagnostics_enabled() {
15354 return;
15355 }
15356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15357 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15358 }
15359
15360 pub fn go_to_prev_diagnostic(
15361 &mut self,
15362 action: &GoToPreviousDiagnostic,
15363 window: &mut Window,
15364 cx: &mut Context<Self>,
15365 ) {
15366 if !self.diagnostics_enabled() {
15367 return;
15368 }
15369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15370 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15371 }
15372
15373 pub fn go_to_diagnostic_impl(
15374 &mut self,
15375 direction: Direction,
15376 severity: GoToDiagnosticSeverityFilter,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 let buffer = self.buffer.read(cx).snapshot(cx);
15381 let selection = self.selections.newest::<usize>(cx);
15382
15383 let mut active_group_id = None;
15384 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15385 && active_group.active_range.start.to_offset(&buffer) == selection.start
15386 {
15387 active_group_id = Some(active_group.group_id);
15388 }
15389
15390 fn filtered(
15391 snapshot: EditorSnapshot,
15392 severity: GoToDiagnosticSeverityFilter,
15393 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15394 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15395 diagnostics
15396 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15397 .filter(|entry| entry.range.start != entry.range.end)
15398 .filter(|entry| !entry.diagnostic.is_unnecessary)
15399 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15400 }
15401
15402 let snapshot = self.snapshot(window, cx);
15403 let before = filtered(
15404 snapshot.clone(),
15405 severity,
15406 buffer
15407 .diagnostics_in_range(0..selection.start)
15408 .filter(|entry| entry.range.start <= selection.start),
15409 );
15410 let after = filtered(
15411 snapshot,
15412 severity,
15413 buffer
15414 .diagnostics_in_range(selection.start..buffer.len())
15415 .filter(|entry| entry.range.start >= selection.start),
15416 );
15417
15418 let mut found: Option<DiagnosticEntry<usize>> = None;
15419 if direction == Direction::Prev {
15420 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15421 {
15422 for diagnostic in prev_diagnostics.into_iter().rev() {
15423 if diagnostic.range.start != selection.start
15424 || active_group_id
15425 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15426 {
15427 found = Some(diagnostic);
15428 break 'outer;
15429 }
15430 }
15431 }
15432 } else {
15433 for diagnostic in after.chain(before) {
15434 if diagnostic.range.start != selection.start
15435 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15436 {
15437 found = Some(diagnostic);
15438 break;
15439 }
15440 }
15441 }
15442 let Some(next_diagnostic) = found else {
15443 return;
15444 };
15445
15446 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15447 return;
15448 };
15449 self.change_selections(Default::default(), window, cx, |s| {
15450 s.select_ranges(vec![
15451 next_diagnostic.range.start..next_diagnostic.range.start,
15452 ])
15453 });
15454 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15455 self.refresh_edit_prediction(false, true, window, cx);
15456 }
15457
15458 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15460 let snapshot = self.snapshot(window, cx);
15461 let selection = self.selections.newest::<Point>(cx);
15462 self.go_to_hunk_before_or_after_position(
15463 &snapshot,
15464 selection.head(),
15465 Direction::Next,
15466 window,
15467 cx,
15468 );
15469 }
15470
15471 pub fn go_to_hunk_before_or_after_position(
15472 &mut self,
15473 snapshot: &EditorSnapshot,
15474 position: Point,
15475 direction: Direction,
15476 window: &mut Window,
15477 cx: &mut Context<Editor>,
15478 ) {
15479 let row = if direction == Direction::Next {
15480 self.hunk_after_position(snapshot, position)
15481 .map(|hunk| hunk.row_range.start)
15482 } else {
15483 self.hunk_before_position(snapshot, position)
15484 };
15485
15486 if let Some(row) = row {
15487 let destination = Point::new(row.0, 0);
15488 let autoscroll = Autoscroll::center();
15489
15490 self.unfold_ranges(&[destination..destination], false, false, cx);
15491 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15492 s.select_ranges([destination..destination]);
15493 });
15494 }
15495 }
15496
15497 fn hunk_after_position(
15498 &mut self,
15499 snapshot: &EditorSnapshot,
15500 position: Point,
15501 ) -> Option<MultiBufferDiffHunk> {
15502 snapshot
15503 .buffer_snapshot
15504 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15505 .find(|hunk| hunk.row_range.start.0 > position.row)
15506 .or_else(|| {
15507 snapshot
15508 .buffer_snapshot
15509 .diff_hunks_in_range(Point::zero()..position)
15510 .find(|hunk| hunk.row_range.end.0 < position.row)
15511 })
15512 }
15513
15514 fn go_to_prev_hunk(
15515 &mut self,
15516 _: &GoToPreviousHunk,
15517 window: &mut Window,
15518 cx: &mut Context<Self>,
15519 ) {
15520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15521 let snapshot = self.snapshot(window, cx);
15522 let selection = self.selections.newest::<Point>(cx);
15523 self.go_to_hunk_before_or_after_position(
15524 &snapshot,
15525 selection.head(),
15526 Direction::Prev,
15527 window,
15528 cx,
15529 );
15530 }
15531
15532 fn hunk_before_position(
15533 &mut self,
15534 snapshot: &EditorSnapshot,
15535 position: Point,
15536 ) -> Option<MultiBufferRow> {
15537 snapshot
15538 .buffer_snapshot
15539 .diff_hunk_before(position)
15540 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15541 }
15542
15543 fn go_to_next_change(
15544 &mut self,
15545 _: &GoToNextChange,
15546 window: &mut Window,
15547 cx: &mut Context<Self>,
15548 ) {
15549 if let Some(selections) = self
15550 .change_list
15551 .next_change(1, Direction::Next)
15552 .map(|s| s.to_vec())
15553 {
15554 self.change_selections(Default::default(), window, cx, |s| {
15555 let map = s.display_map();
15556 s.select_display_ranges(selections.iter().map(|a| {
15557 let point = a.to_display_point(&map);
15558 point..point
15559 }))
15560 })
15561 }
15562 }
15563
15564 fn go_to_previous_change(
15565 &mut self,
15566 _: &GoToPreviousChange,
15567 window: &mut Window,
15568 cx: &mut Context<Self>,
15569 ) {
15570 if let Some(selections) = self
15571 .change_list
15572 .next_change(1, Direction::Prev)
15573 .map(|s| s.to_vec())
15574 {
15575 self.change_selections(Default::default(), window, cx, |s| {
15576 let map = s.display_map();
15577 s.select_display_ranges(selections.iter().map(|a| {
15578 let point = a.to_display_point(&map);
15579 point..point
15580 }))
15581 })
15582 }
15583 }
15584
15585 fn go_to_line<T: 'static>(
15586 &mut self,
15587 position: Anchor,
15588 highlight_color: Option<Hsla>,
15589 window: &mut Window,
15590 cx: &mut Context<Self>,
15591 ) {
15592 let snapshot = self.snapshot(window, cx).display_snapshot;
15593 let position = position.to_point(&snapshot.buffer_snapshot);
15594 let start = snapshot
15595 .buffer_snapshot
15596 .clip_point(Point::new(position.row, 0), Bias::Left);
15597 let end = start + Point::new(1, 0);
15598 let start = snapshot.buffer_snapshot.anchor_before(start);
15599 let end = snapshot.buffer_snapshot.anchor_before(end);
15600
15601 self.highlight_rows::<T>(
15602 start..end,
15603 highlight_color
15604 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15605 Default::default(),
15606 cx,
15607 );
15608
15609 if self.buffer.read(cx).is_singleton() {
15610 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15611 }
15612 }
15613
15614 pub fn go_to_definition(
15615 &mut self,
15616 _: &GoToDefinition,
15617 window: &mut Window,
15618 cx: &mut Context<Self>,
15619 ) -> Task<Result<Navigated>> {
15620 let definition =
15621 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15622 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15623 cx.spawn_in(window, async move |editor, cx| {
15624 if definition.await? == Navigated::Yes {
15625 return Ok(Navigated::Yes);
15626 }
15627 match fallback_strategy {
15628 GoToDefinitionFallback::None => Ok(Navigated::No),
15629 GoToDefinitionFallback::FindAllReferences => {
15630 match editor.update_in(cx, |editor, window, cx| {
15631 editor.find_all_references(&FindAllReferences, window, cx)
15632 })? {
15633 Some(references) => references.await,
15634 None => Ok(Navigated::No),
15635 }
15636 }
15637 }
15638 })
15639 }
15640
15641 pub fn go_to_declaration(
15642 &mut self,
15643 _: &GoToDeclaration,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) -> Task<Result<Navigated>> {
15647 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15648 }
15649
15650 pub fn go_to_declaration_split(
15651 &mut self,
15652 _: &GoToDeclaration,
15653 window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) -> Task<Result<Navigated>> {
15656 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15657 }
15658
15659 pub fn go_to_implementation(
15660 &mut self,
15661 _: &GoToImplementation,
15662 window: &mut Window,
15663 cx: &mut Context<Self>,
15664 ) -> Task<Result<Navigated>> {
15665 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15666 }
15667
15668 pub fn go_to_implementation_split(
15669 &mut self,
15670 _: &GoToImplementationSplit,
15671 window: &mut Window,
15672 cx: &mut Context<Self>,
15673 ) -> Task<Result<Navigated>> {
15674 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15675 }
15676
15677 pub fn go_to_type_definition(
15678 &mut self,
15679 _: &GoToTypeDefinition,
15680 window: &mut Window,
15681 cx: &mut Context<Self>,
15682 ) -> Task<Result<Navigated>> {
15683 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15684 }
15685
15686 pub fn go_to_definition_split(
15687 &mut self,
15688 _: &GoToDefinitionSplit,
15689 window: &mut Window,
15690 cx: &mut Context<Self>,
15691 ) -> Task<Result<Navigated>> {
15692 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15693 }
15694
15695 pub fn go_to_type_definition_split(
15696 &mut self,
15697 _: &GoToTypeDefinitionSplit,
15698 window: &mut Window,
15699 cx: &mut Context<Self>,
15700 ) -> Task<Result<Navigated>> {
15701 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15702 }
15703
15704 fn go_to_definition_of_kind(
15705 &mut self,
15706 kind: GotoDefinitionKind,
15707 split: bool,
15708 window: &mut Window,
15709 cx: &mut Context<Self>,
15710 ) -> Task<Result<Navigated>> {
15711 let Some(provider) = self.semantics_provider.clone() else {
15712 return Task::ready(Ok(Navigated::No));
15713 };
15714 let head = self.selections.newest::<usize>(cx).head();
15715 let buffer = self.buffer.read(cx);
15716 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15717 return Task::ready(Ok(Navigated::No));
15718 };
15719 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15720 return Task::ready(Ok(Navigated::No));
15721 };
15722
15723 cx.spawn_in(window, async move |editor, cx| {
15724 let definitions = definitions.await?;
15725 let navigated = editor
15726 .update_in(cx, |editor, window, cx| {
15727 editor.navigate_to_hover_links(
15728 Some(kind),
15729 definitions
15730 .into_iter()
15731 .filter(|location| {
15732 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15733 })
15734 .map(HoverLink::Text)
15735 .collect::<Vec<_>>(),
15736 split,
15737 window,
15738 cx,
15739 )
15740 })?
15741 .await?;
15742 anyhow::Ok(navigated)
15743 })
15744 }
15745
15746 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15747 let selection = self.selections.newest_anchor();
15748 let head = selection.head();
15749 let tail = selection.tail();
15750
15751 let Some((buffer, start_position)) =
15752 self.buffer.read(cx).text_anchor_for_position(head, cx)
15753 else {
15754 return;
15755 };
15756
15757 let end_position = if head != tail {
15758 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15759 return;
15760 };
15761 Some(pos)
15762 } else {
15763 None
15764 };
15765
15766 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15767 let url = if let Some(end_pos) = end_position {
15768 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15769 } else {
15770 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15771 };
15772
15773 if let Some(url) = url {
15774 editor.update(cx, |_, cx| {
15775 cx.open_url(&url);
15776 })
15777 } else {
15778 Ok(())
15779 }
15780 });
15781
15782 url_finder.detach();
15783 }
15784
15785 pub fn open_selected_filename(
15786 &mut self,
15787 _: &OpenSelectedFilename,
15788 window: &mut Window,
15789 cx: &mut Context<Self>,
15790 ) {
15791 let Some(workspace) = self.workspace() else {
15792 return;
15793 };
15794
15795 let position = self.selections.newest_anchor().head();
15796
15797 let Some((buffer, buffer_position)) =
15798 self.buffer.read(cx).text_anchor_for_position(position, cx)
15799 else {
15800 return;
15801 };
15802
15803 let project = self.project.clone();
15804
15805 cx.spawn_in(window, async move |_, cx| {
15806 let result = find_file(&buffer, project, buffer_position, cx).await;
15807
15808 if let Some((_, path)) = result {
15809 workspace
15810 .update_in(cx, |workspace, window, cx| {
15811 workspace.open_resolved_path(path, window, cx)
15812 })?
15813 .await?;
15814 }
15815 anyhow::Ok(())
15816 })
15817 .detach();
15818 }
15819
15820 pub(crate) fn navigate_to_hover_links(
15821 &mut self,
15822 kind: Option<GotoDefinitionKind>,
15823 definitions: Vec<HoverLink>,
15824 split: bool,
15825 window: &mut Window,
15826 cx: &mut Context<Editor>,
15827 ) -> Task<Result<Navigated>> {
15828 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15829 let mut first_url_or_file = None;
15830 let definitions: Vec<_> = definitions
15831 .into_iter()
15832 .filter_map(|def| match def {
15833 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15834 HoverLink::InlayHint(lsp_location, server_id) => {
15835 let computation =
15836 self.compute_target_location(lsp_location, server_id, window, cx);
15837 Some(cx.background_spawn(computation))
15838 }
15839 HoverLink::Url(url) => {
15840 first_url_or_file = Some(Either::Left(url));
15841 None
15842 }
15843 HoverLink::File(path) => {
15844 first_url_or_file = Some(Either::Right(path));
15845 None
15846 }
15847 })
15848 .collect();
15849
15850 let workspace = self.workspace();
15851
15852 cx.spawn_in(window, async move |editor, acx| {
15853 let mut locations: Vec<Location> = future::join_all(definitions)
15854 .await
15855 .into_iter()
15856 .filter_map(|location| location.transpose())
15857 .collect::<Result<_>>()
15858 .context("location tasks")?;
15859
15860 if locations.len() > 1 {
15861 let Some(workspace) = workspace else {
15862 return Ok(Navigated::No);
15863 };
15864
15865 let tab_kind = match kind {
15866 Some(GotoDefinitionKind::Implementation) => "Implementations",
15867 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15868 Some(GotoDefinitionKind::Declaration) => "Declarations",
15869 Some(GotoDefinitionKind::Type) => "Types",
15870 };
15871 let title = editor
15872 .update_in(acx, |_, _, cx| {
15873 let target = locations
15874 .iter()
15875 .map(|location| {
15876 location
15877 .buffer
15878 .read(cx)
15879 .text_for_range(location.range.clone())
15880 .collect::<String>()
15881 })
15882 .filter(|text| !text.contains('\n'))
15883 .unique()
15884 .take(3)
15885 .join(", ");
15886 if target.is_empty() {
15887 tab_kind.to_owned()
15888 } else {
15889 format!("{tab_kind} for {target}")
15890 }
15891 })
15892 .context("buffer title")?;
15893
15894 let opened = workspace
15895 .update_in(acx, |workspace, window, cx| {
15896 Self::open_locations_in_multibuffer(
15897 workspace,
15898 locations,
15899 title,
15900 split,
15901 MultibufferSelectionMode::First,
15902 window,
15903 cx,
15904 )
15905 })
15906 .is_ok();
15907
15908 anyhow::Ok(Navigated::from_bool(opened))
15909 } else if locations.is_empty() {
15910 // If there is one definition, just open it directly
15911 match first_url_or_file {
15912 Some(Either::Left(url)) => {
15913 acx.update(|_, cx| cx.open_url(&url))?;
15914 Ok(Navigated::Yes)
15915 }
15916 Some(Either::Right(path)) => {
15917 let Some(workspace) = workspace else {
15918 return Ok(Navigated::No);
15919 };
15920
15921 workspace
15922 .update_in(acx, |workspace, window, cx| {
15923 workspace.open_resolved_path(path, window, cx)
15924 })?
15925 .await?;
15926 Ok(Navigated::Yes)
15927 }
15928 None => Ok(Navigated::No),
15929 }
15930 } else {
15931 let Some(workspace) = workspace else {
15932 return Ok(Navigated::No);
15933 };
15934
15935 let target = locations.pop().unwrap();
15936 editor.update_in(acx, |editor, window, cx| {
15937 let pane = workspace.read(cx).active_pane().clone();
15938
15939 let range = target.range.to_point(target.buffer.read(cx));
15940 let range = editor.range_for_match(&range);
15941 let range = collapse_multiline_range(range);
15942
15943 if !split
15944 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15945 {
15946 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15947 } else {
15948 window.defer(cx, move |window, cx| {
15949 let target_editor: Entity<Self> =
15950 workspace.update(cx, |workspace, cx| {
15951 let pane = if split {
15952 workspace.adjacent_pane(window, cx)
15953 } else {
15954 workspace.active_pane().clone()
15955 };
15956
15957 workspace.open_project_item(
15958 pane,
15959 target.buffer.clone(),
15960 true,
15961 true,
15962 window,
15963 cx,
15964 )
15965 });
15966 target_editor.update(cx, |target_editor, cx| {
15967 // When selecting a definition in a different buffer, disable the nav history
15968 // to avoid creating a history entry at the previous cursor location.
15969 pane.update(cx, |pane, _| pane.disable_history());
15970 target_editor.go_to_singleton_buffer_range(range, window, cx);
15971 pane.update(cx, |pane, _| pane.enable_history());
15972 });
15973 });
15974 }
15975 Navigated::Yes
15976 })
15977 }
15978 })
15979 }
15980
15981 fn compute_target_location(
15982 &self,
15983 lsp_location: lsp::Location,
15984 server_id: LanguageServerId,
15985 window: &mut Window,
15986 cx: &mut Context<Self>,
15987 ) -> Task<anyhow::Result<Option<Location>>> {
15988 let Some(project) = self.project.clone() else {
15989 return Task::ready(Ok(None));
15990 };
15991
15992 cx.spawn_in(window, async move |editor, cx| {
15993 let location_task = editor.update(cx, |_, cx| {
15994 project.update(cx, |project, cx| {
15995 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
15996 })
15997 })?;
15998 let location = Some({
15999 let target_buffer_handle = location_task.await.context("open local buffer")?;
16000 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16001 let target_start = target_buffer
16002 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16003 let target_end = target_buffer
16004 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16005 target_buffer.anchor_after(target_start)
16006 ..target_buffer.anchor_before(target_end)
16007 })?;
16008 Location {
16009 buffer: target_buffer_handle,
16010 range,
16011 }
16012 });
16013 Ok(location)
16014 })
16015 }
16016
16017 pub fn find_all_references(
16018 &mut self,
16019 _: &FindAllReferences,
16020 window: &mut Window,
16021 cx: &mut Context<Self>,
16022 ) -> Option<Task<Result<Navigated>>> {
16023 let selection = self.selections.newest::<usize>(cx);
16024 let multi_buffer = self.buffer.read(cx);
16025 let head = selection.head();
16026
16027 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16028 let head_anchor = multi_buffer_snapshot.anchor_at(
16029 head,
16030 if head < selection.tail() {
16031 Bias::Right
16032 } else {
16033 Bias::Left
16034 },
16035 );
16036
16037 match self
16038 .find_all_references_task_sources
16039 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16040 {
16041 Ok(_) => {
16042 log::info!(
16043 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16044 );
16045 return None;
16046 }
16047 Err(i) => {
16048 self.find_all_references_task_sources.insert(i, head_anchor);
16049 }
16050 }
16051
16052 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16053 let workspace = self.workspace()?;
16054 let project = workspace.read(cx).project().clone();
16055 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16056 Some(cx.spawn_in(window, async move |editor, cx| {
16057 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16058 if let Ok(i) = editor
16059 .find_all_references_task_sources
16060 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16061 {
16062 editor.find_all_references_task_sources.remove(i);
16063 }
16064 });
16065
16066 let locations = references.await?;
16067 if locations.is_empty() {
16068 return anyhow::Ok(Navigated::No);
16069 }
16070
16071 workspace.update_in(cx, |workspace, window, cx| {
16072 let target = locations
16073 .iter()
16074 .map(|location| {
16075 location
16076 .buffer
16077 .read(cx)
16078 .text_for_range(location.range.clone())
16079 .collect::<String>()
16080 })
16081 .filter(|text| !text.contains('\n'))
16082 .unique()
16083 .take(3)
16084 .join(", ");
16085 let title = if target.is_empty() {
16086 "References".to_owned()
16087 } else {
16088 format!("References to {target}")
16089 };
16090 Self::open_locations_in_multibuffer(
16091 workspace,
16092 locations,
16093 title,
16094 false,
16095 MultibufferSelectionMode::First,
16096 window,
16097 cx,
16098 );
16099 Navigated::Yes
16100 })
16101 }))
16102 }
16103
16104 /// Opens a multibuffer with the given project locations in it
16105 pub fn open_locations_in_multibuffer(
16106 workspace: &mut Workspace,
16107 mut locations: Vec<Location>,
16108 title: String,
16109 split: bool,
16110 multibuffer_selection_mode: MultibufferSelectionMode,
16111 window: &mut Window,
16112 cx: &mut Context<Workspace>,
16113 ) {
16114 if locations.is_empty() {
16115 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16116 return;
16117 }
16118
16119 // If there are multiple definitions, open them in a multibuffer
16120 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16121 let mut locations = locations.into_iter().peekable();
16122 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16123 let capability = workspace.project().read(cx).capability();
16124
16125 let excerpt_buffer = cx.new(|cx| {
16126 let mut multibuffer = MultiBuffer::new(capability);
16127 while let Some(location) = locations.next() {
16128 let buffer = location.buffer.read(cx);
16129 let mut ranges_for_buffer = Vec::new();
16130 let range = location.range.to_point(buffer);
16131 ranges_for_buffer.push(range.clone());
16132
16133 while let Some(next_location) = locations.peek() {
16134 if next_location.buffer == location.buffer {
16135 ranges_for_buffer.push(next_location.range.to_point(buffer));
16136 locations.next();
16137 } else {
16138 break;
16139 }
16140 }
16141
16142 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16143 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16144 PathKey::for_buffer(&location.buffer, cx),
16145 location.buffer.clone(),
16146 ranges_for_buffer,
16147 DEFAULT_MULTIBUFFER_CONTEXT,
16148 cx,
16149 );
16150 ranges.extend(new_ranges)
16151 }
16152
16153 multibuffer.with_title(title)
16154 });
16155
16156 let editor = cx.new(|cx| {
16157 Editor::for_multibuffer(
16158 excerpt_buffer,
16159 Some(workspace.project().clone()),
16160 window,
16161 cx,
16162 )
16163 });
16164 editor.update(cx, |editor, cx| {
16165 match multibuffer_selection_mode {
16166 MultibufferSelectionMode::First => {
16167 if let Some(first_range) = ranges.first() {
16168 editor.change_selections(
16169 SelectionEffects::no_scroll(),
16170 window,
16171 cx,
16172 |selections| {
16173 selections.clear_disjoint();
16174 selections
16175 .select_anchor_ranges(std::iter::once(first_range.clone()));
16176 },
16177 );
16178 }
16179 editor.highlight_background::<Self>(
16180 &ranges,
16181 |theme| theme.colors().editor_highlighted_line_background,
16182 cx,
16183 );
16184 }
16185 MultibufferSelectionMode::All => {
16186 editor.change_selections(
16187 SelectionEffects::no_scroll(),
16188 window,
16189 cx,
16190 |selections| {
16191 selections.clear_disjoint();
16192 selections.select_anchor_ranges(ranges);
16193 },
16194 );
16195 }
16196 }
16197 editor.register_buffers_with_language_servers(cx);
16198 });
16199
16200 let item = Box::new(editor);
16201 let item_id = item.item_id();
16202
16203 if split {
16204 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16205 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16206 let (preview_item_id, preview_item_idx) =
16207 workspace.active_pane().read_with(cx, |pane, _| {
16208 (pane.preview_item_id(), pane.preview_item_idx())
16209 });
16210
16211 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16212
16213 if let Some(preview_item_id) = preview_item_id {
16214 workspace.active_pane().update(cx, |pane, cx| {
16215 pane.remove_item(preview_item_id, false, false, window, cx);
16216 });
16217 }
16218 } else {
16219 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16220 }
16221 workspace.active_pane().update(cx, |pane, cx| {
16222 pane.set_preview_item_id(Some(item_id), cx);
16223 });
16224 }
16225
16226 pub fn rename(
16227 &mut self,
16228 _: &Rename,
16229 window: &mut Window,
16230 cx: &mut Context<Self>,
16231 ) -> Option<Task<Result<()>>> {
16232 use language::ToOffset as _;
16233
16234 let provider = self.semantics_provider.clone()?;
16235 let selection = self.selections.newest_anchor().clone();
16236 let (cursor_buffer, cursor_buffer_position) = self
16237 .buffer
16238 .read(cx)
16239 .text_anchor_for_position(selection.head(), cx)?;
16240 let (tail_buffer, cursor_buffer_position_end) = self
16241 .buffer
16242 .read(cx)
16243 .text_anchor_for_position(selection.tail(), cx)?;
16244 if tail_buffer != cursor_buffer {
16245 return None;
16246 }
16247
16248 let snapshot = cursor_buffer.read(cx).snapshot();
16249 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16250 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16251 let prepare_rename = provider
16252 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16253 .unwrap_or_else(|| Task::ready(Ok(None)));
16254 drop(snapshot);
16255
16256 Some(cx.spawn_in(window, async move |this, cx| {
16257 let rename_range = if let Some(range) = prepare_rename.await? {
16258 Some(range)
16259 } else {
16260 this.update(cx, |this, cx| {
16261 let buffer = this.buffer.read(cx).snapshot(cx);
16262 let mut buffer_highlights = this
16263 .document_highlights_for_position(selection.head(), &buffer)
16264 .filter(|highlight| {
16265 highlight.start.excerpt_id == selection.head().excerpt_id
16266 && highlight.end.excerpt_id == selection.head().excerpt_id
16267 });
16268 buffer_highlights
16269 .next()
16270 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16271 })?
16272 };
16273 if let Some(rename_range) = rename_range {
16274 this.update_in(cx, |this, window, cx| {
16275 let snapshot = cursor_buffer.read(cx).snapshot();
16276 let rename_buffer_range = rename_range.to_offset(&snapshot);
16277 let cursor_offset_in_rename_range =
16278 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16279 let cursor_offset_in_rename_range_end =
16280 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16281
16282 this.take_rename(false, window, cx);
16283 let buffer = this.buffer.read(cx).read(cx);
16284 let cursor_offset = selection.head().to_offset(&buffer);
16285 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16286 let rename_end = rename_start + rename_buffer_range.len();
16287 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16288 let mut old_highlight_id = None;
16289 let old_name: Arc<str> = buffer
16290 .chunks(rename_start..rename_end, true)
16291 .map(|chunk| {
16292 if old_highlight_id.is_none() {
16293 old_highlight_id = chunk.syntax_highlight_id;
16294 }
16295 chunk.text
16296 })
16297 .collect::<String>()
16298 .into();
16299
16300 drop(buffer);
16301
16302 // Position the selection in the rename editor so that it matches the current selection.
16303 this.show_local_selections = false;
16304 let rename_editor = cx.new(|cx| {
16305 let mut editor = Editor::single_line(window, cx);
16306 editor.buffer.update(cx, |buffer, cx| {
16307 buffer.edit([(0..0, old_name.clone())], None, cx)
16308 });
16309 let rename_selection_range = match cursor_offset_in_rename_range
16310 .cmp(&cursor_offset_in_rename_range_end)
16311 {
16312 Ordering::Equal => {
16313 editor.select_all(&SelectAll, window, cx);
16314 return editor;
16315 }
16316 Ordering::Less => {
16317 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16318 }
16319 Ordering::Greater => {
16320 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16321 }
16322 };
16323 if rename_selection_range.end > old_name.len() {
16324 editor.select_all(&SelectAll, window, cx);
16325 } else {
16326 editor.change_selections(Default::default(), window, cx, |s| {
16327 s.select_ranges([rename_selection_range]);
16328 });
16329 }
16330 editor
16331 });
16332 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16333 if e == &EditorEvent::Focused {
16334 cx.emit(EditorEvent::FocusedIn)
16335 }
16336 })
16337 .detach();
16338
16339 let write_highlights =
16340 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16341 let read_highlights =
16342 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16343 let ranges = write_highlights
16344 .iter()
16345 .flat_map(|(_, ranges)| ranges.iter())
16346 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16347 .cloned()
16348 .collect();
16349
16350 this.highlight_text::<Rename>(
16351 ranges,
16352 HighlightStyle {
16353 fade_out: Some(0.6),
16354 ..Default::default()
16355 },
16356 cx,
16357 );
16358 let rename_focus_handle = rename_editor.focus_handle(cx);
16359 window.focus(&rename_focus_handle);
16360 let block_id = this.insert_blocks(
16361 [BlockProperties {
16362 style: BlockStyle::Flex,
16363 placement: BlockPlacement::Below(range.start),
16364 height: Some(1),
16365 render: Arc::new({
16366 let rename_editor = rename_editor.clone();
16367 move |cx: &mut BlockContext| {
16368 let mut text_style = cx.editor_style.text.clone();
16369 if let Some(highlight_style) = old_highlight_id
16370 .and_then(|h| h.style(&cx.editor_style.syntax))
16371 {
16372 text_style = text_style.highlight(highlight_style);
16373 }
16374 div()
16375 .block_mouse_except_scroll()
16376 .pl(cx.anchor_x)
16377 .child(EditorElement::new(
16378 &rename_editor,
16379 EditorStyle {
16380 background: cx.theme().system().transparent,
16381 local_player: cx.editor_style.local_player,
16382 text: text_style,
16383 scrollbar_width: cx.editor_style.scrollbar_width,
16384 syntax: cx.editor_style.syntax.clone(),
16385 status: cx.editor_style.status.clone(),
16386 inlay_hints_style: HighlightStyle {
16387 font_weight: Some(FontWeight::BOLD),
16388 ..make_inlay_hints_style(cx.app)
16389 },
16390 edit_prediction_styles: make_suggestion_styles(
16391 cx.app,
16392 ),
16393 ..EditorStyle::default()
16394 },
16395 ))
16396 .into_any_element()
16397 }
16398 }),
16399 priority: 0,
16400 }],
16401 Some(Autoscroll::fit()),
16402 cx,
16403 )[0];
16404 this.pending_rename = Some(RenameState {
16405 range,
16406 old_name,
16407 editor: rename_editor,
16408 block_id,
16409 });
16410 })?;
16411 }
16412
16413 Ok(())
16414 }))
16415 }
16416
16417 pub fn confirm_rename(
16418 &mut self,
16419 _: &ConfirmRename,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) -> Option<Task<Result<()>>> {
16423 let rename = self.take_rename(false, window, cx)?;
16424 let workspace = self.workspace()?.downgrade();
16425 let (buffer, start) = self
16426 .buffer
16427 .read(cx)
16428 .text_anchor_for_position(rename.range.start, cx)?;
16429 let (end_buffer, _) = self
16430 .buffer
16431 .read(cx)
16432 .text_anchor_for_position(rename.range.end, cx)?;
16433 if buffer != end_buffer {
16434 return None;
16435 }
16436
16437 let old_name = rename.old_name;
16438 let new_name = rename.editor.read(cx).text(cx);
16439
16440 let rename = self.semantics_provider.as_ref()?.perform_rename(
16441 &buffer,
16442 start,
16443 new_name.clone(),
16444 cx,
16445 )?;
16446
16447 Some(cx.spawn_in(window, async move |editor, cx| {
16448 let project_transaction = rename.await?;
16449 Self::open_project_transaction(
16450 &editor,
16451 workspace,
16452 project_transaction,
16453 format!("Rename: {} → {}", old_name, new_name),
16454 cx,
16455 )
16456 .await?;
16457
16458 editor.update(cx, |editor, cx| {
16459 editor.refresh_document_highlights(cx);
16460 })?;
16461 Ok(())
16462 }))
16463 }
16464
16465 fn take_rename(
16466 &mut self,
16467 moving_cursor: bool,
16468 window: &mut Window,
16469 cx: &mut Context<Self>,
16470 ) -> Option<RenameState> {
16471 let rename = self.pending_rename.take()?;
16472 if rename.editor.focus_handle(cx).is_focused(window) {
16473 window.focus(&self.focus_handle);
16474 }
16475
16476 self.remove_blocks(
16477 [rename.block_id].into_iter().collect(),
16478 Some(Autoscroll::fit()),
16479 cx,
16480 );
16481 self.clear_highlights::<Rename>(cx);
16482 self.show_local_selections = true;
16483
16484 if moving_cursor {
16485 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16486 editor.selections.newest::<usize>(cx).head()
16487 });
16488
16489 // Update the selection to match the position of the selection inside
16490 // the rename editor.
16491 let snapshot = self.buffer.read(cx).read(cx);
16492 let rename_range = rename.range.to_offset(&snapshot);
16493 let cursor_in_editor = snapshot
16494 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16495 .min(rename_range.end);
16496 drop(snapshot);
16497
16498 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16499 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16500 });
16501 } else {
16502 self.refresh_document_highlights(cx);
16503 }
16504
16505 Some(rename)
16506 }
16507
16508 pub fn pending_rename(&self) -> Option<&RenameState> {
16509 self.pending_rename.as_ref()
16510 }
16511
16512 fn format(
16513 &mut self,
16514 _: &Format,
16515 window: &mut Window,
16516 cx: &mut Context<Self>,
16517 ) -> Option<Task<Result<()>>> {
16518 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16519
16520 let project = match &self.project {
16521 Some(project) => project.clone(),
16522 None => return None,
16523 };
16524
16525 Some(self.perform_format(
16526 project,
16527 FormatTrigger::Manual,
16528 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16529 window,
16530 cx,
16531 ))
16532 }
16533
16534 fn format_selections(
16535 &mut self,
16536 _: &FormatSelections,
16537 window: &mut Window,
16538 cx: &mut Context<Self>,
16539 ) -> Option<Task<Result<()>>> {
16540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16541
16542 let project = match &self.project {
16543 Some(project) => project.clone(),
16544 None => return None,
16545 };
16546
16547 let ranges = self
16548 .selections
16549 .all_adjusted(cx)
16550 .into_iter()
16551 .map(|selection| selection.range())
16552 .collect_vec();
16553
16554 Some(self.perform_format(
16555 project,
16556 FormatTrigger::Manual,
16557 FormatTarget::Ranges(ranges),
16558 window,
16559 cx,
16560 ))
16561 }
16562
16563 fn perform_format(
16564 &mut self,
16565 project: Entity<Project>,
16566 trigger: FormatTrigger,
16567 target: FormatTarget,
16568 window: &mut Window,
16569 cx: &mut Context<Self>,
16570 ) -> Task<Result<()>> {
16571 let buffer = self.buffer.clone();
16572 let (buffers, target) = match target {
16573 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16574 FormatTarget::Ranges(selection_ranges) => {
16575 let multi_buffer = buffer.read(cx);
16576 let snapshot = multi_buffer.read(cx);
16577 let mut buffers = HashSet::default();
16578 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16579 BTreeMap::new();
16580 for selection_range in selection_ranges {
16581 for (buffer, buffer_range, _) in
16582 snapshot.range_to_buffer_ranges(selection_range)
16583 {
16584 let buffer_id = buffer.remote_id();
16585 let start = buffer.anchor_before(buffer_range.start);
16586 let end = buffer.anchor_after(buffer_range.end);
16587 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16588 buffer_id_to_ranges
16589 .entry(buffer_id)
16590 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16591 .or_insert_with(|| vec![start..end]);
16592 }
16593 }
16594 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16595 }
16596 };
16597
16598 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16599 let selections_prev = transaction_id_prev
16600 .and_then(|transaction_id_prev| {
16601 // default to selections as they were after the last edit, if we have them,
16602 // instead of how they are now.
16603 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16604 // will take you back to where you made the last edit, instead of staying where you scrolled
16605 self.selection_history
16606 .transaction(transaction_id_prev)
16607 .map(|t| t.0.clone())
16608 })
16609 .unwrap_or_else(|| self.selections.disjoint_anchors());
16610
16611 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16612 let format = project.update(cx, |project, cx| {
16613 project.format(buffers, target, true, trigger, cx)
16614 });
16615
16616 cx.spawn_in(window, async move |editor, cx| {
16617 let transaction = futures::select_biased! {
16618 transaction = format.log_err().fuse() => transaction,
16619 () = timeout => {
16620 log::warn!("timed out waiting for formatting");
16621 None
16622 }
16623 };
16624
16625 buffer
16626 .update(cx, |buffer, cx| {
16627 if let Some(transaction) = transaction
16628 && !buffer.is_singleton()
16629 {
16630 buffer.push_transaction(&transaction.0, cx);
16631 }
16632 cx.notify();
16633 })
16634 .ok();
16635
16636 if let Some(transaction_id_now) =
16637 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16638 {
16639 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16640 if has_new_transaction {
16641 _ = editor.update(cx, |editor, _| {
16642 editor
16643 .selection_history
16644 .insert_transaction(transaction_id_now, selections_prev);
16645 });
16646 }
16647 }
16648
16649 Ok(())
16650 })
16651 }
16652
16653 fn organize_imports(
16654 &mut self,
16655 _: &OrganizeImports,
16656 window: &mut Window,
16657 cx: &mut Context<Self>,
16658 ) -> Option<Task<Result<()>>> {
16659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16660 let project = match &self.project {
16661 Some(project) => project.clone(),
16662 None => return None,
16663 };
16664 Some(self.perform_code_action_kind(
16665 project,
16666 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16667 window,
16668 cx,
16669 ))
16670 }
16671
16672 fn perform_code_action_kind(
16673 &mut self,
16674 project: Entity<Project>,
16675 kind: CodeActionKind,
16676 window: &mut Window,
16677 cx: &mut Context<Self>,
16678 ) -> Task<Result<()>> {
16679 let buffer = self.buffer.clone();
16680 let buffers = buffer.read(cx).all_buffers();
16681 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16682 let apply_action = project.update(cx, |project, cx| {
16683 project.apply_code_action_kind(buffers, kind, true, cx)
16684 });
16685 cx.spawn_in(window, async move |_, cx| {
16686 let transaction = futures::select_biased! {
16687 () = timeout => {
16688 log::warn!("timed out waiting for executing code action");
16689 None
16690 }
16691 transaction = apply_action.log_err().fuse() => transaction,
16692 };
16693 buffer
16694 .update(cx, |buffer, cx| {
16695 // check if we need this
16696 if let Some(transaction) = transaction
16697 && !buffer.is_singleton()
16698 {
16699 buffer.push_transaction(&transaction.0, cx);
16700 }
16701 cx.notify();
16702 })
16703 .ok();
16704 Ok(())
16705 })
16706 }
16707
16708 pub fn restart_language_server(
16709 &mut self,
16710 _: &RestartLanguageServer,
16711 _: &mut Window,
16712 cx: &mut Context<Self>,
16713 ) {
16714 if let Some(project) = self.project.clone() {
16715 self.buffer.update(cx, |multi_buffer, cx| {
16716 project.update(cx, |project, cx| {
16717 project.restart_language_servers_for_buffers(
16718 multi_buffer.all_buffers().into_iter().collect(),
16719 HashSet::default(),
16720 cx,
16721 );
16722 });
16723 })
16724 }
16725 }
16726
16727 pub fn stop_language_server(
16728 &mut self,
16729 _: &StopLanguageServer,
16730 _: &mut Window,
16731 cx: &mut Context<Self>,
16732 ) {
16733 if let Some(project) = self.project.clone() {
16734 self.buffer.update(cx, |multi_buffer, cx| {
16735 project.update(cx, |project, cx| {
16736 project.stop_language_servers_for_buffers(
16737 multi_buffer.all_buffers().into_iter().collect(),
16738 HashSet::default(),
16739 cx,
16740 );
16741 cx.emit(project::Event::RefreshInlayHints);
16742 });
16743 });
16744 }
16745 }
16746
16747 fn cancel_language_server_work(
16748 workspace: &mut Workspace,
16749 _: &actions::CancelLanguageServerWork,
16750 _: &mut Window,
16751 cx: &mut Context<Workspace>,
16752 ) {
16753 let project = workspace.project();
16754 let buffers = workspace
16755 .active_item(cx)
16756 .and_then(|item| item.act_as::<Editor>(cx))
16757 .map_or(HashSet::default(), |editor| {
16758 editor.read(cx).buffer.read(cx).all_buffers()
16759 });
16760 project.update(cx, |project, cx| {
16761 project.cancel_language_server_work_for_buffers(buffers, cx);
16762 });
16763 }
16764
16765 fn show_character_palette(
16766 &mut self,
16767 _: &ShowCharacterPalette,
16768 window: &mut Window,
16769 _: &mut Context<Self>,
16770 ) {
16771 window.show_character_palette();
16772 }
16773
16774 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16775 if !self.diagnostics_enabled() {
16776 return;
16777 }
16778
16779 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16780 let buffer = self.buffer.read(cx).snapshot(cx);
16781 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16782 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16783 let is_valid = buffer
16784 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16785 .any(|entry| {
16786 entry.diagnostic.is_primary
16787 && !entry.range.is_empty()
16788 && entry.range.start == primary_range_start
16789 && entry.diagnostic.message == active_diagnostics.active_message
16790 });
16791
16792 if !is_valid {
16793 self.dismiss_diagnostics(cx);
16794 }
16795 }
16796 }
16797
16798 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16799 match &self.active_diagnostics {
16800 ActiveDiagnostic::Group(group) => Some(group),
16801 _ => None,
16802 }
16803 }
16804
16805 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16806 if !self.diagnostics_enabled() {
16807 return;
16808 }
16809 self.dismiss_diagnostics(cx);
16810 self.active_diagnostics = ActiveDiagnostic::All;
16811 }
16812
16813 fn activate_diagnostics(
16814 &mut self,
16815 buffer_id: BufferId,
16816 diagnostic: DiagnosticEntry<usize>,
16817 window: &mut Window,
16818 cx: &mut Context<Self>,
16819 ) {
16820 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16821 return;
16822 }
16823 self.dismiss_diagnostics(cx);
16824 let snapshot = self.snapshot(window, cx);
16825 let buffer = self.buffer.read(cx).snapshot(cx);
16826 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16827 return;
16828 };
16829
16830 let diagnostic_group = buffer
16831 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16832 .collect::<Vec<_>>();
16833
16834 let blocks =
16835 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16836
16837 let blocks = self.display_map.update(cx, |display_map, cx| {
16838 display_map.insert_blocks(blocks, cx).into_iter().collect()
16839 });
16840 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16841 active_range: buffer.anchor_before(diagnostic.range.start)
16842 ..buffer.anchor_after(diagnostic.range.end),
16843 active_message: diagnostic.diagnostic.message.clone(),
16844 group_id: diagnostic.diagnostic.group_id,
16845 blocks,
16846 });
16847 cx.notify();
16848 }
16849
16850 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16851 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16852 return;
16853 };
16854
16855 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16856 if let ActiveDiagnostic::Group(group) = prev {
16857 self.display_map.update(cx, |display_map, cx| {
16858 display_map.remove_blocks(group.blocks, cx);
16859 });
16860 cx.notify();
16861 }
16862 }
16863
16864 /// Disable inline diagnostics rendering for this editor.
16865 pub fn disable_inline_diagnostics(&mut self) {
16866 self.inline_diagnostics_enabled = false;
16867 self.inline_diagnostics_update = Task::ready(());
16868 self.inline_diagnostics.clear();
16869 }
16870
16871 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16872 self.diagnostics_enabled = false;
16873 self.dismiss_diagnostics(cx);
16874 self.inline_diagnostics_update = Task::ready(());
16875 self.inline_diagnostics.clear();
16876 }
16877
16878 pub fn diagnostics_enabled(&self) -> bool {
16879 self.diagnostics_enabled && self.mode.is_full()
16880 }
16881
16882 pub fn inline_diagnostics_enabled(&self) -> bool {
16883 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16884 }
16885
16886 pub fn show_inline_diagnostics(&self) -> bool {
16887 self.show_inline_diagnostics
16888 }
16889
16890 pub fn toggle_inline_diagnostics(
16891 &mut self,
16892 _: &ToggleInlineDiagnostics,
16893 window: &mut Window,
16894 cx: &mut Context<Editor>,
16895 ) {
16896 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16897 self.refresh_inline_diagnostics(false, window, cx);
16898 }
16899
16900 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16901 self.diagnostics_max_severity = severity;
16902 self.display_map.update(cx, |display_map, _| {
16903 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16904 });
16905 }
16906
16907 pub fn toggle_diagnostics(
16908 &mut self,
16909 _: &ToggleDiagnostics,
16910 window: &mut Window,
16911 cx: &mut Context<Editor>,
16912 ) {
16913 if !self.diagnostics_enabled() {
16914 return;
16915 }
16916
16917 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16918 EditorSettings::get_global(cx)
16919 .diagnostics_max_severity
16920 .filter(|severity| severity != &DiagnosticSeverity::Off)
16921 .unwrap_or(DiagnosticSeverity::Hint)
16922 } else {
16923 DiagnosticSeverity::Off
16924 };
16925 self.set_max_diagnostics_severity(new_severity, cx);
16926 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16927 self.active_diagnostics = ActiveDiagnostic::None;
16928 self.inline_diagnostics_update = Task::ready(());
16929 self.inline_diagnostics.clear();
16930 } else {
16931 self.refresh_inline_diagnostics(false, window, cx);
16932 }
16933
16934 cx.notify();
16935 }
16936
16937 pub fn toggle_minimap(
16938 &mut self,
16939 _: &ToggleMinimap,
16940 window: &mut Window,
16941 cx: &mut Context<Editor>,
16942 ) {
16943 if self.supports_minimap(cx) {
16944 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16945 }
16946 }
16947
16948 fn refresh_inline_diagnostics(
16949 &mut self,
16950 debounce: bool,
16951 window: &mut Window,
16952 cx: &mut Context<Self>,
16953 ) {
16954 let max_severity = ProjectSettings::get_global(cx)
16955 .diagnostics
16956 .inline
16957 .max_severity
16958 .unwrap_or(self.diagnostics_max_severity);
16959
16960 if !self.inline_diagnostics_enabled()
16961 || !self.show_inline_diagnostics
16962 || max_severity == DiagnosticSeverity::Off
16963 {
16964 self.inline_diagnostics_update = Task::ready(());
16965 self.inline_diagnostics.clear();
16966 return;
16967 }
16968
16969 let debounce_ms = ProjectSettings::get_global(cx)
16970 .diagnostics
16971 .inline
16972 .update_debounce_ms;
16973 let debounce = if debounce && debounce_ms > 0 {
16974 Some(Duration::from_millis(debounce_ms))
16975 } else {
16976 None
16977 };
16978 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16979 if let Some(debounce) = debounce {
16980 cx.background_executor().timer(debounce).await;
16981 }
16982 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16983 editor
16984 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16985 .ok()
16986 }) else {
16987 return;
16988 };
16989
16990 let new_inline_diagnostics = cx
16991 .background_spawn(async move {
16992 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16993 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16994 let message = diagnostic_entry
16995 .diagnostic
16996 .message
16997 .split_once('\n')
16998 .map(|(line, _)| line)
16999 .map(SharedString::new)
17000 .unwrap_or_else(|| {
17001 SharedString::from(diagnostic_entry.diagnostic.message)
17002 });
17003 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17004 let (Ok(i) | Err(i)) = inline_diagnostics
17005 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17006 inline_diagnostics.insert(
17007 i,
17008 (
17009 start_anchor,
17010 InlineDiagnostic {
17011 message,
17012 group_id: diagnostic_entry.diagnostic.group_id,
17013 start: diagnostic_entry.range.start.to_point(&snapshot),
17014 is_primary: diagnostic_entry.diagnostic.is_primary,
17015 severity: diagnostic_entry.diagnostic.severity,
17016 },
17017 ),
17018 );
17019 }
17020 inline_diagnostics
17021 })
17022 .await;
17023
17024 editor
17025 .update(cx, |editor, cx| {
17026 editor.inline_diagnostics = new_inline_diagnostics;
17027 cx.notify();
17028 })
17029 .ok();
17030 });
17031 }
17032
17033 fn pull_diagnostics(
17034 &mut self,
17035 buffer_id: Option<BufferId>,
17036 window: &Window,
17037 cx: &mut Context<Self>,
17038 ) -> Option<()> {
17039 if !self.mode().is_full() {
17040 return None;
17041 }
17042 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17043 .diagnostics
17044 .lsp_pull_diagnostics;
17045 if !pull_diagnostics_settings.enabled {
17046 return None;
17047 }
17048 let project = self.project()?.downgrade();
17049 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17050 let mut buffers = self.buffer.read(cx).all_buffers();
17051 if let Some(buffer_id) = buffer_id {
17052 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17053 }
17054
17055 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17056 cx.background_executor().timer(debounce).await;
17057
17058 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17059 buffers
17060 .into_iter()
17061 .filter_map(|buffer| {
17062 project
17063 .update(cx, |project, cx| {
17064 project.lsp_store().update(cx, |lsp_store, cx| {
17065 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17066 })
17067 })
17068 .ok()
17069 })
17070 .collect::<FuturesUnordered<_>>()
17071 }) else {
17072 return;
17073 };
17074
17075 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17076 match pull_task {
17077 Ok(()) => {
17078 if editor
17079 .update_in(cx, |editor, window, cx| {
17080 editor.update_diagnostics_state(window, cx);
17081 })
17082 .is_err()
17083 {
17084 return;
17085 }
17086 }
17087 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17088 }
17089 }
17090 });
17091
17092 Some(())
17093 }
17094
17095 pub fn set_selections_from_remote(
17096 &mut self,
17097 selections: Vec<Selection<Anchor>>,
17098 pending_selection: Option<Selection<Anchor>>,
17099 window: &mut Window,
17100 cx: &mut Context<Self>,
17101 ) {
17102 let old_cursor_position = self.selections.newest_anchor().head();
17103 self.selections.change_with(cx, |s| {
17104 s.select_anchors(selections);
17105 if let Some(pending_selection) = pending_selection {
17106 s.set_pending(pending_selection, SelectMode::Character);
17107 } else {
17108 s.clear_pending();
17109 }
17110 });
17111 self.selections_did_change(
17112 false,
17113 &old_cursor_position,
17114 SelectionEffects::default(),
17115 window,
17116 cx,
17117 );
17118 }
17119
17120 pub fn transact(
17121 &mut self,
17122 window: &mut Window,
17123 cx: &mut Context<Self>,
17124 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17125 ) -> Option<TransactionId> {
17126 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17127 this.start_transaction_at(Instant::now(), window, cx);
17128 update(this, window, cx);
17129 this.end_transaction_at(Instant::now(), cx)
17130 })
17131 }
17132
17133 pub fn start_transaction_at(
17134 &mut self,
17135 now: Instant,
17136 window: &mut Window,
17137 cx: &mut Context<Self>,
17138 ) -> Option<TransactionId> {
17139 self.end_selection(window, cx);
17140 if let Some(tx_id) = self
17141 .buffer
17142 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17143 {
17144 self.selection_history
17145 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17146 cx.emit(EditorEvent::TransactionBegun {
17147 transaction_id: tx_id,
17148 });
17149 Some(tx_id)
17150 } else {
17151 None
17152 }
17153 }
17154
17155 pub fn end_transaction_at(
17156 &mut self,
17157 now: Instant,
17158 cx: &mut Context<Self>,
17159 ) -> Option<TransactionId> {
17160 if let Some(transaction_id) = self
17161 .buffer
17162 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17163 {
17164 if let Some((_, end_selections)) =
17165 self.selection_history.transaction_mut(transaction_id)
17166 {
17167 *end_selections = Some(self.selections.disjoint_anchors());
17168 } else {
17169 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17170 }
17171
17172 cx.emit(EditorEvent::Edited { transaction_id });
17173 Some(transaction_id)
17174 } else {
17175 None
17176 }
17177 }
17178
17179 pub fn modify_transaction_selection_history(
17180 &mut self,
17181 transaction_id: TransactionId,
17182 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17183 ) -> bool {
17184 self.selection_history
17185 .transaction_mut(transaction_id)
17186 .map(modify)
17187 .is_some()
17188 }
17189
17190 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17191 if self.selection_mark_mode {
17192 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17193 s.move_with(|_, sel| {
17194 sel.collapse_to(sel.head(), SelectionGoal::None);
17195 });
17196 })
17197 }
17198 self.selection_mark_mode = true;
17199 cx.notify();
17200 }
17201
17202 pub fn swap_selection_ends(
17203 &mut self,
17204 _: &actions::SwapSelectionEnds,
17205 window: &mut Window,
17206 cx: &mut Context<Self>,
17207 ) {
17208 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17209 s.move_with(|_, sel| {
17210 if sel.start != sel.end {
17211 sel.reversed = !sel.reversed
17212 }
17213 });
17214 });
17215 self.request_autoscroll(Autoscroll::newest(), cx);
17216 cx.notify();
17217 }
17218
17219 pub fn toggle_focus(
17220 workspace: &mut Workspace,
17221 _: &actions::ToggleFocus,
17222 window: &mut Window,
17223 cx: &mut Context<Workspace>,
17224 ) {
17225 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17226 return;
17227 };
17228 workspace.activate_item(&item, true, true, window, cx);
17229 }
17230
17231 pub fn toggle_fold(
17232 &mut self,
17233 _: &actions::ToggleFold,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) {
17237 if self.is_singleton(cx) {
17238 let selection = self.selections.newest::<Point>(cx);
17239
17240 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17241 let range = if selection.is_empty() {
17242 let point = selection.head().to_display_point(&display_map);
17243 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17244 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17245 .to_point(&display_map);
17246 start..end
17247 } else {
17248 selection.range()
17249 };
17250 if display_map.folds_in_range(range).next().is_some() {
17251 self.unfold_lines(&Default::default(), window, cx)
17252 } else {
17253 self.fold(&Default::default(), window, cx)
17254 }
17255 } else {
17256 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17257 let buffer_ids: HashSet<_> = self
17258 .selections
17259 .disjoint_anchor_ranges()
17260 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17261 .collect();
17262
17263 let should_unfold = buffer_ids
17264 .iter()
17265 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17266
17267 for buffer_id in buffer_ids {
17268 if should_unfold {
17269 self.unfold_buffer(buffer_id, cx);
17270 } else {
17271 self.fold_buffer(buffer_id, cx);
17272 }
17273 }
17274 }
17275 }
17276
17277 pub fn toggle_fold_recursive(
17278 &mut self,
17279 _: &actions::ToggleFoldRecursive,
17280 window: &mut Window,
17281 cx: &mut Context<Self>,
17282 ) {
17283 let selection = self.selections.newest::<Point>(cx);
17284
17285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17286 let range = if selection.is_empty() {
17287 let point = selection.head().to_display_point(&display_map);
17288 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17289 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17290 .to_point(&display_map);
17291 start..end
17292 } else {
17293 selection.range()
17294 };
17295 if display_map.folds_in_range(range).next().is_some() {
17296 self.unfold_recursive(&Default::default(), window, cx)
17297 } else {
17298 self.fold_recursive(&Default::default(), window, cx)
17299 }
17300 }
17301
17302 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17303 if self.is_singleton(cx) {
17304 let mut to_fold = Vec::new();
17305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17306 let selections = self.selections.all_adjusted(cx);
17307
17308 for selection in selections {
17309 let range = selection.range().sorted();
17310 let buffer_start_row = range.start.row;
17311
17312 if range.start.row != range.end.row {
17313 let mut found = false;
17314 let mut row = range.start.row;
17315 while row <= range.end.row {
17316 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17317 {
17318 found = true;
17319 row = crease.range().end.row + 1;
17320 to_fold.push(crease);
17321 } else {
17322 row += 1
17323 }
17324 }
17325 if found {
17326 continue;
17327 }
17328 }
17329
17330 for row in (0..=range.start.row).rev() {
17331 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17332 && crease.range().end.row >= buffer_start_row
17333 {
17334 to_fold.push(crease);
17335 if row <= range.start.row {
17336 break;
17337 }
17338 }
17339 }
17340 }
17341
17342 self.fold_creases(to_fold, true, window, cx);
17343 } else {
17344 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17345 let buffer_ids = self
17346 .selections
17347 .disjoint_anchor_ranges()
17348 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17349 .collect::<HashSet<_>>();
17350 for buffer_id in buffer_ids {
17351 self.fold_buffer(buffer_id, cx);
17352 }
17353 }
17354 }
17355
17356 pub fn toggle_fold_all(
17357 &mut self,
17358 _: &actions::ToggleFoldAll,
17359 window: &mut Window,
17360 cx: &mut Context<Self>,
17361 ) {
17362 if self.buffer.read(cx).is_singleton() {
17363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17364 let has_folds = display_map
17365 .folds_in_range(0..display_map.buffer_snapshot.len())
17366 .next()
17367 .is_some();
17368
17369 if has_folds {
17370 self.unfold_all(&actions::UnfoldAll, window, cx);
17371 } else {
17372 self.fold_all(&actions::FoldAll, window, cx);
17373 }
17374 } else {
17375 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17376 let should_unfold = buffer_ids
17377 .iter()
17378 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17379
17380 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17381 editor
17382 .update_in(cx, |editor, _, cx| {
17383 for buffer_id in buffer_ids {
17384 if should_unfold {
17385 editor.unfold_buffer(buffer_id, cx);
17386 } else {
17387 editor.fold_buffer(buffer_id, cx);
17388 }
17389 }
17390 })
17391 .ok();
17392 });
17393 }
17394 }
17395
17396 fn fold_at_level(
17397 &mut self,
17398 fold_at: &FoldAtLevel,
17399 window: &mut Window,
17400 cx: &mut Context<Self>,
17401 ) {
17402 if !self.buffer.read(cx).is_singleton() {
17403 return;
17404 }
17405
17406 let fold_at_level = fold_at.0;
17407 let snapshot = self.buffer.read(cx).snapshot(cx);
17408 let mut to_fold = Vec::new();
17409 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17410
17411 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17412 while start_row < end_row {
17413 match self
17414 .snapshot(window, cx)
17415 .crease_for_buffer_row(MultiBufferRow(start_row))
17416 {
17417 Some(crease) => {
17418 let nested_start_row = crease.range().start.row + 1;
17419 let nested_end_row = crease.range().end.row;
17420
17421 if current_level < fold_at_level {
17422 stack.push((nested_start_row, nested_end_row, current_level + 1));
17423 } else if current_level == fold_at_level {
17424 to_fold.push(crease);
17425 }
17426
17427 start_row = nested_end_row + 1;
17428 }
17429 None => start_row += 1,
17430 }
17431 }
17432 }
17433
17434 self.fold_creases(to_fold, true, window, cx);
17435 }
17436
17437 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17438 if self.buffer.read(cx).is_singleton() {
17439 let mut fold_ranges = Vec::new();
17440 let snapshot = self.buffer.read(cx).snapshot(cx);
17441
17442 for row in 0..snapshot.max_row().0 {
17443 if let Some(foldable_range) = self
17444 .snapshot(window, cx)
17445 .crease_for_buffer_row(MultiBufferRow(row))
17446 {
17447 fold_ranges.push(foldable_range);
17448 }
17449 }
17450
17451 self.fold_creases(fold_ranges, true, window, cx);
17452 } else {
17453 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17454 editor
17455 .update_in(cx, |editor, _, cx| {
17456 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17457 editor.fold_buffer(buffer_id, cx);
17458 }
17459 })
17460 .ok();
17461 });
17462 }
17463 }
17464
17465 pub fn fold_function_bodies(
17466 &mut self,
17467 _: &actions::FoldFunctionBodies,
17468 window: &mut Window,
17469 cx: &mut Context<Self>,
17470 ) {
17471 let snapshot = self.buffer.read(cx).snapshot(cx);
17472
17473 let ranges = snapshot
17474 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17475 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17476 .collect::<Vec<_>>();
17477
17478 let creases = ranges
17479 .into_iter()
17480 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17481 .collect();
17482
17483 self.fold_creases(creases, true, window, cx);
17484 }
17485
17486 pub fn fold_recursive(
17487 &mut self,
17488 _: &actions::FoldRecursive,
17489 window: &mut Window,
17490 cx: &mut Context<Self>,
17491 ) {
17492 let mut to_fold = Vec::new();
17493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17494 let selections = self.selections.all_adjusted(cx);
17495
17496 for selection in selections {
17497 let range = selection.range().sorted();
17498 let buffer_start_row = range.start.row;
17499
17500 if range.start.row != range.end.row {
17501 let mut found = false;
17502 for row in range.start.row..=range.end.row {
17503 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17504 found = true;
17505 to_fold.push(crease);
17506 }
17507 }
17508 if found {
17509 continue;
17510 }
17511 }
17512
17513 for row in (0..=range.start.row).rev() {
17514 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17515 if crease.range().end.row >= buffer_start_row {
17516 to_fold.push(crease);
17517 } else {
17518 break;
17519 }
17520 }
17521 }
17522 }
17523
17524 self.fold_creases(to_fold, true, window, cx);
17525 }
17526
17527 pub fn fold_at(
17528 &mut self,
17529 buffer_row: MultiBufferRow,
17530 window: &mut Window,
17531 cx: &mut Context<Self>,
17532 ) {
17533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17534
17535 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17536 let autoscroll = self
17537 .selections
17538 .all::<Point>(cx)
17539 .iter()
17540 .any(|selection| crease.range().overlaps(&selection.range()));
17541
17542 self.fold_creases(vec![crease], autoscroll, window, cx);
17543 }
17544 }
17545
17546 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17547 if self.is_singleton(cx) {
17548 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17549 let buffer = &display_map.buffer_snapshot;
17550 let selections = self.selections.all::<Point>(cx);
17551 let ranges = selections
17552 .iter()
17553 .map(|s| {
17554 let range = s.display_range(&display_map).sorted();
17555 let mut start = range.start.to_point(&display_map);
17556 let mut end = range.end.to_point(&display_map);
17557 start.column = 0;
17558 end.column = buffer.line_len(MultiBufferRow(end.row));
17559 start..end
17560 })
17561 .collect::<Vec<_>>();
17562
17563 self.unfold_ranges(&ranges, true, true, cx);
17564 } else {
17565 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17566 let buffer_ids = self
17567 .selections
17568 .disjoint_anchor_ranges()
17569 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17570 .collect::<HashSet<_>>();
17571 for buffer_id in buffer_ids {
17572 self.unfold_buffer(buffer_id, cx);
17573 }
17574 }
17575 }
17576
17577 pub fn unfold_recursive(
17578 &mut self,
17579 _: &UnfoldRecursive,
17580 _window: &mut Window,
17581 cx: &mut Context<Self>,
17582 ) {
17583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17584 let selections = self.selections.all::<Point>(cx);
17585 let ranges = selections
17586 .iter()
17587 .map(|s| {
17588 let mut range = s.display_range(&display_map).sorted();
17589 *range.start.column_mut() = 0;
17590 *range.end.column_mut() = display_map.line_len(range.end.row());
17591 let start = range.start.to_point(&display_map);
17592 let end = range.end.to_point(&display_map);
17593 start..end
17594 })
17595 .collect::<Vec<_>>();
17596
17597 self.unfold_ranges(&ranges, true, true, cx);
17598 }
17599
17600 pub fn unfold_at(
17601 &mut self,
17602 buffer_row: MultiBufferRow,
17603 _window: &mut Window,
17604 cx: &mut Context<Self>,
17605 ) {
17606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17607
17608 let intersection_range = Point::new(buffer_row.0, 0)
17609 ..Point::new(
17610 buffer_row.0,
17611 display_map.buffer_snapshot.line_len(buffer_row),
17612 );
17613
17614 let autoscroll = self
17615 .selections
17616 .all::<Point>(cx)
17617 .iter()
17618 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17619
17620 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17621 }
17622
17623 pub fn unfold_all(
17624 &mut self,
17625 _: &actions::UnfoldAll,
17626 _window: &mut Window,
17627 cx: &mut Context<Self>,
17628 ) {
17629 if self.buffer.read(cx).is_singleton() {
17630 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17631 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17632 } else {
17633 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17634 editor
17635 .update(cx, |editor, cx| {
17636 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17637 editor.unfold_buffer(buffer_id, cx);
17638 }
17639 })
17640 .ok();
17641 });
17642 }
17643 }
17644
17645 pub fn fold_selected_ranges(
17646 &mut self,
17647 _: &FoldSelectedRanges,
17648 window: &mut Window,
17649 cx: &mut Context<Self>,
17650 ) {
17651 let selections = self.selections.all_adjusted(cx);
17652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17653 let ranges = selections
17654 .into_iter()
17655 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17656 .collect::<Vec<_>>();
17657 self.fold_creases(ranges, true, window, cx);
17658 }
17659
17660 pub fn fold_ranges<T: ToOffset + Clone>(
17661 &mut self,
17662 ranges: Vec<Range<T>>,
17663 auto_scroll: bool,
17664 window: &mut Window,
17665 cx: &mut Context<Self>,
17666 ) {
17667 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17668 let ranges = ranges
17669 .into_iter()
17670 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17671 .collect::<Vec<_>>();
17672 self.fold_creases(ranges, auto_scroll, window, cx);
17673 }
17674
17675 pub fn fold_creases<T: ToOffset + Clone>(
17676 &mut self,
17677 creases: Vec<Crease<T>>,
17678 auto_scroll: bool,
17679 _window: &mut Window,
17680 cx: &mut Context<Self>,
17681 ) {
17682 if creases.is_empty() {
17683 return;
17684 }
17685
17686 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17687
17688 if auto_scroll {
17689 self.request_autoscroll(Autoscroll::fit(), cx);
17690 }
17691
17692 cx.notify();
17693
17694 self.scrollbar_marker_state.dirty = true;
17695 self.folds_did_change(cx);
17696 }
17697
17698 /// Removes any folds whose ranges intersect any of the given ranges.
17699 pub fn unfold_ranges<T: ToOffset + Clone>(
17700 &mut self,
17701 ranges: &[Range<T>],
17702 inclusive: bool,
17703 auto_scroll: bool,
17704 cx: &mut Context<Self>,
17705 ) {
17706 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17707 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17708 });
17709 self.folds_did_change(cx);
17710 }
17711
17712 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17713 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17714 return;
17715 }
17716 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17717 self.display_map.update(cx, |display_map, cx| {
17718 display_map.fold_buffers([buffer_id], cx)
17719 });
17720 cx.emit(EditorEvent::BufferFoldToggled {
17721 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17722 folded: true,
17723 });
17724 cx.notify();
17725 }
17726
17727 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17728 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17729 return;
17730 }
17731 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17732 self.display_map.update(cx, |display_map, cx| {
17733 display_map.unfold_buffers([buffer_id], cx);
17734 });
17735 cx.emit(EditorEvent::BufferFoldToggled {
17736 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17737 folded: false,
17738 });
17739 cx.notify();
17740 }
17741
17742 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17743 self.display_map.read(cx).is_buffer_folded(buffer)
17744 }
17745
17746 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17747 self.display_map.read(cx).folded_buffers()
17748 }
17749
17750 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17751 self.display_map.update(cx, |display_map, cx| {
17752 display_map.disable_header_for_buffer(buffer_id, cx);
17753 });
17754 cx.notify();
17755 }
17756
17757 /// Removes any folds with the given ranges.
17758 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17759 &mut self,
17760 ranges: &[Range<T>],
17761 type_id: TypeId,
17762 auto_scroll: bool,
17763 cx: &mut Context<Self>,
17764 ) {
17765 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17766 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17767 });
17768 self.folds_did_change(cx);
17769 }
17770
17771 fn remove_folds_with<T: ToOffset + Clone>(
17772 &mut self,
17773 ranges: &[Range<T>],
17774 auto_scroll: bool,
17775 cx: &mut Context<Self>,
17776 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17777 ) {
17778 if ranges.is_empty() {
17779 return;
17780 }
17781
17782 let mut buffers_affected = HashSet::default();
17783 let multi_buffer = self.buffer().read(cx);
17784 for range in ranges {
17785 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17786 buffers_affected.insert(buffer.read(cx).remote_id());
17787 };
17788 }
17789
17790 self.display_map.update(cx, update);
17791
17792 if auto_scroll {
17793 self.request_autoscroll(Autoscroll::fit(), cx);
17794 }
17795
17796 cx.notify();
17797 self.scrollbar_marker_state.dirty = true;
17798 self.active_indent_guides_state.dirty = true;
17799 }
17800
17801 pub fn update_renderer_widths(
17802 &mut self,
17803 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17804 cx: &mut Context<Self>,
17805 ) -> bool {
17806 self.display_map
17807 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17808 }
17809
17810 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17811 self.display_map.read(cx).fold_placeholder.clone()
17812 }
17813
17814 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17815 self.buffer.update(cx, |buffer, cx| {
17816 buffer.set_all_diff_hunks_expanded(cx);
17817 });
17818 }
17819
17820 pub fn expand_all_diff_hunks(
17821 &mut self,
17822 _: &ExpandAllDiffHunks,
17823 _window: &mut Window,
17824 cx: &mut Context<Self>,
17825 ) {
17826 self.buffer.update(cx, |buffer, cx| {
17827 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17828 });
17829 }
17830
17831 pub fn toggle_selected_diff_hunks(
17832 &mut self,
17833 _: &ToggleSelectedDiffHunks,
17834 _window: &mut Window,
17835 cx: &mut Context<Self>,
17836 ) {
17837 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17838 self.toggle_diff_hunks_in_ranges(ranges, cx);
17839 }
17840
17841 pub fn diff_hunks_in_ranges<'a>(
17842 &'a self,
17843 ranges: &'a [Range<Anchor>],
17844 buffer: &'a MultiBufferSnapshot,
17845 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17846 ranges.iter().flat_map(move |range| {
17847 let end_excerpt_id = range.end.excerpt_id;
17848 let range = range.to_point(buffer);
17849 let mut peek_end = range.end;
17850 if range.end.row < buffer.max_row().0 {
17851 peek_end = Point::new(range.end.row + 1, 0);
17852 }
17853 buffer
17854 .diff_hunks_in_range(range.start..peek_end)
17855 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17856 })
17857 }
17858
17859 pub fn has_stageable_diff_hunks_in_ranges(
17860 &self,
17861 ranges: &[Range<Anchor>],
17862 snapshot: &MultiBufferSnapshot,
17863 ) -> bool {
17864 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17865 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17866 }
17867
17868 pub fn toggle_staged_selected_diff_hunks(
17869 &mut self,
17870 _: &::git::ToggleStaged,
17871 _: &mut Window,
17872 cx: &mut Context<Self>,
17873 ) {
17874 let snapshot = self.buffer.read(cx).snapshot(cx);
17875 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17876 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17877 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17878 }
17879
17880 pub fn set_render_diff_hunk_controls(
17881 &mut self,
17882 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17883 cx: &mut Context<Self>,
17884 ) {
17885 self.render_diff_hunk_controls = render_diff_hunk_controls;
17886 cx.notify();
17887 }
17888
17889 pub fn stage_and_next(
17890 &mut self,
17891 _: &::git::StageAndNext,
17892 window: &mut Window,
17893 cx: &mut Context<Self>,
17894 ) {
17895 self.do_stage_or_unstage_and_next(true, window, cx);
17896 }
17897
17898 pub fn unstage_and_next(
17899 &mut self,
17900 _: &::git::UnstageAndNext,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) {
17904 self.do_stage_or_unstage_and_next(false, window, cx);
17905 }
17906
17907 pub fn stage_or_unstage_diff_hunks(
17908 &mut self,
17909 stage: bool,
17910 ranges: Vec<Range<Anchor>>,
17911 cx: &mut Context<Self>,
17912 ) {
17913 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17914 cx.spawn(async move |this, cx| {
17915 task.await?;
17916 this.update(cx, |this, cx| {
17917 let snapshot = this.buffer.read(cx).snapshot(cx);
17918 let chunk_by = this
17919 .diff_hunks_in_ranges(&ranges, &snapshot)
17920 .chunk_by(|hunk| hunk.buffer_id);
17921 for (buffer_id, hunks) in &chunk_by {
17922 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17923 }
17924 })
17925 })
17926 .detach_and_log_err(cx);
17927 }
17928
17929 fn save_buffers_for_ranges_if_needed(
17930 &mut self,
17931 ranges: &[Range<Anchor>],
17932 cx: &mut Context<Editor>,
17933 ) -> Task<Result<()>> {
17934 let multibuffer = self.buffer.read(cx);
17935 let snapshot = multibuffer.read(cx);
17936 let buffer_ids: HashSet<_> = ranges
17937 .iter()
17938 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17939 .collect();
17940 drop(snapshot);
17941
17942 let mut buffers = HashSet::default();
17943 for buffer_id in buffer_ids {
17944 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17945 let buffer = buffer_entity.read(cx);
17946 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17947 {
17948 buffers.insert(buffer_entity);
17949 }
17950 }
17951 }
17952
17953 if let Some(project) = &self.project {
17954 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17955 } else {
17956 Task::ready(Ok(()))
17957 }
17958 }
17959
17960 fn do_stage_or_unstage_and_next(
17961 &mut self,
17962 stage: bool,
17963 window: &mut Window,
17964 cx: &mut Context<Self>,
17965 ) {
17966 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17967
17968 if ranges.iter().any(|range| range.start != range.end) {
17969 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17970 return;
17971 }
17972
17973 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17974 let snapshot = self.snapshot(window, cx);
17975 let position = self.selections.newest::<Point>(cx).head();
17976 let mut row = snapshot
17977 .buffer_snapshot
17978 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17979 .find(|hunk| hunk.row_range.start.0 > position.row)
17980 .map(|hunk| hunk.row_range.start);
17981
17982 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17983 // Outside of the project diff editor, wrap around to the beginning.
17984 if !all_diff_hunks_expanded {
17985 row = row.or_else(|| {
17986 snapshot
17987 .buffer_snapshot
17988 .diff_hunks_in_range(Point::zero()..position)
17989 .find(|hunk| hunk.row_range.end.0 < position.row)
17990 .map(|hunk| hunk.row_range.start)
17991 });
17992 }
17993
17994 if let Some(row) = row {
17995 let destination = Point::new(row.0, 0);
17996 let autoscroll = Autoscroll::center();
17997
17998 self.unfold_ranges(&[destination..destination], false, false, cx);
17999 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18000 s.select_ranges([destination..destination]);
18001 });
18002 }
18003 }
18004
18005 fn do_stage_or_unstage(
18006 &self,
18007 stage: bool,
18008 buffer_id: BufferId,
18009 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18010 cx: &mut App,
18011 ) -> Option<()> {
18012 let project = self.project()?;
18013 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18014 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18015 let buffer_snapshot = buffer.read(cx).snapshot();
18016 let file_exists = buffer_snapshot
18017 .file()
18018 .is_some_and(|file| file.disk_state().exists());
18019 diff.update(cx, |diff, cx| {
18020 diff.stage_or_unstage_hunks(
18021 stage,
18022 &hunks
18023 .map(|hunk| buffer_diff::DiffHunk {
18024 buffer_range: hunk.buffer_range,
18025 diff_base_byte_range: hunk.diff_base_byte_range,
18026 secondary_status: hunk.secondary_status,
18027 range: Point::zero()..Point::zero(), // unused
18028 })
18029 .collect::<Vec<_>>(),
18030 &buffer_snapshot,
18031 file_exists,
18032 cx,
18033 )
18034 });
18035 None
18036 }
18037
18038 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18039 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18040 self.buffer
18041 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18042 }
18043
18044 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18045 self.buffer.update(cx, |buffer, cx| {
18046 let ranges = vec![Anchor::min()..Anchor::max()];
18047 if !buffer.all_diff_hunks_expanded()
18048 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18049 {
18050 buffer.collapse_diff_hunks(ranges, cx);
18051 true
18052 } else {
18053 false
18054 }
18055 })
18056 }
18057
18058 fn toggle_diff_hunks_in_ranges(
18059 &mut self,
18060 ranges: Vec<Range<Anchor>>,
18061 cx: &mut Context<Editor>,
18062 ) {
18063 self.buffer.update(cx, |buffer, cx| {
18064 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18065 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18066 })
18067 }
18068
18069 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18070 self.buffer.update(cx, |buffer, cx| {
18071 let snapshot = buffer.snapshot(cx);
18072 let excerpt_id = range.end.excerpt_id;
18073 let point_range = range.to_point(&snapshot);
18074 let expand = !buffer.single_hunk_is_expanded(range, cx);
18075 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18076 })
18077 }
18078
18079 pub(crate) fn apply_all_diff_hunks(
18080 &mut self,
18081 _: &ApplyAllDiffHunks,
18082 window: &mut Window,
18083 cx: &mut Context<Self>,
18084 ) {
18085 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18086
18087 let buffers = self.buffer.read(cx).all_buffers();
18088 for branch_buffer in buffers {
18089 branch_buffer.update(cx, |branch_buffer, cx| {
18090 branch_buffer.merge_into_base(Vec::new(), cx);
18091 });
18092 }
18093
18094 if let Some(project) = self.project.clone() {
18095 self.save(
18096 SaveOptions {
18097 format: true,
18098 autosave: false,
18099 },
18100 project,
18101 window,
18102 cx,
18103 )
18104 .detach_and_log_err(cx);
18105 }
18106 }
18107
18108 pub(crate) fn apply_selected_diff_hunks(
18109 &mut self,
18110 _: &ApplyDiffHunk,
18111 window: &mut Window,
18112 cx: &mut Context<Self>,
18113 ) {
18114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18115 let snapshot = self.snapshot(window, cx);
18116 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18117 let mut ranges_by_buffer = HashMap::default();
18118 self.transact(window, cx, |editor, _window, cx| {
18119 for hunk in hunks {
18120 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18121 ranges_by_buffer
18122 .entry(buffer.clone())
18123 .or_insert_with(Vec::new)
18124 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18125 }
18126 }
18127
18128 for (buffer, ranges) in ranges_by_buffer {
18129 buffer.update(cx, |buffer, cx| {
18130 buffer.merge_into_base(ranges, cx);
18131 });
18132 }
18133 });
18134
18135 if let Some(project) = self.project.clone() {
18136 self.save(
18137 SaveOptions {
18138 format: true,
18139 autosave: false,
18140 },
18141 project,
18142 window,
18143 cx,
18144 )
18145 .detach_and_log_err(cx);
18146 }
18147 }
18148
18149 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18150 if hovered != self.gutter_hovered {
18151 self.gutter_hovered = hovered;
18152 cx.notify();
18153 }
18154 }
18155
18156 pub fn insert_blocks(
18157 &mut self,
18158 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18159 autoscroll: Option<Autoscroll>,
18160 cx: &mut Context<Self>,
18161 ) -> Vec<CustomBlockId> {
18162 let blocks = self
18163 .display_map
18164 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18165 if let Some(autoscroll) = autoscroll {
18166 self.request_autoscroll(autoscroll, cx);
18167 }
18168 cx.notify();
18169 blocks
18170 }
18171
18172 pub fn resize_blocks(
18173 &mut self,
18174 heights: HashMap<CustomBlockId, u32>,
18175 autoscroll: Option<Autoscroll>,
18176 cx: &mut Context<Self>,
18177 ) {
18178 self.display_map
18179 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18180 if let Some(autoscroll) = autoscroll {
18181 self.request_autoscroll(autoscroll, cx);
18182 }
18183 cx.notify();
18184 }
18185
18186 pub fn replace_blocks(
18187 &mut self,
18188 renderers: HashMap<CustomBlockId, RenderBlock>,
18189 autoscroll: Option<Autoscroll>,
18190 cx: &mut Context<Self>,
18191 ) {
18192 self.display_map
18193 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18194 if let Some(autoscroll) = autoscroll {
18195 self.request_autoscroll(autoscroll, cx);
18196 }
18197 cx.notify();
18198 }
18199
18200 pub fn remove_blocks(
18201 &mut self,
18202 block_ids: HashSet<CustomBlockId>,
18203 autoscroll: Option<Autoscroll>,
18204 cx: &mut Context<Self>,
18205 ) {
18206 self.display_map.update(cx, |display_map, cx| {
18207 display_map.remove_blocks(block_ids, cx)
18208 });
18209 if let Some(autoscroll) = autoscroll {
18210 self.request_autoscroll(autoscroll, cx);
18211 }
18212 cx.notify();
18213 }
18214
18215 pub fn row_for_block(
18216 &self,
18217 block_id: CustomBlockId,
18218 cx: &mut Context<Self>,
18219 ) -> Option<DisplayRow> {
18220 self.display_map
18221 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18222 }
18223
18224 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18225 self.focused_block = Some(focused_block);
18226 }
18227
18228 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18229 self.focused_block.take()
18230 }
18231
18232 pub fn insert_creases(
18233 &mut self,
18234 creases: impl IntoIterator<Item = Crease<Anchor>>,
18235 cx: &mut Context<Self>,
18236 ) -> Vec<CreaseId> {
18237 self.display_map
18238 .update(cx, |map, cx| map.insert_creases(creases, cx))
18239 }
18240
18241 pub fn remove_creases(
18242 &mut self,
18243 ids: impl IntoIterator<Item = CreaseId>,
18244 cx: &mut Context<Self>,
18245 ) -> Vec<(CreaseId, Range<Anchor>)> {
18246 self.display_map
18247 .update(cx, |map, cx| map.remove_creases(ids, cx))
18248 }
18249
18250 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18251 self.display_map
18252 .update(cx, |map, cx| map.snapshot(cx))
18253 .longest_row()
18254 }
18255
18256 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18257 self.display_map
18258 .update(cx, |map, cx| map.snapshot(cx))
18259 .max_point()
18260 }
18261
18262 pub fn text(&self, cx: &App) -> String {
18263 self.buffer.read(cx).read(cx).text()
18264 }
18265
18266 pub fn is_empty(&self, cx: &App) -> bool {
18267 self.buffer.read(cx).read(cx).is_empty()
18268 }
18269
18270 pub fn text_option(&self, cx: &App) -> Option<String> {
18271 let text = self.text(cx);
18272 let text = text.trim();
18273
18274 if text.is_empty() {
18275 return None;
18276 }
18277
18278 Some(text.to_string())
18279 }
18280
18281 pub fn set_text(
18282 &mut self,
18283 text: impl Into<Arc<str>>,
18284 window: &mut Window,
18285 cx: &mut Context<Self>,
18286 ) {
18287 self.transact(window, cx, |this, _, cx| {
18288 this.buffer
18289 .read(cx)
18290 .as_singleton()
18291 .expect("you can only call set_text on editors for singleton buffers")
18292 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18293 });
18294 }
18295
18296 pub fn display_text(&self, cx: &mut App) -> String {
18297 self.display_map
18298 .update(cx, |map, cx| map.snapshot(cx))
18299 .text()
18300 }
18301
18302 fn create_minimap(
18303 &self,
18304 minimap_settings: MinimapSettings,
18305 window: &mut Window,
18306 cx: &mut Context<Self>,
18307 ) -> Option<Entity<Self>> {
18308 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18309 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18310 }
18311
18312 fn initialize_new_minimap(
18313 &self,
18314 minimap_settings: MinimapSettings,
18315 window: &mut Window,
18316 cx: &mut Context<Self>,
18317 ) -> Entity<Self> {
18318 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18319
18320 let mut minimap = Editor::new_internal(
18321 EditorMode::Minimap {
18322 parent: cx.weak_entity(),
18323 },
18324 self.buffer.clone(),
18325 None,
18326 Some(self.display_map.clone()),
18327 window,
18328 cx,
18329 );
18330 minimap.scroll_manager.clone_state(&self.scroll_manager);
18331 minimap.set_text_style_refinement(TextStyleRefinement {
18332 font_size: Some(MINIMAP_FONT_SIZE),
18333 font_weight: Some(MINIMAP_FONT_WEIGHT),
18334 ..Default::default()
18335 });
18336 minimap.update_minimap_configuration(minimap_settings, cx);
18337 cx.new(|_| minimap)
18338 }
18339
18340 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18341 let current_line_highlight = minimap_settings
18342 .current_line_highlight
18343 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18344 self.set_current_line_highlight(Some(current_line_highlight));
18345 }
18346
18347 pub fn minimap(&self) -> Option<&Entity<Self>> {
18348 self.minimap
18349 .as_ref()
18350 .filter(|_| self.minimap_visibility.visible())
18351 }
18352
18353 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18354 let mut wrap_guides = smallvec![];
18355
18356 if self.show_wrap_guides == Some(false) {
18357 return wrap_guides;
18358 }
18359
18360 let settings = self.buffer.read(cx).language_settings(cx);
18361 if settings.show_wrap_guides {
18362 match self.soft_wrap_mode(cx) {
18363 SoftWrap::Column(soft_wrap) => {
18364 wrap_guides.push((soft_wrap as usize, true));
18365 }
18366 SoftWrap::Bounded(soft_wrap) => {
18367 wrap_guides.push((soft_wrap as usize, true));
18368 }
18369 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18370 }
18371 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18372 }
18373
18374 wrap_guides
18375 }
18376
18377 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18378 let settings = self.buffer.read(cx).language_settings(cx);
18379 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18380 match mode {
18381 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18382 SoftWrap::None
18383 }
18384 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18385 language_settings::SoftWrap::PreferredLineLength => {
18386 SoftWrap::Column(settings.preferred_line_length)
18387 }
18388 language_settings::SoftWrap::Bounded => {
18389 SoftWrap::Bounded(settings.preferred_line_length)
18390 }
18391 }
18392 }
18393
18394 pub fn set_soft_wrap_mode(
18395 &mut self,
18396 mode: language_settings::SoftWrap,
18397
18398 cx: &mut Context<Self>,
18399 ) {
18400 self.soft_wrap_mode_override = Some(mode);
18401 cx.notify();
18402 }
18403
18404 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18405 self.hard_wrap = hard_wrap;
18406 cx.notify();
18407 }
18408
18409 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18410 self.text_style_refinement = Some(style);
18411 }
18412
18413 /// called by the Element so we know what style we were most recently rendered with.
18414 pub(crate) fn set_style(
18415 &mut self,
18416 style: EditorStyle,
18417 window: &mut Window,
18418 cx: &mut Context<Self>,
18419 ) {
18420 // We intentionally do not inform the display map about the minimap style
18421 // so that wrapping is not recalculated and stays consistent for the editor
18422 // and its linked minimap.
18423 if !self.mode.is_minimap() {
18424 let rem_size = window.rem_size();
18425 self.display_map.update(cx, |map, cx| {
18426 map.set_font(
18427 style.text.font(),
18428 style.text.font_size.to_pixels(rem_size),
18429 cx,
18430 )
18431 });
18432 }
18433 self.style = Some(style);
18434 }
18435
18436 pub fn style(&self) -> Option<&EditorStyle> {
18437 self.style.as_ref()
18438 }
18439
18440 // Called by the element. This method is not designed to be called outside of the editor
18441 // element's layout code because it does not notify when rewrapping is computed synchronously.
18442 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18443 self.display_map
18444 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18445 }
18446
18447 pub fn set_soft_wrap(&mut self) {
18448 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18449 }
18450
18451 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18452 if self.soft_wrap_mode_override.is_some() {
18453 self.soft_wrap_mode_override.take();
18454 } else {
18455 let soft_wrap = match self.soft_wrap_mode(cx) {
18456 SoftWrap::GitDiff => return,
18457 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18458 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18459 language_settings::SoftWrap::None
18460 }
18461 };
18462 self.soft_wrap_mode_override = Some(soft_wrap);
18463 }
18464 cx.notify();
18465 }
18466
18467 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18468 let Some(workspace) = self.workspace() else {
18469 return;
18470 };
18471 let fs = workspace.read(cx).app_state().fs.clone();
18472 let current_show = TabBarSettings::get_global(cx).show;
18473 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18474 setting.show = Some(!current_show);
18475 });
18476 }
18477
18478 pub fn toggle_indent_guides(
18479 &mut self,
18480 _: &ToggleIndentGuides,
18481 _: &mut Window,
18482 cx: &mut Context<Self>,
18483 ) {
18484 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18485 self.buffer
18486 .read(cx)
18487 .language_settings(cx)
18488 .indent_guides
18489 .enabled
18490 });
18491 self.show_indent_guides = Some(!currently_enabled);
18492 cx.notify();
18493 }
18494
18495 fn should_show_indent_guides(&self) -> Option<bool> {
18496 self.show_indent_guides
18497 }
18498
18499 pub fn toggle_line_numbers(
18500 &mut self,
18501 _: &ToggleLineNumbers,
18502 _: &mut Window,
18503 cx: &mut Context<Self>,
18504 ) {
18505 let mut editor_settings = EditorSettings::get_global(cx).clone();
18506 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18507 EditorSettings::override_global(editor_settings, cx);
18508 }
18509
18510 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18511 if let Some(show_line_numbers) = self.show_line_numbers {
18512 return show_line_numbers;
18513 }
18514 EditorSettings::get_global(cx).gutter.line_numbers
18515 }
18516
18517 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18518 self.use_relative_line_numbers
18519 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18520 }
18521
18522 pub fn toggle_relative_line_numbers(
18523 &mut self,
18524 _: &ToggleRelativeLineNumbers,
18525 _: &mut Window,
18526 cx: &mut Context<Self>,
18527 ) {
18528 let is_relative = self.should_use_relative_line_numbers(cx);
18529 self.set_relative_line_number(Some(!is_relative), cx)
18530 }
18531
18532 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18533 self.use_relative_line_numbers = is_relative;
18534 cx.notify();
18535 }
18536
18537 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18538 self.show_gutter = show_gutter;
18539 cx.notify();
18540 }
18541
18542 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18543 self.show_scrollbars = ScrollbarAxes {
18544 horizontal: show,
18545 vertical: show,
18546 };
18547 cx.notify();
18548 }
18549
18550 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18551 self.show_scrollbars.vertical = show;
18552 cx.notify();
18553 }
18554
18555 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18556 self.show_scrollbars.horizontal = show;
18557 cx.notify();
18558 }
18559
18560 pub fn set_minimap_visibility(
18561 &mut self,
18562 minimap_visibility: MinimapVisibility,
18563 window: &mut Window,
18564 cx: &mut Context<Self>,
18565 ) {
18566 if self.minimap_visibility != minimap_visibility {
18567 if minimap_visibility.visible() && self.minimap.is_none() {
18568 let minimap_settings = EditorSettings::get_global(cx).minimap;
18569 self.minimap =
18570 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18571 }
18572 self.minimap_visibility = minimap_visibility;
18573 cx.notify();
18574 }
18575 }
18576
18577 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18578 self.set_show_scrollbars(false, cx);
18579 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18580 }
18581
18582 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18583 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18584 }
18585
18586 /// Normally the text in full mode and auto height editors is padded on the
18587 /// left side by roughly half a character width for improved hit testing.
18588 ///
18589 /// Use this method to disable this for cases where this is not wanted (e.g.
18590 /// if you want to align the editor text with some other text above or below)
18591 /// or if you want to add this padding to single-line editors.
18592 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18593 self.offset_content = offset_content;
18594 cx.notify();
18595 }
18596
18597 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18598 self.show_line_numbers = Some(show_line_numbers);
18599 cx.notify();
18600 }
18601
18602 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18603 self.disable_expand_excerpt_buttons = true;
18604 cx.notify();
18605 }
18606
18607 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18608 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18609 cx.notify();
18610 }
18611
18612 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18613 self.show_code_actions = Some(show_code_actions);
18614 cx.notify();
18615 }
18616
18617 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18618 self.show_runnables = Some(show_runnables);
18619 cx.notify();
18620 }
18621
18622 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18623 self.show_breakpoints = Some(show_breakpoints);
18624 cx.notify();
18625 }
18626
18627 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18628 if self.display_map.read(cx).masked != masked {
18629 self.display_map.update(cx, |map, _| map.masked = masked);
18630 }
18631 cx.notify()
18632 }
18633
18634 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18635 self.show_wrap_guides = Some(show_wrap_guides);
18636 cx.notify();
18637 }
18638
18639 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18640 self.show_indent_guides = Some(show_indent_guides);
18641 cx.notify();
18642 }
18643
18644 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18645 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18646 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18647 && let Some(dir) = file.abs_path(cx).parent()
18648 {
18649 return Some(dir.to_owned());
18650 }
18651
18652 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18653 return Some(project_path.path.to_path_buf());
18654 }
18655 }
18656
18657 None
18658 }
18659
18660 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18661 self.active_excerpt(cx)?
18662 .1
18663 .read(cx)
18664 .file()
18665 .and_then(|f| f.as_local())
18666 }
18667
18668 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18669 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18670 let buffer = buffer.read(cx);
18671 if let Some(project_path) = buffer.project_path(cx) {
18672 let project = self.project()?.read(cx);
18673 project.absolute_path(&project_path, cx)
18674 } else {
18675 buffer
18676 .file()
18677 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18678 }
18679 })
18680 }
18681
18682 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18683 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18684 let project_path = buffer.read(cx).project_path(cx)?;
18685 let project = self.project()?.read(cx);
18686 let entry = project.entry_for_path(&project_path, cx)?;
18687 let path = entry.path.to_path_buf();
18688 Some(path)
18689 })
18690 }
18691
18692 pub fn reveal_in_finder(
18693 &mut self,
18694 _: &RevealInFileManager,
18695 _window: &mut Window,
18696 cx: &mut Context<Self>,
18697 ) {
18698 if let Some(target) = self.target_file(cx) {
18699 cx.reveal_path(&target.abs_path(cx));
18700 }
18701 }
18702
18703 pub fn copy_path(
18704 &mut self,
18705 _: &zed_actions::workspace::CopyPath,
18706 _window: &mut Window,
18707 cx: &mut Context<Self>,
18708 ) {
18709 if let Some(path) = self.target_file_abs_path(cx)
18710 && let Some(path) = path.to_str()
18711 {
18712 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18713 }
18714 }
18715
18716 pub fn copy_relative_path(
18717 &mut self,
18718 _: &zed_actions::workspace::CopyRelativePath,
18719 _window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) {
18722 if let Some(path) = self.target_file_path(cx)
18723 && let Some(path) = path.to_str()
18724 {
18725 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18726 }
18727 }
18728
18729 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18730 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18731 buffer.read(cx).project_path(cx)
18732 } else {
18733 None
18734 }
18735 }
18736
18737 // Returns true if the editor handled a go-to-line request
18738 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18739 maybe!({
18740 let breakpoint_store = self.breakpoint_store.as_ref()?;
18741
18742 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18743 else {
18744 self.clear_row_highlights::<ActiveDebugLine>();
18745 return None;
18746 };
18747
18748 let position = active_stack_frame.position;
18749 let buffer_id = position.buffer_id?;
18750 let snapshot = self
18751 .project
18752 .as_ref()?
18753 .read(cx)
18754 .buffer_for_id(buffer_id, cx)?
18755 .read(cx)
18756 .snapshot();
18757
18758 let mut handled = false;
18759 for (id, ExcerptRange { context, .. }) in
18760 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18761 {
18762 if context.start.cmp(&position, &snapshot).is_ge()
18763 || context.end.cmp(&position, &snapshot).is_lt()
18764 {
18765 continue;
18766 }
18767 let snapshot = self.buffer.read(cx).snapshot(cx);
18768 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18769
18770 handled = true;
18771 self.clear_row_highlights::<ActiveDebugLine>();
18772
18773 self.go_to_line::<ActiveDebugLine>(
18774 multibuffer_anchor,
18775 Some(cx.theme().colors().editor_debugger_active_line_background),
18776 window,
18777 cx,
18778 );
18779
18780 cx.notify();
18781 }
18782
18783 handled.then_some(())
18784 })
18785 .is_some()
18786 }
18787
18788 pub fn copy_file_name_without_extension(
18789 &mut self,
18790 _: &CopyFileNameWithoutExtension,
18791 _: &mut Window,
18792 cx: &mut Context<Self>,
18793 ) {
18794 if let Some(file) = self.target_file(cx)
18795 && let Some(file_stem) = file.path().file_stem()
18796 && let Some(name) = file_stem.to_str()
18797 {
18798 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18799 }
18800 }
18801
18802 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18803 if let Some(file) = self.target_file(cx)
18804 && let Some(file_name) = file.path().file_name()
18805 && let Some(name) = file_name.to_str()
18806 {
18807 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18808 }
18809 }
18810
18811 pub fn toggle_git_blame(
18812 &mut self,
18813 _: &::git::Blame,
18814 window: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) {
18817 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18818
18819 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18820 self.start_git_blame(true, window, cx);
18821 }
18822
18823 cx.notify();
18824 }
18825
18826 pub fn toggle_git_blame_inline(
18827 &mut self,
18828 _: &ToggleGitBlameInline,
18829 window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) {
18832 self.toggle_git_blame_inline_internal(true, window, cx);
18833 cx.notify();
18834 }
18835
18836 pub fn open_git_blame_commit(
18837 &mut self,
18838 _: &OpenGitBlameCommit,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) {
18842 self.open_git_blame_commit_internal(window, cx);
18843 }
18844
18845 fn open_git_blame_commit_internal(
18846 &mut self,
18847 window: &mut Window,
18848 cx: &mut Context<Self>,
18849 ) -> Option<()> {
18850 let blame = self.blame.as_ref()?;
18851 let snapshot = self.snapshot(window, cx);
18852 let cursor = self.selections.newest::<Point>(cx).head();
18853 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18854 let blame_entry = blame
18855 .update(cx, |blame, cx| {
18856 blame
18857 .blame_for_rows(
18858 &[RowInfo {
18859 buffer_id: Some(buffer.remote_id()),
18860 buffer_row: Some(point.row),
18861 ..Default::default()
18862 }],
18863 cx,
18864 )
18865 .next()
18866 })
18867 .flatten()?;
18868 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18869 let repo = blame.read(cx).repository(cx)?;
18870 let workspace = self.workspace()?.downgrade();
18871 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18872 None
18873 }
18874
18875 pub fn git_blame_inline_enabled(&self) -> bool {
18876 self.git_blame_inline_enabled
18877 }
18878
18879 pub fn toggle_selection_menu(
18880 &mut self,
18881 _: &ToggleSelectionMenu,
18882 _: &mut Window,
18883 cx: &mut Context<Self>,
18884 ) {
18885 self.show_selection_menu = self
18886 .show_selection_menu
18887 .map(|show_selections_menu| !show_selections_menu)
18888 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18889
18890 cx.notify();
18891 }
18892
18893 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18894 self.show_selection_menu
18895 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18896 }
18897
18898 fn start_git_blame(
18899 &mut self,
18900 user_triggered: bool,
18901 window: &mut Window,
18902 cx: &mut Context<Self>,
18903 ) {
18904 if let Some(project) = self.project() {
18905 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18906 return;
18907 };
18908
18909 if buffer.read(cx).file().is_none() {
18910 return;
18911 }
18912
18913 let focused = self.focus_handle(cx).contains_focused(window, cx);
18914
18915 let project = project.clone();
18916 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18917 self.blame_subscription =
18918 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18919 self.blame = Some(blame);
18920 }
18921 }
18922
18923 fn toggle_git_blame_inline_internal(
18924 &mut self,
18925 user_triggered: bool,
18926 window: &mut Window,
18927 cx: &mut Context<Self>,
18928 ) {
18929 if self.git_blame_inline_enabled {
18930 self.git_blame_inline_enabled = false;
18931 self.show_git_blame_inline = false;
18932 self.show_git_blame_inline_delay_task.take();
18933 } else {
18934 self.git_blame_inline_enabled = true;
18935 self.start_git_blame_inline(user_triggered, window, cx);
18936 }
18937
18938 cx.notify();
18939 }
18940
18941 fn start_git_blame_inline(
18942 &mut self,
18943 user_triggered: bool,
18944 window: &mut Window,
18945 cx: &mut Context<Self>,
18946 ) {
18947 self.start_git_blame(user_triggered, window, cx);
18948
18949 if ProjectSettings::get_global(cx)
18950 .git
18951 .inline_blame_delay()
18952 .is_some()
18953 {
18954 self.start_inline_blame_timer(window, cx);
18955 } else {
18956 self.show_git_blame_inline = true
18957 }
18958 }
18959
18960 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18961 self.blame.as_ref()
18962 }
18963
18964 pub fn show_git_blame_gutter(&self) -> bool {
18965 self.show_git_blame_gutter
18966 }
18967
18968 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18969 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18970 }
18971
18972 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18973 self.show_git_blame_inline
18974 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18975 && !self.newest_selection_head_on_empty_line(cx)
18976 && self.has_blame_entries(cx)
18977 }
18978
18979 fn has_blame_entries(&self, cx: &App) -> bool {
18980 self.blame()
18981 .is_some_and(|blame| blame.read(cx).has_generated_entries())
18982 }
18983
18984 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18985 let cursor_anchor = self.selections.newest_anchor().head();
18986
18987 let snapshot = self.buffer.read(cx).snapshot(cx);
18988 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18989
18990 snapshot.line_len(buffer_row) == 0
18991 }
18992
18993 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18994 let buffer_and_selection = maybe!({
18995 let selection = self.selections.newest::<Point>(cx);
18996 let selection_range = selection.range();
18997
18998 let multi_buffer = self.buffer().read(cx);
18999 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19000 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19001
19002 let (buffer, range, _) = if selection.reversed {
19003 buffer_ranges.first()
19004 } else {
19005 buffer_ranges.last()
19006 }?;
19007
19008 let selection = text::ToPoint::to_point(&range.start, buffer).row
19009 ..text::ToPoint::to_point(&range.end, buffer).row;
19010 Some((
19011 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
19012 selection,
19013 ))
19014 });
19015
19016 let Some((buffer, selection)) = buffer_and_selection else {
19017 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19018 };
19019
19020 let Some(project) = self.project() else {
19021 return Task::ready(Err(anyhow!("editor does not have project")));
19022 };
19023
19024 project.update(cx, |project, cx| {
19025 project.get_permalink_to_line(&buffer, selection, cx)
19026 })
19027 }
19028
19029 pub fn copy_permalink_to_line(
19030 &mut self,
19031 _: &CopyPermalinkToLine,
19032 window: &mut Window,
19033 cx: &mut Context<Self>,
19034 ) {
19035 let permalink_task = self.get_permalink_to_line(cx);
19036 let workspace = self.workspace();
19037
19038 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19039 Ok(permalink) => {
19040 cx.update(|_, cx| {
19041 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19042 })
19043 .ok();
19044 }
19045 Err(err) => {
19046 let message = format!("Failed to copy permalink: {err}");
19047
19048 anyhow::Result::<()>::Err(err).log_err();
19049
19050 if let Some(workspace) = workspace {
19051 workspace
19052 .update_in(cx, |workspace, _, cx| {
19053 struct CopyPermalinkToLine;
19054
19055 workspace.show_toast(
19056 Toast::new(
19057 NotificationId::unique::<CopyPermalinkToLine>(),
19058 message,
19059 ),
19060 cx,
19061 )
19062 })
19063 .ok();
19064 }
19065 }
19066 })
19067 .detach();
19068 }
19069
19070 pub fn copy_file_location(
19071 &mut self,
19072 _: &CopyFileLocation,
19073 _: &mut Window,
19074 cx: &mut Context<Self>,
19075 ) {
19076 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19077 if let Some(file) = self.target_file(cx)
19078 && let Some(path) = file.path().to_str()
19079 {
19080 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19081 }
19082 }
19083
19084 pub fn open_permalink_to_line(
19085 &mut self,
19086 _: &OpenPermalinkToLine,
19087 window: &mut Window,
19088 cx: &mut Context<Self>,
19089 ) {
19090 let permalink_task = self.get_permalink_to_line(cx);
19091 let workspace = self.workspace();
19092
19093 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19094 Ok(permalink) => {
19095 cx.update(|_, cx| {
19096 cx.open_url(permalink.as_ref());
19097 })
19098 .ok();
19099 }
19100 Err(err) => {
19101 let message = format!("Failed to open permalink: {err}");
19102
19103 anyhow::Result::<()>::Err(err).log_err();
19104
19105 if let Some(workspace) = workspace {
19106 workspace
19107 .update(cx, |workspace, cx| {
19108 struct OpenPermalinkToLine;
19109
19110 workspace.show_toast(
19111 Toast::new(
19112 NotificationId::unique::<OpenPermalinkToLine>(),
19113 message,
19114 ),
19115 cx,
19116 )
19117 })
19118 .ok();
19119 }
19120 }
19121 })
19122 .detach();
19123 }
19124
19125 pub fn insert_uuid_v4(
19126 &mut self,
19127 _: &InsertUuidV4,
19128 window: &mut Window,
19129 cx: &mut Context<Self>,
19130 ) {
19131 self.insert_uuid(UuidVersion::V4, window, cx);
19132 }
19133
19134 pub fn insert_uuid_v7(
19135 &mut self,
19136 _: &InsertUuidV7,
19137 window: &mut Window,
19138 cx: &mut Context<Self>,
19139 ) {
19140 self.insert_uuid(UuidVersion::V7, window, cx);
19141 }
19142
19143 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19145 self.transact(window, cx, |this, window, cx| {
19146 let edits = this
19147 .selections
19148 .all::<Point>(cx)
19149 .into_iter()
19150 .map(|selection| {
19151 let uuid = match version {
19152 UuidVersion::V4 => uuid::Uuid::new_v4(),
19153 UuidVersion::V7 => uuid::Uuid::now_v7(),
19154 };
19155
19156 (selection.range(), uuid.to_string())
19157 });
19158 this.edit(edits, cx);
19159 this.refresh_edit_prediction(true, false, window, cx);
19160 });
19161 }
19162
19163 pub fn open_selections_in_multibuffer(
19164 &mut self,
19165 _: &OpenSelectionsInMultibuffer,
19166 window: &mut Window,
19167 cx: &mut Context<Self>,
19168 ) {
19169 let multibuffer = self.buffer.read(cx);
19170
19171 let Some(buffer) = multibuffer.as_singleton() else {
19172 return;
19173 };
19174
19175 let Some(workspace) = self.workspace() else {
19176 return;
19177 };
19178
19179 let title = multibuffer.title(cx).to_string();
19180
19181 let locations = self
19182 .selections
19183 .all_anchors(cx)
19184 .iter()
19185 .map(|selection| Location {
19186 buffer: buffer.clone(),
19187 range: selection.start.text_anchor..selection.end.text_anchor,
19188 })
19189 .collect::<Vec<_>>();
19190
19191 cx.spawn_in(window, async move |_, cx| {
19192 workspace.update_in(cx, |workspace, window, cx| {
19193 Self::open_locations_in_multibuffer(
19194 workspace,
19195 locations,
19196 format!("Selections for '{title}'"),
19197 false,
19198 MultibufferSelectionMode::All,
19199 window,
19200 cx,
19201 );
19202 })
19203 })
19204 .detach();
19205 }
19206
19207 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19208 /// last highlight added will be used.
19209 ///
19210 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19211 pub fn highlight_rows<T: 'static>(
19212 &mut self,
19213 range: Range<Anchor>,
19214 color: Hsla,
19215 options: RowHighlightOptions,
19216 cx: &mut Context<Self>,
19217 ) {
19218 let snapshot = self.buffer().read(cx).snapshot(cx);
19219 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19220 let ix = row_highlights.binary_search_by(|highlight| {
19221 Ordering::Equal
19222 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19223 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19224 });
19225
19226 if let Err(mut ix) = ix {
19227 let index = post_inc(&mut self.highlight_order);
19228
19229 // If this range intersects with the preceding highlight, then merge it with
19230 // the preceding highlight. Otherwise insert a new highlight.
19231 let mut merged = false;
19232 if ix > 0 {
19233 let prev_highlight = &mut row_highlights[ix - 1];
19234 if prev_highlight
19235 .range
19236 .end
19237 .cmp(&range.start, &snapshot)
19238 .is_ge()
19239 {
19240 ix -= 1;
19241 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19242 prev_highlight.range.end = range.end;
19243 }
19244 merged = true;
19245 prev_highlight.index = index;
19246 prev_highlight.color = color;
19247 prev_highlight.options = options;
19248 }
19249 }
19250
19251 if !merged {
19252 row_highlights.insert(
19253 ix,
19254 RowHighlight {
19255 range: range.clone(),
19256 index,
19257 color,
19258 options,
19259 type_id: TypeId::of::<T>(),
19260 },
19261 );
19262 }
19263
19264 // If any of the following highlights intersect with this one, merge them.
19265 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19266 let highlight = &row_highlights[ix];
19267 if next_highlight
19268 .range
19269 .start
19270 .cmp(&highlight.range.end, &snapshot)
19271 .is_le()
19272 {
19273 if next_highlight
19274 .range
19275 .end
19276 .cmp(&highlight.range.end, &snapshot)
19277 .is_gt()
19278 {
19279 row_highlights[ix].range.end = next_highlight.range.end;
19280 }
19281 row_highlights.remove(ix + 1);
19282 } else {
19283 break;
19284 }
19285 }
19286 }
19287 }
19288
19289 /// Remove any highlighted row ranges of the given type that intersect the
19290 /// given ranges.
19291 pub fn remove_highlighted_rows<T: 'static>(
19292 &mut self,
19293 ranges_to_remove: Vec<Range<Anchor>>,
19294 cx: &mut Context<Self>,
19295 ) {
19296 let snapshot = self.buffer().read(cx).snapshot(cx);
19297 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19298 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19299 row_highlights.retain(|highlight| {
19300 while let Some(range_to_remove) = ranges_to_remove.peek() {
19301 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19302 Ordering::Less | Ordering::Equal => {
19303 ranges_to_remove.next();
19304 }
19305 Ordering::Greater => {
19306 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19307 Ordering::Less | Ordering::Equal => {
19308 return false;
19309 }
19310 Ordering::Greater => break,
19311 }
19312 }
19313 }
19314 }
19315
19316 true
19317 })
19318 }
19319
19320 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19321 pub fn clear_row_highlights<T: 'static>(&mut self) {
19322 self.highlighted_rows.remove(&TypeId::of::<T>());
19323 }
19324
19325 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19326 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19327 self.highlighted_rows
19328 .get(&TypeId::of::<T>())
19329 .map_or(&[] as &[_], |vec| vec.as_slice())
19330 .iter()
19331 .map(|highlight| (highlight.range.clone(), highlight.color))
19332 }
19333
19334 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19335 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19336 /// Allows to ignore certain kinds of highlights.
19337 pub fn highlighted_display_rows(
19338 &self,
19339 window: &mut Window,
19340 cx: &mut App,
19341 ) -> BTreeMap<DisplayRow, LineHighlight> {
19342 let snapshot = self.snapshot(window, cx);
19343 let mut used_highlight_orders = HashMap::default();
19344 self.highlighted_rows
19345 .iter()
19346 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19347 .fold(
19348 BTreeMap::<DisplayRow, LineHighlight>::new(),
19349 |mut unique_rows, highlight| {
19350 let start = highlight.range.start.to_display_point(&snapshot);
19351 let end = highlight.range.end.to_display_point(&snapshot);
19352 let start_row = start.row().0;
19353 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19354 && end.column() == 0
19355 {
19356 end.row().0.saturating_sub(1)
19357 } else {
19358 end.row().0
19359 };
19360 for row in start_row..=end_row {
19361 let used_index =
19362 used_highlight_orders.entry(row).or_insert(highlight.index);
19363 if highlight.index >= *used_index {
19364 *used_index = highlight.index;
19365 unique_rows.insert(
19366 DisplayRow(row),
19367 LineHighlight {
19368 include_gutter: highlight.options.include_gutter,
19369 border: None,
19370 background: highlight.color.into(),
19371 type_id: Some(highlight.type_id),
19372 },
19373 );
19374 }
19375 }
19376 unique_rows
19377 },
19378 )
19379 }
19380
19381 pub fn highlighted_display_row_for_autoscroll(
19382 &self,
19383 snapshot: &DisplaySnapshot,
19384 ) -> Option<DisplayRow> {
19385 self.highlighted_rows
19386 .values()
19387 .flat_map(|highlighted_rows| highlighted_rows.iter())
19388 .filter_map(|highlight| {
19389 if highlight.options.autoscroll {
19390 Some(highlight.range.start.to_display_point(snapshot).row())
19391 } else {
19392 None
19393 }
19394 })
19395 .min()
19396 }
19397
19398 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19399 self.highlight_background::<SearchWithinRange>(
19400 ranges,
19401 |colors| colors.colors().editor_document_highlight_read_background,
19402 cx,
19403 )
19404 }
19405
19406 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19407 self.breadcrumb_header = Some(new_header);
19408 }
19409
19410 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19411 self.clear_background_highlights::<SearchWithinRange>(cx);
19412 }
19413
19414 pub fn highlight_background<T: 'static>(
19415 &mut self,
19416 ranges: &[Range<Anchor>],
19417 color_fetcher: fn(&Theme) -> Hsla,
19418 cx: &mut Context<Self>,
19419 ) {
19420 self.background_highlights.insert(
19421 HighlightKey::Type(TypeId::of::<T>()),
19422 (color_fetcher, Arc::from(ranges)),
19423 );
19424 self.scrollbar_marker_state.dirty = true;
19425 cx.notify();
19426 }
19427
19428 pub fn highlight_background_key<T: 'static>(
19429 &mut self,
19430 key: usize,
19431 ranges: &[Range<Anchor>],
19432 color_fetcher: fn(&Theme) -> Hsla,
19433 cx: &mut Context<Self>,
19434 ) {
19435 self.background_highlights.insert(
19436 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19437 (color_fetcher, Arc::from(ranges)),
19438 );
19439 self.scrollbar_marker_state.dirty = true;
19440 cx.notify();
19441 }
19442
19443 pub fn clear_background_highlights<T: 'static>(
19444 &mut self,
19445 cx: &mut Context<Self>,
19446 ) -> Option<BackgroundHighlight> {
19447 let text_highlights = self
19448 .background_highlights
19449 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19450 if !text_highlights.1.is_empty() {
19451 self.scrollbar_marker_state.dirty = true;
19452 cx.notify();
19453 }
19454 Some(text_highlights)
19455 }
19456
19457 pub fn highlight_gutter<T: 'static>(
19458 &mut self,
19459 ranges: impl Into<Vec<Range<Anchor>>>,
19460 color_fetcher: fn(&App) -> Hsla,
19461 cx: &mut Context<Self>,
19462 ) {
19463 self.gutter_highlights
19464 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19465 cx.notify();
19466 }
19467
19468 pub fn clear_gutter_highlights<T: 'static>(
19469 &mut self,
19470 cx: &mut Context<Self>,
19471 ) -> Option<GutterHighlight> {
19472 cx.notify();
19473 self.gutter_highlights.remove(&TypeId::of::<T>())
19474 }
19475
19476 pub fn insert_gutter_highlight<T: 'static>(
19477 &mut self,
19478 range: Range<Anchor>,
19479 color_fetcher: fn(&App) -> Hsla,
19480 cx: &mut Context<Self>,
19481 ) {
19482 let snapshot = self.buffer().read(cx).snapshot(cx);
19483 let mut highlights = self
19484 .gutter_highlights
19485 .remove(&TypeId::of::<T>())
19486 .map(|(_, highlights)| highlights)
19487 .unwrap_or_default();
19488 let ix = highlights.binary_search_by(|highlight| {
19489 Ordering::Equal
19490 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19491 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19492 });
19493 if let Err(ix) = ix {
19494 highlights.insert(ix, range);
19495 }
19496 self.gutter_highlights
19497 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19498 }
19499
19500 pub fn remove_gutter_highlights<T: 'static>(
19501 &mut self,
19502 ranges_to_remove: Vec<Range<Anchor>>,
19503 cx: &mut Context<Self>,
19504 ) {
19505 let snapshot = self.buffer().read(cx).snapshot(cx);
19506 let Some((color_fetcher, mut gutter_highlights)) =
19507 self.gutter_highlights.remove(&TypeId::of::<T>())
19508 else {
19509 return;
19510 };
19511 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19512 gutter_highlights.retain(|highlight| {
19513 while let Some(range_to_remove) = ranges_to_remove.peek() {
19514 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19515 Ordering::Less | Ordering::Equal => {
19516 ranges_to_remove.next();
19517 }
19518 Ordering::Greater => {
19519 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19520 Ordering::Less | Ordering::Equal => {
19521 return false;
19522 }
19523 Ordering::Greater => break,
19524 }
19525 }
19526 }
19527 }
19528
19529 true
19530 });
19531 self.gutter_highlights
19532 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19533 }
19534
19535 #[cfg(feature = "test-support")]
19536 pub fn all_text_highlights(
19537 &self,
19538 window: &mut Window,
19539 cx: &mut Context<Self>,
19540 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19541 let snapshot = self.snapshot(window, cx);
19542 self.display_map.update(cx, |display_map, _| {
19543 display_map
19544 .all_text_highlights()
19545 .map(|highlight| {
19546 let (style, ranges) = highlight.as_ref();
19547 (
19548 *style,
19549 ranges
19550 .iter()
19551 .map(|range| range.clone().to_display_points(&snapshot))
19552 .collect(),
19553 )
19554 })
19555 .collect()
19556 })
19557 }
19558
19559 #[cfg(feature = "test-support")]
19560 pub fn all_text_background_highlights(
19561 &self,
19562 window: &mut Window,
19563 cx: &mut Context<Self>,
19564 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19565 let snapshot = self.snapshot(window, cx);
19566 let buffer = &snapshot.buffer_snapshot;
19567 let start = buffer.anchor_before(0);
19568 let end = buffer.anchor_after(buffer.len());
19569 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19570 }
19571
19572 #[cfg(feature = "test-support")]
19573 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19574 let snapshot = self.buffer().read(cx).snapshot(cx);
19575
19576 let highlights = self
19577 .background_highlights
19578 .get(&HighlightKey::Type(TypeId::of::<
19579 items::BufferSearchHighlights,
19580 >()));
19581
19582 if let Some((_color, ranges)) = highlights {
19583 ranges
19584 .iter()
19585 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19586 .collect_vec()
19587 } else {
19588 vec![]
19589 }
19590 }
19591
19592 fn document_highlights_for_position<'a>(
19593 &'a self,
19594 position: Anchor,
19595 buffer: &'a MultiBufferSnapshot,
19596 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19597 let read_highlights = self
19598 .background_highlights
19599 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19600 .map(|h| &h.1);
19601 let write_highlights = self
19602 .background_highlights
19603 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19604 .map(|h| &h.1);
19605 let left_position = position.bias_left(buffer);
19606 let right_position = position.bias_right(buffer);
19607 read_highlights
19608 .into_iter()
19609 .chain(write_highlights)
19610 .flat_map(move |ranges| {
19611 let start_ix = match ranges.binary_search_by(|probe| {
19612 let cmp = probe.end.cmp(&left_position, buffer);
19613 if cmp.is_ge() {
19614 Ordering::Greater
19615 } else {
19616 Ordering::Less
19617 }
19618 }) {
19619 Ok(i) | Err(i) => i,
19620 };
19621
19622 ranges[start_ix..]
19623 .iter()
19624 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19625 })
19626 }
19627
19628 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19629 self.background_highlights
19630 .get(&HighlightKey::Type(TypeId::of::<T>()))
19631 .is_some_and(|(_, highlights)| !highlights.is_empty())
19632 }
19633
19634 pub fn background_highlights_in_range(
19635 &self,
19636 search_range: Range<Anchor>,
19637 display_snapshot: &DisplaySnapshot,
19638 theme: &Theme,
19639 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19640 let mut results = Vec::new();
19641 for (color_fetcher, ranges) in self.background_highlights.values() {
19642 let color = color_fetcher(theme);
19643 let start_ix = match ranges.binary_search_by(|probe| {
19644 let cmp = probe
19645 .end
19646 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19647 if cmp.is_gt() {
19648 Ordering::Greater
19649 } else {
19650 Ordering::Less
19651 }
19652 }) {
19653 Ok(i) | Err(i) => i,
19654 };
19655 for range in &ranges[start_ix..] {
19656 if range
19657 .start
19658 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19659 .is_ge()
19660 {
19661 break;
19662 }
19663
19664 let start = range.start.to_display_point(display_snapshot);
19665 let end = range.end.to_display_point(display_snapshot);
19666 results.push((start..end, color))
19667 }
19668 }
19669 results
19670 }
19671
19672 pub fn background_highlight_row_ranges<T: 'static>(
19673 &self,
19674 search_range: Range<Anchor>,
19675 display_snapshot: &DisplaySnapshot,
19676 count: usize,
19677 ) -> Vec<RangeInclusive<DisplayPoint>> {
19678 let mut results = Vec::new();
19679 let Some((_, ranges)) = self
19680 .background_highlights
19681 .get(&HighlightKey::Type(TypeId::of::<T>()))
19682 else {
19683 return vec![];
19684 };
19685
19686 let start_ix = match ranges.binary_search_by(|probe| {
19687 let cmp = probe
19688 .end
19689 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19690 if cmp.is_gt() {
19691 Ordering::Greater
19692 } else {
19693 Ordering::Less
19694 }
19695 }) {
19696 Ok(i) | Err(i) => i,
19697 };
19698 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19699 if let (Some(start_display), Some(end_display)) = (start, end) {
19700 results.push(
19701 start_display.to_display_point(display_snapshot)
19702 ..=end_display.to_display_point(display_snapshot),
19703 );
19704 }
19705 };
19706 let mut start_row: Option<Point> = None;
19707 let mut end_row: Option<Point> = None;
19708 if ranges.len() > count {
19709 return Vec::new();
19710 }
19711 for range in &ranges[start_ix..] {
19712 if range
19713 .start
19714 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19715 .is_ge()
19716 {
19717 break;
19718 }
19719 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19720 if let Some(current_row) = &end_row
19721 && end.row == current_row.row
19722 {
19723 continue;
19724 }
19725 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19726 if start_row.is_none() {
19727 assert_eq!(end_row, None);
19728 start_row = Some(start);
19729 end_row = Some(end);
19730 continue;
19731 }
19732 if let Some(current_end) = end_row.as_mut() {
19733 if start.row > current_end.row + 1 {
19734 push_region(start_row, end_row);
19735 start_row = Some(start);
19736 end_row = Some(end);
19737 } else {
19738 // Merge two hunks.
19739 *current_end = end;
19740 }
19741 } else {
19742 unreachable!();
19743 }
19744 }
19745 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19746 push_region(start_row, end_row);
19747 results
19748 }
19749
19750 pub fn gutter_highlights_in_range(
19751 &self,
19752 search_range: Range<Anchor>,
19753 display_snapshot: &DisplaySnapshot,
19754 cx: &App,
19755 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19756 let mut results = Vec::new();
19757 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19758 let color = color_fetcher(cx);
19759 let start_ix = match ranges.binary_search_by(|probe| {
19760 let cmp = probe
19761 .end
19762 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19763 if cmp.is_gt() {
19764 Ordering::Greater
19765 } else {
19766 Ordering::Less
19767 }
19768 }) {
19769 Ok(i) | Err(i) => i,
19770 };
19771 for range in &ranges[start_ix..] {
19772 if range
19773 .start
19774 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19775 .is_ge()
19776 {
19777 break;
19778 }
19779
19780 let start = range.start.to_display_point(display_snapshot);
19781 let end = range.end.to_display_point(display_snapshot);
19782 results.push((start..end, color))
19783 }
19784 }
19785 results
19786 }
19787
19788 /// Get the text ranges corresponding to the redaction query
19789 pub fn redacted_ranges(
19790 &self,
19791 search_range: Range<Anchor>,
19792 display_snapshot: &DisplaySnapshot,
19793 cx: &App,
19794 ) -> Vec<Range<DisplayPoint>> {
19795 display_snapshot
19796 .buffer_snapshot
19797 .redacted_ranges(search_range, |file| {
19798 if let Some(file) = file {
19799 file.is_private()
19800 && EditorSettings::get(
19801 Some(SettingsLocation {
19802 worktree_id: file.worktree_id(cx),
19803 path: file.path().as_ref(),
19804 }),
19805 cx,
19806 )
19807 .redact_private_values
19808 } else {
19809 false
19810 }
19811 })
19812 .map(|range| {
19813 range.start.to_display_point(display_snapshot)
19814 ..range.end.to_display_point(display_snapshot)
19815 })
19816 .collect()
19817 }
19818
19819 pub fn highlight_text_key<T: 'static>(
19820 &mut self,
19821 key: usize,
19822 ranges: Vec<Range<Anchor>>,
19823 style: HighlightStyle,
19824 cx: &mut Context<Self>,
19825 ) {
19826 self.display_map.update(cx, |map, _| {
19827 map.highlight_text(
19828 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19829 ranges,
19830 style,
19831 );
19832 });
19833 cx.notify();
19834 }
19835
19836 pub fn highlight_text<T: 'static>(
19837 &mut self,
19838 ranges: Vec<Range<Anchor>>,
19839 style: HighlightStyle,
19840 cx: &mut Context<Self>,
19841 ) {
19842 self.display_map.update(cx, |map, _| {
19843 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19844 });
19845 cx.notify();
19846 }
19847
19848 pub(crate) fn highlight_inlays<T: 'static>(
19849 &mut self,
19850 highlights: Vec<InlayHighlight>,
19851 style: HighlightStyle,
19852 cx: &mut Context<Self>,
19853 ) {
19854 self.display_map.update(cx, |map, _| {
19855 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19856 });
19857 cx.notify();
19858 }
19859
19860 pub fn text_highlights<'a, T: 'static>(
19861 &'a self,
19862 cx: &'a App,
19863 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19864 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19865 }
19866
19867 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19868 let cleared = self
19869 .display_map
19870 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19871 if cleared {
19872 cx.notify();
19873 }
19874 }
19875
19876 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19877 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19878 && self.focus_handle.is_focused(window)
19879 }
19880
19881 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19882 self.show_cursor_when_unfocused = is_enabled;
19883 cx.notify();
19884 }
19885
19886 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19887 cx.notify();
19888 }
19889
19890 fn on_debug_session_event(
19891 &mut self,
19892 _session: Entity<Session>,
19893 event: &SessionEvent,
19894 cx: &mut Context<Self>,
19895 ) {
19896 if let SessionEvent::InvalidateInlineValue = event {
19897 self.refresh_inline_values(cx);
19898 }
19899 }
19900
19901 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19902 let Some(project) = self.project.clone() else {
19903 return;
19904 };
19905
19906 if !self.inline_value_cache.enabled {
19907 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19908 self.splice_inlays(&inlays, Vec::new(), cx);
19909 return;
19910 }
19911
19912 let current_execution_position = self
19913 .highlighted_rows
19914 .get(&TypeId::of::<ActiveDebugLine>())
19915 .and_then(|lines| lines.last().map(|line| line.range.end));
19916
19917 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19918 let inline_values = editor
19919 .update(cx, |editor, cx| {
19920 let Some(current_execution_position) = current_execution_position else {
19921 return Some(Task::ready(Ok(Vec::new())));
19922 };
19923
19924 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19925 let snapshot = buffer.snapshot(cx);
19926
19927 let excerpt = snapshot.excerpt_containing(
19928 current_execution_position..current_execution_position,
19929 )?;
19930
19931 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19932 })?;
19933
19934 let range =
19935 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19936
19937 project.inline_values(buffer, range, cx)
19938 })
19939 .ok()
19940 .flatten()?
19941 .await
19942 .context("refreshing debugger inlays")
19943 .log_err()?;
19944
19945 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19946
19947 for (buffer_id, inline_value) in inline_values
19948 .into_iter()
19949 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19950 {
19951 buffer_inline_values
19952 .entry(buffer_id)
19953 .or_default()
19954 .push(inline_value);
19955 }
19956
19957 editor
19958 .update(cx, |editor, cx| {
19959 let snapshot = editor.buffer.read(cx).snapshot(cx);
19960 let mut new_inlays = Vec::default();
19961
19962 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19963 let buffer_id = buffer_snapshot.remote_id();
19964 buffer_inline_values
19965 .get(&buffer_id)
19966 .into_iter()
19967 .flatten()
19968 .for_each(|hint| {
19969 let inlay = Inlay::debugger(
19970 post_inc(&mut editor.next_inlay_id),
19971 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19972 hint.text(),
19973 );
19974 if !inlay.text.chars().contains(&'\n') {
19975 new_inlays.push(inlay);
19976 }
19977 });
19978 }
19979
19980 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19981 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19982
19983 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19984 })
19985 .ok()?;
19986 Some(())
19987 });
19988 }
19989
19990 fn on_buffer_event(
19991 &mut self,
19992 multibuffer: &Entity<MultiBuffer>,
19993 event: &multi_buffer::Event,
19994 window: &mut Window,
19995 cx: &mut Context<Self>,
19996 ) {
19997 match event {
19998 multi_buffer::Event::Edited {
19999 singleton_buffer_edited,
20000 edited_buffer,
20001 } => {
20002 self.scrollbar_marker_state.dirty = true;
20003 self.active_indent_guides_state.dirty = true;
20004 self.refresh_active_diagnostics(cx);
20005 self.refresh_code_actions(window, cx);
20006 self.refresh_selected_text_highlights(true, window, cx);
20007 self.refresh_single_line_folds(window, cx);
20008 refresh_matching_bracket_highlights(self, window, cx);
20009 if self.has_active_edit_prediction() {
20010 self.update_visible_edit_prediction(window, cx);
20011 }
20012 if let Some(project) = self.project.as_ref()
20013 && let Some(edited_buffer) = edited_buffer
20014 {
20015 project.update(cx, |project, cx| {
20016 self.registered_buffers
20017 .entry(edited_buffer.read(cx).remote_id())
20018 .or_insert_with(|| {
20019 project.register_buffer_with_language_servers(edited_buffer, cx)
20020 });
20021 });
20022 }
20023 cx.emit(EditorEvent::BufferEdited);
20024 cx.emit(SearchEvent::MatchesInvalidated);
20025
20026 if let Some(buffer) = edited_buffer {
20027 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20028 }
20029
20030 if *singleton_buffer_edited {
20031 if let Some(buffer) = edited_buffer
20032 && buffer.read(cx).file().is_none()
20033 {
20034 cx.emit(EditorEvent::TitleChanged);
20035 }
20036 if let Some(project) = &self.project {
20037 #[allow(clippy::mutable_key_type)]
20038 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20039 multibuffer
20040 .all_buffers()
20041 .into_iter()
20042 .filter_map(|buffer| {
20043 buffer.update(cx, |buffer, cx| {
20044 let language = buffer.language()?;
20045 let should_discard = project.update(cx, |project, cx| {
20046 project.is_local()
20047 && !project.has_language_servers_for(buffer, cx)
20048 });
20049 should_discard.not().then_some(language.clone())
20050 })
20051 })
20052 .collect::<HashSet<_>>()
20053 });
20054 if !languages_affected.is_empty() {
20055 self.refresh_inlay_hints(
20056 InlayHintRefreshReason::BufferEdited(languages_affected),
20057 cx,
20058 );
20059 }
20060 }
20061 }
20062
20063 let Some(project) = &self.project else { return };
20064 let (telemetry, is_via_ssh) = {
20065 let project = project.read(cx);
20066 let telemetry = project.client().telemetry().clone();
20067 let is_via_ssh = project.is_via_ssh();
20068 (telemetry, is_via_ssh)
20069 };
20070 refresh_linked_ranges(self, window, cx);
20071 telemetry.log_edit_event("editor", is_via_ssh);
20072 }
20073 multi_buffer::Event::ExcerptsAdded {
20074 buffer,
20075 predecessor,
20076 excerpts,
20077 } => {
20078 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20079 let buffer_id = buffer.read(cx).remote_id();
20080 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20081 && let Some(project) = &self.project
20082 {
20083 update_uncommitted_diff_for_buffer(
20084 cx.entity(),
20085 project,
20086 [buffer.clone()],
20087 self.buffer.clone(),
20088 cx,
20089 )
20090 .detach();
20091 }
20092 self.update_lsp_data(false, Some(buffer_id), window, cx);
20093 cx.emit(EditorEvent::ExcerptsAdded {
20094 buffer: buffer.clone(),
20095 predecessor: *predecessor,
20096 excerpts: excerpts.clone(),
20097 });
20098 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20099 }
20100 multi_buffer::Event::ExcerptsRemoved {
20101 ids,
20102 removed_buffer_ids,
20103 } => {
20104 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20105 let buffer = self.buffer.read(cx);
20106 self.registered_buffers
20107 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20108 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20109 cx.emit(EditorEvent::ExcerptsRemoved {
20110 ids: ids.clone(),
20111 removed_buffer_ids: removed_buffer_ids.clone(),
20112 });
20113 }
20114 multi_buffer::Event::ExcerptsEdited {
20115 excerpt_ids,
20116 buffer_ids,
20117 } => {
20118 self.display_map.update(cx, |map, cx| {
20119 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20120 });
20121 cx.emit(EditorEvent::ExcerptsEdited {
20122 ids: excerpt_ids.clone(),
20123 });
20124 }
20125 multi_buffer::Event::ExcerptsExpanded { ids } => {
20126 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20127 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20128 }
20129 multi_buffer::Event::Reparsed(buffer_id) => {
20130 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20131 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20132
20133 cx.emit(EditorEvent::Reparsed(*buffer_id));
20134 }
20135 multi_buffer::Event::DiffHunksToggled => {
20136 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20137 }
20138 multi_buffer::Event::LanguageChanged(buffer_id) => {
20139 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20140 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20141 cx.emit(EditorEvent::Reparsed(*buffer_id));
20142 cx.notify();
20143 }
20144 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20145 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20146 multi_buffer::Event::FileHandleChanged
20147 | multi_buffer::Event::Reloaded
20148 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20149 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20150 multi_buffer::Event::DiagnosticsUpdated => {
20151 self.update_diagnostics_state(window, cx);
20152 }
20153 _ => {}
20154 };
20155 }
20156
20157 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20158 if !self.diagnostics_enabled() {
20159 return;
20160 }
20161 self.refresh_active_diagnostics(cx);
20162 self.refresh_inline_diagnostics(true, window, cx);
20163 self.scrollbar_marker_state.dirty = true;
20164 cx.notify();
20165 }
20166
20167 pub fn start_temporary_diff_override(&mut self) {
20168 self.load_diff_task.take();
20169 self.temporary_diff_override = true;
20170 }
20171
20172 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20173 self.temporary_diff_override = false;
20174 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20175 self.buffer.update(cx, |buffer, cx| {
20176 buffer.set_all_diff_hunks_collapsed(cx);
20177 });
20178
20179 if let Some(project) = self.project.clone() {
20180 self.load_diff_task = Some(
20181 update_uncommitted_diff_for_buffer(
20182 cx.entity(),
20183 &project,
20184 self.buffer.read(cx).all_buffers(),
20185 self.buffer.clone(),
20186 cx,
20187 )
20188 .shared(),
20189 );
20190 }
20191 }
20192
20193 fn on_display_map_changed(
20194 &mut self,
20195 _: Entity<DisplayMap>,
20196 _: &mut Window,
20197 cx: &mut Context<Self>,
20198 ) {
20199 cx.notify();
20200 }
20201
20202 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20203 if self.diagnostics_enabled() {
20204 let new_severity = EditorSettings::get_global(cx)
20205 .diagnostics_max_severity
20206 .unwrap_or(DiagnosticSeverity::Hint);
20207 self.set_max_diagnostics_severity(new_severity, cx);
20208 }
20209 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20210 self.update_edit_prediction_settings(cx);
20211 self.refresh_edit_prediction(true, false, window, cx);
20212 self.refresh_inline_values(cx);
20213 self.refresh_inlay_hints(
20214 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20215 self.selections.newest_anchor().head(),
20216 &self.buffer.read(cx).snapshot(cx),
20217 cx,
20218 )),
20219 cx,
20220 );
20221
20222 let old_cursor_shape = self.cursor_shape;
20223 let old_show_breadcrumbs = self.show_breadcrumbs;
20224
20225 {
20226 let editor_settings = EditorSettings::get_global(cx);
20227 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20228 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20229 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20230 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20231 }
20232
20233 if old_cursor_shape != self.cursor_shape {
20234 cx.emit(EditorEvent::CursorShapeChanged);
20235 }
20236
20237 if old_show_breadcrumbs != self.show_breadcrumbs {
20238 cx.emit(EditorEvent::BreadcrumbsChanged);
20239 }
20240
20241 let project_settings = ProjectSettings::get_global(cx);
20242 self.serialize_dirty_buffers =
20243 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20244
20245 if self.mode.is_full() {
20246 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20247 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20248 if self.show_inline_diagnostics != show_inline_diagnostics {
20249 self.show_inline_diagnostics = show_inline_diagnostics;
20250 self.refresh_inline_diagnostics(false, window, cx);
20251 }
20252
20253 if self.git_blame_inline_enabled != inline_blame_enabled {
20254 self.toggle_git_blame_inline_internal(false, window, cx);
20255 }
20256
20257 let minimap_settings = EditorSettings::get_global(cx).minimap;
20258 if self.minimap_visibility != MinimapVisibility::Disabled {
20259 if self.minimap_visibility.settings_visibility()
20260 != minimap_settings.minimap_enabled()
20261 {
20262 self.set_minimap_visibility(
20263 MinimapVisibility::for_mode(self.mode(), cx),
20264 window,
20265 cx,
20266 );
20267 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20268 minimap_entity.update(cx, |minimap_editor, cx| {
20269 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20270 })
20271 }
20272 }
20273 }
20274
20275 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20276 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20277 }) {
20278 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20279 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20280 }
20281 self.refresh_colors(false, None, window, cx);
20282 }
20283
20284 cx.notify();
20285 }
20286
20287 pub fn set_searchable(&mut self, searchable: bool) {
20288 self.searchable = searchable;
20289 }
20290
20291 pub fn searchable(&self) -> bool {
20292 self.searchable
20293 }
20294
20295 fn open_proposed_changes_editor(
20296 &mut self,
20297 _: &OpenProposedChangesEditor,
20298 window: &mut Window,
20299 cx: &mut Context<Self>,
20300 ) {
20301 let Some(workspace) = self.workspace() else {
20302 cx.propagate();
20303 return;
20304 };
20305
20306 let selections = self.selections.all::<usize>(cx);
20307 let multi_buffer = self.buffer.read(cx);
20308 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20309 let mut new_selections_by_buffer = HashMap::default();
20310 for selection in selections {
20311 for (buffer, range, _) in
20312 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20313 {
20314 let mut range = range.to_point(buffer);
20315 range.start.column = 0;
20316 range.end.column = buffer.line_len(range.end.row);
20317 new_selections_by_buffer
20318 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20319 .or_insert(Vec::new())
20320 .push(range)
20321 }
20322 }
20323
20324 let proposed_changes_buffers = new_selections_by_buffer
20325 .into_iter()
20326 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20327 .collect::<Vec<_>>();
20328 let proposed_changes_editor = cx.new(|cx| {
20329 ProposedChangesEditor::new(
20330 "Proposed changes",
20331 proposed_changes_buffers,
20332 self.project.clone(),
20333 window,
20334 cx,
20335 )
20336 });
20337
20338 window.defer(cx, move |window, cx| {
20339 workspace.update(cx, |workspace, cx| {
20340 workspace.active_pane().update(cx, |pane, cx| {
20341 pane.add_item(
20342 Box::new(proposed_changes_editor),
20343 true,
20344 true,
20345 None,
20346 window,
20347 cx,
20348 );
20349 });
20350 });
20351 });
20352 }
20353
20354 pub fn open_excerpts_in_split(
20355 &mut self,
20356 _: &OpenExcerptsSplit,
20357 window: &mut Window,
20358 cx: &mut Context<Self>,
20359 ) {
20360 self.open_excerpts_common(None, true, window, cx)
20361 }
20362
20363 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20364 self.open_excerpts_common(None, false, window, cx)
20365 }
20366
20367 fn open_excerpts_common(
20368 &mut self,
20369 jump_data: Option<JumpData>,
20370 split: bool,
20371 window: &mut Window,
20372 cx: &mut Context<Self>,
20373 ) {
20374 let Some(workspace) = self.workspace() else {
20375 cx.propagate();
20376 return;
20377 };
20378
20379 if self.buffer.read(cx).is_singleton() {
20380 cx.propagate();
20381 return;
20382 }
20383
20384 let mut new_selections_by_buffer = HashMap::default();
20385 match &jump_data {
20386 Some(JumpData::MultiBufferPoint {
20387 excerpt_id,
20388 position,
20389 anchor,
20390 line_offset_from_top,
20391 }) => {
20392 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20393 if let Some(buffer) = multi_buffer_snapshot
20394 .buffer_id_for_excerpt(*excerpt_id)
20395 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20396 {
20397 let buffer_snapshot = buffer.read(cx).snapshot();
20398 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20399 language::ToPoint::to_point(anchor, &buffer_snapshot)
20400 } else {
20401 buffer_snapshot.clip_point(*position, Bias::Left)
20402 };
20403 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20404 new_selections_by_buffer.insert(
20405 buffer,
20406 (
20407 vec![jump_to_offset..jump_to_offset],
20408 Some(*line_offset_from_top),
20409 ),
20410 );
20411 }
20412 }
20413 Some(JumpData::MultiBufferRow {
20414 row,
20415 line_offset_from_top,
20416 }) => {
20417 let point = MultiBufferPoint::new(row.0, 0);
20418 if let Some((buffer, buffer_point, _)) =
20419 self.buffer.read(cx).point_to_buffer_point(point, cx)
20420 {
20421 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20422 new_selections_by_buffer
20423 .entry(buffer)
20424 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20425 .0
20426 .push(buffer_offset..buffer_offset)
20427 }
20428 }
20429 None => {
20430 let selections = self.selections.all::<usize>(cx);
20431 let multi_buffer = self.buffer.read(cx);
20432 for selection in selections {
20433 for (snapshot, range, _, anchor) in multi_buffer
20434 .snapshot(cx)
20435 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20436 {
20437 if let Some(anchor) = anchor {
20438 // selection is in a deleted hunk
20439 let Some(buffer_id) = anchor.buffer_id else {
20440 continue;
20441 };
20442 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20443 continue;
20444 };
20445 let offset = text::ToOffset::to_offset(
20446 &anchor.text_anchor,
20447 &buffer_handle.read(cx).snapshot(),
20448 );
20449 let range = offset..offset;
20450 new_selections_by_buffer
20451 .entry(buffer_handle)
20452 .or_insert((Vec::new(), None))
20453 .0
20454 .push(range)
20455 } else {
20456 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20457 else {
20458 continue;
20459 };
20460 new_selections_by_buffer
20461 .entry(buffer_handle)
20462 .or_insert((Vec::new(), None))
20463 .0
20464 .push(range)
20465 }
20466 }
20467 }
20468 }
20469 }
20470
20471 new_selections_by_buffer
20472 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20473
20474 if new_selections_by_buffer.is_empty() {
20475 return;
20476 }
20477
20478 // We defer the pane interaction because we ourselves are a workspace item
20479 // and activating a new item causes the pane to call a method on us reentrantly,
20480 // which panics if we're on the stack.
20481 window.defer(cx, move |window, cx| {
20482 workspace.update(cx, |workspace, cx| {
20483 let pane = if split {
20484 workspace.adjacent_pane(window, cx)
20485 } else {
20486 workspace.active_pane().clone()
20487 };
20488
20489 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20490 let editor = buffer
20491 .read(cx)
20492 .file()
20493 .is_none()
20494 .then(|| {
20495 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20496 // so `workspace.open_project_item` will never find them, always opening a new editor.
20497 // Instead, we try to activate the existing editor in the pane first.
20498 let (editor, pane_item_index) =
20499 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20500 let editor = item.downcast::<Editor>()?;
20501 let singleton_buffer =
20502 editor.read(cx).buffer().read(cx).as_singleton()?;
20503 if singleton_buffer == buffer {
20504 Some((editor, i))
20505 } else {
20506 None
20507 }
20508 })?;
20509 pane.update(cx, |pane, cx| {
20510 pane.activate_item(pane_item_index, true, true, window, cx)
20511 });
20512 Some(editor)
20513 })
20514 .flatten()
20515 .unwrap_or_else(|| {
20516 workspace.open_project_item::<Self>(
20517 pane.clone(),
20518 buffer,
20519 true,
20520 true,
20521 window,
20522 cx,
20523 )
20524 });
20525
20526 editor.update(cx, |editor, cx| {
20527 let autoscroll = match scroll_offset {
20528 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20529 None => Autoscroll::newest(),
20530 };
20531 let nav_history = editor.nav_history.take();
20532 editor.change_selections(
20533 SelectionEffects::scroll(autoscroll),
20534 window,
20535 cx,
20536 |s| {
20537 s.select_ranges(ranges);
20538 },
20539 );
20540 editor.nav_history = nav_history;
20541 });
20542 }
20543 })
20544 });
20545 }
20546
20547 // For now, don't allow opening excerpts in buffers that aren't backed by
20548 // regular project files.
20549 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20550 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20551 }
20552
20553 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20554 let snapshot = self.buffer.read(cx).read(cx);
20555 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20556 Some(
20557 ranges
20558 .iter()
20559 .map(move |range| {
20560 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20561 })
20562 .collect(),
20563 )
20564 }
20565
20566 fn selection_replacement_ranges(
20567 &self,
20568 range: Range<OffsetUtf16>,
20569 cx: &mut App,
20570 ) -> Vec<Range<OffsetUtf16>> {
20571 let selections = self.selections.all::<OffsetUtf16>(cx);
20572 let newest_selection = selections
20573 .iter()
20574 .max_by_key(|selection| selection.id)
20575 .unwrap();
20576 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20577 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20578 let snapshot = self.buffer.read(cx).read(cx);
20579 selections
20580 .into_iter()
20581 .map(|mut selection| {
20582 selection.start.0 =
20583 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20584 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20585 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20586 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20587 })
20588 .collect()
20589 }
20590
20591 fn report_editor_event(
20592 &self,
20593 reported_event: ReportEditorEvent,
20594 file_extension: Option<String>,
20595 cx: &App,
20596 ) {
20597 if cfg!(any(test, feature = "test-support")) {
20598 return;
20599 }
20600
20601 let Some(project) = &self.project else { return };
20602
20603 // If None, we are in a file without an extension
20604 let file = self
20605 .buffer
20606 .read(cx)
20607 .as_singleton()
20608 .and_then(|b| b.read(cx).file());
20609 let file_extension = file_extension.or(file
20610 .as_ref()
20611 .and_then(|file| Path::new(file.file_name(cx)).extension())
20612 .and_then(|e| e.to_str())
20613 .map(|a| a.to_string()));
20614
20615 let vim_mode = vim_enabled(cx);
20616
20617 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20618 let copilot_enabled = edit_predictions_provider
20619 == language::language_settings::EditPredictionProvider::Copilot;
20620 let copilot_enabled_for_language = self
20621 .buffer
20622 .read(cx)
20623 .language_settings(cx)
20624 .show_edit_predictions;
20625
20626 let project = project.read(cx);
20627 let event_type = reported_event.event_type();
20628
20629 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20630 telemetry::event!(
20631 event_type,
20632 type = if auto_saved {"autosave"} else {"manual"},
20633 file_extension,
20634 vim_mode,
20635 copilot_enabled,
20636 copilot_enabled_for_language,
20637 edit_predictions_provider,
20638 is_via_ssh = project.is_via_ssh(),
20639 );
20640 } else {
20641 telemetry::event!(
20642 event_type,
20643 file_extension,
20644 vim_mode,
20645 copilot_enabled,
20646 copilot_enabled_for_language,
20647 edit_predictions_provider,
20648 is_via_ssh = project.is_via_ssh(),
20649 );
20650 };
20651 }
20652
20653 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20654 /// with each line being an array of {text, highlight} objects.
20655 fn copy_highlight_json(
20656 &mut self,
20657 _: &CopyHighlightJson,
20658 window: &mut Window,
20659 cx: &mut Context<Self>,
20660 ) {
20661 #[derive(Serialize)]
20662 struct Chunk<'a> {
20663 text: String,
20664 highlight: Option<&'a str>,
20665 }
20666
20667 let snapshot = self.buffer.read(cx).snapshot(cx);
20668 let range = self
20669 .selected_text_range(false, window, cx)
20670 .and_then(|selection| {
20671 if selection.range.is_empty() {
20672 None
20673 } else {
20674 Some(selection.range)
20675 }
20676 })
20677 .unwrap_or_else(|| 0..snapshot.len());
20678
20679 let chunks = snapshot.chunks(range, true);
20680 let mut lines = Vec::new();
20681 let mut line: VecDeque<Chunk> = VecDeque::new();
20682
20683 let Some(style) = self.style.as_ref() else {
20684 return;
20685 };
20686
20687 for chunk in chunks {
20688 let highlight = chunk
20689 .syntax_highlight_id
20690 .and_then(|id| id.name(&style.syntax));
20691 let mut chunk_lines = chunk.text.split('\n').peekable();
20692 while let Some(text) = chunk_lines.next() {
20693 let mut merged_with_last_token = false;
20694 if let Some(last_token) = line.back_mut()
20695 && last_token.highlight == highlight
20696 {
20697 last_token.text.push_str(text);
20698 merged_with_last_token = true;
20699 }
20700
20701 if !merged_with_last_token {
20702 line.push_back(Chunk {
20703 text: text.into(),
20704 highlight,
20705 });
20706 }
20707
20708 if chunk_lines.peek().is_some() {
20709 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20710 line.pop_front();
20711 }
20712 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20713 line.pop_back();
20714 }
20715
20716 lines.push(mem::take(&mut line));
20717 }
20718 }
20719 }
20720
20721 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20722 return;
20723 };
20724 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20725 }
20726
20727 pub fn open_context_menu(
20728 &mut self,
20729 _: &OpenContextMenu,
20730 window: &mut Window,
20731 cx: &mut Context<Self>,
20732 ) {
20733 self.request_autoscroll(Autoscroll::newest(), cx);
20734 let position = self.selections.newest_display(cx).start;
20735 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20736 }
20737
20738 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20739 &self.inlay_hint_cache
20740 }
20741
20742 pub fn replay_insert_event(
20743 &mut self,
20744 text: &str,
20745 relative_utf16_range: Option<Range<isize>>,
20746 window: &mut Window,
20747 cx: &mut Context<Self>,
20748 ) {
20749 if !self.input_enabled {
20750 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20751 return;
20752 }
20753 if let Some(relative_utf16_range) = relative_utf16_range {
20754 let selections = self.selections.all::<OffsetUtf16>(cx);
20755 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20756 let new_ranges = selections.into_iter().map(|range| {
20757 let start = OffsetUtf16(
20758 range
20759 .head()
20760 .0
20761 .saturating_add_signed(relative_utf16_range.start),
20762 );
20763 let end = OffsetUtf16(
20764 range
20765 .head()
20766 .0
20767 .saturating_add_signed(relative_utf16_range.end),
20768 );
20769 start..end
20770 });
20771 s.select_ranges(new_ranges);
20772 });
20773 }
20774
20775 self.handle_input(text, window, cx);
20776 }
20777
20778 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20779 let Some(provider) = self.semantics_provider.as_ref() else {
20780 return false;
20781 };
20782
20783 let mut supports = false;
20784 self.buffer().update(cx, |this, cx| {
20785 this.for_each_buffer(|buffer| {
20786 supports |= provider.supports_inlay_hints(buffer, cx);
20787 });
20788 });
20789
20790 supports
20791 }
20792
20793 pub fn is_focused(&self, window: &Window) -> bool {
20794 self.focus_handle.is_focused(window)
20795 }
20796
20797 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20798 cx.emit(EditorEvent::Focused);
20799
20800 if let Some(descendant) = self
20801 .last_focused_descendant
20802 .take()
20803 .and_then(|descendant| descendant.upgrade())
20804 {
20805 window.focus(&descendant);
20806 } else {
20807 if let Some(blame) = self.blame.as_ref() {
20808 blame.update(cx, GitBlame::focus)
20809 }
20810
20811 self.blink_manager.update(cx, BlinkManager::enable);
20812 self.show_cursor_names(window, cx);
20813 self.buffer.update(cx, |buffer, cx| {
20814 buffer.finalize_last_transaction(cx);
20815 if self.leader_id.is_none() {
20816 buffer.set_active_selections(
20817 &self.selections.disjoint_anchors(),
20818 self.selections.line_mode,
20819 self.cursor_shape,
20820 cx,
20821 );
20822 }
20823 });
20824 }
20825 }
20826
20827 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20828 cx.emit(EditorEvent::FocusedIn)
20829 }
20830
20831 fn handle_focus_out(
20832 &mut self,
20833 event: FocusOutEvent,
20834 _window: &mut Window,
20835 cx: &mut Context<Self>,
20836 ) {
20837 if event.blurred != self.focus_handle {
20838 self.last_focused_descendant = Some(event.blurred);
20839 }
20840 self.selection_drag_state = SelectionDragState::None;
20841 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20842 }
20843
20844 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20845 self.blink_manager.update(cx, BlinkManager::disable);
20846 self.buffer
20847 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20848
20849 if let Some(blame) = self.blame.as_ref() {
20850 blame.update(cx, GitBlame::blur)
20851 }
20852 if !self.hover_state.focused(window, cx) {
20853 hide_hover(self, cx);
20854 }
20855 if !self
20856 .context_menu
20857 .borrow()
20858 .as_ref()
20859 .is_some_and(|context_menu| context_menu.focused(window, cx))
20860 {
20861 self.hide_context_menu(window, cx);
20862 }
20863 self.discard_edit_prediction(false, cx);
20864 cx.emit(EditorEvent::Blurred);
20865 cx.notify();
20866 }
20867
20868 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20869 let mut pending: String = window
20870 .pending_input_keystrokes()
20871 .into_iter()
20872 .flatten()
20873 .filter_map(|keystroke| {
20874 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20875 keystroke.key_char.clone()
20876 } else {
20877 None
20878 }
20879 })
20880 .collect();
20881
20882 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20883 pending = "".to_string();
20884 }
20885
20886 let existing_pending = self
20887 .text_highlights::<PendingInput>(cx)
20888 .map(|(_, ranges)| ranges.to_vec());
20889 if existing_pending.is_none() && pending.is_empty() {
20890 return;
20891 }
20892 let transaction =
20893 self.transact(window, cx, |this, window, cx| {
20894 let selections = this.selections.all::<usize>(cx);
20895 let edits = selections
20896 .iter()
20897 .map(|selection| (selection.end..selection.end, pending.clone()));
20898 this.edit(edits, cx);
20899 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20900 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20901 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20902 }));
20903 });
20904 if let Some(existing_ranges) = existing_pending {
20905 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20906 this.edit(edits, cx);
20907 }
20908 });
20909
20910 let snapshot = self.snapshot(window, cx);
20911 let ranges = self
20912 .selections
20913 .all::<usize>(cx)
20914 .into_iter()
20915 .map(|selection| {
20916 snapshot.buffer_snapshot.anchor_after(selection.end)
20917 ..snapshot
20918 .buffer_snapshot
20919 .anchor_before(selection.end + pending.len())
20920 })
20921 .collect();
20922
20923 if pending.is_empty() {
20924 self.clear_highlights::<PendingInput>(cx);
20925 } else {
20926 self.highlight_text::<PendingInput>(
20927 ranges,
20928 HighlightStyle {
20929 underline: Some(UnderlineStyle {
20930 thickness: px(1.),
20931 color: None,
20932 wavy: false,
20933 }),
20934 ..Default::default()
20935 },
20936 cx,
20937 );
20938 }
20939
20940 self.ime_transaction = self.ime_transaction.or(transaction);
20941 if let Some(transaction) = self.ime_transaction {
20942 self.buffer.update(cx, |buffer, cx| {
20943 buffer.group_until_transaction(transaction, cx);
20944 });
20945 }
20946
20947 if self.text_highlights::<PendingInput>(cx).is_none() {
20948 self.ime_transaction.take();
20949 }
20950 }
20951
20952 pub fn register_action_renderer(
20953 &mut self,
20954 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20955 ) -> Subscription {
20956 let id = self.next_editor_action_id.post_inc();
20957 self.editor_actions
20958 .borrow_mut()
20959 .insert(id, Box::new(listener));
20960
20961 let editor_actions = self.editor_actions.clone();
20962 Subscription::new(move || {
20963 editor_actions.borrow_mut().remove(&id);
20964 })
20965 }
20966
20967 pub fn register_action<A: Action>(
20968 &mut self,
20969 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20970 ) -> Subscription {
20971 let id = self.next_editor_action_id.post_inc();
20972 let listener = Arc::new(listener);
20973 self.editor_actions.borrow_mut().insert(
20974 id,
20975 Box::new(move |_, window, _| {
20976 let listener = listener.clone();
20977 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20978 let action = action.downcast_ref().unwrap();
20979 if phase == DispatchPhase::Bubble {
20980 listener(action, window, cx)
20981 }
20982 })
20983 }),
20984 );
20985
20986 let editor_actions = self.editor_actions.clone();
20987 Subscription::new(move || {
20988 editor_actions.borrow_mut().remove(&id);
20989 })
20990 }
20991
20992 pub fn file_header_size(&self) -> u32 {
20993 FILE_HEADER_HEIGHT
20994 }
20995
20996 pub fn restore(
20997 &mut self,
20998 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20999 window: &mut Window,
21000 cx: &mut Context<Self>,
21001 ) {
21002 let workspace = self.workspace();
21003 let project = self.project();
21004 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21005 let mut tasks = Vec::new();
21006 for (buffer_id, changes) in revert_changes {
21007 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21008 buffer.update(cx, |buffer, cx| {
21009 buffer.edit(
21010 changes
21011 .into_iter()
21012 .map(|(range, text)| (range, text.to_string())),
21013 None,
21014 cx,
21015 );
21016 });
21017
21018 if let Some(project) =
21019 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21020 {
21021 project.update(cx, |project, cx| {
21022 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21023 })
21024 }
21025 }
21026 }
21027 tasks
21028 });
21029 cx.spawn_in(window, async move |_, cx| {
21030 for (buffer, task) in save_tasks {
21031 let result = task.await;
21032 if result.is_err() {
21033 let Some(path) = buffer
21034 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21035 .ok()
21036 else {
21037 continue;
21038 };
21039 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21040 let Some(task) = cx
21041 .update_window_entity(workspace, |workspace, window, cx| {
21042 workspace
21043 .open_path_preview(path, None, false, false, false, window, cx)
21044 })
21045 .ok()
21046 else {
21047 continue;
21048 };
21049 task.await.log_err();
21050 }
21051 }
21052 }
21053 })
21054 .detach();
21055 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21056 selections.refresh()
21057 });
21058 }
21059
21060 pub fn to_pixel_point(
21061 &self,
21062 source: multi_buffer::Anchor,
21063 editor_snapshot: &EditorSnapshot,
21064 window: &mut Window,
21065 ) -> Option<gpui::Point<Pixels>> {
21066 let source_point = source.to_display_point(editor_snapshot);
21067 self.display_to_pixel_point(source_point, editor_snapshot, window)
21068 }
21069
21070 pub fn display_to_pixel_point(
21071 &self,
21072 source: DisplayPoint,
21073 editor_snapshot: &EditorSnapshot,
21074 window: &mut Window,
21075 ) -> Option<gpui::Point<Pixels>> {
21076 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21077 let text_layout_details = self.text_layout_details(window);
21078 let scroll_top = text_layout_details
21079 .scroll_anchor
21080 .scroll_position(editor_snapshot)
21081 .y;
21082
21083 if source.row().as_f32() < scroll_top.floor() {
21084 return None;
21085 }
21086 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21087 let source_y = line_height * (source.row().as_f32() - scroll_top);
21088 Some(gpui::Point::new(source_x, source_y))
21089 }
21090
21091 pub fn has_visible_completions_menu(&self) -> bool {
21092 !self.edit_prediction_preview_is_active()
21093 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21094 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21095 })
21096 }
21097
21098 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21099 if self.mode.is_minimap() {
21100 return;
21101 }
21102 self.addons
21103 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21104 }
21105
21106 pub fn unregister_addon<T: Addon>(&mut self) {
21107 self.addons.remove(&std::any::TypeId::of::<T>());
21108 }
21109
21110 pub fn addon<T: Addon>(&self) -> Option<&T> {
21111 let type_id = std::any::TypeId::of::<T>();
21112 self.addons
21113 .get(&type_id)
21114 .and_then(|item| item.to_any().downcast_ref::<T>())
21115 }
21116
21117 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21118 let type_id = std::any::TypeId::of::<T>();
21119 self.addons
21120 .get_mut(&type_id)
21121 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21122 }
21123
21124 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21125 let text_layout_details = self.text_layout_details(window);
21126 let style = &text_layout_details.editor_style;
21127 let font_id = window.text_system().resolve_font(&style.text.font());
21128 let font_size = style.text.font_size.to_pixels(window.rem_size());
21129 let line_height = style.text.line_height_in_pixels(window.rem_size());
21130 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21131 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21132
21133 CharacterDimensions {
21134 em_width,
21135 em_advance,
21136 line_height,
21137 }
21138 }
21139
21140 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21141 self.load_diff_task.clone()
21142 }
21143
21144 fn read_metadata_from_db(
21145 &mut self,
21146 item_id: u64,
21147 workspace_id: WorkspaceId,
21148 window: &mut Window,
21149 cx: &mut Context<Editor>,
21150 ) {
21151 if self.is_singleton(cx)
21152 && !self.mode.is_minimap()
21153 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21154 {
21155 let buffer_snapshot = OnceCell::new();
21156
21157 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21158 && !folds.is_empty()
21159 {
21160 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21161 self.fold_ranges(
21162 folds
21163 .into_iter()
21164 .map(|(start, end)| {
21165 snapshot.clip_offset(start, Bias::Left)
21166 ..snapshot.clip_offset(end, Bias::Right)
21167 })
21168 .collect(),
21169 false,
21170 window,
21171 cx,
21172 );
21173 }
21174
21175 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21176 && !selections.is_empty()
21177 {
21178 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21179 // skip adding the initial selection to selection history
21180 self.selection_history.mode = SelectionHistoryMode::Skipping;
21181 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21182 s.select_ranges(selections.into_iter().map(|(start, end)| {
21183 snapshot.clip_offset(start, Bias::Left)
21184 ..snapshot.clip_offset(end, Bias::Right)
21185 }));
21186 });
21187 self.selection_history.mode = SelectionHistoryMode::Normal;
21188 };
21189 }
21190
21191 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21192 }
21193
21194 fn update_lsp_data(
21195 &mut self,
21196 ignore_cache: bool,
21197 for_buffer: Option<BufferId>,
21198 window: &mut Window,
21199 cx: &mut Context<'_, Self>,
21200 ) {
21201 self.pull_diagnostics(for_buffer, window, cx);
21202 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21203 }
21204}
21205
21206fn vim_enabled(cx: &App) -> bool {
21207 cx.global::<SettingsStore>()
21208 .raw_user_settings()
21209 .get("vim_mode")
21210 == Some(&serde_json::Value::Bool(true))
21211}
21212
21213fn process_completion_for_edit(
21214 completion: &Completion,
21215 intent: CompletionIntent,
21216 buffer: &Entity<Buffer>,
21217 cursor_position: &text::Anchor,
21218 cx: &mut Context<Editor>,
21219) -> CompletionEdit {
21220 let buffer = buffer.read(cx);
21221 let buffer_snapshot = buffer.snapshot();
21222 let (snippet, new_text) = if completion.is_snippet() {
21223 // Workaround for typescript language server issues so that methods don't expand within
21224 // strings and functions with type expressions. The previous point is used because the query
21225 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21226 let mut snippet_source = completion.new_text.clone();
21227 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21228 previous_point.column = previous_point.column.saturating_sub(1);
21229 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21230 && scope.prefers_label_for_snippet_in_completion()
21231 && let Some(label) = completion.label()
21232 && matches!(
21233 completion.kind(),
21234 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21235 )
21236 {
21237 snippet_source = label;
21238 }
21239 match Snippet::parse(&snippet_source).log_err() {
21240 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21241 None => (None, completion.new_text.clone()),
21242 }
21243 } else {
21244 (None, completion.new_text.clone())
21245 };
21246
21247 let mut range_to_replace = {
21248 let replace_range = &completion.replace_range;
21249 if let CompletionSource::Lsp {
21250 insert_range: Some(insert_range),
21251 ..
21252 } = &completion.source
21253 {
21254 debug_assert_eq!(
21255 insert_range.start, replace_range.start,
21256 "insert_range and replace_range should start at the same position"
21257 );
21258 debug_assert!(
21259 insert_range
21260 .start
21261 .cmp(cursor_position, &buffer_snapshot)
21262 .is_le(),
21263 "insert_range should start before or at cursor position"
21264 );
21265 debug_assert!(
21266 replace_range
21267 .start
21268 .cmp(cursor_position, &buffer_snapshot)
21269 .is_le(),
21270 "replace_range should start before or at cursor position"
21271 );
21272
21273 let should_replace = match intent {
21274 CompletionIntent::CompleteWithInsert => false,
21275 CompletionIntent::CompleteWithReplace => true,
21276 CompletionIntent::Complete | CompletionIntent::Compose => {
21277 let insert_mode =
21278 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21279 .completions
21280 .lsp_insert_mode;
21281 match insert_mode {
21282 LspInsertMode::Insert => false,
21283 LspInsertMode::Replace => true,
21284 LspInsertMode::ReplaceSubsequence => {
21285 let mut text_to_replace = buffer.chars_for_range(
21286 buffer.anchor_before(replace_range.start)
21287 ..buffer.anchor_after(replace_range.end),
21288 );
21289 let mut current_needle = text_to_replace.next();
21290 for haystack_ch in completion.label.text.chars() {
21291 if let Some(needle_ch) = current_needle
21292 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21293 {
21294 current_needle = text_to_replace.next();
21295 }
21296 }
21297 current_needle.is_none()
21298 }
21299 LspInsertMode::ReplaceSuffix => {
21300 if replace_range
21301 .end
21302 .cmp(cursor_position, &buffer_snapshot)
21303 .is_gt()
21304 {
21305 let range_after_cursor = *cursor_position..replace_range.end;
21306 let text_after_cursor = buffer
21307 .text_for_range(
21308 buffer.anchor_before(range_after_cursor.start)
21309 ..buffer.anchor_after(range_after_cursor.end),
21310 )
21311 .collect::<String>()
21312 .to_ascii_lowercase();
21313 completion
21314 .label
21315 .text
21316 .to_ascii_lowercase()
21317 .ends_with(&text_after_cursor)
21318 } else {
21319 true
21320 }
21321 }
21322 }
21323 }
21324 };
21325
21326 if should_replace {
21327 replace_range.clone()
21328 } else {
21329 insert_range.clone()
21330 }
21331 } else {
21332 replace_range.clone()
21333 }
21334 };
21335
21336 if range_to_replace
21337 .end
21338 .cmp(cursor_position, &buffer_snapshot)
21339 .is_lt()
21340 {
21341 range_to_replace.end = *cursor_position;
21342 }
21343
21344 CompletionEdit {
21345 new_text,
21346 replace_range: range_to_replace.to_offset(buffer),
21347 snippet,
21348 }
21349}
21350
21351struct CompletionEdit {
21352 new_text: String,
21353 replace_range: Range<usize>,
21354 snippet: Option<Snippet>,
21355}
21356
21357fn insert_extra_newline_brackets(
21358 buffer: &MultiBufferSnapshot,
21359 range: Range<usize>,
21360 language: &language::LanguageScope,
21361) -> bool {
21362 let leading_whitespace_len = buffer
21363 .reversed_chars_at(range.start)
21364 .take_while(|c| c.is_whitespace() && *c != '\n')
21365 .map(|c| c.len_utf8())
21366 .sum::<usize>();
21367 let trailing_whitespace_len = buffer
21368 .chars_at(range.end)
21369 .take_while(|c| c.is_whitespace() && *c != '\n')
21370 .map(|c| c.len_utf8())
21371 .sum::<usize>();
21372 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21373
21374 language.brackets().any(|(pair, enabled)| {
21375 let pair_start = pair.start.trim_end();
21376 let pair_end = pair.end.trim_start();
21377
21378 enabled
21379 && pair.newline
21380 && buffer.contains_str_at(range.end, pair_end)
21381 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21382 })
21383}
21384
21385fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21386 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21387 [(buffer, range, _)] => (*buffer, range.clone()),
21388 _ => return false,
21389 };
21390 let pair = {
21391 let mut result: Option<BracketMatch> = None;
21392
21393 for pair in buffer
21394 .all_bracket_ranges(range.clone())
21395 .filter(move |pair| {
21396 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21397 })
21398 {
21399 let len = pair.close_range.end - pair.open_range.start;
21400
21401 if let Some(existing) = &result {
21402 let existing_len = existing.close_range.end - existing.open_range.start;
21403 if len > existing_len {
21404 continue;
21405 }
21406 }
21407
21408 result = Some(pair);
21409 }
21410
21411 result
21412 };
21413 let Some(pair) = pair else {
21414 return false;
21415 };
21416 pair.newline_only
21417 && buffer
21418 .chars_for_range(pair.open_range.end..range.start)
21419 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21420 .all(|c| c.is_whitespace() && c != '\n')
21421}
21422
21423fn update_uncommitted_diff_for_buffer(
21424 editor: Entity<Editor>,
21425 project: &Entity<Project>,
21426 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21427 buffer: Entity<MultiBuffer>,
21428 cx: &mut App,
21429) -> Task<()> {
21430 let mut tasks = Vec::new();
21431 project.update(cx, |project, cx| {
21432 for buffer in buffers {
21433 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21434 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21435 }
21436 }
21437 });
21438 cx.spawn(async move |cx| {
21439 let diffs = future::join_all(tasks).await;
21440 if editor
21441 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21442 .unwrap_or(false)
21443 {
21444 return;
21445 }
21446
21447 buffer
21448 .update(cx, |buffer, cx| {
21449 for diff in diffs.into_iter().flatten() {
21450 buffer.add_diff(diff, cx);
21451 }
21452 })
21453 .ok();
21454 })
21455}
21456
21457fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21458 let tab_size = tab_size.get() as usize;
21459 let mut width = offset;
21460
21461 for ch in text.chars() {
21462 width += if ch == '\t' {
21463 tab_size - (width % tab_size)
21464 } else {
21465 1
21466 };
21467 }
21468
21469 width - offset
21470}
21471
21472#[cfg(test)]
21473mod tests {
21474 use super::*;
21475
21476 #[test]
21477 fn test_string_size_with_expanded_tabs() {
21478 let nz = |val| NonZeroU32::new(val).unwrap();
21479 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21480 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21481 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21482 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21483 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21484 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21485 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21486 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21487 }
21488}
21489
21490/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21491struct WordBreakingTokenizer<'a> {
21492 input: &'a str,
21493}
21494
21495impl<'a> WordBreakingTokenizer<'a> {
21496 fn new(input: &'a str) -> Self {
21497 Self { input }
21498 }
21499}
21500
21501fn is_char_ideographic(ch: char) -> bool {
21502 use unicode_script::Script::*;
21503 use unicode_script::UnicodeScript;
21504 matches!(ch.script(), Han | Tangut | Yi)
21505}
21506
21507fn is_grapheme_ideographic(text: &str) -> bool {
21508 text.chars().any(is_char_ideographic)
21509}
21510
21511fn is_grapheme_whitespace(text: &str) -> bool {
21512 text.chars().any(|x| x.is_whitespace())
21513}
21514
21515fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21516 text.chars()
21517 .next()
21518 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21519}
21520
21521#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21522enum WordBreakToken<'a> {
21523 Word { token: &'a str, grapheme_len: usize },
21524 InlineWhitespace { token: &'a str, grapheme_len: usize },
21525 Newline,
21526}
21527
21528impl<'a> Iterator for WordBreakingTokenizer<'a> {
21529 /// Yields a span, the count of graphemes in the token, and whether it was
21530 /// whitespace. Note that it also breaks at word boundaries.
21531 type Item = WordBreakToken<'a>;
21532
21533 fn next(&mut self) -> Option<Self::Item> {
21534 use unicode_segmentation::UnicodeSegmentation;
21535 if self.input.is_empty() {
21536 return None;
21537 }
21538
21539 let mut iter = self.input.graphemes(true).peekable();
21540 let mut offset = 0;
21541 let mut grapheme_len = 0;
21542 if let Some(first_grapheme) = iter.next() {
21543 let is_newline = first_grapheme == "\n";
21544 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21545 offset += first_grapheme.len();
21546 grapheme_len += 1;
21547 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21548 if let Some(grapheme) = iter.peek().copied()
21549 && should_stay_with_preceding_ideograph(grapheme)
21550 {
21551 offset += grapheme.len();
21552 grapheme_len += 1;
21553 }
21554 } else {
21555 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21556 let mut next_word_bound = words.peek().copied();
21557 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21558 next_word_bound = words.next();
21559 }
21560 while let Some(grapheme) = iter.peek().copied() {
21561 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21562 break;
21563 };
21564 if is_grapheme_whitespace(grapheme) != is_whitespace
21565 || (grapheme == "\n") != is_newline
21566 {
21567 break;
21568 };
21569 offset += grapheme.len();
21570 grapheme_len += 1;
21571 iter.next();
21572 }
21573 }
21574 let token = &self.input[..offset];
21575 self.input = &self.input[offset..];
21576 if token == "\n" {
21577 Some(WordBreakToken::Newline)
21578 } else if is_whitespace {
21579 Some(WordBreakToken::InlineWhitespace {
21580 token,
21581 grapheme_len,
21582 })
21583 } else {
21584 Some(WordBreakToken::Word {
21585 token,
21586 grapheme_len,
21587 })
21588 }
21589 } else {
21590 None
21591 }
21592 }
21593}
21594
21595#[test]
21596fn test_word_breaking_tokenizer() {
21597 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21598 ("", &[]),
21599 (" ", &[whitespace(" ", 2)]),
21600 ("Ʒ", &[word("Ʒ", 1)]),
21601 ("Ǽ", &[word("Ǽ", 1)]),
21602 ("⋑", &[word("⋑", 1)]),
21603 ("⋑⋑", &[word("⋑⋑", 2)]),
21604 (
21605 "原理,进而",
21606 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21607 ),
21608 (
21609 "hello world",
21610 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21611 ),
21612 (
21613 "hello, world",
21614 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21615 ),
21616 (
21617 " hello world",
21618 &[
21619 whitespace(" ", 2),
21620 word("hello", 5),
21621 whitespace(" ", 1),
21622 word("world", 5),
21623 ],
21624 ),
21625 (
21626 "这是什么 \n 钢笔",
21627 &[
21628 word("这", 1),
21629 word("是", 1),
21630 word("什", 1),
21631 word("么", 1),
21632 whitespace(" ", 1),
21633 newline(),
21634 whitespace(" ", 1),
21635 word("钢", 1),
21636 word("笔", 1),
21637 ],
21638 ),
21639 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21640 ];
21641
21642 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21643 WordBreakToken::Word {
21644 token,
21645 grapheme_len,
21646 }
21647 }
21648
21649 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21650 WordBreakToken::InlineWhitespace {
21651 token,
21652 grapheme_len,
21653 }
21654 }
21655
21656 fn newline() -> WordBreakToken<'static> {
21657 WordBreakToken::Newline
21658 }
21659
21660 for (input, result) in tests {
21661 assert_eq!(
21662 WordBreakingTokenizer::new(input)
21663 .collect::<Vec<_>>()
21664 .as_slice(),
21665 *result,
21666 );
21667 }
21668}
21669
21670fn wrap_with_prefix(
21671 first_line_prefix: String,
21672 subsequent_lines_prefix: String,
21673 unwrapped_text: String,
21674 wrap_column: usize,
21675 tab_size: NonZeroU32,
21676 preserve_existing_whitespace: bool,
21677) -> String {
21678 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21679 let subsequent_lines_prefix_len =
21680 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21681 let mut wrapped_text = String::new();
21682 let mut current_line = first_line_prefix.clone();
21683 let mut is_first_line = true;
21684
21685 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21686 let mut current_line_len = first_line_prefix_len;
21687 let mut in_whitespace = false;
21688 for token in tokenizer {
21689 let have_preceding_whitespace = in_whitespace;
21690 match token {
21691 WordBreakToken::Word {
21692 token,
21693 grapheme_len,
21694 } => {
21695 in_whitespace = false;
21696 let current_prefix_len = if is_first_line {
21697 first_line_prefix_len
21698 } else {
21699 subsequent_lines_prefix_len
21700 };
21701 if current_line_len + grapheme_len > wrap_column
21702 && current_line_len != current_prefix_len
21703 {
21704 wrapped_text.push_str(current_line.trim_end());
21705 wrapped_text.push('\n');
21706 is_first_line = false;
21707 current_line = subsequent_lines_prefix.clone();
21708 current_line_len = subsequent_lines_prefix_len;
21709 }
21710 current_line.push_str(token);
21711 current_line_len += grapheme_len;
21712 }
21713 WordBreakToken::InlineWhitespace {
21714 mut token,
21715 mut grapheme_len,
21716 } => {
21717 in_whitespace = true;
21718 if have_preceding_whitespace && !preserve_existing_whitespace {
21719 continue;
21720 }
21721 if !preserve_existing_whitespace {
21722 token = " ";
21723 grapheme_len = 1;
21724 }
21725 let current_prefix_len = if is_first_line {
21726 first_line_prefix_len
21727 } else {
21728 subsequent_lines_prefix_len
21729 };
21730 if current_line_len + grapheme_len > wrap_column {
21731 wrapped_text.push_str(current_line.trim_end());
21732 wrapped_text.push('\n');
21733 is_first_line = false;
21734 current_line = subsequent_lines_prefix.clone();
21735 current_line_len = subsequent_lines_prefix_len;
21736 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21737 current_line.push_str(token);
21738 current_line_len += grapheme_len;
21739 }
21740 }
21741 WordBreakToken::Newline => {
21742 in_whitespace = true;
21743 let current_prefix_len = if is_first_line {
21744 first_line_prefix_len
21745 } else {
21746 subsequent_lines_prefix_len
21747 };
21748 if preserve_existing_whitespace {
21749 wrapped_text.push_str(current_line.trim_end());
21750 wrapped_text.push('\n');
21751 is_first_line = false;
21752 current_line = subsequent_lines_prefix.clone();
21753 current_line_len = subsequent_lines_prefix_len;
21754 } else if have_preceding_whitespace {
21755 continue;
21756 } else if current_line_len + 1 > wrap_column
21757 && current_line_len != current_prefix_len
21758 {
21759 wrapped_text.push_str(current_line.trim_end());
21760 wrapped_text.push('\n');
21761 is_first_line = false;
21762 current_line = subsequent_lines_prefix.clone();
21763 current_line_len = subsequent_lines_prefix_len;
21764 } else if current_line_len != current_prefix_len {
21765 current_line.push(' ');
21766 current_line_len += 1;
21767 }
21768 }
21769 }
21770 }
21771
21772 if !current_line.is_empty() {
21773 wrapped_text.push_str(¤t_line);
21774 }
21775 wrapped_text
21776}
21777
21778#[test]
21779fn test_wrap_with_prefix() {
21780 assert_eq!(
21781 wrap_with_prefix(
21782 "# ".to_string(),
21783 "# ".to_string(),
21784 "abcdefg".to_string(),
21785 4,
21786 NonZeroU32::new(4).unwrap(),
21787 false,
21788 ),
21789 "# abcdefg"
21790 );
21791 assert_eq!(
21792 wrap_with_prefix(
21793 "".to_string(),
21794 "".to_string(),
21795 "\thello world".to_string(),
21796 8,
21797 NonZeroU32::new(4).unwrap(),
21798 false,
21799 ),
21800 "hello\nworld"
21801 );
21802 assert_eq!(
21803 wrap_with_prefix(
21804 "// ".to_string(),
21805 "// ".to_string(),
21806 "xx \nyy zz aa bb cc".to_string(),
21807 12,
21808 NonZeroU32::new(4).unwrap(),
21809 false,
21810 ),
21811 "// xx yy zz\n// aa bb cc"
21812 );
21813 assert_eq!(
21814 wrap_with_prefix(
21815 String::new(),
21816 String::new(),
21817 "这是什么 \n 钢笔".to_string(),
21818 3,
21819 NonZeroU32::new(4).unwrap(),
21820 false,
21821 ),
21822 "这是什\n么 钢\n笔"
21823 );
21824}
21825
21826pub trait CollaborationHub {
21827 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21828 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21829 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21830}
21831
21832impl CollaborationHub for Entity<Project> {
21833 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21834 self.read(cx).collaborators()
21835 }
21836
21837 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21838 self.read(cx).user_store().read(cx).participant_indices()
21839 }
21840
21841 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21842 let this = self.read(cx);
21843 let user_ids = this.collaborators().values().map(|c| c.user_id);
21844 this.user_store().read(cx).participant_names(user_ids, cx)
21845 }
21846}
21847
21848pub trait SemanticsProvider {
21849 fn hover(
21850 &self,
21851 buffer: &Entity<Buffer>,
21852 position: text::Anchor,
21853 cx: &mut App,
21854 ) -> Option<Task<Vec<project::Hover>>>;
21855
21856 fn inline_values(
21857 &self,
21858 buffer_handle: Entity<Buffer>,
21859 range: Range<text::Anchor>,
21860 cx: &mut App,
21861 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21862
21863 fn inlay_hints(
21864 &self,
21865 buffer_handle: Entity<Buffer>,
21866 range: Range<text::Anchor>,
21867 cx: &mut App,
21868 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21869
21870 fn resolve_inlay_hint(
21871 &self,
21872 hint: InlayHint,
21873 buffer_handle: Entity<Buffer>,
21874 server_id: LanguageServerId,
21875 cx: &mut App,
21876 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21877
21878 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21879
21880 fn document_highlights(
21881 &self,
21882 buffer: &Entity<Buffer>,
21883 position: text::Anchor,
21884 cx: &mut App,
21885 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21886
21887 fn definitions(
21888 &self,
21889 buffer: &Entity<Buffer>,
21890 position: text::Anchor,
21891 kind: GotoDefinitionKind,
21892 cx: &mut App,
21893 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21894
21895 fn range_for_rename(
21896 &self,
21897 buffer: &Entity<Buffer>,
21898 position: text::Anchor,
21899 cx: &mut App,
21900 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21901
21902 fn perform_rename(
21903 &self,
21904 buffer: &Entity<Buffer>,
21905 position: text::Anchor,
21906 new_name: String,
21907 cx: &mut App,
21908 ) -> Option<Task<Result<ProjectTransaction>>>;
21909}
21910
21911pub trait CompletionProvider {
21912 fn completions(
21913 &self,
21914 excerpt_id: ExcerptId,
21915 buffer: &Entity<Buffer>,
21916 buffer_position: text::Anchor,
21917 trigger: CompletionContext,
21918 window: &mut Window,
21919 cx: &mut Context<Editor>,
21920 ) -> Task<Result<Vec<CompletionResponse>>>;
21921
21922 fn resolve_completions(
21923 &self,
21924 _buffer: Entity<Buffer>,
21925 _completion_indices: Vec<usize>,
21926 _completions: Rc<RefCell<Box<[Completion]>>>,
21927 _cx: &mut Context<Editor>,
21928 ) -> Task<Result<bool>> {
21929 Task::ready(Ok(false))
21930 }
21931
21932 fn apply_additional_edits_for_completion(
21933 &self,
21934 _buffer: Entity<Buffer>,
21935 _completions: Rc<RefCell<Box<[Completion]>>>,
21936 _completion_index: usize,
21937 _push_to_history: bool,
21938 _cx: &mut Context<Editor>,
21939 ) -> Task<Result<Option<language::Transaction>>> {
21940 Task::ready(Ok(None))
21941 }
21942
21943 fn is_completion_trigger(
21944 &self,
21945 buffer: &Entity<Buffer>,
21946 position: language::Anchor,
21947 text: &str,
21948 trigger_in_words: bool,
21949 menu_is_open: bool,
21950 cx: &mut Context<Editor>,
21951 ) -> bool;
21952
21953 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21954
21955 fn sort_completions(&self) -> bool {
21956 true
21957 }
21958
21959 fn filter_completions(&self) -> bool {
21960 true
21961 }
21962}
21963
21964pub trait CodeActionProvider {
21965 fn id(&self) -> Arc<str>;
21966
21967 fn code_actions(
21968 &self,
21969 buffer: &Entity<Buffer>,
21970 range: Range<text::Anchor>,
21971 window: &mut Window,
21972 cx: &mut App,
21973 ) -> Task<Result<Vec<CodeAction>>>;
21974
21975 fn apply_code_action(
21976 &self,
21977 buffer_handle: Entity<Buffer>,
21978 action: CodeAction,
21979 excerpt_id: ExcerptId,
21980 push_to_history: bool,
21981 window: &mut Window,
21982 cx: &mut App,
21983 ) -> Task<Result<ProjectTransaction>>;
21984}
21985
21986impl CodeActionProvider for Entity<Project> {
21987 fn id(&self) -> Arc<str> {
21988 "project".into()
21989 }
21990
21991 fn code_actions(
21992 &self,
21993 buffer: &Entity<Buffer>,
21994 range: Range<text::Anchor>,
21995 _window: &mut Window,
21996 cx: &mut App,
21997 ) -> Task<Result<Vec<CodeAction>>> {
21998 self.update(cx, |project, cx| {
21999 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22000 let code_actions = project.code_actions(buffer, range, None, cx);
22001 cx.background_spawn(async move {
22002 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22003 Ok(code_lens_actions
22004 .context("code lens fetch")?
22005 .into_iter()
22006 .chain(code_actions.context("code action fetch")?)
22007 .collect())
22008 })
22009 })
22010 }
22011
22012 fn apply_code_action(
22013 &self,
22014 buffer_handle: Entity<Buffer>,
22015 action: CodeAction,
22016 _excerpt_id: ExcerptId,
22017 push_to_history: bool,
22018 _window: &mut Window,
22019 cx: &mut App,
22020 ) -> Task<Result<ProjectTransaction>> {
22021 self.update(cx, |project, cx| {
22022 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22023 })
22024 }
22025}
22026
22027fn snippet_completions(
22028 project: &Project,
22029 buffer: &Entity<Buffer>,
22030 buffer_position: text::Anchor,
22031 cx: &mut App,
22032) -> Task<Result<CompletionResponse>> {
22033 let languages = buffer.read(cx).languages_at(buffer_position);
22034 let snippet_store = project.snippets().read(cx);
22035
22036 let scopes: Vec<_> = languages
22037 .iter()
22038 .filter_map(|language| {
22039 let language_name = language.lsp_id();
22040 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22041
22042 if snippets.is_empty() {
22043 None
22044 } else {
22045 Some((language.default_scope(), snippets))
22046 }
22047 })
22048 .collect();
22049
22050 if scopes.is_empty() {
22051 return Task::ready(Ok(CompletionResponse {
22052 completions: vec![],
22053 is_incomplete: false,
22054 }));
22055 }
22056
22057 let snapshot = buffer.read(cx).text_snapshot();
22058 let chars: String = snapshot
22059 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22060 .collect();
22061 let executor = cx.background_executor().clone();
22062
22063 cx.background_spawn(async move {
22064 let mut is_incomplete = false;
22065 let mut completions: Vec<Completion> = Vec::new();
22066 for (scope, snippets) in scopes.into_iter() {
22067 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22068 let mut last_word = chars
22069 .chars()
22070 .take_while(|c| classifier.is_word(*c))
22071 .collect::<String>();
22072 last_word = last_word.chars().rev().collect();
22073
22074 if last_word.is_empty() {
22075 return Ok(CompletionResponse {
22076 completions: vec![],
22077 is_incomplete: true,
22078 });
22079 }
22080
22081 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22082 let to_lsp = |point: &text::Anchor| {
22083 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22084 point_to_lsp(end)
22085 };
22086 let lsp_end = to_lsp(&buffer_position);
22087
22088 let candidates = snippets
22089 .iter()
22090 .enumerate()
22091 .flat_map(|(ix, snippet)| {
22092 snippet
22093 .prefix
22094 .iter()
22095 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22096 })
22097 .collect::<Vec<StringMatchCandidate>>();
22098
22099 const MAX_RESULTS: usize = 100;
22100 let mut matches = fuzzy::match_strings(
22101 &candidates,
22102 &last_word,
22103 last_word.chars().any(|c| c.is_uppercase()),
22104 true,
22105 MAX_RESULTS,
22106 &Default::default(),
22107 executor.clone(),
22108 )
22109 .await;
22110
22111 if matches.len() >= MAX_RESULTS {
22112 is_incomplete = true;
22113 }
22114
22115 // Remove all candidates where the query's start does not match the start of any word in the candidate
22116 if let Some(query_start) = last_word.chars().next() {
22117 matches.retain(|string_match| {
22118 split_words(&string_match.string).any(|word| {
22119 // Check that the first codepoint of the word as lowercase matches the first
22120 // codepoint of the query as lowercase
22121 word.chars()
22122 .flat_map(|codepoint| codepoint.to_lowercase())
22123 .zip(query_start.to_lowercase())
22124 .all(|(word_cp, query_cp)| word_cp == query_cp)
22125 })
22126 });
22127 }
22128
22129 let matched_strings = matches
22130 .into_iter()
22131 .map(|m| m.string)
22132 .collect::<HashSet<_>>();
22133
22134 completions.extend(snippets.iter().filter_map(|snippet| {
22135 let matching_prefix = snippet
22136 .prefix
22137 .iter()
22138 .find(|prefix| matched_strings.contains(*prefix))?;
22139 let start = as_offset - last_word.len();
22140 let start = snapshot.anchor_before(start);
22141 let range = start..buffer_position;
22142 let lsp_start = to_lsp(&start);
22143 let lsp_range = lsp::Range {
22144 start: lsp_start,
22145 end: lsp_end,
22146 };
22147 Some(Completion {
22148 replace_range: range,
22149 new_text: snippet.body.clone(),
22150 source: CompletionSource::Lsp {
22151 insert_range: None,
22152 server_id: LanguageServerId(usize::MAX),
22153 resolved: true,
22154 lsp_completion: Box::new(lsp::CompletionItem {
22155 label: snippet.prefix.first().unwrap().clone(),
22156 kind: Some(CompletionItemKind::SNIPPET),
22157 label_details: snippet.description.as_ref().map(|description| {
22158 lsp::CompletionItemLabelDetails {
22159 detail: Some(description.clone()),
22160 description: None,
22161 }
22162 }),
22163 insert_text_format: Some(InsertTextFormat::SNIPPET),
22164 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22165 lsp::InsertReplaceEdit {
22166 new_text: snippet.body.clone(),
22167 insert: lsp_range,
22168 replace: lsp_range,
22169 },
22170 )),
22171 filter_text: Some(snippet.body.clone()),
22172 sort_text: Some(char::MAX.to_string()),
22173 ..lsp::CompletionItem::default()
22174 }),
22175 lsp_defaults: None,
22176 },
22177 label: CodeLabel {
22178 text: matching_prefix.clone(),
22179 runs: Vec::new(),
22180 filter_range: 0..matching_prefix.len(),
22181 },
22182 icon_path: None,
22183 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22184 single_line: snippet.name.clone().into(),
22185 plain_text: snippet
22186 .description
22187 .clone()
22188 .map(|description| description.into()),
22189 }),
22190 insert_text_mode: None,
22191 confirm: None,
22192 })
22193 }))
22194 }
22195
22196 Ok(CompletionResponse {
22197 completions,
22198 is_incomplete,
22199 })
22200 })
22201}
22202
22203impl CompletionProvider for Entity<Project> {
22204 fn completions(
22205 &self,
22206 _excerpt_id: ExcerptId,
22207 buffer: &Entity<Buffer>,
22208 buffer_position: text::Anchor,
22209 options: CompletionContext,
22210 _window: &mut Window,
22211 cx: &mut Context<Editor>,
22212 ) -> Task<Result<Vec<CompletionResponse>>> {
22213 self.update(cx, |project, cx| {
22214 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22215 let project_completions = project.completions(buffer, buffer_position, options, cx);
22216 cx.background_spawn(async move {
22217 let mut responses = project_completions.await?;
22218 let snippets = snippets.await?;
22219 if !snippets.completions.is_empty() {
22220 responses.push(snippets);
22221 }
22222 Ok(responses)
22223 })
22224 })
22225 }
22226
22227 fn resolve_completions(
22228 &self,
22229 buffer: Entity<Buffer>,
22230 completion_indices: Vec<usize>,
22231 completions: Rc<RefCell<Box<[Completion]>>>,
22232 cx: &mut Context<Editor>,
22233 ) -> Task<Result<bool>> {
22234 self.update(cx, |project, cx| {
22235 project.lsp_store().update(cx, |lsp_store, cx| {
22236 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22237 })
22238 })
22239 }
22240
22241 fn apply_additional_edits_for_completion(
22242 &self,
22243 buffer: Entity<Buffer>,
22244 completions: Rc<RefCell<Box<[Completion]>>>,
22245 completion_index: usize,
22246 push_to_history: bool,
22247 cx: &mut Context<Editor>,
22248 ) -> Task<Result<Option<language::Transaction>>> {
22249 self.update(cx, |project, cx| {
22250 project.lsp_store().update(cx, |lsp_store, cx| {
22251 lsp_store.apply_additional_edits_for_completion(
22252 buffer,
22253 completions,
22254 completion_index,
22255 push_to_history,
22256 cx,
22257 )
22258 })
22259 })
22260 }
22261
22262 fn is_completion_trigger(
22263 &self,
22264 buffer: &Entity<Buffer>,
22265 position: language::Anchor,
22266 text: &str,
22267 trigger_in_words: bool,
22268 menu_is_open: bool,
22269 cx: &mut Context<Editor>,
22270 ) -> bool {
22271 let mut chars = text.chars();
22272 let char = if let Some(char) = chars.next() {
22273 char
22274 } else {
22275 return false;
22276 };
22277 if chars.next().is_some() {
22278 return false;
22279 }
22280
22281 let buffer = buffer.read(cx);
22282 let snapshot = buffer.snapshot();
22283 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22284 return false;
22285 }
22286 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22287 if trigger_in_words && classifier.is_word(char) {
22288 return true;
22289 }
22290
22291 buffer.completion_triggers().contains(text)
22292 }
22293}
22294
22295impl SemanticsProvider for Entity<Project> {
22296 fn hover(
22297 &self,
22298 buffer: &Entity<Buffer>,
22299 position: text::Anchor,
22300 cx: &mut App,
22301 ) -> Option<Task<Vec<project::Hover>>> {
22302 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22303 }
22304
22305 fn document_highlights(
22306 &self,
22307 buffer: &Entity<Buffer>,
22308 position: text::Anchor,
22309 cx: &mut App,
22310 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22311 Some(self.update(cx, |project, cx| {
22312 project.document_highlights(buffer, position, cx)
22313 }))
22314 }
22315
22316 fn definitions(
22317 &self,
22318 buffer: &Entity<Buffer>,
22319 position: text::Anchor,
22320 kind: GotoDefinitionKind,
22321 cx: &mut App,
22322 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22323 Some(self.update(cx, |project, cx| match kind {
22324 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22325 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22326 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22327 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22328 }))
22329 }
22330
22331 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22332 self.update(cx, |project, cx| {
22333 if project
22334 .active_debug_session(cx)
22335 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22336 {
22337 return true;
22338 }
22339
22340 buffer.update(cx, |buffer, cx| {
22341 project.any_language_server_supports_inlay_hints(buffer, cx)
22342 })
22343 })
22344 }
22345
22346 fn inline_values(
22347 &self,
22348 buffer_handle: Entity<Buffer>,
22349 range: Range<text::Anchor>,
22350 cx: &mut App,
22351 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22352 self.update(cx, |project, cx| {
22353 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22354
22355 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22356 })
22357 }
22358
22359 fn inlay_hints(
22360 &self,
22361 buffer_handle: Entity<Buffer>,
22362 range: Range<text::Anchor>,
22363 cx: &mut App,
22364 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22365 Some(self.update(cx, |project, cx| {
22366 project.inlay_hints(buffer_handle, range, cx)
22367 }))
22368 }
22369
22370 fn resolve_inlay_hint(
22371 &self,
22372 hint: InlayHint,
22373 buffer_handle: Entity<Buffer>,
22374 server_id: LanguageServerId,
22375 cx: &mut App,
22376 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22377 Some(self.update(cx, |project, cx| {
22378 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22379 }))
22380 }
22381
22382 fn range_for_rename(
22383 &self,
22384 buffer: &Entity<Buffer>,
22385 position: text::Anchor,
22386 cx: &mut App,
22387 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22388 Some(self.update(cx, |project, cx| {
22389 let buffer = buffer.clone();
22390 let task = project.prepare_rename(buffer.clone(), position, cx);
22391 cx.spawn(async move |_, cx| {
22392 Ok(match task.await? {
22393 PrepareRenameResponse::Success(range) => Some(range),
22394 PrepareRenameResponse::InvalidPosition => None,
22395 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22396 // Fallback on using TreeSitter info to determine identifier range
22397 buffer.read_with(cx, |buffer, _| {
22398 let snapshot = buffer.snapshot();
22399 let (range, kind) = snapshot.surrounding_word(position, false);
22400 if kind != Some(CharKind::Word) {
22401 return None;
22402 }
22403 Some(
22404 snapshot.anchor_before(range.start)
22405 ..snapshot.anchor_after(range.end),
22406 )
22407 })?
22408 }
22409 })
22410 })
22411 }))
22412 }
22413
22414 fn perform_rename(
22415 &self,
22416 buffer: &Entity<Buffer>,
22417 position: text::Anchor,
22418 new_name: String,
22419 cx: &mut App,
22420 ) -> Option<Task<Result<ProjectTransaction>>> {
22421 Some(self.update(cx, |project, cx| {
22422 project.perform_rename(buffer.clone(), position, new_name, cx)
22423 }))
22424 }
22425}
22426
22427fn inlay_hint_settings(
22428 location: Anchor,
22429 snapshot: &MultiBufferSnapshot,
22430 cx: &mut Context<Editor>,
22431) -> InlayHintSettings {
22432 let file = snapshot.file_at(location);
22433 let language = snapshot.language_at(location).map(|l| l.name());
22434 language_settings(language, file, cx).inlay_hints
22435}
22436
22437fn consume_contiguous_rows(
22438 contiguous_row_selections: &mut Vec<Selection<Point>>,
22439 selection: &Selection<Point>,
22440 display_map: &DisplaySnapshot,
22441 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22442) -> (MultiBufferRow, MultiBufferRow) {
22443 contiguous_row_selections.push(selection.clone());
22444 let start_row = starting_row(selection, display_map);
22445 let mut end_row = ending_row(selection, display_map);
22446
22447 while let Some(next_selection) = selections.peek() {
22448 if next_selection.start.row <= end_row.0 {
22449 end_row = ending_row(next_selection, display_map);
22450 contiguous_row_selections.push(selections.next().unwrap().clone());
22451 } else {
22452 break;
22453 }
22454 }
22455 (start_row, end_row)
22456}
22457
22458fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22459 if selection.start.column > 0 {
22460 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22461 } else {
22462 MultiBufferRow(selection.start.row)
22463 }
22464}
22465
22466fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22467 if next_selection.end.column > 0 || next_selection.is_empty() {
22468 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22469 } else {
22470 MultiBufferRow(next_selection.end.row)
22471 }
22472}
22473
22474impl EditorSnapshot {
22475 pub fn remote_selections_in_range<'a>(
22476 &'a self,
22477 range: &'a Range<Anchor>,
22478 collaboration_hub: &dyn CollaborationHub,
22479 cx: &'a App,
22480 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22481 let participant_names = collaboration_hub.user_names(cx);
22482 let participant_indices = collaboration_hub.user_participant_indices(cx);
22483 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22484 let collaborators_by_replica_id = collaborators_by_peer_id
22485 .values()
22486 .map(|collaborator| (collaborator.replica_id, collaborator))
22487 .collect::<HashMap<_, _>>();
22488 self.buffer_snapshot
22489 .selections_in_range(range, false)
22490 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22491 if replica_id == AGENT_REPLICA_ID {
22492 Some(RemoteSelection {
22493 replica_id,
22494 selection,
22495 cursor_shape,
22496 line_mode,
22497 collaborator_id: CollaboratorId::Agent,
22498 user_name: Some("Agent".into()),
22499 color: cx.theme().players().agent(),
22500 })
22501 } else {
22502 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22503 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22504 let user_name = participant_names.get(&collaborator.user_id).cloned();
22505 Some(RemoteSelection {
22506 replica_id,
22507 selection,
22508 cursor_shape,
22509 line_mode,
22510 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22511 user_name,
22512 color: if let Some(index) = participant_index {
22513 cx.theme().players().color_for_participant(index.0)
22514 } else {
22515 cx.theme().players().absent()
22516 },
22517 })
22518 }
22519 })
22520 }
22521
22522 pub fn hunks_for_ranges(
22523 &self,
22524 ranges: impl IntoIterator<Item = Range<Point>>,
22525 ) -> Vec<MultiBufferDiffHunk> {
22526 let mut hunks = Vec::new();
22527 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22528 HashMap::default();
22529 for query_range in ranges {
22530 let query_rows =
22531 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22532 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22533 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22534 ) {
22535 // Include deleted hunks that are adjacent to the query range, because
22536 // otherwise they would be missed.
22537 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22538 if hunk.status().is_deleted() {
22539 intersects_range |= hunk.row_range.start == query_rows.end;
22540 intersects_range |= hunk.row_range.end == query_rows.start;
22541 }
22542 if intersects_range {
22543 if !processed_buffer_rows
22544 .entry(hunk.buffer_id)
22545 .or_default()
22546 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22547 {
22548 continue;
22549 }
22550 hunks.push(hunk);
22551 }
22552 }
22553 }
22554
22555 hunks
22556 }
22557
22558 fn display_diff_hunks_for_rows<'a>(
22559 &'a self,
22560 display_rows: Range<DisplayRow>,
22561 folded_buffers: &'a HashSet<BufferId>,
22562 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22563 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22564 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22565
22566 self.buffer_snapshot
22567 .diff_hunks_in_range(buffer_start..buffer_end)
22568 .filter_map(|hunk| {
22569 if folded_buffers.contains(&hunk.buffer_id) {
22570 return None;
22571 }
22572
22573 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22574 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22575
22576 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22577 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22578
22579 let display_hunk = if hunk_display_start.column() != 0 {
22580 DisplayDiffHunk::Folded {
22581 display_row: hunk_display_start.row(),
22582 }
22583 } else {
22584 let mut end_row = hunk_display_end.row();
22585 if hunk_display_end.column() > 0 {
22586 end_row.0 += 1;
22587 }
22588 let is_created_file = hunk.is_created_file();
22589 DisplayDiffHunk::Unfolded {
22590 status: hunk.status(),
22591 diff_base_byte_range: hunk.diff_base_byte_range,
22592 display_row_range: hunk_display_start.row()..end_row,
22593 multi_buffer_range: Anchor::range_in_buffer(
22594 hunk.excerpt_id,
22595 hunk.buffer_id,
22596 hunk.buffer_range,
22597 ),
22598 is_created_file,
22599 }
22600 };
22601
22602 Some(display_hunk)
22603 })
22604 }
22605
22606 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22607 self.display_snapshot.buffer_snapshot.language_at(position)
22608 }
22609
22610 pub fn is_focused(&self) -> bool {
22611 self.is_focused
22612 }
22613
22614 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22615 self.placeholder_text.as_ref()
22616 }
22617
22618 pub fn scroll_position(&self) -> gpui::Point<f32> {
22619 self.scroll_anchor.scroll_position(&self.display_snapshot)
22620 }
22621
22622 fn gutter_dimensions(
22623 &self,
22624 font_id: FontId,
22625 font_size: Pixels,
22626 max_line_number_width: Pixels,
22627 cx: &App,
22628 ) -> Option<GutterDimensions> {
22629 if !self.show_gutter {
22630 return None;
22631 }
22632
22633 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22634 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22635
22636 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22637 matches!(
22638 ProjectSettings::get_global(cx).git.git_gutter,
22639 Some(GitGutterSetting::TrackedFiles)
22640 )
22641 });
22642 let gutter_settings = EditorSettings::get_global(cx).gutter;
22643 let show_line_numbers = self
22644 .show_line_numbers
22645 .unwrap_or(gutter_settings.line_numbers);
22646 let line_gutter_width = if show_line_numbers {
22647 // Avoid flicker-like gutter resizes when the line number gains another digit by
22648 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22649 let min_width_for_number_on_gutter =
22650 ch_advance * gutter_settings.min_line_number_digits as f32;
22651 max_line_number_width.max(min_width_for_number_on_gutter)
22652 } else {
22653 0.0.into()
22654 };
22655
22656 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22657 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22658
22659 let git_blame_entries_width =
22660 self.git_blame_gutter_max_author_length
22661 .map(|max_author_length| {
22662 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22663 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22664
22665 /// The number of characters to dedicate to gaps and margins.
22666 const SPACING_WIDTH: usize = 4;
22667
22668 let max_char_count = max_author_length.min(renderer.max_author_length())
22669 + ::git::SHORT_SHA_LENGTH
22670 + MAX_RELATIVE_TIMESTAMP.len()
22671 + SPACING_WIDTH;
22672
22673 ch_advance * max_char_count
22674 });
22675
22676 let is_singleton = self.buffer_snapshot.is_singleton();
22677
22678 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22679 left_padding += if !is_singleton {
22680 ch_width * 4.0
22681 } else if show_runnables || show_breakpoints {
22682 ch_width * 3.0
22683 } else if show_git_gutter && show_line_numbers {
22684 ch_width * 2.0
22685 } else if show_git_gutter || show_line_numbers {
22686 ch_width
22687 } else {
22688 px(0.)
22689 };
22690
22691 let shows_folds = is_singleton && gutter_settings.folds;
22692
22693 let right_padding = if shows_folds && show_line_numbers {
22694 ch_width * 4.0
22695 } else if shows_folds || (!is_singleton && show_line_numbers) {
22696 ch_width * 3.0
22697 } else if show_line_numbers {
22698 ch_width
22699 } else {
22700 px(0.)
22701 };
22702
22703 Some(GutterDimensions {
22704 left_padding,
22705 right_padding,
22706 width: line_gutter_width + left_padding + right_padding,
22707 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22708 git_blame_entries_width,
22709 })
22710 }
22711
22712 pub fn render_crease_toggle(
22713 &self,
22714 buffer_row: MultiBufferRow,
22715 row_contains_cursor: bool,
22716 editor: Entity<Editor>,
22717 window: &mut Window,
22718 cx: &mut App,
22719 ) -> Option<AnyElement> {
22720 let folded = self.is_line_folded(buffer_row);
22721 let mut is_foldable = false;
22722
22723 if let Some(crease) = self
22724 .crease_snapshot
22725 .query_row(buffer_row, &self.buffer_snapshot)
22726 {
22727 is_foldable = true;
22728 match crease {
22729 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22730 if let Some(render_toggle) = render_toggle {
22731 let toggle_callback =
22732 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22733 if folded {
22734 editor.update(cx, |editor, cx| {
22735 editor.fold_at(buffer_row, window, cx)
22736 });
22737 } else {
22738 editor.update(cx, |editor, cx| {
22739 editor.unfold_at(buffer_row, window, cx)
22740 });
22741 }
22742 });
22743 return Some((render_toggle)(
22744 buffer_row,
22745 folded,
22746 toggle_callback,
22747 window,
22748 cx,
22749 ));
22750 }
22751 }
22752 }
22753 }
22754
22755 is_foldable |= self.starts_indent(buffer_row);
22756
22757 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22758 Some(
22759 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22760 .toggle_state(folded)
22761 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22762 if folded {
22763 this.unfold_at(buffer_row, window, cx);
22764 } else {
22765 this.fold_at(buffer_row, window, cx);
22766 }
22767 }))
22768 .into_any_element(),
22769 )
22770 } else {
22771 None
22772 }
22773 }
22774
22775 pub fn render_crease_trailer(
22776 &self,
22777 buffer_row: MultiBufferRow,
22778 window: &mut Window,
22779 cx: &mut App,
22780 ) -> Option<AnyElement> {
22781 let folded = self.is_line_folded(buffer_row);
22782 if let Crease::Inline { render_trailer, .. } = self
22783 .crease_snapshot
22784 .query_row(buffer_row, &self.buffer_snapshot)?
22785 {
22786 let render_trailer = render_trailer.as_ref()?;
22787 Some(render_trailer(buffer_row, folded, window, cx))
22788 } else {
22789 None
22790 }
22791 }
22792}
22793
22794impl Deref for EditorSnapshot {
22795 type Target = DisplaySnapshot;
22796
22797 fn deref(&self) -> &Self::Target {
22798 &self.display_snapshot
22799 }
22800}
22801
22802#[derive(Clone, Debug, PartialEq, Eq)]
22803pub enum EditorEvent {
22804 InputIgnored {
22805 text: Arc<str>,
22806 },
22807 InputHandled {
22808 utf16_range_to_replace: Option<Range<isize>>,
22809 text: Arc<str>,
22810 },
22811 ExcerptsAdded {
22812 buffer: Entity<Buffer>,
22813 predecessor: ExcerptId,
22814 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22815 },
22816 ExcerptsRemoved {
22817 ids: Vec<ExcerptId>,
22818 removed_buffer_ids: Vec<BufferId>,
22819 },
22820 BufferFoldToggled {
22821 ids: Vec<ExcerptId>,
22822 folded: bool,
22823 },
22824 ExcerptsEdited {
22825 ids: Vec<ExcerptId>,
22826 },
22827 ExcerptsExpanded {
22828 ids: Vec<ExcerptId>,
22829 },
22830 BufferEdited,
22831 Edited {
22832 transaction_id: clock::Lamport,
22833 },
22834 Reparsed(BufferId),
22835 Focused,
22836 FocusedIn,
22837 Blurred,
22838 DirtyChanged,
22839 Saved,
22840 TitleChanged,
22841 DiffBaseChanged,
22842 SelectionsChanged {
22843 local: bool,
22844 },
22845 ScrollPositionChanged {
22846 local: bool,
22847 autoscroll: bool,
22848 },
22849 Closed,
22850 TransactionUndone {
22851 transaction_id: clock::Lamport,
22852 },
22853 TransactionBegun {
22854 transaction_id: clock::Lamport,
22855 },
22856 Reloaded,
22857 CursorShapeChanged,
22858 BreadcrumbsChanged,
22859 PushedToNavHistory {
22860 anchor: Anchor,
22861 is_deactivate: bool,
22862 },
22863}
22864
22865impl EventEmitter<EditorEvent> for Editor {}
22866
22867impl Focusable for Editor {
22868 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22869 self.focus_handle.clone()
22870 }
22871}
22872
22873impl Render for Editor {
22874 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22875 let settings = ThemeSettings::get_global(cx);
22876
22877 let mut text_style = match self.mode {
22878 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22879 color: cx.theme().colors().editor_foreground,
22880 font_family: settings.ui_font.family.clone(),
22881 font_features: settings.ui_font.features.clone(),
22882 font_fallbacks: settings.ui_font.fallbacks.clone(),
22883 font_size: rems(0.875).into(),
22884 font_weight: settings.ui_font.weight,
22885 line_height: relative(settings.buffer_line_height.value()),
22886 ..Default::default()
22887 },
22888 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22889 color: cx.theme().colors().editor_foreground,
22890 font_family: settings.buffer_font.family.clone(),
22891 font_features: settings.buffer_font.features.clone(),
22892 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22893 font_size: settings.buffer_font_size(cx).into(),
22894 font_weight: settings.buffer_font.weight,
22895 line_height: relative(settings.buffer_line_height.value()),
22896 ..Default::default()
22897 },
22898 };
22899 if let Some(text_style_refinement) = &self.text_style_refinement {
22900 text_style.refine(text_style_refinement)
22901 }
22902
22903 let background = match self.mode {
22904 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22905 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22906 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22907 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22908 };
22909
22910 EditorElement::new(
22911 &cx.entity(),
22912 EditorStyle {
22913 background,
22914 border: cx.theme().colors().border,
22915 local_player: cx.theme().players().local(),
22916 text: text_style,
22917 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22918 syntax: cx.theme().syntax().clone(),
22919 status: cx.theme().status().clone(),
22920 inlay_hints_style: make_inlay_hints_style(cx),
22921 edit_prediction_styles: make_suggestion_styles(cx),
22922 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22923 show_underlines: self.diagnostics_enabled(),
22924 },
22925 )
22926 }
22927}
22928
22929impl EntityInputHandler for Editor {
22930 fn text_for_range(
22931 &mut self,
22932 range_utf16: Range<usize>,
22933 adjusted_range: &mut Option<Range<usize>>,
22934 _: &mut Window,
22935 cx: &mut Context<Self>,
22936 ) -> Option<String> {
22937 let snapshot = self.buffer.read(cx).read(cx);
22938 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22939 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22940 if (start.0..end.0) != range_utf16 {
22941 adjusted_range.replace(start.0..end.0);
22942 }
22943 Some(snapshot.text_for_range(start..end).collect())
22944 }
22945
22946 fn selected_text_range(
22947 &mut self,
22948 ignore_disabled_input: bool,
22949 _: &mut Window,
22950 cx: &mut Context<Self>,
22951 ) -> Option<UTF16Selection> {
22952 // Prevent the IME menu from appearing when holding down an alphabetic key
22953 // while input is disabled.
22954 if !ignore_disabled_input && !self.input_enabled {
22955 return None;
22956 }
22957
22958 let selection = self.selections.newest::<OffsetUtf16>(cx);
22959 let range = selection.range();
22960
22961 Some(UTF16Selection {
22962 range: range.start.0..range.end.0,
22963 reversed: selection.reversed,
22964 })
22965 }
22966
22967 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22968 let snapshot = self.buffer.read(cx).read(cx);
22969 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22970 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22971 }
22972
22973 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22974 self.clear_highlights::<InputComposition>(cx);
22975 self.ime_transaction.take();
22976 }
22977
22978 fn replace_text_in_range(
22979 &mut self,
22980 range_utf16: Option<Range<usize>>,
22981 text: &str,
22982 window: &mut Window,
22983 cx: &mut Context<Self>,
22984 ) {
22985 if !self.input_enabled {
22986 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22987 return;
22988 }
22989
22990 self.transact(window, cx, |this, window, cx| {
22991 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22992 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22993 Some(this.selection_replacement_ranges(range_utf16, cx))
22994 } else {
22995 this.marked_text_ranges(cx)
22996 };
22997
22998 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22999 let newest_selection_id = this.selections.newest_anchor().id;
23000 this.selections
23001 .all::<OffsetUtf16>(cx)
23002 .iter()
23003 .zip(ranges_to_replace.iter())
23004 .find_map(|(selection, range)| {
23005 if selection.id == newest_selection_id {
23006 Some(
23007 (range.start.0 as isize - selection.head().0 as isize)
23008 ..(range.end.0 as isize - selection.head().0 as isize),
23009 )
23010 } else {
23011 None
23012 }
23013 })
23014 });
23015
23016 cx.emit(EditorEvent::InputHandled {
23017 utf16_range_to_replace: range_to_replace,
23018 text: text.into(),
23019 });
23020
23021 if let Some(new_selected_ranges) = new_selected_ranges {
23022 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23023 selections.select_ranges(new_selected_ranges)
23024 });
23025 this.backspace(&Default::default(), window, cx);
23026 }
23027
23028 this.handle_input(text, window, cx);
23029 });
23030
23031 if let Some(transaction) = self.ime_transaction {
23032 self.buffer.update(cx, |buffer, cx| {
23033 buffer.group_until_transaction(transaction, cx);
23034 });
23035 }
23036
23037 self.unmark_text(window, cx);
23038 }
23039
23040 fn replace_and_mark_text_in_range(
23041 &mut self,
23042 range_utf16: Option<Range<usize>>,
23043 text: &str,
23044 new_selected_range_utf16: Option<Range<usize>>,
23045 window: &mut Window,
23046 cx: &mut Context<Self>,
23047 ) {
23048 if !self.input_enabled {
23049 return;
23050 }
23051
23052 let transaction = self.transact(window, cx, |this, window, cx| {
23053 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23054 let snapshot = this.buffer.read(cx).read(cx);
23055 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23056 for marked_range in &mut marked_ranges {
23057 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23058 marked_range.start.0 += relative_range_utf16.start;
23059 marked_range.start =
23060 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23061 marked_range.end =
23062 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23063 }
23064 }
23065 Some(marked_ranges)
23066 } else if let Some(range_utf16) = range_utf16 {
23067 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23068 Some(this.selection_replacement_ranges(range_utf16, cx))
23069 } else {
23070 None
23071 };
23072
23073 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23074 let newest_selection_id = this.selections.newest_anchor().id;
23075 this.selections
23076 .all::<OffsetUtf16>(cx)
23077 .iter()
23078 .zip(ranges_to_replace.iter())
23079 .find_map(|(selection, range)| {
23080 if selection.id == newest_selection_id {
23081 Some(
23082 (range.start.0 as isize - selection.head().0 as isize)
23083 ..(range.end.0 as isize - selection.head().0 as isize),
23084 )
23085 } else {
23086 None
23087 }
23088 })
23089 });
23090
23091 cx.emit(EditorEvent::InputHandled {
23092 utf16_range_to_replace: range_to_replace,
23093 text: text.into(),
23094 });
23095
23096 if let Some(ranges) = ranges_to_replace {
23097 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23098 s.select_ranges(ranges)
23099 });
23100 }
23101
23102 let marked_ranges = {
23103 let snapshot = this.buffer.read(cx).read(cx);
23104 this.selections
23105 .disjoint_anchors()
23106 .iter()
23107 .map(|selection| {
23108 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23109 })
23110 .collect::<Vec<_>>()
23111 };
23112
23113 if text.is_empty() {
23114 this.unmark_text(window, cx);
23115 } else {
23116 this.highlight_text::<InputComposition>(
23117 marked_ranges.clone(),
23118 HighlightStyle {
23119 underline: Some(UnderlineStyle {
23120 thickness: px(1.),
23121 color: None,
23122 wavy: false,
23123 }),
23124 ..Default::default()
23125 },
23126 cx,
23127 );
23128 }
23129
23130 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23131 let use_autoclose = this.use_autoclose;
23132 let use_auto_surround = this.use_auto_surround;
23133 this.set_use_autoclose(false);
23134 this.set_use_auto_surround(false);
23135 this.handle_input(text, window, cx);
23136 this.set_use_autoclose(use_autoclose);
23137 this.set_use_auto_surround(use_auto_surround);
23138
23139 if let Some(new_selected_range) = new_selected_range_utf16 {
23140 let snapshot = this.buffer.read(cx).read(cx);
23141 let new_selected_ranges = marked_ranges
23142 .into_iter()
23143 .map(|marked_range| {
23144 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23145 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23146 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23147 snapshot.clip_offset_utf16(new_start, Bias::Left)
23148 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23149 })
23150 .collect::<Vec<_>>();
23151
23152 drop(snapshot);
23153 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23154 selections.select_ranges(new_selected_ranges)
23155 });
23156 }
23157 });
23158
23159 self.ime_transaction = self.ime_transaction.or(transaction);
23160 if let Some(transaction) = self.ime_transaction {
23161 self.buffer.update(cx, |buffer, cx| {
23162 buffer.group_until_transaction(transaction, cx);
23163 });
23164 }
23165
23166 if self.text_highlights::<InputComposition>(cx).is_none() {
23167 self.ime_transaction.take();
23168 }
23169 }
23170
23171 fn bounds_for_range(
23172 &mut self,
23173 range_utf16: Range<usize>,
23174 element_bounds: gpui::Bounds<Pixels>,
23175 window: &mut Window,
23176 cx: &mut Context<Self>,
23177 ) -> Option<gpui::Bounds<Pixels>> {
23178 let text_layout_details = self.text_layout_details(window);
23179 let CharacterDimensions {
23180 em_width,
23181 em_advance,
23182 line_height,
23183 } = self.character_dimensions(window);
23184
23185 let snapshot = self.snapshot(window, cx);
23186 let scroll_position = snapshot.scroll_position();
23187 let scroll_left = scroll_position.x * em_advance;
23188
23189 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23190 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23191 + self.gutter_dimensions.full_width();
23192 let y = line_height * (start.row().as_f32() - scroll_position.y);
23193
23194 Some(Bounds {
23195 origin: element_bounds.origin + point(x, y),
23196 size: size(em_width, line_height),
23197 })
23198 }
23199
23200 fn character_index_for_point(
23201 &mut self,
23202 point: gpui::Point<Pixels>,
23203 _window: &mut Window,
23204 _cx: &mut Context<Self>,
23205 ) -> Option<usize> {
23206 let position_map = self.last_position_map.as_ref()?;
23207 if !position_map.text_hitbox.contains(&point) {
23208 return None;
23209 }
23210 let display_point = position_map.point_for_position(point).previous_valid;
23211 let anchor = position_map
23212 .snapshot
23213 .display_point_to_anchor(display_point, Bias::Left);
23214 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23215 Some(utf16_offset.0)
23216 }
23217}
23218
23219trait SelectionExt {
23220 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23221 fn spanned_rows(
23222 &self,
23223 include_end_if_at_line_start: bool,
23224 map: &DisplaySnapshot,
23225 ) -> Range<MultiBufferRow>;
23226}
23227
23228impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23229 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23230 let start = self
23231 .start
23232 .to_point(&map.buffer_snapshot)
23233 .to_display_point(map);
23234 let end = self
23235 .end
23236 .to_point(&map.buffer_snapshot)
23237 .to_display_point(map);
23238 if self.reversed {
23239 end..start
23240 } else {
23241 start..end
23242 }
23243 }
23244
23245 fn spanned_rows(
23246 &self,
23247 include_end_if_at_line_start: bool,
23248 map: &DisplaySnapshot,
23249 ) -> Range<MultiBufferRow> {
23250 let start = self.start.to_point(&map.buffer_snapshot);
23251 let mut end = self.end.to_point(&map.buffer_snapshot);
23252 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23253 end.row -= 1;
23254 }
23255
23256 let buffer_start = map.prev_line_boundary(start).0;
23257 let buffer_end = map.next_line_boundary(end).0;
23258 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23259 }
23260}
23261
23262impl<T: InvalidationRegion> InvalidationStack<T> {
23263 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23264 where
23265 S: Clone + ToOffset,
23266 {
23267 while let Some(region) = self.last() {
23268 let all_selections_inside_invalidation_ranges =
23269 if selections.len() == region.ranges().len() {
23270 selections
23271 .iter()
23272 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23273 .all(|(selection, invalidation_range)| {
23274 let head = selection.head().to_offset(buffer);
23275 invalidation_range.start <= head && invalidation_range.end >= head
23276 })
23277 } else {
23278 false
23279 };
23280
23281 if all_selections_inside_invalidation_ranges {
23282 break;
23283 } else {
23284 self.pop();
23285 }
23286 }
23287 }
23288}
23289
23290impl<T> Default for InvalidationStack<T> {
23291 fn default() -> Self {
23292 Self(Default::default())
23293 }
23294}
23295
23296impl<T> Deref for InvalidationStack<T> {
23297 type Target = Vec<T>;
23298
23299 fn deref(&self) -> &Self::Target {
23300 &self.0
23301 }
23302}
23303
23304impl<T> DerefMut for InvalidationStack<T> {
23305 fn deref_mut(&mut self) -> &mut Self::Target {
23306 &mut self.0
23307 }
23308}
23309
23310impl InvalidationRegion for SnippetState {
23311 fn ranges(&self) -> &[Range<Anchor>] {
23312 &self.ranges[self.active_index]
23313 }
23314}
23315
23316fn edit_prediction_edit_text(
23317 current_snapshot: &BufferSnapshot,
23318 edits: &[(Range<Anchor>, String)],
23319 edit_preview: &EditPreview,
23320 include_deletions: bool,
23321 cx: &App,
23322) -> HighlightedText {
23323 let edits = edits
23324 .iter()
23325 .map(|(anchor, text)| {
23326 (
23327 anchor.start.text_anchor..anchor.end.text_anchor,
23328 text.clone(),
23329 )
23330 })
23331 .collect::<Vec<_>>();
23332
23333 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23334}
23335
23336fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23337 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23338 // Just show the raw edit text with basic styling
23339 let mut text = String::new();
23340 let mut highlights = Vec::new();
23341
23342 let insertion_highlight_style = HighlightStyle {
23343 color: Some(cx.theme().colors().text),
23344 ..Default::default()
23345 };
23346
23347 for (_, edit_text) in edits {
23348 let start_offset = text.len();
23349 text.push_str(edit_text);
23350 let end_offset = text.len();
23351
23352 if start_offset < end_offset {
23353 highlights.push((start_offset..end_offset, insertion_highlight_style));
23354 }
23355 }
23356
23357 HighlightedText {
23358 text: text.into(),
23359 highlights,
23360 }
23361}
23362
23363pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23364 match severity {
23365 lsp::DiagnosticSeverity::ERROR => colors.error,
23366 lsp::DiagnosticSeverity::WARNING => colors.warning,
23367 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23368 lsp::DiagnosticSeverity::HINT => colors.info,
23369 _ => colors.ignored,
23370 }
23371}
23372
23373pub fn styled_runs_for_code_label<'a>(
23374 label: &'a CodeLabel,
23375 syntax_theme: &'a theme::SyntaxTheme,
23376) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23377 let fade_out = HighlightStyle {
23378 fade_out: Some(0.35),
23379 ..Default::default()
23380 };
23381
23382 let mut prev_end = label.filter_range.end;
23383 label
23384 .runs
23385 .iter()
23386 .enumerate()
23387 .flat_map(move |(ix, (range, highlight_id))| {
23388 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23389 style
23390 } else {
23391 return Default::default();
23392 };
23393 let mut muted_style = style;
23394 muted_style.highlight(fade_out);
23395
23396 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23397 if range.start >= label.filter_range.end {
23398 if range.start > prev_end {
23399 runs.push((prev_end..range.start, fade_out));
23400 }
23401 runs.push((range.clone(), muted_style));
23402 } else if range.end <= label.filter_range.end {
23403 runs.push((range.clone(), style));
23404 } else {
23405 runs.push((range.start..label.filter_range.end, style));
23406 runs.push((label.filter_range.end..range.end, muted_style));
23407 }
23408 prev_end = cmp::max(prev_end, range.end);
23409
23410 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23411 runs.push((prev_end..label.text.len(), fade_out));
23412 }
23413
23414 runs
23415 })
23416}
23417
23418pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23419 let mut prev_index = 0;
23420 let mut prev_codepoint: Option<char> = None;
23421 text.char_indices()
23422 .chain([(text.len(), '\0')])
23423 .filter_map(move |(index, codepoint)| {
23424 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23425 let is_boundary = index == text.len()
23426 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23427 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23428 if is_boundary {
23429 let chunk = &text[prev_index..index];
23430 prev_index = index;
23431 Some(chunk)
23432 } else {
23433 None
23434 }
23435 })
23436}
23437
23438pub trait RangeToAnchorExt: Sized {
23439 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23440
23441 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23442 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23443 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23444 }
23445}
23446
23447impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23448 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23449 let start_offset = self.start.to_offset(snapshot);
23450 let end_offset = self.end.to_offset(snapshot);
23451 if start_offset == end_offset {
23452 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23453 } else {
23454 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23455 }
23456 }
23457}
23458
23459pub trait RowExt {
23460 fn as_f32(&self) -> f32;
23461
23462 fn next_row(&self) -> Self;
23463
23464 fn previous_row(&self) -> Self;
23465
23466 fn minus(&self, other: Self) -> u32;
23467}
23468
23469impl RowExt for DisplayRow {
23470 fn as_f32(&self) -> f32 {
23471 self.0 as f32
23472 }
23473
23474 fn next_row(&self) -> Self {
23475 Self(self.0 + 1)
23476 }
23477
23478 fn previous_row(&self) -> Self {
23479 Self(self.0.saturating_sub(1))
23480 }
23481
23482 fn minus(&self, other: Self) -> u32 {
23483 self.0 - other.0
23484 }
23485}
23486
23487impl RowExt for MultiBufferRow {
23488 fn as_f32(&self) -> f32 {
23489 self.0 as f32
23490 }
23491
23492 fn next_row(&self) -> Self {
23493 Self(self.0 + 1)
23494 }
23495
23496 fn previous_row(&self) -> Self {
23497 Self(self.0.saturating_sub(1))
23498 }
23499
23500 fn minus(&self, other: Self) -> u32 {
23501 self.0 - other.0
23502 }
23503}
23504
23505trait RowRangeExt {
23506 type Row;
23507
23508 fn len(&self) -> usize;
23509
23510 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23511}
23512
23513impl RowRangeExt for Range<MultiBufferRow> {
23514 type Row = MultiBufferRow;
23515
23516 fn len(&self) -> usize {
23517 (self.end.0 - self.start.0) as usize
23518 }
23519
23520 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23521 (self.start.0..self.end.0).map(MultiBufferRow)
23522 }
23523}
23524
23525impl RowRangeExt for Range<DisplayRow> {
23526 type Row = DisplayRow;
23527
23528 fn len(&self) -> usize {
23529 (self.end.0 - self.start.0) as usize
23530 }
23531
23532 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23533 (self.start.0..self.end.0).map(DisplayRow)
23534 }
23535}
23536
23537/// If select range has more than one line, we
23538/// just point the cursor to range.start.
23539fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23540 if range.start.row == range.end.row {
23541 range
23542 } else {
23543 range.start..range.start
23544 }
23545}
23546pub struct KillRing(ClipboardItem);
23547impl Global for KillRing {}
23548
23549const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23550
23551enum BreakpointPromptEditAction {
23552 Log,
23553 Condition,
23554 HitCondition,
23555}
23556
23557struct BreakpointPromptEditor {
23558 pub(crate) prompt: Entity<Editor>,
23559 editor: WeakEntity<Editor>,
23560 breakpoint_anchor: Anchor,
23561 breakpoint: Breakpoint,
23562 edit_action: BreakpointPromptEditAction,
23563 block_ids: HashSet<CustomBlockId>,
23564 editor_margins: Arc<Mutex<EditorMargins>>,
23565 _subscriptions: Vec<Subscription>,
23566}
23567
23568impl BreakpointPromptEditor {
23569 const MAX_LINES: u8 = 4;
23570
23571 fn new(
23572 editor: WeakEntity<Editor>,
23573 breakpoint_anchor: Anchor,
23574 breakpoint: Breakpoint,
23575 edit_action: BreakpointPromptEditAction,
23576 window: &mut Window,
23577 cx: &mut Context<Self>,
23578 ) -> Self {
23579 let base_text = match edit_action {
23580 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23581 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23582 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23583 }
23584 .map(|msg| msg.to_string())
23585 .unwrap_or_default();
23586
23587 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23588 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23589
23590 let prompt = cx.new(|cx| {
23591 let mut prompt = Editor::new(
23592 EditorMode::AutoHeight {
23593 min_lines: 1,
23594 max_lines: Some(Self::MAX_LINES as usize),
23595 },
23596 buffer,
23597 None,
23598 window,
23599 cx,
23600 );
23601 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23602 prompt.set_show_cursor_when_unfocused(false, cx);
23603 prompt.set_placeholder_text(
23604 match edit_action {
23605 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23606 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23607 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23608 },
23609 cx,
23610 );
23611
23612 prompt
23613 });
23614
23615 Self {
23616 prompt,
23617 editor,
23618 breakpoint_anchor,
23619 breakpoint,
23620 edit_action,
23621 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23622 block_ids: Default::default(),
23623 _subscriptions: vec![],
23624 }
23625 }
23626
23627 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23628 self.block_ids.extend(block_ids)
23629 }
23630
23631 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23632 if let Some(editor) = self.editor.upgrade() {
23633 let message = self
23634 .prompt
23635 .read(cx)
23636 .buffer
23637 .read(cx)
23638 .as_singleton()
23639 .expect("A multi buffer in breakpoint prompt isn't possible")
23640 .read(cx)
23641 .as_rope()
23642 .to_string();
23643
23644 editor.update(cx, |editor, cx| {
23645 editor.edit_breakpoint_at_anchor(
23646 self.breakpoint_anchor,
23647 self.breakpoint.clone(),
23648 match self.edit_action {
23649 BreakpointPromptEditAction::Log => {
23650 BreakpointEditAction::EditLogMessage(message.into())
23651 }
23652 BreakpointPromptEditAction::Condition => {
23653 BreakpointEditAction::EditCondition(message.into())
23654 }
23655 BreakpointPromptEditAction::HitCondition => {
23656 BreakpointEditAction::EditHitCondition(message.into())
23657 }
23658 },
23659 cx,
23660 );
23661
23662 editor.remove_blocks(self.block_ids.clone(), None, cx);
23663 cx.focus_self(window);
23664 });
23665 }
23666 }
23667
23668 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23669 self.editor
23670 .update(cx, |editor, cx| {
23671 editor.remove_blocks(self.block_ids.clone(), None, cx);
23672 window.focus(&editor.focus_handle);
23673 })
23674 .log_err();
23675 }
23676
23677 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23678 let settings = ThemeSettings::get_global(cx);
23679 let text_style = TextStyle {
23680 color: if self.prompt.read(cx).read_only(cx) {
23681 cx.theme().colors().text_disabled
23682 } else {
23683 cx.theme().colors().text
23684 },
23685 font_family: settings.buffer_font.family.clone(),
23686 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23687 font_size: settings.buffer_font_size(cx).into(),
23688 font_weight: settings.buffer_font.weight,
23689 line_height: relative(settings.buffer_line_height.value()),
23690 ..Default::default()
23691 };
23692 EditorElement::new(
23693 &self.prompt,
23694 EditorStyle {
23695 background: cx.theme().colors().editor_background,
23696 local_player: cx.theme().players().local(),
23697 text: text_style,
23698 ..Default::default()
23699 },
23700 )
23701 }
23702}
23703
23704impl Render for BreakpointPromptEditor {
23705 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23706 let editor_margins = *self.editor_margins.lock();
23707 let gutter_dimensions = editor_margins.gutter;
23708 h_flex()
23709 .key_context("Editor")
23710 .bg(cx.theme().colors().editor_background)
23711 .border_y_1()
23712 .border_color(cx.theme().status().info_border)
23713 .size_full()
23714 .py(window.line_height() / 2.5)
23715 .on_action(cx.listener(Self::confirm))
23716 .on_action(cx.listener(Self::cancel))
23717 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23718 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23719 }
23720}
23721
23722impl Focusable for BreakpointPromptEditor {
23723 fn focus_handle(&self, cx: &App) -> FocusHandle {
23724 self.prompt.focus_handle(cx)
23725 }
23726}
23727
23728fn all_edits_insertions_or_deletions(
23729 edits: &Vec<(Range<Anchor>, String)>,
23730 snapshot: &MultiBufferSnapshot,
23731) -> bool {
23732 let mut all_insertions = true;
23733 let mut all_deletions = true;
23734
23735 for (range, new_text) in edits.iter() {
23736 let range_is_empty = range.to_offset(snapshot).is_empty();
23737 let text_is_empty = new_text.is_empty();
23738
23739 if range_is_empty != text_is_empty {
23740 if range_is_empty {
23741 all_deletions = false;
23742 } else {
23743 all_insertions = false;
23744 }
23745 } else {
23746 return false;
23747 }
23748
23749 if !all_insertions && !all_deletions {
23750 return false;
23751 }
23752 }
23753 all_insertions || all_deletions
23754}
23755
23756struct MissingEditPredictionKeybindingTooltip;
23757
23758impl Render for MissingEditPredictionKeybindingTooltip {
23759 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23760 ui::tooltip_container(window, cx, |container, _, cx| {
23761 container
23762 .flex_shrink_0()
23763 .max_w_80()
23764 .min_h(rems_from_px(124.))
23765 .justify_between()
23766 .child(
23767 v_flex()
23768 .flex_1()
23769 .text_ui_sm(cx)
23770 .child(Label::new("Conflict with Accept Keybinding"))
23771 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23772 )
23773 .child(
23774 h_flex()
23775 .pb_1()
23776 .gap_1()
23777 .items_end()
23778 .w_full()
23779 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23780 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23781 }))
23782 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23783 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23784 })),
23785 )
23786 })
23787 }
23788}
23789
23790#[derive(Debug, Clone, Copy, PartialEq)]
23791pub struct LineHighlight {
23792 pub background: Background,
23793 pub border: Option<gpui::Hsla>,
23794 pub include_gutter: bool,
23795 pub type_id: Option<TypeId>,
23796}
23797
23798struct LineManipulationResult {
23799 pub new_text: String,
23800 pub line_count_before: usize,
23801 pub line_count_after: usize,
23802}
23803
23804fn render_diff_hunk_controls(
23805 row: u32,
23806 status: &DiffHunkStatus,
23807 hunk_range: Range<Anchor>,
23808 is_created_file: bool,
23809 line_height: Pixels,
23810 editor: &Entity<Editor>,
23811 _window: &mut Window,
23812 cx: &mut App,
23813) -> AnyElement {
23814 h_flex()
23815 .h(line_height)
23816 .mr_1()
23817 .gap_1()
23818 .px_0p5()
23819 .pb_1()
23820 .border_x_1()
23821 .border_b_1()
23822 .border_color(cx.theme().colors().border_variant)
23823 .rounded_b_lg()
23824 .bg(cx.theme().colors().editor_background)
23825 .gap_1()
23826 .block_mouse_except_scroll()
23827 .shadow_md()
23828 .child(if status.has_secondary_hunk() {
23829 Button::new(("stage", row as u64), "Stage")
23830 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23831 .tooltip({
23832 let focus_handle = editor.focus_handle(cx);
23833 move |window, cx| {
23834 Tooltip::for_action_in(
23835 "Stage Hunk",
23836 &::git::ToggleStaged,
23837 &focus_handle,
23838 window,
23839 cx,
23840 )
23841 }
23842 })
23843 .on_click({
23844 let editor = editor.clone();
23845 move |_event, _window, cx| {
23846 editor.update(cx, |editor, cx| {
23847 editor.stage_or_unstage_diff_hunks(
23848 true,
23849 vec![hunk_range.start..hunk_range.start],
23850 cx,
23851 );
23852 });
23853 }
23854 })
23855 } else {
23856 Button::new(("unstage", row as u64), "Unstage")
23857 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23858 .tooltip({
23859 let focus_handle = editor.focus_handle(cx);
23860 move |window, cx| {
23861 Tooltip::for_action_in(
23862 "Unstage Hunk",
23863 &::git::ToggleStaged,
23864 &focus_handle,
23865 window,
23866 cx,
23867 )
23868 }
23869 })
23870 .on_click({
23871 let editor = editor.clone();
23872 move |_event, _window, cx| {
23873 editor.update(cx, |editor, cx| {
23874 editor.stage_or_unstage_diff_hunks(
23875 false,
23876 vec![hunk_range.start..hunk_range.start],
23877 cx,
23878 );
23879 });
23880 }
23881 })
23882 })
23883 .child(
23884 Button::new(("restore", row as u64), "Restore")
23885 .tooltip({
23886 let focus_handle = editor.focus_handle(cx);
23887 move |window, cx| {
23888 Tooltip::for_action_in(
23889 "Restore Hunk",
23890 &::git::Restore,
23891 &focus_handle,
23892 window,
23893 cx,
23894 )
23895 }
23896 })
23897 .on_click({
23898 let editor = editor.clone();
23899 move |_event, window, cx| {
23900 editor.update(cx, |editor, cx| {
23901 let snapshot = editor.snapshot(window, cx);
23902 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23903 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23904 });
23905 }
23906 })
23907 .disabled(is_created_file),
23908 )
23909 .when(
23910 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23911 |el| {
23912 el.child(
23913 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23914 .shape(IconButtonShape::Square)
23915 .icon_size(IconSize::Small)
23916 // .disabled(!has_multiple_hunks)
23917 .tooltip({
23918 let focus_handle = editor.focus_handle(cx);
23919 move |window, cx| {
23920 Tooltip::for_action_in(
23921 "Next Hunk",
23922 &GoToHunk,
23923 &focus_handle,
23924 window,
23925 cx,
23926 )
23927 }
23928 })
23929 .on_click({
23930 let editor = editor.clone();
23931 move |_event, window, cx| {
23932 editor.update(cx, |editor, cx| {
23933 let snapshot = editor.snapshot(window, cx);
23934 let position =
23935 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23936 editor.go_to_hunk_before_or_after_position(
23937 &snapshot,
23938 position,
23939 Direction::Next,
23940 window,
23941 cx,
23942 );
23943 editor.expand_selected_diff_hunks(cx);
23944 });
23945 }
23946 }),
23947 )
23948 .child(
23949 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23950 .shape(IconButtonShape::Square)
23951 .icon_size(IconSize::Small)
23952 // .disabled(!has_multiple_hunks)
23953 .tooltip({
23954 let focus_handle = editor.focus_handle(cx);
23955 move |window, cx| {
23956 Tooltip::for_action_in(
23957 "Previous Hunk",
23958 &GoToPreviousHunk,
23959 &focus_handle,
23960 window,
23961 cx,
23962 )
23963 }
23964 })
23965 .on_click({
23966 let editor = editor.clone();
23967 move |_event, window, cx| {
23968 editor.update(cx, |editor, cx| {
23969 let snapshot = editor.snapshot(window, cx);
23970 let point =
23971 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23972 editor.go_to_hunk_before_or_after_position(
23973 &snapshot,
23974 point,
23975 Direction::Prev,
23976 window,
23977 cx,
23978 );
23979 editor.expand_selected_diff_hunks(cx);
23980 });
23981 }
23982 }),
23983 )
23984 },
23985 )
23986 .into_any_element()
23987}