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,
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, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
130 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, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{
169 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
170};
171use serde::{Deserialize, Serialize};
172use settings::{
173 GitGutterSetting, InlayHintSettings, Settings, SettingsLocation, SettingsStore,
174 update_settings_file,
175};
176use smallvec::{SmallVec, smallvec};
177use snippet::Snippet;
178use std::{
179 any::{Any, TypeId},
180 borrow::Cow,
181 cell::{OnceCell, RefCell},
182 cmp::{self, Ordering, Reverse},
183 iter::Peekable,
184 mem,
185 num::NonZeroU32,
186 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
193use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
194use theme::{
195 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
196 observe_buffer_font_size_adjustment,
197};
198use ui::{
199 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
200 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
201};
202use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
203use workspace::{
204 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
205 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
206 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
207 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
208 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
209 searchable::SearchEvent,
210};
211
212use crate::{
213 code_context_menus::CompletionsMenuSource,
214 editor_settings::MultiCursorModifier,
215 hover_links::{find_url, find_url_from_range},
216 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
217};
218
219pub const FILE_HEADER_HEIGHT: u32 = 2;
220pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233
234pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
235pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238pub type RenderDiffHunkControlsFn = Arc<
239 dyn Fn(
240 u32,
241 &DiffHunkStatus,
242 Range<Anchor>,
243 bool,
244 Pixels,
245 &Entity<Editor>,
246 &mut Window,
247 &mut App,
248 ) -> AnyElement,
249>;
250
251enum ReportEditorEvent {
252 Saved { auto_saved: bool },
253 EditorOpened,
254 Closed,
255}
256
257impl ReportEditorEvent {
258 pub fn event_type(&self) -> &'static str {
259 match self {
260 Self::Saved { .. } => "Editor Saved",
261 Self::EditorOpened => "Editor Opened",
262 Self::Closed => "Editor Closed",
263 }
264 }
265}
266
267struct InlineValueCache {
268 enabled: bool,
269 inlays: Vec<InlayId>,
270 refresh_task: Task<Option<()>>,
271}
272
273impl InlineValueCache {
274 fn new(enabled: bool) -> Self {
275 Self {
276 enabled,
277 inlays: Vec::new(),
278 refresh_task: Task::ready(None),
279 }
280 }
281}
282
283#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
284pub enum InlayId {
285 EditPrediction(usize),
286 DebuggerValue(usize),
287 // LSP
288 Hint(usize),
289 Color(usize),
290}
291
292impl InlayId {
293 fn id(&self) -> usize {
294 match self {
295 Self::EditPrediction(id) => *id,
296 Self::DebuggerValue(id) => *id,
297 Self::Hint(id) => *id,
298 Self::Color(id) => *id,
299 }
300 }
301}
302
303pub enum ActiveDebugLine {}
304pub enum DebugStackFrameLine {}
305enum DocumentHighlightRead {}
306enum DocumentHighlightWrite {}
307enum InputComposition {}
308pub enum PendingInput {}
309enum SelectedTextHighlight {}
310
311pub enum ConflictsOuter {}
312pub enum ConflictsOurs {}
313pub enum ConflictsTheirs {}
314pub enum ConflictsOursMarker {}
315pub enum ConflictsTheirsMarker {}
316
317#[derive(Debug, Copy, Clone, PartialEq, Eq)]
318pub enum Navigated {
319 Yes,
320 No,
321}
322
323impl Navigated {
324 pub fn from_bool(yes: bool) -> Navigated {
325 if yes { Navigated::Yes } else { Navigated::No }
326 }
327}
328
329#[derive(Debug, Clone, PartialEq, Eq)]
330enum DisplayDiffHunk {
331 Folded {
332 display_row: DisplayRow,
333 },
334 Unfolded {
335 is_created_file: bool,
336 diff_base_byte_range: Range<usize>,
337 display_row_range: Range<DisplayRow>,
338 multi_buffer_range: Range<Anchor>,
339 status: DiffHunkStatus,
340 },
341}
342
343pub enum HideMouseCursorOrigin {
344 TypingAction,
345 MovementAction,
346}
347
348pub fn init_settings(cx: &mut App) {
349 EditorSettings::register(cx);
350}
351
352pub fn init(cx: &mut App) {
353 init_settings(cx);
354
355 cx.set_global(GlobalBlameRenderer(Arc::new(())));
356
357 workspace::register_project_item::<Editor>(cx);
358 workspace::FollowableViewRegistry::register::<Editor>(cx);
359 workspace::register_serializable_item::<Editor>(cx);
360
361 cx.observe_new(
362 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
363 workspace.register_action(Editor::new_file);
364 workspace.register_action(Editor::new_file_vertical);
365 workspace.register_action(Editor::new_file_horizontal);
366 workspace.register_action(Editor::cancel_language_server_work);
367 workspace.register_action(Editor::toggle_focus);
368 },
369 )
370 .detach();
371
372 cx.on_action(move |_: &workspace::NewFile, cx| {
373 let app_state = workspace::AppState::global(cx);
374 if let Some(app_state) = app_state.upgrade() {
375 workspace::open_new(
376 Default::default(),
377 app_state,
378 cx,
379 |workspace, window, cx| {
380 Editor::new_file(workspace, &Default::default(), window, cx)
381 },
382 )
383 .detach();
384 }
385 });
386 cx.on_action(move |_: &workspace::NewWindow, cx| {
387 let app_state = workspace::AppState::global(cx);
388 if let Some(app_state) = app_state.upgrade() {
389 workspace::open_new(
390 Default::default(),
391 app_state,
392 cx,
393 |workspace, window, cx| {
394 cx.activate(true);
395 Editor::new_file(workspace, &Default::default(), window, cx)
396 },
397 )
398 .detach();
399 }
400 });
401}
402
403pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
404 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
405}
406
407pub trait DiagnosticRenderer {
408 fn render_group(
409 &self,
410 diagnostic_group: Vec<DiagnosticEntry<Point>>,
411 buffer_id: BufferId,
412 snapshot: EditorSnapshot,
413 editor: WeakEntity<Editor>,
414 cx: &mut App,
415 ) -> Vec<BlockProperties<Anchor>>;
416
417 fn render_hover(
418 &self,
419 diagnostic_group: Vec<DiagnosticEntry<Point>>,
420 range: Range<Point>,
421 buffer_id: BufferId,
422 cx: &mut App,
423 ) -> Option<Entity<markdown::Markdown>>;
424
425 fn open_link(
426 &self,
427 editor: &mut Editor,
428 link: SharedString,
429 window: &mut Window,
430 cx: &mut Context<Editor>,
431 );
432}
433
434pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
435
436impl GlobalDiagnosticRenderer {
437 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
438 cx.try_global::<Self>().map(|g| g.0.clone())
439 }
440}
441
442impl gpui::Global for GlobalDiagnosticRenderer {}
443pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
444 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
445}
446
447pub struct SearchWithinRange;
448
449trait InvalidationRegion {
450 fn ranges(&self) -> &[Range<Anchor>];
451}
452
453#[derive(Clone, Debug, PartialEq)]
454pub enum SelectPhase {
455 Begin {
456 position: DisplayPoint,
457 add: bool,
458 click_count: usize,
459 },
460 BeginColumnar {
461 position: DisplayPoint,
462 reset: bool,
463 mode: ColumnarMode,
464 goal_column: u32,
465 },
466 Extend {
467 position: DisplayPoint,
468 click_count: usize,
469 },
470 Update {
471 position: DisplayPoint,
472 goal_column: u32,
473 scroll_delta: gpui::Point<f32>,
474 },
475 End,
476}
477
478#[derive(Clone, Debug, PartialEq)]
479pub enum ColumnarMode {
480 FromMouse,
481 FromSelection,
482}
483
484#[derive(Clone, Debug)]
485pub enum SelectMode {
486 Character,
487 Word(Range<Anchor>),
488 Line(Range<Anchor>),
489 All,
490}
491
492#[derive(Clone, PartialEq, Eq, Debug)]
493pub enum EditorMode {
494 SingleLine,
495 AutoHeight {
496 min_lines: usize,
497 max_lines: Option<usize>,
498 },
499 Full {
500 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
501 scale_ui_elements_with_buffer_font_size: bool,
502 /// When set to `true`, the editor will render a background for the active line.
503 show_active_line_background: bool,
504 /// When set to `true`, the editor's height will be determined by its content.
505 sized_by_content: bool,
506 },
507 Minimap {
508 parent: WeakEntity<Editor>,
509 },
510}
511
512impl EditorMode {
513 pub fn full() -> Self {
514 Self::Full {
515 scale_ui_elements_with_buffer_font_size: true,
516 show_active_line_background: true,
517 sized_by_content: false,
518 }
519 }
520
521 #[inline]
522 pub fn is_full(&self) -> bool {
523 matches!(self, Self::Full { .. })
524 }
525
526 #[inline]
527 pub fn is_single_line(&self) -> bool {
528 matches!(self, Self::SingleLine { .. })
529 }
530
531 #[inline]
532 fn is_minimap(&self) -> bool {
533 matches!(self, Self::Minimap { .. })
534 }
535}
536
537#[derive(Copy, Clone, Debug)]
538pub enum SoftWrap {
539 /// Prefer not to wrap at all.
540 ///
541 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
542 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
543 GitDiff,
544 /// Prefer a single line generally, unless an overly long line is encountered.
545 None,
546 /// Soft wrap lines that exceed the editor width.
547 EditorWidth,
548 /// Soft wrap lines at the preferred line length.
549 Column(u32),
550 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
551 Bounded(u32),
552}
553
554#[derive(Clone)]
555pub struct EditorStyle {
556 pub background: Hsla,
557 pub border: Hsla,
558 pub local_player: PlayerColor,
559 pub text: TextStyle,
560 pub scrollbar_width: Pixels,
561 pub syntax: Arc<SyntaxTheme>,
562 pub status: StatusColors,
563 pub inlay_hints_style: HighlightStyle,
564 pub edit_prediction_styles: EditPredictionStyles,
565 pub unnecessary_code_fade: f32,
566 pub show_underlines: bool,
567}
568
569impl Default for EditorStyle {
570 fn default() -> Self {
571 Self {
572 background: Hsla::default(),
573 border: Hsla::default(),
574 local_player: PlayerColor::default(),
575 text: TextStyle::default(),
576 scrollbar_width: Pixels::default(),
577 syntax: Default::default(),
578 // HACK: Status colors don't have a real default.
579 // We should look into removing the status colors from the editor
580 // style and retrieve them directly from the theme.
581 status: StatusColors::dark(),
582 inlay_hints_style: HighlightStyle::default(),
583 edit_prediction_styles: EditPredictionStyles {
584 insertion: HighlightStyle::default(),
585 whitespace: HighlightStyle::default(),
586 },
587 unnecessary_code_fade: Default::default(),
588 show_underlines: true,
589 }
590 }
591}
592
593pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
594 let show_background = language_settings::language_settings(None, None, cx)
595 .inlay_hints
596 .show_background;
597
598 HighlightStyle {
599 color: Some(cx.theme().status().hint),
600 background_color: show_background.then(|| cx.theme().status().hint_background),
601 ..HighlightStyle::default()
602 }
603}
604
605pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
606 EditPredictionStyles {
607 insertion: HighlightStyle {
608 color: Some(cx.theme().status().predictive),
609 ..HighlightStyle::default()
610 },
611 whitespace: HighlightStyle {
612 background_color: Some(cx.theme().status().created_background),
613 ..HighlightStyle::default()
614 },
615 }
616}
617
618type CompletionId = usize;
619
620pub(crate) enum EditDisplayMode {
621 TabAccept,
622 DiffPopover,
623 Inline,
624}
625
626enum EditPrediction {
627 Edit {
628 edits: Vec<(Range<Anchor>, String)>,
629 edit_preview: Option<EditPreview>,
630 display_mode: EditDisplayMode,
631 snapshot: BufferSnapshot,
632 },
633 Move {
634 target: Anchor,
635 snapshot: BufferSnapshot,
636 },
637}
638
639struct EditPredictionState {
640 inlay_ids: Vec<InlayId>,
641 completion: EditPrediction,
642 completion_id: Option<SharedString>,
643 invalidation_range: Range<Anchor>,
644}
645
646enum EditPredictionSettings {
647 Disabled,
648 Enabled {
649 show_in_menu: bool,
650 preview_requires_modifier: bool,
651 },
652}
653
654enum EditPredictionHighlight {}
655
656#[derive(Debug, Clone)]
657struct InlineDiagnostic {
658 message: SharedString,
659 group_id: usize,
660 is_primary: bool,
661 start: Point,
662 severity: lsp::DiagnosticSeverity,
663}
664
665pub enum MenuEditPredictionsPolicy {
666 Never,
667 ByProvider,
668}
669
670pub enum EditPredictionPreview {
671 /// Modifier is not pressed
672 Inactive { released_too_fast: bool },
673 /// Modifier pressed
674 Active {
675 since: Instant,
676 previous_scroll_position: Option<ScrollAnchor>,
677 },
678}
679
680impl EditPredictionPreview {
681 pub fn released_too_fast(&self) -> bool {
682 match self {
683 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
684 EditPredictionPreview::Active { .. } => false,
685 }
686 }
687
688 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
689 if let EditPredictionPreview::Active {
690 previous_scroll_position,
691 ..
692 } = self
693 {
694 *previous_scroll_position = scroll_position;
695 }
696 }
697}
698
699pub struct ContextMenuOptions {
700 pub min_entries_visible: usize,
701 pub max_entries_visible: usize,
702 pub placement: Option<ContextMenuPlacement>,
703}
704
705#[derive(Debug, Clone, PartialEq, Eq)]
706pub enum ContextMenuPlacement {
707 Above,
708 Below,
709}
710
711#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
712struct EditorActionId(usize);
713
714impl EditorActionId {
715 pub fn post_inc(&mut self) -> Self {
716 let answer = self.0;
717
718 *self = Self(answer + 1);
719
720 Self(answer)
721 }
722}
723
724// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
725// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
726
727type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
728type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
729
730#[derive(Default)]
731struct ScrollbarMarkerState {
732 scrollbar_size: Size<Pixels>,
733 dirty: bool,
734 markers: Arc<[PaintQuad]>,
735 pending_refresh: Option<Task<Result<()>>>,
736}
737
738impl ScrollbarMarkerState {
739 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
740 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
741 }
742}
743
744#[derive(Clone, Copy, PartialEq, Eq)]
745pub enum MinimapVisibility {
746 Disabled,
747 Enabled {
748 /// The configuration currently present in the users settings.
749 setting_configuration: bool,
750 /// Whether to override the currently set visibility from the users setting.
751 toggle_override: bool,
752 },
753}
754
755impl MinimapVisibility {
756 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
757 if mode.is_full() {
758 Self::Enabled {
759 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
760 toggle_override: false,
761 }
762 } else {
763 Self::Disabled
764 }
765 }
766
767 fn hidden(&self) -> Self {
768 match *self {
769 Self::Enabled {
770 setting_configuration,
771 ..
772 } => Self::Enabled {
773 setting_configuration,
774 toggle_override: setting_configuration,
775 },
776 Self::Disabled => Self::Disabled,
777 }
778 }
779
780 fn disabled(&self) -> bool {
781 matches!(*self, Self::Disabled)
782 }
783
784 fn settings_visibility(&self) -> bool {
785 match *self {
786 Self::Enabled {
787 setting_configuration,
788 ..
789 } => setting_configuration,
790 _ => false,
791 }
792 }
793
794 fn visible(&self) -> bool {
795 match *self {
796 Self::Enabled {
797 setting_configuration,
798 toggle_override,
799 } => setting_configuration ^ toggle_override,
800 _ => false,
801 }
802 }
803
804 fn toggle_visibility(&self) -> Self {
805 match *self {
806 Self::Enabled {
807 toggle_override,
808 setting_configuration,
809 } => Self::Enabled {
810 setting_configuration,
811 toggle_override: !toggle_override,
812 },
813 Self::Disabled => Self::Disabled,
814 }
815 }
816}
817
818#[derive(Clone, Debug)]
819struct RunnableTasks {
820 templates: Vec<(TaskSourceKind, TaskTemplate)>,
821 offset: multi_buffer::Anchor,
822 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
823 column: u32,
824 // Values of all named captures, including those starting with '_'
825 extra_variables: HashMap<String, String>,
826 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
827 context_range: Range<BufferOffset>,
828}
829
830impl RunnableTasks {
831 fn resolve<'a>(
832 &'a self,
833 cx: &'a task::TaskContext,
834 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
835 self.templates.iter().filter_map(|(kind, template)| {
836 template
837 .resolve_task(&kind.to_id_base(), cx)
838 .map(|task| (kind.clone(), task))
839 })
840 }
841}
842
843#[derive(Clone)]
844pub struct ResolvedTasks {
845 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
846 position: Anchor,
847}
848
849#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
850struct BufferOffset(usize);
851
852/// Addons allow storing per-editor state in other crates (e.g. Vim)
853pub trait Addon: 'static {
854 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
855
856 fn render_buffer_header_controls(
857 &self,
858 _: &ExcerptInfo,
859 _: &Window,
860 _: &App,
861 ) -> Option<AnyElement> {
862 None
863 }
864
865 fn to_any(&self) -> &dyn std::any::Any;
866
867 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
868 None
869 }
870}
871
872struct ChangeLocation {
873 current: Option<Vec<Anchor>>,
874 original: Vec<Anchor>,
875}
876impl ChangeLocation {
877 fn locations(&self) -> &[Anchor] {
878 self.current.as_ref().unwrap_or(&self.original)
879 }
880}
881
882/// A set of caret positions, registered when the editor was edited.
883pub struct ChangeList {
884 changes: Vec<ChangeLocation>,
885 /// Currently "selected" change.
886 position: Option<usize>,
887}
888
889impl ChangeList {
890 pub fn new() -> Self {
891 Self {
892 changes: Vec::new(),
893 position: None,
894 }
895 }
896
897 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
898 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
899 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
900 if self.changes.is_empty() {
901 return None;
902 }
903
904 let prev = self.position.unwrap_or(self.changes.len());
905 let next = if direction == Direction::Prev {
906 prev.saturating_sub(count)
907 } else {
908 (prev + count).min(self.changes.len() - 1)
909 };
910 self.position = Some(next);
911 self.changes.get(next).map(|change| change.locations())
912 }
913
914 /// Adds a new change to the list, resetting the change list position.
915 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
916 self.position.take();
917 if let Some(last) = self.changes.last_mut()
918 && group
919 {
920 last.current = Some(new_positions)
921 } else {
922 self.changes.push(ChangeLocation {
923 original: new_positions,
924 current: None,
925 });
926 }
927 }
928
929 pub fn last(&self) -> Option<&[Anchor]> {
930 self.changes.last().map(|change| change.locations())
931 }
932
933 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
934 self.changes.last().map(|change| change.original.as_slice())
935 }
936
937 pub fn invert_last_group(&mut self) {
938 if let Some(last) = self.changes.last_mut()
939 && let Some(current) = last.current.as_mut()
940 {
941 mem::swap(&mut last.original, current);
942 }
943 }
944}
945
946#[derive(Clone)]
947struct InlineBlamePopoverState {
948 scroll_handle: ScrollHandle,
949 commit_message: Option<ParsedCommitMessage>,
950 markdown: Entity<Markdown>,
951}
952
953struct InlineBlamePopover {
954 position: gpui::Point<Pixels>,
955 hide_task: Option<Task<()>>,
956 popover_bounds: Option<Bounds<Pixels>>,
957 popover_state: InlineBlamePopoverState,
958 keyboard_grace: bool,
959}
960
961enum SelectionDragState {
962 /// State when no drag related activity is detected.
963 None,
964 /// State when the mouse is down on a selection that is about to be dragged.
965 ReadyToDrag {
966 selection: Selection<Anchor>,
967 click_position: gpui::Point<Pixels>,
968 mouse_down_time: Instant,
969 },
970 /// State when the mouse is dragging the selection in the editor.
971 Dragging {
972 selection: Selection<Anchor>,
973 drop_cursor: Selection<Anchor>,
974 hide_drop_cursor: bool,
975 },
976}
977
978enum ColumnarSelectionState {
979 FromMouse {
980 selection_tail: Anchor,
981 display_point: Option<DisplayPoint>,
982 },
983 FromSelection {
984 selection_tail: Anchor,
985 },
986}
987
988/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
989/// a breakpoint on them.
990#[derive(Clone, Copy, Debug, PartialEq, Eq)]
991struct PhantomBreakpointIndicator {
992 display_row: DisplayRow,
993 /// There's a small debounce between hovering over the line and showing the indicator.
994 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
995 is_active: bool,
996 collides_with_existing_breakpoint: bool,
997}
998
999/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1000///
1001/// See the [module level documentation](self) for more information.
1002pub struct Editor {
1003 focus_handle: FocusHandle,
1004 last_focused_descendant: Option<WeakFocusHandle>,
1005 /// The text buffer being edited
1006 buffer: Entity<MultiBuffer>,
1007 /// Map of how text in the buffer should be displayed.
1008 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1009 pub display_map: Entity<DisplayMap>,
1010 placeholder_display_map: Option<Entity<DisplayMap>>,
1011 pub selections: SelectionsCollection,
1012 pub scroll_manager: ScrollManager,
1013 /// When inline assist editors are linked, they all render cursors because
1014 /// typing enters text into each of them, even the ones that aren't focused.
1015 pub(crate) show_cursor_when_unfocused: bool,
1016 columnar_selection_state: Option<ColumnarSelectionState>,
1017 add_selections_state: Option<AddSelectionsState>,
1018 select_next_state: Option<SelectNextState>,
1019 select_prev_state: Option<SelectNextState>,
1020 selection_history: SelectionHistory,
1021 defer_selection_effects: bool,
1022 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1023 autoclose_regions: Vec<AutocloseRegion>,
1024 snippet_stack: InvalidationStack<SnippetState>,
1025 select_syntax_node_history: SelectSyntaxNodeHistory,
1026 ime_transaction: Option<TransactionId>,
1027 pub diagnostics_max_severity: DiagnosticSeverity,
1028 active_diagnostics: ActiveDiagnostic,
1029 show_inline_diagnostics: bool,
1030 inline_diagnostics_update: Task<()>,
1031 inline_diagnostics_enabled: bool,
1032 diagnostics_enabled: bool,
1033 word_completions_enabled: bool,
1034 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1035 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1036 hard_wrap: Option<usize>,
1037 project: Option<Entity<Project>>,
1038 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1039 completion_provider: Option<Rc<dyn CompletionProvider>>,
1040 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1041 blink_manager: Entity<BlinkManager>,
1042 show_cursor_names: bool,
1043 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1044 pub show_local_selections: bool,
1045 mode: EditorMode,
1046 show_breadcrumbs: bool,
1047 show_gutter: bool,
1048 show_scrollbars: ScrollbarAxes,
1049 minimap_visibility: MinimapVisibility,
1050 offset_content: bool,
1051 disable_expand_excerpt_buttons: bool,
1052 show_line_numbers: Option<bool>,
1053 use_relative_line_numbers: Option<bool>,
1054 show_git_diff_gutter: Option<bool>,
1055 show_code_actions: Option<bool>,
1056 show_runnables: Option<bool>,
1057 show_breakpoints: Option<bool>,
1058 show_wrap_guides: Option<bool>,
1059 show_indent_guides: Option<bool>,
1060 highlight_order: usize,
1061 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1062 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1063 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1064 scrollbar_marker_state: ScrollbarMarkerState,
1065 active_indent_guides_state: ActiveIndentGuidesState,
1066 nav_history: Option<ItemNavHistory>,
1067 context_menu: RefCell<Option<CodeContextMenu>>,
1068 context_menu_options: Option<ContextMenuOptions>,
1069 mouse_context_menu: Option<MouseContextMenu>,
1070 completion_tasks: Vec<(CompletionId, Task<()>)>,
1071 inline_blame_popover: Option<InlineBlamePopover>,
1072 inline_blame_popover_show_task: Option<Task<()>>,
1073 signature_help_state: SignatureHelpState,
1074 auto_signature_help: Option<bool>,
1075 find_all_references_task_sources: Vec<Anchor>,
1076 next_completion_id: CompletionId,
1077 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1078 code_actions_task: Option<Task<Result<()>>>,
1079 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1080 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 document_highlights_task: Option<Task<()>>,
1082 linked_editing_range_task: Option<Task<Option<()>>>,
1083 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1084 pending_rename: Option<RenameState>,
1085 searchable: bool,
1086 cursor_shape: CursorShape,
1087 current_line_highlight: Option<CurrentLineHighlight>,
1088 collapse_matches: bool,
1089 autoindent_mode: Option<AutoindentMode>,
1090 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1091 input_enabled: bool,
1092 use_modal_editing: bool,
1093 read_only: bool,
1094 leader_id: Option<CollaboratorId>,
1095 remote_id: Option<ViewId>,
1096 pub hover_state: HoverState,
1097 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1098 gutter_hovered: bool,
1099 hovered_link_state: Option<HoveredLinkState>,
1100 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1101 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1102 active_edit_prediction: Option<EditPredictionState>,
1103 /// Used to prevent flickering as the user types while the menu is open
1104 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1105 edit_prediction_settings: EditPredictionSettings,
1106 edit_predictions_hidden_for_vim_mode: bool,
1107 show_edit_predictions_override: Option<bool>,
1108 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1109 edit_prediction_preview: EditPredictionPreview,
1110 edit_prediction_indent_conflict: bool,
1111 edit_prediction_requires_modifier_in_indent_conflict: bool,
1112 inlay_hint_cache: InlayHintCache,
1113 next_inlay_id: usize,
1114 _subscriptions: Vec<Subscription>,
1115 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1116 gutter_dimensions: GutterDimensions,
1117 style: Option<EditorStyle>,
1118 text_style_refinement: Option<TextStyleRefinement>,
1119 next_editor_action_id: EditorActionId,
1120 editor_actions: Rc<
1121 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1122 >,
1123 use_autoclose: bool,
1124 use_auto_surround: bool,
1125 auto_replace_emoji_shortcode: bool,
1126 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1127 show_git_blame_gutter: bool,
1128 show_git_blame_inline: bool,
1129 show_git_blame_inline_delay_task: Option<Task<()>>,
1130 git_blame_inline_enabled: bool,
1131 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1132 serialize_dirty_buffers: bool,
1133 show_selection_menu: Option<bool>,
1134 blame: Option<Entity<GitBlame>>,
1135 blame_subscription: Option<Subscription>,
1136 custom_context_menu: Option<
1137 Box<
1138 dyn 'static
1139 + Fn(
1140 &mut Self,
1141 DisplayPoint,
1142 &mut Window,
1143 &mut Context<Self>,
1144 ) -> Option<Entity<ui::ContextMenu>>,
1145 >,
1146 >,
1147 last_bounds: Option<Bounds<Pixels>>,
1148 last_position_map: Option<Rc<PositionMap>>,
1149 expect_bounds_change: Option<Bounds<Pixels>>,
1150 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1151 tasks_update_task: Option<Task<()>>,
1152 breakpoint_store: Option<Entity<BreakpointStore>>,
1153 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1154 hovered_diff_hunk_row: Option<DisplayRow>,
1155 pull_diagnostics_task: Task<()>,
1156 in_project_search: bool,
1157 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1158 breadcrumb_header: Option<String>,
1159 focused_block: Option<FocusedBlock>,
1160 next_scroll_position: NextScrollCursorCenterTopBottom,
1161 addons: HashMap<TypeId, Box<dyn Addon>>,
1162 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1163 load_diff_task: Option<Shared<Task<()>>>,
1164 /// Whether we are temporarily displaying a diff other than git's
1165 temporary_diff_override: bool,
1166 selection_mark_mode: bool,
1167 toggle_fold_multiple_buffers: Task<()>,
1168 _scroll_cursor_center_top_bottom_task: Task<()>,
1169 serialize_selections: Task<()>,
1170 serialize_folds: Task<()>,
1171 mouse_cursor_hidden: bool,
1172 minimap: Option<Entity<Self>>,
1173 hide_mouse_mode: HideMouseMode,
1174 pub change_list: ChangeList,
1175 inline_value_cache: InlineValueCache,
1176 selection_drag_state: SelectionDragState,
1177 next_color_inlay_id: usize,
1178 colors: Option<LspColorData>,
1179 folding_newlines: Task<()>,
1180 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1181}
1182
1183#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1184enum NextScrollCursorCenterTopBottom {
1185 #[default]
1186 Center,
1187 Top,
1188 Bottom,
1189}
1190
1191impl NextScrollCursorCenterTopBottom {
1192 fn next(&self) -> Self {
1193 match self {
1194 Self::Center => Self::Top,
1195 Self::Top => Self::Bottom,
1196 Self::Bottom => Self::Center,
1197 }
1198 }
1199}
1200
1201#[derive(Clone)]
1202pub struct EditorSnapshot {
1203 pub mode: EditorMode,
1204 show_gutter: bool,
1205 show_line_numbers: Option<bool>,
1206 show_git_diff_gutter: Option<bool>,
1207 show_code_actions: Option<bool>,
1208 show_runnables: Option<bool>,
1209 show_breakpoints: Option<bool>,
1210 git_blame_gutter_max_author_length: Option<usize>,
1211 pub display_snapshot: DisplaySnapshot,
1212 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1213 is_focused: bool,
1214 scroll_anchor: ScrollAnchor,
1215 ongoing_scroll: OngoingScroll,
1216 current_line_highlight: CurrentLineHighlight,
1217 gutter_hovered: bool,
1218}
1219
1220#[derive(Default, Debug, Clone, Copy)]
1221pub struct GutterDimensions {
1222 pub left_padding: Pixels,
1223 pub right_padding: Pixels,
1224 pub width: Pixels,
1225 pub margin: Pixels,
1226 pub git_blame_entries_width: Option<Pixels>,
1227}
1228
1229impl GutterDimensions {
1230 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1231 Self {
1232 margin: Self::default_gutter_margin(font_id, font_size, cx),
1233 ..Default::default()
1234 }
1235 }
1236
1237 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1238 -cx.text_system().descent(font_id, font_size)
1239 }
1240 /// The full width of the space taken up by the gutter.
1241 pub fn full_width(&self) -> Pixels {
1242 self.margin + self.width
1243 }
1244
1245 /// The width of the space reserved for the fold indicators,
1246 /// use alongside 'justify_end' and `gutter_width` to
1247 /// right align content with the line numbers
1248 pub fn fold_area_width(&self) -> Pixels {
1249 self.margin + self.right_padding
1250 }
1251}
1252
1253struct CharacterDimensions {
1254 em_width: Pixels,
1255 em_advance: Pixels,
1256 line_height: Pixels,
1257}
1258
1259#[derive(Debug)]
1260pub struct RemoteSelection {
1261 pub replica_id: ReplicaId,
1262 pub selection: Selection<Anchor>,
1263 pub cursor_shape: CursorShape,
1264 pub collaborator_id: CollaboratorId,
1265 pub line_mode: bool,
1266 pub user_name: Option<SharedString>,
1267 pub color: PlayerColor,
1268}
1269
1270#[derive(Clone, Debug)]
1271struct SelectionHistoryEntry {
1272 selections: Arc<[Selection<Anchor>]>,
1273 select_next_state: Option<SelectNextState>,
1274 select_prev_state: Option<SelectNextState>,
1275 add_selections_state: Option<AddSelectionsState>,
1276}
1277
1278#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1279enum SelectionHistoryMode {
1280 Normal,
1281 Undoing,
1282 Redoing,
1283 Skipping,
1284}
1285
1286#[derive(Clone, PartialEq, Eq, Hash)]
1287struct HoveredCursor {
1288 replica_id: u16,
1289 selection_id: usize,
1290}
1291
1292impl Default for SelectionHistoryMode {
1293 fn default() -> Self {
1294 Self::Normal
1295 }
1296}
1297
1298#[derive(Debug)]
1299/// SelectionEffects controls the side-effects of updating the selection.
1300///
1301/// The default behaviour does "what you mostly want":
1302/// - it pushes to the nav history if the cursor moved by >10 lines
1303/// - it re-triggers completion requests
1304/// - it scrolls to fit
1305///
1306/// You might want to modify these behaviours. For example when doing a "jump"
1307/// like go to definition, we always want to add to nav history; but when scrolling
1308/// in vim mode we never do.
1309///
1310/// Similarly, you might want to disable scrolling if you don't want the viewport to
1311/// move.
1312#[derive(Clone)]
1313pub struct SelectionEffects {
1314 nav_history: Option<bool>,
1315 completions: bool,
1316 scroll: Option<Autoscroll>,
1317}
1318
1319impl Default for SelectionEffects {
1320 fn default() -> Self {
1321 Self {
1322 nav_history: None,
1323 completions: true,
1324 scroll: Some(Autoscroll::fit()),
1325 }
1326 }
1327}
1328impl SelectionEffects {
1329 pub fn scroll(scroll: Autoscroll) -> Self {
1330 Self {
1331 scroll: Some(scroll),
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn no_scroll() -> Self {
1337 Self {
1338 scroll: None,
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn completions(self, completions: bool) -> Self {
1344 Self {
1345 completions,
1346 ..self
1347 }
1348 }
1349
1350 pub fn nav_history(self, nav_history: bool) -> Self {
1351 Self {
1352 nav_history: Some(nav_history),
1353 ..self
1354 }
1355 }
1356}
1357
1358struct DeferredSelectionEffectsState {
1359 changed: bool,
1360 effects: SelectionEffects,
1361 old_cursor_position: Anchor,
1362 history_entry: SelectionHistoryEntry,
1363}
1364
1365#[derive(Default)]
1366struct SelectionHistory {
1367 #[allow(clippy::type_complexity)]
1368 selections_by_transaction:
1369 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1370 mode: SelectionHistoryMode,
1371 undo_stack: VecDeque<SelectionHistoryEntry>,
1372 redo_stack: VecDeque<SelectionHistoryEntry>,
1373}
1374
1375impl SelectionHistory {
1376 #[track_caller]
1377 fn insert_transaction(
1378 &mut self,
1379 transaction_id: TransactionId,
1380 selections: Arc<[Selection<Anchor>]>,
1381 ) {
1382 if selections.is_empty() {
1383 log::error!(
1384 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1385 std::panic::Location::caller()
1386 );
1387 return;
1388 }
1389 self.selections_by_transaction
1390 .insert(transaction_id, (selections, None));
1391 }
1392
1393 #[allow(clippy::type_complexity)]
1394 fn transaction(
1395 &self,
1396 transaction_id: TransactionId,
1397 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1398 self.selections_by_transaction.get(&transaction_id)
1399 }
1400
1401 #[allow(clippy::type_complexity)]
1402 fn transaction_mut(
1403 &mut self,
1404 transaction_id: TransactionId,
1405 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1406 self.selections_by_transaction.get_mut(&transaction_id)
1407 }
1408
1409 fn push(&mut self, entry: SelectionHistoryEntry) {
1410 if !entry.selections.is_empty() {
1411 match self.mode {
1412 SelectionHistoryMode::Normal => {
1413 self.push_undo(entry);
1414 self.redo_stack.clear();
1415 }
1416 SelectionHistoryMode::Undoing => self.push_redo(entry),
1417 SelectionHistoryMode::Redoing => self.push_undo(entry),
1418 SelectionHistoryMode::Skipping => {}
1419 }
1420 }
1421 }
1422
1423 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1424 if self
1425 .undo_stack
1426 .back()
1427 .is_none_or(|e| e.selections != entry.selections)
1428 {
1429 self.undo_stack.push_back(entry);
1430 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1431 self.undo_stack.pop_front();
1432 }
1433 }
1434 }
1435
1436 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1437 if self
1438 .redo_stack
1439 .back()
1440 .is_none_or(|e| e.selections != entry.selections)
1441 {
1442 self.redo_stack.push_back(entry);
1443 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1444 self.redo_stack.pop_front();
1445 }
1446 }
1447 }
1448}
1449
1450#[derive(Clone, Copy)]
1451pub struct RowHighlightOptions {
1452 pub autoscroll: bool,
1453 pub include_gutter: bool,
1454}
1455
1456impl Default for RowHighlightOptions {
1457 fn default() -> Self {
1458 Self {
1459 autoscroll: Default::default(),
1460 include_gutter: true,
1461 }
1462 }
1463}
1464
1465struct RowHighlight {
1466 index: usize,
1467 range: Range<Anchor>,
1468 color: Hsla,
1469 options: RowHighlightOptions,
1470 type_id: TypeId,
1471}
1472
1473#[derive(Clone, Debug)]
1474struct AddSelectionsState {
1475 groups: Vec<AddSelectionsGroup>,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsGroup {
1480 above: bool,
1481 stack: Vec<usize>,
1482}
1483
1484#[derive(Clone)]
1485struct SelectNextState {
1486 query: AhoCorasick,
1487 wordwise: bool,
1488 done: bool,
1489}
1490
1491impl std::fmt::Debug for SelectNextState {
1492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1493 f.debug_struct(std::any::type_name::<Self>())
1494 .field("wordwise", &self.wordwise)
1495 .field("done", &self.done)
1496 .finish()
1497 }
1498}
1499
1500#[derive(Debug)]
1501struct AutocloseRegion {
1502 selection_id: usize,
1503 range: Range<Anchor>,
1504 pair: BracketPair,
1505}
1506
1507#[derive(Debug)]
1508struct SnippetState {
1509 ranges: Vec<Vec<Range<Anchor>>>,
1510 active_index: usize,
1511 choices: Vec<Option<Vec<String>>>,
1512}
1513
1514#[doc(hidden)]
1515pub struct RenameState {
1516 pub range: Range<Anchor>,
1517 pub old_name: Arc<str>,
1518 pub editor: Entity<Editor>,
1519 block_id: CustomBlockId,
1520}
1521
1522struct InvalidationStack<T>(Vec<T>);
1523
1524struct RegisteredEditPredictionProvider {
1525 provider: Arc<dyn EditPredictionProviderHandle>,
1526 _subscription: Subscription,
1527}
1528
1529#[derive(Debug, PartialEq, Eq)]
1530pub struct ActiveDiagnosticGroup {
1531 pub active_range: Range<Anchor>,
1532 pub active_message: String,
1533 pub group_id: usize,
1534 pub blocks: HashSet<CustomBlockId>,
1535}
1536
1537#[derive(Debug, PartialEq, Eq)]
1538
1539pub(crate) enum ActiveDiagnostic {
1540 None,
1541 All,
1542 Group(ActiveDiagnosticGroup),
1543}
1544
1545#[derive(Serialize, Deserialize, Clone, Debug)]
1546pub struct ClipboardSelection {
1547 /// The number of bytes in this selection.
1548 pub len: usize,
1549 /// Whether this was a full-line selection.
1550 pub is_entire_line: bool,
1551 /// The indentation of the first line when this content was originally copied.
1552 pub first_line_indent: u32,
1553}
1554
1555// selections, scroll behavior, was newest selection reversed
1556type SelectSyntaxNodeHistoryState = (
1557 Box<[Selection<usize>]>,
1558 SelectSyntaxNodeScrollBehavior,
1559 bool,
1560);
1561
1562#[derive(Default)]
1563struct SelectSyntaxNodeHistory {
1564 stack: Vec<SelectSyntaxNodeHistoryState>,
1565 // disable temporarily to allow changing selections without losing the stack
1566 pub disable_clearing: bool,
1567}
1568
1569impl SelectSyntaxNodeHistory {
1570 pub fn try_clear(&mut self) {
1571 if !self.disable_clearing {
1572 self.stack.clear();
1573 }
1574 }
1575
1576 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1577 self.stack.push(selection);
1578 }
1579
1580 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1581 self.stack.pop()
1582 }
1583}
1584
1585enum SelectSyntaxNodeScrollBehavior {
1586 CursorTop,
1587 FitSelection,
1588 CursorBottom,
1589}
1590
1591#[derive(Debug)]
1592pub(crate) struct NavigationData {
1593 cursor_anchor: Anchor,
1594 cursor_position: Point,
1595 scroll_anchor: ScrollAnchor,
1596 scroll_top_row: u32,
1597}
1598
1599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600pub enum GotoDefinitionKind {
1601 Symbol,
1602 Declaration,
1603 Type,
1604 Implementation,
1605}
1606
1607#[derive(Debug, Clone)]
1608enum InlayHintRefreshReason {
1609 ModifiersChanged(bool),
1610 Toggle(bool),
1611 SettingsChange(InlayHintSettings),
1612 NewLinesShown,
1613 BufferEdited(HashSet<Arc<Language>>),
1614 RefreshRequested,
1615 ExcerptsRemoved(Vec<ExcerptId>),
1616}
1617
1618impl InlayHintRefreshReason {
1619 fn description(&self) -> &'static str {
1620 match self {
1621 Self::ModifiersChanged(_) => "modifiers changed",
1622 Self::Toggle(_) => "toggle",
1623 Self::SettingsChange(_) => "settings change",
1624 Self::NewLinesShown => "new lines shown",
1625 Self::BufferEdited(_) => "buffer edited",
1626 Self::RefreshRequested => "refresh requested",
1627 Self::ExcerptsRemoved(_) => "excerpts removed",
1628 }
1629 }
1630}
1631
1632pub enum FormatTarget {
1633 Buffers(HashSet<Entity<Buffer>>),
1634 Ranges(Vec<Range<MultiBufferPoint>>),
1635}
1636
1637pub(crate) struct FocusedBlock {
1638 id: BlockId,
1639 focus_handle: WeakFocusHandle,
1640}
1641
1642#[derive(Clone)]
1643enum JumpData {
1644 MultiBufferRow {
1645 row: MultiBufferRow,
1646 line_offset_from_top: u32,
1647 },
1648 MultiBufferPoint {
1649 excerpt_id: ExcerptId,
1650 position: Point,
1651 anchor: text::Anchor,
1652 line_offset_from_top: u32,
1653 },
1654}
1655
1656pub enum MultibufferSelectionMode {
1657 First,
1658 All,
1659}
1660
1661#[derive(Clone, Copy, Debug, Default)]
1662pub struct RewrapOptions {
1663 pub override_language_settings: bool,
1664 pub preserve_existing_whitespace: bool,
1665}
1666
1667impl Editor {
1668 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1669 let buffer = cx.new(|cx| Buffer::local("", cx));
1670 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1671 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1672 }
1673
1674 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(EditorMode::full(), buffer, None, window, cx)
1678 }
1679
1680 pub fn auto_height(
1681 min_lines: usize,
1682 max_lines: usize,
1683 window: &mut Window,
1684 cx: &mut Context<Self>,
1685 ) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(
1689 EditorMode::AutoHeight {
1690 min_lines,
1691 max_lines: Some(max_lines),
1692 },
1693 buffer,
1694 None,
1695 window,
1696 cx,
1697 )
1698 }
1699
1700 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1701 /// The editor grows as tall as needed to fit its content.
1702 pub fn auto_height_unbounded(
1703 min_lines: usize,
1704 window: &mut Window,
1705 cx: &mut Context<Self>,
1706 ) -> Self {
1707 let buffer = cx.new(|cx| Buffer::local("", cx));
1708 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1709 Self::new(
1710 EditorMode::AutoHeight {
1711 min_lines,
1712 max_lines: None,
1713 },
1714 buffer,
1715 None,
1716 window,
1717 cx,
1718 )
1719 }
1720
1721 pub fn for_buffer(
1722 buffer: Entity<Buffer>,
1723 project: Option<Entity<Project>>,
1724 window: &mut Window,
1725 cx: &mut Context<Self>,
1726 ) -> Self {
1727 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1728 Self::new(EditorMode::full(), buffer, project, window, cx)
1729 }
1730
1731 pub fn for_multibuffer(
1732 buffer: Entity<MultiBuffer>,
1733 project: Option<Entity<Project>>,
1734 window: &mut Window,
1735 cx: &mut Context<Self>,
1736 ) -> Self {
1737 Self::new(EditorMode::full(), buffer, project, window, cx)
1738 }
1739
1740 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1741 let mut clone = Self::new(
1742 self.mode.clone(),
1743 self.buffer.clone(),
1744 self.project.clone(),
1745 window,
1746 cx,
1747 );
1748 self.display_map.update(cx, |display_map, cx| {
1749 let snapshot = display_map.snapshot(cx);
1750 clone.display_map.update(cx, |display_map, cx| {
1751 display_map.set_state(&snapshot, cx);
1752 });
1753 });
1754 clone.folds_did_change(cx);
1755 clone.selections.clone_state(&self.selections);
1756 clone.scroll_manager.clone_state(&self.scroll_manager);
1757 clone.searchable = self.searchable;
1758 clone.read_only = self.read_only;
1759 clone
1760 }
1761
1762 pub fn new(
1763 mode: EditorMode,
1764 buffer: Entity<MultiBuffer>,
1765 project: Option<Entity<Project>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 Editor::new_internal(mode, buffer, project, None, window, cx)
1770 }
1771
1772 fn new_internal(
1773 mode: EditorMode,
1774 buffer: Entity<MultiBuffer>,
1775 project: Option<Entity<Project>>,
1776 display_map: Option<Entity<DisplayMap>>,
1777 window: &mut Window,
1778 cx: &mut Context<Self>,
1779 ) -> Self {
1780 debug_assert!(
1781 display_map.is_none() || mode.is_minimap(),
1782 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1783 );
1784
1785 let full_mode = mode.is_full();
1786 let is_minimap = mode.is_minimap();
1787 let diagnostics_max_severity = if full_mode {
1788 EditorSettings::get_global(cx)
1789 .diagnostics_max_severity
1790 .unwrap_or(DiagnosticSeverity::Hint)
1791 } else {
1792 DiagnosticSeverity::Off
1793 };
1794 let style = window.text_style();
1795 let font_size = style.font_size.to_pixels(window.rem_size());
1796 let editor = cx.entity().downgrade();
1797 let fold_placeholder = FoldPlaceholder {
1798 constrain_width: false,
1799 render: Arc::new(move |fold_id, fold_range, cx| {
1800 let editor = editor.clone();
1801 div()
1802 .id(fold_id)
1803 .bg(cx.theme().colors().ghost_element_background)
1804 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1805 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1806 .rounded_xs()
1807 .size_full()
1808 .cursor_pointer()
1809 .child("⋯")
1810 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1811 .on_click(move |_, _window, cx| {
1812 editor
1813 .update(cx, |editor, cx| {
1814 editor.unfold_ranges(
1815 &[fold_range.start..fold_range.end],
1816 true,
1817 false,
1818 cx,
1819 );
1820 cx.stop_propagation();
1821 })
1822 .ok();
1823 })
1824 .into_any()
1825 }),
1826 merge_adjacent: true,
1827 ..FoldPlaceholder::default()
1828 };
1829 let display_map = display_map.unwrap_or_else(|| {
1830 cx.new(|cx| {
1831 DisplayMap::new(
1832 buffer.clone(),
1833 style.font(),
1834 font_size,
1835 None,
1836 FILE_HEADER_HEIGHT,
1837 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1838 fold_placeholder,
1839 diagnostics_max_severity,
1840 cx,
1841 )
1842 })
1843 });
1844
1845 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1846
1847 let blink_manager = cx.new(|cx| {
1848 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1849 if is_minimap {
1850 blink_manager.disable(cx);
1851 }
1852 blink_manager
1853 });
1854
1855 let soft_wrap_mode_override =
1856 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1857
1858 let mut project_subscriptions = Vec::new();
1859 if full_mode && let Some(project) = project.as_ref() {
1860 project_subscriptions.push(cx.subscribe_in(
1861 project,
1862 window,
1863 |editor, _, event, window, cx| match event {
1864 project::Event::RefreshCodeLens => {
1865 // we always query lens with actions, without storing them, always refreshing them
1866 }
1867 project::Event::RefreshInlayHints => {
1868 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1869 }
1870 project::Event::LanguageServerAdded(..)
1871 | project::Event::LanguageServerRemoved(..) => {
1872 if editor.tasks_update_task.is_none() {
1873 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1874 }
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1897 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1898 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1899 }
1900 }
1901
1902 project::Event::EntryRenamed(transaction) => {
1903 let Some(workspace) = editor.workspace() else {
1904 return;
1905 };
1906 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1907 else {
1908 return;
1909 };
1910 if active_editor.entity_id() == cx.entity_id() {
1911 let edited_buffers_already_open = {
1912 let other_editors: Vec<Entity<Editor>> = workspace
1913 .read(cx)
1914 .panes()
1915 .iter()
1916 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1917 .filter(|editor| editor.entity_id() != cx.entity_id())
1918 .collect();
1919
1920 transaction.0.keys().all(|buffer| {
1921 other_editors.iter().any(|editor| {
1922 let multi_buffer = editor.read(cx).buffer();
1923 multi_buffer.read(cx).is_singleton()
1924 && multi_buffer.read(cx).as_singleton().map_or(
1925 false,
1926 |singleton| {
1927 singleton.entity_id() == buffer.entity_id()
1928 },
1929 )
1930 })
1931 })
1932 };
1933
1934 if !edited_buffers_already_open {
1935 let workspace = workspace.downgrade();
1936 let transaction = transaction.clone();
1937 cx.defer_in(window, move |_, window, cx| {
1938 cx.spawn_in(window, async move |editor, cx| {
1939 Self::open_project_transaction(
1940 &editor,
1941 workspace,
1942 transaction,
1943 "Rename".to_string(),
1944 cx,
1945 )
1946 .await
1947 .ok()
1948 })
1949 .detach();
1950 });
1951 }
1952 }
1953 }
1954
1955 _ => {}
1956 },
1957 ));
1958 if let Some(task_inventory) = project
1959 .read(cx)
1960 .task_store()
1961 .read(cx)
1962 .task_inventory()
1963 .cloned()
1964 {
1965 project_subscriptions.push(cx.observe_in(
1966 &task_inventory,
1967 window,
1968 |editor, _, window, cx| {
1969 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1970 },
1971 ));
1972 };
1973
1974 project_subscriptions.push(cx.subscribe_in(
1975 &project.read(cx).breakpoint_store(),
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 BreakpointStoreEvent::ClearDebugLines => {
1979 editor.clear_row_highlights::<ActiveDebugLine>();
1980 editor.refresh_inline_values(cx);
1981 }
1982 BreakpointStoreEvent::SetDebugLine => {
1983 if editor.go_to_active_debug_line(window, cx) {
1984 cx.stop_propagation();
1985 }
1986
1987 editor.refresh_inline_values(cx);
1988 }
1989 _ => {}
1990 },
1991 ));
1992 let git_store = project.read(cx).git_store().clone();
1993 let project = project.clone();
1994 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1995 if let GitStoreEvent::RepositoryUpdated(
1996 _,
1997 RepositoryEvent::Updated {
1998 new_instance: true, ..
1999 },
2000 _,
2001 ) = event
2002 {
2003 this.load_diff_task = Some(
2004 update_uncommitted_diff_for_buffer(
2005 cx.entity(),
2006 &project,
2007 this.buffer.read(cx).all_buffers(),
2008 this.buffer.clone(),
2009 cx,
2010 )
2011 .shared(),
2012 );
2013 }
2014 }));
2015 }
2016
2017 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2018
2019 let inlay_hint_settings =
2020 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2021 let focus_handle = cx.focus_handle();
2022 if !is_minimap {
2023 cx.on_focus(&focus_handle, window, Self::handle_focus)
2024 .detach();
2025 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2026 .detach();
2027 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2028 .detach();
2029 cx.on_blur(&focus_handle, window, Self::handle_blur)
2030 .detach();
2031 cx.observe_pending_input(window, Self::observe_pending_input)
2032 .detach();
2033 }
2034
2035 let show_indent_guides =
2036 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2037 Some(false)
2038 } else {
2039 None
2040 };
2041
2042 let breakpoint_store = match (&mode, project.as_ref()) {
2043 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2044 _ => None,
2045 };
2046
2047 let mut code_action_providers = Vec::new();
2048 let mut load_uncommitted_diff = None;
2049 if let Some(project) = project.clone() {
2050 load_uncommitted_diff = Some(
2051 update_uncommitted_diff_for_buffer(
2052 cx.entity(),
2053 &project,
2054 buffer.read(cx).all_buffers(),
2055 buffer.clone(),
2056 cx,
2057 )
2058 .shared(),
2059 );
2060 code_action_providers.push(Rc::new(project) as Rc<_>);
2061 }
2062
2063 let mut editor = Self {
2064 focus_handle,
2065 show_cursor_when_unfocused: false,
2066 last_focused_descendant: None,
2067 buffer: buffer.clone(),
2068 display_map: display_map.clone(),
2069 placeholder_display_map: None,
2070 selections,
2071 scroll_manager: ScrollManager::new(cx),
2072 columnar_selection_state: None,
2073 add_selections_state: None,
2074 select_next_state: None,
2075 select_prev_state: None,
2076 selection_history: SelectionHistory::default(),
2077 defer_selection_effects: false,
2078 deferred_selection_effects_state: None,
2079 autoclose_regions: Vec::new(),
2080 snippet_stack: InvalidationStack::default(),
2081 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2082 ime_transaction: None,
2083 active_diagnostics: ActiveDiagnostic::None,
2084 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2085 inline_diagnostics_update: Task::ready(()),
2086 inline_diagnostics: Vec::new(),
2087 soft_wrap_mode_override,
2088 diagnostics_max_severity,
2089 hard_wrap: None,
2090 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2091 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2092 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2093 project,
2094 blink_manager: blink_manager.clone(),
2095 show_local_selections: true,
2096 show_scrollbars: ScrollbarAxes {
2097 horizontal: full_mode,
2098 vertical: full_mode,
2099 },
2100 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2101 offset_content: !matches!(mode, EditorMode::SingleLine),
2102 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2103 show_gutter: full_mode,
2104 show_line_numbers: (!full_mode).then_some(false),
2105 use_relative_line_numbers: None,
2106 disable_expand_excerpt_buttons: !full_mode,
2107 show_git_diff_gutter: None,
2108 show_code_actions: None,
2109 show_runnables: None,
2110 show_breakpoints: None,
2111 show_wrap_guides: None,
2112 show_indent_guides,
2113 highlight_order: 0,
2114 highlighted_rows: HashMap::default(),
2115 background_highlights: HashMap::default(),
2116 gutter_highlights: HashMap::default(),
2117 scrollbar_marker_state: ScrollbarMarkerState::default(),
2118 active_indent_guides_state: ActiveIndentGuidesState::default(),
2119 nav_history: None,
2120 context_menu: RefCell::new(None),
2121 context_menu_options: None,
2122 mouse_context_menu: None,
2123 completion_tasks: Vec::new(),
2124 inline_blame_popover: None,
2125 inline_blame_popover_show_task: None,
2126 signature_help_state: SignatureHelpState::default(),
2127 auto_signature_help: None,
2128 find_all_references_task_sources: Vec::new(),
2129 next_completion_id: 0,
2130 next_inlay_id: 0,
2131 code_action_providers,
2132 available_code_actions: None,
2133 code_actions_task: None,
2134 quick_selection_highlight_task: None,
2135 debounced_selection_highlight_task: None,
2136 document_highlights_task: None,
2137 linked_editing_range_task: None,
2138 pending_rename: None,
2139 searchable: !is_minimap,
2140 cursor_shape: EditorSettings::get_global(cx)
2141 .cursor_shape
2142 .unwrap_or_default(),
2143 current_line_highlight: None,
2144 autoindent_mode: Some(AutoindentMode::EachLine),
2145 collapse_matches: false,
2146 workspace: None,
2147 input_enabled: !is_minimap,
2148 use_modal_editing: full_mode,
2149 read_only: is_minimap,
2150 use_autoclose: true,
2151 use_auto_surround: true,
2152 auto_replace_emoji_shortcode: false,
2153 jsx_tag_auto_close_enabled_in_any_buffer: false,
2154 leader_id: None,
2155 remote_id: None,
2156 hover_state: HoverState::default(),
2157 pending_mouse_down: None,
2158 hovered_link_state: None,
2159 edit_prediction_provider: None,
2160 active_edit_prediction: None,
2161 stale_edit_prediction_in_menu: None,
2162 edit_prediction_preview: EditPredictionPreview::Inactive {
2163 released_too_fast: false,
2164 },
2165 inline_diagnostics_enabled: full_mode,
2166 diagnostics_enabled: full_mode,
2167 word_completions_enabled: full_mode,
2168 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2169 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2170 gutter_hovered: false,
2171 pixel_position_of_newest_cursor: None,
2172 last_bounds: None,
2173 last_position_map: None,
2174 expect_bounds_change: None,
2175 gutter_dimensions: GutterDimensions::default(),
2176 style: None,
2177 show_cursor_names: false,
2178 hovered_cursors: HashMap::default(),
2179 next_editor_action_id: EditorActionId::default(),
2180 editor_actions: Rc::default(),
2181 edit_predictions_hidden_for_vim_mode: false,
2182 show_edit_predictions_override: None,
2183 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2184 edit_prediction_settings: EditPredictionSettings::Disabled,
2185 edit_prediction_indent_conflict: false,
2186 edit_prediction_requires_modifier_in_indent_conflict: true,
2187 custom_context_menu: None,
2188 show_git_blame_gutter: false,
2189 show_git_blame_inline: false,
2190 show_selection_menu: None,
2191 show_git_blame_inline_delay_task: None,
2192 git_blame_inline_enabled: full_mode
2193 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2194 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2195 serialize_dirty_buffers: !is_minimap
2196 && ProjectSettings::get_global(cx)
2197 .session
2198 .restore_unsaved_buffers,
2199 blame: None,
2200 blame_subscription: None,
2201 tasks: BTreeMap::default(),
2202
2203 breakpoint_store,
2204 gutter_breakpoint_indicator: (None, None),
2205 hovered_diff_hunk_row: None,
2206 _subscriptions: (!is_minimap)
2207 .then(|| {
2208 vec![
2209 cx.observe(&buffer, Self::on_buffer_changed),
2210 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2211 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2212 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2213 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2214 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2215 cx.observe_window_activation(window, |editor, window, cx| {
2216 let active = window.is_window_active();
2217 editor.blink_manager.update(cx, |blink_manager, cx| {
2218 if active {
2219 blink_manager.enable(cx);
2220 } else {
2221 blink_manager.disable(cx);
2222 }
2223 });
2224 if active {
2225 editor.show_mouse_cursor(cx);
2226 }
2227 }),
2228 ]
2229 })
2230 .unwrap_or_default(),
2231 tasks_update_task: None,
2232 pull_diagnostics_task: Task::ready(()),
2233 colors: None,
2234 next_color_inlay_id: 0,
2235 linked_edit_ranges: Default::default(),
2236 in_project_search: false,
2237 previous_search_ranges: None,
2238 breadcrumb_header: None,
2239 focused_block: None,
2240 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2241 addons: HashMap::default(),
2242 registered_buffers: HashMap::default(),
2243 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2244 selection_mark_mode: false,
2245 toggle_fold_multiple_buffers: Task::ready(()),
2246 serialize_selections: Task::ready(()),
2247 serialize_folds: Task::ready(()),
2248 text_style_refinement: None,
2249 load_diff_task: load_uncommitted_diff,
2250 temporary_diff_override: false,
2251 mouse_cursor_hidden: false,
2252 minimap: None,
2253 hide_mouse_mode: EditorSettings::get_global(cx)
2254 .hide_mouse
2255 .unwrap_or_default(),
2256 change_list: ChangeList::new(),
2257 mode,
2258 selection_drag_state: SelectionDragState::None,
2259 folding_newlines: Task::ready(()),
2260 lookup_key: None,
2261 };
2262
2263 if is_minimap {
2264 return editor;
2265 }
2266
2267 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2268 editor
2269 ._subscriptions
2270 .push(cx.observe(breakpoints, |_, _, cx| {
2271 cx.notify();
2272 }));
2273 }
2274 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2275 editor._subscriptions.extend(project_subscriptions);
2276
2277 editor._subscriptions.push(cx.subscribe_in(
2278 &cx.entity(),
2279 window,
2280 |editor, _, e: &EditorEvent, window, cx| match e {
2281 EditorEvent::ScrollPositionChanged { local, .. } => {
2282 if *local {
2283 let new_anchor = editor.scroll_manager.anchor();
2284 let snapshot = editor.snapshot(window, cx);
2285 editor.update_restoration_data(cx, move |data| {
2286 data.scroll_position = (
2287 new_anchor.top_row(&snapshot.buffer_snapshot),
2288 new_anchor.offset,
2289 );
2290 });
2291 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2292 editor.inline_blame_popover.take();
2293 }
2294 }
2295 EditorEvent::Edited { .. } => {
2296 if !vim_enabled(cx) {
2297 let (map, selections) = editor.selections.all_adjusted_display(cx);
2298 let pop_state = editor
2299 .change_list
2300 .last()
2301 .map(|previous| {
2302 previous.len() == selections.len()
2303 && previous.iter().enumerate().all(|(ix, p)| {
2304 p.to_display_point(&map).row()
2305 == selections[ix].head().row()
2306 })
2307 })
2308 .unwrap_or(false);
2309 let new_positions = selections
2310 .into_iter()
2311 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2312 .collect();
2313 editor
2314 .change_list
2315 .push_to_change_list(pop_state, new_positions);
2316 }
2317 }
2318 _ => (),
2319 },
2320 ));
2321
2322 if let Some(dap_store) = editor
2323 .project
2324 .as_ref()
2325 .map(|project| project.read(cx).dap_store())
2326 {
2327 let weak_editor = cx.weak_entity();
2328
2329 editor
2330 ._subscriptions
2331 .push(
2332 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2333 let session_entity = cx.entity();
2334 weak_editor
2335 .update(cx, |editor, cx| {
2336 editor._subscriptions.push(
2337 cx.subscribe(&session_entity, Self::on_debug_session_event),
2338 );
2339 })
2340 .ok();
2341 }),
2342 );
2343
2344 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2345 editor
2346 ._subscriptions
2347 .push(cx.subscribe(&session, Self::on_debug_session_event));
2348 }
2349 }
2350
2351 // skip adding the initial selection to selection history
2352 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2353 editor.end_selection(window, cx);
2354 editor.selection_history.mode = SelectionHistoryMode::Normal;
2355
2356 editor.scroll_manager.show_scrollbars(window, cx);
2357 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2358
2359 if full_mode {
2360 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2361 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2362
2363 if editor.git_blame_inline_enabled {
2364 editor.start_git_blame_inline(false, window, cx);
2365 }
2366
2367 editor.go_to_active_debug_line(window, cx);
2368
2369 if let Some(buffer) = buffer.read(cx).as_singleton()
2370 && let Some(project) = editor.project()
2371 {
2372 let handle = project.update(cx, |project, cx| {
2373 project.register_buffer_with_language_servers(&buffer, cx)
2374 });
2375 editor
2376 .registered_buffers
2377 .insert(buffer.read(cx).remote_id(), handle);
2378 }
2379
2380 editor.minimap =
2381 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2382 editor.colors = Some(LspColorData::new(cx));
2383 editor.update_lsp_data(false, None, window, cx);
2384 }
2385
2386 if editor.mode.is_full() {
2387 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2388 }
2389
2390 editor
2391 }
2392
2393 pub fn deploy_mouse_context_menu(
2394 &mut self,
2395 position: gpui::Point<Pixels>,
2396 context_menu: Entity<ContextMenu>,
2397 window: &mut Window,
2398 cx: &mut Context<Self>,
2399 ) {
2400 self.mouse_context_menu = Some(MouseContextMenu::new(
2401 self,
2402 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2403 context_menu,
2404 window,
2405 cx,
2406 ));
2407 }
2408
2409 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2410 self.mouse_context_menu
2411 .as_ref()
2412 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2413 }
2414
2415 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2416 if self
2417 .selections
2418 .pending
2419 .as_ref()
2420 .is_some_and(|pending_selection| {
2421 let snapshot = self.buffer().read(cx).snapshot(cx);
2422 pending_selection
2423 .selection
2424 .range()
2425 .includes(range, &snapshot)
2426 })
2427 {
2428 return true;
2429 }
2430
2431 self.selections
2432 .disjoint_in_range::<usize>(range.clone(), cx)
2433 .into_iter()
2434 .any(|selection| {
2435 // This is needed to cover a corner case, if we just check for an existing
2436 // selection in the fold range, having a cursor at the start of the fold
2437 // marks it as selected. Non-empty selections don't cause this.
2438 let length = selection.end - selection.start;
2439 length > 0
2440 })
2441 }
2442
2443 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2444 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2445 }
2446
2447 fn key_context_internal(
2448 &self,
2449 has_active_edit_prediction: bool,
2450 window: &Window,
2451 cx: &App,
2452 ) -> KeyContext {
2453 let mut key_context = KeyContext::new_with_defaults();
2454 key_context.add("Editor");
2455 let mode = match self.mode {
2456 EditorMode::SingleLine => "single_line",
2457 EditorMode::AutoHeight { .. } => "auto_height",
2458 EditorMode::Minimap { .. } => "minimap",
2459 EditorMode::Full { .. } => "full",
2460 };
2461
2462 if EditorSettings::jupyter_enabled(cx) {
2463 key_context.add("jupyter");
2464 }
2465
2466 key_context.set("mode", mode);
2467 if self.pending_rename.is_some() {
2468 key_context.add("renaming");
2469 }
2470
2471 match self.context_menu.borrow().as_ref() {
2472 Some(CodeContextMenu::Completions(menu)) => {
2473 if menu.visible() {
2474 key_context.add("menu");
2475 key_context.add("showing_completions");
2476 }
2477 }
2478 Some(CodeContextMenu::CodeActions(menu)) => {
2479 if menu.visible() {
2480 key_context.add("menu");
2481 key_context.add("showing_code_actions")
2482 }
2483 }
2484 None => {}
2485 }
2486
2487 if self.signature_help_state.has_multiple_signatures() {
2488 key_context.add("showing_signature_help");
2489 }
2490
2491 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2492 if !self.focus_handle(cx).contains_focused(window, cx)
2493 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2494 {
2495 for addon in self.addons.values() {
2496 addon.extend_key_context(&mut key_context, cx)
2497 }
2498 }
2499
2500 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2501 if let Some(extension) = singleton_buffer
2502 .read(cx)
2503 .file()
2504 .and_then(|file| file.path().extension()?.to_str())
2505 {
2506 key_context.set("extension", extension.to_string());
2507 }
2508 } else {
2509 key_context.add("multibuffer");
2510 }
2511
2512 if has_active_edit_prediction {
2513 if self.edit_prediction_in_conflict() {
2514 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2515 } else {
2516 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2517 key_context.add("copilot_suggestion");
2518 }
2519 }
2520
2521 if self.selection_mark_mode {
2522 key_context.add("selection_mode");
2523 }
2524
2525 key_context
2526 }
2527
2528 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2529 if self.mouse_cursor_hidden {
2530 self.mouse_cursor_hidden = false;
2531 cx.notify();
2532 }
2533 }
2534
2535 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2536 let hide_mouse_cursor = match origin {
2537 HideMouseCursorOrigin::TypingAction => {
2538 matches!(
2539 self.hide_mouse_mode,
2540 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2541 )
2542 }
2543 HideMouseCursorOrigin::MovementAction => {
2544 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2545 }
2546 };
2547 if self.mouse_cursor_hidden != hide_mouse_cursor {
2548 self.mouse_cursor_hidden = hide_mouse_cursor;
2549 cx.notify();
2550 }
2551 }
2552
2553 pub fn edit_prediction_in_conflict(&self) -> bool {
2554 if !self.show_edit_predictions_in_menu() {
2555 return false;
2556 }
2557
2558 let showing_completions = self
2559 .context_menu
2560 .borrow()
2561 .as_ref()
2562 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2563
2564 showing_completions
2565 || self.edit_prediction_requires_modifier()
2566 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2567 // bindings to insert tab characters.
2568 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2569 }
2570
2571 pub fn accept_edit_prediction_keybind(
2572 &self,
2573 accept_partial: bool,
2574 window: &Window,
2575 cx: &App,
2576 ) -> AcceptEditPredictionBinding {
2577 let key_context = self.key_context_internal(true, window, cx);
2578 let in_conflict = self.edit_prediction_in_conflict();
2579
2580 let bindings = if accept_partial {
2581 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2582 } else {
2583 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2584 };
2585
2586 // TODO: if the binding contains multiple keystrokes, display all of them, not
2587 // just the first one.
2588 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2589 !in_conflict
2590 || binding
2591 .keystrokes()
2592 .first()
2593 .is_some_and(|keystroke| keystroke.modifiers().modified())
2594 }))
2595 }
2596
2597 pub fn new_file(
2598 workspace: &mut Workspace,
2599 _: &workspace::NewFile,
2600 window: &mut Window,
2601 cx: &mut Context<Workspace>,
2602 ) {
2603 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2604 "Failed to create buffer",
2605 window,
2606 cx,
2607 |e, _, _| match e.error_code() {
2608 ErrorCode::RemoteUpgradeRequired => Some(format!(
2609 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2610 e.error_tag("required").unwrap_or("the latest version")
2611 )),
2612 _ => None,
2613 },
2614 );
2615 }
2616
2617 pub fn new_in_workspace(
2618 workspace: &mut Workspace,
2619 window: &mut Window,
2620 cx: &mut Context<Workspace>,
2621 ) -> Task<Result<Entity<Editor>>> {
2622 let project = workspace.project().clone();
2623 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2624
2625 cx.spawn_in(window, async move |workspace, cx| {
2626 let buffer = create.await?;
2627 workspace.update_in(cx, |workspace, window, cx| {
2628 let editor =
2629 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2630 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2631 editor
2632 })
2633 })
2634 }
2635
2636 fn new_file_vertical(
2637 workspace: &mut Workspace,
2638 _: &workspace::NewFileSplitVertical,
2639 window: &mut Window,
2640 cx: &mut Context<Workspace>,
2641 ) {
2642 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2643 }
2644
2645 fn new_file_horizontal(
2646 workspace: &mut Workspace,
2647 _: &workspace::NewFileSplitHorizontal,
2648 window: &mut Window,
2649 cx: &mut Context<Workspace>,
2650 ) {
2651 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2652 }
2653
2654 fn new_file_in_direction(
2655 workspace: &mut Workspace,
2656 direction: SplitDirection,
2657 window: &mut Window,
2658 cx: &mut Context<Workspace>,
2659 ) {
2660 let project = workspace.project().clone();
2661 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2662
2663 cx.spawn_in(window, async move |workspace, cx| {
2664 let buffer = create.await?;
2665 workspace.update_in(cx, move |workspace, window, cx| {
2666 workspace.split_item(
2667 direction,
2668 Box::new(
2669 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2670 ),
2671 window,
2672 cx,
2673 )
2674 })?;
2675 anyhow::Ok(())
2676 })
2677 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2678 match e.error_code() {
2679 ErrorCode::RemoteUpgradeRequired => Some(format!(
2680 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2681 e.error_tag("required").unwrap_or("the latest version")
2682 )),
2683 _ => None,
2684 }
2685 });
2686 }
2687
2688 pub fn leader_id(&self) -> Option<CollaboratorId> {
2689 self.leader_id
2690 }
2691
2692 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2693 &self.buffer
2694 }
2695
2696 pub fn project(&self) -> Option<&Entity<Project>> {
2697 self.project.as_ref()
2698 }
2699
2700 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2701 self.workspace.as_ref()?.0.upgrade()
2702 }
2703
2704 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2705 self.buffer().read(cx).title(cx)
2706 }
2707
2708 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2709 let git_blame_gutter_max_author_length = self
2710 .render_git_blame_gutter(cx)
2711 .then(|| {
2712 if let Some(blame) = self.blame.as_ref() {
2713 let max_author_length =
2714 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2715 Some(max_author_length)
2716 } else {
2717 None
2718 }
2719 })
2720 .flatten();
2721
2722 EditorSnapshot {
2723 mode: self.mode.clone(),
2724 show_gutter: self.show_gutter,
2725 show_line_numbers: self.show_line_numbers,
2726 show_git_diff_gutter: self.show_git_diff_gutter,
2727 show_code_actions: self.show_code_actions,
2728 show_runnables: self.show_runnables,
2729 show_breakpoints: self.show_breakpoints,
2730 git_blame_gutter_max_author_length,
2731 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2732 placeholder_display_snapshot: self
2733 .placeholder_display_map
2734 .as_ref()
2735 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2736 scroll_anchor: self.scroll_manager.anchor(),
2737 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2738 is_focused: self.focus_handle.is_focused(window),
2739 current_line_highlight: self
2740 .current_line_highlight
2741 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2742 gutter_hovered: self.gutter_hovered,
2743 }
2744 }
2745
2746 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2747 self.buffer.read(cx).language_at(point, cx)
2748 }
2749
2750 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2751 self.buffer.read(cx).read(cx).file_at(point).cloned()
2752 }
2753
2754 pub fn active_excerpt(
2755 &self,
2756 cx: &App,
2757 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2758 self.buffer
2759 .read(cx)
2760 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2761 }
2762
2763 pub fn mode(&self) -> &EditorMode {
2764 &self.mode
2765 }
2766
2767 pub fn set_mode(&mut self, mode: EditorMode) {
2768 self.mode = mode;
2769 }
2770
2771 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2772 self.collaboration_hub.as_deref()
2773 }
2774
2775 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2776 self.collaboration_hub = Some(hub);
2777 }
2778
2779 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2780 self.in_project_search = in_project_search;
2781 }
2782
2783 pub fn set_custom_context_menu(
2784 &mut self,
2785 f: impl 'static
2786 + Fn(
2787 &mut Self,
2788 DisplayPoint,
2789 &mut Window,
2790 &mut Context<Self>,
2791 ) -> Option<Entity<ui::ContextMenu>>,
2792 ) {
2793 self.custom_context_menu = Some(Box::new(f))
2794 }
2795
2796 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2797 self.completion_provider = provider;
2798 }
2799
2800 #[cfg(any(test, feature = "test-support"))]
2801 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2802 self.completion_provider.clone()
2803 }
2804
2805 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2806 self.semantics_provider.clone()
2807 }
2808
2809 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2810 self.semantics_provider = provider;
2811 }
2812
2813 pub fn set_edit_prediction_provider<T>(
2814 &mut self,
2815 provider: Option<Entity<T>>,
2816 window: &mut Window,
2817 cx: &mut Context<Self>,
2818 ) where
2819 T: EditPredictionProvider,
2820 {
2821 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2822 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2823 if this.focus_handle.is_focused(window) {
2824 this.update_visible_edit_prediction(window, cx);
2825 }
2826 }),
2827 provider: Arc::new(provider),
2828 });
2829 self.update_edit_prediction_settings(cx);
2830 self.refresh_edit_prediction(false, false, window, cx);
2831 }
2832
2833 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2834 self.placeholder_display_map
2835 .as_ref()
2836 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2837 }
2838
2839 pub fn set_placeholder_text(
2840 &mut self,
2841 placeholder_text: &str,
2842 window: &mut Window,
2843 cx: &mut Context<Self>,
2844 ) {
2845 let multibuffer = cx
2846 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2847
2848 let style = window.text_style();
2849
2850 self.placeholder_display_map = Some(cx.new(|cx| {
2851 DisplayMap::new(
2852 multibuffer,
2853 style.font(),
2854 style.font_size.to_pixels(window.rem_size()),
2855 None,
2856 FILE_HEADER_HEIGHT,
2857 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2858 Default::default(),
2859 DiagnosticSeverity::Off,
2860 cx,
2861 )
2862 }));
2863 cx.notify();
2864 }
2865
2866 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2867 self.cursor_shape = cursor_shape;
2868
2869 // Disrupt blink for immediate user feedback that the cursor shape has changed
2870 self.blink_manager.update(cx, BlinkManager::show_cursor);
2871
2872 cx.notify();
2873 }
2874
2875 pub fn set_current_line_highlight(
2876 &mut self,
2877 current_line_highlight: Option<CurrentLineHighlight>,
2878 ) {
2879 self.current_line_highlight = current_line_highlight;
2880 }
2881
2882 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2883 self.collapse_matches = collapse_matches;
2884 }
2885
2886 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2887 let buffers = self.buffer.read(cx).all_buffers();
2888 let Some(project) = self.project.as_ref() else {
2889 return;
2890 };
2891 project.update(cx, |project, cx| {
2892 for buffer in buffers {
2893 self.registered_buffers
2894 .entry(buffer.read(cx).remote_id())
2895 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2896 }
2897 })
2898 }
2899
2900 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2901 if self.collapse_matches {
2902 return range.start..range.start;
2903 }
2904 range.clone()
2905 }
2906
2907 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2908 if self.display_map.read(cx).clip_at_line_ends != clip {
2909 self.display_map
2910 .update(cx, |map, _| map.clip_at_line_ends = clip);
2911 }
2912 }
2913
2914 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2915 self.input_enabled = input_enabled;
2916 }
2917
2918 pub fn set_edit_predictions_hidden_for_vim_mode(
2919 &mut self,
2920 hidden: bool,
2921 window: &mut Window,
2922 cx: &mut Context<Self>,
2923 ) {
2924 if hidden != self.edit_predictions_hidden_for_vim_mode {
2925 self.edit_predictions_hidden_for_vim_mode = hidden;
2926 if hidden {
2927 self.update_visible_edit_prediction(window, cx);
2928 } else {
2929 self.refresh_edit_prediction(true, false, window, cx);
2930 }
2931 }
2932 }
2933
2934 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2935 self.menu_edit_predictions_policy = value;
2936 }
2937
2938 pub fn set_autoindent(&mut self, autoindent: bool) {
2939 if autoindent {
2940 self.autoindent_mode = Some(AutoindentMode::EachLine);
2941 } else {
2942 self.autoindent_mode = None;
2943 }
2944 }
2945
2946 pub fn read_only(&self, cx: &App) -> bool {
2947 self.read_only || self.buffer.read(cx).read_only()
2948 }
2949
2950 pub fn set_read_only(&mut self, read_only: bool) {
2951 self.read_only = read_only;
2952 }
2953
2954 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2955 self.use_autoclose = autoclose;
2956 }
2957
2958 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2959 self.use_auto_surround = auto_surround;
2960 }
2961
2962 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2963 self.auto_replace_emoji_shortcode = auto_replace;
2964 }
2965
2966 pub fn toggle_edit_predictions(
2967 &mut self,
2968 _: &ToggleEditPrediction,
2969 window: &mut Window,
2970 cx: &mut Context<Self>,
2971 ) {
2972 if self.show_edit_predictions_override.is_some() {
2973 self.set_show_edit_predictions(None, window, cx);
2974 } else {
2975 let show_edit_predictions = !self.edit_predictions_enabled();
2976 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2977 }
2978 }
2979
2980 pub fn set_show_edit_predictions(
2981 &mut self,
2982 show_edit_predictions: Option<bool>,
2983 window: &mut Window,
2984 cx: &mut Context<Self>,
2985 ) {
2986 self.show_edit_predictions_override = show_edit_predictions;
2987 self.update_edit_prediction_settings(cx);
2988
2989 if let Some(false) = show_edit_predictions {
2990 self.discard_edit_prediction(false, cx);
2991 } else {
2992 self.refresh_edit_prediction(false, true, window, cx);
2993 }
2994 }
2995
2996 fn edit_predictions_disabled_in_scope(
2997 &self,
2998 buffer: &Entity<Buffer>,
2999 buffer_position: language::Anchor,
3000 cx: &App,
3001 ) -> bool {
3002 let snapshot = buffer.read(cx).snapshot();
3003 let settings = snapshot.settings_at(buffer_position, cx);
3004
3005 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3006 return false;
3007 };
3008
3009 scope.override_name().is_some_and(|scope_name| {
3010 settings
3011 .edit_predictions_disabled_in
3012 .iter()
3013 .any(|s| s == scope_name)
3014 })
3015 }
3016
3017 pub fn set_use_modal_editing(&mut self, to: bool) {
3018 self.use_modal_editing = to;
3019 }
3020
3021 pub fn use_modal_editing(&self) -> bool {
3022 self.use_modal_editing
3023 }
3024
3025 fn selections_did_change(
3026 &mut self,
3027 local: bool,
3028 old_cursor_position: &Anchor,
3029 effects: SelectionEffects,
3030 window: &mut Window,
3031 cx: &mut Context<Self>,
3032 ) {
3033 window.invalidate_character_coordinates();
3034
3035 // Copy selections to primary selection buffer
3036 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3037 if local {
3038 let selections = self.selections.all::<usize>(cx);
3039 let buffer_handle = self.buffer.read(cx).read(cx);
3040
3041 let mut text = String::new();
3042 for (index, selection) in selections.iter().enumerate() {
3043 let text_for_selection = buffer_handle
3044 .text_for_range(selection.start..selection.end)
3045 .collect::<String>();
3046
3047 text.push_str(&text_for_selection);
3048 if index != selections.len() - 1 {
3049 text.push('\n');
3050 }
3051 }
3052
3053 if !text.is_empty() {
3054 cx.write_to_primary(ClipboardItem::new_string(text));
3055 }
3056 }
3057
3058 let selection_anchors = self.selections.disjoint_anchors();
3059
3060 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3061 self.buffer.update(cx, |buffer, cx| {
3062 buffer.set_active_selections(
3063 &selection_anchors,
3064 self.selections.line_mode,
3065 self.cursor_shape,
3066 cx,
3067 )
3068 });
3069 }
3070 let display_map = self
3071 .display_map
3072 .update(cx, |display_map, cx| display_map.snapshot(cx));
3073 let buffer = &display_map.buffer_snapshot;
3074 if self.selections.count() == 1 {
3075 self.add_selections_state = None;
3076 }
3077 self.select_next_state = None;
3078 self.select_prev_state = None;
3079 self.select_syntax_node_history.try_clear();
3080 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3081 self.snippet_stack.invalidate(&selection_anchors, buffer);
3082 self.take_rename(false, window, cx);
3083
3084 let newest_selection = self.selections.newest_anchor();
3085 let new_cursor_position = newest_selection.head();
3086 let selection_start = newest_selection.start;
3087
3088 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3089 self.push_to_nav_history(
3090 *old_cursor_position,
3091 Some(new_cursor_position.to_point(buffer)),
3092 false,
3093 effects.nav_history == Some(true),
3094 cx,
3095 );
3096 }
3097
3098 if local {
3099 if let Some(buffer_id) = new_cursor_position.buffer_id
3100 && !self.registered_buffers.contains_key(&buffer_id)
3101 && let Some(project) = self.project.as_ref()
3102 {
3103 project.update(cx, |project, cx| {
3104 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3105 return;
3106 };
3107 self.registered_buffers.insert(
3108 buffer_id,
3109 project.register_buffer_with_language_servers(&buffer, cx),
3110 );
3111 })
3112 }
3113
3114 let mut context_menu = self.context_menu.borrow_mut();
3115 let completion_menu = match context_menu.as_ref() {
3116 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3117 Some(CodeContextMenu::CodeActions(_)) => {
3118 *context_menu = None;
3119 None
3120 }
3121 None => None,
3122 };
3123 let completion_position = completion_menu.map(|menu| menu.initial_position);
3124 drop(context_menu);
3125
3126 if effects.completions
3127 && let Some(completion_position) = completion_position
3128 {
3129 let start_offset = selection_start.to_offset(buffer);
3130 let position_matches = start_offset == completion_position.to_offset(buffer);
3131 let continue_showing = if position_matches {
3132 if self.snippet_stack.is_empty() {
3133 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3134 } else {
3135 // Snippet choices can be shown even when the cursor is in whitespace.
3136 // Dismissing the menu with actions like backspace is handled by
3137 // invalidation regions.
3138 true
3139 }
3140 } else {
3141 false
3142 };
3143
3144 if continue_showing {
3145 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3146 } else {
3147 self.hide_context_menu(window, cx);
3148 }
3149 }
3150
3151 hide_hover(self, cx);
3152
3153 if old_cursor_position.to_display_point(&display_map).row()
3154 != new_cursor_position.to_display_point(&display_map).row()
3155 {
3156 self.available_code_actions.take();
3157 }
3158 self.refresh_code_actions(window, cx);
3159 self.refresh_document_highlights(cx);
3160 self.refresh_selected_text_highlights(false, window, cx);
3161 refresh_matching_bracket_highlights(self, window, cx);
3162 self.update_visible_edit_prediction(window, cx);
3163 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3164 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3165 self.inline_blame_popover.take();
3166 if self.git_blame_inline_enabled {
3167 self.start_inline_blame_timer(window, cx);
3168 }
3169 }
3170
3171 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3172 cx.emit(EditorEvent::SelectionsChanged { local });
3173
3174 let selections = &self.selections.disjoint;
3175 if selections.len() == 1 {
3176 cx.emit(SearchEvent::ActiveMatchChanged)
3177 }
3178 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3179 let inmemory_selections = selections
3180 .iter()
3181 .map(|s| {
3182 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3183 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3184 })
3185 .collect();
3186 self.update_restoration_data(cx, |data| {
3187 data.selections = inmemory_selections;
3188 });
3189
3190 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3191 && let Some(workspace_id) =
3192 self.workspace.as_ref().and_then(|workspace| workspace.1)
3193 {
3194 let snapshot = self.buffer().read(cx).snapshot(cx);
3195 let selections = selections.clone();
3196 let background_executor = cx.background_executor().clone();
3197 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3198 self.serialize_selections = cx.background_spawn(async move {
3199 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3200 let db_selections = selections
3201 .iter()
3202 .map(|selection| {
3203 (
3204 selection.start.to_offset(&snapshot),
3205 selection.end.to_offset(&snapshot),
3206 )
3207 })
3208 .collect();
3209
3210 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3211 .await
3212 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3213 .log_err();
3214 });
3215 }
3216 }
3217
3218 cx.notify();
3219 }
3220
3221 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3222 use text::ToOffset as _;
3223 use text::ToPoint as _;
3224
3225 if self.mode.is_minimap()
3226 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3227 {
3228 return;
3229 }
3230
3231 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3232 return;
3233 };
3234
3235 let snapshot = singleton.read(cx).snapshot();
3236 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3237 let display_snapshot = display_map.snapshot(cx);
3238
3239 display_snapshot
3240 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3241 .map(|fold| {
3242 fold.range.start.text_anchor.to_point(&snapshot)
3243 ..fold.range.end.text_anchor.to_point(&snapshot)
3244 })
3245 .collect()
3246 });
3247 self.update_restoration_data(cx, |data| {
3248 data.folds = inmemory_folds;
3249 });
3250
3251 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3252 return;
3253 };
3254 let background_executor = cx.background_executor().clone();
3255 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3256 let db_folds = self.display_map.update(cx, |display_map, cx| {
3257 display_map
3258 .snapshot(cx)
3259 .folds_in_range(0..snapshot.len())
3260 .map(|fold| {
3261 (
3262 fold.range.start.text_anchor.to_offset(&snapshot),
3263 fold.range.end.text_anchor.to_offset(&snapshot),
3264 )
3265 })
3266 .collect()
3267 });
3268 self.serialize_folds = cx.background_spawn(async move {
3269 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3270 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3271 .await
3272 .with_context(|| {
3273 format!(
3274 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3275 )
3276 })
3277 .log_err();
3278 });
3279 }
3280
3281 pub fn sync_selections(
3282 &mut self,
3283 other: Entity<Editor>,
3284 cx: &mut Context<Self>,
3285 ) -> gpui::Subscription {
3286 let other_selections = other.read(cx).selections.disjoint.to_vec();
3287 self.selections.change_with(cx, |selections| {
3288 selections.select_anchors(other_selections);
3289 });
3290
3291 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3292 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3293 let other_selections = other.read(cx).selections.disjoint.to_vec();
3294 if other_selections.is_empty() {
3295 return;
3296 }
3297 this.selections.change_with(cx, |selections| {
3298 selections.select_anchors(other_selections);
3299 });
3300 }
3301 });
3302
3303 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3304 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3305 let these_selections = this.selections.disjoint.to_vec();
3306 if these_selections.is_empty() {
3307 return;
3308 }
3309 other.update(cx, |other_editor, cx| {
3310 other_editor.selections.change_with(cx, |selections| {
3311 selections.select_anchors(these_selections);
3312 })
3313 });
3314 }
3315 });
3316
3317 Subscription::join(other_subscription, this_subscription)
3318 }
3319
3320 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3321 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3322 /// effects of selection change occur at the end of the transaction.
3323 pub fn change_selections<R>(
3324 &mut self,
3325 effects: SelectionEffects,
3326 window: &mut Window,
3327 cx: &mut Context<Self>,
3328 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3329 ) -> R {
3330 if let Some(state) = &mut self.deferred_selection_effects_state {
3331 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3332 state.effects.completions = effects.completions;
3333 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3334 let (changed, result) = self.selections.change_with(cx, change);
3335 state.changed |= changed;
3336 return result;
3337 }
3338 let mut state = DeferredSelectionEffectsState {
3339 changed: false,
3340 effects,
3341 old_cursor_position: self.selections.newest_anchor().head(),
3342 history_entry: SelectionHistoryEntry {
3343 selections: self.selections.disjoint_anchors(),
3344 select_next_state: self.select_next_state.clone(),
3345 select_prev_state: self.select_prev_state.clone(),
3346 add_selections_state: self.add_selections_state.clone(),
3347 },
3348 };
3349 let (changed, result) = self.selections.change_with(cx, change);
3350 state.changed = state.changed || changed;
3351 if self.defer_selection_effects {
3352 self.deferred_selection_effects_state = Some(state);
3353 } else {
3354 self.apply_selection_effects(state, window, cx);
3355 }
3356 result
3357 }
3358
3359 /// Defers the effects of selection change, so that the effects of multiple calls to
3360 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3361 /// to selection history and the state of popovers based on selection position aren't
3362 /// erroneously updated.
3363 pub fn with_selection_effects_deferred<R>(
3364 &mut self,
3365 window: &mut Window,
3366 cx: &mut Context<Self>,
3367 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3368 ) -> R {
3369 let already_deferred = self.defer_selection_effects;
3370 self.defer_selection_effects = true;
3371 let result = update(self, window, cx);
3372 if !already_deferred {
3373 self.defer_selection_effects = false;
3374 if let Some(state) = self.deferred_selection_effects_state.take() {
3375 self.apply_selection_effects(state, window, cx);
3376 }
3377 }
3378 result
3379 }
3380
3381 fn apply_selection_effects(
3382 &mut self,
3383 state: DeferredSelectionEffectsState,
3384 window: &mut Window,
3385 cx: &mut Context<Self>,
3386 ) {
3387 if state.changed {
3388 self.selection_history.push(state.history_entry);
3389
3390 if let Some(autoscroll) = state.effects.scroll {
3391 self.request_autoscroll(autoscroll, cx);
3392 }
3393
3394 let old_cursor_position = &state.old_cursor_position;
3395
3396 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3397
3398 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3399 self.show_signature_help(&ShowSignatureHelp, window, cx);
3400 }
3401 }
3402 }
3403
3404 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3405 where
3406 I: IntoIterator<Item = (Range<S>, T)>,
3407 S: ToOffset,
3408 T: Into<Arc<str>>,
3409 {
3410 if self.read_only(cx) {
3411 return;
3412 }
3413
3414 self.buffer
3415 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3416 }
3417
3418 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3419 where
3420 I: IntoIterator<Item = (Range<S>, T)>,
3421 S: ToOffset,
3422 T: Into<Arc<str>>,
3423 {
3424 if self.read_only(cx) {
3425 return;
3426 }
3427
3428 self.buffer.update(cx, |buffer, cx| {
3429 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3430 });
3431 }
3432
3433 pub fn edit_with_block_indent<I, S, T>(
3434 &mut self,
3435 edits: I,
3436 original_indent_columns: Vec<Option<u32>>,
3437 cx: &mut Context<Self>,
3438 ) where
3439 I: IntoIterator<Item = (Range<S>, T)>,
3440 S: ToOffset,
3441 T: Into<Arc<str>>,
3442 {
3443 if self.read_only(cx) {
3444 return;
3445 }
3446
3447 self.buffer.update(cx, |buffer, cx| {
3448 buffer.edit(
3449 edits,
3450 Some(AutoindentMode::Block {
3451 original_indent_columns,
3452 }),
3453 cx,
3454 )
3455 });
3456 }
3457
3458 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3459 self.hide_context_menu(window, cx);
3460
3461 match phase {
3462 SelectPhase::Begin {
3463 position,
3464 add,
3465 click_count,
3466 } => self.begin_selection(position, add, click_count, window, cx),
3467 SelectPhase::BeginColumnar {
3468 position,
3469 goal_column,
3470 reset,
3471 mode,
3472 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3473 SelectPhase::Extend {
3474 position,
3475 click_count,
3476 } => self.extend_selection(position, click_count, window, cx),
3477 SelectPhase::Update {
3478 position,
3479 goal_column,
3480 scroll_delta,
3481 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3482 SelectPhase::End => self.end_selection(window, cx),
3483 }
3484 }
3485
3486 fn extend_selection(
3487 &mut self,
3488 position: DisplayPoint,
3489 click_count: usize,
3490 window: &mut Window,
3491 cx: &mut Context<Self>,
3492 ) {
3493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3494 let tail = self.selections.newest::<usize>(cx).tail();
3495 self.begin_selection(position, false, click_count, window, cx);
3496
3497 let position = position.to_offset(&display_map, Bias::Left);
3498 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3499
3500 let mut pending_selection = self
3501 .selections
3502 .pending_anchor()
3503 .expect("extend_selection not called with pending selection");
3504 if position >= tail {
3505 pending_selection.start = tail_anchor;
3506 } else {
3507 pending_selection.end = tail_anchor;
3508 pending_selection.reversed = true;
3509 }
3510
3511 let mut pending_mode = self.selections.pending_mode().unwrap();
3512 match &mut pending_mode {
3513 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3514 _ => {}
3515 }
3516
3517 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3518 SelectionEffects::scroll(Autoscroll::fit())
3519 } else {
3520 SelectionEffects::no_scroll()
3521 };
3522
3523 self.change_selections(effects, window, cx, |s| {
3524 s.set_pending(pending_selection, pending_mode)
3525 });
3526 }
3527
3528 fn begin_selection(
3529 &mut self,
3530 position: DisplayPoint,
3531 add: bool,
3532 click_count: usize,
3533 window: &mut Window,
3534 cx: &mut Context<Self>,
3535 ) {
3536 if !self.focus_handle.is_focused(window) {
3537 self.last_focused_descendant = None;
3538 window.focus(&self.focus_handle);
3539 }
3540
3541 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3542 let buffer = &display_map.buffer_snapshot;
3543 let position = display_map.clip_point(position, Bias::Left);
3544
3545 let start;
3546 let end;
3547 let mode;
3548 let mut auto_scroll;
3549 match click_count {
3550 1 => {
3551 start = buffer.anchor_before(position.to_point(&display_map));
3552 end = start;
3553 mode = SelectMode::Character;
3554 auto_scroll = true;
3555 }
3556 2 => {
3557 let position = display_map
3558 .clip_point(position, Bias::Left)
3559 .to_offset(&display_map, Bias::Left);
3560 let (range, _) = buffer.surrounding_word(position, false);
3561 start = buffer.anchor_before(range.start);
3562 end = buffer.anchor_before(range.end);
3563 mode = SelectMode::Word(start..end);
3564 auto_scroll = true;
3565 }
3566 3 => {
3567 let position = display_map
3568 .clip_point(position, Bias::Left)
3569 .to_point(&display_map);
3570 let line_start = display_map.prev_line_boundary(position).0;
3571 let next_line_start = buffer.clip_point(
3572 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3573 Bias::Left,
3574 );
3575 start = buffer.anchor_before(line_start);
3576 end = buffer.anchor_before(next_line_start);
3577 mode = SelectMode::Line(start..end);
3578 auto_scroll = true;
3579 }
3580 _ => {
3581 start = buffer.anchor_before(0);
3582 end = buffer.anchor_before(buffer.len());
3583 mode = SelectMode::All;
3584 auto_scroll = false;
3585 }
3586 }
3587 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3588
3589 let point_to_delete: Option<usize> = {
3590 let selected_points: Vec<Selection<Point>> =
3591 self.selections.disjoint_in_range(start..end, cx);
3592
3593 if !add || click_count > 1 {
3594 None
3595 } else if !selected_points.is_empty() {
3596 Some(selected_points[0].id)
3597 } else {
3598 let clicked_point_already_selected =
3599 self.selections.disjoint.iter().find(|selection| {
3600 selection.start.to_point(buffer) == start.to_point(buffer)
3601 || selection.end.to_point(buffer) == end.to_point(buffer)
3602 });
3603
3604 clicked_point_already_selected.map(|selection| selection.id)
3605 }
3606 };
3607
3608 let selections_count = self.selections.count();
3609 let effects = if auto_scroll {
3610 SelectionEffects::default()
3611 } else {
3612 SelectionEffects::no_scroll()
3613 };
3614
3615 self.change_selections(effects, window, cx, |s| {
3616 if let Some(point_to_delete) = point_to_delete {
3617 s.delete(point_to_delete);
3618
3619 if selections_count == 1 {
3620 s.set_pending_anchor_range(start..end, mode);
3621 }
3622 } else {
3623 if !add {
3624 s.clear_disjoint();
3625 }
3626
3627 s.set_pending_anchor_range(start..end, mode);
3628 }
3629 });
3630 }
3631
3632 fn begin_columnar_selection(
3633 &mut self,
3634 position: DisplayPoint,
3635 goal_column: u32,
3636 reset: bool,
3637 mode: ColumnarMode,
3638 window: &mut Window,
3639 cx: &mut Context<Self>,
3640 ) {
3641 if !self.focus_handle.is_focused(window) {
3642 self.last_focused_descendant = None;
3643 window.focus(&self.focus_handle);
3644 }
3645
3646 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3647
3648 if reset {
3649 let pointer_position = display_map
3650 .buffer_snapshot
3651 .anchor_before(position.to_point(&display_map));
3652
3653 self.change_selections(
3654 SelectionEffects::scroll(Autoscroll::newest()),
3655 window,
3656 cx,
3657 |s| {
3658 s.clear_disjoint();
3659 s.set_pending_anchor_range(
3660 pointer_position..pointer_position,
3661 SelectMode::Character,
3662 );
3663 },
3664 );
3665 };
3666
3667 let tail = self.selections.newest::<Point>(cx).tail();
3668 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3669 self.columnar_selection_state = match mode {
3670 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3671 selection_tail: selection_anchor,
3672 display_point: if reset {
3673 if position.column() != goal_column {
3674 Some(DisplayPoint::new(position.row(), goal_column))
3675 } else {
3676 None
3677 }
3678 } else {
3679 None
3680 },
3681 }),
3682 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3683 selection_tail: selection_anchor,
3684 }),
3685 };
3686
3687 if !reset {
3688 self.select_columns(position, goal_column, &display_map, window, cx);
3689 }
3690 }
3691
3692 fn update_selection(
3693 &mut self,
3694 position: DisplayPoint,
3695 goal_column: u32,
3696 scroll_delta: gpui::Point<f32>,
3697 window: &mut Window,
3698 cx: &mut Context<Self>,
3699 ) {
3700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3701
3702 if self.columnar_selection_state.is_some() {
3703 self.select_columns(position, goal_column, &display_map, window, cx);
3704 } else if let Some(mut pending) = self.selections.pending_anchor() {
3705 let buffer = &display_map.buffer_snapshot;
3706 let head;
3707 let tail;
3708 let mode = self.selections.pending_mode().unwrap();
3709 match &mode {
3710 SelectMode::Character => {
3711 head = position.to_point(&display_map);
3712 tail = pending.tail().to_point(buffer);
3713 }
3714 SelectMode::Word(original_range) => {
3715 let offset = display_map
3716 .clip_point(position, Bias::Left)
3717 .to_offset(&display_map, Bias::Left);
3718 let original_range = original_range.to_offset(buffer);
3719
3720 let head_offset = if buffer.is_inside_word(offset, false)
3721 || original_range.contains(&offset)
3722 {
3723 let (word_range, _) = buffer.surrounding_word(offset, false);
3724 if word_range.start < original_range.start {
3725 word_range.start
3726 } else {
3727 word_range.end
3728 }
3729 } else {
3730 offset
3731 };
3732
3733 head = head_offset.to_point(buffer);
3734 if head_offset <= original_range.start {
3735 tail = original_range.end.to_point(buffer);
3736 } else {
3737 tail = original_range.start.to_point(buffer);
3738 }
3739 }
3740 SelectMode::Line(original_range) => {
3741 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3742
3743 let position = display_map
3744 .clip_point(position, Bias::Left)
3745 .to_point(&display_map);
3746 let line_start = display_map.prev_line_boundary(position).0;
3747 let next_line_start = buffer.clip_point(
3748 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3749 Bias::Left,
3750 );
3751
3752 if line_start < original_range.start {
3753 head = line_start
3754 } else {
3755 head = next_line_start
3756 }
3757
3758 if head <= original_range.start {
3759 tail = original_range.end;
3760 } else {
3761 tail = original_range.start;
3762 }
3763 }
3764 SelectMode::All => {
3765 return;
3766 }
3767 };
3768
3769 if head < tail {
3770 pending.start = buffer.anchor_before(head);
3771 pending.end = buffer.anchor_before(tail);
3772 pending.reversed = true;
3773 } else {
3774 pending.start = buffer.anchor_before(tail);
3775 pending.end = buffer.anchor_before(head);
3776 pending.reversed = false;
3777 }
3778
3779 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3780 s.set_pending(pending, mode);
3781 });
3782 } else {
3783 log::error!("update_selection dispatched with no pending selection");
3784 return;
3785 }
3786
3787 self.apply_scroll_delta(scroll_delta, window, cx);
3788 cx.notify();
3789 }
3790
3791 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3792 self.columnar_selection_state.take();
3793 if self.selections.pending_anchor().is_some() {
3794 let selections = self.selections.all::<usize>(cx);
3795 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3796 s.select(selections);
3797 s.clear_pending();
3798 });
3799 }
3800 }
3801
3802 fn select_columns(
3803 &mut self,
3804 head: DisplayPoint,
3805 goal_column: u32,
3806 display_map: &DisplaySnapshot,
3807 window: &mut Window,
3808 cx: &mut Context<Self>,
3809 ) {
3810 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3811 return;
3812 };
3813
3814 let tail = match columnar_state {
3815 ColumnarSelectionState::FromMouse {
3816 selection_tail,
3817 display_point,
3818 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3819 ColumnarSelectionState::FromSelection { selection_tail } => {
3820 selection_tail.to_display_point(display_map)
3821 }
3822 };
3823
3824 let start_row = cmp::min(tail.row(), head.row());
3825 let end_row = cmp::max(tail.row(), head.row());
3826 let start_column = cmp::min(tail.column(), goal_column);
3827 let end_column = cmp::max(tail.column(), goal_column);
3828 let reversed = start_column < tail.column();
3829
3830 let selection_ranges = (start_row.0..=end_row.0)
3831 .map(DisplayRow)
3832 .filter_map(|row| {
3833 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3834 || start_column <= display_map.line_len(row))
3835 && !display_map.is_block_line(row)
3836 {
3837 let start = display_map
3838 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3839 .to_point(display_map);
3840 let end = display_map
3841 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3842 .to_point(display_map);
3843 if reversed {
3844 Some(end..start)
3845 } else {
3846 Some(start..end)
3847 }
3848 } else {
3849 None
3850 }
3851 })
3852 .collect::<Vec<_>>();
3853
3854 let ranges = match columnar_state {
3855 ColumnarSelectionState::FromMouse { .. } => {
3856 let mut non_empty_ranges = selection_ranges
3857 .iter()
3858 .filter(|selection_range| selection_range.start != selection_range.end)
3859 .peekable();
3860 if non_empty_ranges.peek().is_some() {
3861 non_empty_ranges.cloned().collect()
3862 } else {
3863 selection_ranges
3864 }
3865 }
3866 _ => selection_ranges,
3867 };
3868
3869 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3870 s.select_ranges(ranges);
3871 });
3872 cx.notify();
3873 }
3874
3875 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3876 self.selections
3877 .all_adjusted(cx)
3878 .iter()
3879 .any(|selection| !selection.is_empty())
3880 }
3881
3882 pub fn has_pending_nonempty_selection(&self) -> bool {
3883 let pending_nonempty_selection = match self.selections.pending_anchor() {
3884 Some(Selection { start, end, .. }) => start != end,
3885 None => false,
3886 };
3887
3888 pending_nonempty_selection
3889 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3890 }
3891
3892 pub fn has_pending_selection(&self) -> bool {
3893 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3894 }
3895
3896 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3897 self.selection_mark_mode = false;
3898 self.selection_drag_state = SelectionDragState::None;
3899
3900 if self.clear_expanded_diff_hunks(cx) {
3901 cx.notify();
3902 return;
3903 }
3904 if self.dismiss_menus_and_popups(true, window, cx) {
3905 return;
3906 }
3907
3908 if self.mode.is_full()
3909 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3910 {
3911 return;
3912 }
3913
3914 cx.propagate();
3915 }
3916
3917 pub fn dismiss_menus_and_popups(
3918 &mut self,
3919 is_user_requested: bool,
3920 window: &mut Window,
3921 cx: &mut Context<Self>,
3922 ) -> bool {
3923 if self.take_rename(false, window, cx).is_some() {
3924 return true;
3925 }
3926
3927 if hide_hover(self, cx) {
3928 return true;
3929 }
3930
3931 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3932 return true;
3933 }
3934
3935 if self.hide_context_menu(window, cx).is_some() {
3936 return true;
3937 }
3938
3939 if self.mouse_context_menu.take().is_some() {
3940 return true;
3941 }
3942
3943 if is_user_requested && self.discard_edit_prediction(true, cx) {
3944 return true;
3945 }
3946
3947 if self.snippet_stack.pop().is_some() {
3948 return true;
3949 }
3950
3951 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3952 self.dismiss_diagnostics(cx);
3953 return true;
3954 }
3955
3956 false
3957 }
3958
3959 fn linked_editing_ranges_for(
3960 &self,
3961 selection: Range<text::Anchor>,
3962 cx: &App,
3963 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3964 if self.linked_edit_ranges.is_empty() {
3965 return None;
3966 }
3967 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3968 selection.end.buffer_id.and_then(|end_buffer_id| {
3969 if selection.start.buffer_id != Some(end_buffer_id) {
3970 return None;
3971 }
3972 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3973 let snapshot = buffer.read(cx).snapshot();
3974 self.linked_edit_ranges
3975 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3976 .map(|ranges| (ranges, snapshot, buffer))
3977 })?;
3978 use text::ToOffset as TO;
3979 // find offset from the start of current range to current cursor position
3980 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3981
3982 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3983 let start_difference = start_offset - start_byte_offset;
3984 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3985 let end_difference = end_offset - start_byte_offset;
3986 // Current range has associated linked ranges.
3987 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3988 for range in linked_ranges.iter() {
3989 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3990 let end_offset = start_offset + end_difference;
3991 let start_offset = start_offset + start_difference;
3992 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3993 continue;
3994 }
3995 if self.selections.disjoint_anchor_ranges().any(|s| {
3996 if s.start.buffer_id != selection.start.buffer_id
3997 || s.end.buffer_id != selection.end.buffer_id
3998 {
3999 return false;
4000 }
4001 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4002 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4003 }) {
4004 continue;
4005 }
4006 let start = buffer_snapshot.anchor_after(start_offset);
4007 let end = buffer_snapshot.anchor_after(end_offset);
4008 linked_edits
4009 .entry(buffer.clone())
4010 .or_default()
4011 .push(start..end);
4012 }
4013 Some(linked_edits)
4014 }
4015
4016 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4017 let text: Arc<str> = text.into();
4018
4019 if self.read_only(cx) {
4020 return;
4021 }
4022
4023 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4024
4025 let selections = self.selections.all_adjusted(cx);
4026 let mut bracket_inserted = false;
4027 let mut edits = Vec::new();
4028 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4029 let mut new_selections = Vec::with_capacity(selections.len());
4030 let mut new_autoclose_regions = Vec::new();
4031 let snapshot = self.buffer.read(cx).read(cx);
4032 let mut clear_linked_edit_ranges = false;
4033
4034 for (selection, autoclose_region) in
4035 self.selections_with_autoclose_regions(selections, &snapshot)
4036 {
4037 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4038 // Determine if the inserted text matches the opening or closing
4039 // bracket of any of this language's bracket pairs.
4040 let mut bracket_pair = None;
4041 let mut is_bracket_pair_start = false;
4042 let mut is_bracket_pair_end = false;
4043 if !text.is_empty() {
4044 let mut bracket_pair_matching_end = None;
4045 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4046 // and they are removing the character that triggered IME popup.
4047 for (pair, enabled) in scope.brackets() {
4048 if !pair.close && !pair.surround {
4049 continue;
4050 }
4051
4052 if enabled && pair.start.ends_with(text.as_ref()) {
4053 let prefix_len = pair.start.len() - text.len();
4054 let preceding_text_matches_prefix = prefix_len == 0
4055 || (selection.start.column >= (prefix_len as u32)
4056 && snapshot.contains_str_at(
4057 Point::new(
4058 selection.start.row,
4059 selection.start.column - (prefix_len as u32),
4060 ),
4061 &pair.start[..prefix_len],
4062 ));
4063 if preceding_text_matches_prefix {
4064 bracket_pair = Some(pair.clone());
4065 is_bracket_pair_start = true;
4066 break;
4067 }
4068 }
4069 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4070 {
4071 // take first bracket pair matching end, but don't break in case a later bracket
4072 // pair matches start
4073 bracket_pair_matching_end = Some(pair.clone());
4074 }
4075 }
4076 if let Some(end) = bracket_pair_matching_end
4077 && bracket_pair.is_none()
4078 {
4079 bracket_pair = Some(end);
4080 is_bracket_pair_end = true;
4081 }
4082 }
4083
4084 if let Some(bracket_pair) = bracket_pair {
4085 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4086 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4087 let auto_surround =
4088 self.use_auto_surround && snapshot_settings.use_auto_surround;
4089 if selection.is_empty() {
4090 if is_bracket_pair_start {
4091 // If the inserted text is a suffix of an opening bracket and the
4092 // selection is preceded by the rest of the opening bracket, then
4093 // insert the closing bracket.
4094 let following_text_allows_autoclose = snapshot
4095 .chars_at(selection.start)
4096 .next()
4097 .is_none_or(|c| scope.should_autoclose_before(c));
4098
4099 let preceding_text_allows_autoclose = selection.start.column == 0
4100 || snapshot
4101 .reversed_chars_at(selection.start)
4102 .next()
4103 .is_none_or(|c| {
4104 bracket_pair.start != bracket_pair.end
4105 || !snapshot
4106 .char_classifier_at(selection.start)
4107 .is_word(c)
4108 });
4109
4110 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4111 && bracket_pair.start.len() == 1
4112 {
4113 let target = bracket_pair.start.chars().next().unwrap();
4114 let current_line_count = snapshot
4115 .reversed_chars_at(selection.start)
4116 .take_while(|&c| c != '\n')
4117 .filter(|&c| c == target)
4118 .count();
4119 current_line_count % 2 == 1
4120 } else {
4121 false
4122 };
4123
4124 if autoclose
4125 && bracket_pair.close
4126 && following_text_allows_autoclose
4127 && preceding_text_allows_autoclose
4128 && !is_closing_quote
4129 {
4130 let anchor = snapshot.anchor_before(selection.end);
4131 new_selections.push((selection.map(|_| anchor), text.len()));
4132 new_autoclose_regions.push((
4133 anchor,
4134 text.len(),
4135 selection.id,
4136 bracket_pair.clone(),
4137 ));
4138 edits.push((
4139 selection.range(),
4140 format!("{}{}", text, bracket_pair.end).into(),
4141 ));
4142 bracket_inserted = true;
4143 continue;
4144 }
4145 }
4146
4147 if let Some(region) = autoclose_region {
4148 // If the selection is followed by an auto-inserted closing bracket,
4149 // then don't insert that closing bracket again; just move the selection
4150 // past the closing bracket.
4151 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4152 && text.as_ref() == region.pair.end.as_str()
4153 && snapshot.contains_str_at(region.range.end, text.as_ref());
4154 if should_skip {
4155 let anchor = snapshot.anchor_after(selection.end);
4156 new_selections
4157 .push((selection.map(|_| anchor), region.pair.end.len()));
4158 continue;
4159 }
4160 }
4161
4162 let always_treat_brackets_as_autoclosed = snapshot
4163 .language_settings_at(selection.start, cx)
4164 .always_treat_brackets_as_autoclosed;
4165 if always_treat_brackets_as_autoclosed
4166 && is_bracket_pair_end
4167 && snapshot.contains_str_at(selection.end, text.as_ref())
4168 {
4169 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4170 // and the inserted text is a closing bracket and the selection is followed
4171 // by the closing bracket then move the selection past the closing bracket.
4172 let anchor = snapshot.anchor_after(selection.end);
4173 new_selections.push((selection.map(|_| anchor), text.len()));
4174 continue;
4175 }
4176 }
4177 // If an opening bracket is 1 character long and is typed while
4178 // text is selected, then surround that text with the bracket pair.
4179 else if auto_surround
4180 && bracket_pair.surround
4181 && is_bracket_pair_start
4182 && bracket_pair.start.chars().count() == 1
4183 {
4184 edits.push((selection.start..selection.start, text.clone()));
4185 edits.push((
4186 selection.end..selection.end,
4187 bracket_pair.end.as_str().into(),
4188 ));
4189 bracket_inserted = true;
4190 new_selections.push((
4191 Selection {
4192 id: selection.id,
4193 start: snapshot.anchor_after(selection.start),
4194 end: snapshot.anchor_before(selection.end),
4195 reversed: selection.reversed,
4196 goal: selection.goal,
4197 },
4198 0,
4199 ));
4200 continue;
4201 }
4202 }
4203 }
4204
4205 if self.auto_replace_emoji_shortcode
4206 && selection.is_empty()
4207 && text.as_ref().ends_with(':')
4208 && let Some(possible_emoji_short_code) =
4209 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4210 && !possible_emoji_short_code.is_empty()
4211 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4212 {
4213 let emoji_shortcode_start = Point::new(
4214 selection.start.row,
4215 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4216 );
4217
4218 // Remove shortcode from buffer
4219 edits.push((
4220 emoji_shortcode_start..selection.start,
4221 "".to_string().into(),
4222 ));
4223 new_selections.push((
4224 Selection {
4225 id: selection.id,
4226 start: snapshot.anchor_after(emoji_shortcode_start),
4227 end: snapshot.anchor_before(selection.start),
4228 reversed: selection.reversed,
4229 goal: selection.goal,
4230 },
4231 0,
4232 ));
4233
4234 // Insert emoji
4235 let selection_start_anchor = snapshot.anchor_after(selection.start);
4236 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4237 edits.push((selection.start..selection.end, emoji.to_string().into()));
4238
4239 continue;
4240 }
4241
4242 // If not handling any auto-close operation, then just replace the selected
4243 // text with the given input and move the selection to the end of the
4244 // newly inserted text.
4245 let anchor = snapshot.anchor_after(selection.end);
4246 if !self.linked_edit_ranges.is_empty() {
4247 let start_anchor = snapshot.anchor_before(selection.start);
4248
4249 let is_word_char = text.chars().next().is_none_or(|char| {
4250 let classifier = snapshot
4251 .char_classifier_at(start_anchor.to_offset(&snapshot))
4252 .ignore_punctuation(true);
4253 classifier.is_word(char)
4254 });
4255
4256 if is_word_char {
4257 if let Some(ranges) = self
4258 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4259 {
4260 for (buffer, edits) in ranges {
4261 linked_edits
4262 .entry(buffer.clone())
4263 .or_default()
4264 .extend(edits.into_iter().map(|range| (range, text.clone())));
4265 }
4266 }
4267 } else {
4268 clear_linked_edit_ranges = true;
4269 }
4270 }
4271
4272 new_selections.push((selection.map(|_| anchor), 0));
4273 edits.push((selection.start..selection.end, text.clone()));
4274 }
4275
4276 drop(snapshot);
4277
4278 self.transact(window, cx, |this, window, cx| {
4279 if clear_linked_edit_ranges {
4280 this.linked_edit_ranges.clear();
4281 }
4282 let initial_buffer_versions =
4283 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4284
4285 this.buffer.update(cx, |buffer, cx| {
4286 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4287 });
4288 for (buffer, edits) in linked_edits {
4289 buffer.update(cx, |buffer, cx| {
4290 let snapshot = buffer.snapshot();
4291 let edits = edits
4292 .into_iter()
4293 .map(|(range, text)| {
4294 use text::ToPoint as TP;
4295 let end_point = TP::to_point(&range.end, &snapshot);
4296 let start_point = TP::to_point(&range.start, &snapshot);
4297 (start_point..end_point, text)
4298 })
4299 .sorted_by_key(|(range, _)| range.start);
4300 buffer.edit(edits, None, cx);
4301 })
4302 }
4303 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4304 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4305 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4306 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4307 .zip(new_selection_deltas)
4308 .map(|(selection, delta)| Selection {
4309 id: selection.id,
4310 start: selection.start + delta,
4311 end: selection.end + delta,
4312 reversed: selection.reversed,
4313 goal: SelectionGoal::None,
4314 })
4315 .collect::<Vec<_>>();
4316
4317 let mut i = 0;
4318 for (position, delta, selection_id, pair) in new_autoclose_regions {
4319 let position = position.to_offset(&map.buffer_snapshot) + delta;
4320 let start = map.buffer_snapshot.anchor_before(position);
4321 let end = map.buffer_snapshot.anchor_after(position);
4322 while let Some(existing_state) = this.autoclose_regions.get(i) {
4323 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4324 Ordering::Less => i += 1,
4325 Ordering::Greater => break,
4326 Ordering::Equal => {
4327 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4328 Ordering::Less => i += 1,
4329 Ordering::Equal => break,
4330 Ordering::Greater => break,
4331 }
4332 }
4333 }
4334 }
4335 this.autoclose_regions.insert(
4336 i,
4337 AutocloseRegion {
4338 selection_id,
4339 range: start..end,
4340 pair,
4341 },
4342 );
4343 }
4344
4345 let had_active_edit_prediction = this.has_active_edit_prediction();
4346 this.change_selections(
4347 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4348 window,
4349 cx,
4350 |s| s.select(new_selections),
4351 );
4352
4353 if !bracket_inserted
4354 && let Some(on_type_format_task) =
4355 this.trigger_on_type_formatting(text.to_string(), window, cx)
4356 {
4357 on_type_format_task.detach_and_log_err(cx);
4358 }
4359
4360 let editor_settings = EditorSettings::get_global(cx);
4361 if bracket_inserted
4362 && (editor_settings.auto_signature_help
4363 || editor_settings.show_signature_help_after_edits)
4364 {
4365 this.show_signature_help(&ShowSignatureHelp, window, cx);
4366 }
4367
4368 let trigger_in_words =
4369 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4370 if this.hard_wrap.is_some() {
4371 let latest: Range<Point> = this.selections.newest(cx).range();
4372 if latest.is_empty()
4373 && this
4374 .buffer()
4375 .read(cx)
4376 .snapshot(cx)
4377 .line_len(MultiBufferRow(latest.start.row))
4378 == latest.start.column
4379 {
4380 this.rewrap_impl(
4381 RewrapOptions {
4382 override_language_settings: true,
4383 preserve_existing_whitespace: true,
4384 },
4385 cx,
4386 )
4387 }
4388 }
4389 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4390 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4391 this.refresh_edit_prediction(true, false, window, cx);
4392 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4393 });
4394 }
4395
4396 fn find_possible_emoji_shortcode_at_position(
4397 snapshot: &MultiBufferSnapshot,
4398 position: Point,
4399 ) -> Option<String> {
4400 let mut chars = Vec::new();
4401 let mut found_colon = false;
4402 for char in snapshot.reversed_chars_at(position).take(100) {
4403 // Found a possible emoji shortcode in the middle of the buffer
4404 if found_colon {
4405 if char.is_whitespace() {
4406 chars.reverse();
4407 return Some(chars.iter().collect());
4408 }
4409 // If the previous character is not a whitespace, we are in the middle of a word
4410 // and we only want to complete the shortcode if the word is made up of other emojis
4411 let mut containing_word = String::new();
4412 for ch in snapshot
4413 .reversed_chars_at(position)
4414 .skip(chars.len() + 1)
4415 .take(100)
4416 {
4417 if ch.is_whitespace() {
4418 break;
4419 }
4420 containing_word.push(ch);
4421 }
4422 let containing_word = containing_word.chars().rev().collect::<String>();
4423 if util::word_consists_of_emojis(containing_word.as_str()) {
4424 chars.reverse();
4425 return Some(chars.iter().collect());
4426 }
4427 }
4428
4429 if char.is_whitespace() || !char.is_ascii() {
4430 return None;
4431 }
4432 if char == ':' {
4433 found_colon = true;
4434 } else {
4435 chars.push(char);
4436 }
4437 }
4438 // Found a possible emoji shortcode at the beginning of the buffer
4439 chars.reverse();
4440 Some(chars.iter().collect())
4441 }
4442
4443 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4444 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4445 self.transact(window, cx, |this, window, cx| {
4446 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4447 let selections = this.selections.all::<usize>(cx);
4448 let multi_buffer = this.buffer.read(cx);
4449 let buffer = multi_buffer.snapshot(cx);
4450 selections
4451 .iter()
4452 .map(|selection| {
4453 let start_point = selection.start.to_point(&buffer);
4454 let mut existing_indent =
4455 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4456 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4457 let start = selection.start;
4458 let end = selection.end;
4459 let selection_is_empty = start == end;
4460 let language_scope = buffer.language_scope_at(start);
4461 let (
4462 comment_delimiter,
4463 doc_delimiter,
4464 insert_extra_newline,
4465 indent_on_newline,
4466 indent_on_extra_newline,
4467 ) = if let Some(language) = &language_scope {
4468 let mut insert_extra_newline =
4469 insert_extra_newline_brackets(&buffer, start..end, language)
4470 || insert_extra_newline_tree_sitter(&buffer, start..end);
4471
4472 // Comment extension on newline is allowed only for cursor selections
4473 let comment_delimiter = maybe!({
4474 if !selection_is_empty {
4475 return None;
4476 }
4477
4478 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4479 return None;
4480 }
4481
4482 let delimiters = language.line_comment_prefixes();
4483 let max_len_of_delimiter =
4484 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4485 let (snapshot, range) =
4486 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4487
4488 let num_of_whitespaces = snapshot
4489 .chars_for_range(range.clone())
4490 .take_while(|c| c.is_whitespace())
4491 .count();
4492 let comment_candidate = snapshot
4493 .chars_for_range(range.clone())
4494 .skip(num_of_whitespaces)
4495 .take(max_len_of_delimiter)
4496 .collect::<String>();
4497 let (delimiter, trimmed_len) = delimiters
4498 .iter()
4499 .filter_map(|delimiter| {
4500 let prefix = delimiter.trim_end();
4501 if comment_candidate.starts_with(prefix) {
4502 Some((delimiter, prefix.len()))
4503 } else {
4504 None
4505 }
4506 })
4507 .max_by_key(|(_, len)| *len)?;
4508
4509 if let Some(BlockCommentConfig {
4510 start: block_start, ..
4511 }) = language.block_comment()
4512 {
4513 let block_start_trimmed = block_start.trim_end();
4514 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4515 let line_content = snapshot
4516 .chars_for_range(range)
4517 .skip(num_of_whitespaces)
4518 .take(block_start_trimmed.len())
4519 .collect::<String>();
4520
4521 if line_content.starts_with(block_start_trimmed) {
4522 return None;
4523 }
4524 }
4525 }
4526
4527 let cursor_is_placed_after_comment_marker =
4528 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4529 if cursor_is_placed_after_comment_marker {
4530 Some(delimiter.clone())
4531 } else {
4532 None
4533 }
4534 });
4535
4536 let mut indent_on_newline = IndentSize::spaces(0);
4537 let mut indent_on_extra_newline = IndentSize::spaces(0);
4538
4539 let doc_delimiter = maybe!({
4540 if !selection_is_empty {
4541 return None;
4542 }
4543
4544 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4545 return None;
4546 }
4547
4548 let BlockCommentConfig {
4549 start: start_tag,
4550 end: end_tag,
4551 prefix: delimiter,
4552 tab_size: len,
4553 } = language.documentation_comment()?;
4554 let is_within_block_comment = buffer
4555 .language_scope_at(start_point)
4556 .is_some_and(|scope| scope.override_name() == Some("comment"));
4557 if !is_within_block_comment {
4558 return None;
4559 }
4560
4561 let (snapshot, range) =
4562 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4563
4564 let num_of_whitespaces = snapshot
4565 .chars_for_range(range.clone())
4566 .take_while(|c| c.is_whitespace())
4567 .count();
4568
4569 // 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.
4570 let column = start_point.column;
4571 let cursor_is_after_start_tag = {
4572 let start_tag_len = start_tag.len();
4573 let start_tag_line = snapshot
4574 .chars_for_range(range.clone())
4575 .skip(num_of_whitespaces)
4576 .take(start_tag_len)
4577 .collect::<String>();
4578 if start_tag_line.starts_with(start_tag.as_ref()) {
4579 num_of_whitespaces + start_tag_len <= column as usize
4580 } else {
4581 false
4582 }
4583 };
4584
4585 let cursor_is_after_delimiter = {
4586 let delimiter_trim = delimiter.trim_end();
4587 let delimiter_line = snapshot
4588 .chars_for_range(range.clone())
4589 .skip(num_of_whitespaces)
4590 .take(delimiter_trim.len())
4591 .collect::<String>();
4592 if delimiter_line.starts_with(delimiter_trim) {
4593 num_of_whitespaces + delimiter_trim.len() <= column as usize
4594 } else {
4595 false
4596 }
4597 };
4598
4599 let cursor_is_before_end_tag_if_exists = {
4600 let mut char_position = 0u32;
4601 let mut end_tag_offset = None;
4602
4603 'outer: for chunk in snapshot.text_for_range(range) {
4604 if let Some(byte_pos) = chunk.find(&**end_tag) {
4605 let chars_before_match =
4606 chunk[..byte_pos].chars().count() as u32;
4607 end_tag_offset =
4608 Some(char_position + chars_before_match);
4609 break 'outer;
4610 }
4611 char_position += chunk.chars().count() as u32;
4612 }
4613
4614 if let Some(end_tag_offset) = end_tag_offset {
4615 let cursor_is_before_end_tag = column <= end_tag_offset;
4616 if cursor_is_after_start_tag {
4617 if cursor_is_before_end_tag {
4618 insert_extra_newline = true;
4619 }
4620 let cursor_is_at_start_of_end_tag =
4621 column == end_tag_offset;
4622 if cursor_is_at_start_of_end_tag {
4623 indent_on_extra_newline.len = *len;
4624 }
4625 }
4626 cursor_is_before_end_tag
4627 } else {
4628 true
4629 }
4630 };
4631
4632 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4633 && cursor_is_before_end_tag_if_exists
4634 {
4635 if cursor_is_after_start_tag {
4636 indent_on_newline.len = *len;
4637 }
4638 Some(delimiter.clone())
4639 } else {
4640 None
4641 }
4642 });
4643
4644 (
4645 comment_delimiter,
4646 doc_delimiter,
4647 insert_extra_newline,
4648 indent_on_newline,
4649 indent_on_extra_newline,
4650 )
4651 } else {
4652 (
4653 None,
4654 None,
4655 false,
4656 IndentSize::default(),
4657 IndentSize::default(),
4658 )
4659 };
4660
4661 let prevent_auto_indent = doc_delimiter.is_some();
4662 let delimiter = comment_delimiter.or(doc_delimiter);
4663
4664 let capacity_for_delimiter =
4665 delimiter.as_deref().map(str::len).unwrap_or_default();
4666 let mut new_text = String::with_capacity(
4667 1 + capacity_for_delimiter
4668 + existing_indent.len as usize
4669 + indent_on_newline.len as usize
4670 + indent_on_extra_newline.len as usize,
4671 );
4672 new_text.push('\n');
4673 new_text.extend(existing_indent.chars());
4674 new_text.extend(indent_on_newline.chars());
4675
4676 if let Some(delimiter) = &delimiter {
4677 new_text.push_str(delimiter);
4678 }
4679
4680 if insert_extra_newline {
4681 new_text.push('\n');
4682 new_text.extend(existing_indent.chars());
4683 new_text.extend(indent_on_extra_newline.chars());
4684 }
4685
4686 let anchor = buffer.anchor_after(end);
4687 let new_selection = selection.map(|_| anchor);
4688 (
4689 ((start..end, new_text), prevent_auto_indent),
4690 (insert_extra_newline, new_selection),
4691 )
4692 })
4693 .unzip()
4694 };
4695
4696 let mut auto_indent_edits = Vec::new();
4697 let mut edits = Vec::new();
4698 for (edit, prevent_auto_indent) in edits_with_flags {
4699 if prevent_auto_indent {
4700 edits.push(edit);
4701 } else {
4702 auto_indent_edits.push(edit);
4703 }
4704 }
4705 if !edits.is_empty() {
4706 this.edit(edits, cx);
4707 }
4708 if !auto_indent_edits.is_empty() {
4709 this.edit_with_autoindent(auto_indent_edits, cx);
4710 }
4711
4712 let buffer = this.buffer.read(cx).snapshot(cx);
4713 let new_selections = selection_info
4714 .into_iter()
4715 .map(|(extra_newline_inserted, new_selection)| {
4716 let mut cursor = new_selection.end.to_point(&buffer);
4717 if extra_newline_inserted {
4718 cursor.row -= 1;
4719 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4720 }
4721 new_selection.map(|_| cursor)
4722 })
4723 .collect();
4724
4725 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4726 this.refresh_edit_prediction(true, false, window, cx);
4727 });
4728 }
4729
4730 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4731 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4732
4733 let buffer = self.buffer.read(cx);
4734 let snapshot = buffer.snapshot(cx);
4735
4736 let mut edits = Vec::new();
4737 let mut rows = Vec::new();
4738
4739 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4740 let cursor = selection.head();
4741 let row = cursor.row;
4742
4743 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4744
4745 let newline = "\n".to_string();
4746 edits.push((start_of_line..start_of_line, newline));
4747
4748 rows.push(row + rows_inserted as u32);
4749 }
4750
4751 self.transact(window, cx, |editor, window, cx| {
4752 editor.edit(edits, cx);
4753
4754 editor.change_selections(Default::default(), window, cx, |s| {
4755 let mut index = 0;
4756 s.move_cursors_with(|map, _, _| {
4757 let row = rows[index];
4758 index += 1;
4759
4760 let point = Point::new(row, 0);
4761 let boundary = map.next_line_boundary(point).1;
4762 let clipped = map.clip_point(boundary, Bias::Left);
4763
4764 (clipped, SelectionGoal::None)
4765 });
4766 });
4767
4768 let mut indent_edits = Vec::new();
4769 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4770 for row in rows {
4771 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4772 for (row, indent) in indents {
4773 if indent.len == 0 {
4774 continue;
4775 }
4776
4777 let text = match indent.kind {
4778 IndentKind::Space => " ".repeat(indent.len as usize),
4779 IndentKind::Tab => "\t".repeat(indent.len as usize),
4780 };
4781 let point = Point::new(row.0, 0);
4782 indent_edits.push((point..point, text));
4783 }
4784 }
4785 editor.edit(indent_edits, cx);
4786 });
4787 }
4788
4789 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4790 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4791
4792 let buffer = self.buffer.read(cx);
4793 let snapshot = buffer.snapshot(cx);
4794
4795 let mut edits = Vec::new();
4796 let mut rows = Vec::new();
4797 let mut rows_inserted = 0;
4798
4799 for selection in self.selections.all_adjusted(cx) {
4800 let cursor = selection.head();
4801 let row = cursor.row;
4802
4803 let point = Point::new(row + 1, 0);
4804 let start_of_line = snapshot.clip_point(point, Bias::Left);
4805
4806 let newline = "\n".to_string();
4807 edits.push((start_of_line..start_of_line, newline));
4808
4809 rows_inserted += 1;
4810 rows.push(row + rows_inserted);
4811 }
4812
4813 self.transact(window, cx, |editor, window, cx| {
4814 editor.edit(edits, cx);
4815
4816 editor.change_selections(Default::default(), window, cx, |s| {
4817 let mut index = 0;
4818 s.move_cursors_with(|map, _, _| {
4819 let row = rows[index];
4820 index += 1;
4821
4822 let point = Point::new(row, 0);
4823 let boundary = map.next_line_boundary(point).1;
4824 let clipped = map.clip_point(boundary, Bias::Left);
4825
4826 (clipped, SelectionGoal::None)
4827 });
4828 });
4829
4830 let mut indent_edits = Vec::new();
4831 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4832 for row in rows {
4833 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4834 for (row, indent) in indents {
4835 if indent.len == 0 {
4836 continue;
4837 }
4838
4839 let text = match indent.kind {
4840 IndentKind::Space => " ".repeat(indent.len as usize),
4841 IndentKind::Tab => "\t".repeat(indent.len as usize),
4842 };
4843 let point = Point::new(row.0, 0);
4844 indent_edits.push((point..point, text));
4845 }
4846 }
4847 editor.edit(indent_edits, cx);
4848 });
4849 }
4850
4851 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4852 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4853 original_indent_columns: Vec::new(),
4854 });
4855 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4856 }
4857
4858 fn insert_with_autoindent_mode(
4859 &mut self,
4860 text: &str,
4861 autoindent_mode: Option<AutoindentMode>,
4862 window: &mut Window,
4863 cx: &mut Context<Self>,
4864 ) {
4865 if self.read_only(cx) {
4866 return;
4867 }
4868
4869 let text: Arc<str> = text.into();
4870 self.transact(window, cx, |this, window, cx| {
4871 let old_selections = this.selections.all_adjusted(cx);
4872 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4873 let anchors = {
4874 let snapshot = buffer.read(cx);
4875 old_selections
4876 .iter()
4877 .map(|s| {
4878 let anchor = snapshot.anchor_after(s.head());
4879 s.map(|_| anchor)
4880 })
4881 .collect::<Vec<_>>()
4882 };
4883 buffer.edit(
4884 old_selections
4885 .iter()
4886 .map(|s| (s.start..s.end, text.clone())),
4887 autoindent_mode,
4888 cx,
4889 );
4890 anchors
4891 });
4892
4893 this.change_selections(Default::default(), window, cx, |s| {
4894 s.select_anchors(selection_anchors);
4895 });
4896
4897 cx.notify();
4898 });
4899 }
4900
4901 fn trigger_completion_on_input(
4902 &mut self,
4903 text: &str,
4904 trigger_in_words: bool,
4905 window: &mut Window,
4906 cx: &mut Context<Self>,
4907 ) {
4908 let completions_source = self
4909 .context_menu
4910 .borrow()
4911 .as_ref()
4912 .and_then(|menu| match menu {
4913 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4914 CodeContextMenu::CodeActions(_) => None,
4915 });
4916
4917 match completions_source {
4918 Some(CompletionsMenuSource::Words { .. }) => {
4919 self.open_or_update_completions_menu(
4920 Some(CompletionsMenuSource::Words {
4921 ignore_threshold: false,
4922 }),
4923 None,
4924 window,
4925 cx,
4926 );
4927 }
4928 Some(CompletionsMenuSource::Normal)
4929 | Some(CompletionsMenuSource::SnippetChoices)
4930 | None
4931 if self.is_completion_trigger(
4932 text,
4933 trigger_in_words,
4934 completions_source.is_some(),
4935 cx,
4936 ) =>
4937 {
4938 self.show_completions(
4939 &ShowCompletions {
4940 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4941 },
4942 window,
4943 cx,
4944 )
4945 }
4946 _ => {
4947 self.hide_context_menu(window, cx);
4948 }
4949 }
4950 }
4951
4952 fn is_completion_trigger(
4953 &self,
4954 text: &str,
4955 trigger_in_words: bool,
4956 menu_is_open: bool,
4957 cx: &mut Context<Self>,
4958 ) -> bool {
4959 let position = self.selections.newest_anchor().head();
4960 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4961 return false;
4962 };
4963
4964 if let Some(completion_provider) = &self.completion_provider {
4965 completion_provider.is_completion_trigger(
4966 &buffer,
4967 position.text_anchor,
4968 text,
4969 trigger_in_words,
4970 menu_is_open,
4971 cx,
4972 )
4973 } else {
4974 false
4975 }
4976 }
4977
4978 /// If any empty selections is touching the start of its innermost containing autoclose
4979 /// region, expand it to select the brackets.
4980 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4981 let selections = self.selections.all::<usize>(cx);
4982 let buffer = self.buffer.read(cx).read(cx);
4983 let new_selections = self
4984 .selections_with_autoclose_regions(selections, &buffer)
4985 .map(|(mut selection, region)| {
4986 if !selection.is_empty() {
4987 return selection;
4988 }
4989
4990 if let Some(region) = region {
4991 let mut range = region.range.to_offset(&buffer);
4992 if selection.start == range.start && range.start >= region.pair.start.len() {
4993 range.start -= region.pair.start.len();
4994 if buffer.contains_str_at(range.start, ®ion.pair.start)
4995 && buffer.contains_str_at(range.end, ®ion.pair.end)
4996 {
4997 range.end += region.pair.end.len();
4998 selection.start = range.start;
4999 selection.end = range.end;
5000
5001 return selection;
5002 }
5003 }
5004 }
5005
5006 let always_treat_brackets_as_autoclosed = buffer
5007 .language_settings_at(selection.start, cx)
5008 .always_treat_brackets_as_autoclosed;
5009
5010 if !always_treat_brackets_as_autoclosed {
5011 return selection;
5012 }
5013
5014 if let Some(scope) = buffer.language_scope_at(selection.start) {
5015 for (pair, enabled) in scope.brackets() {
5016 if !enabled || !pair.close {
5017 continue;
5018 }
5019
5020 if buffer.contains_str_at(selection.start, &pair.end) {
5021 let pair_start_len = pair.start.len();
5022 if buffer.contains_str_at(
5023 selection.start.saturating_sub(pair_start_len),
5024 &pair.start,
5025 ) {
5026 selection.start -= pair_start_len;
5027 selection.end += pair.end.len();
5028
5029 return selection;
5030 }
5031 }
5032 }
5033 }
5034
5035 selection
5036 })
5037 .collect();
5038
5039 drop(buffer);
5040 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5041 selections.select(new_selections)
5042 });
5043 }
5044
5045 /// Iterate the given selections, and for each one, find the smallest surrounding
5046 /// autoclose region. This uses the ordering of the selections and the autoclose
5047 /// regions to avoid repeated comparisons.
5048 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5049 &'a self,
5050 selections: impl IntoIterator<Item = Selection<D>>,
5051 buffer: &'a MultiBufferSnapshot,
5052 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5053 let mut i = 0;
5054 let mut regions = self.autoclose_regions.as_slice();
5055 selections.into_iter().map(move |selection| {
5056 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5057
5058 let mut enclosing = None;
5059 while let Some(pair_state) = regions.get(i) {
5060 if pair_state.range.end.to_offset(buffer) < range.start {
5061 regions = ®ions[i + 1..];
5062 i = 0;
5063 } else if pair_state.range.start.to_offset(buffer) > range.end {
5064 break;
5065 } else {
5066 if pair_state.selection_id == selection.id {
5067 enclosing = Some(pair_state);
5068 }
5069 i += 1;
5070 }
5071 }
5072
5073 (selection, enclosing)
5074 })
5075 }
5076
5077 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5078 fn invalidate_autoclose_regions(
5079 &mut self,
5080 mut selections: &[Selection<Anchor>],
5081 buffer: &MultiBufferSnapshot,
5082 ) {
5083 self.autoclose_regions.retain(|state| {
5084 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5085 return false;
5086 }
5087
5088 let mut i = 0;
5089 while let Some(selection) = selections.get(i) {
5090 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5091 selections = &selections[1..];
5092 continue;
5093 }
5094 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5095 break;
5096 }
5097 if selection.id == state.selection_id {
5098 return true;
5099 } else {
5100 i += 1;
5101 }
5102 }
5103 false
5104 });
5105 }
5106
5107 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5108 let offset = position.to_offset(buffer);
5109 let (word_range, kind) = buffer.surrounding_word(offset, true);
5110 if offset > word_range.start && kind == Some(CharKind::Word) {
5111 Some(
5112 buffer
5113 .text_for_range(word_range.start..offset)
5114 .collect::<String>(),
5115 )
5116 } else {
5117 None
5118 }
5119 }
5120
5121 pub fn toggle_inline_values(
5122 &mut self,
5123 _: &ToggleInlineValues,
5124 _: &mut Window,
5125 cx: &mut Context<Self>,
5126 ) {
5127 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5128
5129 self.refresh_inline_values(cx);
5130 }
5131
5132 pub fn toggle_inlay_hints(
5133 &mut self,
5134 _: &ToggleInlayHints,
5135 _: &mut Window,
5136 cx: &mut Context<Self>,
5137 ) {
5138 self.refresh_inlay_hints(
5139 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5140 cx,
5141 );
5142 }
5143
5144 pub fn inlay_hints_enabled(&self) -> bool {
5145 self.inlay_hint_cache.enabled
5146 }
5147
5148 pub fn inline_values_enabled(&self) -> bool {
5149 self.inline_value_cache.enabled
5150 }
5151
5152 #[cfg(any(test, feature = "test-support"))]
5153 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5154 self.display_map
5155 .read(cx)
5156 .current_inlays()
5157 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5158 .cloned()
5159 .collect()
5160 }
5161
5162 #[cfg(any(test, feature = "test-support"))]
5163 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5164 self.display_map
5165 .read(cx)
5166 .current_inlays()
5167 .cloned()
5168 .collect()
5169 }
5170
5171 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5172 if self.semantics_provider.is_none() || !self.mode.is_full() {
5173 return;
5174 }
5175
5176 let reason_description = reason.description();
5177 let ignore_debounce = matches!(
5178 reason,
5179 InlayHintRefreshReason::SettingsChange(_)
5180 | InlayHintRefreshReason::Toggle(_)
5181 | InlayHintRefreshReason::ExcerptsRemoved(_)
5182 | InlayHintRefreshReason::ModifiersChanged(_)
5183 );
5184 let (invalidate_cache, required_languages) = match reason {
5185 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5186 match self.inlay_hint_cache.modifiers_override(enabled) {
5187 Some(enabled) => {
5188 if enabled {
5189 (InvalidationStrategy::RefreshRequested, None)
5190 } else {
5191 self.splice_inlays(
5192 &self
5193 .visible_inlay_hints(cx)
5194 .iter()
5195 .map(|inlay| inlay.id)
5196 .collect::<Vec<InlayId>>(),
5197 Vec::new(),
5198 cx,
5199 );
5200 return;
5201 }
5202 }
5203 None => return,
5204 }
5205 }
5206 InlayHintRefreshReason::Toggle(enabled) => {
5207 if self.inlay_hint_cache.toggle(enabled) {
5208 if enabled {
5209 (InvalidationStrategy::RefreshRequested, None)
5210 } else {
5211 self.splice_inlays(
5212 &self
5213 .visible_inlay_hints(cx)
5214 .iter()
5215 .map(|inlay| inlay.id)
5216 .collect::<Vec<InlayId>>(),
5217 Vec::new(),
5218 cx,
5219 );
5220 return;
5221 }
5222 } else {
5223 return;
5224 }
5225 }
5226 InlayHintRefreshReason::SettingsChange(new_settings) => {
5227 match self.inlay_hint_cache.update_settings(
5228 &self.buffer,
5229 new_settings,
5230 self.visible_inlay_hints(cx),
5231 cx,
5232 ) {
5233 ControlFlow::Break(Some(InlaySplice {
5234 to_remove,
5235 to_insert,
5236 })) => {
5237 self.splice_inlays(&to_remove, to_insert, cx);
5238 return;
5239 }
5240 ControlFlow::Break(None) => return,
5241 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5242 }
5243 }
5244 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5245 if let Some(InlaySplice {
5246 to_remove,
5247 to_insert,
5248 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5249 {
5250 self.splice_inlays(&to_remove, to_insert, cx);
5251 }
5252 self.display_map.update(cx, |display_map, _| {
5253 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5254 });
5255 return;
5256 }
5257 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5258 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5259 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5260 }
5261 InlayHintRefreshReason::RefreshRequested => {
5262 (InvalidationStrategy::RefreshRequested, None)
5263 }
5264 };
5265
5266 if let Some(InlaySplice {
5267 to_remove,
5268 to_insert,
5269 }) = self.inlay_hint_cache.spawn_hint_refresh(
5270 reason_description,
5271 self.visible_excerpts(required_languages.as_ref(), cx),
5272 invalidate_cache,
5273 ignore_debounce,
5274 cx,
5275 ) {
5276 self.splice_inlays(&to_remove, to_insert, cx);
5277 }
5278 }
5279
5280 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5281 self.display_map
5282 .read(cx)
5283 .current_inlays()
5284 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5285 .cloned()
5286 .collect()
5287 }
5288
5289 pub fn visible_excerpts(
5290 &self,
5291 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5292 cx: &mut Context<Editor>,
5293 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5294 let Some(project) = self.project() else {
5295 return HashMap::default();
5296 };
5297 let project = project.read(cx);
5298 let multi_buffer = self.buffer().read(cx);
5299 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5300 let multi_buffer_visible_start = self
5301 .scroll_manager
5302 .anchor()
5303 .anchor
5304 .to_point(&multi_buffer_snapshot);
5305 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5306 multi_buffer_visible_start
5307 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5308 Bias::Left,
5309 );
5310 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5311 multi_buffer_snapshot
5312 .range_to_buffer_ranges(multi_buffer_visible_range)
5313 .into_iter()
5314 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5315 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5316 let buffer_file = project::File::from_dyn(buffer.file())?;
5317 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5318 let worktree_entry = buffer_worktree
5319 .read(cx)
5320 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5321 if worktree_entry.is_ignored {
5322 return None;
5323 }
5324
5325 let language = buffer.language()?;
5326 if let Some(restrict_to_languages) = restrict_to_languages
5327 && !restrict_to_languages.contains(language)
5328 {
5329 return None;
5330 }
5331 Some((
5332 excerpt_id,
5333 (
5334 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5335 buffer.version().clone(),
5336 excerpt_visible_range,
5337 ),
5338 ))
5339 })
5340 .collect()
5341 }
5342
5343 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5344 TextLayoutDetails {
5345 text_system: window.text_system().clone(),
5346 editor_style: self.style.clone().unwrap(),
5347 rem_size: window.rem_size(),
5348 scroll_anchor: self.scroll_manager.anchor(),
5349 visible_rows: self.visible_line_count(),
5350 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5351 }
5352 }
5353
5354 pub fn splice_inlays(
5355 &self,
5356 to_remove: &[InlayId],
5357 to_insert: Vec<Inlay>,
5358 cx: &mut Context<Self>,
5359 ) {
5360 self.display_map.update(cx, |display_map, cx| {
5361 display_map.splice_inlays(to_remove, to_insert, cx)
5362 });
5363 cx.notify();
5364 }
5365
5366 fn trigger_on_type_formatting(
5367 &self,
5368 input: String,
5369 window: &mut Window,
5370 cx: &mut Context<Self>,
5371 ) -> Option<Task<Result<()>>> {
5372 if input.len() != 1 {
5373 return None;
5374 }
5375
5376 let project = self.project()?;
5377 let position = self.selections.newest_anchor().head();
5378 let (buffer, buffer_position) = self
5379 .buffer
5380 .read(cx)
5381 .text_anchor_for_position(position, cx)?;
5382
5383 let settings = language_settings::language_settings(
5384 buffer
5385 .read(cx)
5386 .language_at(buffer_position)
5387 .map(|l| l.name()),
5388 buffer.read(cx).file(),
5389 cx,
5390 );
5391 if !settings.use_on_type_format {
5392 return None;
5393 }
5394
5395 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5396 // hence we do LSP request & edit on host side only — add formats to host's history.
5397 let push_to_lsp_host_history = true;
5398 // If this is not the host, append its history with new edits.
5399 let push_to_client_history = project.read(cx).is_via_collab();
5400
5401 let on_type_formatting = project.update(cx, |project, cx| {
5402 project.on_type_format(
5403 buffer.clone(),
5404 buffer_position,
5405 input,
5406 push_to_lsp_host_history,
5407 cx,
5408 )
5409 });
5410 Some(cx.spawn_in(window, async move |editor, cx| {
5411 if let Some(transaction) = on_type_formatting.await? {
5412 if push_to_client_history {
5413 buffer
5414 .update(cx, |buffer, _| {
5415 buffer.push_transaction(transaction, Instant::now());
5416 buffer.finalize_last_transaction();
5417 })
5418 .ok();
5419 }
5420 editor.update(cx, |editor, cx| {
5421 editor.refresh_document_highlights(cx);
5422 })?;
5423 }
5424 Ok(())
5425 }))
5426 }
5427
5428 pub fn show_word_completions(
5429 &mut self,
5430 _: &ShowWordCompletions,
5431 window: &mut Window,
5432 cx: &mut Context<Self>,
5433 ) {
5434 self.open_or_update_completions_menu(
5435 Some(CompletionsMenuSource::Words {
5436 ignore_threshold: true,
5437 }),
5438 None,
5439 window,
5440 cx,
5441 );
5442 }
5443
5444 pub fn show_completions(
5445 &mut self,
5446 options: &ShowCompletions,
5447 window: &mut Window,
5448 cx: &mut Context<Self>,
5449 ) {
5450 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5451 }
5452
5453 fn open_or_update_completions_menu(
5454 &mut self,
5455 requested_source: Option<CompletionsMenuSource>,
5456 trigger: Option<&str>,
5457 window: &mut Window,
5458 cx: &mut Context<Self>,
5459 ) {
5460 if self.pending_rename.is_some() {
5461 return;
5462 }
5463
5464 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5465
5466 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5467 // inserted and selected. To handle that case, the start of the selection is used so that
5468 // the menu starts with all choices.
5469 let position = self
5470 .selections
5471 .newest_anchor()
5472 .start
5473 .bias_right(&multibuffer_snapshot);
5474 if position.diff_base_anchor.is_some() {
5475 return;
5476 }
5477 let (buffer, buffer_position) =
5478 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5479 output
5480 } else {
5481 return;
5482 };
5483 let buffer_snapshot = buffer.read(cx).snapshot();
5484
5485 let query: Option<Arc<String>> =
5486 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5487
5488 drop(multibuffer_snapshot);
5489
5490 let mut ignore_word_threshold = false;
5491 let provider = match requested_source {
5492 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5493 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5494 ignore_word_threshold = ignore_threshold;
5495 None
5496 }
5497 Some(CompletionsMenuSource::SnippetChoices) => {
5498 log::error!("bug: SnippetChoices requested_source is not handled");
5499 None
5500 }
5501 };
5502
5503 let sort_completions = provider
5504 .as_ref()
5505 .is_some_and(|provider| provider.sort_completions());
5506
5507 let filter_completions = provider
5508 .as_ref()
5509 .is_none_or(|provider| provider.filter_completions());
5510
5511 let trigger_kind = match trigger {
5512 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5513 CompletionTriggerKind::TRIGGER_CHARACTER
5514 }
5515 _ => CompletionTriggerKind::INVOKED,
5516 };
5517 let completion_context = CompletionContext {
5518 trigger_character: trigger.and_then(|trigger| {
5519 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5520 Some(String::from(trigger))
5521 } else {
5522 None
5523 }
5524 }),
5525 trigger_kind,
5526 };
5527
5528 // Hide the current completions menu when a trigger char is typed. Without this, cached
5529 // completions from before the trigger char may be reused (#32774). Snippet choices could
5530 // involve trigger chars, so this is skipped in that case.
5531 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5532 {
5533 let menu_is_open = matches!(
5534 self.context_menu.borrow().as_ref(),
5535 Some(CodeContextMenu::Completions(_))
5536 );
5537 if menu_is_open {
5538 self.hide_context_menu(window, cx);
5539 }
5540 }
5541
5542 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5543 if filter_completions {
5544 menu.filter(query.clone(), provider.clone(), window, cx);
5545 }
5546 // When `is_incomplete` is false, no need to re-query completions when the current query
5547 // is a suffix of the initial query.
5548 if !menu.is_incomplete {
5549 // If the new query is a suffix of the old query (typing more characters) and
5550 // the previous result was complete, the existing completions can be filtered.
5551 //
5552 // Note that this is always true for snippet completions.
5553 let query_matches = match (&menu.initial_query, &query) {
5554 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5555 (None, _) => true,
5556 _ => false,
5557 };
5558 if query_matches {
5559 let position_matches = if menu.initial_position == position {
5560 true
5561 } else {
5562 let snapshot = self.buffer.read(cx).read(cx);
5563 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5564 };
5565 if position_matches {
5566 return;
5567 }
5568 }
5569 }
5570 };
5571
5572 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5573 buffer_snapshot.surrounding_word(buffer_position, false)
5574 {
5575 let word_to_exclude = buffer_snapshot
5576 .text_for_range(word_range.clone())
5577 .collect::<String>();
5578 (
5579 buffer_snapshot.anchor_before(word_range.start)
5580 ..buffer_snapshot.anchor_after(buffer_position),
5581 Some(word_to_exclude),
5582 )
5583 } else {
5584 (buffer_position..buffer_position, None)
5585 };
5586
5587 let language = buffer_snapshot
5588 .language_at(buffer_position)
5589 .map(|language| language.name());
5590
5591 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5592 .completions
5593 .clone();
5594
5595 let show_completion_documentation = buffer_snapshot
5596 .settings_at(buffer_position, cx)
5597 .show_completion_documentation;
5598
5599 // The document can be large, so stay in reasonable bounds when searching for words,
5600 // otherwise completion pop-up might be slow to appear.
5601 const WORD_LOOKUP_ROWS: u32 = 5_000;
5602 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5603 let min_word_search = buffer_snapshot.clip_point(
5604 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5605 Bias::Left,
5606 );
5607 let max_word_search = buffer_snapshot.clip_point(
5608 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5609 Bias::Right,
5610 );
5611 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5612 ..buffer_snapshot.point_to_offset(max_word_search);
5613
5614 let skip_digits = query
5615 .as_ref()
5616 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5617
5618 let omit_word_completions = !self.word_completions_enabled
5619 || (!ignore_word_threshold
5620 && match &query {
5621 Some(query) => query.chars().count() < completion_settings.words_min_length,
5622 None => completion_settings.words_min_length != 0,
5623 });
5624
5625 let (mut words, provider_responses) = match &provider {
5626 Some(provider) => {
5627 let provider_responses = provider.completions(
5628 position.excerpt_id,
5629 &buffer,
5630 buffer_position,
5631 completion_context,
5632 window,
5633 cx,
5634 );
5635
5636 let words = match (omit_word_completions, completion_settings.words) {
5637 (true, _) | (_, WordsCompletionMode::Disabled) => {
5638 Task::ready(BTreeMap::default())
5639 }
5640 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5641 .background_spawn(async move {
5642 buffer_snapshot.words_in_range(WordsQuery {
5643 fuzzy_contents: None,
5644 range: word_search_range,
5645 skip_digits,
5646 })
5647 }),
5648 };
5649
5650 (words, provider_responses)
5651 }
5652 None => {
5653 let words = if omit_word_completions {
5654 Task::ready(BTreeMap::default())
5655 } else {
5656 cx.background_spawn(async move {
5657 buffer_snapshot.words_in_range(WordsQuery {
5658 fuzzy_contents: None,
5659 range: word_search_range,
5660 skip_digits,
5661 })
5662 })
5663 };
5664 (words, Task::ready(Ok(Vec::new())))
5665 }
5666 };
5667
5668 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5669
5670 let id = post_inc(&mut self.next_completion_id);
5671 let task = cx.spawn_in(window, async move |editor, cx| {
5672 let Ok(()) = editor.update(cx, |this, _| {
5673 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5674 }) else {
5675 return;
5676 };
5677
5678 // TODO: Ideally completions from different sources would be selectively re-queried, so
5679 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5680 let mut completions = Vec::new();
5681 let mut is_incomplete = false;
5682 let mut display_options: Option<CompletionDisplayOptions> = None;
5683 if let Some(provider_responses) = provider_responses.await.log_err()
5684 && !provider_responses.is_empty()
5685 {
5686 for response in provider_responses {
5687 completions.extend(response.completions);
5688 is_incomplete = is_incomplete || response.is_incomplete;
5689 match display_options.as_mut() {
5690 None => {
5691 display_options = Some(response.display_options);
5692 }
5693 Some(options) => options.merge(&response.display_options),
5694 }
5695 }
5696 if completion_settings.words == WordsCompletionMode::Fallback {
5697 words = Task::ready(BTreeMap::default());
5698 }
5699 }
5700 let display_options = display_options.unwrap_or_default();
5701
5702 let mut words = words.await;
5703 if let Some(word_to_exclude) = &word_to_exclude {
5704 words.remove(word_to_exclude);
5705 }
5706 for lsp_completion in &completions {
5707 words.remove(&lsp_completion.new_text);
5708 }
5709 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5710 replace_range: word_replace_range.clone(),
5711 new_text: word.clone(),
5712 label: CodeLabel::plain(word, None),
5713 icon_path: None,
5714 documentation: None,
5715 source: CompletionSource::BufferWord {
5716 word_range,
5717 resolved: false,
5718 },
5719 insert_text_mode: Some(InsertTextMode::AS_IS),
5720 confirm: None,
5721 }));
5722
5723 let menu = if completions.is_empty() {
5724 None
5725 } else {
5726 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5727 let languages = editor
5728 .workspace
5729 .as_ref()
5730 .and_then(|(workspace, _)| workspace.upgrade())
5731 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5732 let menu = CompletionsMenu::new(
5733 id,
5734 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5735 sort_completions,
5736 show_completion_documentation,
5737 position,
5738 query.clone(),
5739 is_incomplete,
5740 buffer.clone(),
5741 completions.into(),
5742 display_options,
5743 snippet_sort_order,
5744 languages,
5745 language,
5746 cx,
5747 );
5748
5749 let query = if filter_completions { query } else { None };
5750 let matches_task = if let Some(query) = query {
5751 menu.do_async_filtering(query, cx)
5752 } else {
5753 Task::ready(menu.unfiltered_matches())
5754 };
5755 (menu, matches_task)
5756 }) else {
5757 return;
5758 };
5759
5760 let matches = matches_task.await;
5761
5762 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5763 // Newer menu already set, so exit.
5764 if let Some(CodeContextMenu::Completions(prev_menu)) =
5765 editor.context_menu.borrow().as_ref()
5766 && prev_menu.id > id
5767 {
5768 return;
5769 };
5770
5771 // Only valid to take prev_menu because it the new menu is immediately set
5772 // below, or the menu is hidden.
5773 if let Some(CodeContextMenu::Completions(prev_menu)) =
5774 editor.context_menu.borrow_mut().take()
5775 {
5776 let position_matches =
5777 if prev_menu.initial_position == menu.initial_position {
5778 true
5779 } else {
5780 let snapshot = editor.buffer.read(cx).read(cx);
5781 prev_menu.initial_position.to_offset(&snapshot)
5782 == menu.initial_position.to_offset(&snapshot)
5783 };
5784 if position_matches {
5785 // Preserve markdown cache before `set_filter_results` because it will
5786 // try to populate the documentation cache.
5787 menu.preserve_markdown_cache(prev_menu);
5788 }
5789 };
5790
5791 menu.set_filter_results(matches, provider, window, cx);
5792 }) else {
5793 return;
5794 };
5795
5796 menu.visible().then_some(menu)
5797 };
5798
5799 editor
5800 .update_in(cx, |editor, window, cx| {
5801 if editor.focus_handle.is_focused(window)
5802 && let Some(menu) = menu
5803 {
5804 *editor.context_menu.borrow_mut() =
5805 Some(CodeContextMenu::Completions(menu));
5806
5807 crate::hover_popover::hide_hover(editor, cx);
5808 if editor.show_edit_predictions_in_menu() {
5809 editor.update_visible_edit_prediction(window, cx);
5810 } else {
5811 editor.discard_edit_prediction(false, cx);
5812 }
5813
5814 cx.notify();
5815 return;
5816 }
5817
5818 if editor.completion_tasks.len() <= 1 {
5819 // If there are no more completion tasks and the last menu was empty, we should hide it.
5820 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5821 // If it was already hidden and we don't show edit predictions in the menu,
5822 // we should also show the edit prediction when available.
5823 if was_hidden && editor.show_edit_predictions_in_menu() {
5824 editor.update_visible_edit_prediction(window, cx);
5825 }
5826 }
5827 })
5828 .ok();
5829 });
5830
5831 self.completion_tasks.push((id, task));
5832 }
5833
5834 #[cfg(feature = "test-support")]
5835 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5836 let menu = self.context_menu.borrow();
5837 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5838 let completions = menu.completions.borrow();
5839 Some(completions.to_vec())
5840 } else {
5841 None
5842 }
5843 }
5844
5845 pub fn with_completions_menu_matching_id<R>(
5846 &self,
5847 id: CompletionId,
5848 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5849 ) -> R {
5850 let mut context_menu = self.context_menu.borrow_mut();
5851 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5852 return f(None);
5853 };
5854 if completions_menu.id != id {
5855 return f(None);
5856 }
5857 f(Some(completions_menu))
5858 }
5859
5860 pub fn confirm_completion(
5861 &mut self,
5862 action: &ConfirmCompletion,
5863 window: &mut Window,
5864 cx: &mut Context<Self>,
5865 ) -> Option<Task<Result<()>>> {
5866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5867 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5868 }
5869
5870 pub fn confirm_completion_insert(
5871 &mut self,
5872 _: &ConfirmCompletionInsert,
5873 window: &mut Window,
5874 cx: &mut Context<Self>,
5875 ) -> Option<Task<Result<()>>> {
5876 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5877 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5878 }
5879
5880 pub fn confirm_completion_replace(
5881 &mut self,
5882 _: &ConfirmCompletionReplace,
5883 window: &mut Window,
5884 cx: &mut Context<Self>,
5885 ) -> Option<Task<Result<()>>> {
5886 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5887 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5888 }
5889
5890 pub fn compose_completion(
5891 &mut self,
5892 action: &ComposeCompletion,
5893 window: &mut Window,
5894 cx: &mut Context<Self>,
5895 ) -> Option<Task<Result<()>>> {
5896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5897 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5898 }
5899
5900 fn do_completion(
5901 &mut self,
5902 item_ix: Option<usize>,
5903 intent: CompletionIntent,
5904 window: &mut Window,
5905 cx: &mut Context<Editor>,
5906 ) -> Option<Task<Result<()>>> {
5907 use language::ToOffset as _;
5908
5909 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5910 else {
5911 return None;
5912 };
5913
5914 let candidate_id = {
5915 let entries = completions_menu.entries.borrow();
5916 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5917 if self.show_edit_predictions_in_menu() {
5918 self.discard_edit_prediction(true, cx);
5919 }
5920 mat.candidate_id
5921 };
5922
5923 let completion = completions_menu
5924 .completions
5925 .borrow()
5926 .get(candidate_id)?
5927 .clone();
5928 cx.stop_propagation();
5929
5930 let buffer_handle = completions_menu.buffer.clone();
5931
5932 let CompletionEdit {
5933 new_text,
5934 snippet,
5935 replace_range,
5936 } = process_completion_for_edit(
5937 &completion,
5938 intent,
5939 &buffer_handle,
5940 &completions_menu.initial_position.text_anchor,
5941 cx,
5942 );
5943
5944 let buffer = buffer_handle.read(cx);
5945 let snapshot = self.buffer.read(cx).snapshot(cx);
5946 let newest_anchor = self.selections.newest_anchor();
5947 let replace_range_multibuffer = {
5948 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5949 let multibuffer_anchor = snapshot
5950 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5951 .unwrap()
5952 ..snapshot
5953 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5954 .unwrap();
5955 multibuffer_anchor.start.to_offset(&snapshot)
5956 ..multibuffer_anchor.end.to_offset(&snapshot)
5957 };
5958 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5959 return None;
5960 }
5961
5962 let old_text = buffer
5963 .text_for_range(replace_range.clone())
5964 .collect::<String>();
5965 let lookbehind = newest_anchor
5966 .start
5967 .text_anchor
5968 .to_offset(buffer)
5969 .saturating_sub(replace_range.start);
5970 let lookahead = replace_range
5971 .end
5972 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5973 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5974 let suffix = &old_text[lookbehind.min(old_text.len())..];
5975
5976 let selections = self.selections.all::<usize>(cx);
5977 let mut ranges = Vec::new();
5978 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5979
5980 for selection in &selections {
5981 let range = if selection.id == newest_anchor.id {
5982 replace_range_multibuffer.clone()
5983 } else {
5984 let mut range = selection.range();
5985
5986 // if prefix is present, don't duplicate it
5987 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5988 range.start = range.start.saturating_sub(lookbehind);
5989
5990 // if suffix is also present, mimic the newest cursor and replace it
5991 if selection.id != newest_anchor.id
5992 && snapshot.contains_str_at(range.end, suffix)
5993 {
5994 range.end += lookahead;
5995 }
5996 }
5997 range
5998 };
5999
6000 ranges.push(range.clone());
6001
6002 if !self.linked_edit_ranges.is_empty() {
6003 let start_anchor = snapshot.anchor_before(range.start);
6004 let end_anchor = snapshot.anchor_after(range.end);
6005 if let Some(ranges) = self
6006 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6007 {
6008 for (buffer, edits) in ranges {
6009 linked_edits
6010 .entry(buffer.clone())
6011 .or_default()
6012 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6013 }
6014 }
6015 }
6016 }
6017
6018 let common_prefix_len = old_text
6019 .chars()
6020 .zip(new_text.chars())
6021 .take_while(|(a, b)| a == b)
6022 .map(|(a, _)| a.len_utf8())
6023 .sum::<usize>();
6024
6025 cx.emit(EditorEvent::InputHandled {
6026 utf16_range_to_replace: None,
6027 text: new_text[common_prefix_len..].into(),
6028 });
6029
6030 self.transact(window, cx, |editor, window, cx| {
6031 if let Some(mut snippet) = snippet {
6032 snippet.text = new_text.to_string();
6033 editor
6034 .insert_snippet(&ranges, snippet, window, cx)
6035 .log_err();
6036 } else {
6037 editor.buffer.update(cx, |multi_buffer, cx| {
6038 let auto_indent = match completion.insert_text_mode {
6039 Some(InsertTextMode::AS_IS) => None,
6040 _ => editor.autoindent_mode.clone(),
6041 };
6042 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6043 multi_buffer.edit(edits, auto_indent, cx);
6044 });
6045 }
6046 for (buffer, edits) in linked_edits {
6047 buffer.update(cx, |buffer, cx| {
6048 let snapshot = buffer.snapshot();
6049 let edits = edits
6050 .into_iter()
6051 .map(|(range, text)| {
6052 use text::ToPoint as TP;
6053 let end_point = TP::to_point(&range.end, &snapshot);
6054 let start_point = TP::to_point(&range.start, &snapshot);
6055 (start_point..end_point, text)
6056 })
6057 .sorted_by_key(|(range, _)| range.start);
6058 buffer.edit(edits, None, cx);
6059 })
6060 }
6061
6062 editor.refresh_edit_prediction(true, false, window, cx);
6063 });
6064 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6065
6066 let show_new_completions_on_confirm = completion
6067 .confirm
6068 .as_ref()
6069 .is_some_and(|confirm| confirm(intent, window, cx));
6070 if show_new_completions_on_confirm {
6071 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6072 }
6073
6074 let provider = self.completion_provider.as_ref()?;
6075 drop(completion);
6076 let apply_edits = provider.apply_additional_edits_for_completion(
6077 buffer_handle,
6078 completions_menu.completions.clone(),
6079 candidate_id,
6080 true,
6081 cx,
6082 );
6083
6084 let editor_settings = EditorSettings::get_global(cx);
6085 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6086 // After the code completion is finished, users often want to know what signatures are needed.
6087 // so we should automatically call signature_help
6088 self.show_signature_help(&ShowSignatureHelp, window, cx);
6089 }
6090
6091 Some(cx.foreground_executor().spawn(async move {
6092 apply_edits.await?;
6093 Ok(())
6094 }))
6095 }
6096
6097 pub fn toggle_code_actions(
6098 &mut self,
6099 action: &ToggleCodeActions,
6100 window: &mut Window,
6101 cx: &mut Context<Self>,
6102 ) {
6103 let quick_launch = action.quick_launch;
6104 let mut context_menu = self.context_menu.borrow_mut();
6105 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6106 if code_actions.deployed_from == action.deployed_from {
6107 // Toggle if we're selecting the same one
6108 *context_menu = None;
6109 cx.notify();
6110 return;
6111 } else {
6112 // Otherwise, clear it and start a new one
6113 *context_menu = None;
6114 cx.notify();
6115 }
6116 }
6117 drop(context_menu);
6118 let snapshot = self.snapshot(window, cx);
6119 let deployed_from = action.deployed_from.clone();
6120 let action = action.clone();
6121 self.completion_tasks.clear();
6122 self.discard_edit_prediction(false, cx);
6123
6124 let multibuffer_point = match &action.deployed_from {
6125 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6126 DisplayPoint::new(*row, 0).to_point(&snapshot)
6127 }
6128 _ => self.selections.newest::<Point>(cx).head(),
6129 };
6130 let Some((buffer, buffer_row)) = snapshot
6131 .buffer_snapshot
6132 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6133 .and_then(|(buffer_snapshot, range)| {
6134 self.buffer()
6135 .read(cx)
6136 .buffer(buffer_snapshot.remote_id())
6137 .map(|buffer| (buffer, range.start.row))
6138 })
6139 else {
6140 return;
6141 };
6142 let buffer_id = buffer.read(cx).remote_id();
6143 let tasks = self
6144 .tasks
6145 .get(&(buffer_id, buffer_row))
6146 .map(|t| Arc::new(t.to_owned()));
6147
6148 if !self.focus_handle.is_focused(window) {
6149 return;
6150 }
6151 let project = self.project.clone();
6152
6153 let code_actions_task = match deployed_from {
6154 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6155 _ => self.code_actions(buffer_row, window, cx),
6156 };
6157
6158 let runnable_task = match deployed_from {
6159 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6160 _ => {
6161 let mut task_context_task = Task::ready(None);
6162 if let Some(tasks) = &tasks
6163 && let Some(project) = project
6164 {
6165 task_context_task =
6166 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6167 }
6168
6169 cx.spawn_in(window, {
6170 let buffer = buffer.clone();
6171 async move |editor, cx| {
6172 let task_context = task_context_task.await;
6173
6174 let resolved_tasks =
6175 tasks
6176 .zip(task_context.clone())
6177 .map(|(tasks, task_context)| ResolvedTasks {
6178 templates: tasks.resolve(&task_context).collect(),
6179 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6180 multibuffer_point.row,
6181 tasks.column,
6182 )),
6183 });
6184 let debug_scenarios = editor
6185 .update(cx, |editor, cx| {
6186 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6187 })?
6188 .await;
6189 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6190 }
6191 })
6192 }
6193 };
6194
6195 cx.spawn_in(window, async move |editor, cx| {
6196 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6197 let code_actions = code_actions_task.await;
6198 let spawn_straight_away = quick_launch
6199 && resolved_tasks
6200 .as_ref()
6201 .is_some_and(|tasks| tasks.templates.len() == 1)
6202 && code_actions
6203 .as_ref()
6204 .is_none_or(|actions| actions.is_empty())
6205 && debug_scenarios.is_empty();
6206
6207 editor.update_in(cx, |editor, window, cx| {
6208 crate::hover_popover::hide_hover(editor, cx);
6209 let actions = CodeActionContents::new(
6210 resolved_tasks,
6211 code_actions,
6212 debug_scenarios,
6213 task_context.unwrap_or_default(),
6214 );
6215
6216 // Don't show the menu if there are no actions available
6217 if actions.is_empty() {
6218 cx.notify();
6219 return Task::ready(Ok(()));
6220 }
6221
6222 *editor.context_menu.borrow_mut() =
6223 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6224 buffer,
6225 actions,
6226 selected_item: Default::default(),
6227 scroll_handle: UniformListScrollHandle::default(),
6228 deployed_from,
6229 }));
6230 cx.notify();
6231 if spawn_straight_away
6232 && let Some(task) = editor.confirm_code_action(
6233 &ConfirmCodeAction { item_ix: Some(0) },
6234 window,
6235 cx,
6236 )
6237 {
6238 return task;
6239 }
6240
6241 Task::ready(Ok(()))
6242 })
6243 })
6244 .detach_and_log_err(cx);
6245 }
6246
6247 fn debug_scenarios(
6248 &mut self,
6249 resolved_tasks: &Option<ResolvedTasks>,
6250 buffer: &Entity<Buffer>,
6251 cx: &mut App,
6252 ) -> Task<Vec<task::DebugScenario>> {
6253 maybe!({
6254 let project = self.project()?;
6255 let dap_store = project.read(cx).dap_store();
6256 let mut scenarios = vec![];
6257 let resolved_tasks = resolved_tasks.as_ref()?;
6258 let buffer = buffer.read(cx);
6259 let language = buffer.language()?;
6260 let file = buffer.file();
6261 let debug_adapter = language_settings(language.name().into(), file, cx)
6262 .debuggers
6263 .first()
6264 .map(SharedString::from)
6265 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6266
6267 dap_store.update(cx, |dap_store, cx| {
6268 for (_, task) in &resolved_tasks.templates {
6269 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6270 task.original_task().clone(),
6271 debug_adapter.clone().into(),
6272 task.display_label().to_owned().into(),
6273 cx,
6274 );
6275 scenarios.push(maybe_scenario);
6276 }
6277 });
6278 Some(cx.background_spawn(async move {
6279 futures::future::join_all(scenarios)
6280 .await
6281 .into_iter()
6282 .flatten()
6283 .collect::<Vec<_>>()
6284 }))
6285 })
6286 .unwrap_or_else(|| Task::ready(vec![]))
6287 }
6288
6289 fn code_actions(
6290 &mut self,
6291 buffer_row: u32,
6292 window: &mut Window,
6293 cx: &mut Context<Self>,
6294 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6295 let mut task = self.code_actions_task.take();
6296 cx.spawn_in(window, async move |editor, cx| {
6297 while let Some(prev_task) = task {
6298 prev_task.await.log_err();
6299 task = editor
6300 .update(cx, |this, _| this.code_actions_task.take())
6301 .ok()?;
6302 }
6303
6304 editor
6305 .update(cx, |editor, cx| {
6306 editor
6307 .available_code_actions
6308 .clone()
6309 .and_then(|(location, code_actions)| {
6310 let snapshot = location.buffer.read(cx).snapshot();
6311 let point_range = location.range.to_point(&snapshot);
6312 let point_range = point_range.start.row..=point_range.end.row;
6313 if point_range.contains(&buffer_row) {
6314 Some(code_actions)
6315 } else {
6316 None
6317 }
6318 })
6319 })
6320 .ok()
6321 .flatten()
6322 })
6323 }
6324
6325 pub fn confirm_code_action(
6326 &mut self,
6327 action: &ConfirmCodeAction,
6328 window: &mut Window,
6329 cx: &mut Context<Self>,
6330 ) -> Option<Task<Result<()>>> {
6331 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6332
6333 let actions_menu =
6334 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6335 menu
6336 } else {
6337 return None;
6338 };
6339
6340 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6341 let action = actions_menu.actions.get(action_ix)?;
6342 let title = action.label();
6343 let buffer = actions_menu.buffer;
6344 let workspace = self.workspace()?;
6345
6346 match action {
6347 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6348 workspace.update(cx, |workspace, cx| {
6349 workspace.schedule_resolved_task(
6350 task_source_kind,
6351 resolved_task,
6352 false,
6353 window,
6354 cx,
6355 );
6356
6357 Some(Task::ready(Ok(())))
6358 })
6359 }
6360 CodeActionsItem::CodeAction {
6361 excerpt_id,
6362 action,
6363 provider,
6364 } => {
6365 let apply_code_action =
6366 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6367 let workspace = workspace.downgrade();
6368 Some(cx.spawn_in(window, async move |editor, cx| {
6369 let project_transaction = apply_code_action.await?;
6370 Self::open_project_transaction(
6371 &editor,
6372 workspace,
6373 project_transaction,
6374 title,
6375 cx,
6376 )
6377 .await
6378 }))
6379 }
6380 CodeActionsItem::DebugScenario(scenario) => {
6381 let context = actions_menu.actions.context;
6382
6383 workspace.update(cx, |workspace, cx| {
6384 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6385 workspace.start_debug_session(
6386 scenario,
6387 context,
6388 Some(buffer),
6389 None,
6390 window,
6391 cx,
6392 );
6393 });
6394 Some(Task::ready(Ok(())))
6395 }
6396 }
6397 }
6398
6399 pub async fn open_project_transaction(
6400 editor: &WeakEntity<Editor>,
6401 workspace: WeakEntity<Workspace>,
6402 transaction: ProjectTransaction,
6403 title: String,
6404 cx: &mut AsyncWindowContext,
6405 ) -> Result<()> {
6406 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6407 cx.update(|_, cx| {
6408 entries.sort_unstable_by_key(|(buffer, _)| {
6409 buffer.read(cx).file().map(|f| f.path().clone())
6410 });
6411 })?;
6412
6413 // If the project transaction's edits are all contained within this editor, then
6414 // avoid opening a new editor to display them.
6415
6416 if let Some((buffer, transaction)) = entries.first() {
6417 if entries.len() == 1 {
6418 let excerpt = editor.update(cx, |editor, cx| {
6419 editor
6420 .buffer()
6421 .read(cx)
6422 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6423 })?;
6424 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6425 && excerpted_buffer == *buffer
6426 {
6427 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6428 let excerpt_range = excerpt_range.to_offset(buffer);
6429 buffer
6430 .edited_ranges_for_transaction::<usize>(transaction)
6431 .all(|range| {
6432 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6433 })
6434 })?;
6435
6436 if all_edits_within_excerpt {
6437 return Ok(());
6438 }
6439 }
6440 }
6441 } else {
6442 return Ok(());
6443 }
6444
6445 let mut ranges_to_highlight = Vec::new();
6446 let excerpt_buffer = cx.new(|cx| {
6447 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6448 for (buffer_handle, transaction) in &entries {
6449 let edited_ranges = buffer_handle
6450 .read(cx)
6451 .edited_ranges_for_transaction::<Point>(transaction)
6452 .collect::<Vec<_>>();
6453 let (ranges, _) = multibuffer.set_excerpts_for_path(
6454 PathKey::for_buffer(buffer_handle, cx),
6455 buffer_handle.clone(),
6456 edited_ranges,
6457 multibuffer_context_lines(cx),
6458 cx,
6459 );
6460
6461 ranges_to_highlight.extend(ranges);
6462 }
6463 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6464 multibuffer
6465 })?;
6466
6467 workspace.update_in(cx, |workspace, window, cx| {
6468 let project = workspace.project().clone();
6469 let editor =
6470 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6471 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6472 editor.update(cx, |editor, cx| {
6473 editor.highlight_background::<Self>(
6474 &ranges_to_highlight,
6475 |theme| theme.colors().editor_highlighted_line_background,
6476 cx,
6477 );
6478 });
6479 })?;
6480
6481 Ok(())
6482 }
6483
6484 pub fn clear_code_action_providers(&mut self) {
6485 self.code_action_providers.clear();
6486 self.available_code_actions.take();
6487 }
6488
6489 pub fn add_code_action_provider(
6490 &mut self,
6491 provider: Rc<dyn CodeActionProvider>,
6492 window: &mut Window,
6493 cx: &mut Context<Self>,
6494 ) {
6495 if self
6496 .code_action_providers
6497 .iter()
6498 .any(|existing_provider| existing_provider.id() == provider.id())
6499 {
6500 return;
6501 }
6502
6503 self.code_action_providers.push(provider);
6504 self.refresh_code_actions(window, cx);
6505 }
6506
6507 pub fn remove_code_action_provider(
6508 &mut self,
6509 id: Arc<str>,
6510 window: &mut Window,
6511 cx: &mut Context<Self>,
6512 ) {
6513 self.code_action_providers
6514 .retain(|provider| provider.id() != id);
6515 self.refresh_code_actions(window, cx);
6516 }
6517
6518 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6519 !self.code_action_providers.is_empty()
6520 && EditorSettings::get_global(cx).toolbar.code_actions
6521 }
6522
6523 pub fn has_available_code_actions(&self) -> bool {
6524 self.available_code_actions
6525 .as_ref()
6526 .is_some_and(|(_, actions)| !actions.is_empty())
6527 }
6528
6529 fn render_inline_code_actions(
6530 &self,
6531 icon_size: ui::IconSize,
6532 display_row: DisplayRow,
6533 is_active: bool,
6534 cx: &mut Context<Self>,
6535 ) -> AnyElement {
6536 let show_tooltip = !self.context_menu_visible();
6537 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6538 .icon_size(icon_size)
6539 .shape(ui::IconButtonShape::Square)
6540 .icon_color(ui::Color::Hidden)
6541 .toggle_state(is_active)
6542 .when(show_tooltip, |this| {
6543 this.tooltip({
6544 let focus_handle = self.focus_handle.clone();
6545 move |window, cx| {
6546 Tooltip::for_action_in(
6547 "Toggle Code Actions",
6548 &ToggleCodeActions {
6549 deployed_from: None,
6550 quick_launch: false,
6551 },
6552 &focus_handle,
6553 window,
6554 cx,
6555 )
6556 }
6557 })
6558 })
6559 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6560 window.focus(&editor.focus_handle(cx));
6561 editor.toggle_code_actions(
6562 &crate::actions::ToggleCodeActions {
6563 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6564 display_row,
6565 )),
6566 quick_launch: false,
6567 },
6568 window,
6569 cx,
6570 );
6571 }))
6572 .into_any_element()
6573 }
6574
6575 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6576 &self.context_menu
6577 }
6578
6579 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6580 let newest_selection = self.selections.newest_anchor().clone();
6581 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6582 let buffer = self.buffer.read(cx);
6583 if newest_selection.head().diff_base_anchor.is_some() {
6584 return None;
6585 }
6586 let (start_buffer, start) =
6587 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6588 let (end_buffer, end) =
6589 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6590 if start_buffer != end_buffer {
6591 return None;
6592 }
6593
6594 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6595 cx.background_executor()
6596 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6597 .await;
6598
6599 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6600 let providers = this.code_action_providers.clone();
6601 let tasks = this
6602 .code_action_providers
6603 .iter()
6604 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6605 .collect::<Vec<_>>();
6606 (providers, tasks)
6607 })?;
6608
6609 let mut actions = Vec::new();
6610 for (provider, provider_actions) in
6611 providers.into_iter().zip(future::join_all(tasks).await)
6612 {
6613 if let Some(provider_actions) = provider_actions.log_err() {
6614 actions.extend(provider_actions.into_iter().map(|action| {
6615 AvailableCodeAction {
6616 excerpt_id: newest_selection.start.excerpt_id,
6617 action,
6618 provider: provider.clone(),
6619 }
6620 }));
6621 }
6622 }
6623
6624 this.update(cx, |this, cx| {
6625 this.available_code_actions = if actions.is_empty() {
6626 None
6627 } else {
6628 Some((
6629 Location {
6630 buffer: start_buffer,
6631 range: start..end,
6632 },
6633 actions.into(),
6634 ))
6635 };
6636 cx.notify();
6637 })
6638 }));
6639 None
6640 }
6641
6642 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6643 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6644 self.show_git_blame_inline = false;
6645
6646 self.show_git_blame_inline_delay_task =
6647 Some(cx.spawn_in(window, async move |this, cx| {
6648 cx.background_executor().timer(delay).await;
6649
6650 this.update(cx, |this, cx| {
6651 this.show_git_blame_inline = true;
6652 cx.notify();
6653 })
6654 .log_err();
6655 }));
6656 }
6657 }
6658
6659 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6660 let snapshot = self.snapshot(window, cx);
6661 let cursor = self.selections.newest::<Point>(cx).head();
6662 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6663 else {
6664 return;
6665 };
6666
6667 let Some(blame) = self.blame.as_ref() else {
6668 return;
6669 };
6670
6671 let row_info = RowInfo {
6672 buffer_id: Some(buffer.remote_id()),
6673 buffer_row: Some(point.row),
6674 ..Default::default()
6675 };
6676 let Some((buffer, blame_entry)) = blame
6677 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6678 .flatten()
6679 else {
6680 return;
6681 };
6682
6683 let anchor = self.selections.newest_anchor().head();
6684 let position = self.to_pixel_point(anchor, &snapshot, window);
6685 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6686 self.show_blame_popover(
6687 buffer,
6688 &blame_entry,
6689 position + last_bounds.origin,
6690 true,
6691 cx,
6692 );
6693 };
6694 }
6695
6696 fn show_blame_popover(
6697 &mut self,
6698 buffer: BufferId,
6699 blame_entry: &BlameEntry,
6700 position: gpui::Point<Pixels>,
6701 ignore_timeout: bool,
6702 cx: &mut Context<Self>,
6703 ) {
6704 if let Some(state) = &mut self.inline_blame_popover {
6705 state.hide_task.take();
6706 } else {
6707 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6708 let blame_entry = blame_entry.clone();
6709 let show_task = cx.spawn(async move |editor, cx| {
6710 if !ignore_timeout {
6711 cx.background_executor()
6712 .timer(std::time::Duration::from_millis(blame_popover_delay))
6713 .await;
6714 }
6715 editor
6716 .update(cx, |editor, cx| {
6717 editor.inline_blame_popover_show_task.take();
6718 let Some(blame) = editor.blame.as_ref() else {
6719 return;
6720 };
6721 let blame = blame.read(cx);
6722 let details = blame.details_for_entry(buffer, &blame_entry);
6723 let markdown = cx.new(|cx| {
6724 Markdown::new(
6725 details
6726 .as_ref()
6727 .map(|message| message.message.clone())
6728 .unwrap_or_default(),
6729 None,
6730 None,
6731 cx,
6732 )
6733 });
6734 editor.inline_blame_popover = Some(InlineBlamePopover {
6735 position,
6736 hide_task: None,
6737 popover_bounds: None,
6738 popover_state: InlineBlamePopoverState {
6739 scroll_handle: ScrollHandle::new(),
6740 commit_message: details,
6741 markdown,
6742 },
6743 keyboard_grace: ignore_timeout,
6744 });
6745 cx.notify();
6746 })
6747 .ok();
6748 });
6749 self.inline_blame_popover_show_task = Some(show_task);
6750 }
6751 }
6752
6753 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6754 self.inline_blame_popover_show_task.take();
6755 if let Some(state) = &mut self.inline_blame_popover {
6756 let hide_task = cx.spawn(async move |editor, cx| {
6757 cx.background_executor()
6758 .timer(std::time::Duration::from_millis(100))
6759 .await;
6760 editor
6761 .update(cx, |editor, cx| {
6762 editor.inline_blame_popover.take();
6763 cx.notify();
6764 })
6765 .ok();
6766 });
6767 state.hide_task = Some(hide_task);
6768 }
6769 }
6770
6771 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6772 if self.pending_rename.is_some() {
6773 return None;
6774 }
6775
6776 let provider = self.semantics_provider.clone()?;
6777 let buffer = self.buffer.read(cx);
6778 let newest_selection = self.selections.newest_anchor().clone();
6779 let cursor_position = newest_selection.head();
6780 let (cursor_buffer, cursor_buffer_position) =
6781 buffer.text_anchor_for_position(cursor_position, cx)?;
6782 let (tail_buffer, tail_buffer_position) =
6783 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6784 if cursor_buffer != tail_buffer {
6785 return None;
6786 }
6787
6788 let snapshot = cursor_buffer.read(cx).snapshot();
6789 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6790 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6791 if start_word_range != end_word_range {
6792 self.document_highlights_task.take();
6793 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6794 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6795 return None;
6796 }
6797
6798 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6799 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6800 cx.background_executor()
6801 .timer(Duration::from_millis(debounce))
6802 .await;
6803
6804 let highlights = if let Some(highlights) = cx
6805 .update(|cx| {
6806 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6807 })
6808 .ok()
6809 .flatten()
6810 {
6811 highlights.await.log_err()
6812 } else {
6813 None
6814 };
6815
6816 if let Some(highlights) = highlights {
6817 this.update(cx, |this, cx| {
6818 if this.pending_rename.is_some() {
6819 return;
6820 }
6821
6822 let buffer = this.buffer.read(cx);
6823 if buffer
6824 .text_anchor_for_position(cursor_position, cx)
6825 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6826 {
6827 return;
6828 }
6829
6830 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6831 let mut write_ranges = Vec::new();
6832 let mut read_ranges = Vec::new();
6833 for highlight in highlights {
6834 let buffer_id = cursor_buffer.read(cx).remote_id();
6835 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6836 {
6837 let start = highlight
6838 .range
6839 .start
6840 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6841 let end = highlight
6842 .range
6843 .end
6844 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6845 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6846 continue;
6847 }
6848
6849 let range = Anchor {
6850 buffer_id: Some(buffer_id),
6851 excerpt_id,
6852 text_anchor: start,
6853 diff_base_anchor: None,
6854 }..Anchor {
6855 buffer_id: Some(buffer_id),
6856 excerpt_id,
6857 text_anchor: end,
6858 diff_base_anchor: None,
6859 };
6860 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6861 write_ranges.push(range);
6862 } else {
6863 read_ranges.push(range);
6864 }
6865 }
6866 }
6867
6868 this.highlight_background::<DocumentHighlightRead>(
6869 &read_ranges,
6870 |theme| theme.colors().editor_document_highlight_read_background,
6871 cx,
6872 );
6873 this.highlight_background::<DocumentHighlightWrite>(
6874 &write_ranges,
6875 |theme| theme.colors().editor_document_highlight_write_background,
6876 cx,
6877 );
6878 cx.notify();
6879 })
6880 .log_err();
6881 }
6882 }));
6883 None
6884 }
6885
6886 fn prepare_highlight_query_from_selection(
6887 &mut self,
6888 cx: &mut Context<Editor>,
6889 ) -> Option<(String, Range<Anchor>)> {
6890 if matches!(self.mode, EditorMode::SingleLine) {
6891 return None;
6892 }
6893 if !EditorSettings::get_global(cx).selection_highlight {
6894 return None;
6895 }
6896 if self.selections.count() != 1 || self.selections.line_mode {
6897 return None;
6898 }
6899 let selection = self.selections.newest::<Point>(cx);
6900 if selection.is_empty() || selection.start.row != selection.end.row {
6901 return None;
6902 }
6903 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6904 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6905 let query = multi_buffer_snapshot
6906 .text_for_range(selection_anchor_range.clone())
6907 .collect::<String>();
6908 if query.trim().is_empty() {
6909 return None;
6910 }
6911 Some((query, selection_anchor_range))
6912 }
6913
6914 fn update_selection_occurrence_highlights(
6915 &mut self,
6916 query_text: String,
6917 query_range: Range<Anchor>,
6918 multi_buffer_range_to_query: Range<Point>,
6919 use_debounce: bool,
6920 window: &mut Window,
6921 cx: &mut Context<Editor>,
6922 ) -> Task<()> {
6923 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6924 cx.spawn_in(window, async move |editor, cx| {
6925 if use_debounce {
6926 cx.background_executor()
6927 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6928 .await;
6929 }
6930 let match_task = cx.background_spawn(async move {
6931 let buffer_ranges = multi_buffer_snapshot
6932 .range_to_buffer_ranges(multi_buffer_range_to_query)
6933 .into_iter()
6934 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6935 let mut match_ranges = Vec::new();
6936 let Ok(regex) = project::search::SearchQuery::text(
6937 query_text.clone(),
6938 false,
6939 false,
6940 false,
6941 Default::default(),
6942 Default::default(),
6943 false,
6944 None,
6945 ) else {
6946 return Vec::default();
6947 };
6948 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6949 match_ranges.extend(
6950 regex
6951 .search(buffer_snapshot, Some(search_range.clone()))
6952 .await
6953 .into_iter()
6954 .filter_map(|match_range| {
6955 let match_start = buffer_snapshot
6956 .anchor_after(search_range.start + match_range.start);
6957 let match_end = buffer_snapshot
6958 .anchor_before(search_range.start + match_range.end);
6959 let match_anchor_range = Anchor::range_in_buffer(
6960 excerpt_id,
6961 buffer_snapshot.remote_id(),
6962 match_start..match_end,
6963 );
6964 (match_anchor_range != query_range).then_some(match_anchor_range)
6965 }),
6966 );
6967 }
6968 match_ranges
6969 });
6970 let match_ranges = match_task.await;
6971 editor
6972 .update_in(cx, |editor, _, cx| {
6973 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6974 if !match_ranges.is_empty() {
6975 editor.highlight_background::<SelectedTextHighlight>(
6976 &match_ranges,
6977 |theme| theme.colors().editor_document_highlight_bracket_background,
6978 cx,
6979 )
6980 }
6981 })
6982 .log_err();
6983 })
6984 }
6985
6986 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6987 struct NewlineFold;
6988 let type_id = std::any::TypeId::of::<NewlineFold>();
6989 if !self.mode.is_single_line() {
6990 return;
6991 }
6992 let snapshot = self.snapshot(window, cx);
6993 if snapshot.buffer_snapshot.max_point().row == 0 {
6994 return;
6995 }
6996 let task = cx.background_spawn(async move {
6997 let new_newlines = snapshot
6998 .buffer_chars_at(0)
6999 .filter_map(|(c, i)| {
7000 if c == '\n' {
7001 Some(
7002 snapshot.buffer_snapshot.anchor_after(i)
7003 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7004 )
7005 } else {
7006 None
7007 }
7008 })
7009 .collect::<Vec<_>>();
7010 let existing_newlines = snapshot
7011 .folds_in_range(0..snapshot.buffer_snapshot.len())
7012 .filter_map(|fold| {
7013 if fold.placeholder.type_tag == Some(type_id) {
7014 Some(fold.range.start..fold.range.end)
7015 } else {
7016 None
7017 }
7018 })
7019 .collect::<Vec<_>>();
7020
7021 (new_newlines, existing_newlines)
7022 });
7023 self.folding_newlines = cx.spawn(async move |this, cx| {
7024 let (new_newlines, existing_newlines) = task.await;
7025 if new_newlines == existing_newlines {
7026 return;
7027 }
7028 let placeholder = FoldPlaceholder {
7029 render: Arc::new(move |_, _, cx| {
7030 div()
7031 .bg(cx.theme().status().hint_background)
7032 .border_b_1()
7033 .size_full()
7034 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7035 .border_color(cx.theme().status().hint)
7036 .child("\\n")
7037 .into_any()
7038 }),
7039 constrain_width: false,
7040 merge_adjacent: false,
7041 type_tag: Some(type_id),
7042 };
7043 let creases = new_newlines
7044 .into_iter()
7045 .map(|range| Crease::simple(range, placeholder.clone()))
7046 .collect();
7047 this.update(cx, |this, cx| {
7048 this.display_map.update(cx, |display_map, cx| {
7049 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7050 display_map.fold(creases, cx);
7051 });
7052 })
7053 .ok();
7054 });
7055 }
7056
7057 fn refresh_selected_text_highlights(
7058 &mut self,
7059 on_buffer_edit: bool,
7060 window: &mut Window,
7061 cx: &mut Context<Editor>,
7062 ) {
7063 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7064 else {
7065 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7066 self.quick_selection_highlight_task.take();
7067 self.debounced_selection_highlight_task.take();
7068 return;
7069 };
7070 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7071 if on_buffer_edit
7072 || self
7073 .quick_selection_highlight_task
7074 .as_ref()
7075 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7076 {
7077 let multi_buffer_visible_start = self
7078 .scroll_manager
7079 .anchor()
7080 .anchor
7081 .to_point(&multi_buffer_snapshot);
7082 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7083 multi_buffer_visible_start
7084 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7085 Bias::Left,
7086 );
7087 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7088 self.quick_selection_highlight_task = Some((
7089 query_range.clone(),
7090 self.update_selection_occurrence_highlights(
7091 query_text.clone(),
7092 query_range.clone(),
7093 multi_buffer_visible_range,
7094 false,
7095 window,
7096 cx,
7097 ),
7098 ));
7099 }
7100 if on_buffer_edit
7101 || self
7102 .debounced_selection_highlight_task
7103 .as_ref()
7104 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7105 {
7106 let multi_buffer_start = multi_buffer_snapshot
7107 .anchor_before(0)
7108 .to_point(&multi_buffer_snapshot);
7109 let multi_buffer_end = multi_buffer_snapshot
7110 .anchor_after(multi_buffer_snapshot.len())
7111 .to_point(&multi_buffer_snapshot);
7112 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7113 self.debounced_selection_highlight_task = Some((
7114 query_range.clone(),
7115 self.update_selection_occurrence_highlights(
7116 query_text,
7117 query_range,
7118 multi_buffer_full_range,
7119 true,
7120 window,
7121 cx,
7122 ),
7123 ));
7124 }
7125 }
7126
7127 pub fn refresh_edit_prediction(
7128 &mut self,
7129 debounce: bool,
7130 user_requested: bool,
7131 window: &mut Window,
7132 cx: &mut Context<Self>,
7133 ) -> Option<()> {
7134 if DisableAiSettings::get_global(cx).disable_ai {
7135 return None;
7136 }
7137
7138 let provider = self.edit_prediction_provider()?;
7139 let cursor = self.selections.newest_anchor().head();
7140 let (buffer, cursor_buffer_position) =
7141 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7142
7143 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7144 self.discard_edit_prediction(false, cx);
7145 return None;
7146 }
7147
7148 if !user_requested
7149 && (!self.should_show_edit_predictions()
7150 || !self.is_focused(window)
7151 || buffer.read(cx).is_empty())
7152 {
7153 self.discard_edit_prediction(false, cx);
7154 return None;
7155 }
7156
7157 self.update_visible_edit_prediction(window, cx);
7158 provider.refresh(
7159 self.project.clone(),
7160 buffer,
7161 cursor_buffer_position,
7162 debounce,
7163 cx,
7164 );
7165 Some(())
7166 }
7167
7168 fn show_edit_predictions_in_menu(&self) -> bool {
7169 match self.edit_prediction_settings {
7170 EditPredictionSettings::Disabled => false,
7171 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7172 }
7173 }
7174
7175 pub fn edit_predictions_enabled(&self) -> bool {
7176 match self.edit_prediction_settings {
7177 EditPredictionSettings::Disabled => false,
7178 EditPredictionSettings::Enabled { .. } => true,
7179 }
7180 }
7181
7182 fn edit_prediction_requires_modifier(&self) -> bool {
7183 match self.edit_prediction_settings {
7184 EditPredictionSettings::Disabled => false,
7185 EditPredictionSettings::Enabled {
7186 preview_requires_modifier,
7187 ..
7188 } => preview_requires_modifier,
7189 }
7190 }
7191
7192 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7193 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7194 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7195 self.discard_edit_prediction(false, cx);
7196 } else {
7197 let selection = self.selections.newest_anchor();
7198 let cursor = selection.head();
7199
7200 if let Some((buffer, cursor_buffer_position)) =
7201 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7202 {
7203 self.edit_prediction_settings =
7204 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7205 }
7206 }
7207 }
7208
7209 fn edit_prediction_settings_at_position(
7210 &self,
7211 buffer: &Entity<Buffer>,
7212 buffer_position: language::Anchor,
7213 cx: &App,
7214 ) -> EditPredictionSettings {
7215 if !self.mode.is_full()
7216 || !self.show_edit_predictions_override.unwrap_or(true)
7217 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7218 {
7219 return EditPredictionSettings::Disabled;
7220 }
7221
7222 let buffer = buffer.read(cx);
7223
7224 let file = buffer.file();
7225
7226 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7227 return EditPredictionSettings::Disabled;
7228 };
7229
7230 let by_provider = matches!(
7231 self.menu_edit_predictions_policy,
7232 MenuEditPredictionsPolicy::ByProvider
7233 );
7234
7235 let show_in_menu = by_provider
7236 && self
7237 .edit_prediction_provider
7238 .as_ref()
7239 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7240
7241 let preview_requires_modifier =
7242 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7243
7244 EditPredictionSettings::Enabled {
7245 show_in_menu,
7246 preview_requires_modifier,
7247 }
7248 }
7249
7250 fn should_show_edit_predictions(&self) -> bool {
7251 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7252 }
7253
7254 pub fn edit_prediction_preview_is_active(&self) -> bool {
7255 matches!(
7256 self.edit_prediction_preview,
7257 EditPredictionPreview::Active { .. }
7258 )
7259 }
7260
7261 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7262 let cursor = self.selections.newest_anchor().head();
7263 if let Some((buffer, cursor_position)) =
7264 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7265 {
7266 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7267 } else {
7268 false
7269 }
7270 }
7271
7272 pub fn supports_minimap(&self, cx: &App) -> bool {
7273 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7274 }
7275
7276 fn edit_predictions_enabled_in_buffer(
7277 &self,
7278 buffer: &Entity<Buffer>,
7279 buffer_position: language::Anchor,
7280 cx: &App,
7281 ) -> bool {
7282 maybe!({
7283 if self.read_only(cx) {
7284 return Some(false);
7285 }
7286 let provider = self.edit_prediction_provider()?;
7287 if !provider.is_enabled(buffer, buffer_position, cx) {
7288 return Some(false);
7289 }
7290 let buffer = buffer.read(cx);
7291 let Some(file) = buffer.file() else {
7292 return Some(true);
7293 };
7294 let settings = all_language_settings(Some(file), cx);
7295 Some(settings.edit_predictions_enabled_for_file(file, cx))
7296 })
7297 .unwrap_or(false)
7298 }
7299
7300 fn cycle_edit_prediction(
7301 &mut self,
7302 direction: Direction,
7303 window: &mut Window,
7304 cx: &mut Context<Self>,
7305 ) -> Option<()> {
7306 let provider = self.edit_prediction_provider()?;
7307 let cursor = self.selections.newest_anchor().head();
7308 let (buffer, cursor_buffer_position) =
7309 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7310 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7311 return None;
7312 }
7313
7314 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7315 self.update_visible_edit_prediction(window, cx);
7316
7317 Some(())
7318 }
7319
7320 pub fn show_edit_prediction(
7321 &mut self,
7322 _: &ShowEditPrediction,
7323 window: &mut Window,
7324 cx: &mut Context<Self>,
7325 ) {
7326 if !self.has_active_edit_prediction() {
7327 self.refresh_edit_prediction(false, true, window, cx);
7328 return;
7329 }
7330
7331 self.update_visible_edit_prediction(window, cx);
7332 }
7333
7334 pub fn display_cursor_names(
7335 &mut self,
7336 _: &DisplayCursorNames,
7337 window: &mut Window,
7338 cx: &mut Context<Self>,
7339 ) {
7340 self.show_cursor_names(window, cx);
7341 }
7342
7343 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7344 self.show_cursor_names = true;
7345 cx.notify();
7346 cx.spawn_in(window, async move |this, cx| {
7347 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7348 this.update(cx, |this, cx| {
7349 this.show_cursor_names = false;
7350 cx.notify()
7351 })
7352 .ok()
7353 })
7354 .detach();
7355 }
7356
7357 pub fn next_edit_prediction(
7358 &mut self,
7359 _: &NextEditPrediction,
7360 window: &mut Window,
7361 cx: &mut Context<Self>,
7362 ) {
7363 if self.has_active_edit_prediction() {
7364 self.cycle_edit_prediction(Direction::Next, window, cx);
7365 } else {
7366 let is_copilot_disabled = self
7367 .refresh_edit_prediction(false, true, window, cx)
7368 .is_none();
7369 if is_copilot_disabled {
7370 cx.propagate();
7371 }
7372 }
7373 }
7374
7375 pub fn previous_edit_prediction(
7376 &mut self,
7377 _: &PreviousEditPrediction,
7378 window: &mut Window,
7379 cx: &mut Context<Self>,
7380 ) {
7381 if self.has_active_edit_prediction() {
7382 self.cycle_edit_prediction(Direction::Prev, window, cx);
7383 } else {
7384 let is_copilot_disabled = self
7385 .refresh_edit_prediction(false, true, window, cx)
7386 .is_none();
7387 if is_copilot_disabled {
7388 cx.propagate();
7389 }
7390 }
7391 }
7392
7393 pub fn accept_edit_prediction(
7394 &mut self,
7395 _: &AcceptEditPrediction,
7396 window: &mut Window,
7397 cx: &mut Context<Self>,
7398 ) {
7399 if self.show_edit_predictions_in_menu() {
7400 self.hide_context_menu(window, cx);
7401 }
7402
7403 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7404 return;
7405 };
7406
7407 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7408
7409 match &active_edit_prediction.completion {
7410 EditPrediction::Move { target, .. } => {
7411 let target = *target;
7412
7413 if let Some(position_map) = &self.last_position_map {
7414 if position_map
7415 .visible_row_range
7416 .contains(&target.to_display_point(&position_map.snapshot).row())
7417 || !self.edit_prediction_requires_modifier()
7418 {
7419 self.unfold_ranges(&[target..target], true, false, cx);
7420 // Note that this is also done in vim's handler of the Tab action.
7421 self.change_selections(
7422 SelectionEffects::scroll(Autoscroll::newest()),
7423 window,
7424 cx,
7425 |selections| {
7426 selections.select_anchor_ranges([target..target]);
7427 },
7428 );
7429 self.clear_row_highlights::<EditPredictionPreview>();
7430
7431 self.edit_prediction_preview
7432 .set_previous_scroll_position(None);
7433 } else {
7434 self.edit_prediction_preview
7435 .set_previous_scroll_position(Some(
7436 position_map.snapshot.scroll_anchor,
7437 ));
7438
7439 self.highlight_rows::<EditPredictionPreview>(
7440 target..target,
7441 cx.theme().colors().editor_highlighted_line_background,
7442 RowHighlightOptions {
7443 autoscroll: true,
7444 ..Default::default()
7445 },
7446 cx,
7447 );
7448 self.request_autoscroll(Autoscroll::fit(), cx);
7449 }
7450 }
7451 }
7452 EditPrediction::Edit { edits, .. } => {
7453 if let Some(provider) = self.edit_prediction_provider() {
7454 provider.accept(cx);
7455 }
7456
7457 // Store the transaction ID and selections before applying the edit
7458 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7459
7460 let snapshot = self.buffer.read(cx).snapshot(cx);
7461 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7462
7463 self.buffer.update(cx, |buffer, cx| {
7464 buffer.edit(edits.iter().cloned(), None, cx)
7465 });
7466
7467 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7468 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7469 });
7470
7471 let selections = self.selections.disjoint_anchors();
7472 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7473 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7474 if has_new_transaction {
7475 self.selection_history
7476 .insert_transaction(transaction_id_now, selections);
7477 }
7478 }
7479
7480 self.update_visible_edit_prediction(window, cx);
7481 if self.active_edit_prediction.is_none() {
7482 self.refresh_edit_prediction(true, true, window, cx);
7483 }
7484
7485 cx.notify();
7486 }
7487 }
7488
7489 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7490 }
7491
7492 pub fn accept_partial_edit_prediction(
7493 &mut self,
7494 _: &AcceptPartialEditPrediction,
7495 window: &mut Window,
7496 cx: &mut Context<Self>,
7497 ) {
7498 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7499 return;
7500 };
7501 if self.selections.count() != 1 {
7502 return;
7503 }
7504
7505 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7506
7507 match &active_edit_prediction.completion {
7508 EditPrediction::Move { target, .. } => {
7509 let target = *target;
7510 self.change_selections(
7511 SelectionEffects::scroll(Autoscroll::newest()),
7512 window,
7513 cx,
7514 |selections| {
7515 selections.select_anchor_ranges([target..target]);
7516 },
7517 );
7518 }
7519 EditPrediction::Edit { edits, .. } => {
7520 // Find an insertion that starts at the cursor position.
7521 let snapshot = self.buffer.read(cx).snapshot(cx);
7522 let cursor_offset = self.selections.newest::<usize>(cx).head();
7523 let insertion = edits.iter().find_map(|(range, text)| {
7524 let range = range.to_offset(&snapshot);
7525 if range.is_empty() && range.start == cursor_offset {
7526 Some(text)
7527 } else {
7528 None
7529 }
7530 });
7531
7532 if let Some(text) = insertion {
7533 let mut partial_completion = text
7534 .chars()
7535 .by_ref()
7536 .take_while(|c| c.is_alphabetic())
7537 .collect::<String>();
7538 if partial_completion.is_empty() {
7539 partial_completion = text
7540 .chars()
7541 .by_ref()
7542 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7543 .collect::<String>();
7544 }
7545
7546 cx.emit(EditorEvent::InputHandled {
7547 utf16_range_to_replace: None,
7548 text: partial_completion.clone().into(),
7549 });
7550
7551 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7552
7553 self.refresh_edit_prediction(true, true, window, cx);
7554 cx.notify();
7555 } else {
7556 self.accept_edit_prediction(&Default::default(), window, cx);
7557 }
7558 }
7559 }
7560 }
7561
7562 fn discard_edit_prediction(
7563 &mut self,
7564 should_report_edit_prediction_event: bool,
7565 cx: &mut Context<Self>,
7566 ) -> bool {
7567 if should_report_edit_prediction_event {
7568 let completion_id = self
7569 .active_edit_prediction
7570 .as_ref()
7571 .and_then(|active_completion| active_completion.completion_id.clone());
7572
7573 self.report_edit_prediction_event(completion_id, false, cx);
7574 }
7575
7576 if let Some(provider) = self.edit_prediction_provider() {
7577 provider.discard(cx);
7578 }
7579
7580 self.take_active_edit_prediction(cx)
7581 }
7582
7583 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7584 let Some(provider) = self.edit_prediction_provider() else {
7585 return;
7586 };
7587
7588 let Some((_, buffer, _)) = self
7589 .buffer
7590 .read(cx)
7591 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7592 else {
7593 return;
7594 };
7595
7596 let extension = buffer
7597 .read(cx)
7598 .file()
7599 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7600
7601 let event_type = match accepted {
7602 true => "Edit Prediction Accepted",
7603 false => "Edit Prediction Discarded",
7604 };
7605 telemetry::event!(
7606 event_type,
7607 provider = provider.name(),
7608 prediction_id = id,
7609 suggestion_accepted = accepted,
7610 file_extension = extension,
7611 );
7612 }
7613
7614 pub fn has_active_edit_prediction(&self) -> bool {
7615 self.active_edit_prediction.is_some()
7616 }
7617
7618 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7619 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7620 return false;
7621 };
7622
7623 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7624 self.clear_highlights::<EditPredictionHighlight>(cx);
7625 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7626 true
7627 }
7628
7629 /// Returns true when we're displaying the edit prediction popover below the cursor
7630 /// like we are not previewing and the LSP autocomplete menu is visible
7631 /// or we are in `when_holding_modifier` mode.
7632 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7633 if self.edit_prediction_preview_is_active()
7634 || !self.show_edit_predictions_in_menu()
7635 || !self.edit_predictions_enabled()
7636 {
7637 return false;
7638 }
7639
7640 if self.has_visible_completions_menu() {
7641 return true;
7642 }
7643
7644 has_completion && self.edit_prediction_requires_modifier()
7645 }
7646
7647 fn handle_modifiers_changed(
7648 &mut self,
7649 modifiers: Modifiers,
7650 position_map: &PositionMap,
7651 window: &mut Window,
7652 cx: &mut Context<Self>,
7653 ) {
7654 if self.show_edit_predictions_in_menu() {
7655 self.update_edit_prediction_preview(&modifiers, window, cx);
7656 }
7657
7658 self.update_selection_mode(&modifiers, position_map, window, cx);
7659
7660 let mouse_position = window.mouse_position();
7661 if !position_map.text_hitbox.is_hovered(window) {
7662 return;
7663 }
7664
7665 self.update_hovered_link(
7666 position_map.point_for_position(mouse_position),
7667 &position_map.snapshot,
7668 modifiers,
7669 window,
7670 cx,
7671 )
7672 }
7673
7674 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7675 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7676 if invert {
7677 match multi_cursor_setting {
7678 MultiCursorModifier::Alt => modifiers.alt,
7679 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7680 }
7681 } else {
7682 match multi_cursor_setting {
7683 MultiCursorModifier::Alt => modifiers.secondary(),
7684 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7685 }
7686 }
7687 }
7688
7689 fn columnar_selection_mode(
7690 modifiers: &Modifiers,
7691 cx: &mut Context<Self>,
7692 ) -> Option<ColumnarMode> {
7693 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7694 if Self::multi_cursor_modifier(false, modifiers, cx) {
7695 Some(ColumnarMode::FromMouse)
7696 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7697 Some(ColumnarMode::FromSelection)
7698 } else {
7699 None
7700 }
7701 } else {
7702 None
7703 }
7704 }
7705
7706 fn update_selection_mode(
7707 &mut self,
7708 modifiers: &Modifiers,
7709 position_map: &PositionMap,
7710 window: &mut Window,
7711 cx: &mut Context<Self>,
7712 ) {
7713 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7714 return;
7715 };
7716 if self.selections.pending.is_none() {
7717 return;
7718 }
7719
7720 let mouse_position = window.mouse_position();
7721 let point_for_position = position_map.point_for_position(mouse_position);
7722 let position = point_for_position.previous_valid;
7723
7724 self.select(
7725 SelectPhase::BeginColumnar {
7726 position,
7727 reset: false,
7728 mode,
7729 goal_column: point_for_position.exact_unclipped.column(),
7730 },
7731 window,
7732 cx,
7733 );
7734 }
7735
7736 fn update_edit_prediction_preview(
7737 &mut self,
7738 modifiers: &Modifiers,
7739 window: &mut Window,
7740 cx: &mut Context<Self>,
7741 ) {
7742 let mut modifiers_held = false;
7743 if let Some(accept_keystroke) = self
7744 .accept_edit_prediction_keybind(false, window, cx)
7745 .keystroke()
7746 {
7747 modifiers_held = modifiers_held
7748 || (accept_keystroke.modifiers() == modifiers
7749 && accept_keystroke.modifiers().modified());
7750 };
7751 if let Some(accept_partial_keystroke) = self
7752 .accept_edit_prediction_keybind(true, window, cx)
7753 .keystroke()
7754 {
7755 modifiers_held = modifiers_held
7756 || (accept_partial_keystroke.modifiers() == modifiers
7757 && accept_partial_keystroke.modifiers().modified());
7758 }
7759
7760 if modifiers_held {
7761 if matches!(
7762 self.edit_prediction_preview,
7763 EditPredictionPreview::Inactive { .. }
7764 ) {
7765 self.edit_prediction_preview = EditPredictionPreview::Active {
7766 previous_scroll_position: None,
7767 since: Instant::now(),
7768 };
7769
7770 self.update_visible_edit_prediction(window, cx);
7771 cx.notify();
7772 }
7773 } else if let EditPredictionPreview::Active {
7774 previous_scroll_position,
7775 since,
7776 } = self.edit_prediction_preview
7777 {
7778 if let (Some(previous_scroll_position), Some(position_map)) =
7779 (previous_scroll_position, self.last_position_map.as_ref())
7780 {
7781 self.set_scroll_position(
7782 previous_scroll_position
7783 .scroll_position(&position_map.snapshot.display_snapshot),
7784 window,
7785 cx,
7786 );
7787 }
7788
7789 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7790 released_too_fast: since.elapsed() < Duration::from_millis(200),
7791 };
7792 self.clear_row_highlights::<EditPredictionPreview>();
7793 self.update_visible_edit_prediction(window, cx);
7794 cx.notify();
7795 }
7796 }
7797
7798 fn update_visible_edit_prediction(
7799 &mut self,
7800 _window: &mut Window,
7801 cx: &mut Context<Self>,
7802 ) -> Option<()> {
7803 if DisableAiSettings::get_global(cx).disable_ai {
7804 return None;
7805 }
7806
7807 if self.ime_transaction.is_some() {
7808 self.discard_edit_prediction(false, cx);
7809 return None;
7810 }
7811
7812 let selection = self.selections.newest_anchor();
7813 let cursor = selection.head();
7814 let multibuffer = self.buffer.read(cx).snapshot(cx);
7815 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7816 let excerpt_id = cursor.excerpt_id;
7817
7818 let show_in_menu = self.show_edit_predictions_in_menu();
7819 let completions_menu_has_precedence = !show_in_menu
7820 && (self.context_menu.borrow().is_some()
7821 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7822
7823 if completions_menu_has_precedence
7824 || !offset_selection.is_empty()
7825 || self
7826 .active_edit_prediction
7827 .as_ref()
7828 .is_some_and(|completion| {
7829 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7830 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7831 !invalidation_range.contains(&offset_selection.head())
7832 })
7833 {
7834 self.discard_edit_prediction(false, cx);
7835 return None;
7836 }
7837
7838 self.take_active_edit_prediction(cx);
7839 let Some(provider) = self.edit_prediction_provider() else {
7840 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7841 return None;
7842 };
7843
7844 let (buffer, cursor_buffer_position) =
7845 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7846
7847 self.edit_prediction_settings =
7848 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7849
7850 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7851 self.discard_edit_prediction(false, cx);
7852 return None;
7853 };
7854
7855 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7856
7857 if self.edit_prediction_indent_conflict {
7858 let cursor_point = cursor.to_point(&multibuffer);
7859
7860 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7861
7862 if let Some((_, indent)) = indents.iter().next()
7863 && indent.len == cursor_point.column
7864 {
7865 self.edit_prediction_indent_conflict = false;
7866 }
7867 }
7868
7869 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7870 let edits = edit_prediction
7871 .edits
7872 .into_iter()
7873 .flat_map(|(range, new_text)| {
7874 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7875 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7876 Some((start..end, new_text))
7877 })
7878 .collect::<Vec<_>>();
7879 if edits.is_empty() {
7880 return None;
7881 }
7882
7883 let first_edit_start = edits.first().unwrap().0.start;
7884 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7885 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7886
7887 let last_edit_end = edits.last().unwrap().0.end;
7888 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7889 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7890
7891 let cursor_row = cursor.to_point(&multibuffer).row;
7892
7893 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7894
7895 let mut inlay_ids = Vec::new();
7896 let invalidation_row_range;
7897 let move_invalidation_row_range = if cursor_row < edit_start_row {
7898 Some(cursor_row..edit_end_row)
7899 } else if cursor_row > edit_end_row {
7900 Some(edit_start_row..cursor_row)
7901 } else {
7902 None
7903 };
7904 let supports_jump = self
7905 .edit_prediction_provider
7906 .as_ref()
7907 .map(|provider| provider.provider.supports_jump_to_edit())
7908 .unwrap_or(true);
7909
7910 let is_move = supports_jump
7911 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7912 let completion = if is_move {
7913 invalidation_row_range =
7914 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7915 let target = first_edit_start;
7916 EditPrediction::Move { target, snapshot }
7917 } else {
7918 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7919 && !self.edit_predictions_hidden_for_vim_mode;
7920
7921 if show_completions_in_buffer {
7922 if edits
7923 .iter()
7924 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7925 {
7926 let mut inlays = Vec::new();
7927 for (range, new_text) in &edits {
7928 let inlay = Inlay::edit_prediction(
7929 post_inc(&mut self.next_inlay_id),
7930 range.start,
7931 new_text.as_str(),
7932 );
7933 inlay_ids.push(inlay.id);
7934 inlays.push(inlay);
7935 }
7936
7937 self.splice_inlays(&[], inlays, cx);
7938 } else {
7939 let background_color = cx.theme().status().deleted_background;
7940 self.highlight_text::<EditPredictionHighlight>(
7941 edits.iter().map(|(range, _)| range.clone()).collect(),
7942 HighlightStyle {
7943 background_color: Some(background_color),
7944 ..Default::default()
7945 },
7946 cx,
7947 );
7948 }
7949 }
7950
7951 invalidation_row_range = edit_start_row..edit_end_row;
7952
7953 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7954 if provider.show_tab_accept_marker() {
7955 EditDisplayMode::TabAccept
7956 } else {
7957 EditDisplayMode::Inline
7958 }
7959 } else {
7960 EditDisplayMode::DiffPopover
7961 };
7962
7963 EditPrediction::Edit {
7964 edits,
7965 edit_preview: edit_prediction.edit_preview,
7966 display_mode,
7967 snapshot,
7968 }
7969 };
7970
7971 let invalidation_range = multibuffer
7972 .anchor_before(Point::new(invalidation_row_range.start, 0))
7973 ..multibuffer.anchor_after(Point::new(
7974 invalidation_row_range.end,
7975 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7976 ));
7977
7978 self.stale_edit_prediction_in_menu = None;
7979 self.active_edit_prediction = Some(EditPredictionState {
7980 inlay_ids,
7981 completion,
7982 completion_id: edit_prediction.id,
7983 invalidation_range,
7984 });
7985
7986 cx.notify();
7987
7988 Some(())
7989 }
7990
7991 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7992 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7993 }
7994
7995 fn clear_tasks(&mut self) {
7996 self.tasks.clear()
7997 }
7998
7999 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8000 if self.tasks.insert(key, value).is_some() {
8001 // This case should hopefully be rare, but just in case...
8002 log::error!(
8003 "multiple different run targets found on a single line, only the last target will be rendered"
8004 )
8005 }
8006 }
8007
8008 /// Get all display points of breakpoints that will be rendered within editor
8009 ///
8010 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8011 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8012 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8013 fn active_breakpoints(
8014 &self,
8015 range: Range<DisplayRow>,
8016 window: &mut Window,
8017 cx: &mut Context<Self>,
8018 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8019 let mut breakpoint_display_points = HashMap::default();
8020
8021 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8022 return breakpoint_display_points;
8023 };
8024
8025 let snapshot = self.snapshot(window, cx);
8026
8027 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8028 let Some(project) = self.project() else {
8029 return breakpoint_display_points;
8030 };
8031
8032 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8033 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8034
8035 for (buffer_snapshot, range, excerpt_id) in
8036 multi_buffer_snapshot.range_to_buffer_ranges(range)
8037 {
8038 let Some(buffer) = project
8039 .read(cx)
8040 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8041 else {
8042 continue;
8043 };
8044 let breakpoints = breakpoint_store.read(cx).breakpoints(
8045 &buffer,
8046 Some(
8047 buffer_snapshot.anchor_before(range.start)
8048 ..buffer_snapshot.anchor_after(range.end),
8049 ),
8050 buffer_snapshot,
8051 cx,
8052 );
8053 for (breakpoint, state) in breakpoints {
8054 let multi_buffer_anchor =
8055 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8056 let position = multi_buffer_anchor
8057 .to_point(multi_buffer_snapshot)
8058 .to_display_point(&snapshot);
8059
8060 breakpoint_display_points.insert(
8061 position.row(),
8062 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8063 );
8064 }
8065 }
8066
8067 breakpoint_display_points
8068 }
8069
8070 fn breakpoint_context_menu(
8071 &self,
8072 anchor: Anchor,
8073 window: &mut Window,
8074 cx: &mut Context<Self>,
8075 ) -> Entity<ui::ContextMenu> {
8076 let weak_editor = cx.weak_entity();
8077 let focus_handle = self.focus_handle(cx);
8078
8079 let row = self
8080 .buffer
8081 .read(cx)
8082 .snapshot(cx)
8083 .summary_for_anchor::<Point>(&anchor)
8084 .row;
8085
8086 let breakpoint = self
8087 .breakpoint_at_row(row, window, cx)
8088 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8089
8090 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8091 "Edit Log Breakpoint"
8092 } else {
8093 "Set Log Breakpoint"
8094 };
8095
8096 let condition_breakpoint_msg = if breakpoint
8097 .as_ref()
8098 .is_some_and(|bp| bp.1.condition.is_some())
8099 {
8100 "Edit Condition Breakpoint"
8101 } else {
8102 "Set Condition Breakpoint"
8103 };
8104
8105 let hit_condition_breakpoint_msg = if breakpoint
8106 .as_ref()
8107 .is_some_and(|bp| bp.1.hit_condition.is_some())
8108 {
8109 "Edit Hit Condition Breakpoint"
8110 } else {
8111 "Set Hit Condition Breakpoint"
8112 };
8113
8114 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8115 "Unset Breakpoint"
8116 } else {
8117 "Set Breakpoint"
8118 };
8119
8120 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8121
8122 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8123 BreakpointState::Enabled => Some("Disable"),
8124 BreakpointState::Disabled => Some("Enable"),
8125 });
8126
8127 let (anchor, breakpoint) =
8128 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8129
8130 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8131 menu.on_blur_subscription(Subscription::new(|| {}))
8132 .context(focus_handle)
8133 .when(run_to_cursor, |this| {
8134 let weak_editor = weak_editor.clone();
8135 this.entry("Run to cursor", None, move |window, cx| {
8136 weak_editor
8137 .update(cx, |editor, cx| {
8138 editor.change_selections(
8139 SelectionEffects::no_scroll(),
8140 window,
8141 cx,
8142 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8143 );
8144 })
8145 .ok();
8146
8147 window.dispatch_action(Box::new(RunToCursor), cx);
8148 })
8149 .separator()
8150 })
8151 .when_some(toggle_state_msg, |this, msg| {
8152 this.entry(msg, None, {
8153 let weak_editor = weak_editor.clone();
8154 let breakpoint = breakpoint.clone();
8155 move |_window, cx| {
8156 weak_editor
8157 .update(cx, |this, cx| {
8158 this.edit_breakpoint_at_anchor(
8159 anchor,
8160 breakpoint.as_ref().clone(),
8161 BreakpointEditAction::InvertState,
8162 cx,
8163 );
8164 })
8165 .log_err();
8166 }
8167 })
8168 })
8169 .entry(set_breakpoint_msg, None, {
8170 let weak_editor = weak_editor.clone();
8171 let breakpoint = breakpoint.clone();
8172 move |_window, cx| {
8173 weak_editor
8174 .update(cx, |this, cx| {
8175 this.edit_breakpoint_at_anchor(
8176 anchor,
8177 breakpoint.as_ref().clone(),
8178 BreakpointEditAction::Toggle,
8179 cx,
8180 );
8181 })
8182 .log_err();
8183 }
8184 })
8185 .entry(log_breakpoint_msg, None, {
8186 let breakpoint = breakpoint.clone();
8187 let weak_editor = weak_editor.clone();
8188 move |window, cx| {
8189 weak_editor
8190 .update(cx, |this, cx| {
8191 this.add_edit_breakpoint_block(
8192 anchor,
8193 breakpoint.as_ref(),
8194 BreakpointPromptEditAction::Log,
8195 window,
8196 cx,
8197 );
8198 })
8199 .log_err();
8200 }
8201 })
8202 .entry(condition_breakpoint_msg, None, {
8203 let breakpoint = breakpoint.clone();
8204 let weak_editor = weak_editor.clone();
8205 move |window, cx| {
8206 weak_editor
8207 .update(cx, |this, cx| {
8208 this.add_edit_breakpoint_block(
8209 anchor,
8210 breakpoint.as_ref(),
8211 BreakpointPromptEditAction::Condition,
8212 window,
8213 cx,
8214 );
8215 })
8216 .log_err();
8217 }
8218 })
8219 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8220 weak_editor
8221 .update(cx, |this, cx| {
8222 this.add_edit_breakpoint_block(
8223 anchor,
8224 breakpoint.as_ref(),
8225 BreakpointPromptEditAction::HitCondition,
8226 window,
8227 cx,
8228 );
8229 })
8230 .log_err();
8231 })
8232 })
8233 }
8234
8235 fn render_breakpoint(
8236 &self,
8237 position: Anchor,
8238 row: DisplayRow,
8239 breakpoint: &Breakpoint,
8240 state: Option<BreakpointSessionState>,
8241 cx: &mut Context<Self>,
8242 ) -> IconButton {
8243 let is_rejected = state.is_some_and(|s| !s.verified);
8244 // Is it a breakpoint that shows up when hovering over gutter?
8245 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8246 (false, false),
8247 |PhantomBreakpointIndicator {
8248 is_active,
8249 display_row,
8250 collides_with_existing_breakpoint,
8251 }| {
8252 (
8253 is_active && display_row == row,
8254 collides_with_existing_breakpoint,
8255 )
8256 },
8257 );
8258
8259 let (color, icon) = {
8260 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8261 (false, false) => ui::IconName::DebugBreakpoint,
8262 (true, false) => ui::IconName::DebugLogBreakpoint,
8263 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8264 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8265 };
8266
8267 let color = if is_phantom {
8268 Color::Hint
8269 } else if is_rejected {
8270 Color::Disabled
8271 } else {
8272 Color::Debugger
8273 };
8274
8275 (color, icon)
8276 };
8277
8278 let breakpoint = Arc::from(breakpoint.clone());
8279
8280 let alt_as_text = gpui::Keystroke {
8281 modifiers: Modifiers::secondary_key(),
8282 ..Default::default()
8283 };
8284 let primary_action_text = if breakpoint.is_disabled() {
8285 "Enable breakpoint"
8286 } else if is_phantom && !collides_with_existing {
8287 "Set breakpoint"
8288 } else {
8289 "Unset breakpoint"
8290 };
8291 let focus_handle = self.focus_handle.clone();
8292
8293 let meta = if is_rejected {
8294 SharedString::from("No executable code is associated with this line.")
8295 } else if collides_with_existing && !breakpoint.is_disabled() {
8296 SharedString::from(format!(
8297 "{alt_as_text}-click to disable,\nright-click for more options."
8298 ))
8299 } else {
8300 SharedString::from("Right-click for more options.")
8301 };
8302 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8303 .icon_size(IconSize::XSmall)
8304 .size(ui::ButtonSize::None)
8305 .when(is_rejected, |this| {
8306 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8307 })
8308 .icon_color(color)
8309 .style(ButtonStyle::Transparent)
8310 .on_click(cx.listener({
8311 move |editor, event: &ClickEvent, window, cx| {
8312 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8313 BreakpointEditAction::InvertState
8314 } else {
8315 BreakpointEditAction::Toggle
8316 };
8317
8318 window.focus(&editor.focus_handle(cx));
8319 editor.edit_breakpoint_at_anchor(
8320 position,
8321 breakpoint.as_ref().clone(),
8322 edit_action,
8323 cx,
8324 );
8325 }
8326 }))
8327 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8328 editor.set_breakpoint_context_menu(
8329 row,
8330 Some(position),
8331 event.position(),
8332 window,
8333 cx,
8334 );
8335 }))
8336 .tooltip(move |window, cx| {
8337 Tooltip::with_meta_in(
8338 primary_action_text,
8339 Some(&ToggleBreakpoint),
8340 meta.clone(),
8341 &focus_handle,
8342 window,
8343 cx,
8344 )
8345 })
8346 }
8347
8348 fn build_tasks_context(
8349 project: &Entity<Project>,
8350 buffer: &Entity<Buffer>,
8351 buffer_row: u32,
8352 tasks: &Arc<RunnableTasks>,
8353 cx: &mut Context<Self>,
8354 ) -> Task<Option<task::TaskContext>> {
8355 let position = Point::new(buffer_row, tasks.column);
8356 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8357 let location = Location {
8358 buffer: buffer.clone(),
8359 range: range_start..range_start,
8360 };
8361 // Fill in the environmental variables from the tree-sitter captures
8362 let mut captured_task_variables = TaskVariables::default();
8363 for (capture_name, value) in tasks.extra_variables.clone() {
8364 captured_task_variables.insert(
8365 task::VariableName::Custom(capture_name.into()),
8366 value.clone(),
8367 );
8368 }
8369 project.update(cx, |project, cx| {
8370 project.task_store().update(cx, |task_store, cx| {
8371 task_store.task_context_for_location(captured_task_variables, location, cx)
8372 })
8373 })
8374 }
8375
8376 pub fn spawn_nearest_task(
8377 &mut self,
8378 action: &SpawnNearestTask,
8379 window: &mut Window,
8380 cx: &mut Context<Self>,
8381 ) {
8382 let Some((workspace, _)) = self.workspace.clone() else {
8383 return;
8384 };
8385 let Some(project) = self.project.clone() else {
8386 return;
8387 };
8388
8389 // Try to find a closest, enclosing node using tree-sitter that has a task
8390 let Some((buffer, buffer_row, tasks)) = self
8391 .find_enclosing_node_task(cx)
8392 // Or find the task that's closest in row-distance.
8393 .or_else(|| self.find_closest_task(cx))
8394 else {
8395 return;
8396 };
8397
8398 let reveal_strategy = action.reveal;
8399 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8400 cx.spawn_in(window, async move |_, cx| {
8401 let context = task_context.await?;
8402 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8403
8404 let resolved = &mut resolved_task.resolved;
8405 resolved.reveal = reveal_strategy;
8406
8407 workspace
8408 .update_in(cx, |workspace, window, cx| {
8409 workspace.schedule_resolved_task(
8410 task_source_kind,
8411 resolved_task,
8412 false,
8413 window,
8414 cx,
8415 );
8416 })
8417 .ok()
8418 })
8419 .detach();
8420 }
8421
8422 fn find_closest_task(
8423 &mut self,
8424 cx: &mut Context<Self>,
8425 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8426 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8427
8428 let ((buffer_id, row), tasks) = self
8429 .tasks
8430 .iter()
8431 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8432
8433 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8434 let tasks = Arc::new(tasks.to_owned());
8435 Some((buffer, *row, tasks))
8436 }
8437
8438 fn find_enclosing_node_task(
8439 &mut self,
8440 cx: &mut Context<Self>,
8441 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8442 let snapshot = self.buffer.read(cx).snapshot(cx);
8443 let offset = self.selections.newest::<usize>(cx).head();
8444 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8445 let buffer_id = excerpt.buffer().remote_id();
8446
8447 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8448 let mut cursor = layer.node().walk();
8449
8450 while cursor.goto_first_child_for_byte(offset).is_some() {
8451 if cursor.node().end_byte() == offset {
8452 cursor.goto_next_sibling();
8453 }
8454 }
8455
8456 // Ascend to the smallest ancestor that contains the range and has a task.
8457 loop {
8458 let node = cursor.node();
8459 let node_range = node.byte_range();
8460 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8461
8462 // Check if this node contains our offset
8463 if node_range.start <= offset && node_range.end >= offset {
8464 // If it contains offset, check for task
8465 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8466 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8467 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8468 }
8469 }
8470
8471 if !cursor.goto_parent() {
8472 break;
8473 }
8474 }
8475 None
8476 }
8477
8478 fn render_run_indicator(
8479 &self,
8480 _style: &EditorStyle,
8481 is_active: bool,
8482 row: DisplayRow,
8483 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8484 cx: &mut Context<Self>,
8485 ) -> IconButton {
8486 let color = Color::Muted;
8487 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8488
8489 IconButton::new(
8490 ("run_indicator", row.0 as usize),
8491 ui::IconName::PlayOutlined,
8492 )
8493 .shape(ui::IconButtonShape::Square)
8494 .icon_size(IconSize::XSmall)
8495 .icon_color(color)
8496 .toggle_state(is_active)
8497 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8498 let quick_launch = match e {
8499 ClickEvent::Keyboard(_) => true,
8500 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8501 };
8502
8503 window.focus(&editor.focus_handle(cx));
8504 editor.toggle_code_actions(
8505 &ToggleCodeActions {
8506 deployed_from: Some(CodeActionSource::RunMenu(row)),
8507 quick_launch,
8508 },
8509 window,
8510 cx,
8511 );
8512 }))
8513 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8514 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8515 }))
8516 }
8517
8518 pub fn context_menu_visible(&self) -> bool {
8519 !self.edit_prediction_preview_is_active()
8520 && self
8521 .context_menu
8522 .borrow()
8523 .as_ref()
8524 .is_some_and(|menu| menu.visible())
8525 }
8526
8527 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8528 self.context_menu
8529 .borrow()
8530 .as_ref()
8531 .map(|menu| menu.origin())
8532 }
8533
8534 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8535 self.context_menu_options = Some(options);
8536 }
8537
8538 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8539 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8540
8541 fn render_edit_prediction_popover(
8542 &mut self,
8543 text_bounds: &Bounds<Pixels>,
8544 content_origin: gpui::Point<Pixels>,
8545 right_margin: Pixels,
8546 editor_snapshot: &EditorSnapshot,
8547 visible_row_range: Range<DisplayRow>,
8548 scroll_top: f32,
8549 scroll_bottom: f32,
8550 line_layouts: &[LineWithInvisibles],
8551 line_height: Pixels,
8552 scroll_pixel_position: gpui::Point<Pixels>,
8553 newest_selection_head: Option<DisplayPoint>,
8554 editor_width: Pixels,
8555 style: &EditorStyle,
8556 window: &mut Window,
8557 cx: &mut App,
8558 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8559 if self.mode().is_minimap() {
8560 return None;
8561 }
8562 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8563
8564 if self.edit_prediction_visible_in_cursor_popover(true) {
8565 return None;
8566 }
8567
8568 match &active_edit_prediction.completion {
8569 EditPrediction::Move { target, .. } => {
8570 let target_display_point = target.to_display_point(editor_snapshot);
8571
8572 if self.edit_prediction_requires_modifier() {
8573 if !self.edit_prediction_preview_is_active() {
8574 return None;
8575 }
8576
8577 self.render_edit_prediction_modifier_jump_popover(
8578 text_bounds,
8579 content_origin,
8580 visible_row_range,
8581 line_layouts,
8582 line_height,
8583 scroll_pixel_position,
8584 newest_selection_head,
8585 target_display_point,
8586 window,
8587 cx,
8588 )
8589 } else {
8590 self.render_edit_prediction_eager_jump_popover(
8591 text_bounds,
8592 content_origin,
8593 editor_snapshot,
8594 visible_row_range,
8595 scroll_top,
8596 scroll_bottom,
8597 line_height,
8598 scroll_pixel_position,
8599 target_display_point,
8600 editor_width,
8601 window,
8602 cx,
8603 )
8604 }
8605 }
8606 EditPrediction::Edit {
8607 display_mode: EditDisplayMode::Inline,
8608 ..
8609 } => None,
8610 EditPrediction::Edit {
8611 display_mode: EditDisplayMode::TabAccept,
8612 edits,
8613 ..
8614 } => {
8615 let range = &edits.first()?.0;
8616 let target_display_point = range.end.to_display_point(editor_snapshot);
8617
8618 self.render_edit_prediction_end_of_line_popover(
8619 "Accept",
8620 editor_snapshot,
8621 visible_row_range,
8622 target_display_point,
8623 line_height,
8624 scroll_pixel_position,
8625 content_origin,
8626 editor_width,
8627 window,
8628 cx,
8629 )
8630 }
8631 EditPrediction::Edit {
8632 edits,
8633 edit_preview,
8634 display_mode: EditDisplayMode::DiffPopover,
8635 snapshot,
8636 } => self.render_edit_prediction_diff_popover(
8637 text_bounds,
8638 content_origin,
8639 right_margin,
8640 editor_snapshot,
8641 visible_row_range,
8642 line_layouts,
8643 line_height,
8644 scroll_pixel_position,
8645 newest_selection_head,
8646 editor_width,
8647 style,
8648 edits,
8649 edit_preview,
8650 snapshot,
8651 window,
8652 cx,
8653 ),
8654 }
8655 }
8656
8657 fn render_edit_prediction_modifier_jump_popover(
8658 &mut self,
8659 text_bounds: &Bounds<Pixels>,
8660 content_origin: gpui::Point<Pixels>,
8661 visible_row_range: Range<DisplayRow>,
8662 line_layouts: &[LineWithInvisibles],
8663 line_height: Pixels,
8664 scroll_pixel_position: gpui::Point<Pixels>,
8665 newest_selection_head: Option<DisplayPoint>,
8666 target_display_point: DisplayPoint,
8667 window: &mut Window,
8668 cx: &mut App,
8669 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8670 let scrolled_content_origin =
8671 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8672
8673 const SCROLL_PADDING_Y: Pixels = px(12.);
8674
8675 if target_display_point.row() < visible_row_range.start {
8676 return self.render_edit_prediction_scroll_popover(
8677 |_| SCROLL_PADDING_Y,
8678 IconName::ArrowUp,
8679 visible_row_range,
8680 line_layouts,
8681 newest_selection_head,
8682 scrolled_content_origin,
8683 window,
8684 cx,
8685 );
8686 } else if target_display_point.row() >= visible_row_range.end {
8687 return self.render_edit_prediction_scroll_popover(
8688 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8689 IconName::ArrowDown,
8690 visible_row_range,
8691 line_layouts,
8692 newest_selection_head,
8693 scrolled_content_origin,
8694 window,
8695 cx,
8696 );
8697 }
8698
8699 const POLE_WIDTH: Pixels = px(2.);
8700
8701 let line_layout =
8702 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8703 let target_column = target_display_point.column() as usize;
8704
8705 let target_x = line_layout.x_for_index(target_column);
8706 let target_y =
8707 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8708
8709 let flag_on_right = target_x < text_bounds.size.width / 2.;
8710
8711 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8712 border_color.l += 0.001;
8713
8714 let mut element = v_flex()
8715 .items_end()
8716 .when(flag_on_right, |el| el.items_start())
8717 .child(if flag_on_right {
8718 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8719 .rounded_bl(px(0.))
8720 .rounded_tl(px(0.))
8721 .border_l_2()
8722 .border_color(border_color)
8723 } else {
8724 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8725 .rounded_br(px(0.))
8726 .rounded_tr(px(0.))
8727 .border_r_2()
8728 .border_color(border_color)
8729 })
8730 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8731 .into_any();
8732
8733 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8734
8735 let mut origin = scrolled_content_origin + point(target_x, target_y)
8736 - point(
8737 if flag_on_right {
8738 POLE_WIDTH
8739 } else {
8740 size.width - POLE_WIDTH
8741 },
8742 size.height - line_height,
8743 );
8744
8745 origin.x = origin.x.max(content_origin.x);
8746
8747 element.prepaint_at(origin, window, cx);
8748
8749 Some((element, origin))
8750 }
8751
8752 fn render_edit_prediction_scroll_popover(
8753 &mut self,
8754 to_y: impl Fn(Size<Pixels>) -> Pixels,
8755 scroll_icon: IconName,
8756 visible_row_range: Range<DisplayRow>,
8757 line_layouts: &[LineWithInvisibles],
8758 newest_selection_head: Option<DisplayPoint>,
8759 scrolled_content_origin: gpui::Point<Pixels>,
8760 window: &mut Window,
8761 cx: &mut App,
8762 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8763 let mut element = self
8764 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8765 .into_any();
8766
8767 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8768
8769 let cursor = newest_selection_head?;
8770 let cursor_row_layout =
8771 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8772 let cursor_column = cursor.column() as usize;
8773
8774 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8775
8776 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8777
8778 element.prepaint_at(origin, window, cx);
8779 Some((element, origin))
8780 }
8781
8782 fn render_edit_prediction_eager_jump_popover(
8783 &mut self,
8784 text_bounds: &Bounds<Pixels>,
8785 content_origin: gpui::Point<Pixels>,
8786 editor_snapshot: &EditorSnapshot,
8787 visible_row_range: Range<DisplayRow>,
8788 scroll_top: f32,
8789 scroll_bottom: f32,
8790 line_height: Pixels,
8791 scroll_pixel_position: gpui::Point<Pixels>,
8792 target_display_point: DisplayPoint,
8793 editor_width: Pixels,
8794 window: &mut Window,
8795 cx: &mut App,
8796 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8797 if target_display_point.row().as_f32() < scroll_top {
8798 let mut element = self
8799 .render_edit_prediction_line_popover(
8800 "Jump to Edit",
8801 Some(IconName::ArrowUp),
8802 window,
8803 cx,
8804 )?
8805 .into_any();
8806
8807 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8808 let offset = point(
8809 (text_bounds.size.width - size.width) / 2.,
8810 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8811 );
8812
8813 let origin = text_bounds.origin + offset;
8814 element.prepaint_at(origin, window, cx);
8815 Some((element, origin))
8816 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8817 let mut element = self
8818 .render_edit_prediction_line_popover(
8819 "Jump to Edit",
8820 Some(IconName::ArrowDown),
8821 window,
8822 cx,
8823 )?
8824 .into_any();
8825
8826 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8827 let offset = point(
8828 (text_bounds.size.width - size.width) / 2.,
8829 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8830 );
8831
8832 let origin = text_bounds.origin + offset;
8833 element.prepaint_at(origin, window, cx);
8834 Some((element, origin))
8835 } else {
8836 self.render_edit_prediction_end_of_line_popover(
8837 "Jump to Edit",
8838 editor_snapshot,
8839 visible_row_range,
8840 target_display_point,
8841 line_height,
8842 scroll_pixel_position,
8843 content_origin,
8844 editor_width,
8845 window,
8846 cx,
8847 )
8848 }
8849 }
8850
8851 fn render_edit_prediction_end_of_line_popover(
8852 self: &mut Editor,
8853 label: &'static str,
8854 editor_snapshot: &EditorSnapshot,
8855 visible_row_range: Range<DisplayRow>,
8856 target_display_point: DisplayPoint,
8857 line_height: Pixels,
8858 scroll_pixel_position: gpui::Point<Pixels>,
8859 content_origin: gpui::Point<Pixels>,
8860 editor_width: Pixels,
8861 window: &mut Window,
8862 cx: &mut App,
8863 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8864 let target_line_end = DisplayPoint::new(
8865 target_display_point.row(),
8866 editor_snapshot.line_len(target_display_point.row()),
8867 );
8868
8869 let mut element = self
8870 .render_edit_prediction_line_popover(label, None, window, cx)?
8871 .into_any();
8872
8873 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8874
8875 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8876
8877 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8878 let mut origin = start_point
8879 + line_origin
8880 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8881 origin.x = origin.x.max(content_origin.x);
8882
8883 let max_x = content_origin.x + editor_width - size.width;
8884
8885 if origin.x > max_x {
8886 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8887
8888 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8889 origin.y += offset;
8890 IconName::ArrowUp
8891 } else {
8892 origin.y -= offset;
8893 IconName::ArrowDown
8894 };
8895
8896 element = self
8897 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8898 .into_any();
8899
8900 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8901
8902 origin.x = content_origin.x + editor_width - size.width - px(2.);
8903 }
8904
8905 element.prepaint_at(origin, window, cx);
8906 Some((element, origin))
8907 }
8908
8909 fn render_edit_prediction_diff_popover(
8910 self: &Editor,
8911 text_bounds: &Bounds<Pixels>,
8912 content_origin: gpui::Point<Pixels>,
8913 right_margin: Pixels,
8914 editor_snapshot: &EditorSnapshot,
8915 visible_row_range: Range<DisplayRow>,
8916 line_layouts: &[LineWithInvisibles],
8917 line_height: Pixels,
8918 scroll_pixel_position: gpui::Point<Pixels>,
8919 newest_selection_head: Option<DisplayPoint>,
8920 editor_width: Pixels,
8921 style: &EditorStyle,
8922 edits: &Vec<(Range<Anchor>, String)>,
8923 edit_preview: &Option<language::EditPreview>,
8924 snapshot: &language::BufferSnapshot,
8925 window: &mut Window,
8926 cx: &mut App,
8927 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8928 let edit_start = edits
8929 .first()
8930 .unwrap()
8931 .0
8932 .start
8933 .to_display_point(editor_snapshot);
8934 let edit_end = edits
8935 .last()
8936 .unwrap()
8937 .0
8938 .end
8939 .to_display_point(editor_snapshot);
8940
8941 let is_visible = visible_row_range.contains(&edit_start.row())
8942 || visible_row_range.contains(&edit_end.row());
8943 if !is_visible {
8944 return None;
8945 }
8946
8947 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8948 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8949 } else {
8950 // Fallback for providers without edit_preview
8951 crate::edit_prediction_fallback_text(edits, cx)
8952 };
8953
8954 let styled_text = highlighted_edits.to_styled_text(&style.text);
8955 let line_count = highlighted_edits.text.lines().count();
8956
8957 const BORDER_WIDTH: Pixels = px(1.);
8958
8959 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8960 let has_keybind = keybind.is_some();
8961
8962 let mut element = h_flex()
8963 .items_start()
8964 .child(
8965 h_flex()
8966 .bg(cx.theme().colors().editor_background)
8967 .border(BORDER_WIDTH)
8968 .shadow_xs()
8969 .border_color(cx.theme().colors().border)
8970 .rounded_l_lg()
8971 .when(line_count > 1, |el| el.rounded_br_lg())
8972 .pr_1()
8973 .child(styled_text),
8974 )
8975 .child(
8976 h_flex()
8977 .h(line_height + BORDER_WIDTH * 2.)
8978 .px_1p5()
8979 .gap_1()
8980 // Workaround: For some reason, there's a gap if we don't do this
8981 .ml(-BORDER_WIDTH)
8982 .shadow(vec![gpui::BoxShadow {
8983 color: gpui::black().opacity(0.05),
8984 offset: point(px(1.), px(1.)),
8985 blur_radius: px(2.),
8986 spread_radius: px(0.),
8987 }])
8988 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8989 .border(BORDER_WIDTH)
8990 .border_color(cx.theme().colors().border)
8991 .rounded_r_lg()
8992 .id("edit_prediction_diff_popover_keybind")
8993 .when(!has_keybind, |el| {
8994 let status_colors = cx.theme().status();
8995
8996 el.bg(status_colors.error_background)
8997 .border_color(status_colors.error.opacity(0.6))
8998 .child(Icon::new(IconName::Info).color(Color::Error))
8999 .cursor_default()
9000 .hoverable_tooltip(move |_window, cx| {
9001 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9002 })
9003 })
9004 .children(keybind),
9005 )
9006 .into_any();
9007
9008 let longest_row =
9009 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9010 let longest_line_width = if visible_row_range.contains(&longest_row) {
9011 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9012 } else {
9013 layout_line(
9014 longest_row,
9015 editor_snapshot,
9016 style,
9017 editor_width,
9018 |_| false,
9019 window,
9020 cx,
9021 )
9022 .width
9023 };
9024
9025 let viewport_bounds =
9026 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9027 right: -right_margin,
9028 ..Default::default()
9029 });
9030
9031 let x_after_longest =
9032 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9033 - scroll_pixel_position.x;
9034
9035 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9036
9037 // Fully visible if it can be displayed within the window (allow overlapping other
9038 // panes). However, this is only allowed if the popover starts within text_bounds.
9039 let can_position_to_the_right = x_after_longest < text_bounds.right()
9040 && x_after_longest + element_bounds.width < viewport_bounds.right();
9041
9042 let mut origin = if can_position_to_the_right {
9043 point(
9044 x_after_longest,
9045 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9046 - scroll_pixel_position.y,
9047 )
9048 } else {
9049 let cursor_row = newest_selection_head.map(|head| head.row());
9050 let above_edit = edit_start
9051 .row()
9052 .0
9053 .checked_sub(line_count as u32)
9054 .map(DisplayRow);
9055 let below_edit = Some(edit_end.row() + 1);
9056 let above_cursor =
9057 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9058 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9059
9060 // Place the edit popover adjacent to the edit if there is a location
9061 // available that is onscreen and does not obscure the cursor. Otherwise,
9062 // place it adjacent to the cursor.
9063 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9064 .into_iter()
9065 .flatten()
9066 .find(|&start_row| {
9067 let end_row = start_row + line_count as u32;
9068 visible_row_range.contains(&start_row)
9069 && visible_row_range.contains(&end_row)
9070 && cursor_row
9071 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9072 })?;
9073
9074 content_origin
9075 + point(
9076 -scroll_pixel_position.x,
9077 row_target.as_f32() * line_height - scroll_pixel_position.y,
9078 )
9079 };
9080
9081 origin.x -= BORDER_WIDTH;
9082
9083 window.defer_draw(element, origin, 1);
9084
9085 // Do not return an element, since it will already be drawn due to defer_draw.
9086 None
9087 }
9088
9089 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9090 px(30.)
9091 }
9092
9093 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9094 if self.read_only(cx) {
9095 cx.theme().players().read_only()
9096 } else {
9097 self.style.as_ref().unwrap().local_player
9098 }
9099 }
9100
9101 fn render_edit_prediction_accept_keybind(
9102 &self,
9103 window: &mut Window,
9104 cx: &App,
9105 ) -> Option<AnyElement> {
9106 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9107 let accept_keystroke = accept_binding.keystroke()?;
9108
9109 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9110
9111 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9112 Color::Accent
9113 } else {
9114 Color::Muted
9115 };
9116
9117 h_flex()
9118 .px_0p5()
9119 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9120 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9121 .text_size(TextSize::XSmall.rems(cx))
9122 .child(h_flex().children(ui::render_modifiers(
9123 accept_keystroke.modifiers(),
9124 PlatformStyle::platform(),
9125 Some(modifiers_color),
9126 Some(IconSize::XSmall.rems().into()),
9127 true,
9128 )))
9129 .when(is_platform_style_mac, |parent| {
9130 parent.child(accept_keystroke.key().to_string())
9131 })
9132 .when(!is_platform_style_mac, |parent| {
9133 parent.child(
9134 Key::new(
9135 util::capitalize(accept_keystroke.key()),
9136 Some(Color::Default),
9137 )
9138 .size(Some(IconSize::XSmall.rems().into())),
9139 )
9140 })
9141 .into_any()
9142 .into()
9143 }
9144
9145 fn render_edit_prediction_line_popover(
9146 &self,
9147 label: impl Into<SharedString>,
9148 icon: Option<IconName>,
9149 window: &mut Window,
9150 cx: &App,
9151 ) -> Option<Stateful<Div>> {
9152 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9153
9154 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9155 let has_keybind = keybind.is_some();
9156
9157 let result = h_flex()
9158 .id("ep-line-popover")
9159 .py_0p5()
9160 .pl_1()
9161 .pr(padding_right)
9162 .gap_1()
9163 .rounded_md()
9164 .border_1()
9165 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9166 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9167 .shadow_xs()
9168 .when(!has_keybind, |el| {
9169 let status_colors = cx.theme().status();
9170
9171 el.bg(status_colors.error_background)
9172 .border_color(status_colors.error.opacity(0.6))
9173 .pl_2()
9174 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9175 .cursor_default()
9176 .hoverable_tooltip(move |_window, cx| {
9177 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9178 })
9179 })
9180 .children(keybind)
9181 .child(
9182 Label::new(label)
9183 .size(LabelSize::Small)
9184 .when(!has_keybind, |el| {
9185 el.color(cx.theme().status().error.into()).strikethrough()
9186 }),
9187 )
9188 .when(!has_keybind, |el| {
9189 el.child(
9190 h_flex().ml_1().child(
9191 Icon::new(IconName::Info)
9192 .size(IconSize::Small)
9193 .color(cx.theme().status().error.into()),
9194 ),
9195 )
9196 })
9197 .when_some(icon, |element, icon| {
9198 element.child(
9199 div()
9200 .mt(px(1.5))
9201 .child(Icon::new(icon).size(IconSize::Small)),
9202 )
9203 });
9204
9205 Some(result)
9206 }
9207
9208 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9209 let accent_color = cx.theme().colors().text_accent;
9210 let editor_bg_color = cx.theme().colors().editor_background;
9211 editor_bg_color.blend(accent_color.opacity(0.1))
9212 }
9213
9214 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9215 let accent_color = cx.theme().colors().text_accent;
9216 let editor_bg_color = cx.theme().colors().editor_background;
9217 editor_bg_color.blend(accent_color.opacity(0.6))
9218 }
9219 fn get_prediction_provider_icon_name(
9220 provider: &Option<RegisteredEditPredictionProvider>,
9221 ) -> IconName {
9222 match provider {
9223 Some(provider) => match provider.provider.name() {
9224 "copilot" => IconName::Copilot,
9225 "supermaven" => IconName::Supermaven,
9226 _ => IconName::ZedPredict,
9227 },
9228 None => IconName::ZedPredict,
9229 }
9230 }
9231
9232 fn render_edit_prediction_cursor_popover(
9233 &self,
9234 min_width: Pixels,
9235 max_width: Pixels,
9236 cursor_point: Point,
9237 style: &EditorStyle,
9238 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9239 _window: &Window,
9240 cx: &mut Context<Editor>,
9241 ) -> Option<AnyElement> {
9242 let provider = self.edit_prediction_provider.as_ref()?;
9243 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9244
9245 let is_refreshing = provider.provider.is_refreshing(cx);
9246
9247 fn pending_completion_container(icon: IconName) -> Div {
9248 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9249 }
9250
9251 let completion = match &self.active_edit_prediction {
9252 Some(prediction) => {
9253 if !self.has_visible_completions_menu() {
9254 const RADIUS: Pixels = px(6.);
9255 const BORDER_WIDTH: Pixels = px(1.);
9256
9257 return Some(
9258 h_flex()
9259 .elevation_2(cx)
9260 .border(BORDER_WIDTH)
9261 .border_color(cx.theme().colors().border)
9262 .when(accept_keystroke.is_none(), |el| {
9263 el.border_color(cx.theme().status().error)
9264 })
9265 .rounded(RADIUS)
9266 .rounded_tl(px(0.))
9267 .overflow_hidden()
9268 .child(div().px_1p5().child(match &prediction.completion {
9269 EditPrediction::Move { target, snapshot } => {
9270 use text::ToPoint as _;
9271 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9272 {
9273 Icon::new(IconName::ZedPredictDown)
9274 } else {
9275 Icon::new(IconName::ZedPredictUp)
9276 }
9277 }
9278 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9279 }))
9280 .child(
9281 h_flex()
9282 .gap_1()
9283 .py_1()
9284 .px_2()
9285 .rounded_r(RADIUS - BORDER_WIDTH)
9286 .border_l_1()
9287 .border_color(cx.theme().colors().border)
9288 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9289 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9290 el.child(
9291 Label::new("Hold")
9292 .size(LabelSize::Small)
9293 .when(accept_keystroke.is_none(), |el| {
9294 el.strikethrough()
9295 })
9296 .line_height_style(LineHeightStyle::UiLabel),
9297 )
9298 })
9299 .id("edit_prediction_cursor_popover_keybind")
9300 .when(accept_keystroke.is_none(), |el| {
9301 let status_colors = cx.theme().status();
9302
9303 el.bg(status_colors.error_background)
9304 .border_color(status_colors.error.opacity(0.6))
9305 .child(Icon::new(IconName::Info).color(Color::Error))
9306 .cursor_default()
9307 .hoverable_tooltip(move |_window, cx| {
9308 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9309 .into()
9310 })
9311 })
9312 .when_some(
9313 accept_keystroke.as_ref(),
9314 |el, accept_keystroke| {
9315 el.child(h_flex().children(ui::render_modifiers(
9316 accept_keystroke.modifiers(),
9317 PlatformStyle::platform(),
9318 Some(Color::Default),
9319 Some(IconSize::XSmall.rems().into()),
9320 false,
9321 )))
9322 },
9323 ),
9324 )
9325 .into_any(),
9326 );
9327 }
9328
9329 self.render_edit_prediction_cursor_popover_preview(
9330 prediction,
9331 cursor_point,
9332 style,
9333 cx,
9334 )?
9335 }
9336
9337 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9338 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9339 stale_completion,
9340 cursor_point,
9341 style,
9342 cx,
9343 )?,
9344
9345 None => pending_completion_container(provider_icon)
9346 .child(Label::new("...").size(LabelSize::Small)),
9347 },
9348
9349 None => pending_completion_container(provider_icon)
9350 .child(Label::new("...").size(LabelSize::Small)),
9351 };
9352
9353 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9354 completion
9355 .with_animation(
9356 "loading-completion",
9357 Animation::new(Duration::from_secs(2))
9358 .repeat()
9359 .with_easing(pulsating_between(0.4, 0.8)),
9360 |label, delta| label.opacity(delta),
9361 )
9362 .into_any_element()
9363 } else {
9364 completion.into_any_element()
9365 };
9366
9367 let has_completion = self.active_edit_prediction.is_some();
9368
9369 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9370 Some(
9371 h_flex()
9372 .min_w(min_width)
9373 .max_w(max_width)
9374 .flex_1()
9375 .elevation_2(cx)
9376 .border_color(cx.theme().colors().border)
9377 .child(
9378 div()
9379 .flex_1()
9380 .py_1()
9381 .px_2()
9382 .overflow_hidden()
9383 .child(completion),
9384 )
9385 .when_some(accept_keystroke, |el, accept_keystroke| {
9386 if !accept_keystroke.modifiers().modified() {
9387 return el;
9388 }
9389
9390 el.child(
9391 h_flex()
9392 .h_full()
9393 .border_l_1()
9394 .rounded_r_lg()
9395 .border_color(cx.theme().colors().border)
9396 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9397 .gap_1()
9398 .py_1()
9399 .px_2()
9400 .child(
9401 h_flex()
9402 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9403 .when(is_platform_style_mac, |parent| parent.gap_1())
9404 .child(h_flex().children(ui::render_modifiers(
9405 accept_keystroke.modifiers(),
9406 PlatformStyle::platform(),
9407 Some(if !has_completion {
9408 Color::Muted
9409 } else {
9410 Color::Default
9411 }),
9412 None,
9413 false,
9414 ))),
9415 )
9416 .child(Label::new("Preview").into_any_element())
9417 .opacity(if has_completion { 1.0 } else { 0.4 }),
9418 )
9419 })
9420 .into_any(),
9421 )
9422 }
9423
9424 fn render_edit_prediction_cursor_popover_preview(
9425 &self,
9426 completion: &EditPredictionState,
9427 cursor_point: Point,
9428 style: &EditorStyle,
9429 cx: &mut Context<Editor>,
9430 ) -> Option<Div> {
9431 use text::ToPoint as _;
9432
9433 fn render_relative_row_jump(
9434 prefix: impl Into<String>,
9435 current_row: u32,
9436 target_row: u32,
9437 ) -> Div {
9438 let (row_diff, arrow) = if target_row < current_row {
9439 (current_row - target_row, IconName::ArrowUp)
9440 } else {
9441 (target_row - current_row, IconName::ArrowDown)
9442 };
9443
9444 h_flex()
9445 .child(
9446 Label::new(format!("{}{}", prefix.into(), row_diff))
9447 .color(Color::Muted)
9448 .size(LabelSize::Small),
9449 )
9450 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9451 }
9452
9453 let supports_jump = self
9454 .edit_prediction_provider
9455 .as_ref()
9456 .map(|provider| provider.provider.supports_jump_to_edit())
9457 .unwrap_or(true);
9458
9459 match &completion.completion {
9460 EditPrediction::Move {
9461 target, snapshot, ..
9462 } => {
9463 if !supports_jump {
9464 return None;
9465 }
9466
9467 Some(
9468 h_flex()
9469 .px_2()
9470 .gap_2()
9471 .flex_1()
9472 .child(
9473 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9474 Icon::new(IconName::ZedPredictDown)
9475 } else {
9476 Icon::new(IconName::ZedPredictUp)
9477 },
9478 )
9479 .child(Label::new("Jump to Edit")),
9480 )
9481 }
9482
9483 EditPrediction::Edit {
9484 edits,
9485 edit_preview,
9486 snapshot,
9487 display_mode: _,
9488 } => {
9489 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9490
9491 let (highlighted_edits, has_more_lines) =
9492 if let Some(edit_preview) = edit_preview.as_ref() {
9493 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9494 .first_line_preview()
9495 } else {
9496 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9497 };
9498
9499 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9500 .with_default_highlights(&style.text, highlighted_edits.highlights);
9501
9502 let preview = h_flex()
9503 .gap_1()
9504 .min_w_16()
9505 .child(styled_text)
9506 .when(has_more_lines, |parent| parent.child("…"));
9507
9508 let left = if supports_jump && first_edit_row != cursor_point.row {
9509 render_relative_row_jump("", cursor_point.row, first_edit_row)
9510 .into_any_element()
9511 } else {
9512 let icon_name =
9513 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9514 Icon::new(icon_name).into_any_element()
9515 };
9516
9517 Some(
9518 h_flex()
9519 .h_full()
9520 .flex_1()
9521 .gap_2()
9522 .pr_1()
9523 .overflow_x_hidden()
9524 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9525 .child(left)
9526 .child(preview),
9527 )
9528 }
9529 }
9530 }
9531
9532 pub fn render_context_menu(
9533 &self,
9534 style: &EditorStyle,
9535 max_height_in_lines: u32,
9536 window: &mut Window,
9537 cx: &mut Context<Editor>,
9538 ) -> Option<AnyElement> {
9539 let menu = self.context_menu.borrow();
9540 let menu = menu.as_ref()?;
9541 if !menu.visible() {
9542 return None;
9543 };
9544 Some(menu.render(style, max_height_in_lines, window, cx))
9545 }
9546
9547 fn render_context_menu_aside(
9548 &mut self,
9549 max_size: Size<Pixels>,
9550 window: &mut Window,
9551 cx: &mut Context<Editor>,
9552 ) -> Option<AnyElement> {
9553 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9554 if menu.visible() {
9555 menu.render_aside(max_size, window, cx)
9556 } else {
9557 None
9558 }
9559 })
9560 }
9561
9562 fn hide_context_menu(
9563 &mut self,
9564 window: &mut Window,
9565 cx: &mut Context<Self>,
9566 ) -> Option<CodeContextMenu> {
9567 cx.notify();
9568 self.completion_tasks.clear();
9569 let context_menu = self.context_menu.borrow_mut().take();
9570 self.stale_edit_prediction_in_menu.take();
9571 self.update_visible_edit_prediction(window, cx);
9572 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9573 && let Some(completion_provider) = &self.completion_provider
9574 {
9575 completion_provider.selection_changed(None, window, cx);
9576 }
9577 context_menu
9578 }
9579
9580 fn show_snippet_choices(
9581 &mut self,
9582 choices: &Vec<String>,
9583 selection: Range<Anchor>,
9584 cx: &mut Context<Self>,
9585 ) {
9586 let Some((_, buffer, _)) = self
9587 .buffer()
9588 .read(cx)
9589 .excerpt_containing(selection.start, cx)
9590 else {
9591 return;
9592 };
9593 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9594 else {
9595 return;
9596 };
9597 if buffer != end_buffer {
9598 log::error!("expected anchor range to have matching buffer IDs");
9599 return;
9600 }
9601
9602 let id = post_inc(&mut self.next_completion_id);
9603 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9604 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9605 CompletionsMenu::new_snippet_choices(
9606 id,
9607 true,
9608 choices,
9609 selection,
9610 buffer,
9611 snippet_sort_order,
9612 ),
9613 ));
9614 }
9615
9616 pub fn insert_snippet(
9617 &mut self,
9618 insertion_ranges: &[Range<usize>],
9619 snippet: Snippet,
9620 window: &mut Window,
9621 cx: &mut Context<Self>,
9622 ) -> Result<()> {
9623 struct Tabstop<T> {
9624 is_end_tabstop: bool,
9625 ranges: Vec<Range<T>>,
9626 choices: Option<Vec<String>>,
9627 }
9628
9629 let tabstops = self.buffer.update(cx, |buffer, cx| {
9630 let snippet_text: Arc<str> = snippet.text.clone().into();
9631 let edits = insertion_ranges
9632 .iter()
9633 .cloned()
9634 .map(|range| (range, snippet_text.clone()));
9635 let autoindent_mode = AutoindentMode::Block {
9636 original_indent_columns: Vec::new(),
9637 };
9638 buffer.edit(edits, Some(autoindent_mode), cx);
9639
9640 let snapshot = &*buffer.read(cx);
9641 let snippet = &snippet;
9642 snippet
9643 .tabstops
9644 .iter()
9645 .map(|tabstop| {
9646 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9647 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9648 });
9649 let mut tabstop_ranges = tabstop
9650 .ranges
9651 .iter()
9652 .flat_map(|tabstop_range| {
9653 let mut delta = 0_isize;
9654 insertion_ranges.iter().map(move |insertion_range| {
9655 let insertion_start = insertion_range.start as isize + delta;
9656 delta +=
9657 snippet.text.len() as isize - insertion_range.len() as isize;
9658
9659 let start = ((insertion_start + tabstop_range.start) as usize)
9660 .min(snapshot.len());
9661 let end = ((insertion_start + tabstop_range.end) as usize)
9662 .min(snapshot.len());
9663 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9664 })
9665 })
9666 .collect::<Vec<_>>();
9667 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9668
9669 Tabstop {
9670 is_end_tabstop,
9671 ranges: tabstop_ranges,
9672 choices: tabstop.choices.clone(),
9673 }
9674 })
9675 .collect::<Vec<_>>()
9676 });
9677 if let Some(tabstop) = tabstops.first() {
9678 self.change_selections(Default::default(), window, cx, |s| {
9679 // Reverse order so that the first range is the newest created selection.
9680 // Completions will use it and autoscroll will prioritize it.
9681 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9682 });
9683
9684 if let Some(choices) = &tabstop.choices
9685 && let Some(selection) = tabstop.ranges.first()
9686 {
9687 self.show_snippet_choices(choices, selection.clone(), cx)
9688 }
9689
9690 // If we're already at the last tabstop and it's at the end of the snippet,
9691 // we're done, we don't need to keep the state around.
9692 if !tabstop.is_end_tabstop {
9693 let choices = tabstops
9694 .iter()
9695 .map(|tabstop| tabstop.choices.clone())
9696 .collect();
9697
9698 let ranges = tabstops
9699 .into_iter()
9700 .map(|tabstop| tabstop.ranges)
9701 .collect::<Vec<_>>();
9702
9703 self.snippet_stack.push(SnippetState {
9704 active_index: 0,
9705 ranges,
9706 choices,
9707 });
9708 }
9709
9710 // Check whether the just-entered snippet ends with an auto-closable bracket.
9711 if self.autoclose_regions.is_empty() {
9712 let snapshot = self.buffer.read(cx).snapshot(cx);
9713 let mut all_selections = self.selections.all::<Point>(cx);
9714 for selection in &mut all_selections {
9715 let selection_head = selection.head();
9716 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9717 continue;
9718 };
9719
9720 let mut bracket_pair = None;
9721 let max_lookup_length = scope
9722 .brackets()
9723 .map(|(pair, _)| {
9724 pair.start
9725 .as_str()
9726 .chars()
9727 .count()
9728 .max(pair.end.as_str().chars().count())
9729 })
9730 .max();
9731 if let Some(max_lookup_length) = max_lookup_length {
9732 let next_text = snapshot
9733 .chars_at(selection_head)
9734 .take(max_lookup_length)
9735 .collect::<String>();
9736 let prev_text = snapshot
9737 .reversed_chars_at(selection_head)
9738 .take(max_lookup_length)
9739 .collect::<String>();
9740
9741 for (pair, enabled) in scope.brackets() {
9742 if enabled
9743 && pair.close
9744 && prev_text.starts_with(pair.start.as_str())
9745 && next_text.starts_with(pair.end.as_str())
9746 {
9747 bracket_pair = Some(pair.clone());
9748 break;
9749 }
9750 }
9751 }
9752
9753 if let Some(pair) = bracket_pair {
9754 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9755 let autoclose_enabled =
9756 self.use_autoclose && snapshot_settings.use_autoclose;
9757 if autoclose_enabled {
9758 let start = snapshot.anchor_after(selection_head);
9759 let end = snapshot.anchor_after(selection_head);
9760 self.autoclose_regions.push(AutocloseRegion {
9761 selection_id: selection.id,
9762 range: start..end,
9763 pair,
9764 });
9765 }
9766 }
9767 }
9768 }
9769 }
9770 Ok(())
9771 }
9772
9773 pub fn move_to_next_snippet_tabstop(
9774 &mut self,
9775 window: &mut Window,
9776 cx: &mut Context<Self>,
9777 ) -> bool {
9778 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9779 }
9780
9781 pub fn move_to_prev_snippet_tabstop(
9782 &mut self,
9783 window: &mut Window,
9784 cx: &mut Context<Self>,
9785 ) -> bool {
9786 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9787 }
9788
9789 pub fn move_to_snippet_tabstop(
9790 &mut self,
9791 bias: Bias,
9792 window: &mut Window,
9793 cx: &mut Context<Self>,
9794 ) -> bool {
9795 if let Some(mut snippet) = self.snippet_stack.pop() {
9796 match bias {
9797 Bias::Left => {
9798 if snippet.active_index > 0 {
9799 snippet.active_index -= 1;
9800 } else {
9801 self.snippet_stack.push(snippet);
9802 return false;
9803 }
9804 }
9805 Bias::Right => {
9806 if snippet.active_index + 1 < snippet.ranges.len() {
9807 snippet.active_index += 1;
9808 } else {
9809 self.snippet_stack.push(snippet);
9810 return false;
9811 }
9812 }
9813 }
9814 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9815 self.change_selections(Default::default(), window, cx, |s| {
9816 // Reverse order so that the first range is the newest created selection.
9817 // Completions will use it and autoscroll will prioritize it.
9818 s.select_ranges(current_ranges.iter().rev().cloned())
9819 });
9820
9821 if let Some(choices) = &snippet.choices[snippet.active_index]
9822 && let Some(selection) = current_ranges.first()
9823 {
9824 self.show_snippet_choices(choices, selection.clone(), cx);
9825 }
9826
9827 // If snippet state is not at the last tabstop, push it back on the stack
9828 if snippet.active_index + 1 < snippet.ranges.len() {
9829 self.snippet_stack.push(snippet);
9830 }
9831 return true;
9832 }
9833 }
9834
9835 false
9836 }
9837
9838 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9839 self.transact(window, cx, |this, window, cx| {
9840 this.select_all(&SelectAll, window, cx);
9841 this.insert("", window, cx);
9842 });
9843 }
9844
9845 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9846 if self.read_only(cx) {
9847 return;
9848 }
9849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9850 self.transact(window, cx, |this, window, cx| {
9851 this.select_autoclose_pair(window, cx);
9852 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9853 if !this.linked_edit_ranges.is_empty() {
9854 let selections = this.selections.all::<MultiBufferPoint>(cx);
9855 let snapshot = this.buffer.read(cx).snapshot(cx);
9856
9857 for selection in selections.iter() {
9858 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9859 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9860 if selection_start.buffer_id != selection_end.buffer_id {
9861 continue;
9862 }
9863 if let Some(ranges) =
9864 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9865 {
9866 for (buffer, entries) in ranges {
9867 linked_ranges.entry(buffer).or_default().extend(entries);
9868 }
9869 }
9870 }
9871 }
9872
9873 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9874 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9875 for selection in &mut selections {
9876 if selection.is_empty() {
9877 let old_head = selection.head();
9878 let mut new_head =
9879 movement::left(&display_map, old_head.to_display_point(&display_map))
9880 .to_point(&display_map);
9881 if let Some((buffer, line_buffer_range)) = display_map
9882 .buffer_snapshot
9883 .buffer_line_for_row(MultiBufferRow(old_head.row))
9884 {
9885 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9886 let indent_len = match indent_size.kind {
9887 IndentKind::Space => {
9888 buffer.settings_at(line_buffer_range.start, cx).tab_size
9889 }
9890 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9891 };
9892 if old_head.column <= indent_size.len && old_head.column > 0 {
9893 let indent_len = indent_len.get();
9894 new_head = cmp::min(
9895 new_head,
9896 MultiBufferPoint::new(
9897 old_head.row,
9898 ((old_head.column - 1) / indent_len) * indent_len,
9899 ),
9900 );
9901 }
9902 }
9903
9904 selection.set_head(new_head, SelectionGoal::None);
9905 }
9906 }
9907
9908 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9909 this.insert("", window, cx);
9910 let empty_str: Arc<str> = Arc::from("");
9911 for (buffer, edits) in linked_ranges {
9912 let snapshot = buffer.read(cx).snapshot();
9913 use text::ToPoint as TP;
9914
9915 let edits = edits
9916 .into_iter()
9917 .map(|range| {
9918 let end_point = TP::to_point(&range.end, &snapshot);
9919 let mut start_point = TP::to_point(&range.start, &snapshot);
9920
9921 if end_point == start_point {
9922 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9923 .saturating_sub(1);
9924 start_point =
9925 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9926 };
9927
9928 (start_point..end_point, empty_str.clone())
9929 })
9930 .sorted_by_key(|(range, _)| range.start)
9931 .collect::<Vec<_>>();
9932 buffer.update(cx, |this, cx| {
9933 this.edit(edits, None, cx);
9934 })
9935 }
9936 this.refresh_edit_prediction(true, false, window, cx);
9937 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9938 });
9939 }
9940
9941 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9942 if self.read_only(cx) {
9943 return;
9944 }
9945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9946 self.transact(window, cx, |this, window, cx| {
9947 this.change_selections(Default::default(), window, cx, |s| {
9948 s.move_with(|map, selection| {
9949 if selection.is_empty() {
9950 let cursor = movement::right(map, selection.head());
9951 selection.end = cursor;
9952 selection.reversed = true;
9953 selection.goal = SelectionGoal::None;
9954 }
9955 })
9956 });
9957 this.insert("", window, cx);
9958 this.refresh_edit_prediction(true, false, window, cx);
9959 });
9960 }
9961
9962 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9963 if self.mode.is_single_line() {
9964 cx.propagate();
9965 return;
9966 }
9967
9968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9969 if self.move_to_prev_snippet_tabstop(window, cx) {
9970 return;
9971 }
9972 self.outdent(&Outdent, window, cx);
9973 }
9974
9975 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9976 if self.mode.is_single_line() {
9977 cx.propagate();
9978 return;
9979 }
9980
9981 if self.move_to_next_snippet_tabstop(window, cx) {
9982 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9983 return;
9984 }
9985 if self.read_only(cx) {
9986 return;
9987 }
9988 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9989 let mut selections = self.selections.all_adjusted(cx);
9990 let buffer = self.buffer.read(cx);
9991 let snapshot = buffer.snapshot(cx);
9992 let rows_iter = selections.iter().map(|s| s.head().row);
9993 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9994
9995 let has_some_cursor_in_whitespace = selections
9996 .iter()
9997 .filter(|selection| selection.is_empty())
9998 .any(|selection| {
9999 let cursor = selection.head();
10000 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10001 cursor.column < current_indent.len
10002 });
10003
10004 let mut edits = Vec::new();
10005 let mut prev_edited_row = 0;
10006 let mut row_delta = 0;
10007 for selection in &mut selections {
10008 if selection.start.row != prev_edited_row {
10009 row_delta = 0;
10010 }
10011 prev_edited_row = selection.end.row;
10012
10013 // If the selection is non-empty, then increase the indentation of the selected lines.
10014 if !selection.is_empty() {
10015 row_delta =
10016 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10017 continue;
10018 }
10019
10020 let cursor = selection.head();
10021 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10022 if let Some(suggested_indent) =
10023 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10024 {
10025 // Don't do anything if already at suggested indent
10026 // and there is any other cursor which is not
10027 if has_some_cursor_in_whitespace
10028 && cursor.column == current_indent.len
10029 && current_indent.len == suggested_indent.len
10030 {
10031 continue;
10032 }
10033
10034 // Adjust line and move cursor to suggested indent
10035 // if cursor is not at suggested indent
10036 if cursor.column < suggested_indent.len
10037 && cursor.column <= current_indent.len
10038 && current_indent.len <= suggested_indent.len
10039 {
10040 selection.start = Point::new(cursor.row, suggested_indent.len);
10041 selection.end = selection.start;
10042 if row_delta == 0 {
10043 edits.extend(Buffer::edit_for_indent_size_adjustment(
10044 cursor.row,
10045 current_indent,
10046 suggested_indent,
10047 ));
10048 row_delta = suggested_indent.len - current_indent.len;
10049 }
10050 continue;
10051 }
10052
10053 // If current indent is more than suggested indent
10054 // only move cursor to current indent and skip indent
10055 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10056 selection.start = Point::new(cursor.row, current_indent.len);
10057 selection.end = selection.start;
10058 continue;
10059 }
10060 }
10061
10062 // Otherwise, insert a hard or soft tab.
10063 let settings = buffer.language_settings_at(cursor, cx);
10064 let tab_size = if settings.hard_tabs {
10065 IndentSize::tab()
10066 } else {
10067 let tab_size = settings.tab_size.get();
10068 let indent_remainder = snapshot
10069 .text_for_range(Point::new(cursor.row, 0)..cursor)
10070 .flat_map(str::chars)
10071 .fold(row_delta % tab_size, |counter: u32, c| {
10072 if c == '\t' {
10073 0
10074 } else {
10075 (counter + 1) % tab_size
10076 }
10077 });
10078
10079 let chars_to_next_tab_stop = tab_size - indent_remainder;
10080 IndentSize::spaces(chars_to_next_tab_stop)
10081 };
10082 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10083 selection.end = selection.start;
10084 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10085 row_delta += tab_size.len;
10086 }
10087
10088 self.transact(window, cx, |this, window, cx| {
10089 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10090 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10091 this.refresh_edit_prediction(true, false, window, cx);
10092 });
10093 }
10094
10095 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10096 if self.read_only(cx) {
10097 return;
10098 }
10099 if self.mode.is_single_line() {
10100 cx.propagate();
10101 return;
10102 }
10103
10104 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10105 let mut selections = self.selections.all::<Point>(cx);
10106 let mut prev_edited_row = 0;
10107 let mut row_delta = 0;
10108 let mut edits = Vec::new();
10109 let buffer = self.buffer.read(cx);
10110 let snapshot = buffer.snapshot(cx);
10111 for selection in &mut selections {
10112 if selection.start.row != prev_edited_row {
10113 row_delta = 0;
10114 }
10115 prev_edited_row = selection.end.row;
10116
10117 row_delta =
10118 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10119 }
10120
10121 self.transact(window, cx, |this, window, cx| {
10122 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10123 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10124 });
10125 }
10126
10127 fn indent_selection(
10128 buffer: &MultiBuffer,
10129 snapshot: &MultiBufferSnapshot,
10130 selection: &mut Selection<Point>,
10131 edits: &mut Vec<(Range<Point>, String)>,
10132 delta_for_start_row: u32,
10133 cx: &App,
10134 ) -> u32 {
10135 let settings = buffer.language_settings_at(selection.start, cx);
10136 let tab_size = settings.tab_size.get();
10137 let indent_kind = if settings.hard_tabs {
10138 IndentKind::Tab
10139 } else {
10140 IndentKind::Space
10141 };
10142 let mut start_row = selection.start.row;
10143 let mut end_row = selection.end.row + 1;
10144
10145 // If a selection ends at the beginning of a line, don't indent
10146 // that last line.
10147 if selection.end.column == 0 && selection.end.row > selection.start.row {
10148 end_row -= 1;
10149 }
10150
10151 // Avoid re-indenting a row that has already been indented by a
10152 // previous selection, but still update this selection's column
10153 // to reflect that indentation.
10154 if delta_for_start_row > 0 {
10155 start_row += 1;
10156 selection.start.column += delta_for_start_row;
10157 if selection.end.row == selection.start.row {
10158 selection.end.column += delta_for_start_row;
10159 }
10160 }
10161
10162 let mut delta_for_end_row = 0;
10163 let has_multiple_rows = start_row + 1 != end_row;
10164 for row in start_row..end_row {
10165 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10166 let indent_delta = match (current_indent.kind, indent_kind) {
10167 (IndentKind::Space, IndentKind::Space) => {
10168 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10169 IndentSize::spaces(columns_to_next_tab_stop)
10170 }
10171 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10172 (_, IndentKind::Tab) => IndentSize::tab(),
10173 };
10174
10175 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10176 0
10177 } else {
10178 selection.start.column
10179 };
10180 let row_start = Point::new(row, start);
10181 edits.push((
10182 row_start..row_start,
10183 indent_delta.chars().collect::<String>(),
10184 ));
10185
10186 // Update this selection's endpoints to reflect the indentation.
10187 if row == selection.start.row {
10188 selection.start.column += indent_delta.len;
10189 }
10190 if row == selection.end.row {
10191 selection.end.column += indent_delta.len;
10192 delta_for_end_row = indent_delta.len;
10193 }
10194 }
10195
10196 if selection.start.row == selection.end.row {
10197 delta_for_start_row + delta_for_end_row
10198 } else {
10199 delta_for_end_row
10200 }
10201 }
10202
10203 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10204 if self.read_only(cx) {
10205 return;
10206 }
10207 if self.mode.is_single_line() {
10208 cx.propagate();
10209 return;
10210 }
10211
10212 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10213 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10214 let selections = self.selections.all::<Point>(cx);
10215 let mut deletion_ranges = Vec::new();
10216 let mut last_outdent = None;
10217 {
10218 let buffer = self.buffer.read(cx);
10219 let snapshot = buffer.snapshot(cx);
10220 for selection in &selections {
10221 let settings = buffer.language_settings_at(selection.start, cx);
10222 let tab_size = settings.tab_size.get();
10223 let mut rows = selection.spanned_rows(false, &display_map);
10224
10225 // Avoid re-outdenting a row that has already been outdented by a
10226 // previous selection.
10227 if let Some(last_row) = last_outdent
10228 && last_row == rows.start
10229 {
10230 rows.start = rows.start.next_row();
10231 }
10232 let has_multiple_rows = rows.len() > 1;
10233 for row in rows.iter_rows() {
10234 let indent_size = snapshot.indent_size_for_line(row);
10235 if indent_size.len > 0 {
10236 let deletion_len = match indent_size.kind {
10237 IndentKind::Space => {
10238 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10239 if columns_to_prev_tab_stop == 0 {
10240 tab_size
10241 } else {
10242 columns_to_prev_tab_stop
10243 }
10244 }
10245 IndentKind::Tab => 1,
10246 };
10247 let start = if has_multiple_rows
10248 || deletion_len > selection.start.column
10249 || indent_size.len < selection.start.column
10250 {
10251 0
10252 } else {
10253 selection.start.column - deletion_len
10254 };
10255 deletion_ranges.push(
10256 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10257 );
10258 last_outdent = Some(row);
10259 }
10260 }
10261 }
10262 }
10263
10264 self.transact(window, cx, |this, window, cx| {
10265 this.buffer.update(cx, |buffer, cx| {
10266 let empty_str: Arc<str> = Arc::default();
10267 buffer.edit(
10268 deletion_ranges
10269 .into_iter()
10270 .map(|range| (range, empty_str.clone())),
10271 None,
10272 cx,
10273 );
10274 });
10275 let selections = this.selections.all::<usize>(cx);
10276 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10277 });
10278 }
10279
10280 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10281 if self.read_only(cx) {
10282 return;
10283 }
10284 if self.mode.is_single_line() {
10285 cx.propagate();
10286 return;
10287 }
10288
10289 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10290 let selections = self
10291 .selections
10292 .all::<usize>(cx)
10293 .into_iter()
10294 .map(|s| s.range());
10295
10296 self.transact(window, cx, |this, window, cx| {
10297 this.buffer.update(cx, |buffer, cx| {
10298 buffer.autoindent_ranges(selections, cx);
10299 });
10300 let selections = this.selections.all::<usize>(cx);
10301 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10302 });
10303 }
10304
10305 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10306 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10307 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10308 let selections = self.selections.all::<Point>(cx);
10309
10310 let mut new_cursors = Vec::new();
10311 let mut edit_ranges = Vec::new();
10312 let mut selections = selections.iter().peekable();
10313 while let Some(selection) = selections.next() {
10314 let mut rows = selection.spanned_rows(false, &display_map);
10315 let goal_display_column = selection.head().to_display_point(&display_map).column();
10316
10317 // Accumulate contiguous regions of rows that we want to delete.
10318 while let Some(next_selection) = selections.peek() {
10319 let next_rows = next_selection.spanned_rows(false, &display_map);
10320 if next_rows.start <= rows.end {
10321 rows.end = next_rows.end;
10322 selections.next().unwrap();
10323 } else {
10324 break;
10325 }
10326 }
10327
10328 let buffer = &display_map.buffer_snapshot;
10329 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10330 let edit_end;
10331 let cursor_buffer_row;
10332 if buffer.max_point().row >= rows.end.0 {
10333 // If there's a line after the range, delete the \n from the end of the row range
10334 // and position the cursor on the next line.
10335 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10336 cursor_buffer_row = rows.end;
10337 } else {
10338 // If there isn't a line after the range, delete the \n from the line before the
10339 // start of the row range and position the cursor there.
10340 edit_start = edit_start.saturating_sub(1);
10341 edit_end = buffer.len();
10342 cursor_buffer_row = rows.start.previous_row();
10343 }
10344
10345 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10346 *cursor.column_mut() =
10347 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10348
10349 new_cursors.push((
10350 selection.id,
10351 buffer.anchor_after(cursor.to_point(&display_map)),
10352 ));
10353 edit_ranges.push(edit_start..edit_end);
10354 }
10355
10356 self.transact(window, cx, |this, window, cx| {
10357 let buffer = this.buffer.update(cx, |buffer, cx| {
10358 let empty_str: Arc<str> = Arc::default();
10359 buffer.edit(
10360 edit_ranges
10361 .into_iter()
10362 .map(|range| (range, empty_str.clone())),
10363 None,
10364 cx,
10365 );
10366 buffer.snapshot(cx)
10367 });
10368 let new_selections = new_cursors
10369 .into_iter()
10370 .map(|(id, cursor)| {
10371 let cursor = cursor.to_point(&buffer);
10372 Selection {
10373 id,
10374 start: cursor,
10375 end: cursor,
10376 reversed: false,
10377 goal: SelectionGoal::None,
10378 }
10379 })
10380 .collect();
10381
10382 this.change_selections(Default::default(), window, cx, |s| {
10383 s.select(new_selections);
10384 });
10385 });
10386 }
10387
10388 pub fn join_lines_impl(
10389 &mut self,
10390 insert_whitespace: bool,
10391 window: &mut Window,
10392 cx: &mut Context<Self>,
10393 ) {
10394 if self.read_only(cx) {
10395 return;
10396 }
10397 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10398 for selection in self.selections.all::<Point>(cx) {
10399 let start = MultiBufferRow(selection.start.row);
10400 // Treat single line selections as if they include the next line. Otherwise this action
10401 // would do nothing for single line selections individual cursors.
10402 let end = if selection.start.row == selection.end.row {
10403 MultiBufferRow(selection.start.row + 1)
10404 } else {
10405 MultiBufferRow(selection.end.row)
10406 };
10407
10408 if let Some(last_row_range) = row_ranges.last_mut()
10409 && start <= last_row_range.end
10410 {
10411 last_row_range.end = end;
10412 continue;
10413 }
10414 row_ranges.push(start..end);
10415 }
10416
10417 let snapshot = self.buffer.read(cx).snapshot(cx);
10418 let mut cursor_positions = Vec::new();
10419 for row_range in &row_ranges {
10420 let anchor = snapshot.anchor_before(Point::new(
10421 row_range.end.previous_row().0,
10422 snapshot.line_len(row_range.end.previous_row()),
10423 ));
10424 cursor_positions.push(anchor..anchor);
10425 }
10426
10427 self.transact(window, cx, |this, window, cx| {
10428 for row_range in row_ranges.into_iter().rev() {
10429 for row in row_range.iter_rows().rev() {
10430 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10431 let next_line_row = row.next_row();
10432 let indent = snapshot.indent_size_for_line(next_line_row);
10433 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10434
10435 let replace =
10436 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10437 " "
10438 } else {
10439 ""
10440 };
10441
10442 this.buffer.update(cx, |buffer, cx| {
10443 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10444 });
10445 }
10446 }
10447
10448 this.change_selections(Default::default(), window, cx, |s| {
10449 s.select_anchor_ranges(cursor_positions)
10450 });
10451 });
10452 }
10453
10454 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10456 self.join_lines_impl(true, window, cx);
10457 }
10458
10459 pub fn sort_lines_case_sensitive(
10460 &mut self,
10461 _: &SortLinesCaseSensitive,
10462 window: &mut Window,
10463 cx: &mut Context<Self>,
10464 ) {
10465 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10466 }
10467
10468 pub fn sort_lines_by_length(
10469 &mut self,
10470 _: &SortLinesByLength,
10471 window: &mut Window,
10472 cx: &mut Context<Self>,
10473 ) {
10474 self.manipulate_immutable_lines(window, cx, |lines| {
10475 lines.sort_by_key(|&line| line.chars().count())
10476 })
10477 }
10478
10479 pub fn sort_lines_case_insensitive(
10480 &mut self,
10481 _: &SortLinesCaseInsensitive,
10482 window: &mut Window,
10483 cx: &mut Context<Self>,
10484 ) {
10485 self.manipulate_immutable_lines(window, cx, |lines| {
10486 lines.sort_by_key(|line| line.to_lowercase())
10487 })
10488 }
10489
10490 pub fn unique_lines_case_insensitive(
10491 &mut self,
10492 _: &UniqueLinesCaseInsensitive,
10493 window: &mut Window,
10494 cx: &mut Context<Self>,
10495 ) {
10496 self.manipulate_immutable_lines(window, cx, |lines| {
10497 let mut seen = HashSet::default();
10498 lines.retain(|line| seen.insert(line.to_lowercase()));
10499 })
10500 }
10501
10502 pub fn unique_lines_case_sensitive(
10503 &mut self,
10504 _: &UniqueLinesCaseSensitive,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) {
10508 self.manipulate_immutable_lines(window, cx, |lines| {
10509 let mut seen = HashSet::default();
10510 lines.retain(|line| seen.insert(*line));
10511 })
10512 }
10513
10514 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10515 let snapshot = self.buffer.read(cx).snapshot(cx);
10516 for selection in self.selections.disjoint_anchors().iter() {
10517 if snapshot
10518 .language_at(selection.start)
10519 .and_then(|lang| lang.config().wrap_characters.as_ref())
10520 .is_some()
10521 {
10522 return true;
10523 }
10524 }
10525 false
10526 }
10527
10528 fn wrap_selections_in_tag(
10529 &mut self,
10530 _: &WrapSelectionsInTag,
10531 window: &mut Window,
10532 cx: &mut Context<Self>,
10533 ) {
10534 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10535
10536 let snapshot = self.buffer.read(cx).snapshot(cx);
10537
10538 let mut edits = Vec::new();
10539 let mut boundaries = Vec::new();
10540
10541 for selection in self.selections.all::<Point>(cx).iter() {
10542 let Some(wrap_config) = snapshot
10543 .language_at(selection.start)
10544 .and_then(|lang| lang.config().wrap_characters.clone())
10545 else {
10546 continue;
10547 };
10548
10549 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10550 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10551
10552 let start_before = snapshot.anchor_before(selection.start);
10553 let end_after = snapshot.anchor_after(selection.end);
10554
10555 edits.push((start_before..start_before, open_tag));
10556 edits.push((end_after..end_after, close_tag));
10557
10558 boundaries.push((
10559 start_before,
10560 end_after,
10561 wrap_config.start_prefix.len(),
10562 wrap_config.end_suffix.len(),
10563 ));
10564 }
10565
10566 if edits.is_empty() {
10567 return;
10568 }
10569
10570 self.transact(window, cx, |this, window, cx| {
10571 let buffer = this.buffer.update(cx, |buffer, cx| {
10572 buffer.edit(edits, None, cx);
10573 buffer.snapshot(cx)
10574 });
10575
10576 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10577 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10578 boundaries.into_iter()
10579 {
10580 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10581 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10582 new_selections.push(open_offset..open_offset);
10583 new_selections.push(close_offset..close_offset);
10584 }
10585
10586 this.change_selections(Default::default(), window, cx, |s| {
10587 s.select_ranges(new_selections);
10588 });
10589
10590 this.request_autoscroll(Autoscroll::fit(), cx);
10591 });
10592 }
10593
10594 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10595 let Some(project) = self.project.clone() else {
10596 return;
10597 };
10598 self.reload(project, window, cx)
10599 .detach_and_notify_err(window, cx);
10600 }
10601
10602 pub fn restore_file(
10603 &mut self,
10604 _: &::git::RestoreFile,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10609 let mut buffer_ids = HashSet::default();
10610 let snapshot = self.buffer().read(cx).snapshot(cx);
10611 for selection in self.selections.all::<usize>(cx) {
10612 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10613 }
10614
10615 let buffer = self.buffer().read(cx);
10616 let ranges = buffer_ids
10617 .into_iter()
10618 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10619 .collect::<Vec<_>>();
10620
10621 self.restore_hunks_in_ranges(ranges, window, cx);
10622 }
10623
10624 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10625 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10626 let selections = self
10627 .selections
10628 .all(cx)
10629 .into_iter()
10630 .map(|s| s.range())
10631 .collect();
10632 self.restore_hunks_in_ranges(selections, window, cx);
10633 }
10634
10635 pub fn restore_hunks_in_ranges(
10636 &mut self,
10637 ranges: Vec<Range<Point>>,
10638 window: &mut Window,
10639 cx: &mut Context<Editor>,
10640 ) {
10641 let mut revert_changes = HashMap::default();
10642 let chunk_by = self
10643 .snapshot(window, cx)
10644 .hunks_for_ranges(ranges)
10645 .into_iter()
10646 .chunk_by(|hunk| hunk.buffer_id);
10647 for (buffer_id, hunks) in &chunk_by {
10648 let hunks = hunks.collect::<Vec<_>>();
10649 for hunk in &hunks {
10650 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10651 }
10652 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10653 }
10654 drop(chunk_by);
10655 if !revert_changes.is_empty() {
10656 self.transact(window, cx, |editor, window, cx| {
10657 editor.restore(revert_changes, window, cx);
10658 });
10659 }
10660 }
10661
10662 pub fn open_active_item_in_terminal(
10663 &mut self,
10664 _: &OpenInTerminal,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10669 let project_path = buffer.read(cx).project_path(cx)?;
10670 let project = self.project()?.read(cx);
10671 let entry = project.entry_for_path(&project_path, cx)?;
10672 let parent = match &entry.canonical_path {
10673 Some(canonical_path) => canonical_path.to_path_buf(),
10674 None => project.absolute_path(&project_path, cx)?,
10675 }
10676 .parent()?
10677 .to_path_buf();
10678 Some(parent)
10679 }) {
10680 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10681 }
10682 }
10683
10684 fn set_breakpoint_context_menu(
10685 &mut self,
10686 display_row: DisplayRow,
10687 position: Option<Anchor>,
10688 clicked_point: gpui::Point<Pixels>,
10689 window: &mut Window,
10690 cx: &mut Context<Self>,
10691 ) {
10692 let source = self
10693 .buffer
10694 .read(cx)
10695 .snapshot(cx)
10696 .anchor_before(Point::new(display_row.0, 0u32));
10697
10698 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10699
10700 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10701 self,
10702 source,
10703 clicked_point,
10704 context_menu,
10705 window,
10706 cx,
10707 );
10708 }
10709
10710 fn add_edit_breakpoint_block(
10711 &mut self,
10712 anchor: Anchor,
10713 breakpoint: &Breakpoint,
10714 edit_action: BreakpointPromptEditAction,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) {
10718 let weak_editor = cx.weak_entity();
10719 let bp_prompt = cx.new(|cx| {
10720 BreakpointPromptEditor::new(
10721 weak_editor,
10722 anchor,
10723 breakpoint.clone(),
10724 edit_action,
10725 window,
10726 cx,
10727 )
10728 });
10729
10730 let height = bp_prompt.update(cx, |this, cx| {
10731 this.prompt
10732 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10733 });
10734 let cloned_prompt = bp_prompt.clone();
10735 let blocks = vec![BlockProperties {
10736 style: BlockStyle::Sticky,
10737 placement: BlockPlacement::Above(anchor),
10738 height: Some(height),
10739 render: Arc::new(move |cx| {
10740 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10741 cloned_prompt.clone().into_any_element()
10742 }),
10743 priority: 0,
10744 }];
10745
10746 let focus_handle = bp_prompt.focus_handle(cx);
10747 window.focus(&focus_handle);
10748
10749 let block_ids = self.insert_blocks(blocks, None, cx);
10750 bp_prompt.update(cx, |prompt, _| {
10751 prompt.add_block_ids(block_ids);
10752 });
10753 }
10754
10755 pub(crate) fn breakpoint_at_row(
10756 &self,
10757 row: u32,
10758 window: &mut Window,
10759 cx: &mut Context<Self>,
10760 ) -> Option<(Anchor, Breakpoint)> {
10761 let snapshot = self.snapshot(window, cx);
10762 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10763
10764 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10765 }
10766
10767 pub(crate) fn breakpoint_at_anchor(
10768 &self,
10769 breakpoint_position: Anchor,
10770 snapshot: &EditorSnapshot,
10771 cx: &mut Context<Self>,
10772 ) -> Option<(Anchor, Breakpoint)> {
10773 let buffer = self
10774 .buffer
10775 .read(cx)
10776 .buffer_for_anchor(breakpoint_position, cx)?;
10777
10778 let enclosing_excerpt = breakpoint_position.excerpt_id;
10779 let buffer_snapshot = buffer.read(cx).snapshot();
10780
10781 let row = buffer_snapshot
10782 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10783 .row;
10784
10785 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10786 let anchor_end = snapshot
10787 .buffer_snapshot
10788 .anchor_after(Point::new(row, line_len));
10789
10790 self.breakpoint_store
10791 .as_ref()?
10792 .read_with(cx, |breakpoint_store, cx| {
10793 breakpoint_store
10794 .breakpoints(
10795 &buffer,
10796 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10797 &buffer_snapshot,
10798 cx,
10799 )
10800 .next()
10801 .and_then(|(bp, _)| {
10802 let breakpoint_row = buffer_snapshot
10803 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10804 .row;
10805
10806 if breakpoint_row == row {
10807 snapshot
10808 .buffer_snapshot
10809 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10810 .map(|position| (position, bp.bp.clone()))
10811 } else {
10812 None
10813 }
10814 })
10815 })
10816 }
10817
10818 pub fn edit_log_breakpoint(
10819 &mut self,
10820 _: &EditLogBreakpoint,
10821 window: &mut Window,
10822 cx: &mut Context<Self>,
10823 ) {
10824 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10825 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10826 message: None,
10827 state: BreakpointState::Enabled,
10828 condition: None,
10829 hit_condition: None,
10830 });
10831
10832 self.add_edit_breakpoint_block(
10833 anchor,
10834 &breakpoint,
10835 BreakpointPromptEditAction::Log,
10836 window,
10837 cx,
10838 );
10839 }
10840 }
10841
10842 fn breakpoints_at_cursors(
10843 &self,
10844 window: &mut Window,
10845 cx: &mut Context<Self>,
10846 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10847 let snapshot = self.snapshot(window, cx);
10848 let cursors = self
10849 .selections
10850 .disjoint_anchors()
10851 .iter()
10852 .map(|selection| {
10853 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10854
10855 let breakpoint_position = self
10856 .breakpoint_at_row(cursor_position.row, window, cx)
10857 .map(|bp| bp.0)
10858 .unwrap_or_else(|| {
10859 snapshot
10860 .display_snapshot
10861 .buffer_snapshot
10862 .anchor_after(Point::new(cursor_position.row, 0))
10863 });
10864
10865 let breakpoint = self
10866 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10867 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10868
10869 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10870 })
10871 // 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.
10872 .collect::<HashMap<Anchor, _>>();
10873
10874 cursors.into_iter().collect()
10875 }
10876
10877 pub fn enable_breakpoint(
10878 &mut self,
10879 _: &crate::actions::EnableBreakpoint,
10880 window: &mut Window,
10881 cx: &mut Context<Self>,
10882 ) {
10883 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10884 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10885 continue;
10886 };
10887 self.edit_breakpoint_at_anchor(
10888 anchor,
10889 breakpoint,
10890 BreakpointEditAction::InvertState,
10891 cx,
10892 );
10893 }
10894 }
10895
10896 pub fn disable_breakpoint(
10897 &mut self,
10898 _: &crate::actions::DisableBreakpoint,
10899 window: &mut Window,
10900 cx: &mut Context<Self>,
10901 ) {
10902 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10903 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10904 continue;
10905 };
10906 self.edit_breakpoint_at_anchor(
10907 anchor,
10908 breakpoint,
10909 BreakpointEditAction::InvertState,
10910 cx,
10911 );
10912 }
10913 }
10914
10915 pub fn toggle_breakpoint(
10916 &mut self,
10917 _: &crate::actions::ToggleBreakpoint,
10918 window: &mut Window,
10919 cx: &mut Context<Self>,
10920 ) {
10921 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10922 if let Some(breakpoint) = breakpoint {
10923 self.edit_breakpoint_at_anchor(
10924 anchor,
10925 breakpoint,
10926 BreakpointEditAction::Toggle,
10927 cx,
10928 );
10929 } else {
10930 self.edit_breakpoint_at_anchor(
10931 anchor,
10932 Breakpoint::new_standard(),
10933 BreakpointEditAction::Toggle,
10934 cx,
10935 );
10936 }
10937 }
10938 }
10939
10940 pub fn edit_breakpoint_at_anchor(
10941 &mut self,
10942 breakpoint_position: Anchor,
10943 breakpoint: Breakpoint,
10944 edit_action: BreakpointEditAction,
10945 cx: &mut Context<Self>,
10946 ) {
10947 let Some(breakpoint_store) = &self.breakpoint_store else {
10948 return;
10949 };
10950
10951 let Some(buffer) = self
10952 .buffer
10953 .read(cx)
10954 .buffer_for_anchor(breakpoint_position, cx)
10955 else {
10956 return;
10957 };
10958
10959 breakpoint_store.update(cx, |breakpoint_store, cx| {
10960 breakpoint_store.toggle_breakpoint(
10961 buffer,
10962 BreakpointWithPosition {
10963 position: breakpoint_position.text_anchor,
10964 bp: breakpoint,
10965 },
10966 edit_action,
10967 cx,
10968 );
10969 });
10970
10971 cx.notify();
10972 }
10973
10974 #[cfg(any(test, feature = "test-support"))]
10975 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10976 self.breakpoint_store.clone()
10977 }
10978
10979 pub fn prepare_restore_change(
10980 &self,
10981 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10982 hunk: &MultiBufferDiffHunk,
10983 cx: &mut App,
10984 ) -> Option<()> {
10985 if hunk.is_created_file() {
10986 return None;
10987 }
10988 let buffer = self.buffer.read(cx);
10989 let diff = buffer.diff_for(hunk.buffer_id)?;
10990 let buffer = buffer.buffer(hunk.buffer_id)?;
10991 let buffer = buffer.read(cx);
10992 let original_text = diff
10993 .read(cx)
10994 .base_text()
10995 .as_rope()
10996 .slice(hunk.diff_base_byte_range.clone());
10997 let buffer_snapshot = buffer.snapshot();
10998 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10999 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11000 probe
11001 .0
11002 .start
11003 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11004 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11005 }) {
11006 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11007 Some(())
11008 } else {
11009 None
11010 }
11011 }
11012
11013 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11014 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11015 }
11016
11017 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11018 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11019 }
11020
11021 fn manipulate_lines<M>(
11022 &mut self,
11023 window: &mut Window,
11024 cx: &mut Context<Self>,
11025 mut manipulate: M,
11026 ) where
11027 M: FnMut(&str) -> LineManipulationResult,
11028 {
11029 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11030
11031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11032 let buffer = self.buffer.read(cx).snapshot(cx);
11033
11034 let mut edits = Vec::new();
11035
11036 let selections = self.selections.all::<Point>(cx);
11037 let mut selections = selections.iter().peekable();
11038 let mut contiguous_row_selections = Vec::new();
11039 let mut new_selections = Vec::new();
11040 let mut added_lines = 0;
11041 let mut removed_lines = 0;
11042
11043 while let Some(selection) = selections.next() {
11044 let (start_row, end_row) = consume_contiguous_rows(
11045 &mut contiguous_row_selections,
11046 selection,
11047 &display_map,
11048 &mut selections,
11049 );
11050
11051 let start_point = Point::new(start_row.0, 0);
11052 let end_point = Point::new(
11053 end_row.previous_row().0,
11054 buffer.line_len(end_row.previous_row()),
11055 );
11056 let text = buffer
11057 .text_for_range(start_point..end_point)
11058 .collect::<String>();
11059
11060 let LineManipulationResult {
11061 new_text,
11062 line_count_before,
11063 line_count_after,
11064 } = manipulate(&text);
11065
11066 edits.push((start_point..end_point, new_text));
11067
11068 // Selections must change based on added and removed line count
11069 let start_row =
11070 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11071 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11072 new_selections.push(Selection {
11073 id: selection.id,
11074 start: start_row,
11075 end: end_row,
11076 goal: SelectionGoal::None,
11077 reversed: selection.reversed,
11078 });
11079
11080 if line_count_after > line_count_before {
11081 added_lines += line_count_after - line_count_before;
11082 } else if line_count_before > line_count_after {
11083 removed_lines += line_count_before - line_count_after;
11084 }
11085 }
11086
11087 self.transact(window, cx, |this, window, cx| {
11088 let buffer = this.buffer.update(cx, |buffer, cx| {
11089 buffer.edit(edits, None, cx);
11090 buffer.snapshot(cx)
11091 });
11092
11093 // Recalculate offsets on newly edited buffer
11094 let new_selections = new_selections
11095 .iter()
11096 .map(|s| {
11097 let start_point = Point::new(s.start.0, 0);
11098 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11099 Selection {
11100 id: s.id,
11101 start: buffer.point_to_offset(start_point),
11102 end: buffer.point_to_offset(end_point),
11103 goal: s.goal,
11104 reversed: s.reversed,
11105 }
11106 })
11107 .collect();
11108
11109 this.change_selections(Default::default(), window, cx, |s| {
11110 s.select(new_selections);
11111 });
11112
11113 this.request_autoscroll(Autoscroll::fit(), cx);
11114 });
11115 }
11116
11117 fn manipulate_immutable_lines<Fn>(
11118 &mut self,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 mut callback: Fn,
11122 ) where
11123 Fn: FnMut(&mut Vec<&str>),
11124 {
11125 self.manipulate_lines(window, cx, |text| {
11126 let mut lines: Vec<&str> = text.split('\n').collect();
11127 let line_count_before = lines.len();
11128
11129 callback(&mut lines);
11130
11131 LineManipulationResult {
11132 new_text: lines.join("\n"),
11133 line_count_before,
11134 line_count_after: lines.len(),
11135 }
11136 });
11137 }
11138
11139 fn manipulate_mutable_lines<Fn>(
11140 &mut self,
11141 window: &mut Window,
11142 cx: &mut Context<Self>,
11143 mut callback: Fn,
11144 ) where
11145 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11146 {
11147 self.manipulate_lines(window, cx, |text| {
11148 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11149 let line_count_before = lines.len();
11150
11151 callback(&mut lines);
11152
11153 LineManipulationResult {
11154 new_text: lines.join("\n"),
11155 line_count_before,
11156 line_count_after: lines.len(),
11157 }
11158 });
11159 }
11160
11161 pub fn convert_indentation_to_spaces(
11162 &mut self,
11163 _: &ConvertIndentationToSpaces,
11164 window: &mut Window,
11165 cx: &mut Context<Self>,
11166 ) {
11167 let settings = self.buffer.read(cx).language_settings(cx);
11168 let tab_size = settings.tab_size.get() as usize;
11169
11170 self.manipulate_mutable_lines(window, cx, |lines| {
11171 // Allocates a reasonably sized scratch buffer once for the whole loop
11172 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11173 // Avoids recomputing spaces that could be inserted many times
11174 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11175 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11176 .collect();
11177
11178 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11179 let mut chars = line.as_ref().chars();
11180 let mut col = 0;
11181 let mut changed = false;
11182
11183 for ch in chars.by_ref() {
11184 match ch {
11185 ' ' => {
11186 reindented_line.push(' ');
11187 col += 1;
11188 }
11189 '\t' => {
11190 // \t are converted to spaces depending on the current column
11191 let spaces_len = tab_size - (col % tab_size);
11192 reindented_line.extend(&space_cache[spaces_len - 1]);
11193 col += spaces_len;
11194 changed = true;
11195 }
11196 _ => {
11197 // If we dont append before break, the character is consumed
11198 reindented_line.push(ch);
11199 break;
11200 }
11201 }
11202 }
11203
11204 if !changed {
11205 reindented_line.clear();
11206 continue;
11207 }
11208 // Append the rest of the line and replace old reference with new one
11209 reindented_line.extend(chars);
11210 *line = Cow::Owned(reindented_line.clone());
11211 reindented_line.clear();
11212 }
11213 });
11214 }
11215
11216 pub fn convert_indentation_to_tabs(
11217 &mut self,
11218 _: &ConvertIndentationToTabs,
11219 window: &mut Window,
11220 cx: &mut Context<Self>,
11221 ) {
11222 let settings = self.buffer.read(cx).language_settings(cx);
11223 let tab_size = settings.tab_size.get() as usize;
11224
11225 self.manipulate_mutable_lines(window, cx, |lines| {
11226 // Allocates a reasonably sized buffer once for the whole loop
11227 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11228 // Avoids recomputing spaces that could be inserted many times
11229 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11230 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11231 .collect();
11232
11233 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11234 let mut chars = line.chars();
11235 let mut spaces_count = 0;
11236 let mut first_non_indent_char = None;
11237 let mut changed = false;
11238
11239 for ch in chars.by_ref() {
11240 match ch {
11241 ' ' => {
11242 // Keep track of spaces. Append \t when we reach tab_size
11243 spaces_count += 1;
11244 changed = true;
11245 if spaces_count == tab_size {
11246 reindented_line.push('\t');
11247 spaces_count = 0;
11248 }
11249 }
11250 '\t' => {
11251 reindented_line.push('\t');
11252 spaces_count = 0;
11253 }
11254 _ => {
11255 // Dont append it yet, we might have remaining spaces
11256 first_non_indent_char = Some(ch);
11257 break;
11258 }
11259 }
11260 }
11261
11262 if !changed {
11263 reindented_line.clear();
11264 continue;
11265 }
11266 // Remaining spaces that didn't make a full tab stop
11267 if spaces_count > 0 {
11268 reindented_line.extend(&space_cache[spaces_count - 1]);
11269 }
11270 // If we consume an extra character that was not indentation, add it back
11271 if let Some(extra_char) = first_non_indent_char {
11272 reindented_line.push(extra_char);
11273 }
11274 // Append the rest of the line and replace old reference with new one
11275 reindented_line.extend(chars);
11276 *line = Cow::Owned(reindented_line.clone());
11277 reindented_line.clear();
11278 }
11279 });
11280 }
11281
11282 pub fn convert_to_upper_case(
11283 &mut self,
11284 _: &ConvertToUpperCase,
11285 window: &mut Window,
11286 cx: &mut Context<Self>,
11287 ) {
11288 self.manipulate_text(window, cx, |text| text.to_uppercase())
11289 }
11290
11291 pub fn convert_to_lower_case(
11292 &mut self,
11293 _: &ConvertToLowerCase,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 self.manipulate_text(window, cx, |text| text.to_lowercase())
11298 }
11299
11300 pub fn convert_to_title_case(
11301 &mut self,
11302 _: &ConvertToTitleCase,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.manipulate_text(window, cx, |text| {
11307 text.split('\n')
11308 .map(|line| line.to_case(Case::Title))
11309 .join("\n")
11310 })
11311 }
11312
11313 pub fn convert_to_snake_case(
11314 &mut self,
11315 _: &ConvertToSnakeCase,
11316 window: &mut Window,
11317 cx: &mut Context<Self>,
11318 ) {
11319 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11320 }
11321
11322 pub fn convert_to_kebab_case(
11323 &mut self,
11324 _: &ConvertToKebabCase,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11329 }
11330
11331 pub fn convert_to_upper_camel_case(
11332 &mut self,
11333 _: &ConvertToUpperCamelCase,
11334 window: &mut Window,
11335 cx: &mut Context<Self>,
11336 ) {
11337 self.manipulate_text(window, cx, |text| {
11338 text.split('\n')
11339 .map(|line| line.to_case(Case::UpperCamel))
11340 .join("\n")
11341 })
11342 }
11343
11344 pub fn convert_to_lower_camel_case(
11345 &mut self,
11346 _: &ConvertToLowerCamelCase,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11351 }
11352
11353 pub fn convert_to_opposite_case(
11354 &mut self,
11355 _: &ConvertToOppositeCase,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 self.manipulate_text(window, cx, |text| {
11360 text.chars()
11361 .fold(String::with_capacity(text.len()), |mut t, c| {
11362 if c.is_uppercase() {
11363 t.extend(c.to_lowercase());
11364 } else {
11365 t.extend(c.to_uppercase());
11366 }
11367 t
11368 })
11369 })
11370 }
11371
11372 pub fn convert_to_sentence_case(
11373 &mut self,
11374 _: &ConvertToSentenceCase,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11379 }
11380
11381 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11382 self.manipulate_text(window, cx, |text| {
11383 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11384 if has_upper_case_characters {
11385 text.to_lowercase()
11386 } else {
11387 text.to_uppercase()
11388 }
11389 })
11390 }
11391
11392 pub fn convert_to_rot13(
11393 &mut self,
11394 _: &ConvertToRot13,
11395 window: &mut Window,
11396 cx: &mut Context<Self>,
11397 ) {
11398 self.manipulate_text(window, cx, |text| {
11399 text.chars()
11400 .map(|c| match c {
11401 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11402 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11403 _ => c,
11404 })
11405 .collect()
11406 })
11407 }
11408
11409 pub fn convert_to_rot47(
11410 &mut self,
11411 _: &ConvertToRot47,
11412 window: &mut Window,
11413 cx: &mut Context<Self>,
11414 ) {
11415 self.manipulate_text(window, cx, |text| {
11416 text.chars()
11417 .map(|c| {
11418 let code_point = c as u32;
11419 if code_point >= 33 && code_point <= 126 {
11420 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11421 }
11422 c
11423 })
11424 .collect()
11425 })
11426 }
11427
11428 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11429 where
11430 Fn: FnMut(&str) -> String,
11431 {
11432 let buffer = self.buffer.read(cx).snapshot(cx);
11433
11434 let mut new_selections = Vec::new();
11435 let mut edits = Vec::new();
11436 let mut selection_adjustment = 0i32;
11437
11438 for selection in self.selections.all_adjusted(cx) {
11439 let selection_is_empty = selection.is_empty();
11440
11441 let (start, end) = if selection_is_empty {
11442 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11443 (word_range.start, word_range.end)
11444 } else {
11445 (
11446 buffer.point_to_offset(selection.start),
11447 buffer.point_to_offset(selection.end),
11448 )
11449 };
11450
11451 let text = buffer.text_for_range(start..end).collect::<String>();
11452 let old_length = text.len() as i32;
11453 let text = callback(&text);
11454
11455 new_selections.push(Selection {
11456 start: (start as i32 - selection_adjustment) as usize,
11457 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11458 goal: SelectionGoal::None,
11459 id: selection.id,
11460 reversed: selection.reversed,
11461 });
11462
11463 selection_adjustment += old_length - text.len() as i32;
11464
11465 edits.push((start..end, text));
11466 }
11467
11468 self.transact(window, cx, |this, window, cx| {
11469 this.buffer.update(cx, |buffer, cx| {
11470 buffer.edit(edits, None, cx);
11471 });
11472
11473 this.change_selections(Default::default(), window, cx, |s| {
11474 s.select(new_selections);
11475 });
11476
11477 this.request_autoscroll(Autoscroll::fit(), cx);
11478 });
11479 }
11480
11481 pub fn move_selection_on_drop(
11482 &mut self,
11483 selection: &Selection<Anchor>,
11484 target: DisplayPoint,
11485 is_cut: bool,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11490 let buffer = &display_map.buffer_snapshot;
11491 let mut edits = Vec::new();
11492 let insert_point = display_map
11493 .clip_point(target, Bias::Left)
11494 .to_point(&display_map);
11495 let text = buffer
11496 .text_for_range(selection.start..selection.end)
11497 .collect::<String>();
11498 if is_cut {
11499 edits.push(((selection.start..selection.end), String::new()));
11500 }
11501 let insert_anchor = buffer.anchor_before(insert_point);
11502 edits.push(((insert_anchor..insert_anchor), text));
11503 let last_edit_start = insert_anchor.bias_left(buffer);
11504 let last_edit_end = insert_anchor.bias_right(buffer);
11505 self.transact(window, cx, |this, window, cx| {
11506 this.buffer.update(cx, |buffer, cx| {
11507 buffer.edit(edits, None, cx);
11508 });
11509 this.change_selections(Default::default(), window, cx, |s| {
11510 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11511 });
11512 });
11513 }
11514
11515 pub fn clear_selection_drag_state(&mut self) {
11516 self.selection_drag_state = SelectionDragState::None;
11517 }
11518
11519 pub fn duplicate(
11520 &mut self,
11521 upwards: bool,
11522 whole_lines: bool,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11527
11528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11529 let buffer = &display_map.buffer_snapshot;
11530 let selections = self.selections.all::<Point>(cx);
11531
11532 let mut edits = Vec::new();
11533 let mut selections_iter = selections.iter().peekable();
11534 while let Some(selection) = selections_iter.next() {
11535 let mut rows = selection.spanned_rows(false, &display_map);
11536 // duplicate line-wise
11537 if whole_lines || selection.start == selection.end {
11538 // Avoid duplicating the same lines twice.
11539 while let Some(next_selection) = selections_iter.peek() {
11540 let next_rows = next_selection.spanned_rows(false, &display_map);
11541 if next_rows.start < rows.end {
11542 rows.end = next_rows.end;
11543 selections_iter.next().unwrap();
11544 } else {
11545 break;
11546 }
11547 }
11548
11549 // Copy the text from the selected row region and splice it either at the start
11550 // or end of the region.
11551 let start = Point::new(rows.start.0, 0);
11552 let end = Point::new(
11553 rows.end.previous_row().0,
11554 buffer.line_len(rows.end.previous_row()),
11555 );
11556 let text = buffer
11557 .text_for_range(start..end)
11558 .chain(Some("\n"))
11559 .collect::<String>();
11560 let insert_location = if upwards {
11561 Point::new(rows.end.0, 0)
11562 } else {
11563 start
11564 };
11565 edits.push((insert_location..insert_location, text));
11566 } else {
11567 // duplicate character-wise
11568 let start = selection.start;
11569 let end = selection.end;
11570 let text = buffer.text_for_range(start..end).collect::<String>();
11571 edits.push((selection.end..selection.end, text));
11572 }
11573 }
11574
11575 self.transact(window, cx, |this, _, cx| {
11576 this.buffer.update(cx, |buffer, cx| {
11577 buffer.edit(edits, None, cx);
11578 });
11579
11580 this.request_autoscroll(Autoscroll::fit(), cx);
11581 });
11582 }
11583
11584 pub fn duplicate_line_up(
11585 &mut self,
11586 _: &DuplicateLineUp,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 ) {
11590 self.duplicate(true, true, window, cx);
11591 }
11592
11593 pub fn duplicate_line_down(
11594 &mut self,
11595 _: &DuplicateLineDown,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 ) {
11599 self.duplicate(false, true, window, cx);
11600 }
11601
11602 pub fn duplicate_selection(
11603 &mut self,
11604 _: &DuplicateSelection,
11605 window: &mut Window,
11606 cx: &mut Context<Self>,
11607 ) {
11608 self.duplicate(false, false, window, cx);
11609 }
11610
11611 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11612 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11613 if self.mode.is_single_line() {
11614 cx.propagate();
11615 return;
11616 }
11617
11618 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11619 let buffer = self.buffer.read(cx).snapshot(cx);
11620
11621 let mut edits = Vec::new();
11622 let mut unfold_ranges = Vec::new();
11623 let mut refold_creases = Vec::new();
11624
11625 let selections = self.selections.all::<Point>(cx);
11626 let mut selections = selections.iter().peekable();
11627 let mut contiguous_row_selections = Vec::new();
11628 let mut new_selections = Vec::new();
11629
11630 while let Some(selection) = selections.next() {
11631 // Find all the selections that span a contiguous row range
11632 let (start_row, end_row) = consume_contiguous_rows(
11633 &mut contiguous_row_selections,
11634 selection,
11635 &display_map,
11636 &mut selections,
11637 );
11638
11639 // Move the text spanned by the row range to be before the line preceding the row range
11640 if start_row.0 > 0 {
11641 let range_to_move = Point::new(
11642 start_row.previous_row().0,
11643 buffer.line_len(start_row.previous_row()),
11644 )
11645 ..Point::new(
11646 end_row.previous_row().0,
11647 buffer.line_len(end_row.previous_row()),
11648 );
11649 let insertion_point = display_map
11650 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11651 .0;
11652
11653 // Don't move lines across excerpts
11654 if buffer
11655 .excerpt_containing(insertion_point..range_to_move.end)
11656 .is_some()
11657 {
11658 let text = buffer
11659 .text_for_range(range_to_move.clone())
11660 .flat_map(|s| s.chars())
11661 .skip(1)
11662 .chain(['\n'])
11663 .collect::<String>();
11664
11665 edits.push((
11666 buffer.anchor_after(range_to_move.start)
11667 ..buffer.anchor_before(range_to_move.end),
11668 String::new(),
11669 ));
11670 let insertion_anchor = buffer.anchor_after(insertion_point);
11671 edits.push((insertion_anchor..insertion_anchor, text));
11672
11673 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11674
11675 // Move selections up
11676 new_selections.extend(contiguous_row_selections.drain(..).map(
11677 |mut selection| {
11678 selection.start.row -= row_delta;
11679 selection.end.row -= row_delta;
11680 selection
11681 },
11682 ));
11683
11684 // Move folds up
11685 unfold_ranges.push(range_to_move.clone());
11686 for fold in display_map.folds_in_range(
11687 buffer.anchor_before(range_to_move.start)
11688 ..buffer.anchor_after(range_to_move.end),
11689 ) {
11690 let mut start = fold.range.start.to_point(&buffer);
11691 let mut end = fold.range.end.to_point(&buffer);
11692 start.row -= row_delta;
11693 end.row -= row_delta;
11694 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11695 }
11696 }
11697 }
11698
11699 // If we didn't move line(s), preserve the existing selections
11700 new_selections.append(&mut contiguous_row_selections);
11701 }
11702
11703 self.transact(window, cx, |this, window, cx| {
11704 this.unfold_ranges(&unfold_ranges, true, true, cx);
11705 this.buffer.update(cx, |buffer, cx| {
11706 for (range, text) in edits {
11707 buffer.edit([(range, text)], None, cx);
11708 }
11709 });
11710 this.fold_creases(refold_creases, true, window, cx);
11711 this.change_selections(Default::default(), window, cx, |s| {
11712 s.select(new_selections);
11713 })
11714 });
11715 }
11716
11717 pub fn move_line_down(
11718 &mut self,
11719 _: &MoveLineDown,
11720 window: &mut Window,
11721 cx: &mut Context<Self>,
11722 ) {
11723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11724 if self.mode.is_single_line() {
11725 cx.propagate();
11726 return;
11727 }
11728
11729 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11730 let buffer = self.buffer.read(cx).snapshot(cx);
11731
11732 let mut edits = Vec::new();
11733 let mut unfold_ranges = Vec::new();
11734 let mut refold_creases = Vec::new();
11735
11736 let selections = self.selections.all::<Point>(cx);
11737 let mut selections = selections.iter().peekable();
11738 let mut contiguous_row_selections = Vec::new();
11739 let mut new_selections = Vec::new();
11740
11741 while let Some(selection) = selections.next() {
11742 // Find all the selections that span a contiguous row range
11743 let (start_row, end_row) = consume_contiguous_rows(
11744 &mut contiguous_row_selections,
11745 selection,
11746 &display_map,
11747 &mut selections,
11748 );
11749
11750 // Move the text spanned by the row range to be after the last line of the row range
11751 if end_row.0 <= buffer.max_point().row {
11752 let range_to_move =
11753 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11754 let insertion_point = display_map
11755 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11756 .0;
11757
11758 // Don't move lines across excerpt boundaries
11759 if buffer
11760 .excerpt_containing(range_to_move.start..insertion_point)
11761 .is_some()
11762 {
11763 let mut text = String::from("\n");
11764 text.extend(buffer.text_for_range(range_to_move.clone()));
11765 text.pop(); // Drop trailing newline
11766 edits.push((
11767 buffer.anchor_after(range_to_move.start)
11768 ..buffer.anchor_before(range_to_move.end),
11769 String::new(),
11770 ));
11771 let insertion_anchor = buffer.anchor_after(insertion_point);
11772 edits.push((insertion_anchor..insertion_anchor, text));
11773
11774 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11775
11776 // Move selections down
11777 new_selections.extend(contiguous_row_selections.drain(..).map(
11778 |mut selection| {
11779 selection.start.row += row_delta;
11780 selection.end.row += row_delta;
11781 selection
11782 },
11783 ));
11784
11785 // Move folds down
11786 unfold_ranges.push(range_to_move.clone());
11787 for fold in display_map.folds_in_range(
11788 buffer.anchor_before(range_to_move.start)
11789 ..buffer.anchor_after(range_to_move.end),
11790 ) {
11791 let mut start = fold.range.start.to_point(&buffer);
11792 let mut end = fold.range.end.to_point(&buffer);
11793 start.row += row_delta;
11794 end.row += row_delta;
11795 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11796 }
11797 }
11798 }
11799
11800 // If we didn't move line(s), preserve the existing selections
11801 new_selections.append(&mut contiguous_row_selections);
11802 }
11803
11804 self.transact(window, cx, |this, window, cx| {
11805 this.unfold_ranges(&unfold_ranges, true, true, cx);
11806 this.buffer.update(cx, |buffer, cx| {
11807 for (range, text) in edits {
11808 buffer.edit([(range, text)], None, cx);
11809 }
11810 });
11811 this.fold_creases(refold_creases, true, window, cx);
11812 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11813 });
11814 }
11815
11816 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11817 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11818 let text_layout_details = &self.text_layout_details(window);
11819 self.transact(window, cx, |this, window, cx| {
11820 let edits = this.change_selections(Default::default(), window, cx, |s| {
11821 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11822 s.move_with(|display_map, selection| {
11823 if !selection.is_empty() {
11824 return;
11825 }
11826
11827 let mut head = selection.head();
11828 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11829 if head.column() == display_map.line_len(head.row()) {
11830 transpose_offset = display_map
11831 .buffer_snapshot
11832 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11833 }
11834
11835 if transpose_offset == 0 {
11836 return;
11837 }
11838
11839 *head.column_mut() += 1;
11840 head = display_map.clip_point(head, Bias::Right);
11841 let goal = SelectionGoal::HorizontalPosition(
11842 display_map
11843 .x_for_display_point(head, text_layout_details)
11844 .into(),
11845 );
11846 selection.collapse_to(head, goal);
11847
11848 let transpose_start = display_map
11849 .buffer_snapshot
11850 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11851 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11852 let transpose_end = display_map
11853 .buffer_snapshot
11854 .clip_offset(transpose_offset + 1, Bias::Right);
11855 if let Some(ch) =
11856 display_map.buffer_snapshot.chars_at(transpose_start).next()
11857 {
11858 edits.push((transpose_start..transpose_offset, String::new()));
11859 edits.push((transpose_end..transpose_end, ch.to_string()));
11860 }
11861 }
11862 });
11863 edits
11864 });
11865 this.buffer
11866 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11867 let selections = this.selections.all::<usize>(cx);
11868 this.change_selections(Default::default(), window, cx, |s| {
11869 s.select(selections);
11870 });
11871 });
11872 }
11873
11874 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11875 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11876 if self.mode.is_single_line() {
11877 cx.propagate();
11878 return;
11879 }
11880
11881 self.rewrap_impl(RewrapOptions::default(), cx)
11882 }
11883
11884 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11885 let buffer = self.buffer.read(cx).snapshot(cx);
11886 let selections = self.selections.all::<Point>(cx);
11887
11888 #[derive(Clone, Debug, PartialEq)]
11889 enum CommentFormat {
11890 /// single line comment, with prefix for line
11891 Line(String),
11892 /// single line within a block comment, with prefix for line
11893 BlockLine(String),
11894 /// a single line of a block comment that includes the initial delimiter
11895 BlockCommentWithStart(BlockCommentConfig),
11896 /// a single line of a block comment that includes the ending delimiter
11897 BlockCommentWithEnd(BlockCommentConfig),
11898 }
11899
11900 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11901 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11902 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11903 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11904 .peekable();
11905
11906 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11907 row
11908 } else {
11909 return Vec::new();
11910 };
11911
11912 let language_settings = buffer.language_settings_at(selection.head(), cx);
11913 let language_scope = buffer.language_scope_at(selection.head());
11914
11915 let indent_and_prefix_for_row =
11916 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11917 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11918 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11919 &language_scope
11920 {
11921 let indent_end = Point::new(row, indent.len);
11922 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11923 let line_text_after_indent = buffer
11924 .text_for_range(indent_end..line_end)
11925 .collect::<String>();
11926
11927 let is_within_comment_override = buffer
11928 .language_scope_at(indent_end)
11929 .is_some_and(|scope| scope.override_name() == Some("comment"));
11930 let comment_delimiters = if is_within_comment_override {
11931 // we are within a comment syntax node, but we don't
11932 // yet know what kind of comment: block, doc or line
11933 match (
11934 language_scope.documentation_comment(),
11935 language_scope.block_comment(),
11936 ) {
11937 (Some(config), _) | (_, Some(config))
11938 if buffer.contains_str_at(indent_end, &config.start) =>
11939 {
11940 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11941 }
11942 (Some(config), _) | (_, Some(config))
11943 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11944 {
11945 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11946 }
11947 (Some(config), _) | (_, Some(config))
11948 if buffer.contains_str_at(indent_end, &config.prefix) =>
11949 {
11950 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11951 }
11952 (_, _) => language_scope
11953 .line_comment_prefixes()
11954 .iter()
11955 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11956 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11957 }
11958 } else {
11959 // we not in an overridden comment node, but we may
11960 // be within a non-overridden line comment node
11961 language_scope
11962 .line_comment_prefixes()
11963 .iter()
11964 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11965 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11966 };
11967
11968 let rewrap_prefix = language_scope
11969 .rewrap_prefixes()
11970 .iter()
11971 .find_map(|prefix_regex| {
11972 prefix_regex.find(&line_text_after_indent).map(|mat| {
11973 if mat.start() == 0 {
11974 Some(mat.as_str().to_string())
11975 } else {
11976 None
11977 }
11978 })
11979 })
11980 .flatten();
11981 (comment_delimiters, rewrap_prefix)
11982 } else {
11983 (None, None)
11984 };
11985 (indent, comment_prefix, rewrap_prefix)
11986 };
11987
11988 let mut ranges = Vec::new();
11989 let from_empty_selection = selection.is_empty();
11990
11991 let mut current_range_start = first_row;
11992 let mut prev_row = first_row;
11993 let (
11994 mut current_range_indent,
11995 mut current_range_comment_delimiters,
11996 mut current_range_rewrap_prefix,
11997 ) = indent_and_prefix_for_row(first_row);
11998
11999 for row in non_blank_rows_iter.skip(1) {
12000 let has_paragraph_break = row > prev_row + 1;
12001
12002 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12003 indent_and_prefix_for_row(row);
12004
12005 let has_indent_change = row_indent != current_range_indent;
12006 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12007
12008 let has_boundary_change = has_comment_change
12009 || row_rewrap_prefix.is_some()
12010 || (has_indent_change && current_range_comment_delimiters.is_some());
12011
12012 if has_paragraph_break || has_boundary_change {
12013 ranges.push((
12014 language_settings.clone(),
12015 Point::new(current_range_start, 0)
12016 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12017 current_range_indent,
12018 current_range_comment_delimiters.clone(),
12019 current_range_rewrap_prefix.clone(),
12020 from_empty_selection,
12021 ));
12022 current_range_start = row;
12023 current_range_indent = row_indent;
12024 current_range_comment_delimiters = row_comment_delimiters;
12025 current_range_rewrap_prefix = row_rewrap_prefix;
12026 }
12027 prev_row = row;
12028 }
12029
12030 ranges.push((
12031 language_settings.clone(),
12032 Point::new(current_range_start, 0)
12033 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12034 current_range_indent,
12035 current_range_comment_delimiters,
12036 current_range_rewrap_prefix,
12037 from_empty_selection,
12038 ));
12039
12040 ranges
12041 });
12042
12043 let mut edits = Vec::new();
12044 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12045
12046 for (
12047 language_settings,
12048 wrap_range,
12049 mut indent_size,
12050 comment_prefix,
12051 rewrap_prefix,
12052 from_empty_selection,
12053 ) in wrap_ranges
12054 {
12055 let mut start_row = wrap_range.start.row;
12056 let mut end_row = wrap_range.end.row;
12057
12058 // Skip selections that overlap with a range that has already been rewrapped.
12059 let selection_range = start_row..end_row;
12060 if rewrapped_row_ranges
12061 .iter()
12062 .any(|range| range.overlaps(&selection_range))
12063 {
12064 continue;
12065 }
12066
12067 let tab_size = language_settings.tab_size;
12068
12069 let (line_prefix, inside_comment) = match &comment_prefix {
12070 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12071 (Some(prefix.as_str()), true)
12072 }
12073 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12074 (Some(prefix.as_ref()), true)
12075 }
12076 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12077 start: _,
12078 end: _,
12079 prefix,
12080 tab_size,
12081 })) => {
12082 indent_size.len += tab_size;
12083 (Some(prefix.as_ref()), true)
12084 }
12085 None => (None, false),
12086 };
12087 let indent_prefix = indent_size.chars().collect::<String>();
12088 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12089
12090 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12091 RewrapBehavior::InComments => inside_comment,
12092 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12093 RewrapBehavior::Anywhere => true,
12094 };
12095
12096 let should_rewrap = options.override_language_settings
12097 || allow_rewrap_based_on_language
12098 || self.hard_wrap.is_some();
12099 if !should_rewrap {
12100 continue;
12101 }
12102
12103 if from_empty_selection {
12104 'expand_upwards: while start_row > 0 {
12105 let prev_row = start_row - 1;
12106 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12107 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12108 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12109 {
12110 start_row = prev_row;
12111 } else {
12112 break 'expand_upwards;
12113 }
12114 }
12115
12116 'expand_downwards: while end_row < buffer.max_point().row {
12117 let next_row = end_row + 1;
12118 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12119 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12120 && !buffer.is_line_blank(MultiBufferRow(next_row))
12121 {
12122 end_row = next_row;
12123 } else {
12124 break 'expand_downwards;
12125 }
12126 }
12127 }
12128
12129 let start = Point::new(start_row, 0);
12130 let start_offset = start.to_offset(&buffer);
12131 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12132 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12133 let mut first_line_delimiter = None;
12134 let mut last_line_delimiter = None;
12135 let Some(lines_without_prefixes) = selection_text
12136 .lines()
12137 .enumerate()
12138 .map(|(ix, line)| {
12139 let line_trimmed = line.trim_start();
12140 if rewrap_prefix.is_some() && ix > 0 {
12141 Ok(line_trimmed)
12142 } else if let Some(
12143 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12144 start,
12145 prefix,
12146 end,
12147 tab_size,
12148 })
12149 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12150 start,
12151 prefix,
12152 end,
12153 tab_size,
12154 }),
12155 ) = &comment_prefix
12156 {
12157 let line_trimmed = line_trimmed
12158 .strip_prefix(start.as_ref())
12159 .map(|s| {
12160 let mut indent_size = indent_size;
12161 indent_size.len -= tab_size;
12162 let indent_prefix: String = indent_size.chars().collect();
12163 first_line_delimiter = Some((indent_prefix, start));
12164 s.trim_start()
12165 })
12166 .unwrap_or(line_trimmed);
12167 let line_trimmed = line_trimmed
12168 .strip_suffix(end.as_ref())
12169 .map(|s| {
12170 last_line_delimiter = Some(end);
12171 s.trim_end()
12172 })
12173 .unwrap_or(line_trimmed);
12174 let line_trimmed = line_trimmed
12175 .strip_prefix(prefix.as_ref())
12176 .unwrap_or(line_trimmed);
12177 Ok(line_trimmed)
12178 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12179 line_trimmed.strip_prefix(prefix).with_context(|| {
12180 format!("line did not start with prefix {prefix:?}: {line:?}")
12181 })
12182 } else {
12183 line_trimmed
12184 .strip_prefix(&line_prefix.trim_start())
12185 .with_context(|| {
12186 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12187 })
12188 }
12189 })
12190 .collect::<Result<Vec<_>, _>>()
12191 .log_err()
12192 else {
12193 continue;
12194 };
12195
12196 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12197 buffer
12198 .language_settings_at(Point::new(start_row, 0), cx)
12199 .preferred_line_length as usize
12200 });
12201
12202 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12203 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12204 } else {
12205 line_prefix.clone()
12206 };
12207
12208 let wrapped_text = {
12209 let mut wrapped_text = wrap_with_prefix(
12210 line_prefix,
12211 subsequent_lines_prefix,
12212 lines_without_prefixes.join("\n"),
12213 wrap_column,
12214 tab_size,
12215 options.preserve_existing_whitespace,
12216 );
12217
12218 if let Some((indent, delimiter)) = first_line_delimiter {
12219 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12220 }
12221 if let Some(last_line) = last_line_delimiter {
12222 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12223 }
12224
12225 wrapped_text
12226 };
12227
12228 // TODO: should always use char-based diff while still supporting cursor behavior that
12229 // matches vim.
12230 let mut diff_options = DiffOptions::default();
12231 if options.override_language_settings {
12232 diff_options.max_word_diff_len = 0;
12233 diff_options.max_word_diff_line_count = 0;
12234 } else {
12235 diff_options.max_word_diff_len = usize::MAX;
12236 diff_options.max_word_diff_line_count = usize::MAX;
12237 }
12238
12239 for (old_range, new_text) in
12240 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12241 {
12242 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12243 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12244 edits.push((edit_start..edit_end, new_text));
12245 }
12246
12247 rewrapped_row_ranges.push(start_row..=end_row);
12248 }
12249
12250 self.buffer
12251 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12252 }
12253
12254 pub fn cut_common(
12255 &mut self,
12256 cut_no_selection_line: bool,
12257 window: &mut Window,
12258 cx: &mut Context<Self>,
12259 ) -> ClipboardItem {
12260 let mut text = String::new();
12261 let buffer = self.buffer.read(cx).snapshot(cx);
12262 let mut selections = self.selections.all::<Point>(cx);
12263 let mut clipboard_selections = Vec::with_capacity(selections.len());
12264 {
12265 let max_point = buffer.max_point();
12266 let mut is_first = true;
12267 for selection in &mut selections {
12268 let is_entire_line =
12269 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12270 if is_entire_line {
12271 selection.start = Point::new(selection.start.row, 0);
12272 if !selection.is_empty() && selection.end.column == 0 {
12273 selection.end = cmp::min(max_point, selection.end);
12274 } else {
12275 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12276 }
12277 selection.goal = SelectionGoal::None;
12278 }
12279 if is_first {
12280 is_first = false;
12281 } else {
12282 text += "\n";
12283 }
12284 let mut len = 0;
12285 for chunk in buffer.text_for_range(selection.start..selection.end) {
12286 text.push_str(chunk);
12287 len += chunk.len();
12288 }
12289 clipboard_selections.push(ClipboardSelection {
12290 len,
12291 is_entire_line,
12292 first_line_indent: buffer
12293 .indent_size_for_line(MultiBufferRow(selection.start.row))
12294 .len,
12295 });
12296 }
12297 }
12298
12299 self.transact(window, cx, |this, window, cx| {
12300 this.change_selections(Default::default(), window, cx, |s| {
12301 s.select(selections);
12302 });
12303 this.insert("", window, cx);
12304 });
12305 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12306 }
12307
12308 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12310 let item = self.cut_common(true, window, cx);
12311 cx.write_to_clipboard(item);
12312 }
12313
12314 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12316 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12317 s.move_with(|snapshot, sel| {
12318 if sel.is_empty() {
12319 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12320 }
12321 if sel.is_empty() {
12322 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12323 }
12324 });
12325 });
12326 let item = self.cut_common(true, window, cx);
12327 cx.set_global(KillRing(item))
12328 }
12329
12330 pub fn kill_ring_yank(
12331 &mut self,
12332 _: &KillRingYank,
12333 window: &mut Window,
12334 cx: &mut Context<Self>,
12335 ) {
12336 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12337 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12338 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12339 (kill_ring.text().to_string(), kill_ring.metadata_json())
12340 } else {
12341 return;
12342 }
12343 } else {
12344 return;
12345 };
12346 self.do_paste(&text, metadata, false, window, cx);
12347 }
12348
12349 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12350 self.do_copy(true, cx);
12351 }
12352
12353 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12354 self.do_copy(false, cx);
12355 }
12356
12357 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12358 let selections = self.selections.all::<Point>(cx);
12359 let buffer = self.buffer.read(cx).read(cx);
12360 let mut text = String::new();
12361
12362 let mut clipboard_selections = Vec::with_capacity(selections.len());
12363 {
12364 let max_point = buffer.max_point();
12365 let mut is_first = true;
12366 for selection in &selections {
12367 let mut start = selection.start;
12368 let mut end = selection.end;
12369 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12370 if is_entire_line {
12371 start = Point::new(start.row, 0);
12372 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12373 }
12374
12375 let mut trimmed_selections = Vec::new();
12376 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12377 let row = MultiBufferRow(start.row);
12378 let first_indent = buffer.indent_size_for_line(row);
12379 if first_indent.len == 0 || start.column > first_indent.len {
12380 trimmed_selections.push(start..end);
12381 } else {
12382 trimmed_selections.push(
12383 Point::new(row.0, first_indent.len)
12384 ..Point::new(row.0, buffer.line_len(row)),
12385 );
12386 for row in start.row + 1..=end.row {
12387 let mut line_len = buffer.line_len(MultiBufferRow(row));
12388 if row == end.row {
12389 line_len = end.column;
12390 }
12391 if line_len == 0 {
12392 trimmed_selections
12393 .push(Point::new(row, 0)..Point::new(row, line_len));
12394 continue;
12395 }
12396 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12397 if row_indent_size.len >= first_indent.len {
12398 trimmed_selections.push(
12399 Point::new(row, first_indent.len)..Point::new(row, line_len),
12400 );
12401 } else {
12402 trimmed_selections.clear();
12403 trimmed_selections.push(start..end);
12404 break;
12405 }
12406 }
12407 }
12408 } else {
12409 trimmed_selections.push(start..end);
12410 }
12411
12412 for trimmed_range in trimmed_selections {
12413 if is_first {
12414 is_first = false;
12415 } else {
12416 text += "\n";
12417 }
12418 let mut len = 0;
12419 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12420 text.push_str(chunk);
12421 len += chunk.len();
12422 }
12423 clipboard_selections.push(ClipboardSelection {
12424 len,
12425 is_entire_line,
12426 first_line_indent: buffer
12427 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12428 .len,
12429 });
12430 }
12431 }
12432 }
12433
12434 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12435 text,
12436 clipboard_selections,
12437 ));
12438 }
12439
12440 pub fn do_paste(
12441 &mut self,
12442 text: &String,
12443 clipboard_selections: Option<Vec<ClipboardSelection>>,
12444 handle_entire_lines: bool,
12445 window: &mut Window,
12446 cx: &mut Context<Self>,
12447 ) {
12448 if self.read_only(cx) {
12449 return;
12450 }
12451
12452 let clipboard_text = Cow::Borrowed(text);
12453
12454 self.transact(window, cx, |this, window, cx| {
12455 let had_active_edit_prediction = this.has_active_edit_prediction();
12456
12457 if let Some(mut clipboard_selections) = clipboard_selections {
12458 let old_selections = this.selections.all::<usize>(cx);
12459 let all_selections_were_entire_line =
12460 clipboard_selections.iter().all(|s| s.is_entire_line);
12461 let first_selection_indent_column =
12462 clipboard_selections.first().map(|s| s.first_line_indent);
12463 if clipboard_selections.len() != old_selections.len() {
12464 clipboard_selections.drain(..);
12465 }
12466 let cursor_offset = this.selections.last::<usize>(cx).head();
12467 let mut auto_indent_on_paste = true;
12468
12469 this.buffer.update(cx, |buffer, cx| {
12470 let snapshot = buffer.read(cx);
12471 auto_indent_on_paste = snapshot
12472 .language_settings_at(cursor_offset, cx)
12473 .auto_indent_on_paste;
12474
12475 let mut start_offset = 0;
12476 let mut edits = Vec::new();
12477 let mut original_indent_columns = Vec::new();
12478 for (ix, selection) in old_selections.iter().enumerate() {
12479 let to_insert;
12480 let entire_line;
12481 let original_indent_column;
12482 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12483 let end_offset = start_offset + clipboard_selection.len;
12484 to_insert = &clipboard_text[start_offset..end_offset];
12485 entire_line = clipboard_selection.is_entire_line;
12486 start_offset = end_offset + 1;
12487 original_indent_column = Some(clipboard_selection.first_line_indent);
12488 } else {
12489 to_insert = clipboard_text.as_str();
12490 entire_line = all_selections_were_entire_line;
12491 original_indent_column = first_selection_indent_column
12492 }
12493
12494 // If the corresponding selection was empty when this slice of the
12495 // clipboard text was written, then the entire line containing the
12496 // selection was copied. If this selection is also currently empty,
12497 // then paste the line before the current line of the buffer.
12498 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12499 let column = selection.start.to_point(&snapshot).column as usize;
12500 let line_start = selection.start - column;
12501 line_start..line_start
12502 } else {
12503 selection.range()
12504 };
12505
12506 edits.push((range, to_insert));
12507 original_indent_columns.push(original_indent_column);
12508 }
12509 drop(snapshot);
12510
12511 buffer.edit(
12512 edits,
12513 if auto_indent_on_paste {
12514 Some(AutoindentMode::Block {
12515 original_indent_columns,
12516 })
12517 } else {
12518 None
12519 },
12520 cx,
12521 );
12522 });
12523
12524 let selections = this.selections.all::<usize>(cx);
12525 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12526 } else {
12527 this.insert(&clipboard_text, window, cx);
12528 }
12529
12530 let trigger_in_words =
12531 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12532
12533 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12534 });
12535 }
12536
12537 pub fn diff_clipboard_with_selection(
12538 &mut self,
12539 _: &DiffClipboardWithSelection,
12540 window: &mut Window,
12541 cx: &mut Context<Self>,
12542 ) {
12543 let selections = self.selections.all::<usize>(cx);
12544
12545 if selections.is_empty() {
12546 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12547 return;
12548 };
12549
12550 let clipboard_text = match cx.read_from_clipboard() {
12551 Some(item) => match item.entries().first() {
12552 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12553 _ => None,
12554 },
12555 None => None,
12556 };
12557
12558 let Some(clipboard_text) = clipboard_text else {
12559 log::warn!("Clipboard doesn't contain text.");
12560 return;
12561 };
12562
12563 window.dispatch_action(
12564 Box::new(DiffClipboardWithSelectionData {
12565 clipboard_text,
12566 editor: cx.entity(),
12567 }),
12568 cx,
12569 );
12570 }
12571
12572 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12574 if let Some(item) = cx.read_from_clipboard() {
12575 let entries = item.entries();
12576
12577 match entries.first() {
12578 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12579 // of all the pasted entries.
12580 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12581 .do_paste(
12582 clipboard_string.text(),
12583 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12584 true,
12585 window,
12586 cx,
12587 ),
12588 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12589 }
12590 }
12591 }
12592
12593 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12594 if self.read_only(cx) {
12595 return;
12596 }
12597
12598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12599
12600 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12601 if let Some((selections, _)) =
12602 self.selection_history.transaction(transaction_id).cloned()
12603 {
12604 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12605 s.select_anchors(selections.to_vec());
12606 });
12607 } else {
12608 log::error!(
12609 "No entry in selection_history found for undo. \
12610 This may correspond to a bug where undo does not update the selection. \
12611 If this is occurring, please add details to \
12612 https://github.com/zed-industries/zed/issues/22692"
12613 );
12614 }
12615 self.request_autoscroll(Autoscroll::fit(), cx);
12616 self.unmark_text(window, cx);
12617 self.refresh_edit_prediction(true, false, window, cx);
12618 cx.emit(EditorEvent::Edited { transaction_id });
12619 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12620 }
12621 }
12622
12623 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12624 if self.read_only(cx) {
12625 return;
12626 }
12627
12628 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12629
12630 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12631 if let Some((_, Some(selections))) =
12632 self.selection_history.transaction(transaction_id).cloned()
12633 {
12634 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12635 s.select_anchors(selections.to_vec());
12636 });
12637 } else {
12638 log::error!(
12639 "No entry in selection_history found for redo. \
12640 This may correspond to a bug where undo does not update the selection. \
12641 If this is occurring, please add details to \
12642 https://github.com/zed-industries/zed/issues/22692"
12643 );
12644 }
12645 self.request_autoscroll(Autoscroll::fit(), cx);
12646 self.unmark_text(window, cx);
12647 self.refresh_edit_prediction(true, false, window, cx);
12648 cx.emit(EditorEvent::Edited { transaction_id });
12649 }
12650 }
12651
12652 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12653 self.buffer
12654 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12655 }
12656
12657 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12658 self.buffer
12659 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12660 }
12661
12662 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12664 self.change_selections(Default::default(), window, cx, |s| {
12665 s.move_with(|map, selection| {
12666 let cursor = if selection.is_empty() {
12667 movement::left(map, selection.start)
12668 } else {
12669 selection.start
12670 };
12671 selection.collapse_to(cursor, SelectionGoal::None);
12672 });
12673 })
12674 }
12675
12676 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12678 self.change_selections(Default::default(), window, cx, |s| {
12679 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12680 })
12681 }
12682
12683 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12685 self.change_selections(Default::default(), window, cx, |s| {
12686 s.move_with(|map, selection| {
12687 let cursor = if selection.is_empty() {
12688 movement::right(map, selection.end)
12689 } else {
12690 selection.end
12691 };
12692 selection.collapse_to(cursor, SelectionGoal::None)
12693 });
12694 })
12695 }
12696
12697 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12699 self.change_selections(Default::default(), window, cx, |s| {
12700 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12701 })
12702 }
12703
12704 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12705 if self.take_rename(true, window, cx).is_some() {
12706 return;
12707 }
12708
12709 if self.mode.is_single_line() {
12710 cx.propagate();
12711 return;
12712 }
12713
12714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12715
12716 let text_layout_details = &self.text_layout_details(window);
12717 let selection_count = self.selections.count();
12718 let first_selection = self.selections.first_anchor();
12719
12720 self.change_selections(Default::default(), window, cx, |s| {
12721 s.move_with(|map, selection| {
12722 if !selection.is_empty() {
12723 selection.goal = SelectionGoal::None;
12724 }
12725 let (cursor, goal) = movement::up(
12726 map,
12727 selection.start,
12728 selection.goal,
12729 false,
12730 text_layout_details,
12731 );
12732 selection.collapse_to(cursor, goal);
12733 });
12734 });
12735
12736 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12737 {
12738 cx.propagate();
12739 }
12740 }
12741
12742 pub fn move_up_by_lines(
12743 &mut self,
12744 action: &MoveUpByLines,
12745 window: &mut Window,
12746 cx: &mut Context<Self>,
12747 ) {
12748 if self.take_rename(true, window, cx).is_some() {
12749 return;
12750 }
12751
12752 if self.mode.is_single_line() {
12753 cx.propagate();
12754 return;
12755 }
12756
12757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12758
12759 let text_layout_details = &self.text_layout_details(window);
12760
12761 self.change_selections(Default::default(), window, cx, |s| {
12762 s.move_with(|map, selection| {
12763 if !selection.is_empty() {
12764 selection.goal = SelectionGoal::None;
12765 }
12766 let (cursor, goal) = movement::up_by_rows(
12767 map,
12768 selection.start,
12769 action.lines,
12770 selection.goal,
12771 false,
12772 text_layout_details,
12773 );
12774 selection.collapse_to(cursor, goal);
12775 });
12776 })
12777 }
12778
12779 pub fn move_down_by_lines(
12780 &mut self,
12781 action: &MoveDownByLines,
12782 window: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 if self.take_rename(true, window, cx).is_some() {
12786 return;
12787 }
12788
12789 if self.mode.is_single_line() {
12790 cx.propagate();
12791 return;
12792 }
12793
12794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12795
12796 let text_layout_details = &self.text_layout_details(window);
12797
12798 self.change_selections(Default::default(), window, cx, |s| {
12799 s.move_with(|map, selection| {
12800 if !selection.is_empty() {
12801 selection.goal = SelectionGoal::None;
12802 }
12803 let (cursor, goal) = movement::down_by_rows(
12804 map,
12805 selection.start,
12806 action.lines,
12807 selection.goal,
12808 false,
12809 text_layout_details,
12810 );
12811 selection.collapse_to(cursor, goal);
12812 });
12813 })
12814 }
12815
12816 pub fn select_down_by_lines(
12817 &mut self,
12818 action: &SelectDownByLines,
12819 window: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) {
12822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12823 let text_layout_details = &self.text_layout_details(window);
12824 self.change_selections(Default::default(), window, cx, |s| {
12825 s.move_heads_with(|map, head, goal| {
12826 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12827 })
12828 })
12829 }
12830
12831 pub fn select_up_by_lines(
12832 &mut self,
12833 action: &SelectUpByLines,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12838 let text_layout_details = &self.text_layout_details(window);
12839 self.change_selections(Default::default(), window, cx, |s| {
12840 s.move_heads_with(|map, head, goal| {
12841 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12842 })
12843 })
12844 }
12845
12846 pub fn select_page_up(
12847 &mut self,
12848 _: &SelectPageUp,
12849 window: &mut Window,
12850 cx: &mut Context<Self>,
12851 ) {
12852 let Some(row_count) = self.visible_row_count() else {
12853 return;
12854 };
12855
12856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12857
12858 let text_layout_details = &self.text_layout_details(window);
12859
12860 self.change_selections(Default::default(), window, cx, |s| {
12861 s.move_heads_with(|map, head, goal| {
12862 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12863 })
12864 })
12865 }
12866
12867 pub fn move_page_up(
12868 &mut self,
12869 action: &MovePageUp,
12870 window: &mut Window,
12871 cx: &mut Context<Self>,
12872 ) {
12873 if self.take_rename(true, window, cx).is_some() {
12874 return;
12875 }
12876
12877 if self
12878 .context_menu
12879 .borrow_mut()
12880 .as_mut()
12881 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12882 .unwrap_or(false)
12883 {
12884 return;
12885 }
12886
12887 if matches!(self.mode, EditorMode::SingleLine) {
12888 cx.propagate();
12889 return;
12890 }
12891
12892 let Some(row_count) = self.visible_row_count() else {
12893 return;
12894 };
12895
12896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12897
12898 let effects = if action.center_cursor {
12899 SelectionEffects::scroll(Autoscroll::center())
12900 } else {
12901 SelectionEffects::default()
12902 };
12903
12904 let text_layout_details = &self.text_layout_details(window);
12905
12906 self.change_selections(effects, window, cx, |s| {
12907 s.move_with(|map, selection| {
12908 if !selection.is_empty() {
12909 selection.goal = SelectionGoal::None;
12910 }
12911 let (cursor, goal) = movement::up_by_rows(
12912 map,
12913 selection.end,
12914 row_count,
12915 selection.goal,
12916 false,
12917 text_layout_details,
12918 );
12919 selection.collapse_to(cursor, goal);
12920 });
12921 });
12922 }
12923
12924 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12926 let text_layout_details = &self.text_layout_details(window);
12927 self.change_selections(Default::default(), window, cx, |s| {
12928 s.move_heads_with(|map, head, goal| {
12929 movement::up(map, head, goal, false, text_layout_details)
12930 })
12931 })
12932 }
12933
12934 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12935 self.take_rename(true, window, cx);
12936
12937 if self.mode.is_single_line() {
12938 cx.propagate();
12939 return;
12940 }
12941
12942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12943
12944 let text_layout_details = &self.text_layout_details(window);
12945 let selection_count = self.selections.count();
12946 let first_selection = self.selections.first_anchor();
12947
12948 self.change_selections(Default::default(), window, cx, |s| {
12949 s.move_with(|map, selection| {
12950 if !selection.is_empty() {
12951 selection.goal = SelectionGoal::None;
12952 }
12953 let (cursor, goal) = movement::down(
12954 map,
12955 selection.end,
12956 selection.goal,
12957 false,
12958 text_layout_details,
12959 );
12960 selection.collapse_to(cursor, goal);
12961 });
12962 });
12963
12964 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12965 {
12966 cx.propagate();
12967 }
12968 }
12969
12970 pub fn select_page_down(
12971 &mut self,
12972 _: &SelectPageDown,
12973 window: &mut Window,
12974 cx: &mut Context<Self>,
12975 ) {
12976 let Some(row_count) = self.visible_row_count() else {
12977 return;
12978 };
12979
12980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12981
12982 let text_layout_details = &self.text_layout_details(window);
12983
12984 self.change_selections(Default::default(), window, cx, |s| {
12985 s.move_heads_with(|map, head, goal| {
12986 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12987 })
12988 })
12989 }
12990
12991 pub fn move_page_down(
12992 &mut self,
12993 action: &MovePageDown,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) {
12997 if self.take_rename(true, window, cx).is_some() {
12998 return;
12999 }
13000
13001 if self
13002 .context_menu
13003 .borrow_mut()
13004 .as_mut()
13005 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13006 .unwrap_or(false)
13007 {
13008 return;
13009 }
13010
13011 if matches!(self.mode, EditorMode::SingleLine) {
13012 cx.propagate();
13013 return;
13014 }
13015
13016 let Some(row_count) = self.visible_row_count() else {
13017 return;
13018 };
13019
13020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13021
13022 let effects = if action.center_cursor {
13023 SelectionEffects::scroll(Autoscroll::center())
13024 } else {
13025 SelectionEffects::default()
13026 };
13027
13028 let text_layout_details = &self.text_layout_details(window);
13029 self.change_selections(effects, window, cx, |s| {
13030 s.move_with(|map, selection| {
13031 if !selection.is_empty() {
13032 selection.goal = SelectionGoal::None;
13033 }
13034 let (cursor, goal) = movement::down_by_rows(
13035 map,
13036 selection.end,
13037 row_count,
13038 selection.goal,
13039 false,
13040 text_layout_details,
13041 );
13042 selection.collapse_to(cursor, goal);
13043 });
13044 });
13045 }
13046
13047 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13049 let text_layout_details = &self.text_layout_details(window);
13050 self.change_selections(Default::default(), window, cx, |s| {
13051 s.move_heads_with(|map, head, goal| {
13052 movement::down(map, head, goal, false, text_layout_details)
13053 })
13054 });
13055 }
13056
13057 pub fn context_menu_first(
13058 &mut self,
13059 _: &ContextMenuFirst,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13064 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13065 }
13066 }
13067
13068 pub fn context_menu_prev(
13069 &mut self,
13070 _: &ContextMenuPrevious,
13071 window: &mut Window,
13072 cx: &mut Context<Self>,
13073 ) {
13074 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13075 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13076 }
13077 }
13078
13079 pub fn context_menu_next(
13080 &mut self,
13081 _: &ContextMenuNext,
13082 window: &mut Window,
13083 cx: &mut Context<Self>,
13084 ) {
13085 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13086 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13087 }
13088 }
13089
13090 pub fn context_menu_last(
13091 &mut self,
13092 _: &ContextMenuLast,
13093 window: &mut Window,
13094 cx: &mut Context<Self>,
13095 ) {
13096 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13097 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13098 }
13099 }
13100
13101 pub fn signature_help_prev(
13102 &mut self,
13103 _: &SignatureHelpPrevious,
13104 _: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 if let Some(popover) = self.signature_help_state.popover_mut() {
13108 if popover.current_signature == 0 {
13109 popover.current_signature = popover.signatures.len() - 1;
13110 } else {
13111 popover.current_signature -= 1;
13112 }
13113 cx.notify();
13114 }
13115 }
13116
13117 pub fn signature_help_next(
13118 &mut self,
13119 _: &SignatureHelpNext,
13120 _: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) {
13123 if let Some(popover) = self.signature_help_state.popover_mut() {
13124 if popover.current_signature + 1 == popover.signatures.len() {
13125 popover.current_signature = 0;
13126 } else {
13127 popover.current_signature += 1;
13128 }
13129 cx.notify();
13130 }
13131 }
13132
13133 pub fn move_to_previous_word_start(
13134 &mut self,
13135 _: &MoveToPreviousWordStart,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) {
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140 self.change_selections(Default::default(), window, cx, |s| {
13141 s.move_cursors_with(|map, head, _| {
13142 (
13143 movement::previous_word_start(map, head),
13144 SelectionGoal::None,
13145 )
13146 });
13147 })
13148 }
13149
13150 pub fn move_to_previous_subword_start(
13151 &mut self,
13152 _: &MoveToPreviousSubwordStart,
13153 window: &mut Window,
13154 cx: &mut Context<Self>,
13155 ) {
13156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13157 self.change_selections(Default::default(), window, cx, |s| {
13158 s.move_cursors_with(|map, head, _| {
13159 (
13160 movement::previous_subword_start(map, head),
13161 SelectionGoal::None,
13162 )
13163 });
13164 })
13165 }
13166
13167 pub fn select_to_previous_word_start(
13168 &mut self,
13169 _: &SelectToPreviousWordStart,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) {
13173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13174 self.change_selections(Default::default(), window, cx, |s| {
13175 s.move_heads_with(|map, head, _| {
13176 (
13177 movement::previous_word_start(map, head),
13178 SelectionGoal::None,
13179 )
13180 });
13181 })
13182 }
13183
13184 pub fn select_to_previous_subword_start(
13185 &mut self,
13186 _: &SelectToPreviousSubwordStart,
13187 window: &mut Window,
13188 cx: &mut Context<Self>,
13189 ) {
13190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13191 self.change_selections(Default::default(), window, cx, |s| {
13192 s.move_heads_with(|map, head, _| {
13193 (
13194 movement::previous_subword_start(map, head),
13195 SelectionGoal::None,
13196 )
13197 });
13198 })
13199 }
13200
13201 pub fn delete_to_previous_word_start(
13202 &mut self,
13203 action: &DeleteToPreviousWordStart,
13204 window: &mut Window,
13205 cx: &mut Context<Self>,
13206 ) {
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13208 self.transact(window, cx, |this, window, cx| {
13209 this.select_autoclose_pair(window, cx);
13210 this.change_selections(Default::default(), window, cx, |s| {
13211 s.move_with(|map, selection| {
13212 if selection.is_empty() {
13213 let mut cursor = if action.ignore_newlines {
13214 movement::previous_word_start(map, selection.head())
13215 } else {
13216 movement::previous_word_start_or_newline(map, selection.head())
13217 };
13218 cursor = movement::adjust_greedy_deletion(
13219 map,
13220 selection.head(),
13221 cursor,
13222 action.ignore_brackets,
13223 );
13224 selection.set_head(cursor, SelectionGoal::None);
13225 }
13226 });
13227 });
13228 this.insert("", window, cx);
13229 });
13230 }
13231
13232 pub fn delete_to_previous_subword_start(
13233 &mut self,
13234 _: &DeleteToPreviousSubwordStart,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) {
13238 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13239 self.transact(window, cx, |this, window, cx| {
13240 this.select_autoclose_pair(window, cx);
13241 this.change_selections(Default::default(), window, cx, |s| {
13242 s.move_with(|map, selection| {
13243 if selection.is_empty() {
13244 let mut cursor = movement::previous_subword_start(map, selection.head());
13245 cursor =
13246 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13247 selection.set_head(cursor, SelectionGoal::None);
13248 }
13249 });
13250 });
13251 this.insert("", window, cx);
13252 });
13253 }
13254
13255 pub fn move_to_next_word_end(
13256 &mut self,
13257 _: &MoveToNextWordEnd,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13262 self.change_selections(Default::default(), window, cx, |s| {
13263 s.move_cursors_with(|map, head, _| {
13264 (movement::next_word_end(map, head), SelectionGoal::None)
13265 });
13266 })
13267 }
13268
13269 pub fn move_to_next_subword_end(
13270 &mut self,
13271 _: &MoveToNextSubwordEnd,
13272 window: &mut Window,
13273 cx: &mut Context<Self>,
13274 ) {
13275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13276 self.change_selections(Default::default(), window, cx, |s| {
13277 s.move_cursors_with(|map, head, _| {
13278 (movement::next_subword_end(map, head), SelectionGoal::None)
13279 });
13280 })
13281 }
13282
13283 pub fn select_to_next_word_end(
13284 &mut self,
13285 _: &SelectToNextWordEnd,
13286 window: &mut Window,
13287 cx: &mut Context<Self>,
13288 ) {
13289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13290 self.change_selections(Default::default(), window, cx, |s| {
13291 s.move_heads_with(|map, head, _| {
13292 (movement::next_word_end(map, head), SelectionGoal::None)
13293 });
13294 })
13295 }
13296
13297 pub fn select_to_next_subword_end(
13298 &mut self,
13299 _: &SelectToNextSubwordEnd,
13300 window: &mut Window,
13301 cx: &mut Context<Self>,
13302 ) {
13303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13304 self.change_selections(Default::default(), window, cx, |s| {
13305 s.move_heads_with(|map, head, _| {
13306 (movement::next_subword_end(map, head), SelectionGoal::None)
13307 });
13308 })
13309 }
13310
13311 pub fn delete_to_next_word_end(
13312 &mut self,
13313 action: &DeleteToNextWordEnd,
13314 window: &mut Window,
13315 cx: &mut Context<Self>,
13316 ) {
13317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13318 self.transact(window, cx, |this, window, cx| {
13319 this.change_selections(Default::default(), window, cx, |s| {
13320 s.move_with(|map, selection| {
13321 if selection.is_empty() {
13322 let mut cursor = if action.ignore_newlines {
13323 movement::next_word_end(map, selection.head())
13324 } else {
13325 movement::next_word_end_or_newline(map, selection.head())
13326 };
13327 cursor = movement::adjust_greedy_deletion(
13328 map,
13329 selection.head(),
13330 cursor,
13331 action.ignore_brackets,
13332 );
13333 selection.set_head(cursor, SelectionGoal::None);
13334 }
13335 });
13336 });
13337 this.insert("", window, cx);
13338 });
13339 }
13340
13341 pub fn delete_to_next_subword_end(
13342 &mut self,
13343 _: &DeleteToNextSubwordEnd,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13348 self.transact(window, cx, |this, window, cx| {
13349 this.change_selections(Default::default(), window, cx, |s| {
13350 s.move_with(|map, selection| {
13351 if selection.is_empty() {
13352 let mut cursor = movement::next_subword_end(map, selection.head());
13353 cursor =
13354 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13355 selection.set_head(cursor, SelectionGoal::None);
13356 }
13357 });
13358 });
13359 this.insert("", window, cx);
13360 });
13361 }
13362
13363 pub fn move_to_beginning_of_line(
13364 &mut self,
13365 action: &MoveToBeginningOfLine,
13366 window: &mut Window,
13367 cx: &mut Context<Self>,
13368 ) {
13369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13370 self.change_selections(Default::default(), window, cx, |s| {
13371 s.move_cursors_with(|map, head, _| {
13372 (
13373 movement::indented_line_beginning(
13374 map,
13375 head,
13376 action.stop_at_soft_wraps,
13377 action.stop_at_indent,
13378 ),
13379 SelectionGoal::None,
13380 )
13381 });
13382 })
13383 }
13384
13385 pub fn select_to_beginning_of_line(
13386 &mut self,
13387 action: &SelectToBeginningOfLine,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) {
13391 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13392 self.change_selections(Default::default(), window, cx, |s| {
13393 s.move_heads_with(|map, head, _| {
13394 (
13395 movement::indented_line_beginning(
13396 map,
13397 head,
13398 action.stop_at_soft_wraps,
13399 action.stop_at_indent,
13400 ),
13401 SelectionGoal::None,
13402 )
13403 });
13404 });
13405 }
13406
13407 pub fn delete_to_beginning_of_line(
13408 &mut self,
13409 action: &DeleteToBeginningOfLine,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13414 self.transact(window, cx, |this, window, cx| {
13415 this.change_selections(Default::default(), window, cx, |s| {
13416 s.move_with(|_, selection| {
13417 selection.reversed = true;
13418 });
13419 });
13420
13421 this.select_to_beginning_of_line(
13422 &SelectToBeginningOfLine {
13423 stop_at_soft_wraps: false,
13424 stop_at_indent: action.stop_at_indent,
13425 },
13426 window,
13427 cx,
13428 );
13429 this.backspace(&Backspace, window, cx);
13430 });
13431 }
13432
13433 pub fn move_to_end_of_line(
13434 &mut self,
13435 action: &MoveToEndOfLine,
13436 window: &mut Window,
13437 cx: &mut Context<Self>,
13438 ) {
13439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13440 self.change_selections(Default::default(), window, cx, |s| {
13441 s.move_cursors_with(|map, head, _| {
13442 (
13443 movement::line_end(map, head, action.stop_at_soft_wraps),
13444 SelectionGoal::None,
13445 )
13446 });
13447 })
13448 }
13449
13450 pub fn select_to_end_of_line(
13451 &mut self,
13452 action: &SelectToEndOfLine,
13453 window: &mut Window,
13454 cx: &mut Context<Self>,
13455 ) {
13456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13457 self.change_selections(Default::default(), window, cx, |s| {
13458 s.move_heads_with(|map, head, _| {
13459 (
13460 movement::line_end(map, head, action.stop_at_soft_wraps),
13461 SelectionGoal::None,
13462 )
13463 });
13464 })
13465 }
13466
13467 pub fn delete_to_end_of_line(
13468 &mut self,
13469 _: &DeleteToEndOfLine,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) {
13473 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13474 self.transact(window, cx, |this, window, cx| {
13475 this.select_to_end_of_line(
13476 &SelectToEndOfLine {
13477 stop_at_soft_wraps: false,
13478 },
13479 window,
13480 cx,
13481 );
13482 this.delete(&Delete, window, cx);
13483 });
13484 }
13485
13486 pub fn cut_to_end_of_line(
13487 &mut self,
13488 action: &CutToEndOfLine,
13489 window: &mut Window,
13490 cx: &mut Context<Self>,
13491 ) {
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13493 self.transact(window, cx, |this, window, cx| {
13494 this.select_to_end_of_line(
13495 &SelectToEndOfLine {
13496 stop_at_soft_wraps: false,
13497 },
13498 window,
13499 cx,
13500 );
13501 if !action.stop_at_newlines {
13502 this.change_selections(Default::default(), window, cx, |s| {
13503 s.move_with(|_, sel| {
13504 if sel.is_empty() {
13505 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13506 }
13507 });
13508 });
13509 }
13510 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13511 let item = this.cut_common(false, window, cx);
13512 cx.write_to_clipboard(item);
13513 });
13514 }
13515
13516 pub fn move_to_start_of_paragraph(
13517 &mut self,
13518 _: &MoveToStartOfParagraph,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 if matches!(self.mode, EditorMode::SingleLine) {
13523 cx.propagate();
13524 return;
13525 }
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_with(|map, selection| {
13529 selection.collapse_to(
13530 movement::start_of_paragraph(map, selection.head(), 1),
13531 SelectionGoal::None,
13532 )
13533 });
13534 })
13535 }
13536
13537 pub fn move_to_end_of_paragraph(
13538 &mut self,
13539 _: &MoveToEndOfParagraph,
13540 window: &mut Window,
13541 cx: &mut Context<Self>,
13542 ) {
13543 if matches!(self.mode, EditorMode::SingleLine) {
13544 cx.propagate();
13545 return;
13546 }
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.move_with(|map, selection| {
13550 selection.collapse_to(
13551 movement::end_of_paragraph(map, selection.head(), 1),
13552 SelectionGoal::None,
13553 )
13554 });
13555 })
13556 }
13557
13558 pub fn select_to_start_of_paragraph(
13559 &mut self,
13560 _: &SelectToStartOfParagraph,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 if matches!(self.mode, EditorMode::SingleLine) {
13565 cx.propagate();
13566 return;
13567 }
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_heads_with(|map, head, _| {
13571 (
13572 movement::start_of_paragraph(map, head, 1),
13573 SelectionGoal::None,
13574 )
13575 });
13576 })
13577 }
13578
13579 pub fn select_to_end_of_paragraph(
13580 &mut self,
13581 _: &SelectToEndOfParagraph,
13582 window: &mut Window,
13583 cx: &mut Context<Self>,
13584 ) {
13585 if matches!(self.mode, EditorMode::SingleLine) {
13586 cx.propagate();
13587 return;
13588 }
13589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13590 self.change_selections(Default::default(), window, cx, |s| {
13591 s.move_heads_with(|map, head, _| {
13592 (
13593 movement::end_of_paragraph(map, head, 1),
13594 SelectionGoal::None,
13595 )
13596 });
13597 })
13598 }
13599
13600 pub fn move_to_start_of_excerpt(
13601 &mut self,
13602 _: &MoveToStartOfExcerpt,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) {
13606 if matches!(self.mode, EditorMode::SingleLine) {
13607 cx.propagate();
13608 return;
13609 }
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13611 self.change_selections(Default::default(), window, cx, |s| {
13612 s.move_with(|map, selection| {
13613 selection.collapse_to(
13614 movement::start_of_excerpt(
13615 map,
13616 selection.head(),
13617 workspace::searchable::Direction::Prev,
13618 ),
13619 SelectionGoal::None,
13620 )
13621 });
13622 })
13623 }
13624
13625 pub fn move_to_start_of_next_excerpt(
13626 &mut self,
13627 _: &MoveToStartOfNextExcerpt,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 if matches!(self.mode, EditorMode::SingleLine) {
13632 cx.propagate();
13633 return;
13634 }
13635
13636 self.change_selections(Default::default(), window, cx, |s| {
13637 s.move_with(|map, selection| {
13638 selection.collapse_to(
13639 movement::start_of_excerpt(
13640 map,
13641 selection.head(),
13642 workspace::searchable::Direction::Next,
13643 ),
13644 SelectionGoal::None,
13645 )
13646 });
13647 })
13648 }
13649
13650 pub fn move_to_end_of_excerpt(
13651 &mut self,
13652 _: &MoveToEndOfExcerpt,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 if matches!(self.mode, EditorMode::SingleLine) {
13657 cx.propagate();
13658 return;
13659 }
13660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13661 self.change_selections(Default::default(), window, cx, |s| {
13662 s.move_with(|map, selection| {
13663 selection.collapse_to(
13664 movement::end_of_excerpt(
13665 map,
13666 selection.head(),
13667 workspace::searchable::Direction::Next,
13668 ),
13669 SelectionGoal::None,
13670 )
13671 });
13672 })
13673 }
13674
13675 pub fn move_to_end_of_previous_excerpt(
13676 &mut self,
13677 _: &MoveToEndOfPreviousExcerpt,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 if matches!(self.mode, EditorMode::SingleLine) {
13682 cx.propagate();
13683 return;
13684 }
13685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13686 self.change_selections(Default::default(), window, cx, |s| {
13687 s.move_with(|map, selection| {
13688 selection.collapse_to(
13689 movement::end_of_excerpt(
13690 map,
13691 selection.head(),
13692 workspace::searchable::Direction::Prev,
13693 ),
13694 SelectionGoal::None,
13695 )
13696 });
13697 })
13698 }
13699
13700 pub fn select_to_start_of_excerpt(
13701 &mut self,
13702 _: &SelectToStartOfExcerpt,
13703 window: &mut Window,
13704 cx: &mut Context<Self>,
13705 ) {
13706 if matches!(self.mode, EditorMode::SingleLine) {
13707 cx.propagate();
13708 return;
13709 }
13710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13711 self.change_selections(Default::default(), window, cx, |s| {
13712 s.move_heads_with(|map, head, _| {
13713 (
13714 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13715 SelectionGoal::None,
13716 )
13717 });
13718 })
13719 }
13720
13721 pub fn select_to_start_of_next_excerpt(
13722 &mut self,
13723 _: &SelectToStartOfNextExcerpt,
13724 window: &mut Window,
13725 cx: &mut Context<Self>,
13726 ) {
13727 if matches!(self.mode, EditorMode::SingleLine) {
13728 cx.propagate();
13729 return;
13730 }
13731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13732 self.change_selections(Default::default(), window, cx, |s| {
13733 s.move_heads_with(|map, head, _| {
13734 (
13735 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13736 SelectionGoal::None,
13737 )
13738 });
13739 })
13740 }
13741
13742 pub fn select_to_end_of_excerpt(
13743 &mut self,
13744 _: &SelectToEndOfExcerpt,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 if matches!(self.mode, EditorMode::SingleLine) {
13749 cx.propagate();
13750 return;
13751 }
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.move_heads_with(|map, head, _| {
13755 (
13756 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13757 SelectionGoal::None,
13758 )
13759 });
13760 })
13761 }
13762
13763 pub fn select_to_end_of_previous_excerpt(
13764 &mut self,
13765 _: &SelectToEndOfPreviousExcerpt,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 if matches!(self.mode, EditorMode::SingleLine) {
13770 cx.propagate();
13771 return;
13772 }
13773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_heads_with(|map, head, _| {
13776 (
13777 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13778 SelectionGoal::None,
13779 )
13780 });
13781 })
13782 }
13783
13784 pub fn move_to_beginning(
13785 &mut self,
13786 _: &MoveToBeginning,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) {
13790 if matches!(self.mode, EditorMode::SingleLine) {
13791 cx.propagate();
13792 return;
13793 }
13794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13795 self.change_selections(Default::default(), window, cx, |s| {
13796 s.select_ranges(vec![0..0]);
13797 });
13798 }
13799
13800 pub fn select_to_beginning(
13801 &mut self,
13802 _: &SelectToBeginning,
13803 window: &mut Window,
13804 cx: &mut Context<Self>,
13805 ) {
13806 let mut selection = self.selections.last::<Point>(cx);
13807 selection.set_head(Point::zero(), SelectionGoal::None);
13808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13809 self.change_selections(Default::default(), window, cx, |s| {
13810 s.select(vec![selection]);
13811 });
13812 }
13813
13814 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13815 if matches!(self.mode, EditorMode::SingleLine) {
13816 cx.propagate();
13817 return;
13818 }
13819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13820 let cursor = self.buffer.read(cx).read(cx).len();
13821 self.change_selections(Default::default(), window, cx, |s| {
13822 s.select_ranges(vec![cursor..cursor])
13823 });
13824 }
13825
13826 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13827 self.nav_history = nav_history;
13828 }
13829
13830 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13831 self.nav_history.as_ref()
13832 }
13833
13834 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13835 self.push_to_nav_history(
13836 self.selections.newest_anchor().head(),
13837 None,
13838 false,
13839 true,
13840 cx,
13841 );
13842 }
13843
13844 fn push_to_nav_history(
13845 &mut self,
13846 cursor_anchor: Anchor,
13847 new_position: Option<Point>,
13848 is_deactivate: bool,
13849 always: bool,
13850 cx: &mut Context<Self>,
13851 ) {
13852 if let Some(nav_history) = self.nav_history.as_mut() {
13853 let buffer = self.buffer.read(cx).read(cx);
13854 let cursor_position = cursor_anchor.to_point(&buffer);
13855 let scroll_state = self.scroll_manager.anchor();
13856 let scroll_top_row = scroll_state.top_row(&buffer);
13857 drop(buffer);
13858
13859 if let Some(new_position) = new_position {
13860 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13861 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13862 return;
13863 }
13864 }
13865
13866 nav_history.push(
13867 Some(NavigationData {
13868 cursor_anchor,
13869 cursor_position,
13870 scroll_anchor: scroll_state,
13871 scroll_top_row,
13872 }),
13873 cx,
13874 );
13875 cx.emit(EditorEvent::PushedToNavHistory {
13876 anchor: cursor_anchor,
13877 is_deactivate,
13878 })
13879 }
13880 }
13881
13882 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13884 let buffer = self.buffer.read(cx).snapshot(cx);
13885 let mut selection = self.selections.first::<usize>(cx);
13886 selection.set_head(buffer.len(), SelectionGoal::None);
13887 self.change_selections(Default::default(), window, cx, |s| {
13888 s.select(vec![selection]);
13889 });
13890 }
13891
13892 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 let end = self.buffer.read(cx).read(cx).len();
13895 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13896 s.select_ranges(vec![0..end]);
13897 });
13898 }
13899
13900 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13903 let mut selections = self.selections.all::<Point>(cx);
13904 let max_point = display_map.buffer_snapshot.max_point();
13905 for selection in &mut selections {
13906 let rows = selection.spanned_rows(true, &display_map);
13907 selection.start = Point::new(rows.start.0, 0);
13908 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13909 selection.reversed = false;
13910 }
13911 self.change_selections(Default::default(), window, cx, |s| {
13912 s.select(selections);
13913 });
13914 }
13915
13916 pub fn split_selection_into_lines(
13917 &mut self,
13918 action: &SplitSelectionIntoLines,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 let selections = self
13923 .selections
13924 .all::<Point>(cx)
13925 .into_iter()
13926 .map(|selection| selection.start..selection.end)
13927 .collect::<Vec<_>>();
13928 self.unfold_ranges(&selections, true, true, cx);
13929
13930 let mut new_selection_ranges = Vec::new();
13931 {
13932 let buffer = self.buffer.read(cx).read(cx);
13933 for selection in selections {
13934 for row in selection.start.row..selection.end.row {
13935 let line_start = Point::new(row, 0);
13936 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13937
13938 if action.keep_selections {
13939 // Keep the selection range for each line
13940 let selection_start = if row == selection.start.row {
13941 selection.start
13942 } else {
13943 line_start
13944 };
13945 new_selection_ranges.push(selection_start..line_end);
13946 } else {
13947 // Collapse to cursor at end of line
13948 new_selection_ranges.push(line_end..line_end);
13949 }
13950 }
13951
13952 let is_multiline_selection = selection.start.row != selection.end.row;
13953 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13954 // so this action feels more ergonomic when paired with other selection operations
13955 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13956 if !should_skip_last {
13957 if action.keep_selections {
13958 if is_multiline_selection {
13959 let line_start = Point::new(selection.end.row, 0);
13960 new_selection_ranges.push(line_start..selection.end);
13961 } else {
13962 new_selection_ranges.push(selection.start..selection.end);
13963 }
13964 } else {
13965 new_selection_ranges.push(selection.end..selection.end);
13966 }
13967 }
13968 }
13969 }
13970 self.change_selections(Default::default(), window, cx, |s| {
13971 s.select_ranges(new_selection_ranges);
13972 });
13973 }
13974
13975 pub fn add_selection_above(
13976 &mut self,
13977 _: &AddSelectionAbove,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 self.add_selection(true, window, cx);
13982 }
13983
13984 pub fn add_selection_below(
13985 &mut self,
13986 _: &AddSelectionBelow,
13987 window: &mut Window,
13988 cx: &mut Context<Self>,
13989 ) {
13990 self.add_selection(false, window, cx);
13991 }
13992
13993 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13995
13996 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13997 let all_selections = self.selections.all::<Point>(cx);
13998 let text_layout_details = self.text_layout_details(window);
13999
14000 let (mut columnar_selections, new_selections_to_columnarize) = {
14001 if let Some(state) = self.add_selections_state.as_ref() {
14002 let columnar_selection_ids: HashSet<_> = state
14003 .groups
14004 .iter()
14005 .flat_map(|group| group.stack.iter())
14006 .copied()
14007 .collect();
14008
14009 all_selections
14010 .into_iter()
14011 .partition(|s| columnar_selection_ids.contains(&s.id))
14012 } else {
14013 (Vec::new(), all_selections)
14014 }
14015 };
14016
14017 let mut state = self
14018 .add_selections_state
14019 .take()
14020 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14021
14022 for selection in new_selections_to_columnarize {
14023 let range = selection.display_range(&display_map).sorted();
14024 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14025 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14026 let positions = start_x.min(end_x)..start_x.max(end_x);
14027 let mut stack = Vec::new();
14028 for row in range.start.row().0..=range.end.row().0 {
14029 if let Some(selection) = self.selections.build_columnar_selection(
14030 &display_map,
14031 DisplayRow(row),
14032 &positions,
14033 selection.reversed,
14034 &text_layout_details,
14035 ) {
14036 stack.push(selection.id);
14037 columnar_selections.push(selection);
14038 }
14039 }
14040 if !stack.is_empty() {
14041 if above {
14042 stack.reverse();
14043 }
14044 state.groups.push(AddSelectionsGroup { above, stack });
14045 }
14046 }
14047
14048 let mut final_selections = Vec::new();
14049 let end_row = if above {
14050 DisplayRow(0)
14051 } else {
14052 display_map.max_point().row()
14053 };
14054
14055 let mut last_added_item_per_group = HashMap::default();
14056 for group in state.groups.iter_mut() {
14057 if let Some(last_id) = group.stack.last() {
14058 last_added_item_per_group.insert(*last_id, group);
14059 }
14060 }
14061
14062 for selection in columnar_selections {
14063 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14064 if above == group.above {
14065 let range = selection.display_range(&display_map).sorted();
14066 debug_assert_eq!(range.start.row(), range.end.row());
14067 let mut row = range.start.row();
14068 let positions =
14069 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14070 px(start)..px(end)
14071 } else {
14072 let start_x =
14073 display_map.x_for_display_point(range.start, &text_layout_details);
14074 let end_x =
14075 display_map.x_for_display_point(range.end, &text_layout_details);
14076 start_x.min(end_x)..start_x.max(end_x)
14077 };
14078
14079 let mut maybe_new_selection = None;
14080 while row != end_row {
14081 if above {
14082 row.0 -= 1;
14083 } else {
14084 row.0 += 1;
14085 }
14086 if let Some(new_selection) = self.selections.build_columnar_selection(
14087 &display_map,
14088 row,
14089 &positions,
14090 selection.reversed,
14091 &text_layout_details,
14092 ) {
14093 maybe_new_selection = Some(new_selection);
14094 break;
14095 }
14096 }
14097
14098 if let Some(new_selection) = maybe_new_selection {
14099 group.stack.push(new_selection.id);
14100 if above {
14101 final_selections.push(new_selection);
14102 final_selections.push(selection);
14103 } else {
14104 final_selections.push(selection);
14105 final_selections.push(new_selection);
14106 }
14107 } else {
14108 final_selections.push(selection);
14109 }
14110 } else {
14111 group.stack.pop();
14112 }
14113 } else {
14114 final_selections.push(selection);
14115 }
14116 }
14117
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.select(final_selections);
14120 });
14121
14122 let final_selection_ids: HashSet<_> = self
14123 .selections
14124 .all::<Point>(cx)
14125 .iter()
14126 .map(|s| s.id)
14127 .collect();
14128 state.groups.retain_mut(|group| {
14129 // selections might get merged above so we remove invalid items from stacks
14130 group.stack.retain(|id| final_selection_ids.contains(id));
14131
14132 // single selection in stack can be treated as initial state
14133 group.stack.len() > 1
14134 });
14135
14136 if !state.groups.is_empty() {
14137 self.add_selections_state = Some(state);
14138 }
14139 }
14140
14141 fn select_match_ranges(
14142 &mut self,
14143 range: Range<usize>,
14144 reversed: bool,
14145 replace_newest: bool,
14146 auto_scroll: Option<Autoscroll>,
14147 window: &mut Window,
14148 cx: &mut Context<Editor>,
14149 ) {
14150 self.unfold_ranges(
14151 std::slice::from_ref(&range),
14152 false,
14153 auto_scroll.is_some(),
14154 cx,
14155 );
14156 let effects = if let Some(scroll) = auto_scroll {
14157 SelectionEffects::scroll(scroll)
14158 } else {
14159 SelectionEffects::no_scroll()
14160 };
14161 self.change_selections(effects, window, cx, |s| {
14162 if replace_newest {
14163 s.delete(s.newest_anchor().id);
14164 }
14165 if reversed {
14166 s.insert_range(range.end..range.start);
14167 } else {
14168 s.insert_range(range);
14169 }
14170 });
14171 }
14172
14173 pub fn select_next_match_internal(
14174 &mut self,
14175 display_map: &DisplaySnapshot,
14176 replace_newest: bool,
14177 autoscroll: Option<Autoscroll>,
14178 window: &mut Window,
14179 cx: &mut Context<Self>,
14180 ) -> Result<()> {
14181 let buffer = &display_map.buffer_snapshot;
14182 let mut selections = self.selections.all::<usize>(cx);
14183 if let Some(mut select_next_state) = self.select_next_state.take() {
14184 let query = &select_next_state.query;
14185 if !select_next_state.done {
14186 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14187 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14188 let mut next_selected_range = None;
14189
14190 let bytes_after_last_selection =
14191 buffer.bytes_in_range(last_selection.end..buffer.len());
14192 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14193 let query_matches = query
14194 .stream_find_iter(bytes_after_last_selection)
14195 .map(|result| (last_selection.end, result))
14196 .chain(
14197 query
14198 .stream_find_iter(bytes_before_first_selection)
14199 .map(|result| (0, result)),
14200 );
14201
14202 for (start_offset, query_match) in query_matches {
14203 let query_match = query_match.unwrap(); // can only fail due to I/O
14204 let offset_range =
14205 start_offset + query_match.start()..start_offset + query_match.end();
14206
14207 if !select_next_state.wordwise
14208 || (!buffer.is_inside_word(offset_range.start, false)
14209 && !buffer.is_inside_word(offset_range.end, false))
14210 {
14211 // TODO: This is n^2, because we might check all the selections
14212 if !selections
14213 .iter()
14214 .any(|selection| selection.range().overlaps(&offset_range))
14215 {
14216 next_selected_range = Some(offset_range);
14217 break;
14218 }
14219 }
14220 }
14221
14222 if let Some(next_selected_range) = next_selected_range {
14223 self.select_match_ranges(
14224 next_selected_range,
14225 last_selection.reversed,
14226 replace_newest,
14227 autoscroll,
14228 window,
14229 cx,
14230 );
14231 } else {
14232 select_next_state.done = true;
14233 }
14234 }
14235
14236 self.select_next_state = Some(select_next_state);
14237 } else {
14238 let mut only_carets = true;
14239 let mut same_text_selected = true;
14240 let mut selected_text = None;
14241
14242 let mut selections_iter = selections.iter().peekable();
14243 while let Some(selection) = selections_iter.next() {
14244 if selection.start != selection.end {
14245 only_carets = false;
14246 }
14247
14248 if same_text_selected {
14249 if selected_text.is_none() {
14250 selected_text =
14251 Some(buffer.text_for_range(selection.range()).collect::<String>());
14252 }
14253
14254 if let Some(next_selection) = selections_iter.peek() {
14255 if next_selection.range().len() == selection.range().len() {
14256 let next_selected_text = buffer
14257 .text_for_range(next_selection.range())
14258 .collect::<String>();
14259 if Some(next_selected_text) != selected_text {
14260 same_text_selected = false;
14261 selected_text = None;
14262 }
14263 } else {
14264 same_text_selected = false;
14265 selected_text = None;
14266 }
14267 }
14268 }
14269 }
14270
14271 if only_carets {
14272 for selection in &mut selections {
14273 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14274 selection.start = word_range.start;
14275 selection.end = word_range.end;
14276 selection.goal = SelectionGoal::None;
14277 selection.reversed = false;
14278 self.select_match_ranges(
14279 selection.start..selection.end,
14280 selection.reversed,
14281 replace_newest,
14282 autoscroll,
14283 window,
14284 cx,
14285 );
14286 }
14287
14288 if selections.len() == 1 {
14289 let selection = selections
14290 .last()
14291 .expect("ensured that there's only one selection");
14292 let query = buffer
14293 .text_for_range(selection.start..selection.end)
14294 .collect::<String>();
14295 let is_empty = query.is_empty();
14296 let select_state = SelectNextState {
14297 query: AhoCorasick::new(&[query])?,
14298 wordwise: true,
14299 done: is_empty,
14300 };
14301 self.select_next_state = Some(select_state);
14302 } else {
14303 self.select_next_state = None;
14304 }
14305 } else if let Some(selected_text) = selected_text {
14306 self.select_next_state = Some(SelectNextState {
14307 query: AhoCorasick::new(&[selected_text])?,
14308 wordwise: false,
14309 done: false,
14310 });
14311 self.select_next_match_internal(
14312 display_map,
14313 replace_newest,
14314 autoscroll,
14315 window,
14316 cx,
14317 )?;
14318 }
14319 }
14320 Ok(())
14321 }
14322
14323 pub fn select_all_matches(
14324 &mut self,
14325 _action: &SelectAllMatches,
14326 window: &mut Window,
14327 cx: &mut Context<Self>,
14328 ) -> Result<()> {
14329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14330
14331 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14332
14333 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14334 let Some(select_next_state) = self.select_next_state.as_mut() else {
14335 return Ok(());
14336 };
14337 if select_next_state.done {
14338 return Ok(());
14339 }
14340
14341 let mut new_selections = Vec::new();
14342
14343 let reversed = self.selections.oldest::<usize>(cx).reversed;
14344 let buffer = &display_map.buffer_snapshot;
14345 let query_matches = select_next_state
14346 .query
14347 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14348
14349 for query_match in query_matches.into_iter() {
14350 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14351 let offset_range = if reversed {
14352 query_match.end()..query_match.start()
14353 } else {
14354 query_match.start()..query_match.end()
14355 };
14356
14357 if !select_next_state.wordwise
14358 || (!buffer.is_inside_word(offset_range.start, false)
14359 && !buffer.is_inside_word(offset_range.end, false))
14360 {
14361 new_selections.push(offset_range.start..offset_range.end);
14362 }
14363 }
14364
14365 select_next_state.done = true;
14366
14367 if new_selections.is_empty() {
14368 log::error!("bug: new_selections is empty in select_all_matches");
14369 return Ok(());
14370 }
14371
14372 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14373 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14374 selections.select_ranges(new_selections)
14375 });
14376
14377 Ok(())
14378 }
14379
14380 pub fn select_next(
14381 &mut self,
14382 action: &SelectNext,
14383 window: &mut Window,
14384 cx: &mut Context<Self>,
14385 ) -> Result<()> {
14386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14387 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14388 self.select_next_match_internal(
14389 &display_map,
14390 action.replace_newest,
14391 Some(Autoscroll::newest()),
14392 window,
14393 cx,
14394 )?;
14395 Ok(())
14396 }
14397
14398 pub fn select_previous(
14399 &mut self,
14400 action: &SelectPrevious,
14401 window: &mut Window,
14402 cx: &mut Context<Self>,
14403 ) -> Result<()> {
14404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14406 let buffer = &display_map.buffer_snapshot;
14407 let mut selections = self.selections.all::<usize>(cx);
14408 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14409 let query = &select_prev_state.query;
14410 if !select_prev_state.done {
14411 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14412 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14413 let mut next_selected_range = None;
14414 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14415 let bytes_before_last_selection =
14416 buffer.reversed_bytes_in_range(0..last_selection.start);
14417 let bytes_after_first_selection =
14418 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14419 let query_matches = query
14420 .stream_find_iter(bytes_before_last_selection)
14421 .map(|result| (last_selection.start, result))
14422 .chain(
14423 query
14424 .stream_find_iter(bytes_after_first_selection)
14425 .map(|result| (buffer.len(), result)),
14426 );
14427 for (end_offset, query_match) in query_matches {
14428 let query_match = query_match.unwrap(); // can only fail due to I/O
14429 let offset_range =
14430 end_offset - query_match.end()..end_offset - query_match.start();
14431
14432 if !select_prev_state.wordwise
14433 || (!buffer.is_inside_word(offset_range.start, false)
14434 && !buffer.is_inside_word(offset_range.end, false))
14435 {
14436 next_selected_range = Some(offset_range);
14437 break;
14438 }
14439 }
14440
14441 if let Some(next_selected_range) = next_selected_range {
14442 self.select_match_ranges(
14443 next_selected_range,
14444 last_selection.reversed,
14445 action.replace_newest,
14446 Some(Autoscroll::newest()),
14447 window,
14448 cx,
14449 );
14450 } else {
14451 select_prev_state.done = true;
14452 }
14453 }
14454
14455 self.select_prev_state = Some(select_prev_state);
14456 } else {
14457 let mut only_carets = true;
14458 let mut same_text_selected = true;
14459 let mut selected_text = None;
14460
14461 let mut selections_iter = selections.iter().peekable();
14462 while let Some(selection) = selections_iter.next() {
14463 if selection.start != selection.end {
14464 only_carets = false;
14465 }
14466
14467 if same_text_selected {
14468 if selected_text.is_none() {
14469 selected_text =
14470 Some(buffer.text_for_range(selection.range()).collect::<String>());
14471 }
14472
14473 if let Some(next_selection) = selections_iter.peek() {
14474 if next_selection.range().len() == selection.range().len() {
14475 let next_selected_text = buffer
14476 .text_for_range(next_selection.range())
14477 .collect::<String>();
14478 if Some(next_selected_text) != selected_text {
14479 same_text_selected = false;
14480 selected_text = None;
14481 }
14482 } else {
14483 same_text_selected = false;
14484 selected_text = None;
14485 }
14486 }
14487 }
14488 }
14489
14490 if only_carets {
14491 for selection in &mut selections {
14492 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14493 selection.start = word_range.start;
14494 selection.end = word_range.end;
14495 selection.goal = SelectionGoal::None;
14496 selection.reversed = false;
14497 self.select_match_ranges(
14498 selection.start..selection.end,
14499 selection.reversed,
14500 action.replace_newest,
14501 Some(Autoscroll::newest()),
14502 window,
14503 cx,
14504 );
14505 }
14506 if selections.len() == 1 {
14507 let selection = selections
14508 .last()
14509 .expect("ensured that there's only one selection");
14510 let query = buffer
14511 .text_for_range(selection.start..selection.end)
14512 .collect::<String>();
14513 let is_empty = query.is_empty();
14514 let select_state = SelectNextState {
14515 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14516 wordwise: true,
14517 done: is_empty,
14518 };
14519 self.select_prev_state = Some(select_state);
14520 } else {
14521 self.select_prev_state = None;
14522 }
14523 } else if let Some(selected_text) = selected_text {
14524 self.select_prev_state = Some(SelectNextState {
14525 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14526 wordwise: false,
14527 done: false,
14528 });
14529 self.select_previous(action, window, cx)?;
14530 }
14531 }
14532 Ok(())
14533 }
14534
14535 pub fn find_next_match(
14536 &mut self,
14537 _: &FindNextMatch,
14538 window: &mut Window,
14539 cx: &mut Context<Self>,
14540 ) -> Result<()> {
14541 let selections = self.selections.disjoint_anchors();
14542 match selections.first() {
14543 Some(first) if selections.len() >= 2 => {
14544 self.change_selections(Default::default(), window, cx, |s| {
14545 s.select_ranges([first.range()]);
14546 });
14547 }
14548 _ => self.select_next(
14549 &SelectNext {
14550 replace_newest: true,
14551 },
14552 window,
14553 cx,
14554 )?,
14555 }
14556 Ok(())
14557 }
14558
14559 pub fn find_previous_match(
14560 &mut self,
14561 _: &FindPreviousMatch,
14562 window: &mut Window,
14563 cx: &mut Context<Self>,
14564 ) -> Result<()> {
14565 let selections = self.selections.disjoint_anchors();
14566 match selections.last() {
14567 Some(last) if selections.len() >= 2 => {
14568 self.change_selections(Default::default(), window, cx, |s| {
14569 s.select_ranges([last.range()]);
14570 });
14571 }
14572 _ => self.select_previous(
14573 &SelectPrevious {
14574 replace_newest: true,
14575 },
14576 window,
14577 cx,
14578 )?,
14579 }
14580 Ok(())
14581 }
14582
14583 pub fn toggle_comments(
14584 &mut self,
14585 action: &ToggleComments,
14586 window: &mut Window,
14587 cx: &mut Context<Self>,
14588 ) {
14589 if self.read_only(cx) {
14590 return;
14591 }
14592 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14593 let text_layout_details = &self.text_layout_details(window);
14594 self.transact(window, cx, |this, window, cx| {
14595 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14596 let mut edits = Vec::new();
14597 let mut selection_edit_ranges = Vec::new();
14598 let mut last_toggled_row = None;
14599 let snapshot = this.buffer.read(cx).read(cx);
14600 let empty_str: Arc<str> = Arc::default();
14601 let mut suffixes_inserted = Vec::new();
14602 let ignore_indent = action.ignore_indent;
14603
14604 fn comment_prefix_range(
14605 snapshot: &MultiBufferSnapshot,
14606 row: MultiBufferRow,
14607 comment_prefix: &str,
14608 comment_prefix_whitespace: &str,
14609 ignore_indent: bool,
14610 ) -> Range<Point> {
14611 let indent_size = if ignore_indent {
14612 0
14613 } else {
14614 snapshot.indent_size_for_line(row).len
14615 };
14616
14617 let start = Point::new(row.0, indent_size);
14618
14619 let mut line_bytes = snapshot
14620 .bytes_in_range(start..snapshot.max_point())
14621 .flatten()
14622 .copied();
14623
14624 // If this line currently begins with the line comment prefix, then record
14625 // the range containing the prefix.
14626 if line_bytes
14627 .by_ref()
14628 .take(comment_prefix.len())
14629 .eq(comment_prefix.bytes())
14630 {
14631 // Include any whitespace that matches the comment prefix.
14632 let matching_whitespace_len = line_bytes
14633 .zip(comment_prefix_whitespace.bytes())
14634 .take_while(|(a, b)| a == b)
14635 .count() as u32;
14636 let end = Point::new(
14637 start.row,
14638 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14639 );
14640 start..end
14641 } else {
14642 start..start
14643 }
14644 }
14645
14646 fn comment_suffix_range(
14647 snapshot: &MultiBufferSnapshot,
14648 row: MultiBufferRow,
14649 comment_suffix: &str,
14650 comment_suffix_has_leading_space: bool,
14651 ) -> Range<Point> {
14652 let end = Point::new(row.0, snapshot.line_len(row));
14653 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14654
14655 let mut line_end_bytes = snapshot
14656 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14657 .flatten()
14658 .copied();
14659
14660 let leading_space_len = if suffix_start_column > 0
14661 && line_end_bytes.next() == Some(b' ')
14662 && comment_suffix_has_leading_space
14663 {
14664 1
14665 } else {
14666 0
14667 };
14668
14669 // If this line currently begins with the line comment prefix, then record
14670 // the range containing the prefix.
14671 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14672 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14673 start..end
14674 } else {
14675 end..end
14676 }
14677 }
14678
14679 // TODO: Handle selections that cross excerpts
14680 for selection in &mut selections {
14681 let start_column = snapshot
14682 .indent_size_for_line(MultiBufferRow(selection.start.row))
14683 .len;
14684 let language = if let Some(language) =
14685 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14686 {
14687 language
14688 } else {
14689 continue;
14690 };
14691
14692 selection_edit_ranges.clear();
14693
14694 // If multiple selections contain a given row, avoid processing that
14695 // row more than once.
14696 let mut start_row = MultiBufferRow(selection.start.row);
14697 if last_toggled_row == Some(start_row) {
14698 start_row = start_row.next_row();
14699 }
14700 let end_row =
14701 if selection.end.row > selection.start.row && selection.end.column == 0 {
14702 MultiBufferRow(selection.end.row - 1)
14703 } else {
14704 MultiBufferRow(selection.end.row)
14705 };
14706 last_toggled_row = Some(end_row);
14707
14708 if start_row > end_row {
14709 continue;
14710 }
14711
14712 // If the language has line comments, toggle those.
14713 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14714
14715 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14716 if ignore_indent {
14717 full_comment_prefixes = full_comment_prefixes
14718 .into_iter()
14719 .map(|s| Arc::from(s.trim_end()))
14720 .collect();
14721 }
14722
14723 if !full_comment_prefixes.is_empty() {
14724 let first_prefix = full_comment_prefixes
14725 .first()
14726 .expect("prefixes is non-empty");
14727 let prefix_trimmed_lengths = full_comment_prefixes
14728 .iter()
14729 .map(|p| p.trim_end_matches(' ').len())
14730 .collect::<SmallVec<[usize; 4]>>();
14731
14732 let mut all_selection_lines_are_comments = true;
14733
14734 for row in start_row.0..=end_row.0 {
14735 let row = MultiBufferRow(row);
14736 if start_row < end_row && snapshot.is_line_blank(row) {
14737 continue;
14738 }
14739
14740 let prefix_range = full_comment_prefixes
14741 .iter()
14742 .zip(prefix_trimmed_lengths.iter().copied())
14743 .map(|(prefix, trimmed_prefix_len)| {
14744 comment_prefix_range(
14745 snapshot.deref(),
14746 row,
14747 &prefix[..trimmed_prefix_len],
14748 &prefix[trimmed_prefix_len..],
14749 ignore_indent,
14750 )
14751 })
14752 .max_by_key(|range| range.end.column - range.start.column)
14753 .expect("prefixes is non-empty");
14754
14755 if prefix_range.is_empty() {
14756 all_selection_lines_are_comments = false;
14757 }
14758
14759 selection_edit_ranges.push(prefix_range);
14760 }
14761
14762 if all_selection_lines_are_comments {
14763 edits.extend(
14764 selection_edit_ranges
14765 .iter()
14766 .cloned()
14767 .map(|range| (range, empty_str.clone())),
14768 );
14769 } else {
14770 let min_column = selection_edit_ranges
14771 .iter()
14772 .map(|range| range.start.column)
14773 .min()
14774 .unwrap_or(0);
14775 edits.extend(selection_edit_ranges.iter().map(|range| {
14776 let position = Point::new(range.start.row, min_column);
14777 (position..position, first_prefix.clone())
14778 }));
14779 }
14780 } else if let Some(BlockCommentConfig {
14781 start: full_comment_prefix,
14782 end: comment_suffix,
14783 ..
14784 }) = language.block_comment()
14785 {
14786 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14787 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14788 let prefix_range = comment_prefix_range(
14789 snapshot.deref(),
14790 start_row,
14791 comment_prefix,
14792 comment_prefix_whitespace,
14793 ignore_indent,
14794 );
14795 let suffix_range = comment_suffix_range(
14796 snapshot.deref(),
14797 end_row,
14798 comment_suffix.trim_start_matches(' '),
14799 comment_suffix.starts_with(' '),
14800 );
14801
14802 if prefix_range.is_empty() || suffix_range.is_empty() {
14803 edits.push((
14804 prefix_range.start..prefix_range.start,
14805 full_comment_prefix.clone(),
14806 ));
14807 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14808 suffixes_inserted.push((end_row, comment_suffix.len()));
14809 } else {
14810 edits.push((prefix_range, empty_str.clone()));
14811 edits.push((suffix_range, empty_str.clone()));
14812 }
14813 } else {
14814 continue;
14815 }
14816 }
14817
14818 drop(snapshot);
14819 this.buffer.update(cx, |buffer, cx| {
14820 buffer.edit(edits, None, cx);
14821 });
14822
14823 // Adjust selections so that they end before any comment suffixes that
14824 // were inserted.
14825 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14826 let mut selections = this.selections.all::<Point>(cx);
14827 let snapshot = this.buffer.read(cx).read(cx);
14828 for selection in &mut selections {
14829 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14830 match row.cmp(&MultiBufferRow(selection.end.row)) {
14831 Ordering::Less => {
14832 suffixes_inserted.next();
14833 continue;
14834 }
14835 Ordering::Greater => break,
14836 Ordering::Equal => {
14837 if selection.end.column == snapshot.line_len(row) {
14838 if selection.is_empty() {
14839 selection.start.column -= suffix_len as u32;
14840 }
14841 selection.end.column -= suffix_len as u32;
14842 }
14843 break;
14844 }
14845 }
14846 }
14847 }
14848
14849 drop(snapshot);
14850 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14851
14852 let selections = this.selections.all::<Point>(cx);
14853 let selections_on_single_row = selections.windows(2).all(|selections| {
14854 selections[0].start.row == selections[1].start.row
14855 && selections[0].end.row == selections[1].end.row
14856 && selections[0].start.row == selections[0].end.row
14857 });
14858 let selections_selecting = selections
14859 .iter()
14860 .any(|selection| selection.start != selection.end);
14861 let advance_downwards = action.advance_downwards
14862 && selections_on_single_row
14863 && !selections_selecting
14864 && !matches!(this.mode, EditorMode::SingleLine);
14865
14866 if advance_downwards {
14867 let snapshot = this.buffer.read(cx).snapshot(cx);
14868
14869 this.change_selections(Default::default(), window, cx, |s| {
14870 s.move_cursors_with(|display_snapshot, display_point, _| {
14871 let mut point = display_point.to_point(display_snapshot);
14872 point.row += 1;
14873 point = snapshot.clip_point(point, Bias::Left);
14874 let display_point = point.to_display_point(display_snapshot);
14875 let goal = SelectionGoal::HorizontalPosition(
14876 display_snapshot
14877 .x_for_display_point(display_point, text_layout_details)
14878 .into(),
14879 );
14880 (display_point, goal)
14881 })
14882 });
14883 }
14884 });
14885 }
14886
14887 pub fn select_enclosing_symbol(
14888 &mut self,
14889 _: &SelectEnclosingSymbol,
14890 window: &mut Window,
14891 cx: &mut Context<Self>,
14892 ) {
14893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14894
14895 let buffer = self.buffer.read(cx).snapshot(cx);
14896 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14897
14898 fn update_selection(
14899 selection: &Selection<usize>,
14900 buffer_snap: &MultiBufferSnapshot,
14901 ) -> Option<Selection<usize>> {
14902 let cursor = selection.head();
14903 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14904 for symbol in symbols.iter().rev() {
14905 let start = symbol.range.start.to_offset(buffer_snap);
14906 let end = symbol.range.end.to_offset(buffer_snap);
14907 let new_range = start..end;
14908 if start < selection.start || end > selection.end {
14909 return Some(Selection {
14910 id: selection.id,
14911 start: new_range.start,
14912 end: new_range.end,
14913 goal: SelectionGoal::None,
14914 reversed: selection.reversed,
14915 });
14916 }
14917 }
14918 None
14919 }
14920
14921 let mut selected_larger_symbol = false;
14922 let new_selections = old_selections
14923 .iter()
14924 .map(|selection| match update_selection(selection, &buffer) {
14925 Some(new_selection) => {
14926 if new_selection.range() != selection.range() {
14927 selected_larger_symbol = true;
14928 }
14929 new_selection
14930 }
14931 None => selection.clone(),
14932 })
14933 .collect::<Vec<_>>();
14934
14935 if selected_larger_symbol {
14936 self.change_selections(Default::default(), window, cx, |s| {
14937 s.select(new_selections);
14938 });
14939 }
14940 }
14941
14942 pub fn select_larger_syntax_node(
14943 &mut self,
14944 _: &SelectLargerSyntaxNode,
14945 window: &mut Window,
14946 cx: &mut Context<Self>,
14947 ) {
14948 let Some(visible_row_count) = self.visible_row_count() else {
14949 return;
14950 };
14951 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14952 if old_selections.is_empty() {
14953 return;
14954 }
14955
14956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14957
14958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14959 let buffer = self.buffer.read(cx).snapshot(cx);
14960
14961 let mut selected_larger_node = false;
14962 let mut new_selections = old_selections
14963 .iter()
14964 .map(|selection| {
14965 let old_range = selection.start..selection.end;
14966
14967 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14968 // manually select word at selection
14969 if ["string_content", "inline"].contains(&node.kind()) {
14970 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14971 // ignore if word is already selected
14972 if !word_range.is_empty() && old_range != word_range {
14973 let (last_word_range, _) =
14974 buffer.surrounding_word(old_range.end, false);
14975 // only select word if start and end point belongs to same word
14976 if word_range == last_word_range {
14977 selected_larger_node = true;
14978 return Selection {
14979 id: selection.id,
14980 start: word_range.start,
14981 end: word_range.end,
14982 goal: SelectionGoal::None,
14983 reversed: selection.reversed,
14984 };
14985 }
14986 }
14987 }
14988 }
14989
14990 let mut new_range = old_range.clone();
14991 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
14992 {
14993 if !node.is_named() {
14994 new_range = node.start_byte()..node.end_byte();
14995 continue;
14996 }
14997
14998 new_range = match containing_range {
14999 MultiOrSingleBufferOffsetRange::Single(_) => break,
15000 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15001 };
15002 if !display_map.intersects_fold(new_range.start)
15003 && !display_map.intersects_fold(new_range.end)
15004 {
15005 break;
15006 }
15007 }
15008
15009 selected_larger_node |= new_range != old_range;
15010 Selection {
15011 id: selection.id,
15012 start: new_range.start,
15013 end: new_range.end,
15014 goal: SelectionGoal::None,
15015 reversed: selection.reversed,
15016 }
15017 })
15018 .collect::<Vec<_>>();
15019
15020 if !selected_larger_node {
15021 return; // don't put this call in the history
15022 }
15023
15024 // scroll based on transformation done to the last selection created by the user
15025 let (last_old, last_new) = old_selections
15026 .last()
15027 .zip(new_selections.last().cloned())
15028 .expect("old_selections isn't empty");
15029
15030 // revert selection
15031 let is_selection_reversed = {
15032 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15033 new_selections.last_mut().expect("checked above").reversed =
15034 should_newest_selection_be_reversed;
15035 should_newest_selection_be_reversed
15036 };
15037
15038 if selected_larger_node {
15039 self.select_syntax_node_history.disable_clearing = true;
15040 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15041 s.select(new_selections.clone());
15042 });
15043 self.select_syntax_node_history.disable_clearing = false;
15044 }
15045
15046 let start_row = last_new.start.to_display_point(&display_map).row().0;
15047 let end_row = last_new.end.to_display_point(&display_map).row().0;
15048 let selection_height = end_row - start_row + 1;
15049 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15050
15051 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15052 let scroll_behavior = if fits_on_the_screen {
15053 self.request_autoscroll(Autoscroll::fit(), cx);
15054 SelectSyntaxNodeScrollBehavior::FitSelection
15055 } else if is_selection_reversed {
15056 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15057 SelectSyntaxNodeScrollBehavior::CursorTop
15058 } else {
15059 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15060 SelectSyntaxNodeScrollBehavior::CursorBottom
15061 };
15062
15063 self.select_syntax_node_history.push((
15064 old_selections,
15065 scroll_behavior,
15066 is_selection_reversed,
15067 ));
15068 }
15069
15070 pub fn select_smaller_syntax_node(
15071 &mut self,
15072 _: &SelectSmallerSyntaxNode,
15073 window: &mut Window,
15074 cx: &mut Context<Self>,
15075 ) {
15076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15077
15078 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15079 self.select_syntax_node_history.pop()
15080 {
15081 if let Some(selection) = selections.last_mut() {
15082 selection.reversed = is_selection_reversed;
15083 }
15084
15085 self.select_syntax_node_history.disable_clearing = true;
15086 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15087 s.select(selections.to_vec());
15088 });
15089 self.select_syntax_node_history.disable_clearing = false;
15090
15091 match scroll_behavior {
15092 SelectSyntaxNodeScrollBehavior::CursorTop => {
15093 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15094 }
15095 SelectSyntaxNodeScrollBehavior::FitSelection => {
15096 self.request_autoscroll(Autoscroll::fit(), cx);
15097 }
15098 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15099 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15100 }
15101 }
15102 }
15103 }
15104
15105 pub fn unwrap_syntax_node(
15106 &mut self,
15107 _: &UnwrapSyntaxNode,
15108 window: &mut Window,
15109 cx: &mut Context<Self>,
15110 ) {
15111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15112
15113 let buffer = self.buffer.read(cx).snapshot(cx);
15114 let selections = self
15115 .selections
15116 .all::<usize>(cx)
15117 .into_iter()
15118 // subtracting the offset requires sorting
15119 .sorted_by_key(|i| i.start);
15120
15121 let full_edits = selections
15122 .into_iter()
15123 .filter_map(|selection| {
15124 // Only requires two branches once if-let-chains stabilize (#53667)
15125 let child = if !selection.is_empty() {
15126 selection.range()
15127 } else if let Some((_, ancestor_range)) =
15128 buffer.syntax_ancestor(selection.start..selection.end)
15129 {
15130 match ancestor_range {
15131 MultiOrSingleBufferOffsetRange::Single(range) => range,
15132 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15133 }
15134 } else {
15135 selection.range()
15136 };
15137
15138 let mut parent = child.clone();
15139 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15140 parent = match ancestor_range {
15141 MultiOrSingleBufferOffsetRange::Single(range) => range,
15142 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15143 };
15144 if parent.start < child.start || parent.end > child.end {
15145 break;
15146 }
15147 }
15148
15149 if parent == child {
15150 return None;
15151 }
15152 let text = buffer.text_for_range(child).collect::<String>();
15153 Some((selection.id, parent, text))
15154 })
15155 .collect::<Vec<_>>();
15156
15157 self.transact(window, cx, |this, window, cx| {
15158 this.buffer.update(cx, |buffer, cx| {
15159 buffer.edit(
15160 full_edits
15161 .iter()
15162 .map(|(_, p, t)| (p.clone(), t.clone()))
15163 .collect::<Vec<_>>(),
15164 None,
15165 cx,
15166 );
15167 });
15168 this.change_selections(Default::default(), window, cx, |s| {
15169 let mut offset = 0;
15170 let mut selections = vec![];
15171 for (id, parent, text) in full_edits {
15172 let start = parent.start - offset;
15173 offset += parent.len() - text.len();
15174 selections.push(Selection {
15175 id,
15176 start,
15177 end: start + text.len(),
15178 reversed: false,
15179 goal: Default::default(),
15180 });
15181 }
15182 s.select(selections);
15183 });
15184 });
15185 }
15186
15187 pub fn select_next_syntax_node(
15188 &mut self,
15189 _: &SelectNextSyntaxNode,
15190 window: &mut Window,
15191 cx: &mut Context<Self>,
15192 ) {
15193 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15194 if old_selections.is_empty() {
15195 return;
15196 }
15197
15198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15199
15200 let buffer = self.buffer.read(cx).snapshot(cx);
15201 let mut selected_sibling = false;
15202
15203 let new_selections = old_selections
15204 .iter()
15205 .map(|selection| {
15206 let old_range = selection.start..selection.end;
15207
15208 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15209 let new_range = node.byte_range();
15210 selected_sibling = true;
15211 Selection {
15212 id: selection.id,
15213 start: new_range.start,
15214 end: new_range.end,
15215 goal: SelectionGoal::None,
15216 reversed: selection.reversed,
15217 }
15218 } else {
15219 selection.clone()
15220 }
15221 })
15222 .collect::<Vec<_>>();
15223
15224 if selected_sibling {
15225 self.change_selections(
15226 SelectionEffects::scroll(Autoscroll::fit()),
15227 window,
15228 cx,
15229 |s| {
15230 s.select(new_selections);
15231 },
15232 );
15233 }
15234 }
15235
15236 pub fn select_prev_syntax_node(
15237 &mut self,
15238 _: &SelectPreviousSyntaxNode,
15239 window: &mut Window,
15240 cx: &mut Context<Self>,
15241 ) {
15242 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15243 if old_selections.is_empty() {
15244 return;
15245 }
15246
15247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15248
15249 let buffer = self.buffer.read(cx).snapshot(cx);
15250 let mut selected_sibling = false;
15251
15252 let new_selections = old_selections
15253 .iter()
15254 .map(|selection| {
15255 let old_range = selection.start..selection.end;
15256
15257 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15258 let new_range = node.byte_range();
15259 selected_sibling = true;
15260 Selection {
15261 id: selection.id,
15262 start: new_range.start,
15263 end: new_range.end,
15264 goal: SelectionGoal::None,
15265 reversed: selection.reversed,
15266 }
15267 } else {
15268 selection.clone()
15269 }
15270 })
15271 .collect::<Vec<_>>();
15272
15273 if selected_sibling {
15274 self.change_selections(
15275 SelectionEffects::scroll(Autoscroll::fit()),
15276 window,
15277 cx,
15278 |s| {
15279 s.select(new_selections);
15280 },
15281 );
15282 }
15283 }
15284
15285 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15286 if !EditorSettings::get_global(cx).gutter.runnables {
15287 self.clear_tasks();
15288 return Task::ready(());
15289 }
15290 let project = self.project().map(Entity::downgrade);
15291 let task_sources = self.lsp_task_sources(cx);
15292 let multi_buffer = self.buffer.downgrade();
15293 cx.spawn_in(window, async move |editor, cx| {
15294 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15295 let Some(project) = project.and_then(|p| p.upgrade()) else {
15296 return;
15297 };
15298 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15299 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15300 }) else {
15301 return;
15302 };
15303
15304 let hide_runnables = project
15305 .update(cx, |project, _| project.is_via_collab())
15306 .unwrap_or(true);
15307 if hide_runnables {
15308 return;
15309 }
15310 let new_rows =
15311 cx.background_spawn({
15312 let snapshot = display_snapshot.clone();
15313 async move {
15314 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15315 }
15316 })
15317 .await;
15318 let Ok(lsp_tasks) =
15319 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15320 else {
15321 return;
15322 };
15323 let lsp_tasks = lsp_tasks.await;
15324
15325 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15326 lsp_tasks
15327 .into_iter()
15328 .flat_map(|(kind, tasks)| {
15329 tasks.into_iter().filter_map(move |(location, task)| {
15330 Some((kind.clone(), location?, task))
15331 })
15332 })
15333 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15334 let buffer = location.target.buffer;
15335 let buffer_snapshot = buffer.read(cx).snapshot();
15336 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15337 |(excerpt_id, snapshot, _)| {
15338 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15339 display_snapshot
15340 .buffer_snapshot
15341 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15342 } else {
15343 None
15344 }
15345 },
15346 );
15347 if let Some(offset) = offset {
15348 let task_buffer_range =
15349 location.target.range.to_point(&buffer_snapshot);
15350 let context_buffer_range =
15351 task_buffer_range.to_offset(&buffer_snapshot);
15352 let context_range = BufferOffset(context_buffer_range.start)
15353 ..BufferOffset(context_buffer_range.end);
15354
15355 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15356 .or_insert_with(|| RunnableTasks {
15357 templates: Vec::new(),
15358 offset,
15359 column: task_buffer_range.start.column,
15360 extra_variables: HashMap::default(),
15361 context_range,
15362 })
15363 .templates
15364 .push((kind, task.original_task().clone()));
15365 }
15366
15367 acc
15368 })
15369 }) else {
15370 return;
15371 };
15372
15373 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15374 buffer.language_settings(cx).tasks.prefer_lsp
15375 }) else {
15376 return;
15377 };
15378
15379 let rows = Self::runnable_rows(
15380 project,
15381 display_snapshot,
15382 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15383 new_rows,
15384 cx.clone(),
15385 )
15386 .await;
15387 editor
15388 .update(cx, |editor, _| {
15389 editor.clear_tasks();
15390 for (key, mut value) in rows {
15391 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15392 value.templates.extend(lsp_tasks.templates);
15393 }
15394
15395 editor.insert_tasks(key, value);
15396 }
15397 for (key, value) in lsp_tasks_by_rows {
15398 editor.insert_tasks(key, value);
15399 }
15400 })
15401 .ok();
15402 })
15403 }
15404 fn fetch_runnable_ranges(
15405 snapshot: &DisplaySnapshot,
15406 range: Range<Anchor>,
15407 ) -> Vec<language::RunnableRange> {
15408 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15409 }
15410
15411 fn runnable_rows(
15412 project: Entity<Project>,
15413 snapshot: DisplaySnapshot,
15414 prefer_lsp: bool,
15415 runnable_ranges: Vec<RunnableRange>,
15416 cx: AsyncWindowContext,
15417 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15418 cx.spawn(async move |cx| {
15419 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15420 for mut runnable in runnable_ranges {
15421 let Some(tasks) = cx
15422 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15423 .ok()
15424 else {
15425 continue;
15426 };
15427 let mut tasks = tasks.await;
15428
15429 if prefer_lsp {
15430 tasks.retain(|(task_kind, _)| {
15431 !matches!(task_kind, TaskSourceKind::Language { .. })
15432 });
15433 }
15434 if tasks.is_empty() {
15435 continue;
15436 }
15437
15438 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15439 let Some(row) = snapshot
15440 .buffer_snapshot
15441 .buffer_line_for_row(MultiBufferRow(point.row))
15442 .map(|(_, range)| range.start.row)
15443 else {
15444 continue;
15445 };
15446
15447 let context_range =
15448 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15449 runnable_rows.push((
15450 (runnable.buffer_id, row),
15451 RunnableTasks {
15452 templates: tasks,
15453 offset: snapshot
15454 .buffer_snapshot
15455 .anchor_before(runnable.run_range.start),
15456 context_range,
15457 column: point.column,
15458 extra_variables: runnable.extra_captures,
15459 },
15460 ));
15461 }
15462 runnable_rows
15463 })
15464 }
15465
15466 fn templates_with_tags(
15467 project: &Entity<Project>,
15468 runnable: &mut Runnable,
15469 cx: &mut App,
15470 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15471 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15472 let (worktree_id, file) = project
15473 .buffer_for_id(runnable.buffer, cx)
15474 .and_then(|buffer| buffer.read(cx).file())
15475 .map(|file| (file.worktree_id(cx), file.clone()))
15476 .unzip();
15477
15478 (
15479 project.task_store().read(cx).task_inventory().cloned(),
15480 worktree_id,
15481 file,
15482 )
15483 });
15484
15485 let tags = mem::take(&mut runnable.tags);
15486 let language = runnable.language.clone();
15487 cx.spawn(async move |cx| {
15488 let mut templates_with_tags = Vec::new();
15489 if let Some(inventory) = inventory {
15490 for RunnableTag(tag) in tags {
15491 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15492 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15493 }) else {
15494 return templates_with_tags;
15495 };
15496 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15497 move |(_, template)| {
15498 template.tags.iter().any(|source_tag| source_tag == &tag)
15499 },
15500 ));
15501 }
15502 }
15503 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15504
15505 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15506 // Strongest source wins; if we have worktree tag binding, prefer that to
15507 // global and language bindings;
15508 // if we have a global binding, prefer that to language binding.
15509 let first_mismatch = templates_with_tags
15510 .iter()
15511 .position(|(tag_source, _)| tag_source != leading_tag_source);
15512 if let Some(index) = first_mismatch {
15513 templates_with_tags.truncate(index);
15514 }
15515 }
15516
15517 templates_with_tags
15518 })
15519 }
15520
15521 pub fn move_to_enclosing_bracket(
15522 &mut self,
15523 _: &MoveToEnclosingBracket,
15524 window: &mut Window,
15525 cx: &mut Context<Self>,
15526 ) {
15527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15528 self.change_selections(Default::default(), window, cx, |s| {
15529 s.move_offsets_with(|snapshot, selection| {
15530 let Some(enclosing_bracket_ranges) =
15531 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15532 else {
15533 return;
15534 };
15535
15536 let mut best_length = usize::MAX;
15537 let mut best_inside = false;
15538 let mut best_in_bracket_range = false;
15539 let mut best_destination = None;
15540 for (open, close) in enclosing_bracket_ranges {
15541 let close = close.to_inclusive();
15542 let length = close.end() - open.start;
15543 let inside = selection.start >= open.end && selection.end <= *close.start();
15544 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15545 || close.contains(&selection.head());
15546
15547 // If best is next to a bracket and current isn't, skip
15548 if !in_bracket_range && best_in_bracket_range {
15549 continue;
15550 }
15551
15552 // Prefer smaller lengths unless best is inside and current isn't
15553 if length > best_length && (best_inside || !inside) {
15554 continue;
15555 }
15556
15557 best_length = length;
15558 best_inside = inside;
15559 best_in_bracket_range = in_bracket_range;
15560 best_destination = Some(
15561 if close.contains(&selection.start) && close.contains(&selection.end) {
15562 if inside { open.end } else { open.start }
15563 } else if inside {
15564 *close.start()
15565 } else {
15566 *close.end()
15567 },
15568 );
15569 }
15570
15571 if let Some(destination) = best_destination {
15572 selection.collapse_to(destination, SelectionGoal::None);
15573 }
15574 })
15575 });
15576 }
15577
15578 pub fn undo_selection(
15579 &mut self,
15580 _: &UndoSelection,
15581 window: &mut Window,
15582 cx: &mut Context<Self>,
15583 ) {
15584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15585 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15586 self.selection_history.mode = SelectionHistoryMode::Undoing;
15587 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15588 this.end_selection(window, cx);
15589 this.change_selections(
15590 SelectionEffects::scroll(Autoscroll::newest()),
15591 window,
15592 cx,
15593 |s| s.select_anchors(entry.selections.to_vec()),
15594 );
15595 });
15596 self.selection_history.mode = SelectionHistoryMode::Normal;
15597
15598 self.select_next_state = entry.select_next_state;
15599 self.select_prev_state = entry.select_prev_state;
15600 self.add_selections_state = entry.add_selections_state;
15601 }
15602 }
15603
15604 pub fn redo_selection(
15605 &mut self,
15606 _: &RedoSelection,
15607 window: &mut Window,
15608 cx: &mut Context<Self>,
15609 ) {
15610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15611 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15612 self.selection_history.mode = SelectionHistoryMode::Redoing;
15613 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15614 this.end_selection(window, cx);
15615 this.change_selections(
15616 SelectionEffects::scroll(Autoscroll::newest()),
15617 window,
15618 cx,
15619 |s| s.select_anchors(entry.selections.to_vec()),
15620 );
15621 });
15622 self.selection_history.mode = SelectionHistoryMode::Normal;
15623
15624 self.select_next_state = entry.select_next_state;
15625 self.select_prev_state = entry.select_prev_state;
15626 self.add_selections_state = entry.add_selections_state;
15627 }
15628 }
15629
15630 pub fn expand_excerpts(
15631 &mut self,
15632 action: &ExpandExcerpts,
15633 _: &mut Window,
15634 cx: &mut Context<Self>,
15635 ) {
15636 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15637 }
15638
15639 pub fn expand_excerpts_down(
15640 &mut self,
15641 action: &ExpandExcerptsDown,
15642 _: &mut Window,
15643 cx: &mut Context<Self>,
15644 ) {
15645 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15646 }
15647
15648 pub fn expand_excerpts_up(
15649 &mut self,
15650 action: &ExpandExcerptsUp,
15651 _: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) {
15654 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15655 }
15656
15657 pub fn expand_excerpts_for_direction(
15658 &mut self,
15659 lines: u32,
15660 direction: ExpandExcerptDirection,
15661
15662 cx: &mut Context<Self>,
15663 ) {
15664 let selections = self.selections.disjoint_anchors();
15665
15666 let lines = if lines == 0 {
15667 EditorSettings::get_global(cx).expand_excerpt_lines
15668 } else {
15669 lines
15670 };
15671
15672 self.buffer.update(cx, |buffer, cx| {
15673 let snapshot = buffer.snapshot(cx);
15674 let mut excerpt_ids = selections
15675 .iter()
15676 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15677 .collect::<Vec<_>>();
15678 excerpt_ids.sort();
15679 excerpt_ids.dedup();
15680 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15681 })
15682 }
15683
15684 pub fn expand_excerpt(
15685 &mut self,
15686 excerpt: ExcerptId,
15687 direction: ExpandExcerptDirection,
15688 window: &mut Window,
15689 cx: &mut Context<Self>,
15690 ) {
15691 let current_scroll_position = self.scroll_position(cx);
15692 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15693 let mut should_scroll_up = false;
15694
15695 if direction == ExpandExcerptDirection::Down {
15696 let multi_buffer = self.buffer.read(cx);
15697 let snapshot = multi_buffer.snapshot(cx);
15698 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15699 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15700 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15701 {
15702 let buffer_snapshot = buffer.read(cx).snapshot();
15703 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15704 let last_row = buffer_snapshot.max_point().row;
15705 let lines_below = last_row.saturating_sub(excerpt_end_row);
15706 should_scroll_up = lines_below >= lines_to_expand;
15707 }
15708 }
15709
15710 self.buffer.update(cx, |buffer, cx| {
15711 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15712 });
15713
15714 if should_scroll_up {
15715 let new_scroll_position =
15716 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15717 self.set_scroll_position(new_scroll_position, window, cx);
15718 }
15719 }
15720
15721 pub fn go_to_singleton_buffer_point(
15722 &mut self,
15723 point: Point,
15724 window: &mut Window,
15725 cx: &mut Context<Self>,
15726 ) {
15727 self.go_to_singleton_buffer_range(point..point, window, cx);
15728 }
15729
15730 pub fn go_to_singleton_buffer_range(
15731 &mut self,
15732 range: Range<Point>,
15733 window: &mut Window,
15734 cx: &mut Context<Self>,
15735 ) {
15736 let multibuffer = self.buffer().read(cx);
15737 let Some(buffer) = multibuffer.as_singleton() else {
15738 return;
15739 };
15740 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15741 return;
15742 };
15743 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15744 return;
15745 };
15746 self.change_selections(
15747 SelectionEffects::default().nav_history(true),
15748 window,
15749 cx,
15750 |s| s.select_anchor_ranges([start..end]),
15751 );
15752 }
15753
15754 pub fn go_to_diagnostic(
15755 &mut self,
15756 action: &GoToDiagnostic,
15757 window: &mut Window,
15758 cx: &mut Context<Self>,
15759 ) {
15760 if !self.diagnostics_enabled() {
15761 return;
15762 }
15763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15764 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15765 }
15766
15767 pub fn go_to_prev_diagnostic(
15768 &mut self,
15769 action: &GoToPreviousDiagnostic,
15770 window: &mut Window,
15771 cx: &mut Context<Self>,
15772 ) {
15773 if !self.diagnostics_enabled() {
15774 return;
15775 }
15776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15777 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15778 }
15779
15780 pub fn go_to_diagnostic_impl(
15781 &mut self,
15782 direction: Direction,
15783 severity: GoToDiagnosticSeverityFilter,
15784 window: &mut Window,
15785 cx: &mut Context<Self>,
15786 ) {
15787 let buffer = self.buffer.read(cx).snapshot(cx);
15788 let selection = self.selections.newest::<usize>(cx);
15789
15790 let mut active_group_id = None;
15791 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15792 && active_group.active_range.start.to_offset(&buffer) == selection.start
15793 {
15794 active_group_id = Some(active_group.group_id);
15795 }
15796
15797 fn filtered(
15798 snapshot: EditorSnapshot,
15799 severity: GoToDiagnosticSeverityFilter,
15800 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15801 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15802 diagnostics
15803 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15804 .filter(|entry| entry.range.start != entry.range.end)
15805 .filter(|entry| !entry.diagnostic.is_unnecessary)
15806 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15807 }
15808
15809 let snapshot = self.snapshot(window, cx);
15810 let before = filtered(
15811 snapshot.clone(),
15812 severity,
15813 buffer
15814 .diagnostics_in_range(0..selection.start)
15815 .filter(|entry| entry.range.start <= selection.start),
15816 );
15817 let after = filtered(
15818 snapshot,
15819 severity,
15820 buffer
15821 .diagnostics_in_range(selection.start..buffer.len())
15822 .filter(|entry| entry.range.start >= selection.start),
15823 );
15824
15825 let mut found: Option<DiagnosticEntry<usize>> = None;
15826 if direction == Direction::Prev {
15827 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15828 {
15829 for diagnostic in prev_diagnostics.into_iter().rev() {
15830 if diagnostic.range.start != selection.start
15831 || active_group_id
15832 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15833 {
15834 found = Some(diagnostic);
15835 break 'outer;
15836 }
15837 }
15838 }
15839 } else {
15840 for diagnostic in after.chain(before) {
15841 if diagnostic.range.start != selection.start
15842 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15843 {
15844 found = Some(diagnostic);
15845 break;
15846 }
15847 }
15848 }
15849 let Some(next_diagnostic) = found else {
15850 return;
15851 };
15852
15853 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15854 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15855 return;
15856 };
15857 self.change_selections(Default::default(), window, cx, |s| {
15858 s.select_ranges(vec![
15859 next_diagnostic.range.start..next_diagnostic.range.start,
15860 ])
15861 });
15862 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15863 self.refresh_edit_prediction(false, true, window, cx);
15864 }
15865
15866 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15868 let snapshot = self.snapshot(window, cx);
15869 let selection = self.selections.newest::<Point>(cx);
15870 self.go_to_hunk_before_or_after_position(
15871 &snapshot,
15872 selection.head(),
15873 Direction::Next,
15874 window,
15875 cx,
15876 );
15877 }
15878
15879 pub fn go_to_hunk_before_or_after_position(
15880 &mut self,
15881 snapshot: &EditorSnapshot,
15882 position: Point,
15883 direction: Direction,
15884 window: &mut Window,
15885 cx: &mut Context<Editor>,
15886 ) {
15887 let row = if direction == Direction::Next {
15888 self.hunk_after_position(snapshot, position)
15889 .map(|hunk| hunk.row_range.start)
15890 } else {
15891 self.hunk_before_position(snapshot, position)
15892 };
15893
15894 if let Some(row) = row {
15895 let destination = Point::new(row.0, 0);
15896 let autoscroll = Autoscroll::center();
15897
15898 self.unfold_ranges(&[destination..destination], false, false, cx);
15899 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15900 s.select_ranges([destination..destination]);
15901 });
15902 }
15903 }
15904
15905 fn hunk_after_position(
15906 &mut self,
15907 snapshot: &EditorSnapshot,
15908 position: Point,
15909 ) -> Option<MultiBufferDiffHunk> {
15910 snapshot
15911 .buffer_snapshot
15912 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15913 .find(|hunk| hunk.row_range.start.0 > position.row)
15914 .or_else(|| {
15915 snapshot
15916 .buffer_snapshot
15917 .diff_hunks_in_range(Point::zero()..position)
15918 .find(|hunk| hunk.row_range.end.0 < position.row)
15919 })
15920 }
15921
15922 fn go_to_prev_hunk(
15923 &mut self,
15924 _: &GoToPreviousHunk,
15925 window: &mut Window,
15926 cx: &mut Context<Self>,
15927 ) {
15928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15929 let snapshot = self.snapshot(window, cx);
15930 let selection = self.selections.newest::<Point>(cx);
15931 self.go_to_hunk_before_or_after_position(
15932 &snapshot,
15933 selection.head(),
15934 Direction::Prev,
15935 window,
15936 cx,
15937 );
15938 }
15939
15940 fn hunk_before_position(
15941 &mut self,
15942 snapshot: &EditorSnapshot,
15943 position: Point,
15944 ) -> Option<MultiBufferRow> {
15945 snapshot
15946 .buffer_snapshot
15947 .diff_hunk_before(position)
15948 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15949 }
15950
15951 fn go_to_next_change(
15952 &mut self,
15953 _: &GoToNextChange,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 if let Some(selections) = self
15958 .change_list
15959 .next_change(1, Direction::Next)
15960 .map(|s| s.to_vec())
15961 {
15962 self.change_selections(Default::default(), window, cx, |s| {
15963 let map = s.display_map();
15964 s.select_display_ranges(selections.iter().map(|a| {
15965 let point = a.to_display_point(&map);
15966 point..point
15967 }))
15968 })
15969 }
15970 }
15971
15972 fn go_to_previous_change(
15973 &mut self,
15974 _: &GoToPreviousChange,
15975 window: &mut Window,
15976 cx: &mut Context<Self>,
15977 ) {
15978 if let Some(selections) = self
15979 .change_list
15980 .next_change(1, Direction::Prev)
15981 .map(|s| s.to_vec())
15982 {
15983 self.change_selections(Default::default(), window, cx, |s| {
15984 let map = s.display_map();
15985 s.select_display_ranges(selections.iter().map(|a| {
15986 let point = a.to_display_point(&map);
15987 point..point
15988 }))
15989 })
15990 }
15991 }
15992
15993 pub fn go_to_next_document_highlight(
15994 &mut self,
15995 _: &GoToNextDocumentHighlight,
15996 window: &mut Window,
15997 cx: &mut Context<Self>,
15998 ) {
15999 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16000 }
16001
16002 pub fn go_to_prev_document_highlight(
16003 &mut self,
16004 _: &GoToPreviousDocumentHighlight,
16005 window: &mut Window,
16006 cx: &mut Context<Self>,
16007 ) {
16008 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16009 }
16010
16011 pub fn go_to_document_highlight_before_or_after_position(
16012 &mut self,
16013 direction: Direction,
16014 window: &mut Window,
16015 cx: &mut Context<Editor>,
16016 ) {
16017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16018 let snapshot = self.snapshot(window, cx);
16019 let buffer = &snapshot.buffer_snapshot;
16020 let position = self.selections.newest::<Point>(cx).head();
16021 let anchor_position = buffer.anchor_after(position);
16022
16023 // Get all document highlights (both read and write)
16024 let mut all_highlights = Vec::new();
16025
16026 if let Some((_, read_highlights)) = self
16027 .background_highlights
16028 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16029 {
16030 all_highlights.extend(read_highlights.iter());
16031 }
16032
16033 if let Some((_, write_highlights)) = self
16034 .background_highlights
16035 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16036 {
16037 all_highlights.extend(write_highlights.iter());
16038 }
16039
16040 if all_highlights.is_empty() {
16041 return;
16042 }
16043
16044 // Sort highlights by position
16045 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16046
16047 let target_highlight = match direction {
16048 Direction::Next => {
16049 // Find the first highlight after the current position
16050 all_highlights
16051 .iter()
16052 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16053 }
16054 Direction::Prev => {
16055 // Find the last highlight before the current position
16056 all_highlights
16057 .iter()
16058 .rev()
16059 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16060 }
16061 };
16062
16063 if let Some(highlight) = target_highlight {
16064 let destination = highlight.start.to_point(buffer);
16065 let autoscroll = Autoscroll::center();
16066
16067 self.unfold_ranges(&[destination..destination], false, false, cx);
16068 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16069 s.select_ranges([destination..destination]);
16070 });
16071 }
16072 }
16073
16074 fn go_to_line<T: 'static>(
16075 &mut self,
16076 position: Anchor,
16077 highlight_color: Option<Hsla>,
16078 window: &mut Window,
16079 cx: &mut Context<Self>,
16080 ) {
16081 let snapshot = self.snapshot(window, cx).display_snapshot;
16082 let position = position.to_point(&snapshot.buffer_snapshot);
16083 let start = snapshot
16084 .buffer_snapshot
16085 .clip_point(Point::new(position.row, 0), Bias::Left);
16086 let end = start + Point::new(1, 0);
16087 let start = snapshot.buffer_snapshot.anchor_before(start);
16088 let end = snapshot.buffer_snapshot.anchor_before(end);
16089
16090 self.highlight_rows::<T>(
16091 start..end,
16092 highlight_color
16093 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16094 Default::default(),
16095 cx,
16096 );
16097
16098 if self.buffer.read(cx).is_singleton() {
16099 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16100 }
16101 }
16102
16103 pub fn go_to_definition(
16104 &mut self,
16105 _: &GoToDefinition,
16106 window: &mut Window,
16107 cx: &mut Context<Self>,
16108 ) -> Task<Result<Navigated>> {
16109 let definition =
16110 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16111 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16112 cx.spawn_in(window, async move |editor, cx| {
16113 if definition.await? == Navigated::Yes {
16114 return Ok(Navigated::Yes);
16115 }
16116 match fallback_strategy {
16117 GoToDefinitionFallback::None => Ok(Navigated::No),
16118 GoToDefinitionFallback::FindAllReferences => {
16119 match editor.update_in(cx, |editor, window, cx| {
16120 editor.find_all_references(&FindAllReferences, window, cx)
16121 })? {
16122 Some(references) => references.await,
16123 None => Ok(Navigated::No),
16124 }
16125 }
16126 }
16127 })
16128 }
16129
16130 pub fn go_to_declaration(
16131 &mut self,
16132 _: &GoToDeclaration,
16133 window: &mut Window,
16134 cx: &mut Context<Self>,
16135 ) -> Task<Result<Navigated>> {
16136 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16137 }
16138
16139 pub fn go_to_declaration_split(
16140 &mut self,
16141 _: &GoToDeclaration,
16142 window: &mut Window,
16143 cx: &mut Context<Self>,
16144 ) -> Task<Result<Navigated>> {
16145 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16146 }
16147
16148 pub fn go_to_implementation(
16149 &mut self,
16150 _: &GoToImplementation,
16151 window: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) -> Task<Result<Navigated>> {
16154 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16155 }
16156
16157 pub fn go_to_implementation_split(
16158 &mut self,
16159 _: &GoToImplementationSplit,
16160 window: &mut Window,
16161 cx: &mut Context<Self>,
16162 ) -> Task<Result<Navigated>> {
16163 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16164 }
16165
16166 pub fn go_to_type_definition(
16167 &mut self,
16168 _: &GoToTypeDefinition,
16169 window: &mut Window,
16170 cx: &mut Context<Self>,
16171 ) -> Task<Result<Navigated>> {
16172 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16173 }
16174
16175 pub fn go_to_definition_split(
16176 &mut self,
16177 _: &GoToDefinitionSplit,
16178 window: &mut Window,
16179 cx: &mut Context<Self>,
16180 ) -> Task<Result<Navigated>> {
16181 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16182 }
16183
16184 pub fn go_to_type_definition_split(
16185 &mut self,
16186 _: &GoToTypeDefinitionSplit,
16187 window: &mut Window,
16188 cx: &mut Context<Self>,
16189 ) -> Task<Result<Navigated>> {
16190 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16191 }
16192
16193 fn go_to_definition_of_kind(
16194 &mut self,
16195 kind: GotoDefinitionKind,
16196 split: bool,
16197 window: &mut Window,
16198 cx: &mut Context<Self>,
16199 ) -> Task<Result<Navigated>> {
16200 let Some(provider) = self.semantics_provider.clone() else {
16201 return Task::ready(Ok(Navigated::No));
16202 };
16203 let head = self.selections.newest::<usize>(cx).head();
16204 let buffer = self.buffer.read(cx);
16205 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16206 return Task::ready(Ok(Navigated::No));
16207 };
16208 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16209 return Task::ready(Ok(Navigated::No));
16210 };
16211
16212 cx.spawn_in(window, async move |editor, cx| {
16213 let Some(definitions) = definitions.await? else {
16214 return Ok(Navigated::No);
16215 };
16216 let navigated = editor
16217 .update_in(cx, |editor, window, cx| {
16218 editor.navigate_to_hover_links(
16219 Some(kind),
16220 definitions
16221 .into_iter()
16222 .filter(|location| {
16223 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16224 })
16225 .map(HoverLink::Text)
16226 .collect::<Vec<_>>(),
16227 split,
16228 window,
16229 cx,
16230 )
16231 })?
16232 .await?;
16233 anyhow::Ok(navigated)
16234 })
16235 }
16236
16237 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16238 let selection = self.selections.newest_anchor();
16239 let head = selection.head();
16240 let tail = selection.tail();
16241
16242 let Some((buffer, start_position)) =
16243 self.buffer.read(cx).text_anchor_for_position(head, cx)
16244 else {
16245 return;
16246 };
16247
16248 let end_position = if head != tail {
16249 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16250 return;
16251 };
16252 Some(pos)
16253 } else {
16254 None
16255 };
16256
16257 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16258 let url = if let Some(end_pos) = end_position {
16259 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16260 } else {
16261 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16262 };
16263
16264 if let Some(url) = url {
16265 editor.update(cx, |_, cx| {
16266 cx.open_url(&url);
16267 })
16268 } else {
16269 Ok(())
16270 }
16271 });
16272
16273 url_finder.detach();
16274 }
16275
16276 pub fn open_selected_filename(
16277 &mut self,
16278 _: &OpenSelectedFilename,
16279 window: &mut Window,
16280 cx: &mut Context<Self>,
16281 ) {
16282 let Some(workspace) = self.workspace() else {
16283 return;
16284 };
16285
16286 let position = self.selections.newest_anchor().head();
16287
16288 let Some((buffer, buffer_position)) =
16289 self.buffer.read(cx).text_anchor_for_position(position, cx)
16290 else {
16291 return;
16292 };
16293
16294 let project = self.project.clone();
16295
16296 cx.spawn_in(window, async move |_, cx| {
16297 let result = find_file(&buffer, project, buffer_position, cx).await;
16298
16299 if let Some((_, path)) = result {
16300 workspace
16301 .update_in(cx, |workspace, window, cx| {
16302 workspace.open_resolved_path(path, window, cx)
16303 })?
16304 .await?;
16305 }
16306 anyhow::Ok(())
16307 })
16308 .detach();
16309 }
16310
16311 pub(crate) fn navigate_to_hover_links(
16312 &mut self,
16313 kind: Option<GotoDefinitionKind>,
16314 definitions: Vec<HoverLink>,
16315 split: bool,
16316 window: &mut Window,
16317 cx: &mut Context<Editor>,
16318 ) -> Task<Result<Navigated>> {
16319 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16320 let mut first_url_or_file = None;
16321 let definitions: Vec<_> = definitions
16322 .into_iter()
16323 .filter_map(|def| match def {
16324 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16325 HoverLink::InlayHint(lsp_location, server_id) => {
16326 let computation =
16327 self.compute_target_location(lsp_location, server_id, window, cx);
16328 Some(cx.background_spawn(computation))
16329 }
16330 HoverLink::Url(url) => {
16331 first_url_or_file = Some(Either::Left(url));
16332 None
16333 }
16334 HoverLink::File(path) => {
16335 first_url_or_file = Some(Either::Right(path));
16336 None
16337 }
16338 })
16339 .collect();
16340
16341 let workspace = self.workspace();
16342
16343 cx.spawn_in(window, async move |editor, acx| {
16344 let mut locations: Vec<Location> = future::join_all(definitions)
16345 .await
16346 .into_iter()
16347 .filter_map(|location| location.transpose())
16348 .collect::<Result<_>>()
16349 .context("location tasks")?;
16350
16351 if locations.len() > 1 {
16352 let Some(workspace) = workspace else {
16353 return Ok(Navigated::No);
16354 };
16355
16356 let tab_kind = match kind {
16357 Some(GotoDefinitionKind::Implementation) => "Implementations",
16358 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16359 Some(GotoDefinitionKind::Declaration) => "Declarations",
16360 Some(GotoDefinitionKind::Type) => "Types",
16361 };
16362 let title = editor
16363 .update_in(acx, |_, _, cx| {
16364 let target = locations
16365 .iter()
16366 .map(|location| {
16367 location
16368 .buffer
16369 .read(cx)
16370 .text_for_range(location.range.clone())
16371 .collect::<String>()
16372 })
16373 .filter(|text| !text.contains('\n'))
16374 .unique()
16375 .take(3)
16376 .join(", ");
16377 if target.is_empty() {
16378 tab_kind.to_owned()
16379 } else {
16380 format!("{tab_kind} for {target}")
16381 }
16382 })
16383 .context("buffer title")?;
16384
16385 let opened = workspace
16386 .update_in(acx, |workspace, window, cx| {
16387 Self::open_locations_in_multibuffer(
16388 workspace,
16389 locations,
16390 title,
16391 split,
16392 MultibufferSelectionMode::First,
16393 window,
16394 cx,
16395 )
16396 })
16397 .is_ok();
16398
16399 anyhow::Ok(Navigated::from_bool(opened))
16400 } else if locations.is_empty() {
16401 // If there is one url or file, open it directly
16402 match first_url_or_file {
16403 Some(Either::Left(url)) => {
16404 acx.update(|_, cx| cx.open_url(&url))?;
16405 Ok(Navigated::Yes)
16406 }
16407 Some(Either::Right(path)) => {
16408 let Some(workspace) = workspace else {
16409 return Ok(Navigated::No);
16410 };
16411
16412 workspace
16413 .update_in(acx, |workspace, window, cx| {
16414 workspace.open_resolved_path(path, window, cx)
16415 })?
16416 .await?;
16417 Ok(Navigated::Yes)
16418 }
16419 None => Ok(Navigated::No),
16420 }
16421 } else {
16422 let Some(workspace) = workspace else {
16423 return Ok(Navigated::No);
16424 };
16425
16426 let target = locations.pop().unwrap();
16427 editor.update_in(acx, |editor, window, cx| {
16428 let range = target.range.to_point(target.buffer.read(cx));
16429 let range = editor.range_for_match(&range);
16430 let range = collapse_multiline_range(range);
16431
16432 if !split
16433 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16434 {
16435 editor.go_to_singleton_buffer_range(range, window, cx);
16436 } else {
16437 let pane = workspace.read(cx).active_pane().clone();
16438 window.defer(cx, move |window, cx| {
16439 let target_editor: Entity<Self> =
16440 workspace.update(cx, |workspace, cx| {
16441 let pane = if split {
16442 workspace.adjacent_pane(window, cx)
16443 } else {
16444 workspace.active_pane().clone()
16445 };
16446
16447 workspace.open_project_item(
16448 pane,
16449 target.buffer.clone(),
16450 true,
16451 true,
16452 window,
16453 cx,
16454 )
16455 });
16456 target_editor.update(cx, |target_editor, cx| {
16457 // When selecting a definition in a different buffer, disable the nav history
16458 // to avoid creating a history entry at the previous cursor location.
16459 pane.update(cx, |pane, _| pane.disable_history());
16460 target_editor.go_to_singleton_buffer_range(range, window, cx);
16461 pane.update(cx, |pane, _| pane.enable_history());
16462 });
16463 });
16464 }
16465 Navigated::Yes
16466 })
16467 }
16468 })
16469 }
16470
16471 fn compute_target_location(
16472 &self,
16473 lsp_location: lsp::Location,
16474 server_id: LanguageServerId,
16475 window: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) -> Task<anyhow::Result<Option<Location>>> {
16478 let Some(project) = self.project.clone() else {
16479 return Task::ready(Ok(None));
16480 };
16481
16482 cx.spawn_in(window, async move |editor, cx| {
16483 let location_task = editor.update(cx, |_, cx| {
16484 project.update(cx, |project, cx| {
16485 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16486 })
16487 })?;
16488 let location = Some({
16489 let target_buffer_handle = location_task.await.context("open local buffer")?;
16490 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16491 let target_start = target_buffer
16492 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16493 let target_end = target_buffer
16494 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16495 target_buffer.anchor_after(target_start)
16496 ..target_buffer.anchor_before(target_end)
16497 })?;
16498 Location {
16499 buffer: target_buffer_handle,
16500 range,
16501 }
16502 });
16503 Ok(location)
16504 })
16505 }
16506
16507 pub fn find_all_references(
16508 &mut self,
16509 _: &FindAllReferences,
16510 window: &mut Window,
16511 cx: &mut Context<Self>,
16512 ) -> Option<Task<Result<Navigated>>> {
16513 let selection = self.selections.newest::<usize>(cx);
16514 let multi_buffer = self.buffer.read(cx);
16515 let head = selection.head();
16516
16517 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16518 let head_anchor = multi_buffer_snapshot.anchor_at(
16519 head,
16520 if head < selection.tail() {
16521 Bias::Right
16522 } else {
16523 Bias::Left
16524 },
16525 );
16526
16527 match self
16528 .find_all_references_task_sources
16529 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16530 {
16531 Ok(_) => {
16532 log::info!(
16533 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16534 );
16535 return None;
16536 }
16537 Err(i) => {
16538 self.find_all_references_task_sources.insert(i, head_anchor);
16539 }
16540 }
16541
16542 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16543 let workspace = self.workspace()?;
16544 let project = workspace.read(cx).project().clone();
16545 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16546 Some(cx.spawn_in(window, async move |editor, cx| {
16547 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16548 if let Ok(i) = editor
16549 .find_all_references_task_sources
16550 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16551 {
16552 editor.find_all_references_task_sources.remove(i);
16553 }
16554 });
16555
16556 let Some(locations) = references.await? else {
16557 return anyhow::Ok(Navigated::No);
16558 };
16559 if locations.is_empty() {
16560 return anyhow::Ok(Navigated::No);
16561 }
16562
16563 workspace.update_in(cx, |workspace, window, cx| {
16564 let target = locations
16565 .iter()
16566 .map(|location| {
16567 location
16568 .buffer
16569 .read(cx)
16570 .text_for_range(location.range.clone())
16571 .collect::<String>()
16572 })
16573 .filter(|text| !text.contains('\n'))
16574 .unique()
16575 .take(3)
16576 .join(", ");
16577 let title = if target.is_empty() {
16578 "References".to_owned()
16579 } else {
16580 format!("References to {target}")
16581 };
16582 Self::open_locations_in_multibuffer(
16583 workspace,
16584 locations,
16585 title,
16586 false,
16587 MultibufferSelectionMode::First,
16588 window,
16589 cx,
16590 );
16591 Navigated::Yes
16592 })
16593 }))
16594 }
16595
16596 /// Opens a multibuffer with the given project locations in it
16597 pub fn open_locations_in_multibuffer(
16598 workspace: &mut Workspace,
16599 mut locations: Vec<Location>,
16600 title: String,
16601 split: bool,
16602 multibuffer_selection_mode: MultibufferSelectionMode,
16603 window: &mut Window,
16604 cx: &mut Context<Workspace>,
16605 ) {
16606 if locations.is_empty() {
16607 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16608 return;
16609 }
16610
16611 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16612
16613 let mut locations = locations.into_iter().peekable();
16614 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16615 let capability = workspace.project().read(cx).capability();
16616
16617 // a key to find existing multibuffer editors with the same set of locations
16618 // to prevent us from opening more and more multibuffer tabs for searches and the like
16619 let mut key = (title.clone(), vec![]);
16620 let excerpt_buffer = cx.new(|cx| {
16621 let key = &mut key.1;
16622 let mut multibuffer = MultiBuffer::new(capability);
16623 while let Some(location) = locations.next() {
16624 let buffer = location.buffer.read(cx);
16625 let mut ranges_for_buffer = Vec::new();
16626 let range = location.range.to_point(buffer);
16627 ranges_for_buffer.push(range.clone());
16628
16629 while let Some(next_location) =
16630 locations.next_if(|next_location| next_location.buffer == location.buffer)
16631 {
16632 ranges_for_buffer.push(next_location.range.to_point(buffer));
16633 }
16634
16635 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16636 key.push((
16637 location.buffer.read(cx).remote_id(),
16638 ranges_for_buffer.clone(),
16639 ));
16640 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16641 PathKey::for_buffer(&location.buffer, cx),
16642 location.buffer.clone(),
16643 ranges_for_buffer,
16644 multibuffer_context_lines(cx),
16645 cx,
16646 );
16647 ranges.extend(new_ranges)
16648 }
16649
16650 multibuffer.with_title(title)
16651 });
16652 let existing = workspace.active_pane().update(cx, |pane, cx| {
16653 pane.items()
16654 .filter_map(|item| item.downcast::<Editor>())
16655 .find(|editor| {
16656 editor
16657 .read(cx)
16658 .lookup_key
16659 .as_ref()
16660 .and_then(|it| {
16661 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16662 })
16663 .is_some_and(|it| *it == key)
16664 })
16665 });
16666 let editor = existing.unwrap_or_else(|| {
16667 cx.new(|cx| {
16668 let mut editor = Editor::for_multibuffer(
16669 excerpt_buffer,
16670 Some(workspace.project().clone()),
16671 window,
16672 cx,
16673 );
16674 editor.lookup_key = Some(Box::new(key));
16675 editor
16676 })
16677 });
16678 editor.update(cx, |editor, cx| {
16679 match multibuffer_selection_mode {
16680 MultibufferSelectionMode::First => {
16681 if let Some(first_range) = ranges.first() {
16682 editor.change_selections(
16683 SelectionEffects::no_scroll(),
16684 window,
16685 cx,
16686 |selections| {
16687 selections.clear_disjoint();
16688 selections
16689 .select_anchor_ranges(std::iter::once(first_range.clone()));
16690 },
16691 );
16692 }
16693 editor.highlight_background::<Self>(
16694 &ranges,
16695 |theme| theme.colors().editor_highlighted_line_background,
16696 cx,
16697 );
16698 }
16699 MultibufferSelectionMode::All => {
16700 editor.change_selections(
16701 SelectionEffects::no_scroll(),
16702 window,
16703 cx,
16704 |selections| {
16705 selections.clear_disjoint();
16706 selections.select_anchor_ranges(ranges);
16707 },
16708 );
16709 }
16710 }
16711 editor.register_buffers_with_language_servers(cx);
16712 });
16713
16714 let item = Box::new(editor);
16715 let item_id = item.item_id();
16716
16717 if split {
16718 workspace.split_item(SplitDirection::Right, item, window, cx);
16719 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16720 let (preview_item_id, preview_item_idx) =
16721 workspace.active_pane().read_with(cx, |pane, _| {
16722 (pane.preview_item_id(), pane.preview_item_idx())
16723 });
16724
16725 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16726
16727 if let Some(preview_item_id) = preview_item_id {
16728 workspace.active_pane().update(cx, |pane, cx| {
16729 pane.remove_item(preview_item_id, false, false, window, cx);
16730 });
16731 }
16732 } else {
16733 workspace.add_item_to_active_pane(item, None, true, window, cx);
16734 }
16735 workspace.active_pane().update(cx, |pane, cx| {
16736 pane.set_preview_item_id(Some(item_id), cx);
16737 });
16738 }
16739
16740 pub fn rename(
16741 &mut self,
16742 _: &Rename,
16743 window: &mut Window,
16744 cx: &mut Context<Self>,
16745 ) -> Option<Task<Result<()>>> {
16746 use language::ToOffset as _;
16747
16748 let provider = self.semantics_provider.clone()?;
16749 let selection = self.selections.newest_anchor().clone();
16750 let (cursor_buffer, cursor_buffer_position) = self
16751 .buffer
16752 .read(cx)
16753 .text_anchor_for_position(selection.head(), cx)?;
16754 let (tail_buffer, cursor_buffer_position_end) = self
16755 .buffer
16756 .read(cx)
16757 .text_anchor_for_position(selection.tail(), cx)?;
16758 if tail_buffer != cursor_buffer {
16759 return None;
16760 }
16761
16762 let snapshot = cursor_buffer.read(cx).snapshot();
16763 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16764 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16765 let prepare_rename = provider
16766 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16767 .unwrap_or_else(|| Task::ready(Ok(None)));
16768 drop(snapshot);
16769
16770 Some(cx.spawn_in(window, async move |this, cx| {
16771 let rename_range = if let Some(range) = prepare_rename.await? {
16772 Some(range)
16773 } else {
16774 this.update(cx, |this, cx| {
16775 let buffer = this.buffer.read(cx).snapshot(cx);
16776 let mut buffer_highlights = this
16777 .document_highlights_for_position(selection.head(), &buffer)
16778 .filter(|highlight| {
16779 highlight.start.excerpt_id == selection.head().excerpt_id
16780 && highlight.end.excerpt_id == selection.head().excerpt_id
16781 });
16782 buffer_highlights
16783 .next()
16784 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16785 })?
16786 };
16787 if let Some(rename_range) = rename_range {
16788 this.update_in(cx, |this, window, cx| {
16789 let snapshot = cursor_buffer.read(cx).snapshot();
16790 let rename_buffer_range = rename_range.to_offset(&snapshot);
16791 let cursor_offset_in_rename_range =
16792 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16793 let cursor_offset_in_rename_range_end =
16794 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16795
16796 this.take_rename(false, window, cx);
16797 let buffer = this.buffer.read(cx).read(cx);
16798 let cursor_offset = selection.head().to_offset(&buffer);
16799 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16800 let rename_end = rename_start + rename_buffer_range.len();
16801 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16802 let mut old_highlight_id = None;
16803 let old_name: Arc<str> = buffer
16804 .chunks(rename_start..rename_end, true)
16805 .map(|chunk| {
16806 if old_highlight_id.is_none() {
16807 old_highlight_id = chunk.syntax_highlight_id;
16808 }
16809 chunk.text
16810 })
16811 .collect::<String>()
16812 .into();
16813
16814 drop(buffer);
16815
16816 // Position the selection in the rename editor so that it matches the current selection.
16817 this.show_local_selections = false;
16818 let rename_editor = cx.new(|cx| {
16819 let mut editor = Editor::single_line(window, cx);
16820 editor.buffer.update(cx, |buffer, cx| {
16821 buffer.edit([(0..0, old_name.clone())], None, cx)
16822 });
16823 let rename_selection_range = match cursor_offset_in_rename_range
16824 .cmp(&cursor_offset_in_rename_range_end)
16825 {
16826 Ordering::Equal => {
16827 editor.select_all(&SelectAll, window, cx);
16828 return editor;
16829 }
16830 Ordering::Less => {
16831 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16832 }
16833 Ordering::Greater => {
16834 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16835 }
16836 };
16837 if rename_selection_range.end > old_name.len() {
16838 editor.select_all(&SelectAll, window, cx);
16839 } else {
16840 editor.change_selections(Default::default(), window, cx, |s| {
16841 s.select_ranges([rename_selection_range]);
16842 });
16843 }
16844 editor
16845 });
16846 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16847 if e == &EditorEvent::Focused {
16848 cx.emit(EditorEvent::FocusedIn)
16849 }
16850 })
16851 .detach();
16852
16853 let write_highlights =
16854 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16855 let read_highlights =
16856 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16857 let ranges = write_highlights
16858 .iter()
16859 .flat_map(|(_, ranges)| ranges.iter())
16860 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16861 .cloned()
16862 .collect();
16863
16864 this.highlight_text::<Rename>(
16865 ranges,
16866 HighlightStyle {
16867 fade_out: Some(0.6),
16868 ..Default::default()
16869 },
16870 cx,
16871 );
16872 let rename_focus_handle = rename_editor.focus_handle(cx);
16873 window.focus(&rename_focus_handle);
16874 let block_id = this.insert_blocks(
16875 [BlockProperties {
16876 style: BlockStyle::Flex,
16877 placement: BlockPlacement::Below(range.start),
16878 height: Some(1),
16879 render: Arc::new({
16880 let rename_editor = rename_editor.clone();
16881 move |cx: &mut BlockContext| {
16882 let mut text_style = cx.editor_style.text.clone();
16883 if let Some(highlight_style) = old_highlight_id
16884 .and_then(|h| h.style(&cx.editor_style.syntax))
16885 {
16886 text_style = text_style.highlight(highlight_style);
16887 }
16888 div()
16889 .block_mouse_except_scroll()
16890 .pl(cx.anchor_x)
16891 .child(EditorElement::new(
16892 &rename_editor,
16893 EditorStyle {
16894 background: cx.theme().system().transparent,
16895 local_player: cx.editor_style.local_player,
16896 text: text_style,
16897 scrollbar_width: cx.editor_style.scrollbar_width,
16898 syntax: cx.editor_style.syntax.clone(),
16899 status: cx.editor_style.status.clone(),
16900 inlay_hints_style: HighlightStyle {
16901 font_weight: Some(FontWeight::BOLD),
16902 ..make_inlay_hints_style(cx.app)
16903 },
16904 edit_prediction_styles: make_suggestion_styles(
16905 cx.app,
16906 ),
16907 ..EditorStyle::default()
16908 },
16909 ))
16910 .into_any_element()
16911 }
16912 }),
16913 priority: 0,
16914 }],
16915 Some(Autoscroll::fit()),
16916 cx,
16917 )[0];
16918 this.pending_rename = Some(RenameState {
16919 range,
16920 old_name,
16921 editor: rename_editor,
16922 block_id,
16923 });
16924 })?;
16925 }
16926
16927 Ok(())
16928 }))
16929 }
16930
16931 pub fn confirm_rename(
16932 &mut self,
16933 _: &ConfirmRename,
16934 window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) -> Option<Task<Result<()>>> {
16937 let rename = self.take_rename(false, window, cx)?;
16938 let workspace = self.workspace()?.downgrade();
16939 let (buffer, start) = self
16940 .buffer
16941 .read(cx)
16942 .text_anchor_for_position(rename.range.start, cx)?;
16943 let (end_buffer, _) = self
16944 .buffer
16945 .read(cx)
16946 .text_anchor_for_position(rename.range.end, cx)?;
16947 if buffer != end_buffer {
16948 return None;
16949 }
16950
16951 let old_name = rename.old_name;
16952 let new_name = rename.editor.read(cx).text(cx);
16953
16954 let rename = self.semantics_provider.as_ref()?.perform_rename(
16955 &buffer,
16956 start,
16957 new_name.clone(),
16958 cx,
16959 )?;
16960
16961 Some(cx.spawn_in(window, async move |editor, cx| {
16962 let project_transaction = rename.await?;
16963 Self::open_project_transaction(
16964 &editor,
16965 workspace,
16966 project_transaction,
16967 format!("Rename: {} → {}", old_name, new_name),
16968 cx,
16969 )
16970 .await?;
16971
16972 editor.update(cx, |editor, cx| {
16973 editor.refresh_document_highlights(cx);
16974 })?;
16975 Ok(())
16976 }))
16977 }
16978
16979 fn take_rename(
16980 &mut self,
16981 moving_cursor: bool,
16982 window: &mut Window,
16983 cx: &mut Context<Self>,
16984 ) -> Option<RenameState> {
16985 let rename = self.pending_rename.take()?;
16986 if rename.editor.focus_handle(cx).is_focused(window) {
16987 window.focus(&self.focus_handle);
16988 }
16989
16990 self.remove_blocks(
16991 [rename.block_id].into_iter().collect(),
16992 Some(Autoscroll::fit()),
16993 cx,
16994 );
16995 self.clear_highlights::<Rename>(cx);
16996 self.show_local_selections = true;
16997
16998 if moving_cursor {
16999 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17000 editor.selections.newest::<usize>(cx).head()
17001 });
17002
17003 // Update the selection to match the position of the selection inside
17004 // the rename editor.
17005 let snapshot = self.buffer.read(cx).read(cx);
17006 let rename_range = rename.range.to_offset(&snapshot);
17007 let cursor_in_editor = snapshot
17008 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17009 .min(rename_range.end);
17010 drop(snapshot);
17011
17012 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17013 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17014 });
17015 } else {
17016 self.refresh_document_highlights(cx);
17017 }
17018
17019 Some(rename)
17020 }
17021
17022 pub fn pending_rename(&self) -> Option<&RenameState> {
17023 self.pending_rename.as_ref()
17024 }
17025
17026 fn format(
17027 &mut self,
17028 _: &Format,
17029 window: &mut Window,
17030 cx: &mut Context<Self>,
17031 ) -> Option<Task<Result<()>>> {
17032 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17033
17034 let project = match &self.project {
17035 Some(project) => project.clone(),
17036 None => return None,
17037 };
17038
17039 Some(self.perform_format(
17040 project,
17041 FormatTrigger::Manual,
17042 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17043 window,
17044 cx,
17045 ))
17046 }
17047
17048 fn format_selections(
17049 &mut self,
17050 _: &FormatSelections,
17051 window: &mut Window,
17052 cx: &mut Context<Self>,
17053 ) -> Option<Task<Result<()>>> {
17054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17055
17056 let project = match &self.project {
17057 Some(project) => project.clone(),
17058 None => return None,
17059 };
17060
17061 let ranges = self
17062 .selections
17063 .all_adjusted(cx)
17064 .into_iter()
17065 .map(|selection| selection.range())
17066 .collect_vec();
17067
17068 Some(self.perform_format(
17069 project,
17070 FormatTrigger::Manual,
17071 FormatTarget::Ranges(ranges),
17072 window,
17073 cx,
17074 ))
17075 }
17076
17077 fn perform_format(
17078 &mut self,
17079 project: Entity<Project>,
17080 trigger: FormatTrigger,
17081 target: FormatTarget,
17082 window: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) -> Task<Result<()>> {
17085 let buffer = self.buffer.clone();
17086 let (buffers, target) = match target {
17087 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17088 FormatTarget::Ranges(selection_ranges) => {
17089 let multi_buffer = buffer.read(cx);
17090 let snapshot = multi_buffer.read(cx);
17091 let mut buffers = HashSet::default();
17092 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17093 BTreeMap::new();
17094 for selection_range in selection_ranges {
17095 for (buffer, buffer_range, _) in
17096 snapshot.range_to_buffer_ranges(selection_range)
17097 {
17098 let buffer_id = buffer.remote_id();
17099 let start = buffer.anchor_before(buffer_range.start);
17100 let end = buffer.anchor_after(buffer_range.end);
17101 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17102 buffer_id_to_ranges
17103 .entry(buffer_id)
17104 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17105 .or_insert_with(|| vec![start..end]);
17106 }
17107 }
17108 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17109 }
17110 };
17111
17112 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17113 let selections_prev = transaction_id_prev
17114 .and_then(|transaction_id_prev| {
17115 // default to selections as they were after the last edit, if we have them,
17116 // instead of how they are now.
17117 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17118 // will take you back to where you made the last edit, instead of staying where you scrolled
17119 self.selection_history
17120 .transaction(transaction_id_prev)
17121 .map(|t| t.0.clone())
17122 })
17123 .unwrap_or_else(|| self.selections.disjoint_anchors());
17124
17125 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17126 let format = project.update(cx, |project, cx| {
17127 project.format(buffers, target, true, trigger, cx)
17128 });
17129
17130 cx.spawn_in(window, async move |editor, cx| {
17131 let transaction = futures::select_biased! {
17132 transaction = format.log_err().fuse() => transaction,
17133 () = timeout => {
17134 log::warn!("timed out waiting for formatting");
17135 None
17136 }
17137 };
17138
17139 buffer
17140 .update(cx, |buffer, cx| {
17141 if let Some(transaction) = transaction
17142 && !buffer.is_singleton()
17143 {
17144 buffer.push_transaction(&transaction.0, cx);
17145 }
17146 cx.notify();
17147 })
17148 .ok();
17149
17150 if let Some(transaction_id_now) =
17151 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17152 {
17153 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17154 if has_new_transaction {
17155 _ = editor.update(cx, |editor, _| {
17156 editor
17157 .selection_history
17158 .insert_transaction(transaction_id_now, selections_prev);
17159 });
17160 }
17161 }
17162
17163 Ok(())
17164 })
17165 }
17166
17167 fn organize_imports(
17168 &mut self,
17169 _: &OrganizeImports,
17170 window: &mut Window,
17171 cx: &mut Context<Self>,
17172 ) -> Option<Task<Result<()>>> {
17173 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17174 let project = match &self.project {
17175 Some(project) => project.clone(),
17176 None => return None,
17177 };
17178 Some(self.perform_code_action_kind(
17179 project,
17180 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17181 window,
17182 cx,
17183 ))
17184 }
17185
17186 fn perform_code_action_kind(
17187 &mut self,
17188 project: Entity<Project>,
17189 kind: CodeActionKind,
17190 window: &mut Window,
17191 cx: &mut Context<Self>,
17192 ) -> Task<Result<()>> {
17193 let buffer = self.buffer.clone();
17194 let buffers = buffer.read(cx).all_buffers();
17195 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17196 let apply_action = project.update(cx, |project, cx| {
17197 project.apply_code_action_kind(buffers, kind, true, cx)
17198 });
17199 cx.spawn_in(window, async move |_, cx| {
17200 let transaction = futures::select_biased! {
17201 () = timeout => {
17202 log::warn!("timed out waiting for executing code action");
17203 None
17204 }
17205 transaction = apply_action.log_err().fuse() => transaction,
17206 };
17207 buffer
17208 .update(cx, |buffer, cx| {
17209 // check if we need this
17210 if let Some(transaction) = transaction
17211 && !buffer.is_singleton()
17212 {
17213 buffer.push_transaction(&transaction.0, cx);
17214 }
17215 cx.notify();
17216 })
17217 .ok();
17218 Ok(())
17219 })
17220 }
17221
17222 pub fn restart_language_server(
17223 &mut self,
17224 _: &RestartLanguageServer,
17225 _: &mut Window,
17226 cx: &mut Context<Self>,
17227 ) {
17228 if let Some(project) = self.project.clone() {
17229 self.buffer.update(cx, |multi_buffer, cx| {
17230 project.update(cx, |project, cx| {
17231 project.restart_language_servers_for_buffers(
17232 multi_buffer.all_buffers().into_iter().collect(),
17233 HashSet::default(),
17234 cx,
17235 );
17236 });
17237 })
17238 }
17239 }
17240
17241 pub fn stop_language_server(
17242 &mut self,
17243 _: &StopLanguageServer,
17244 _: &mut Window,
17245 cx: &mut Context<Self>,
17246 ) {
17247 if let Some(project) = self.project.clone() {
17248 self.buffer.update(cx, |multi_buffer, cx| {
17249 project.update(cx, |project, cx| {
17250 project.stop_language_servers_for_buffers(
17251 multi_buffer.all_buffers().into_iter().collect(),
17252 HashSet::default(),
17253 cx,
17254 );
17255 cx.emit(project::Event::RefreshInlayHints);
17256 });
17257 });
17258 }
17259 }
17260
17261 fn cancel_language_server_work(
17262 workspace: &mut Workspace,
17263 _: &actions::CancelLanguageServerWork,
17264 _: &mut Window,
17265 cx: &mut Context<Workspace>,
17266 ) {
17267 let project = workspace.project();
17268 let buffers = workspace
17269 .active_item(cx)
17270 .and_then(|item| item.act_as::<Editor>(cx))
17271 .map_or(HashSet::default(), |editor| {
17272 editor.read(cx).buffer.read(cx).all_buffers()
17273 });
17274 project.update(cx, |project, cx| {
17275 project.cancel_language_server_work_for_buffers(buffers, cx);
17276 });
17277 }
17278
17279 fn show_character_palette(
17280 &mut self,
17281 _: &ShowCharacterPalette,
17282 window: &mut Window,
17283 _: &mut Context<Self>,
17284 ) {
17285 window.show_character_palette();
17286 }
17287
17288 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17289 if !self.diagnostics_enabled() {
17290 return;
17291 }
17292
17293 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17294 let buffer = self.buffer.read(cx).snapshot(cx);
17295 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17296 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17297 let is_valid = buffer
17298 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17299 .any(|entry| {
17300 entry.diagnostic.is_primary
17301 && !entry.range.is_empty()
17302 && entry.range.start == primary_range_start
17303 && entry.diagnostic.message == active_diagnostics.active_message
17304 });
17305
17306 if !is_valid {
17307 self.dismiss_diagnostics(cx);
17308 }
17309 }
17310 }
17311
17312 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17313 match &self.active_diagnostics {
17314 ActiveDiagnostic::Group(group) => Some(group),
17315 _ => None,
17316 }
17317 }
17318
17319 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17320 if !self.diagnostics_enabled() {
17321 return;
17322 }
17323 self.dismiss_diagnostics(cx);
17324 self.active_diagnostics = ActiveDiagnostic::All;
17325 }
17326
17327 fn activate_diagnostics(
17328 &mut self,
17329 buffer_id: BufferId,
17330 diagnostic: DiagnosticEntry<usize>,
17331 window: &mut Window,
17332 cx: &mut Context<Self>,
17333 ) {
17334 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17335 return;
17336 }
17337 self.dismiss_diagnostics(cx);
17338 let snapshot = self.snapshot(window, cx);
17339 let buffer = self.buffer.read(cx).snapshot(cx);
17340 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17341 return;
17342 };
17343
17344 let diagnostic_group = buffer
17345 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17346 .collect::<Vec<_>>();
17347
17348 let blocks =
17349 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17350
17351 let blocks = self.display_map.update(cx, |display_map, cx| {
17352 display_map.insert_blocks(blocks, cx).into_iter().collect()
17353 });
17354 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17355 active_range: buffer.anchor_before(diagnostic.range.start)
17356 ..buffer.anchor_after(diagnostic.range.end),
17357 active_message: diagnostic.diagnostic.message.clone(),
17358 group_id: diagnostic.diagnostic.group_id,
17359 blocks,
17360 });
17361 cx.notify();
17362 }
17363
17364 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17365 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17366 return;
17367 };
17368
17369 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17370 if let ActiveDiagnostic::Group(group) = prev {
17371 self.display_map.update(cx, |display_map, cx| {
17372 display_map.remove_blocks(group.blocks, cx);
17373 });
17374 cx.notify();
17375 }
17376 }
17377
17378 /// Disable inline diagnostics rendering for this editor.
17379 pub fn disable_inline_diagnostics(&mut self) {
17380 self.inline_diagnostics_enabled = false;
17381 self.inline_diagnostics_update = Task::ready(());
17382 self.inline_diagnostics.clear();
17383 }
17384
17385 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17386 self.diagnostics_enabled = false;
17387 self.dismiss_diagnostics(cx);
17388 self.inline_diagnostics_update = Task::ready(());
17389 self.inline_diagnostics.clear();
17390 }
17391
17392 pub fn disable_word_completions(&mut self) {
17393 self.word_completions_enabled = false;
17394 }
17395
17396 pub fn diagnostics_enabled(&self) -> bool {
17397 self.diagnostics_enabled && self.mode.is_full()
17398 }
17399
17400 pub fn inline_diagnostics_enabled(&self) -> bool {
17401 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17402 }
17403
17404 pub fn show_inline_diagnostics(&self) -> bool {
17405 self.show_inline_diagnostics
17406 }
17407
17408 pub fn toggle_inline_diagnostics(
17409 &mut self,
17410 _: &ToggleInlineDiagnostics,
17411 window: &mut Window,
17412 cx: &mut Context<Editor>,
17413 ) {
17414 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17415 self.refresh_inline_diagnostics(false, window, cx);
17416 }
17417
17418 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17419 self.diagnostics_max_severity = severity;
17420 self.display_map.update(cx, |display_map, _| {
17421 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17422 });
17423 }
17424
17425 pub fn toggle_diagnostics(
17426 &mut self,
17427 _: &ToggleDiagnostics,
17428 window: &mut Window,
17429 cx: &mut Context<Editor>,
17430 ) {
17431 if !self.diagnostics_enabled() {
17432 return;
17433 }
17434
17435 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17436 EditorSettings::get_global(cx)
17437 .diagnostics_max_severity
17438 .filter(|severity| severity != &DiagnosticSeverity::Off)
17439 .unwrap_or(DiagnosticSeverity::Hint)
17440 } else {
17441 DiagnosticSeverity::Off
17442 };
17443 self.set_max_diagnostics_severity(new_severity, cx);
17444 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17445 self.active_diagnostics = ActiveDiagnostic::None;
17446 self.inline_diagnostics_update = Task::ready(());
17447 self.inline_diagnostics.clear();
17448 } else {
17449 self.refresh_inline_diagnostics(false, window, cx);
17450 }
17451
17452 cx.notify();
17453 }
17454
17455 pub fn toggle_minimap(
17456 &mut self,
17457 _: &ToggleMinimap,
17458 window: &mut Window,
17459 cx: &mut Context<Editor>,
17460 ) {
17461 if self.supports_minimap(cx) {
17462 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17463 }
17464 }
17465
17466 fn refresh_inline_diagnostics(
17467 &mut self,
17468 debounce: bool,
17469 window: &mut Window,
17470 cx: &mut Context<Self>,
17471 ) {
17472 let max_severity = ProjectSettings::get_global(cx)
17473 .diagnostics
17474 .inline
17475 .max_severity
17476 .unwrap_or(self.diagnostics_max_severity);
17477
17478 if !self.inline_diagnostics_enabled()
17479 || !self.show_inline_diagnostics
17480 || max_severity == DiagnosticSeverity::Off
17481 {
17482 self.inline_diagnostics_update = Task::ready(());
17483 self.inline_diagnostics.clear();
17484 return;
17485 }
17486
17487 let debounce_ms = ProjectSettings::get_global(cx)
17488 .diagnostics
17489 .inline
17490 .update_debounce_ms;
17491 let debounce = if debounce && debounce_ms > 0 {
17492 Some(Duration::from_millis(debounce_ms))
17493 } else {
17494 None
17495 };
17496 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17497 if let Some(debounce) = debounce {
17498 cx.background_executor().timer(debounce).await;
17499 }
17500 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17501 editor
17502 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17503 .ok()
17504 }) else {
17505 return;
17506 };
17507
17508 let new_inline_diagnostics = cx
17509 .background_spawn(async move {
17510 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17511 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17512 let message = diagnostic_entry
17513 .diagnostic
17514 .message
17515 .split_once('\n')
17516 .map(|(line, _)| line)
17517 .map(SharedString::new)
17518 .unwrap_or_else(|| {
17519 SharedString::from(diagnostic_entry.diagnostic.message)
17520 });
17521 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17522 let (Ok(i) | Err(i)) = inline_diagnostics
17523 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17524 inline_diagnostics.insert(
17525 i,
17526 (
17527 start_anchor,
17528 InlineDiagnostic {
17529 message,
17530 group_id: diagnostic_entry.diagnostic.group_id,
17531 start: diagnostic_entry.range.start.to_point(&snapshot),
17532 is_primary: diagnostic_entry.diagnostic.is_primary,
17533 severity: diagnostic_entry.diagnostic.severity,
17534 },
17535 ),
17536 );
17537 }
17538 inline_diagnostics
17539 })
17540 .await;
17541
17542 editor
17543 .update(cx, |editor, cx| {
17544 editor.inline_diagnostics = new_inline_diagnostics;
17545 cx.notify();
17546 })
17547 .ok();
17548 });
17549 }
17550
17551 fn pull_diagnostics(
17552 &mut self,
17553 buffer_id: Option<BufferId>,
17554 window: &Window,
17555 cx: &mut Context<Self>,
17556 ) -> Option<()> {
17557 if !self.mode().is_full() {
17558 return None;
17559 }
17560 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17561 .diagnostics
17562 .lsp_pull_diagnostics;
17563 if !pull_diagnostics_settings.enabled {
17564 return None;
17565 }
17566 let project = self.project()?.downgrade();
17567 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17568 let mut buffers = self.buffer.read(cx).all_buffers();
17569 if let Some(buffer_id) = buffer_id {
17570 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17571 }
17572
17573 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17574 cx.background_executor().timer(debounce).await;
17575
17576 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17577 buffers
17578 .into_iter()
17579 .filter_map(|buffer| {
17580 project
17581 .update(cx, |project, cx| {
17582 project.lsp_store().update(cx, |lsp_store, cx| {
17583 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17584 })
17585 })
17586 .ok()
17587 })
17588 .collect::<FuturesUnordered<_>>()
17589 }) else {
17590 return;
17591 };
17592
17593 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17594 match pull_task {
17595 Ok(()) => {
17596 if editor
17597 .update_in(cx, |editor, window, cx| {
17598 editor.update_diagnostics_state(window, cx);
17599 })
17600 .is_err()
17601 {
17602 return;
17603 }
17604 }
17605 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17606 }
17607 }
17608 });
17609
17610 Some(())
17611 }
17612
17613 pub fn set_selections_from_remote(
17614 &mut self,
17615 selections: Vec<Selection<Anchor>>,
17616 pending_selection: Option<Selection<Anchor>>,
17617 window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 let old_cursor_position = self.selections.newest_anchor().head();
17621 self.selections.change_with(cx, |s| {
17622 s.select_anchors(selections);
17623 if let Some(pending_selection) = pending_selection {
17624 s.set_pending(pending_selection, SelectMode::Character);
17625 } else {
17626 s.clear_pending();
17627 }
17628 });
17629 self.selections_did_change(
17630 false,
17631 &old_cursor_position,
17632 SelectionEffects::default(),
17633 window,
17634 cx,
17635 );
17636 }
17637
17638 pub fn transact(
17639 &mut self,
17640 window: &mut Window,
17641 cx: &mut Context<Self>,
17642 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17643 ) -> Option<TransactionId> {
17644 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17645 this.start_transaction_at(Instant::now(), window, cx);
17646 update(this, window, cx);
17647 this.end_transaction_at(Instant::now(), cx)
17648 })
17649 }
17650
17651 pub fn start_transaction_at(
17652 &mut self,
17653 now: Instant,
17654 window: &mut Window,
17655 cx: &mut Context<Self>,
17656 ) -> Option<TransactionId> {
17657 self.end_selection(window, cx);
17658 if let Some(tx_id) = self
17659 .buffer
17660 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17661 {
17662 self.selection_history
17663 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17664 cx.emit(EditorEvent::TransactionBegun {
17665 transaction_id: tx_id,
17666 });
17667 Some(tx_id)
17668 } else {
17669 None
17670 }
17671 }
17672
17673 pub fn end_transaction_at(
17674 &mut self,
17675 now: Instant,
17676 cx: &mut Context<Self>,
17677 ) -> Option<TransactionId> {
17678 if let Some(transaction_id) = self
17679 .buffer
17680 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17681 {
17682 if let Some((_, end_selections)) =
17683 self.selection_history.transaction_mut(transaction_id)
17684 {
17685 *end_selections = Some(self.selections.disjoint_anchors());
17686 } else {
17687 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17688 }
17689
17690 cx.emit(EditorEvent::Edited { transaction_id });
17691 Some(transaction_id)
17692 } else {
17693 None
17694 }
17695 }
17696
17697 pub fn modify_transaction_selection_history(
17698 &mut self,
17699 transaction_id: TransactionId,
17700 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17701 ) -> bool {
17702 self.selection_history
17703 .transaction_mut(transaction_id)
17704 .map(modify)
17705 .is_some()
17706 }
17707
17708 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17709 if self.selection_mark_mode {
17710 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17711 s.move_with(|_, sel| {
17712 sel.collapse_to(sel.head(), SelectionGoal::None);
17713 });
17714 })
17715 }
17716 self.selection_mark_mode = true;
17717 cx.notify();
17718 }
17719
17720 pub fn swap_selection_ends(
17721 &mut self,
17722 _: &actions::SwapSelectionEnds,
17723 window: &mut Window,
17724 cx: &mut Context<Self>,
17725 ) {
17726 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17727 s.move_with(|_, sel| {
17728 if sel.start != sel.end {
17729 sel.reversed = !sel.reversed
17730 }
17731 });
17732 });
17733 self.request_autoscroll(Autoscroll::newest(), cx);
17734 cx.notify();
17735 }
17736
17737 pub fn toggle_focus(
17738 workspace: &mut Workspace,
17739 _: &actions::ToggleFocus,
17740 window: &mut Window,
17741 cx: &mut Context<Workspace>,
17742 ) {
17743 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17744 return;
17745 };
17746 workspace.activate_item(&item, true, true, window, cx);
17747 }
17748
17749 pub fn toggle_fold(
17750 &mut self,
17751 _: &actions::ToggleFold,
17752 window: &mut Window,
17753 cx: &mut Context<Self>,
17754 ) {
17755 if self.is_singleton(cx) {
17756 let selection = self.selections.newest::<Point>(cx);
17757
17758 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17759 let range = if selection.is_empty() {
17760 let point = selection.head().to_display_point(&display_map);
17761 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17762 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17763 .to_point(&display_map);
17764 start..end
17765 } else {
17766 selection.range()
17767 };
17768 if display_map.folds_in_range(range).next().is_some() {
17769 self.unfold_lines(&Default::default(), window, cx)
17770 } else {
17771 self.fold(&Default::default(), window, cx)
17772 }
17773 } else {
17774 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17775 let buffer_ids: HashSet<_> = self
17776 .selections
17777 .disjoint_anchor_ranges()
17778 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17779 .collect();
17780
17781 let should_unfold = buffer_ids
17782 .iter()
17783 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17784
17785 for buffer_id in buffer_ids {
17786 if should_unfold {
17787 self.unfold_buffer(buffer_id, cx);
17788 } else {
17789 self.fold_buffer(buffer_id, cx);
17790 }
17791 }
17792 }
17793 }
17794
17795 pub fn toggle_fold_recursive(
17796 &mut self,
17797 _: &actions::ToggleFoldRecursive,
17798 window: &mut Window,
17799 cx: &mut Context<Self>,
17800 ) {
17801 let selection = self.selections.newest::<Point>(cx);
17802
17803 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17804 let range = if selection.is_empty() {
17805 let point = selection.head().to_display_point(&display_map);
17806 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17807 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17808 .to_point(&display_map);
17809 start..end
17810 } else {
17811 selection.range()
17812 };
17813 if display_map.folds_in_range(range).next().is_some() {
17814 self.unfold_recursive(&Default::default(), window, cx)
17815 } else {
17816 self.fold_recursive(&Default::default(), window, cx)
17817 }
17818 }
17819
17820 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17821 if self.is_singleton(cx) {
17822 let mut to_fold = Vec::new();
17823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17824 let selections = self.selections.all_adjusted(cx);
17825
17826 for selection in selections {
17827 let range = selection.range().sorted();
17828 let buffer_start_row = range.start.row;
17829
17830 if range.start.row != range.end.row {
17831 let mut found = false;
17832 let mut row = range.start.row;
17833 while row <= range.end.row {
17834 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17835 {
17836 found = true;
17837 row = crease.range().end.row + 1;
17838 to_fold.push(crease);
17839 } else {
17840 row += 1
17841 }
17842 }
17843 if found {
17844 continue;
17845 }
17846 }
17847
17848 for row in (0..=range.start.row).rev() {
17849 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17850 && crease.range().end.row >= buffer_start_row
17851 {
17852 to_fold.push(crease);
17853 if row <= range.start.row {
17854 break;
17855 }
17856 }
17857 }
17858 }
17859
17860 self.fold_creases(to_fold, true, window, cx);
17861 } else {
17862 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17863 let buffer_ids = self
17864 .selections
17865 .disjoint_anchor_ranges()
17866 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17867 .collect::<HashSet<_>>();
17868 for buffer_id in buffer_ids {
17869 self.fold_buffer(buffer_id, cx);
17870 }
17871 }
17872 }
17873
17874 pub fn toggle_fold_all(
17875 &mut self,
17876 _: &actions::ToggleFoldAll,
17877 window: &mut Window,
17878 cx: &mut Context<Self>,
17879 ) {
17880 if self.buffer.read(cx).is_singleton() {
17881 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17882 let has_folds = display_map
17883 .folds_in_range(0..display_map.buffer_snapshot.len())
17884 .next()
17885 .is_some();
17886
17887 if has_folds {
17888 self.unfold_all(&actions::UnfoldAll, window, cx);
17889 } else {
17890 self.fold_all(&actions::FoldAll, window, cx);
17891 }
17892 } else {
17893 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17894 let should_unfold = buffer_ids
17895 .iter()
17896 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17897
17898 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17899 editor
17900 .update_in(cx, |editor, _, cx| {
17901 for buffer_id in buffer_ids {
17902 if should_unfold {
17903 editor.unfold_buffer(buffer_id, cx);
17904 } else {
17905 editor.fold_buffer(buffer_id, cx);
17906 }
17907 }
17908 })
17909 .ok();
17910 });
17911 }
17912 }
17913
17914 fn fold_at_level(
17915 &mut self,
17916 fold_at: &FoldAtLevel,
17917 window: &mut Window,
17918 cx: &mut Context<Self>,
17919 ) {
17920 if !self.buffer.read(cx).is_singleton() {
17921 return;
17922 }
17923
17924 let fold_at_level = fold_at.0;
17925 let snapshot = self.buffer.read(cx).snapshot(cx);
17926 let mut to_fold = Vec::new();
17927 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17928
17929 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17930 while start_row < end_row {
17931 match self
17932 .snapshot(window, cx)
17933 .crease_for_buffer_row(MultiBufferRow(start_row))
17934 {
17935 Some(crease) => {
17936 let nested_start_row = crease.range().start.row + 1;
17937 let nested_end_row = crease.range().end.row;
17938
17939 if current_level < fold_at_level {
17940 stack.push((nested_start_row, nested_end_row, current_level + 1));
17941 } else if current_level == fold_at_level {
17942 to_fold.push(crease);
17943 }
17944
17945 start_row = nested_end_row + 1;
17946 }
17947 None => start_row += 1,
17948 }
17949 }
17950 }
17951
17952 self.fold_creases(to_fold, true, window, cx);
17953 }
17954
17955 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17956 if self.buffer.read(cx).is_singleton() {
17957 let mut fold_ranges = Vec::new();
17958 let snapshot = self.buffer.read(cx).snapshot(cx);
17959
17960 for row in 0..snapshot.max_row().0 {
17961 if let Some(foldable_range) = self
17962 .snapshot(window, cx)
17963 .crease_for_buffer_row(MultiBufferRow(row))
17964 {
17965 fold_ranges.push(foldable_range);
17966 }
17967 }
17968
17969 self.fold_creases(fold_ranges, true, window, cx);
17970 } else {
17971 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17972 editor
17973 .update_in(cx, |editor, _, cx| {
17974 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17975 editor.fold_buffer(buffer_id, cx);
17976 }
17977 })
17978 .ok();
17979 });
17980 }
17981 }
17982
17983 pub fn fold_function_bodies(
17984 &mut self,
17985 _: &actions::FoldFunctionBodies,
17986 window: &mut Window,
17987 cx: &mut Context<Self>,
17988 ) {
17989 let snapshot = self.buffer.read(cx).snapshot(cx);
17990
17991 let ranges = snapshot
17992 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17993 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17994 .collect::<Vec<_>>();
17995
17996 let creases = ranges
17997 .into_iter()
17998 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17999 .collect();
18000
18001 self.fold_creases(creases, true, window, cx);
18002 }
18003
18004 pub fn fold_recursive(
18005 &mut self,
18006 _: &actions::FoldRecursive,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) {
18010 let mut to_fold = Vec::new();
18011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18012 let selections = self.selections.all_adjusted(cx);
18013
18014 for selection in selections {
18015 let range = selection.range().sorted();
18016 let buffer_start_row = range.start.row;
18017
18018 if range.start.row != range.end.row {
18019 let mut found = false;
18020 for row in range.start.row..=range.end.row {
18021 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18022 found = true;
18023 to_fold.push(crease);
18024 }
18025 }
18026 if found {
18027 continue;
18028 }
18029 }
18030
18031 for row in (0..=range.start.row).rev() {
18032 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18033 if crease.range().end.row >= buffer_start_row {
18034 to_fold.push(crease);
18035 } else {
18036 break;
18037 }
18038 }
18039 }
18040 }
18041
18042 self.fold_creases(to_fold, true, window, cx);
18043 }
18044
18045 pub fn fold_at(
18046 &mut self,
18047 buffer_row: MultiBufferRow,
18048 window: &mut Window,
18049 cx: &mut Context<Self>,
18050 ) {
18051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18052
18053 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18054 let autoscroll = self
18055 .selections
18056 .all::<Point>(cx)
18057 .iter()
18058 .any(|selection| crease.range().overlaps(&selection.range()));
18059
18060 self.fold_creases(vec![crease], autoscroll, window, cx);
18061 }
18062 }
18063
18064 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18065 if self.is_singleton(cx) {
18066 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18067 let buffer = &display_map.buffer_snapshot;
18068 let selections = self.selections.all::<Point>(cx);
18069 let ranges = selections
18070 .iter()
18071 .map(|s| {
18072 let range = s.display_range(&display_map).sorted();
18073 let mut start = range.start.to_point(&display_map);
18074 let mut end = range.end.to_point(&display_map);
18075 start.column = 0;
18076 end.column = buffer.line_len(MultiBufferRow(end.row));
18077 start..end
18078 })
18079 .collect::<Vec<_>>();
18080
18081 self.unfold_ranges(&ranges, true, true, cx);
18082 } else {
18083 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18084 let buffer_ids = self
18085 .selections
18086 .disjoint_anchor_ranges()
18087 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18088 .collect::<HashSet<_>>();
18089 for buffer_id in buffer_ids {
18090 self.unfold_buffer(buffer_id, cx);
18091 }
18092 }
18093 }
18094
18095 pub fn unfold_recursive(
18096 &mut self,
18097 _: &UnfoldRecursive,
18098 _window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18102 let selections = self.selections.all::<Point>(cx);
18103 let ranges = selections
18104 .iter()
18105 .map(|s| {
18106 let mut range = s.display_range(&display_map).sorted();
18107 *range.start.column_mut() = 0;
18108 *range.end.column_mut() = display_map.line_len(range.end.row());
18109 let start = range.start.to_point(&display_map);
18110 let end = range.end.to_point(&display_map);
18111 start..end
18112 })
18113 .collect::<Vec<_>>();
18114
18115 self.unfold_ranges(&ranges, true, true, cx);
18116 }
18117
18118 pub fn unfold_at(
18119 &mut self,
18120 buffer_row: MultiBufferRow,
18121 _window: &mut Window,
18122 cx: &mut Context<Self>,
18123 ) {
18124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18125
18126 let intersection_range = Point::new(buffer_row.0, 0)
18127 ..Point::new(
18128 buffer_row.0,
18129 display_map.buffer_snapshot.line_len(buffer_row),
18130 );
18131
18132 let autoscroll = self
18133 .selections
18134 .all::<Point>(cx)
18135 .iter()
18136 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18137
18138 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18139 }
18140
18141 pub fn unfold_all(
18142 &mut self,
18143 _: &actions::UnfoldAll,
18144 _window: &mut Window,
18145 cx: &mut Context<Self>,
18146 ) {
18147 if self.buffer.read(cx).is_singleton() {
18148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18149 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18150 } else {
18151 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18152 editor
18153 .update(cx, |editor, cx| {
18154 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18155 editor.unfold_buffer(buffer_id, cx);
18156 }
18157 })
18158 .ok();
18159 });
18160 }
18161 }
18162
18163 pub fn fold_selected_ranges(
18164 &mut self,
18165 _: &FoldSelectedRanges,
18166 window: &mut Window,
18167 cx: &mut Context<Self>,
18168 ) {
18169 let selections = self.selections.all_adjusted(cx);
18170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18171 let ranges = selections
18172 .into_iter()
18173 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18174 .collect::<Vec<_>>();
18175 self.fold_creases(ranges, true, window, cx);
18176 }
18177
18178 pub fn fold_ranges<T: ToOffset + Clone>(
18179 &mut self,
18180 ranges: Vec<Range<T>>,
18181 auto_scroll: bool,
18182 window: &mut Window,
18183 cx: &mut Context<Self>,
18184 ) {
18185 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18186 let ranges = ranges
18187 .into_iter()
18188 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18189 .collect::<Vec<_>>();
18190 self.fold_creases(ranges, auto_scroll, window, cx);
18191 }
18192
18193 pub fn fold_creases<T: ToOffset + Clone>(
18194 &mut self,
18195 creases: Vec<Crease<T>>,
18196 auto_scroll: bool,
18197 _window: &mut Window,
18198 cx: &mut Context<Self>,
18199 ) {
18200 if creases.is_empty() {
18201 return;
18202 }
18203
18204 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18205
18206 if auto_scroll {
18207 self.request_autoscroll(Autoscroll::fit(), cx);
18208 }
18209
18210 cx.notify();
18211
18212 self.scrollbar_marker_state.dirty = true;
18213 self.folds_did_change(cx);
18214 }
18215
18216 /// Removes any folds whose ranges intersect any of the given ranges.
18217 pub fn unfold_ranges<T: ToOffset + Clone>(
18218 &mut self,
18219 ranges: &[Range<T>],
18220 inclusive: bool,
18221 auto_scroll: bool,
18222 cx: &mut Context<Self>,
18223 ) {
18224 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18225 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18226 });
18227 self.folds_did_change(cx);
18228 }
18229
18230 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18231 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18232 return;
18233 }
18234 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18235 self.display_map.update(cx, |display_map, cx| {
18236 display_map.fold_buffers([buffer_id], cx)
18237 });
18238 cx.emit(EditorEvent::BufferFoldToggled {
18239 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18240 folded: true,
18241 });
18242 cx.notify();
18243 }
18244
18245 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18246 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18247 return;
18248 }
18249 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18250 self.display_map.update(cx, |display_map, cx| {
18251 display_map.unfold_buffers([buffer_id], cx);
18252 });
18253 cx.emit(EditorEvent::BufferFoldToggled {
18254 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18255 folded: false,
18256 });
18257 cx.notify();
18258 }
18259
18260 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18261 self.display_map.read(cx).is_buffer_folded(buffer)
18262 }
18263
18264 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18265 self.display_map.read(cx).folded_buffers()
18266 }
18267
18268 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18269 self.display_map.update(cx, |display_map, cx| {
18270 display_map.disable_header_for_buffer(buffer_id, cx);
18271 });
18272 cx.notify();
18273 }
18274
18275 /// Removes any folds with the given ranges.
18276 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18277 &mut self,
18278 ranges: &[Range<T>],
18279 type_id: TypeId,
18280 auto_scroll: bool,
18281 cx: &mut Context<Self>,
18282 ) {
18283 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18284 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18285 });
18286 self.folds_did_change(cx);
18287 }
18288
18289 fn remove_folds_with<T: ToOffset + Clone>(
18290 &mut self,
18291 ranges: &[Range<T>],
18292 auto_scroll: bool,
18293 cx: &mut Context<Self>,
18294 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18295 ) {
18296 if ranges.is_empty() {
18297 return;
18298 }
18299
18300 let mut buffers_affected = HashSet::default();
18301 let multi_buffer = self.buffer().read(cx);
18302 for range in ranges {
18303 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18304 buffers_affected.insert(buffer.read(cx).remote_id());
18305 };
18306 }
18307
18308 self.display_map.update(cx, update);
18309
18310 if auto_scroll {
18311 self.request_autoscroll(Autoscroll::fit(), cx);
18312 }
18313
18314 cx.notify();
18315 self.scrollbar_marker_state.dirty = true;
18316 self.active_indent_guides_state.dirty = true;
18317 }
18318
18319 pub fn update_renderer_widths(
18320 &mut self,
18321 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18322 cx: &mut Context<Self>,
18323 ) -> bool {
18324 self.display_map
18325 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18326 }
18327
18328 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18329 self.display_map.read(cx).fold_placeholder.clone()
18330 }
18331
18332 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18333 self.buffer.update(cx, |buffer, cx| {
18334 buffer.set_all_diff_hunks_expanded(cx);
18335 });
18336 }
18337
18338 pub fn expand_all_diff_hunks(
18339 &mut self,
18340 _: &ExpandAllDiffHunks,
18341 _window: &mut Window,
18342 cx: &mut Context<Self>,
18343 ) {
18344 self.buffer.update(cx, |buffer, cx| {
18345 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18346 });
18347 }
18348
18349 pub fn toggle_selected_diff_hunks(
18350 &mut self,
18351 _: &ToggleSelectedDiffHunks,
18352 _window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) {
18355 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18356 self.toggle_diff_hunks_in_ranges(ranges, cx);
18357 }
18358
18359 pub fn diff_hunks_in_ranges<'a>(
18360 &'a self,
18361 ranges: &'a [Range<Anchor>],
18362 buffer: &'a MultiBufferSnapshot,
18363 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18364 ranges.iter().flat_map(move |range| {
18365 let end_excerpt_id = range.end.excerpt_id;
18366 let range = range.to_point(buffer);
18367 let mut peek_end = range.end;
18368 if range.end.row < buffer.max_row().0 {
18369 peek_end = Point::new(range.end.row + 1, 0);
18370 }
18371 buffer
18372 .diff_hunks_in_range(range.start..peek_end)
18373 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18374 })
18375 }
18376
18377 pub fn has_stageable_diff_hunks_in_ranges(
18378 &self,
18379 ranges: &[Range<Anchor>],
18380 snapshot: &MultiBufferSnapshot,
18381 ) -> bool {
18382 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18383 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18384 }
18385
18386 pub fn toggle_staged_selected_diff_hunks(
18387 &mut self,
18388 _: &::git::ToggleStaged,
18389 _: &mut Window,
18390 cx: &mut Context<Self>,
18391 ) {
18392 let snapshot = self.buffer.read(cx).snapshot(cx);
18393 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18394 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18395 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18396 }
18397
18398 pub fn set_render_diff_hunk_controls(
18399 &mut self,
18400 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18401 cx: &mut Context<Self>,
18402 ) {
18403 self.render_diff_hunk_controls = render_diff_hunk_controls;
18404 cx.notify();
18405 }
18406
18407 pub fn stage_and_next(
18408 &mut self,
18409 _: &::git::StageAndNext,
18410 window: &mut Window,
18411 cx: &mut Context<Self>,
18412 ) {
18413 self.do_stage_or_unstage_and_next(true, window, cx);
18414 }
18415
18416 pub fn unstage_and_next(
18417 &mut self,
18418 _: &::git::UnstageAndNext,
18419 window: &mut Window,
18420 cx: &mut Context<Self>,
18421 ) {
18422 self.do_stage_or_unstage_and_next(false, window, cx);
18423 }
18424
18425 pub fn stage_or_unstage_diff_hunks(
18426 &mut self,
18427 stage: bool,
18428 ranges: Vec<Range<Anchor>>,
18429 cx: &mut Context<Self>,
18430 ) {
18431 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18432 cx.spawn(async move |this, cx| {
18433 task.await?;
18434 this.update(cx, |this, cx| {
18435 let snapshot = this.buffer.read(cx).snapshot(cx);
18436 let chunk_by = this
18437 .diff_hunks_in_ranges(&ranges, &snapshot)
18438 .chunk_by(|hunk| hunk.buffer_id);
18439 for (buffer_id, hunks) in &chunk_by {
18440 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18441 }
18442 })
18443 })
18444 .detach_and_log_err(cx);
18445 }
18446
18447 fn save_buffers_for_ranges_if_needed(
18448 &mut self,
18449 ranges: &[Range<Anchor>],
18450 cx: &mut Context<Editor>,
18451 ) -> Task<Result<()>> {
18452 let multibuffer = self.buffer.read(cx);
18453 let snapshot = multibuffer.read(cx);
18454 let buffer_ids: HashSet<_> = ranges
18455 .iter()
18456 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18457 .collect();
18458 drop(snapshot);
18459
18460 let mut buffers = HashSet::default();
18461 for buffer_id in buffer_ids {
18462 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18463 let buffer = buffer_entity.read(cx);
18464 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18465 {
18466 buffers.insert(buffer_entity);
18467 }
18468 }
18469 }
18470
18471 if let Some(project) = &self.project {
18472 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18473 } else {
18474 Task::ready(Ok(()))
18475 }
18476 }
18477
18478 fn do_stage_or_unstage_and_next(
18479 &mut self,
18480 stage: bool,
18481 window: &mut Window,
18482 cx: &mut Context<Self>,
18483 ) {
18484 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18485
18486 if ranges.iter().any(|range| range.start != range.end) {
18487 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18488 return;
18489 }
18490
18491 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18492 let snapshot = self.snapshot(window, cx);
18493 let position = self.selections.newest::<Point>(cx).head();
18494 let mut row = snapshot
18495 .buffer_snapshot
18496 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18497 .find(|hunk| hunk.row_range.start.0 > position.row)
18498 .map(|hunk| hunk.row_range.start);
18499
18500 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18501 // Outside of the project diff editor, wrap around to the beginning.
18502 if !all_diff_hunks_expanded {
18503 row = row.or_else(|| {
18504 snapshot
18505 .buffer_snapshot
18506 .diff_hunks_in_range(Point::zero()..position)
18507 .find(|hunk| hunk.row_range.end.0 < position.row)
18508 .map(|hunk| hunk.row_range.start)
18509 });
18510 }
18511
18512 if let Some(row) = row {
18513 let destination = Point::new(row.0, 0);
18514 let autoscroll = Autoscroll::center();
18515
18516 self.unfold_ranges(&[destination..destination], false, false, cx);
18517 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18518 s.select_ranges([destination..destination]);
18519 });
18520 }
18521 }
18522
18523 fn do_stage_or_unstage(
18524 &self,
18525 stage: bool,
18526 buffer_id: BufferId,
18527 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18528 cx: &mut App,
18529 ) -> Option<()> {
18530 let project = self.project()?;
18531 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18532 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18533 let buffer_snapshot = buffer.read(cx).snapshot();
18534 let file_exists = buffer_snapshot
18535 .file()
18536 .is_some_and(|file| file.disk_state().exists());
18537 diff.update(cx, |diff, cx| {
18538 diff.stage_or_unstage_hunks(
18539 stage,
18540 &hunks
18541 .map(|hunk| buffer_diff::DiffHunk {
18542 buffer_range: hunk.buffer_range,
18543 diff_base_byte_range: hunk.diff_base_byte_range,
18544 secondary_status: hunk.secondary_status,
18545 range: Point::zero()..Point::zero(), // unused
18546 })
18547 .collect::<Vec<_>>(),
18548 &buffer_snapshot,
18549 file_exists,
18550 cx,
18551 )
18552 });
18553 None
18554 }
18555
18556 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18557 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18558 self.buffer
18559 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18560 }
18561
18562 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18563 self.buffer.update(cx, |buffer, cx| {
18564 let ranges = vec![Anchor::min()..Anchor::max()];
18565 if !buffer.all_diff_hunks_expanded()
18566 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18567 {
18568 buffer.collapse_diff_hunks(ranges, cx);
18569 true
18570 } else {
18571 false
18572 }
18573 })
18574 }
18575
18576 fn toggle_diff_hunks_in_ranges(
18577 &mut self,
18578 ranges: Vec<Range<Anchor>>,
18579 cx: &mut Context<Editor>,
18580 ) {
18581 self.buffer.update(cx, |buffer, cx| {
18582 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18583 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18584 })
18585 }
18586
18587 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18588 self.buffer.update(cx, |buffer, cx| {
18589 let snapshot = buffer.snapshot(cx);
18590 let excerpt_id = range.end.excerpt_id;
18591 let point_range = range.to_point(&snapshot);
18592 let expand = !buffer.single_hunk_is_expanded(range, cx);
18593 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18594 })
18595 }
18596
18597 pub(crate) fn apply_all_diff_hunks(
18598 &mut self,
18599 _: &ApplyAllDiffHunks,
18600 window: &mut Window,
18601 cx: &mut Context<Self>,
18602 ) {
18603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18604
18605 let buffers = self.buffer.read(cx).all_buffers();
18606 for branch_buffer in buffers {
18607 branch_buffer.update(cx, |branch_buffer, cx| {
18608 branch_buffer.merge_into_base(Vec::new(), cx);
18609 });
18610 }
18611
18612 if let Some(project) = self.project.clone() {
18613 self.save(
18614 SaveOptions {
18615 format: true,
18616 autosave: false,
18617 },
18618 project,
18619 window,
18620 cx,
18621 )
18622 .detach_and_log_err(cx);
18623 }
18624 }
18625
18626 pub(crate) fn apply_selected_diff_hunks(
18627 &mut self,
18628 _: &ApplyDiffHunk,
18629 window: &mut Window,
18630 cx: &mut Context<Self>,
18631 ) {
18632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18633 let snapshot = self.snapshot(window, cx);
18634 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18635 let mut ranges_by_buffer = HashMap::default();
18636 self.transact(window, cx, |editor, _window, cx| {
18637 for hunk in hunks {
18638 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18639 ranges_by_buffer
18640 .entry(buffer.clone())
18641 .or_insert_with(Vec::new)
18642 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18643 }
18644 }
18645
18646 for (buffer, ranges) in ranges_by_buffer {
18647 buffer.update(cx, |buffer, cx| {
18648 buffer.merge_into_base(ranges, cx);
18649 });
18650 }
18651 });
18652
18653 if let Some(project) = self.project.clone() {
18654 self.save(
18655 SaveOptions {
18656 format: true,
18657 autosave: false,
18658 },
18659 project,
18660 window,
18661 cx,
18662 )
18663 .detach_and_log_err(cx);
18664 }
18665 }
18666
18667 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18668 if hovered != self.gutter_hovered {
18669 self.gutter_hovered = hovered;
18670 cx.notify();
18671 }
18672 }
18673
18674 pub fn insert_blocks(
18675 &mut self,
18676 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18677 autoscroll: Option<Autoscroll>,
18678 cx: &mut Context<Self>,
18679 ) -> Vec<CustomBlockId> {
18680 let blocks = self
18681 .display_map
18682 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18683 if let Some(autoscroll) = autoscroll {
18684 self.request_autoscroll(autoscroll, cx);
18685 }
18686 cx.notify();
18687 blocks
18688 }
18689
18690 pub fn resize_blocks(
18691 &mut self,
18692 heights: HashMap<CustomBlockId, u32>,
18693 autoscroll: Option<Autoscroll>,
18694 cx: &mut Context<Self>,
18695 ) {
18696 self.display_map
18697 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18698 if let Some(autoscroll) = autoscroll {
18699 self.request_autoscroll(autoscroll, cx);
18700 }
18701 cx.notify();
18702 }
18703
18704 pub fn replace_blocks(
18705 &mut self,
18706 renderers: HashMap<CustomBlockId, RenderBlock>,
18707 autoscroll: Option<Autoscroll>,
18708 cx: &mut Context<Self>,
18709 ) {
18710 self.display_map
18711 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18712 if let Some(autoscroll) = autoscroll {
18713 self.request_autoscroll(autoscroll, cx);
18714 }
18715 cx.notify();
18716 }
18717
18718 pub fn remove_blocks(
18719 &mut self,
18720 block_ids: HashSet<CustomBlockId>,
18721 autoscroll: Option<Autoscroll>,
18722 cx: &mut Context<Self>,
18723 ) {
18724 self.display_map.update(cx, |display_map, cx| {
18725 display_map.remove_blocks(block_ids, cx)
18726 });
18727 if let Some(autoscroll) = autoscroll {
18728 self.request_autoscroll(autoscroll, cx);
18729 }
18730 cx.notify();
18731 }
18732
18733 pub fn row_for_block(
18734 &self,
18735 block_id: CustomBlockId,
18736 cx: &mut Context<Self>,
18737 ) -> Option<DisplayRow> {
18738 self.display_map
18739 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18740 }
18741
18742 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18743 self.focused_block = Some(focused_block);
18744 }
18745
18746 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18747 self.focused_block.take()
18748 }
18749
18750 pub fn insert_creases(
18751 &mut self,
18752 creases: impl IntoIterator<Item = Crease<Anchor>>,
18753 cx: &mut Context<Self>,
18754 ) -> Vec<CreaseId> {
18755 self.display_map
18756 .update(cx, |map, cx| map.insert_creases(creases, cx))
18757 }
18758
18759 pub fn remove_creases(
18760 &mut self,
18761 ids: impl IntoIterator<Item = CreaseId>,
18762 cx: &mut Context<Self>,
18763 ) -> Vec<(CreaseId, Range<Anchor>)> {
18764 self.display_map
18765 .update(cx, |map, cx| map.remove_creases(ids, cx))
18766 }
18767
18768 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18769 self.display_map
18770 .update(cx, |map, cx| map.snapshot(cx))
18771 .longest_row()
18772 }
18773
18774 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18775 self.display_map
18776 .update(cx, |map, cx| map.snapshot(cx))
18777 .max_point()
18778 }
18779
18780 pub fn text(&self, cx: &App) -> String {
18781 self.buffer.read(cx).read(cx).text()
18782 }
18783
18784 pub fn is_empty(&self, cx: &App) -> bool {
18785 self.buffer.read(cx).read(cx).is_empty()
18786 }
18787
18788 pub fn text_option(&self, cx: &App) -> Option<String> {
18789 let text = self.text(cx);
18790 let text = text.trim();
18791
18792 if text.is_empty() {
18793 return None;
18794 }
18795
18796 Some(text.to_string())
18797 }
18798
18799 pub fn set_text(
18800 &mut self,
18801 text: impl Into<Arc<str>>,
18802 window: &mut Window,
18803 cx: &mut Context<Self>,
18804 ) {
18805 self.transact(window, cx, |this, _, cx| {
18806 this.buffer
18807 .read(cx)
18808 .as_singleton()
18809 .expect("you can only call set_text on editors for singleton buffers")
18810 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18811 });
18812 }
18813
18814 pub fn display_text(&self, cx: &mut App) -> String {
18815 self.display_map
18816 .update(cx, |map, cx| map.snapshot(cx))
18817 .text()
18818 }
18819
18820 fn create_minimap(
18821 &self,
18822 minimap_settings: MinimapSettings,
18823 window: &mut Window,
18824 cx: &mut Context<Self>,
18825 ) -> Option<Entity<Self>> {
18826 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18827 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18828 }
18829
18830 fn initialize_new_minimap(
18831 &self,
18832 minimap_settings: MinimapSettings,
18833 window: &mut Window,
18834 cx: &mut Context<Self>,
18835 ) -> Entity<Self> {
18836 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18837
18838 let mut minimap = Editor::new_internal(
18839 EditorMode::Minimap {
18840 parent: cx.weak_entity(),
18841 },
18842 self.buffer.clone(),
18843 None,
18844 Some(self.display_map.clone()),
18845 window,
18846 cx,
18847 );
18848 minimap.scroll_manager.clone_state(&self.scroll_manager);
18849 minimap.set_text_style_refinement(TextStyleRefinement {
18850 font_size: Some(MINIMAP_FONT_SIZE),
18851 font_weight: Some(MINIMAP_FONT_WEIGHT),
18852 ..Default::default()
18853 });
18854 minimap.update_minimap_configuration(minimap_settings, cx);
18855 cx.new(|_| minimap)
18856 }
18857
18858 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18859 let current_line_highlight = minimap_settings
18860 .current_line_highlight
18861 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18862 self.set_current_line_highlight(Some(current_line_highlight));
18863 }
18864
18865 pub fn minimap(&self) -> Option<&Entity<Self>> {
18866 self.minimap
18867 .as_ref()
18868 .filter(|_| self.minimap_visibility.visible())
18869 }
18870
18871 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18872 let mut wrap_guides = smallvec![];
18873
18874 if self.show_wrap_guides == Some(false) {
18875 return wrap_guides;
18876 }
18877
18878 let settings = self.buffer.read(cx).language_settings(cx);
18879 if settings.show_wrap_guides {
18880 match self.soft_wrap_mode(cx) {
18881 SoftWrap::Column(soft_wrap) => {
18882 wrap_guides.push((soft_wrap as usize, true));
18883 }
18884 SoftWrap::Bounded(soft_wrap) => {
18885 wrap_guides.push((soft_wrap as usize, true));
18886 }
18887 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18888 }
18889 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18890 }
18891
18892 wrap_guides
18893 }
18894
18895 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18896 let settings = self.buffer.read(cx).language_settings(cx);
18897 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18898 match mode {
18899 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18900 SoftWrap::None
18901 }
18902 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18903 language_settings::SoftWrap::PreferredLineLength => {
18904 SoftWrap::Column(settings.preferred_line_length)
18905 }
18906 language_settings::SoftWrap::Bounded => {
18907 SoftWrap::Bounded(settings.preferred_line_length)
18908 }
18909 }
18910 }
18911
18912 pub fn set_soft_wrap_mode(
18913 &mut self,
18914 mode: language_settings::SoftWrap,
18915
18916 cx: &mut Context<Self>,
18917 ) {
18918 self.soft_wrap_mode_override = Some(mode);
18919 cx.notify();
18920 }
18921
18922 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18923 self.hard_wrap = hard_wrap;
18924 cx.notify();
18925 }
18926
18927 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18928 self.text_style_refinement = Some(style);
18929 }
18930
18931 /// called by the Element so we know what style we were most recently rendered with.
18932 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
18933 // We intentionally do not inform the display map about the minimap style
18934 // so that wrapping is not recalculated and stays consistent for the editor
18935 // and its linked minimap.
18936 if !self.mode.is_minimap() {
18937 let font = style.text.font();
18938 let font_size = style.text.font_size.to_pixels(window.rem_size());
18939 let display_map = self
18940 .placeholder_display_map
18941 .as_ref()
18942 .filter(|_| self.is_empty(cx))
18943 .unwrap_or(&self.display_map);
18944
18945 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
18946 }
18947 self.style = Some(style);
18948 }
18949
18950 pub fn style(&self) -> Option<&EditorStyle> {
18951 self.style.as_ref()
18952 }
18953
18954 // Called by the element. This method is not designed to be called outside of the editor
18955 // element's layout code because it does not notify when rewrapping is computed synchronously.
18956 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18957 if self.is_empty(cx) {
18958 self.placeholder_display_map
18959 .as_ref()
18960 .map_or(false, |display_map| {
18961 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
18962 })
18963 } else {
18964 self.display_map
18965 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18966 }
18967 }
18968
18969 pub fn set_soft_wrap(&mut self) {
18970 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18971 }
18972
18973 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18974 if self.soft_wrap_mode_override.is_some() {
18975 self.soft_wrap_mode_override.take();
18976 } else {
18977 let soft_wrap = match self.soft_wrap_mode(cx) {
18978 SoftWrap::GitDiff => return,
18979 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18980 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18981 language_settings::SoftWrap::None
18982 }
18983 };
18984 self.soft_wrap_mode_override = Some(soft_wrap);
18985 }
18986 cx.notify();
18987 }
18988
18989 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18990 let Some(workspace) = self.workspace() else {
18991 return;
18992 };
18993 let fs = workspace.read(cx).app_state().fs.clone();
18994 let current_show = TabBarSettings::get_global(cx).show;
18995 update_settings_file(fs, cx, move |setting, _| {
18996 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
18997 });
18998 }
18999
19000 pub fn toggle_indent_guides(
19001 &mut self,
19002 _: &ToggleIndentGuides,
19003 _: &mut Window,
19004 cx: &mut Context<Self>,
19005 ) {
19006 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19007 self.buffer
19008 .read(cx)
19009 .language_settings(cx)
19010 .indent_guides
19011 .enabled
19012 });
19013 self.show_indent_guides = Some(!currently_enabled);
19014 cx.notify();
19015 }
19016
19017 fn should_show_indent_guides(&self) -> Option<bool> {
19018 self.show_indent_guides
19019 }
19020
19021 pub fn toggle_line_numbers(
19022 &mut self,
19023 _: &ToggleLineNumbers,
19024 _: &mut Window,
19025 cx: &mut Context<Self>,
19026 ) {
19027 let mut editor_settings = EditorSettings::get_global(cx).clone();
19028 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19029 EditorSettings::override_global(editor_settings, cx);
19030 }
19031
19032 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19033 if let Some(show_line_numbers) = self.show_line_numbers {
19034 return show_line_numbers;
19035 }
19036 EditorSettings::get_global(cx).gutter.line_numbers
19037 }
19038
19039 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19040 self.use_relative_line_numbers
19041 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19042 }
19043
19044 pub fn toggle_relative_line_numbers(
19045 &mut self,
19046 _: &ToggleRelativeLineNumbers,
19047 _: &mut Window,
19048 cx: &mut Context<Self>,
19049 ) {
19050 let is_relative = self.should_use_relative_line_numbers(cx);
19051 self.set_relative_line_number(Some(!is_relative), cx)
19052 }
19053
19054 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19055 self.use_relative_line_numbers = is_relative;
19056 cx.notify();
19057 }
19058
19059 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19060 self.show_gutter = show_gutter;
19061 cx.notify();
19062 }
19063
19064 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19065 self.show_scrollbars = ScrollbarAxes {
19066 horizontal: show,
19067 vertical: show,
19068 };
19069 cx.notify();
19070 }
19071
19072 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19073 self.show_scrollbars.vertical = show;
19074 cx.notify();
19075 }
19076
19077 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19078 self.show_scrollbars.horizontal = show;
19079 cx.notify();
19080 }
19081
19082 pub fn set_minimap_visibility(
19083 &mut self,
19084 minimap_visibility: MinimapVisibility,
19085 window: &mut Window,
19086 cx: &mut Context<Self>,
19087 ) {
19088 if self.minimap_visibility != minimap_visibility {
19089 if minimap_visibility.visible() && self.minimap.is_none() {
19090 let minimap_settings = EditorSettings::get_global(cx).minimap;
19091 self.minimap =
19092 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19093 }
19094 self.minimap_visibility = minimap_visibility;
19095 cx.notify();
19096 }
19097 }
19098
19099 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19100 self.set_show_scrollbars(false, cx);
19101 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19102 }
19103
19104 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19105 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19106 }
19107
19108 /// Normally the text in full mode and auto height editors is padded on the
19109 /// left side by roughly half a character width for improved hit testing.
19110 ///
19111 /// Use this method to disable this for cases where this is not wanted (e.g.
19112 /// if you want to align the editor text with some other text above or below)
19113 /// or if you want to add this padding to single-line editors.
19114 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19115 self.offset_content = offset_content;
19116 cx.notify();
19117 }
19118
19119 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19120 self.show_line_numbers = Some(show_line_numbers);
19121 cx.notify();
19122 }
19123
19124 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19125 self.disable_expand_excerpt_buttons = true;
19126 cx.notify();
19127 }
19128
19129 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19130 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19131 cx.notify();
19132 }
19133
19134 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19135 self.show_code_actions = Some(show_code_actions);
19136 cx.notify();
19137 }
19138
19139 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19140 self.show_runnables = Some(show_runnables);
19141 cx.notify();
19142 }
19143
19144 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19145 self.show_breakpoints = Some(show_breakpoints);
19146 cx.notify();
19147 }
19148
19149 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19150 if self.display_map.read(cx).masked != masked {
19151 self.display_map.update(cx, |map, _| map.masked = masked);
19152 }
19153 cx.notify()
19154 }
19155
19156 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19157 self.show_wrap_guides = Some(show_wrap_guides);
19158 cx.notify();
19159 }
19160
19161 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19162 self.show_indent_guides = Some(show_indent_guides);
19163 cx.notify();
19164 }
19165
19166 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19167 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19168 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19169 && let Some(dir) = file.abs_path(cx).parent()
19170 {
19171 return Some(dir.to_owned());
19172 }
19173
19174 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19175 return Some(project_path.path.to_path_buf());
19176 }
19177 }
19178
19179 None
19180 }
19181
19182 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19183 self.active_excerpt(cx)?
19184 .1
19185 .read(cx)
19186 .file()
19187 .and_then(|f| f.as_local())
19188 }
19189
19190 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19191 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19192 let buffer = buffer.read(cx);
19193 if let Some(project_path) = buffer.project_path(cx) {
19194 let project = self.project()?.read(cx);
19195 project.absolute_path(&project_path, cx)
19196 } else {
19197 buffer
19198 .file()
19199 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19200 }
19201 })
19202 }
19203
19204 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19205 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19206 let project_path = buffer.read(cx).project_path(cx)?;
19207 let project = self.project()?.read(cx);
19208 let entry = project.entry_for_path(&project_path, cx)?;
19209 let path = entry.path.to_path_buf();
19210 Some(path)
19211 })
19212 }
19213
19214 pub fn reveal_in_finder(
19215 &mut self,
19216 _: &RevealInFileManager,
19217 _window: &mut Window,
19218 cx: &mut Context<Self>,
19219 ) {
19220 if let Some(target) = self.target_file(cx) {
19221 cx.reveal_path(&target.abs_path(cx));
19222 }
19223 }
19224
19225 pub fn copy_path(
19226 &mut self,
19227 _: &zed_actions::workspace::CopyPath,
19228 _window: &mut Window,
19229 cx: &mut Context<Self>,
19230 ) {
19231 if let Some(path) = self.target_file_abs_path(cx)
19232 && let Some(path) = path.to_str()
19233 {
19234 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19235 }
19236 }
19237
19238 pub fn copy_relative_path(
19239 &mut self,
19240 _: &zed_actions::workspace::CopyRelativePath,
19241 _window: &mut Window,
19242 cx: &mut Context<Self>,
19243 ) {
19244 if let Some(path) = self.target_file_path(cx)
19245 && let Some(path) = path.to_str()
19246 {
19247 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19248 }
19249 }
19250
19251 /// Returns the project path for the editor's buffer, if any buffer is
19252 /// opened in the editor.
19253 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19254 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19255 buffer.read(cx).project_path(cx)
19256 } else {
19257 None
19258 }
19259 }
19260
19261 // Returns true if the editor handled a go-to-line request
19262 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19263 maybe!({
19264 let breakpoint_store = self.breakpoint_store.as_ref()?;
19265
19266 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19267 else {
19268 self.clear_row_highlights::<ActiveDebugLine>();
19269 return None;
19270 };
19271
19272 let position = active_stack_frame.position;
19273 let buffer_id = position.buffer_id?;
19274 let snapshot = self
19275 .project
19276 .as_ref()?
19277 .read(cx)
19278 .buffer_for_id(buffer_id, cx)?
19279 .read(cx)
19280 .snapshot();
19281
19282 let mut handled = false;
19283 for (id, ExcerptRange { context, .. }) in
19284 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19285 {
19286 if context.start.cmp(&position, &snapshot).is_ge()
19287 || context.end.cmp(&position, &snapshot).is_lt()
19288 {
19289 continue;
19290 }
19291 let snapshot = self.buffer.read(cx).snapshot(cx);
19292 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19293
19294 handled = true;
19295 self.clear_row_highlights::<ActiveDebugLine>();
19296
19297 self.go_to_line::<ActiveDebugLine>(
19298 multibuffer_anchor,
19299 Some(cx.theme().colors().editor_debugger_active_line_background),
19300 window,
19301 cx,
19302 );
19303
19304 cx.notify();
19305 }
19306
19307 handled.then_some(())
19308 })
19309 .is_some()
19310 }
19311
19312 pub fn copy_file_name_without_extension(
19313 &mut self,
19314 _: &CopyFileNameWithoutExtension,
19315 _: &mut Window,
19316 cx: &mut Context<Self>,
19317 ) {
19318 if let Some(file) = self.target_file(cx)
19319 && let Some(file_stem) = file.path().file_stem()
19320 && let Some(name) = file_stem.to_str()
19321 {
19322 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19323 }
19324 }
19325
19326 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19327 if let Some(file) = self.target_file(cx)
19328 && let Some(file_name) = file.path().file_name()
19329 && let Some(name) = file_name.to_str()
19330 {
19331 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19332 }
19333 }
19334
19335 pub fn toggle_git_blame(
19336 &mut self,
19337 _: &::git::Blame,
19338 window: &mut Window,
19339 cx: &mut Context<Self>,
19340 ) {
19341 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19342
19343 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19344 self.start_git_blame(true, window, cx);
19345 }
19346
19347 cx.notify();
19348 }
19349
19350 pub fn toggle_git_blame_inline(
19351 &mut self,
19352 _: &ToggleGitBlameInline,
19353 window: &mut Window,
19354 cx: &mut Context<Self>,
19355 ) {
19356 self.toggle_git_blame_inline_internal(true, window, cx);
19357 cx.notify();
19358 }
19359
19360 pub fn open_git_blame_commit(
19361 &mut self,
19362 _: &OpenGitBlameCommit,
19363 window: &mut Window,
19364 cx: &mut Context<Self>,
19365 ) {
19366 self.open_git_blame_commit_internal(window, cx);
19367 }
19368
19369 fn open_git_blame_commit_internal(
19370 &mut self,
19371 window: &mut Window,
19372 cx: &mut Context<Self>,
19373 ) -> Option<()> {
19374 let blame = self.blame.as_ref()?;
19375 let snapshot = self.snapshot(window, cx);
19376 let cursor = self.selections.newest::<Point>(cx).head();
19377 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19378 let (_, blame_entry) = blame
19379 .update(cx, |blame, cx| {
19380 blame
19381 .blame_for_rows(
19382 &[RowInfo {
19383 buffer_id: Some(buffer.remote_id()),
19384 buffer_row: Some(point.row),
19385 ..Default::default()
19386 }],
19387 cx,
19388 )
19389 .next()
19390 })
19391 .flatten()?;
19392 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19393 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19394 let workspace = self.workspace()?.downgrade();
19395 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19396 None
19397 }
19398
19399 pub fn git_blame_inline_enabled(&self) -> bool {
19400 self.git_blame_inline_enabled
19401 }
19402
19403 pub fn toggle_selection_menu(
19404 &mut self,
19405 _: &ToggleSelectionMenu,
19406 _: &mut Window,
19407 cx: &mut Context<Self>,
19408 ) {
19409 self.show_selection_menu = self
19410 .show_selection_menu
19411 .map(|show_selections_menu| !show_selections_menu)
19412 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19413
19414 cx.notify();
19415 }
19416
19417 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19418 self.show_selection_menu
19419 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19420 }
19421
19422 fn start_git_blame(
19423 &mut self,
19424 user_triggered: bool,
19425 window: &mut Window,
19426 cx: &mut Context<Self>,
19427 ) {
19428 if let Some(project) = self.project() {
19429 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19430 && buffer.read(cx).file().is_none()
19431 {
19432 return;
19433 }
19434
19435 let focused = self.focus_handle(cx).contains_focused(window, cx);
19436
19437 let project = project.clone();
19438 let blame = cx
19439 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19440 self.blame_subscription =
19441 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19442 self.blame = Some(blame);
19443 }
19444 }
19445
19446 fn toggle_git_blame_inline_internal(
19447 &mut self,
19448 user_triggered: bool,
19449 window: &mut Window,
19450 cx: &mut Context<Self>,
19451 ) {
19452 if self.git_blame_inline_enabled {
19453 self.git_blame_inline_enabled = false;
19454 self.show_git_blame_inline = false;
19455 self.show_git_blame_inline_delay_task.take();
19456 } else {
19457 self.git_blame_inline_enabled = true;
19458 self.start_git_blame_inline(user_triggered, window, cx);
19459 }
19460
19461 cx.notify();
19462 }
19463
19464 fn start_git_blame_inline(
19465 &mut self,
19466 user_triggered: bool,
19467 window: &mut Window,
19468 cx: &mut Context<Self>,
19469 ) {
19470 self.start_git_blame(user_triggered, window, cx);
19471
19472 if ProjectSettings::get_global(cx)
19473 .git
19474 .inline_blame_delay()
19475 .is_some()
19476 {
19477 self.start_inline_blame_timer(window, cx);
19478 } else {
19479 self.show_git_blame_inline = true
19480 }
19481 }
19482
19483 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19484 self.blame.as_ref()
19485 }
19486
19487 pub fn show_git_blame_gutter(&self) -> bool {
19488 self.show_git_blame_gutter
19489 }
19490
19491 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19492 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19493 }
19494
19495 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19496 self.show_git_blame_inline
19497 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19498 && !self.newest_selection_head_on_empty_line(cx)
19499 && self.has_blame_entries(cx)
19500 }
19501
19502 fn has_blame_entries(&self, cx: &App) -> bool {
19503 self.blame()
19504 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19505 }
19506
19507 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19508 let cursor_anchor = self.selections.newest_anchor().head();
19509
19510 let snapshot = self.buffer.read(cx).snapshot(cx);
19511 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19512
19513 snapshot.line_len(buffer_row) == 0
19514 }
19515
19516 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19517 let buffer_and_selection = maybe!({
19518 let selection = self.selections.newest::<Point>(cx);
19519 let selection_range = selection.range();
19520
19521 let multi_buffer = self.buffer().read(cx);
19522 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19523 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19524
19525 let (buffer, range, _) = if selection.reversed {
19526 buffer_ranges.first()
19527 } else {
19528 buffer_ranges.last()
19529 }?;
19530
19531 let selection = text::ToPoint::to_point(&range.start, buffer).row
19532 ..text::ToPoint::to_point(&range.end, buffer).row;
19533 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19534 });
19535
19536 let Some((buffer, selection)) = buffer_and_selection else {
19537 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19538 };
19539
19540 let Some(project) = self.project() else {
19541 return Task::ready(Err(anyhow!("editor does not have project")));
19542 };
19543
19544 project.update(cx, |project, cx| {
19545 project.get_permalink_to_line(&buffer, selection, cx)
19546 })
19547 }
19548
19549 pub fn copy_permalink_to_line(
19550 &mut self,
19551 _: &CopyPermalinkToLine,
19552 window: &mut Window,
19553 cx: &mut Context<Self>,
19554 ) {
19555 let permalink_task = self.get_permalink_to_line(cx);
19556 let workspace = self.workspace();
19557
19558 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19559 Ok(permalink) => {
19560 cx.update(|_, cx| {
19561 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19562 })
19563 .ok();
19564 }
19565 Err(err) => {
19566 let message = format!("Failed to copy permalink: {err}");
19567
19568 anyhow::Result::<()>::Err(err).log_err();
19569
19570 if let Some(workspace) = workspace {
19571 workspace
19572 .update_in(cx, |workspace, _, cx| {
19573 struct CopyPermalinkToLine;
19574
19575 workspace.show_toast(
19576 Toast::new(
19577 NotificationId::unique::<CopyPermalinkToLine>(),
19578 message,
19579 ),
19580 cx,
19581 )
19582 })
19583 .ok();
19584 }
19585 }
19586 })
19587 .detach();
19588 }
19589
19590 pub fn copy_file_location(
19591 &mut self,
19592 _: &CopyFileLocation,
19593 _: &mut Window,
19594 cx: &mut Context<Self>,
19595 ) {
19596 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19597 if let Some(file) = self.target_file(cx)
19598 && let Some(path) = file.path().to_str()
19599 {
19600 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19601 }
19602 }
19603
19604 pub fn open_permalink_to_line(
19605 &mut self,
19606 _: &OpenPermalinkToLine,
19607 window: &mut Window,
19608 cx: &mut Context<Self>,
19609 ) {
19610 let permalink_task = self.get_permalink_to_line(cx);
19611 let workspace = self.workspace();
19612
19613 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19614 Ok(permalink) => {
19615 cx.update(|_, cx| {
19616 cx.open_url(permalink.as_ref());
19617 })
19618 .ok();
19619 }
19620 Err(err) => {
19621 let message = format!("Failed to open permalink: {err}");
19622
19623 anyhow::Result::<()>::Err(err).log_err();
19624
19625 if let Some(workspace) = workspace {
19626 workspace
19627 .update(cx, |workspace, cx| {
19628 struct OpenPermalinkToLine;
19629
19630 workspace.show_toast(
19631 Toast::new(
19632 NotificationId::unique::<OpenPermalinkToLine>(),
19633 message,
19634 ),
19635 cx,
19636 )
19637 })
19638 .ok();
19639 }
19640 }
19641 })
19642 .detach();
19643 }
19644
19645 pub fn insert_uuid_v4(
19646 &mut self,
19647 _: &InsertUuidV4,
19648 window: &mut Window,
19649 cx: &mut Context<Self>,
19650 ) {
19651 self.insert_uuid(UuidVersion::V4, window, cx);
19652 }
19653
19654 pub fn insert_uuid_v7(
19655 &mut self,
19656 _: &InsertUuidV7,
19657 window: &mut Window,
19658 cx: &mut Context<Self>,
19659 ) {
19660 self.insert_uuid(UuidVersion::V7, window, cx);
19661 }
19662
19663 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19664 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19665 self.transact(window, cx, |this, window, cx| {
19666 let edits = this
19667 .selections
19668 .all::<Point>(cx)
19669 .into_iter()
19670 .map(|selection| {
19671 let uuid = match version {
19672 UuidVersion::V4 => uuid::Uuid::new_v4(),
19673 UuidVersion::V7 => uuid::Uuid::now_v7(),
19674 };
19675
19676 (selection.range(), uuid.to_string())
19677 });
19678 this.edit(edits, cx);
19679 this.refresh_edit_prediction(true, false, window, cx);
19680 });
19681 }
19682
19683 pub fn open_selections_in_multibuffer(
19684 &mut self,
19685 _: &OpenSelectionsInMultibuffer,
19686 window: &mut Window,
19687 cx: &mut Context<Self>,
19688 ) {
19689 let multibuffer = self.buffer.read(cx);
19690
19691 let Some(buffer) = multibuffer.as_singleton() else {
19692 return;
19693 };
19694
19695 let Some(workspace) = self.workspace() else {
19696 return;
19697 };
19698
19699 let title = multibuffer.title(cx).to_string();
19700
19701 let locations = self
19702 .selections
19703 .all_anchors(cx)
19704 .iter()
19705 .map(|selection| Location {
19706 buffer: buffer.clone(),
19707 range: selection.start.text_anchor..selection.end.text_anchor,
19708 })
19709 .collect::<Vec<_>>();
19710
19711 cx.spawn_in(window, async move |_, cx| {
19712 workspace.update_in(cx, |workspace, window, cx| {
19713 Self::open_locations_in_multibuffer(
19714 workspace,
19715 locations,
19716 format!("Selections for '{title}'"),
19717 false,
19718 MultibufferSelectionMode::All,
19719 window,
19720 cx,
19721 );
19722 })
19723 })
19724 .detach();
19725 }
19726
19727 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19728 /// last highlight added will be used.
19729 ///
19730 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19731 pub fn highlight_rows<T: 'static>(
19732 &mut self,
19733 range: Range<Anchor>,
19734 color: Hsla,
19735 options: RowHighlightOptions,
19736 cx: &mut Context<Self>,
19737 ) {
19738 let snapshot = self.buffer().read(cx).snapshot(cx);
19739 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19740 let ix = row_highlights.binary_search_by(|highlight| {
19741 Ordering::Equal
19742 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19743 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19744 });
19745
19746 if let Err(mut ix) = ix {
19747 let index = post_inc(&mut self.highlight_order);
19748
19749 // If this range intersects with the preceding highlight, then merge it with
19750 // the preceding highlight. Otherwise insert a new highlight.
19751 let mut merged = false;
19752 if ix > 0 {
19753 let prev_highlight = &mut row_highlights[ix - 1];
19754 if prev_highlight
19755 .range
19756 .end
19757 .cmp(&range.start, &snapshot)
19758 .is_ge()
19759 {
19760 ix -= 1;
19761 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19762 prev_highlight.range.end = range.end;
19763 }
19764 merged = true;
19765 prev_highlight.index = index;
19766 prev_highlight.color = color;
19767 prev_highlight.options = options;
19768 }
19769 }
19770
19771 if !merged {
19772 row_highlights.insert(
19773 ix,
19774 RowHighlight {
19775 range,
19776 index,
19777 color,
19778 options,
19779 type_id: TypeId::of::<T>(),
19780 },
19781 );
19782 }
19783
19784 // If any of the following highlights intersect with this one, merge them.
19785 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19786 let highlight = &row_highlights[ix];
19787 if next_highlight
19788 .range
19789 .start
19790 .cmp(&highlight.range.end, &snapshot)
19791 .is_le()
19792 {
19793 if next_highlight
19794 .range
19795 .end
19796 .cmp(&highlight.range.end, &snapshot)
19797 .is_gt()
19798 {
19799 row_highlights[ix].range.end = next_highlight.range.end;
19800 }
19801 row_highlights.remove(ix + 1);
19802 } else {
19803 break;
19804 }
19805 }
19806 }
19807 }
19808
19809 /// Remove any highlighted row ranges of the given type that intersect the
19810 /// given ranges.
19811 pub fn remove_highlighted_rows<T: 'static>(
19812 &mut self,
19813 ranges_to_remove: Vec<Range<Anchor>>,
19814 cx: &mut Context<Self>,
19815 ) {
19816 let snapshot = self.buffer().read(cx).snapshot(cx);
19817 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19818 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19819 row_highlights.retain(|highlight| {
19820 while let Some(range_to_remove) = ranges_to_remove.peek() {
19821 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19822 Ordering::Less | Ordering::Equal => {
19823 ranges_to_remove.next();
19824 }
19825 Ordering::Greater => {
19826 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19827 Ordering::Less | Ordering::Equal => {
19828 return false;
19829 }
19830 Ordering::Greater => break,
19831 }
19832 }
19833 }
19834 }
19835
19836 true
19837 })
19838 }
19839
19840 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19841 pub fn clear_row_highlights<T: 'static>(&mut self) {
19842 self.highlighted_rows.remove(&TypeId::of::<T>());
19843 }
19844
19845 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19846 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19847 self.highlighted_rows
19848 .get(&TypeId::of::<T>())
19849 .map_or(&[] as &[_], |vec| vec.as_slice())
19850 .iter()
19851 .map(|highlight| (highlight.range.clone(), highlight.color))
19852 }
19853
19854 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19855 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19856 /// Allows to ignore certain kinds of highlights.
19857 pub fn highlighted_display_rows(
19858 &self,
19859 window: &mut Window,
19860 cx: &mut App,
19861 ) -> BTreeMap<DisplayRow, LineHighlight> {
19862 let snapshot = self.snapshot(window, cx);
19863 let mut used_highlight_orders = HashMap::default();
19864 self.highlighted_rows
19865 .iter()
19866 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19867 .fold(
19868 BTreeMap::<DisplayRow, LineHighlight>::new(),
19869 |mut unique_rows, highlight| {
19870 let start = highlight.range.start.to_display_point(&snapshot);
19871 let end = highlight.range.end.to_display_point(&snapshot);
19872 let start_row = start.row().0;
19873 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19874 && end.column() == 0
19875 {
19876 end.row().0.saturating_sub(1)
19877 } else {
19878 end.row().0
19879 };
19880 for row in start_row..=end_row {
19881 let used_index =
19882 used_highlight_orders.entry(row).or_insert(highlight.index);
19883 if highlight.index >= *used_index {
19884 *used_index = highlight.index;
19885 unique_rows.insert(
19886 DisplayRow(row),
19887 LineHighlight {
19888 include_gutter: highlight.options.include_gutter,
19889 border: None,
19890 background: highlight.color.into(),
19891 type_id: Some(highlight.type_id),
19892 },
19893 );
19894 }
19895 }
19896 unique_rows
19897 },
19898 )
19899 }
19900
19901 pub fn highlighted_display_row_for_autoscroll(
19902 &self,
19903 snapshot: &DisplaySnapshot,
19904 ) -> Option<DisplayRow> {
19905 self.highlighted_rows
19906 .values()
19907 .flat_map(|highlighted_rows| highlighted_rows.iter())
19908 .filter_map(|highlight| {
19909 if highlight.options.autoscroll {
19910 Some(highlight.range.start.to_display_point(snapshot).row())
19911 } else {
19912 None
19913 }
19914 })
19915 .min()
19916 }
19917
19918 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19919 self.highlight_background::<SearchWithinRange>(
19920 ranges,
19921 |colors| colors.colors().editor_document_highlight_read_background,
19922 cx,
19923 )
19924 }
19925
19926 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19927 self.breadcrumb_header = Some(new_header);
19928 }
19929
19930 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19931 self.clear_background_highlights::<SearchWithinRange>(cx);
19932 }
19933
19934 pub fn highlight_background<T: 'static>(
19935 &mut self,
19936 ranges: &[Range<Anchor>],
19937 color_fetcher: fn(&Theme) -> Hsla,
19938 cx: &mut Context<Self>,
19939 ) {
19940 self.background_highlights.insert(
19941 HighlightKey::Type(TypeId::of::<T>()),
19942 (color_fetcher, Arc::from(ranges)),
19943 );
19944 self.scrollbar_marker_state.dirty = true;
19945 cx.notify();
19946 }
19947
19948 pub fn highlight_background_key<T: 'static>(
19949 &mut self,
19950 key: usize,
19951 ranges: &[Range<Anchor>],
19952 color_fetcher: fn(&Theme) -> Hsla,
19953 cx: &mut Context<Self>,
19954 ) {
19955 self.background_highlights.insert(
19956 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19957 (color_fetcher, Arc::from(ranges)),
19958 );
19959 self.scrollbar_marker_state.dirty = true;
19960 cx.notify();
19961 }
19962
19963 pub fn clear_background_highlights<T: 'static>(
19964 &mut self,
19965 cx: &mut Context<Self>,
19966 ) -> Option<BackgroundHighlight> {
19967 let text_highlights = self
19968 .background_highlights
19969 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19970 if !text_highlights.1.is_empty() {
19971 self.scrollbar_marker_state.dirty = true;
19972 cx.notify();
19973 }
19974 Some(text_highlights)
19975 }
19976
19977 pub fn highlight_gutter<T: 'static>(
19978 &mut self,
19979 ranges: impl Into<Vec<Range<Anchor>>>,
19980 color_fetcher: fn(&App) -> Hsla,
19981 cx: &mut Context<Self>,
19982 ) {
19983 self.gutter_highlights
19984 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19985 cx.notify();
19986 }
19987
19988 pub fn clear_gutter_highlights<T: 'static>(
19989 &mut self,
19990 cx: &mut Context<Self>,
19991 ) -> Option<GutterHighlight> {
19992 cx.notify();
19993 self.gutter_highlights.remove(&TypeId::of::<T>())
19994 }
19995
19996 pub fn insert_gutter_highlight<T: 'static>(
19997 &mut self,
19998 range: Range<Anchor>,
19999 color_fetcher: fn(&App) -> Hsla,
20000 cx: &mut Context<Self>,
20001 ) {
20002 let snapshot = self.buffer().read(cx).snapshot(cx);
20003 let mut highlights = self
20004 .gutter_highlights
20005 .remove(&TypeId::of::<T>())
20006 .map(|(_, highlights)| highlights)
20007 .unwrap_or_default();
20008 let ix = highlights.binary_search_by(|highlight| {
20009 Ordering::Equal
20010 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20011 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20012 });
20013 if let Err(ix) = ix {
20014 highlights.insert(ix, range);
20015 }
20016 self.gutter_highlights
20017 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20018 }
20019
20020 pub fn remove_gutter_highlights<T: 'static>(
20021 &mut self,
20022 ranges_to_remove: Vec<Range<Anchor>>,
20023 cx: &mut Context<Self>,
20024 ) {
20025 let snapshot = self.buffer().read(cx).snapshot(cx);
20026 let Some((color_fetcher, mut gutter_highlights)) =
20027 self.gutter_highlights.remove(&TypeId::of::<T>())
20028 else {
20029 return;
20030 };
20031 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20032 gutter_highlights.retain(|highlight| {
20033 while let Some(range_to_remove) = ranges_to_remove.peek() {
20034 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20035 Ordering::Less | Ordering::Equal => {
20036 ranges_to_remove.next();
20037 }
20038 Ordering::Greater => {
20039 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20040 Ordering::Less | Ordering::Equal => {
20041 return false;
20042 }
20043 Ordering::Greater => break,
20044 }
20045 }
20046 }
20047 }
20048
20049 true
20050 });
20051 self.gutter_highlights
20052 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20053 }
20054
20055 #[cfg(feature = "test-support")]
20056 pub fn all_text_highlights(
20057 &self,
20058 window: &mut Window,
20059 cx: &mut Context<Self>,
20060 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20061 let snapshot = self.snapshot(window, cx);
20062 self.display_map.update(cx, |display_map, _| {
20063 display_map
20064 .all_text_highlights()
20065 .map(|highlight| {
20066 let (style, ranges) = highlight.as_ref();
20067 (
20068 *style,
20069 ranges
20070 .iter()
20071 .map(|range| range.clone().to_display_points(&snapshot))
20072 .collect(),
20073 )
20074 })
20075 .collect()
20076 })
20077 }
20078
20079 #[cfg(feature = "test-support")]
20080 pub fn all_text_background_highlights(
20081 &self,
20082 window: &mut Window,
20083 cx: &mut Context<Self>,
20084 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20085 let snapshot = self.snapshot(window, cx);
20086 let buffer = &snapshot.buffer_snapshot;
20087 let start = buffer.anchor_before(0);
20088 let end = buffer.anchor_after(buffer.len());
20089 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20090 }
20091
20092 #[cfg(any(test, feature = "test-support"))]
20093 pub fn sorted_background_highlights_in_range(
20094 &self,
20095 search_range: Range<Anchor>,
20096 display_snapshot: &DisplaySnapshot,
20097 theme: &Theme,
20098 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20099 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20100 res.sort_by(|a, b| {
20101 a.0.start
20102 .cmp(&b.0.start)
20103 .then_with(|| a.0.end.cmp(&b.0.end))
20104 .then_with(|| a.1.cmp(&b.1))
20105 });
20106 res
20107 }
20108
20109 #[cfg(feature = "test-support")]
20110 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20111 let snapshot = self.buffer().read(cx).snapshot(cx);
20112
20113 let highlights = self
20114 .background_highlights
20115 .get(&HighlightKey::Type(TypeId::of::<
20116 items::BufferSearchHighlights,
20117 >()));
20118
20119 if let Some((_color, ranges)) = highlights {
20120 ranges
20121 .iter()
20122 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20123 .collect_vec()
20124 } else {
20125 vec![]
20126 }
20127 }
20128
20129 fn document_highlights_for_position<'a>(
20130 &'a self,
20131 position: Anchor,
20132 buffer: &'a MultiBufferSnapshot,
20133 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20134 let read_highlights = self
20135 .background_highlights
20136 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20137 .map(|h| &h.1);
20138 let write_highlights = self
20139 .background_highlights
20140 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20141 .map(|h| &h.1);
20142 let left_position = position.bias_left(buffer);
20143 let right_position = position.bias_right(buffer);
20144 read_highlights
20145 .into_iter()
20146 .chain(write_highlights)
20147 .flat_map(move |ranges| {
20148 let start_ix = match ranges.binary_search_by(|probe| {
20149 let cmp = probe.end.cmp(&left_position, buffer);
20150 if cmp.is_ge() {
20151 Ordering::Greater
20152 } else {
20153 Ordering::Less
20154 }
20155 }) {
20156 Ok(i) | Err(i) => i,
20157 };
20158
20159 ranges[start_ix..]
20160 .iter()
20161 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20162 })
20163 }
20164
20165 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20166 self.background_highlights
20167 .get(&HighlightKey::Type(TypeId::of::<T>()))
20168 .is_some_and(|(_, highlights)| !highlights.is_empty())
20169 }
20170
20171 /// Returns all background highlights for a given range.
20172 ///
20173 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20174 pub fn background_highlights_in_range(
20175 &self,
20176 search_range: Range<Anchor>,
20177 display_snapshot: &DisplaySnapshot,
20178 theme: &Theme,
20179 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20180 let mut results = Vec::new();
20181 for (color_fetcher, ranges) in self.background_highlights.values() {
20182 let color = color_fetcher(theme);
20183 let start_ix = match ranges.binary_search_by(|probe| {
20184 let cmp = probe
20185 .end
20186 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20187 if cmp.is_gt() {
20188 Ordering::Greater
20189 } else {
20190 Ordering::Less
20191 }
20192 }) {
20193 Ok(i) | Err(i) => i,
20194 };
20195 for range in &ranges[start_ix..] {
20196 if range
20197 .start
20198 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20199 .is_ge()
20200 {
20201 break;
20202 }
20203
20204 let start = range.start.to_display_point(display_snapshot);
20205 let end = range.end.to_display_point(display_snapshot);
20206 results.push((start..end, color))
20207 }
20208 }
20209 results
20210 }
20211
20212 pub fn gutter_highlights_in_range(
20213 &self,
20214 search_range: Range<Anchor>,
20215 display_snapshot: &DisplaySnapshot,
20216 cx: &App,
20217 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20218 let mut results = Vec::new();
20219 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20220 let color = color_fetcher(cx);
20221 let start_ix = match ranges.binary_search_by(|probe| {
20222 let cmp = probe
20223 .end
20224 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20225 if cmp.is_gt() {
20226 Ordering::Greater
20227 } else {
20228 Ordering::Less
20229 }
20230 }) {
20231 Ok(i) | Err(i) => i,
20232 };
20233 for range in &ranges[start_ix..] {
20234 if range
20235 .start
20236 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20237 .is_ge()
20238 {
20239 break;
20240 }
20241
20242 let start = range.start.to_display_point(display_snapshot);
20243 let end = range.end.to_display_point(display_snapshot);
20244 results.push((start..end, color))
20245 }
20246 }
20247 results
20248 }
20249
20250 /// Get the text ranges corresponding to the redaction query
20251 pub fn redacted_ranges(
20252 &self,
20253 search_range: Range<Anchor>,
20254 display_snapshot: &DisplaySnapshot,
20255 cx: &App,
20256 ) -> Vec<Range<DisplayPoint>> {
20257 display_snapshot
20258 .buffer_snapshot
20259 .redacted_ranges(search_range, |file| {
20260 if let Some(file) = file {
20261 file.is_private()
20262 && EditorSettings::get(
20263 Some(SettingsLocation {
20264 worktree_id: file.worktree_id(cx),
20265 path: file.path().as_ref(),
20266 }),
20267 cx,
20268 )
20269 .redact_private_values
20270 } else {
20271 false
20272 }
20273 })
20274 .map(|range| {
20275 range.start.to_display_point(display_snapshot)
20276 ..range.end.to_display_point(display_snapshot)
20277 })
20278 .collect()
20279 }
20280
20281 pub fn highlight_text_key<T: 'static>(
20282 &mut self,
20283 key: usize,
20284 ranges: Vec<Range<Anchor>>,
20285 style: HighlightStyle,
20286 cx: &mut Context<Self>,
20287 ) {
20288 self.display_map.update(cx, |map, _| {
20289 map.highlight_text(
20290 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20291 ranges,
20292 style,
20293 );
20294 });
20295 cx.notify();
20296 }
20297
20298 pub fn highlight_text<T: 'static>(
20299 &mut self,
20300 ranges: Vec<Range<Anchor>>,
20301 style: HighlightStyle,
20302 cx: &mut Context<Self>,
20303 ) {
20304 self.display_map.update(cx, |map, _| {
20305 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20306 });
20307 cx.notify();
20308 }
20309
20310 pub(crate) fn highlight_inlays<T: 'static>(
20311 &mut self,
20312 highlights: Vec<InlayHighlight>,
20313 style: HighlightStyle,
20314 cx: &mut Context<Self>,
20315 ) {
20316 self.display_map.update(cx, |map, _| {
20317 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20318 });
20319 cx.notify();
20320 }
20321
20322 pub fn text_highlights<'a, T: 'static>(
20323 &'a self,
20324 cx: &'a App,
20325 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20326 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20327 }
20328
20329 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20330 let cleared = self
20331 .display_map
20332 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20333 if cleared {
20334 cx.notify();
20335 }
20336 }
20337
20338 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20339 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20340 && self.focus_handle.is_focused(window)
20341 }
20342
20343 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20344 self.show_cursor_when_unfocused = is_enabled;
20345 cx.notify();
20346 }
20347
20348 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20349 cx.notify();
20350 }
20351
20352 fn on_debug_session_event(
20353 &mut self,
20354 _session: Entity<Session>,
20355 event: &SessionEvent,
20356 cx: &mut Context<Self>,
20357 ) {
20358 if let SessionEvent::InvalidateInlineValue = event {
20359 self.refresh_inline_values(cx);
20360 }
20361 }
20362
20363 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20364 let Some(project) = self.project.clone() else {
20365 return;
20366 };
20367
20368 if !self.inline_value_cache.enabled {
20369 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20370 self.splice_inlays(&inlays, Vec::new(), cx);
20371 return;
20372 }
20373
20374 let current_execution_position = self
20375 .highlighted_rows
20376 .get(&TypeId::of::<ActiveDebugLine>())
20377 .and_then(|lines| lines.last().map(|line| line.range.end));
20378
20379 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20380 let inline_values = editor
20381 .update(cx, |editor, cx| {
20382 let Some(current_execution_position) = current_execution_position else {
20383 return Some(Task::ready(Ok(Vec::new())));
20384 };
20385
20386 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20387 let snapshot = buffer.snapshot(cx);
20388
20389 let excerpt = snapshot.excerpt_containing(
20390 current_execution_position..current_execution_position,
20391 )?;
20392
20393 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20394 })?;
20395
20396 let range =
20397 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20398
20399 project.inline_values(buffer, range, cx)
20400 })
20401 .ok()
20402 .flatten()?
20403 .await
20404 .context("refreshing debugger inlays")
20405 .log_err()?;
20406
20407 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20408
20409 for (buffer_id, inline_value) in inline_values
20410 .into_iter()
20411 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20412 {
20413 buffer_inline_values
20414 .entry(buffer_id)
20415 .or_default()
20416 .push(inline_value);
20417 }
20418
20419 editor
20420 .update(cx, |editor, cx| {
20421 let snapshot = editor.buffer.read(cx).snapshot(cx);
20422 let mut new_inlays = Vec::default();
20423
20424 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20425 let buffer_id = buffer_snapshot.remote_id();
20426 buffer_inline_values
20427 .get(&buffer_id)
20428 .into_iter()
20429 .flatten()
20430 .for_each(|hint| {
20431 let inlay = Inlay::debugger(
20432 post_inc(&mut editor.next_inlay_id),
20433 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20434 hint.text(),
20435 );
20436 if !inlay.text.chars().contains(&'\n') {
20437 new_inlays.push(inlay);
20438 }
20439 });
20440 }
20441
20442 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20443 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20444
20445 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20446 })
20447 .ok()?;
20448 Some(())
20449 });
20450 }
20451
20452 fn on_buffer_event(
20453 &mut self,
20454 multibuffer: &Entity<MultiBuffer>,
20455 event: &multi_buffer::Event,
20456 window: &mut Window,
20457 cx: &mut Context<Self>,
20458 ) {
20459 match event {
20460 multi_buffer::Event::Edited {
20461 singleton_buffer_edited,
20462 edited_buffer,
20463 } => {
20464 self.scrollbar_marker_state.dirty = true;
20465 self.active_indent_guides_state.dirty = true;
20466 self.refresh_active_diagnostics(cx);
20467 self.refresh_code_actions(window, cx);
20468 self.refresh_selected_text_highlights(true, window, cx);
20469 self.refresh_single_line_folds(window, cx);
20470 refresh_matching_bracket_highlights(self, window, cx);
20471 if self.has_active_edit_prediction() {
20472 self.update_visible_edit_prediction(window, cx);
20473 }
20474 if let Some(project) = self.project.as_ref()
20475 && let Some(edited_buffer) = edited_buffer
20476 {
20477 project.update(cx, |project, cx| {
20478 self.registered_buffers
20479 .entry(edited_buffer.read(cx).remote_id())
20480 .or_insert_with(|| {
20481 project.register_buffer_with_language_servers(edited_buffer, cx)
20482 });
20483 });
20484 }
20485 cx.emit(EditorEvent::BufferEdited);
20486 cx.emit(SearchEvent::MatchesInvalidated);
20487
20488 if let Some(buffer) = edited_buffer {
20489 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20490 }
20491
20492 if *singleton_buffer_edited {
20493 if let Some(buffer) = edited_buffer
20494 && buffer.read(cx).file().is_none()
20495 {
20496 cx.emit(EditorEvent::TitleChanged);
20497 }
20498 if let Some(project) = &self.project {
20499 #[allow(clippy::mutable_key_type)]
20500 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20501 multibuffer
20502 .all_buffers()
20503 .into_iter()
20504 .filter_map(|buffer| {
20505 buffer.update(cx, |buffer, cx| {
20506 let language = buffer.language()?;
20507 let should_discard = project.update(cx, |project, cx| {
20508 project.is_local()
20509 && !project.has_language_servers_for(buffer, cx)
20510 });
20511 should_discard.not().then_some(language.clone())
20512 })
20513 })
20514 .collect::<HashSet<_>>()
20515 });
20516 if !languages_affected.is_empty() {
20517 self.refresh_inlay_hints(
20518 InlayHintRefreshReason::BufferEdited(languages_affected),
20519 cx,
20520 );
20521 }
20522 }
20523 }
20524
20525 let Some(project) = &self.project else { return };
20526 let (telemetry, is_via_ssh) = {
20527 let project = project.read(cx);
20528 let telemetry = project.client().telemetry().clone();
20529 let is_via_ssh = project.is_via_remote_server();
20530 (telemetry, is_via_ssh)
20531 };
20532 refresh_linked_ranges(self, window, cx);
20533 telemetry.log_edit_event("editor", is_via_ssh);
20534 }
20535 multi_buffer::Event::ExcerptsAdded {
20536 buffer,
20537 predecessor,
20538 excerpts,
20539 } => {
20540 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20541 let buffer_id = buffer.read(cx).remote_id();
20542 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20543 && let Some(project) = &self.project
20544 {
20545 update_uncommitted_diff_for_buffer(
20546 cx.entity(),
20547 project,
20548 [buffer.clone()],
20549 self.buffer.clone(),
20550 cx,
20551 )
20552 .detach();
20553 }
20554 self.update_lsp_data(false, Some(buffer_id), window, cx);
20555 cx.emit(EditorEvent::ExcerptsAdded {
20556 buffer: buffer.clone(),
20557 predecessor: *predecessor,
20558 excerpts: excerpts.clone(),
20559 });
20560 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20561 }
20562 multi_buffer::Event::ExcerptsRemoved {
20563 ids,
20564 removed_buffer_ids,
20565 } => {
20566 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20567 let buffer = self.buffer.read(cx);
20568 self.registered_buffers
20569 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20570 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20571 cx.emit(EditorEvent::ExcerptsRemoved {
20572 ids: ids.clone(),
20573 removed_buffer_ids: removed_buffer_ids.clone(),
20574 });
20575 }
20576 multi_buffer::Event::ExcerptsEdited {
20577 excerpt_ids,
20578 buffer_ids,
20579 } => {
20580 self.display_map.update(cx, |map, cx| {
20581 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20582 });
20583 cx.emit(EditorEvent::ExcerptsEdited {
20584 ids: excerpt_ids.clone(),
20585 });
20586 }
20587 multi_buffer::Event::ExcerptsExpanded { ids } => {
20588 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20589 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20590 }
20591 multi_buffer::Event::Reparsed(buffer_id) => {
20592 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20593 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20594
20595 cx.emit(EditorEvent::Reparsed(*buffer_id));
20596 }
20597 multi_buffer::Event::DiffHunksToggled => {
20598 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20599 }
20600 multi_buffer::Event::LanguageChanged(buffer_id) => {
20601 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20602 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20603 cx.emit(EditorEvent::Reparsed(*buffer_id));
20604 cx.notify();
20605 }
20606 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20607 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20608 multi_buffer::Event::FileHandleChanged
20609 | multi_buffer::Event::Reloaded
20610 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20611 multi_buffer::Event::DiagnosticsUpdated => {
20612 self.update_diagnostics_state(window, cx);
20613 }
20614 _ => {}
20615 };
20616 }
20617
20618 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20619 if !self.diagnostics_enabled() {
20620 return;
20621 }
20622 self.refresh_active_diagnostics(cx);
20623 self.refresh_inline_diagnostics(true, window, cx);
20624 self.scrollbar_marker_state.dirty = true;
20625 cx.notify();
20626 }
20627
20628 pub fn start_temporary_diff_override(&mut self) {
20629 self.load_diff_task.take();
20630 self.temporary_diff_override = true;
20631 }
20632
20633 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20634 self.temporary_diff_override = false;
20635 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20636 self.buffer.update(cx, |buffer, cx| {
20637 buffer.set_all_diff_hunks_collapsed(cx);
20638 });
20639
20640 if let Some(project) = self.project.clone() {
20641 self.load_diff_task = Some(
20642 update_uncommitted_diff_for_buffer(
20643 cx.entity(),
20644 &project,
20645 self.buffer.read(cx).all_buffers(),
20646 self.buffer.clone(),
20647 cx,
20648 )
20649 .shared(),
20650 );
20651 }
20652 }
20653
20654 fn on_display_map_changed(
20655 &mut self,
20656 _: Entity<DisplayMap>,
20657 _: &mut Window,
20658 cx: &mut Context<Self>,
20659 ) {
20660 cx.notify();
20661 }
20662
20663 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20664 if self.diagnostics_enabled() {
20665 let new_severity = EditorSettings::get_global(cx)
20666 .diagnostics_max_severity
20667 .unwrap_or(DiagnosticSeverity::Hint);
20668 self.set_max_diagnostics_severity(new_severity, cx);
20669 }
20670 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20671 self.update_edit_prediction_settings(cx);
20672 self.refresh_edit_prediction(true, false, window, cx);
20673 self.refresh_inline_values(cx);
20674 self.refresh_inlay_hints(
20675 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20676 self.selections.newest_anchor().head(),
20677 &self.buffer.read(cx).snapshot(cx),
20678 cx,
20679 )),
20680 cx,
20681 );
20682
20683 let old_cursor_shape = self.cursor_shape;
20684 let old_show_breadcrumbs = self.show_breadcrumbs;
20685
20686 {
20687 let editor_settings = EditorSettings::get_global(cx);
20688 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20689 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20690 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20691 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20692 }
20693
20694 if old_cursor_shape != self.cursor_shape {
20695 cx.emit(EditorEvent::CursorShapeChanged);
20696 }
20697
20698 if old_show_breadcrumbs != self.show_breadcrumbs {
20699 cx.emit(EditorEvent::BreadcrumbsChanged);
20700 }
20701
20702 let project_settings = ProjectSettings::get_global(cx);
20703 self.serialize_dirty_buffers =
20704 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20705
20706 if self.mode.is_full() {
20707 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20708 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20709 if self.show_inline_diagnostics != show_inline_diagnostics {
20710 self.show_inline_diagnostics = show_inline_diagnostics;
20711 self.refresh_inline_diagnostics(false, window, cx);
20712 }
20713
20714 if self.git_blame_inline_enabled != inline_blame_enabled {
20715 self.toggle_git_blame_inline_internal(false, window, cx);
20716 }
20717
20718 let minimap_settings = EditorSettings::get_global(cx).minimap;
20719 if self.minimap_visibility != MinimapVisibility::Disabled {
20720 if self.minimap_visibility.settings_visibility()
20721 != minimap_settings.minimap_enabled()
20722 {
20723 self.set_minimap_visibility(
20724 MinimapVisibility::for_mode(self.mode(), cx),
20725 window,
20726 cx,
20727 );
20728 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20729 minimap_entity.update(cx, |minimap_editor, cx| {
20730 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20731 })
20732 }
20733 }
20734 }
20735
20736 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20737 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20738 }) {
20739 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20740 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20741 }
20742 self.refresh_colors(false, None, window, cx);
20743 }
20744
20745 cx.notify();
20746 }
20747
20748 pub fn set_searchable(&mut self, searchable: bool) {
20749 self.searchable = searchable;
20750 }
20751
20752 pub fn searchable(&self) -> bool {
20753 self.searchable
20754 }
20755
20756 fn open_proposed_changes_editor(
20757 &mut self,
20758 _: &OpenProposedChangesEditor,
20759 window: &mut Window,
20760 cx: &mut Context<Self>,
20761 ) {
20762 let Some(workspace) = self.workspace() else {
20763 cx.propagate();
20764 return;
20765 };
20766
20767 let selections = self.selections.all::<usize>(cx);
20768 let multi_buffer = self.buffer.read(cx);
20769 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20770 let mut new_selections_by_buffer = HashMap::default();
20771 for selection in selections {
20772 for (buffer, range, _) in
20773 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20774 {
20775 let mut range = range.to_point(buffer);
20776 range.start.column = 0;
20777 range.end.column = buffer.line_len(range.end.row);
20778 new_selections_by_buffer
20779 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20780 .or_insert(Vec::new())
20781 .push(range)
20782 }
20783 }
20784
20785 let proposed_changes_buffers = new_selections_by_buffer
20786 .into_iter()
20787 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20788 .collect::<Vec<_>>();
20789 let proposed_changes_editor = cx.new(|cx| {
20790 ProposedChangesEditor::new(
20791 "Proposed changes",
20792 proposed_changes_buffers,
20793 self.project.clone(),
20794 window,
20795 cx,
20796 )
20797 });
20798
20799 window.defer(cx, move |window, cx| {
20800 workspace.update(cx, |workspace, cx| {
20801 workspace.active_pane().update(cx, |pane, cx| {
20802 pane.add_item(
20803 Box::new(proposed_changes_editor),
20804 true,
20805 true,
20806 None,
20807 window,
20808 cx,
20809 );
20810 });
20811 });
20812 });
20813 }
20814
20815 pub fn open_excerpts_in_split(
20816 &mut self,
20817 _: &OpenExcerptsSplit,
20818 window: &mut Window,
20819 cx: &mut Context<Self>,
20820 ) {
20821 self.open_excerpts_common(None, true, window, cx)
20822 }
20823
20824 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20825 self.open_excerpts_common(None, false, window, cx)
20826 }
20827
20828 fn open_excerpts_common(
20829 &mut self,
20830 jump_data: Option<JumpData>,
20831 split: bool,
20832 window: &mut Window,
20833 cx: &mut Context<Self>,
20834 ) {
20835 let Some(workspace) = self.workspace() else {
20836 cx.propagate();
20837 return;
20838 };
20839
20840 if self.buffer.read(cx).is_singleton() {
20841 cx.propagate();
20842 return;
20843 }
20844
20845 let mut new_selections_by_buffer = HashMap::default();
20846 match &jump_data {
20847 Some(JumpData::MultiBufferPoint {
20848 excerpt_id,
20849 position,
20850 anchor,
20851 line_offset_from_top,
20852 }) => {
20853 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20854 if let Some(buffer) = multi_buffer_snapshot
20855 .buffer_id_for_excerpt(*excerpt_id)
20856 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20857 {
20858 let buffer_snapshot = buffer.read(cx).snapshot();
20859 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20860 language::ToPoint::to_point(anchor, &buffer_snapshot)
20861 } else {
20862 buffer_snapshot.clip_point(*position, Bias::Left)
20863 };
20864 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20865 new_selections_by_buffer.insert(
20866 buffer,
20867 (
20868 vec![jump_to_offset..jump_to_offset],
20869 Some(*line_offset_from_top),
20870 ),
20871 );
20872 }
20873 }
20874 Some(JumpData::MultiBufferRow {
20875 row,
20876 line_offset_from_top,
20877 }) => {
20878 let point = MultiBufferPoint::new(row.0, 0);
20879 if let Some((buffer, buffer_point, _)) =
20880 self.buffer.read(cx).point_to_buffer_point(point, cx)
20881 {
20882 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20883 new_selections_by_buffer
20884 .entry(buffer)
20885 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20886 .0
20887 .push(buffer_offset..buffer_offset)
20888 }
20889 }
20890 None => {
20891 let selections = self.selections.all::<usize>(cx);
20892 let multi_buffer = self.buffer.read(cx);
20893 for selection in selections {
20894 for (snapshot, range, _, anchor) in multi_buffer
20895 .snapshot(cx)
20896 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20897 {
20898 if let Some(anchor) = anchor {
20899 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20900 else {
20901 continue;
20902 };
20903 let offset = text::ToOffset::to_offset(
20904 &anchor.text_anchor,
20905 &buffer_handle.read(cx).snapshot(),
20906 );
20907 let range = offset..offset;
20908 new_selections_by_buffer
20909 .entry(buffer_handle)
20910 .or_insert((Vec::new(), None))
20911 .0
20912 .push(range)
20913 } else {
20914 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20915 else {
20916 continue;
20917 };
20918 new_selections_by_buffer
20919 .entry(buffer_handle)
20920 .or_insert((Vec::new(), None))
20921 .0
20922 .push(range)
20923 }
20924 }
20925 }
20926 }
20927 }
20928
20929 new_selections_by_buffer
20930 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20931
20932 if new_selections_by_buffer.is_empty() {
20933 return;
20934 }
20935
20936 // We defer the pane interaction because we ourselves are a workspace item
20937 // and activating a new item causes the pane to call a method on us reentrantly,
20938 // which panics if we're on the stack.
20939 window.defer(cx, move |window, cx| {
20940 workspace.update(cx, |workspace, cx| {
20941 let pane = if split {
20942 workspace.adjacent_pane(window, cx)
20943 } else {
20944 workspace.active_pane().clone()
20945 };
20946
20947 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20948 let editor = buffer
20949 .read(cx)
20950 .file()
20951 .is_none()
20952 .then(|| {
20953 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20954 // so `workspace.open_project_item` will never find them, always opening a new editor.
20955 // Instead, we try to activate the existing editor in the pane first.
20956 let (editor, pane_item_index) =
20957 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20958 let editor = item.downcast::<Editor>()?;
20959 let singleton_buffer =
20960 editor.read(cx).buffer().read(cx).as_singleton()?;
20961 if singleton_buffer == buffer {
20962 Some((editor, i))
20963 } else {
20964 None
20965 }
20966 })?;
20967 pane.update(cx, |pane, cx| {
20968 pane.activate_item(pane_item_index, true, true, window, cx)
20969 });
20970 Some(editor)
20971 })
20972 .flatten()
20973 .unwrap_or_else(|| {
20974 workspace.open_project_item::<Self>(
20975 pane.clone(),
20976 buffer,
20977 true,
20978 true,
20979 window,
20980 cx,
20981 )
20982 });
20983
20984 editor.update(cx, |editor, cx| {
20985 let autoscroll = match scroll_offset {
20986 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20987 None => Autoscroll::newest(),
20988 };
20989 let nav_history = editor.nav_history.take();
20990 editor.change_selections(
20991 SelectionEffects::scroll(autoscroll),
20992 window,
20993 cx,
20994 |s| {
20995 s.select_ranges(ranges);
20996 },
20997 );
20998 editor.nav_history = nav_history;
20999 });
21000 }
21001 })
21002 });
21003 }
21004
21005 // For now, don't allow opening excerpts in buffers that aren't backed by
21006 // regular project files.
21007 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21008 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21009 }
21010
21011 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21012 let snapshot = self.buffer.read(cx).read(cx);
21013 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21014 Some(
21015 ranges
21016 .iter()
21017 .map(move |range| {
21018 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21019 })
21020 .collect(),
21021 )
21022 }
21023
21024 fn selection_replacement_ranges(
21025 &self,
21026 range: Range<OffsetUtf16>,
21027 cx: &mut App,
21028 ) -> Vec<Range<OffsetUtf16>> {
21029 let selections = self.selections.all::<OffsetUtf16>(cx);
21030 let newest_selection = selections
21031 .iter()
21032 .max_by_key(|selection| selection.id)
21033 .unwrap();
21034 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21035 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21036 let snapshot = self.buffer.read(cx).read(cx);
21037 selections
21038 .into_iter()
21039 .map(|mut selection| {
21040 selection.start.0 =
21041 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21042 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21043 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21044 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21045 })
21046 .collect()
21047 }
21048
21049 fn report_editor_event(
21050 &self,
21051 reported_event: ReportEditorEvent,
21052 file_extension: Option<String>,
21053 cx: &App,
21054 ) {
21055 if cfg!(any(test, feature = "test-support")) {
21056 return;
21057 }
21058
21059 let Some(project) = &self.project else { return };
21060
21061 // If None, we are in a file without an extension
21062 let file = self
21063 .buffer
21064 .read(cx)
21065 .as_singleton()
21066 .and_then(|b| b.read(cx).file());
21067 let file_extension = file_extension.or(file
21068 .as_ref()
21069 .and_then(|file| Path::new(file.file_name(cx)).extension())
21070 .and_then(|e| e.to_str())
21071 .map(|a| a.to_string()));
21072
21073 let vim_mode = vim_enabled(cx);
21074
21075 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21076 let copilot_enabled = edit_predictions_provider
21077 == language::language_settings::EditPredictionProvider::Copilot;
21078 let copilot_enabled_for_language = self
21079 .buffer
21080 .read(cx)
21081 .language_settings(cx)
21082 .show_edit_predictions;
21083
21084 let project = project.read(cx);
21085 let event_type = reported_event.event_type();
21086
21087 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21088 telemetry::event!(
21089 event_type,
21090 type = if auto_saved {"autosave"} else {"manual"},
21091 file_extension,
21092 vim_mode,
21093 copilot_enabled,
21094 copilot_enabled_for_language,
21095 edit_predictions_provider,
21096 is_via_ssh = project.is_via_remote_server(),
21097 );
21098 } else {
21099 telemetry::event!(
21100 event_type,
21101 file_extension,
21102 vim_mode,
21103 copilot_enabled,
21104 copilot_enabled_for_language,
21105 edit_predictions_provider,
21106 is_via_ssh = project.is_via_remote_server(),
21107 );
21108 };
21109 }
21110
21111 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21112 /// with each line being an array of {text, highlight} objects.
21113 fn copy_highlight_json(
21114 &mut self,
21115 _: &CopyHighlightJson,
21116 window: &mut Window,
21117 cx: &mut Context<Self>,
21118 ) {
21119 #[derive(Serialize)]
21120 struct Chunk<'a> {
21121 text: String,
21122 highlight: Option<&'a str>,
21123 }
21124
21125 let snapshot = self.buffer.read(cx).snapshot(cx);
21126 let range = self
21127 .selected_text_range(false, window, cx)
21128 .and_then(|selection| {
21129 if selection.range.is_empty() {
21130 None
21131 } else {
21132 Some(selection.range)
21133 }
21134 })
21135 .unwrap_or_else(|| 0..snapshot.len());
21136
21137 let chunks = snapshot.chunks(range, true);
21138 let mut lines = Vec::new();
21139 let mut line: VecDeque<Chunk> = VecDeque::new();
21140
21141 let Some(style) = self.style.as_ref() else {
21142 return;
21143 };
21144
21145 for chunk in chunks {
21146 let highlight = chunk
21147 .syntax_highlight_id
21148 .and_then(|id| id.name(&style.syntax));
21149 let mut chunk_lines = chunk.text.split('\n').peekable();
21150 while let Some(text) = chunk_lines.next() {
21151 let mut merged_with_last_token = false;
21152 if let Some(last_token) = line.back_mut()
21153 && last_token.highlight == highlight
21154 {
21155 last_token.text.push_str(text);
21156 merged_with_last_token = true;
21157 }
21158
21159 if !merged_with_last_token {
21160 line.push_back(Chunk {
21161 text: text.into(),
21162 highlight,
21163 });
21164 }
21165
21166 if chunk_lines.peek().is_some() {
21167 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21168 line.pop_front();
21169 }
21170 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21171 line.pop_back();
21172 }
21173
21174 lines.push(mem::take(&mut line));
21175 }
21176 }
21177 }
21178
21179 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21180 return;
21181 };
21182 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21183 }
21184
21185 pub fn open_context_menu(
21186 &mut self,
21187 _: &OpenContextMenu,
21188 window: &mut Window,
21189 cx: &mut Context<Self>,
21190 ) {
21191 self.request_autoscroll(Autoscroll::newest(), cx);
21192 let position = self.selections.newest_display(cx).start;
21193 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21194 }
21195
21196 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21197 &self.inlay_hint_cache
21198 }
21199
21200 pub fn replay_insert_event(
21201 &mut self,
21202 text: &str,
21203 relative_utf16_range: Option<Range<isize>>,
21204 window: &mut Window,
21205 cx: &mut Context<Self>,
21206 ) {
21207 if !self.input_enabled {
21208 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21209 return;
21210 }
21211 if let Some(relative_utf16_range) = relative_utf16_range {
21212 let selections = self.selections.all::<OffsetUtf16>(cx);
21213 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21214 let new_ranges = selections.into_iter().map(|range| {
21215 let start = OffsetUtf16(
21216 range
21217 .head()
21218 .0
21219 .saturating_add_signed(relative_utf16_range.start),
21220 );
21221 let end = OffsetUtf16(
21222 range
21223 .head()
21224 .0
21225 .saturating_add_signed(relative_utf16_range.end),
21226 );
21227 start..end
21228 });
21229 s.select_ranges(new_ranges);
21230 });
21231 }
21232
21233 self.handle_input(text, window, cx);
21234 }
21235
21236 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21237 let Some(provider) = self.semantics_provider.as_ref() else {
21238 return false;
21239 };
21240
21241 let mut supports = false;
21242 self.buffer().update(cx, |this, cx| {
21243 this.for_each_buffer(|buffer| {
21244 supports |= provider.supports_inlay_hints(buffer, cx);
21245 });
21246 });
21247
21248 supports
21249 }
21250
21251 pub fn is_focused(&self, window: &Window) -> bool {
21252 self.focus_handle.is_focused(window)
21253 }
21254
21255 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21256 cx.emit(EditorEvent::Focused);
21257
21258 if let Some(descendant) = self
21259 .last_focused_descendant
21260 .take()
21261 .and_then(|descendant| descendant.upgrade())
21262 {
21263 window.focus(&descendant);
21264 } else {
21265 if let Some(blame) = self.blame.as_ref() {
21266 blame.update(cx, GitBlame::focus)
21267 }
21268
21269 self.blink_manager.update(cx, BlinkManager::enable);
21270 self.show_cursor_names(window, cx);
21271 self.buffer.update(cx, |buffer, cx| {
21272 buffer.finalize_last_transaction(cx);
21273 if self.leader_id.is_none() {
21274 buffer.set_active_selections(
21275 &self.selections.disjoint_anchors(),
21276 self.selections.line_mode,
21277 self.cursor_shape,
21278 cx,
21279 );
21280 }
21281 });
21282 }
21283 }
21284
21285 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21286 cx.emit(EditorEvent::FocusedIn)
21287 }
21288
21289 fn handle_focus_out(
21290 &mut self,
21291 event: FocusOutEvent,
21292 _window: &mut Window,
21293 cx: &mut Context<Self>,
21294 ) {
21295 if event.blurred != self.focus_handle {
21296 self.last_focused_descendant = Some(event.blurred);
21297 }
21298 self.selection_drag_state = SelectionDragState::None;
21299 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21300 }
21301
21302 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21303 self.blink_manager.update(cx, BlinkManager::disable);
21304 self.buffer
21305 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21306
21307 if let Some(blame) = self.blame.as_ref() {
21308 blame.update(cx, GitBlame::blur)
21309 }
21310 if !self.hover_state.focused(window, cx) {
21311 hide_hover(self, cx);
21312 }
21313 if !self
21314 .context_menu
21315 .borrow()
21316 .as_ref()
21317 .is_some_and(|context_menu| context_menu.focused(window, cx))
21318 {
21319 self.hide_context_menu(window, cx);
21320 }
21321 self.discard_edit_prediction(false, cx);
21322 cx.emit(EditorEvent::Blurred);
21323 cx.notify();
21324 }
21325
21326 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21327 let mut pending: String = window
21328 .pending_input_keystrokes()
21329 .into_iter()
21330 .flatten()
21331 .filter_map(|keystroke| {
21332 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21333 keystroke.key_char.clone()
21334 } else {
21335 None
21336 }
21337 })
21338 .collect();
21339
21340 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21341 pending = "".to_string();
21342 }
21343
21344 let existing_pending = self
21345 .text_highlights::<PendingInput>(cx)
21346 .map(|(_, ranges)| ranges.to_vec());
21347 if existing_pending.is_none() && pending.is_empty() {
21348 return;
21349 }
21350 let transaction =
21351 self.transact(window, cx, |this, window, cx| {
21352 let selections = this.selections.all::<usize>(cx);
21353 let edits = selections
21354 .iter()
21355 .map(|selection| (selection.end..selection.end, pending.clone()));
21356 this.edit(edits, cx);
21357 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21358 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21359 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21360 }));
21361 });
21362 if let Some(existing_ranges) = existing_pending {
21363 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21364 this.edit(edits, cx);
21365 }
21366 });
21367
21368 let snapshot = self.snapshot(window, cx);
21369 let ranges = self
21370 .selections
21371 .all::<usize>(cx)
21372 .into_iter()
21373 .map(|selection| {
21374 snapshot.buffer_snapshot.anchor_after(selection.end)
21375 ..snapshot
21376 .buffer_snapshot
21377 .anchor_before(selection.end + pending.len())
21378 })
21379 .collect();
21380
21381 if pending.is_empty() {
21382 self.clear_highlights::<PendingInput>(cx);
21383 } else {
21384 self.highlight_text::<PendingInput>(
21385 ranges,
21386 HighlightStyle {
21387 underline: Some(UnderlineStyle {
21388 thickness: px(1.),
21389 color: None,
21390 wavy: false,
21391 }),
21392 ..Default::default()
21393 },
21394 cx,
21395 );
21396 }
21397
21398 self.ime_transaction = self.ime_transaction.or(transaction);
21399 if let Some(transaction) = self.ime_transaction {
21400 self.buffer.update(cx, |buffer, cx| {
21401 buffer.group_until_transaction(transaction, cx);
21402 });
21403 }
21404
21405 if self.text_highlights::<PendingInput>(cx).is_none() {
21406 self.ime_transaction.take();
21407 }
21408 }
21409
21410 pub fn register_action_renderer(
21411 &mut self,
21412 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21413 ) -> Subscription {
21414 let id = self.next_editor_action_id.post_inc();
21415 self.editor_actions
21416 .borrow_mut()
21417 .insert(id, Box::new(listener));
21418
21419 let editor_actions = self.editor_actions.clone();
21420 Subscription::new(move || {
21421 editor_actions.borrow_mut().remove(&id);
21422 })
21423 }
21424
21425 pub fn register_action<A: Action>(
21426 &mut self,
21427 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21428 ) -> Subscription {
21429 let id = self.next_editor_action_id.post_inc();
21430 let listener = Arc::new(listener);
21431 self.editor_actions.borrow_mut().insert(
21432 id,
21433 Box::new(move |_, window, _| {
21434 let listener = listener.clone();
21435 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21436 let action = action.downcast_ref().unwrap();
21437 if phase == DispatchPhase::Bubble {
21438 listener(action, window, cx)
21439 }
21440 })
21441 }),
21442 );
21443
21444 let editor_actions = self.editor_actions.clone();
21445 Subscription::new(move || {
21446 editor_actions.borrow_mut().remove(&id);
21447 })
21448 }
21449
21450 pub fn file_header_size(&self) -> u32 {
21451 FILE_HEADER_HEIGHT
21452 }
21453
21454 pub fn restore(
21455 &mut self,
21456 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21457 window: &mut Window,
21458 cx: &mut Context<Self>,
21459 ) {
21460 let workspace = self.workspace();
21461 let project = self.project();
21462 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21463 let mut tasks = Vec::new();
21464 for (buffer_id, changes) in revert_changes {
21465 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21466 buffer.update(cx, |buffer, cx| {
21467 buffer.edit(
21468 changes
21469 .into_iter()
21470 .map(|(range, text)| (range, text.to_string())),
21471 None,
21472 cx,
21473 );
21474 });
21475
21476 if let Some(project) =
21477 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21478 {
21479 project.update(cx, |project, cx| {
21480 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21481 })
21482 }
21483 }
21484 }
21485 tasks
21486 });
21487 cx.spawn_in(window, async move |_, cx| {
21488 for (buffer, task) in save_tasks {
21489 let result = task.await;
21490 if result.is_err() {
21491 let Some(path) = buffer
21492 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21493 .ok()
21494 else {
21495 continue;
21496 };
21497 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21498 let Some(task) = cx
21499 .update_window_entity(workspace, |workspace, window, cx| {
21500 workspace
21501 .open_path_preview(path, None, false, false, false, window, cx)
21502 })
21503 .ok()
21504 else {
21505 continue;
21506 };
21507 task.await.log_err();
21508 }
21509 }
21510 }
21511 })
21512 .detach();
21513 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21514 selections.refresh()
21515 });
21516 }
21517
21518 pub fn to_pixel_point(
21519 &self,
21520 source: multi_buffer::Anchor,
21521 editor_snapshot: &EditorSnapshot,
21522 window: &mut Window,
21523 ) -> Option<gpui::Point<Pixels>> {
21524 let source_point = source.to_display_point(editor_snapshot);
21525 self.display_to_pixel_point(source_point, editor_snapshot, window)
21526 }
21527
21528 pub fn display_to_pixel_point(
21529 &self,
21530 source: DisplayPoint,
21531 editor_snapshot: &EditorSnapshot,
21532 window: &mut Window,
21533 ) -> Option<gpui::Point<Pixels>> {
21534 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21535 let text_layout_details = self.text_layout_details(window);
21536 let scroll_top = text_layout_details
21537 .scroll_anchor
21538 .scroll_position(editor_snapshot)
21539 .y;
21540
21541 if source.row().as_f32() < scroll_top.floor() {
21542 return None;
21543 }
21544 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21545 let source_y = line_height * (source.row().as_f32() - scroll_top);
21546 Some(gpui::Point::new(source_x, source_y))
21547 }
21548
21549 pub fn has_visible_completions_menu(&self) -> bool {
21550 !self.edit_prediction_preview_is_active()
21551 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21552 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21553 })
21554 }
21555
21556 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21557 if self.mode.is_minimap() {
21558 return;
21559 }
21560 self.addons
21561 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21562 }
21563
21564 pub fn unregister_addon<T: Addon>(&mut self) {
21565 self.addons.remove(&std::any::TypeId::of::<T>());
21566 }
21567
21568 pub fn addon<T: Addon>(&self) -> Option<&T> {
21569 let type_id = std::any::TypeId::of::<T>();
21570 self.addons
21571 .get(&type_id)
21572 .and_then(|item| item.to_any().downcast_ref::<T>())
21573 }
21574
21575 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21576 let type_id = std::any::TypeId::of::<T>();
21577 self.addons
21578 .get_mut(&type_id)
21579 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21580 }
21581
21582 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21583 let text_layout_details = self.text_layout_details(window);
21584 let style = &text_layout_details.editor_style;
21585 let font_id = window.text_system().resolve_font(&style.text.font());
21586 let font_size = style.text.font_size.to_pixels(window.rem_size());
21587 let line_height = style.text.line_height_in_pixels(window.rem_size());
21588 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21589 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21590
21591 CharacterDimensions {
21592 em_width,
21593 em_advance,
21594 line_height,
21595 }
21596 }
21597
21598 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21599 self.load_diff_task.clone()
21600 }
21601
21602 fn read_metadata_from_db(
21603 &mut self,
21604 item_id: u64,
21605 workspace_id: WorkspaceId,
21606 window: &mut Window,
21607 cx: &mut Context<Editor>,
21608 ) {
21609 if self.is_singleton(cx)
21610 && !self.mode.is_minimap()
21611 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21612 {
21613 let buffer_snapshot = OnceCell::new();
21614
21615 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21616 && !folds.is_empty()
21617 {
21618 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21619 self.fold_ranges(
21620 folds
21621 .into_iter()
21622 .map(|(start, end)| {
21623 snapshot.clip_offset(start, Bias::Left)
21624 ..snapshot.clip_offset(end, Bias::Right)
21625 })
21626 .collect(),
21627 false,
21628 window,
21629 cx,
21630 );
21631 }
21632
21633 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21634 && !selections.is_empty()
21635 {
21636 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21637 // skip adding the initial selection to selection history
21638 self.selection_history.mode = SelectionHistoryMode::Skipping;
21639 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21640 s.select_ranges(selections.into_iter().map(|(start, end)| {
21641 snapshot.clip_offset(start, Bias::Left)
21642 ..snapshot.clip_offset(end, Bias::Right)
21643 }));
21644 });
21645 self.selection_history.mode = SelectionHistoryMode::Normal;
21646 };
21647 }
21648
21649 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21650 }
21651
21652 fn update_lsp_data(
21653 &mut self,
21654 ignore_cache: bool,
21655 for_buffer: Option<BufferId>,
21656 window: &mut Window,
21657 cx: &mut Context<'_, Self>,
21658 ) {
21659 self.pull_diagnostics(for_buffer, window, cx);
21660 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21661 }
21662}
21663
21664// todo(settings_refactor) this should not be!
21665fn vim_enabled(cx: &App) -> bool {
21666 cx.global::<SettingsStore>()
21667 .raw_user_settings()
21668 .and_then(|settings| settings.content.vim_mode)
21669 == Some(true)
21670}
21671
21672fn process_completion_for_edit(
21673 completion: &Completion,
21674 intent: CompletionIntent,
21675 buffer: &Entity<Buffer>,
21676 cursor_position: &text::Anchor,
21677 cx: &mut Context<Editor>,
21678) -> CompletionEdit {
21679 let buffer = buffer.read(cx);
21680 let buffer_snapshot = buffer.snapshot();
21681 let (snippet, new_text) = if completion.is_snippet() {
21682 // Workaround for typescript language server issues so that methods don't expand within
21683 // strings and functions with type expressions. The previous point is used because the query
21684 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21685 let mut snippet_source = completion.new_text.clone();
21686 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21687 previous_point.column = previous_point.column.saturating_sub(1);
21688 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21689 && scope.prefers_label_for_snippet_in_completion()
21690 && let Some(label) = completion.label()
21691 && matches!(
21692 completion.kind(),
21693 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21694 )
21695 {
21696 snippet_source = label;
21697 }
21698 match Snippet::parse(&snippet_source).log_err() {
21699 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21700 None => (None, completion.new_text.clone()),
21701 }
21702 } else {
21703 (None, completion.new_text.clone())
21704 };
21705
21706 let mut range_to_replace = {
21707 let replace_range = &completion.replace_range;
21708 if let CompletionSource::Lsp {
21709 insert_range: Some(insert_range),
21710 ..
21711 } = &completion.source
21712 {
21713 debug_assert_eq!(
21714 insert_range.start, replace_range.start,
21715 "insert_range and replace_range should start at the same position"
21716 );
21717 debug_assert!(
21718 insert_range
21719 .start
21720 .cmp(cursor_position, &buffer_snapshot)
21721 .is_le(),
21722 "insert_range should start before or at cursor position"
21723 );
21724 debug_assert!(
21725 replace_range
21726 .start
21727 .cmp(cursor_position, &buffer_snapshot)
21728 .is_le(),
21729 "replace_range should start before or at cursor position"
21730 );
21731
21732 let should_replace = match intent {
21733 CompletionIntent::CompleteWithInsert => false,
21734 CompletionIntent::CompleteWithReplace => true,
21735 CompletionIntent::Complete | CompletionIntent::Compose => {
21736 let insert_mode =
21737 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21738 .completions
21739 .lsp_insert_mode;
21740 match insert_mode {
21741 LspInsertMode::Insert => false,
21742 LspInsertMode::Replace => true,
21743 LspInsertMode::ReplaceSubsequence => {
21744 let mut text_to_replace = buffer.chars_for_range(
21745 buffer.anchor_before(replace_range.start)
21746 ..buffer.anchor_after(replace_range.end),
21747 );
21748 let mut current_needle = text_to_replace.next();
21749 for haystack_ch in completion.label.text.chars() {
21750 if let Some(needle_ch) = current_needle
21751 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21752 {
21753 current_needle = text_to_replace.next();
21754 }
21755 }
21756 current_needle.is_none()
21757 }
21758 LspInsertMode::ReplaceSuffix => {
21759 if replace_range
21760 .end
21761 .cmp(cursor_position, &buffer_snapshot)
21762 .is_gt()
21763 {
21764 let range_after_cursor = *cursor_position..replace_range.end;
21765 let text_after_cursor = buffer
21766 .text_for_range(
21767 buffer.anchor_before(range_after_cursor.start)
21768 ..buffer.anchor_after(range_after_cursor.end),
21769 )
21770 .collect::<String>()
21771 .to_ascii_lowercase();
21772 completion
21773 .label
21774 .text
21775 .to_ascii_lowercase()
21776 .ends_with(&text_after_cursor)
21777 } else {
21778 true
21779 }
21780 }
21781 }
21782 }
21783 };
21784
21785 if should_replace {
21786 replace_range.clone()
21787 } else {
21788 insert_range.clone()
21789 }
21790 } else {
21791 replace_range.clone()
21792 }
21793 };
21794
21795 if range_to_replace
21796 .end
21797 .cmp(cursor_position, &buffer_snapshot)
21798 .is_lt()
21799 {
21800 range_to_replace.end = *cursor_position;
21801 }
21802
21803 CompletionEdit {
21804 new_text,
21805 replace_range: range_to_replace.to_offset(buffer),
21806 snippet,
21807 }
21808}
21809
21810struct CompletionEdit {
21811 new_text: String,
21812 replace_range: Range<usize>,
21813 snippet: Option<Snippet>,
21814}
21815
21816fn insert_extra_newline_brackets(
21817 buffer: &MultiBufferSnapshot,
21818 range: Range<usize>,
21819 language: &language::LanguageScope,
21820) -> bool {
21821 let leading_whitespace_len = buffer
21822 .reversed_chars_at(range.start)
21823 .take_while(|c| c.is_whitespace() && *c != '\n')
21824 .map(|c| c.len_utf8())
21825 .sum::<usize>();
21826 let trailing_whitespace_len = buffer
21827 .chars_at(range.end)
21828 .take_while(|c| c.is_whitespace() && *c != '\n')
21829 .map(|c| c.len_utf8())
21830 .sum::<usize>();
21831 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21832
21833 language.brackets().any(|(pair, enabled)| {
21834 let pair_start = pair.start.trim_end();
21835 let pair_end = pair.end.trim_start();
21836
21837 enabled
21838 && pair.newline
21839 && buffer.contains_str_at(range.end, pair_end)
21840 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21841 })
21842}
21843
21844fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21845 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21846 [(buffer, range, _)] => (*buffer, range.clone()),
21847 _ => return false,
21848 };
21849 let pair = {
21850 let mut result: Option<BracketMatch> = None;
21851
21852 for pair in buffer
21853 .all_bracket_ranges(range.clone())
21854 .filter(move |pair| {
21855 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21856 })
21857 {
21858 let len = pair.close_range.end - pair.open_range.start;
21859
21860 if let Some(existing) = &result {
21861 let existing_len = existing.close_range.end - existing.open_range.start;
21862 if len > existing_len {
21863 continue;
21864 }
21865 }
21866
21867 result = Some(pair);
21868 }
21869
21870 result
21871 };
21872 let Some(pair) = pair else {
21873 return false;
21874 };
21875 pair.newline_only
21876 && buffer
21877 .chars_for_range(pair.open_range.end..range.start)
21878 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21879 .all(|c| c.is_whitespace() && c != '\n')
21880}
21881
21882fn update_uncommitted_diff_for_buffer(
21883 editor: Entity<Editor>,
21884 project: &Entity<Project>,
21885 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21886 buffer: Entity<MultiBuffer>,
21887 cx: &mut App,
21888) -> Task<()> {
21889 let mut tasks = Vec::new();
21890 project.update(cx, |project, cx| {
21891 for buffer in buffers {
21892 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21893 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21894 }
21895 }
21896 });
21897 cx.spawn(async move |cx| {
21898 let diffs = future::join_all(tasks).await;
21899 if editor
21900 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21901 .unwrap_or(false)
21902 {
21903 return;
21904 }
21905
21906 buffer
21907 .update(cx, |buffer, cx| {
21908 for diff in diffs.into_iter().flatten() {
21909 buffer.add_diff(diff, cx);
21910 }
21911 })
21912 .ok();
21913 })
21914}
21915
21916fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21917 let tab_size = tab_size.get() as usize;
21918 let mut width = offset;
21919
21920 for ch in text.chars() {
21921 width += if ch == '\t' {
21922 tab_size - (width % tab_size)
21923 } else {
21924 1
21925 };
21926 }
21927
21928 width - offset
21929}
21930
21931#[cfg(test)]
21932mod tests {
21933 use super::*;
21934
21935 #[test]
21936 fn test_string_size_with_expanded_tabs() {
21937 let nz = |val| NonZeroU32::new(val).unwrap();
21938 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21939 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21940 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21941 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21942 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21943 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21944 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21945 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21946 }
21947}
21948
21949/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21950struct WordBreakingTokenizer<'a> {
21951 input: &'a str,
21952}
21953
21954impl<'a> WordBreakingTokenizer<'a> {
21955 fn new(input: &'a str) -> Self {
21956 Self { input }
21957 }
21958}
21959
21960fn is_char_ideographic(ch: char) -> bool {
21961 use unicode_script::Script::*;
21962 use unicode_script::UnicodeScript;
21963 matches!(ch.script(), Han | Tangut | Yi)
21964}
21965
21966fn is_grapheme_ideographic(text: &str) -> bool {
21967 text.chars().any(is_char_ideographic)
21968}
21969
21970fn is_grapheme_whitespace(text: &str) -> bool {
21971 text.chars().any(|x| x.is_whitespace())
21972}
21973
21974fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21975 text.chars()
21976 .next()
21977 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21978}
21979
21980#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21981enum WordBreakToken<'a> {
21982 Word { token: &'a str, grapheme_len: usize },
21983 InlineWhitespace { token: &'a str, grapheme_len: usize },
21984 Newline,
21985}
21986
21987impl<'a> Iterator for WordBreakingTokenizer<'a> {
21988 /// Yields a span, the count of graphemes in the token, and whether it was
21989 /// whitespace. Note that it also breaks at word boundaries.
21990 type Item = WordBreakToken<'a>;
21991
21992 fn next(&mut self) -> Option<Self::Item> {
21993 use unicode_segmentation::UnicodeSegmentation;
21994 if self.input.is_empty() {
21995 return None;
21996 }
21997
21998 let mut iter = self.input.graphemes(true).peekable();
21999 let mut offset = 0;
22000 let mut grapheme_len = 0;
22001 if let Some(first_grapheme) = iter.next() {
22002 let is_newline = first_grapheme == "\n";
22003 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22004 offset += first_grapheme.len();
22005 grapheme_len += 1;
22006 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22007 if let Some(grapheme) = iter.peek().copied()
22008 && should_stay_with_preceding_ideograph(grapheme)
22009 {
22010 offset += grapheme.len();
22011 grapheme_len += 1;
22012 }
22013 } else {
22014 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22015 let mut next_word_bound = words.peek().copied();
22016 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22017 next_word_bound = words.next();
22018 }
22019 while let Some(grapheme) = iter.peek().copied() {
22020 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22021 break;
22022 };
22023 if is_grapheme_whitespace(grapheme) != is_whitespace
22024 || (grapheme == "\n") != is_newline
22025 {
22026 break;
22027 };
22028 offset += grapheme.len();
22029 grapheme_len += 1;
22030 iter.next();
22031 }
22032 }
22033 let token = &self.input[..offset];
22034 self.input = &self.input[offset..];
22035 if token == "\n" {
22036 Some(WordBreakToken::Newline)
22037 } else if is_whitespace {
22038 Some(WordBreakToken::InlineWhitespace {
22039 token,
22040 grapheme_len,
22041 })
22042 } else {
22043 Some(WordBreakToken::Word {
22044 token,
22045 grapheme_len,
22046 })
22047 }
22048 } else {
22049 None
22050 }
22051 }
22052}
22053
22054#[test]
22055fn test_word_breaking_tokenizer() {
22056 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22057 ("", &[]),
22058 (" ", &[whitespace(" ", 2)]),
22059 ("Ʒ", &[word("Ʒ", 1)]),
22060 ("Ǽ", &[word("Ǽ", 1)]),
22061 ("⋑", &[word("⋑", 1)]),
22062 ("⋑⋑", &[word("⋑⋑", 2)]),
22063 (
22064 "原理,进而",
22065 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22066 ),
22067 (
22068 "hello world",
22069 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22070 ),
22071 (
22072 "hello, world",
22073 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22074 ),
22075 (
22076 " hello world",
22077 &[
22078 whitespace(" ", 2),
22079 word("hello", 5),
22080 whitespace(" ", 1),
22081 word("world", 5),
22082 ],
22083 ),
22084 (
22085 "这是什么 \n 钢笔",
22086 &[
22087 word("这", 1),
22088 word("是", 1),
22089 word("什", 1),
22090 word("么", 1),
22091 whitespace(" ", 1),
22092 newline(),
22093 whitespace(" ", 1),
22094 word("钢", 1),
22095 word("笔", 1),
22096 ],
22097 ),
22098 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22099 ];
22100
22101 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22102 WordBreakToken::Word {
22103 token,
22104 grapheme_len,
22105 }
22106 }
22107
22108 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22109 WordBreakToken::InlineWhitespace {
22110 token,
22111 grapheme_len,
22112 }
22113 }
22114
22115 fn newline() -> WordBreakToken<'static> {
22116 WordBreakToken::Newline
22117 }
22118
22119 for (input, result) in tests {
22120 assert_eq!(
22121 WordBreakingTokenizer::new(input)
22122 .collect::<Vec<_>>()
22123 .as_slice(),
22124 *result,
22125 );
22126 }
22127}
22128
22129fn wrap_with_prefix(
22130 first_line_prefix: String,
22131 subsequent_lines_prefix: String,
22132 unwrapped_text: String,
22133 wrap_column: usize,
22134 tab_size: NonZeroU32,
22135 preserve_existing_whitespace: bool,
22136) -> String {
22137 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22138 let subsequent_lines_prefix_len =
22139 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22140 let mut wrapped_text = String::new();
22141 let mut current_line = first_line_prefix;
22142 let mut is_first_line = true;
22143
22144 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22145 let mut current_line_len = first_line_prefix_len;
22146 let mut in_whitespace = false;
22147 for token in tokenizer {
22148 let have_preceding_whitespace = in_whitespace;
22149 match token {
22150 WordBreakToken::Word {
22151 token,
22152 grapheme_len,
22153 } => {
22154 in_whitespace = false;
22155 let current_prefix_len = if is_first_line {
22156 first_line_prefix_len
22157 } else {
22158 subsequent_lines_prefix_len
22159 };
22160 if current_line_len + grapheme_len > wrap_column
22161 && current_line_len != current_prefix_len
22162 {
22163 wrapped_text.push_str(current_line.trim_end());
22164 wrapped_text.push('\n');
22165 is_first_line = false;
22166 current_line = subsequent_lines_prefix.clone();
22167 current_line_len = subsequent_lines_prefix_len;
22168 }
22169 current_line.push_str(token);
22170 current_line_len += grapheme_len;
22171 }
22172 WordBreakToken::InlineWhitespace {
22173 mut token,
22174 mut grapheme_len,
22175 } => {
22176 in_whitespace = true;
22177 if have_preceding_whitespace && !preserve_existing_whitespace {
22178 continue;
22179 }
22180 if !preserve_existing_whitespace {
22181 token = " ";
22182 grapheme_len = 1;
22183 }
22184 let current_prefix_len = if is_first_line {
22185 first_line_prefix_len
22186 } else {
22187 subsequent_lines_prefix_len
22188 };
22189 if current_line_len + grapheme_len > wrap_column {
22190 wrapped_text.push_str(current_line.trim_end());
22191 wrapped_text.push('\n');
22192 is_first_line = false;
22193 current_line = subsequent_lines_prefix.clone();
22194 current_line_len = subsequent_lines_prefix_len;
22195 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22196 current_line.push_str(token);
22197 current_line_len += grapheme_len;
22198 }
22199 }
22200 WordBreakToken::Newline => {
22201 in_whitespace = true;
22202 let current_prefix_len = if is_first_line {
22203 first_line_prefix_len
22204 } else {
22205 subsequent_lines_prefix_len
22206 };
22207 if preserve_existing_whitespace {
22208 wrapped_text.push_str(current_line.trim_end());
22209 wrapped_text.push('\n');
22210 is_first_line = false;
22211 current_line = subsequent_lines_prefix.clone();
22212 current_line_len = subsequent_lines_prefix_len;
22213 } else if have_preceding_whitespace {
22214 continue;
22215 } else if current_line_len + 1 > wrap_column
22216 && current_line_len != current_prefix_len
22217 {
22218 wrapped_text.push_str(current_line.trim_end());
22219 wrapped_text.push('\n');
22220 is_first_line = false;
22221 current_line = subsequent_lines_prefix.clone();
22222 current_line_len = subsequent_lines_prefix_len;
22223 } else if current_line_len != current_prefix_len {
22224 current_line.push(' ');
22225 current_line_len += 1;
22226 }
22227 }
22228 }
22229 }
22230
22231 if !current_line.is_empty() {
22232 wrapped_text.push_str(¤t_line);
22233 }
22234 wrapped_text
22235}
22236
22237#[test]
22238fn test_wrap_with_prefix() {
22239 assert_eq!(
22240 wrap_with_prefix(
22241 "# ".to_string(),
22242 "# ".to_string(),
22243 "abcdefg".to_string(),
22244 4,
22245 NonZeroU32::new(4).unwrap(),
22246 false,
22247 ),
22248 "# abcdefg"
22249 );
22250 assert_eq!(
22251 wrap_with_prefix(
22252 "".to_string(),
22253 "".to_string(),
22254 "\thello world".to_string(),
22255 8,
22256 NonZeroU32::new(4).unwrap(),
22257 false,
22258 ),
22259 "hello\nworld"
22260 );
22261 assert_eq!(
22262 wrap_with_prefix(
22263 "// ".to_string(),
22264 "// ".to_string(),
22265 "xx \nyy zz aa bb cc".to_string(),
22266 12,
22267 NonZeroU32::new(4).unwrap(),
22268 false,
22269 ),
22270 "// xx yy zz\n// aa bb cc"
22271 );
22272 assert_eq!(
22273 wrap_with_prefix(
22274 String::new(),
22275 String::new(),
22276 "这是什么 \n 钢笔".to_string(),
22277 3,
22278 NonZeroU32::new(4).unwrap(),
22279 false,
22280 ),
22281 "这是什\n么 钢\n笔"
22282 );
22283}
22284
22285pub trait CollaborationHub {
22286 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22287 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22288 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22289}
22290
22291impl CollaborationHub for Entity<Project> {
22292 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22293 self.read(cx).collaborators()
22294 }
22295
22296 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22297 self.read(cx).user_store().read(cx).participant_indices()
22298 }
22299
22300 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22301 let this = self.read(cx);
22302 let user_ids = this.collaborators().values().map(|c| c.user_id);
22303 this.user_store().read(cx).participant_names(user_ids, cx)
22304 }
22305}
22306
22307pub trait SemanticsProvider {
22308 fn hover(
22309 &self,
22310 buffer: &Entity<Buffer>,
22311 position: text::Anchor,
22312 cx: &mut App,
22313 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22314
22315 fn inline_values(
22316 &self,
22317 buffer_handle: Entity<Buffer>,
22318 range: Range<text::Anchor>,
22319 cx: &mut App,
22320 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22321
22322 fn inlay_hints(
22323 &self,
22324 buffer_handle: Entity<Buffer>,
22325 range: Range<text::Anchor>,
22326 cx: &mut App,
22327 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22328
22329 fn resolve_inlay_hint(
22330 &self,
22331 hint: InlayHint,
22332 buffer_handle: Entity<Buffer>,
22333 server_id: LanguageServerId,
22334 cx: &mut App,
22335 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22336
22337 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22338
22339 fn document_highlights(
22340 &self,
22341 buffer: &Entity<Buffer>,
22342 position: text::Anchor,
22343 cx: &mut App,
22344 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22345
22346 fn definitions(
22347 &self,
22348 buffer: &Entity<Buffer>,
22349 position: text::Anchor,
22350 kind: GotoDefinitionKind,
22351 cx: &mut App,
22352 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22353
22354 fn range_for_rename(
22355 &self,
22356 buffer: &Entity<Buffer>,
22357 position: text::Anchor,
22358 cx: &mut App,
22359 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22360
22361 fn perform_rename(
22362 &self,
22363 buffer: &Entity<Buffer>,
22364 position: text::Anchor,
22365 new_name: String,
22366 cx: &mut App,
22367 ) -> Option<Task<Result<ProjectTransaction>>>;
22368}
22369
22370pub trait CompletionProvider {
22371 fn completions(
22372 &self,
22373 excerpt_id: ExcerptId,
22374 buffer: &Entity<Buffer>,
22375 buffer_position: text::Anchor,
22376 trigger: CompletionContext,
22377 window: &mut Window,
22378 cx: &mut Context<Editor>,
22379 ) -> Task<Result<Vec<CompletionResponse>>>;
22380
22381 fn resolve_completions(
22382 &self,
22383 _buffer: Entity<Buffer>,
22384 _completion_indices: Vec<usize>,
22385 _completions: Rc<RefCell<Box<[Completion]>>>,
22386 _cx: &mut Context<Editor>,
22387 ) -> Task<Result<bool>> {
22388 Task::ready(Ok(false))
22389 }
22390
22391 fn apply_additional_edits_for_completion(
22392 &self,
22393 _buffer: Entity<Buffer>,
22394 _completions: Rc<RefCell<Box<[Completion]>>>,
22395 _completion_index: usize,
22396 _push_to_history: bool,
22397 _cx: &mut Context<Editor>,
22398 ) -> Task<Result<Option<language::Transaction>>> {
22399 Task::ready(Ok(None))
22400 }
22401
22402 fn is_completion_trigger(
22403 &self,
22404 buffer: &Entity<Buffer>,
22405 position: language::Anchor,
22406 text: &str,
22407 trigger_in_words: bool,
22408 menu_is_open: bool,
22409 cx: &mut Context<Editor>,
22410 ) -> bool;
22411
22412 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22413
22414 fn sort_completions(&self) -> bool {
22415 true
22416 }
22417
22418 fn filter_completions(&self) -> bool {
22419 true
22420 }
22421}
22422
22423pub trait CodeActionProvider {
22424 fn id(&self) -> Arc<str>;
22425
22426 fn code_actions(
22427 &self,
22428 buffer: &Entity<Buffer>,
22429 range: Range<text::Anchor>,
22430 window: &mut Window,
22431 cx: &mut App,
22432 ) -> Task<Result<Vec<CodeAction>>>;
22433
22434 fn apply_code_action(
22435 &self,
22436 buffer_handle: Entity<Buffer>,
22437 action: CodeAction,
22438 excerpt_id: ExcerptId,
22439 push_to_history: bool,
22440 window: &mut Window,
22441 cx: &mut App,
22442 ) -> Task<Result<ProjectTransaction>>;
22443}
22444
22445impl CodeActionProvider for Entity<Project> {
22446 fn id(&self) -> Arc<str> {
22447 "project".into()
22448 }
22449
22450 fn code_actions(
22451 &self,
22452 buffer: &Entity<Buffer>,
22453 range: Range<text::Anchor>,
22454 _window: &mut Window,
22455 cx: &mut App,
22456 ) -> Task<Result<Vec<CodeAction>>> {
22457 self.update(cx, |project, cx| {
22458 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22459 let code_actions = project.code_actions(buffer, range, None, cx);
22460 cx.background_spawn(async move {
22461 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22462 Ok(code_lens_actions
22463 .context("code lens fetch")?
22464 .into_iter()
22465 .flatten()
22466 .chain(
22467 code_actions
22468 .context("code action fetch")?
22469 .into_iter()
22470 .flatten(),
22471 )
22472 .collect())
22473 })
22474 })
22475 }
22476
22477 fn apply_code_action(
22478 &self,
22479 buffer_handle: Entity<Buffer>,
22480 action: CodeAction,
22481 _excerpt_id: ExcerptId,
22482 push_to_history: bool,
22483 _window: &mut Window,
22484 cx: &mut App,
22485 ) -> Task<Result<ProjectTransaction>> {
22486 self.update(cx, |project, cx| {
22487 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22488 })
22489 }
22490}
22491
22492fn snippet_completions(
22493 project: &Project,
22494 buffer: &Entity<Buffer>,
22495 buffer_position: text::Anchor,
22496 cx: &mut App,
22497) -> Task<Result<CompletionResponse>> {
22498 let languages = buffer.read(cx).languages_at(buffer_position);
22499 let snippet_store = project.snippets().read(cx);
22500
22501 let scopes: Vec<_> = languages
22502 .iter()
22503 .filter_map(|language| {
22504 let language_name = language.lsp_id();
22505 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22506
22507 if snippets.is_empty() {
22508 None
22509 } else {
22510 Some((language.default_scope(), snippets))
22511 }
22512 })
22513 .collect();
22514
22515 if scopes.is_empty() {
22516 return Task::ready(Ok(CompletionResponse {
22517 completions: vec![],
22518 display_options: CompletionDisplayOptions::default(),
22519 is_incomplete: false,
22520 }));
22521 }
22522
22523 let snapshot = buffer.read(cx).text_snapshot();
22524 let chars: String = snapshot
22525 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22526 .collect();
22527 let executor = cx.background_executor().clone();
22528
22529 cx.background_spawn(async move {
22530 let mut is_incomplete = false;
22531 let mut completions: Vec<Completion> = Vec::new();
22532 for (scope, snippets) in scopes.into_iter() {
22533 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22534 let mut last_word = chars
22535 .chars()
22536 .take_while(|c| classifier.is_word(*c))
22537 .collect::<String>();
22538 last_word = last_word.chars().rev().collect();
22539
22540 if last_word.is_empty() {
22541 return Ok(CompletionResponse {
22542 completions: vec![],
22543 display_options: CompletionDisplayOptions::default(),
22544 is_incomplete: true,
22545 });
22546 }
22547
22548 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22549 let to_lsp = |point: &text::Anchor| {
22550 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22551 point_to_lsp(end)
22552 };
22553 let lsp_end = to_lsp(&buffer_position);
22554
22555 let candidates = snippets
22556 .iter()
22557 .enumerate()
22558 .flat_map(|(ix, snippet)| {
22559 snippet
22560 .prefix
22561 .iter()
22562 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22563 })
22564 .collect::<Vec<StringMatchCandidate>>();
22565
22566 const MAX_RESULTS: usize = 100;
22567 let mut matches = fuzzy::match_strings(
22568 &candidates,
22569 &last_word,
22570 last_word.chars().any(|c| c.is_uppercase()),
22571 true,
22572 MAX_RESULTS,
22573 &Default::default(),
22574 executor.clone(),
22575 )
22576 .await;
22577
22578 if matches.len() >= MAX_RESULTS {
22579 is_incomplete = true;
22580 }
22581
22582 // Remove all candidates where the query's start does not match the start of any word in the candidate
22583 if let Some(query_start) = last_word.chars().next() {
22584 matches.retain(|string_match| {
22585 split_words(&string_match.string).any(|word| {
22586 // Check that the first codepoint of the word as lowercase matches the first
22587 // codepoint of the query as lowercase
22588 word.chars()
22589 .flat_map(|codepoint| codepoint.to_lowercase())
22590 .zip(query_start.to_lowercase())
22591 .all(|(word_cp, query_cp)| word_cp == query_cp)
22592 })
22593 });
22594 }
22595
22596 let matched_strings = matches
22597 .into_iter()
22598 .map(|m| m.string)
22599 .collect::<HashSet<_>>();
22600
22601 completions.extend(snippets.iter().filter_map(|snippet| {
22602 let matching_prefix = snippet
22603 .prefix
22604 .iter()
22605 .find(|prefix| matched_strings.contains(*prefix))?;
22606 let start = as_offset - last_word.len();
22607 let start = snapshot.anchor_before(start);
22608 let range = start..buffer_position;
22609 let lsp_start = to_lsp(&start);
22610 let lsp_range = lsp::Range {
22611 start: lsp_start,
22612 end: lsp_end,
22613 };
22614 Some(Completion {
22615 replace_range: range,
22616 new_text: snippet.body.clone(),
22617 source: CompletionSource::Lsp {
22618 insert_range: None,
22619 server_id: LanguageServerId(usize::MAX),
22620 resolved: true,
22621 lsp_completion: Box::new(lsp::CompletionItem {
22622 label: snippet.prefix.first().unwrap().clone(),
22623 kind: Some(CompletionItemKind::SNIPPET),
22624 label_details: snippet.description.as_ref().map(|description| {
22625 lsp::CompletionItemLabelDetails {
22626 detail: Some(description.clone()),
22627 description: None,
22628 }
22629 }),
22630 insert_text_format: Some(InsertTextFormat::SNIPPET),
22631 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22632 lsp::InsertReplaceEdit {
22633 new_text: snippet.body.clone(),
22634 insert: lsp_range,
22635 replace: lsp_range,
22636 },
22637 )),
22638 filter_text: Some(snippet.body.clone()),
22639 sort_text: Some(char::MAX.to_string()),
22640 ..lsp::CompletionItem::default()
22641 }),
22642 lsp_defaults: None,
22643 },
22644 label: CodeLabel {
22645 text: matching_prefix.clone(),
22646 runs: Vec::new(),
22647 filter_range: 0..matching_prefix.len(),
22648 },
22649 icon_path: None,
22650 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22651 single_line: snippet.name.clone().into(),
22652 plain_text: snippet
22653 .description
22654 .clone()
22655 .map(|description| description.into()),
22656 }),
22657 insert_text_mode: None,
22658 confirm: None,
22659 })
22660 }))
22661 }
22662
22663 Ok(CompletionResponse {
22664 completions,
22665 display_options: CompletionDisplayOptions::default(),
22666 is_incomplete,
22667 })
22668 })
22669}
22670
22671impl CompletionProvider for Entity<Project> {
22672 fn completions(
22673 &self,
22674 _excerpt_id: ExcerptId,
22675 buffer: &Entity<Buffer>,
22676 buffer_position: text::Anchor,
22677 options: CompletionContext,
22678 _window: &mut Window,
22679 cx: &mut Context<Editor>,
22680 ) -> Task<Result<Vec<CompletionResponse>>> {
22681 self.update(cx, |project, cx| {
22682 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22683 let project_completions = project.completions(buffer, buffer_position, options, cx);
22684 cx.background_spawn(async move {
22685 let mut responses = project_completions.await?;
22686 let snippets = snippets.await?;
22687 if !snippets.completions.is_empty() {
22688 responses.push(snippets);
22689 }
22690 Ok(responses)
22691 })
22692 })
22693 }
22694
22695 fn resolve_completions(
22696 &self,
22697 buffer: Entity<Buffer>,
22698 completion_indices: Vec<usize>,
22699 completions: Rc<RefCell<Box<[Completion]>>>,
22700 cx: &mut Context<Editor>,
22701 ) -> Task<Result<bool>> {
22702 self.update(cx, |project, cx| {
22703 project.lsp_store().update(cx, |lsp_store, cx| {
22704 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22705 })
22706 })
22707 }
22708
22709 fn apply_additional_edits_for_completion(
22710 &self,
22711 buffer: Entity<Buffer>,
22712 completions: Rc<RefCell<Box<[Completion]>>>,
22713 completion_index: usize,
22714 push_to_history: bool,
22715 cx: &mut Context<Editor>,
22716 ) -> Task<Result<Option<language::Transaction>>> {
22717 self.update(cx, |project, cx| {
22718 project.lsp_store().update(cx, |lsp_store, cx| {
22719 lsp_store.apply_additional_edits_for_completion(
22720 buffer,
22721 completions,
22722 completion_index,
22723 push_to_history,
22724 cx,
22725 )
22726 })
22727 })
22728 }
22729
22730 fn is_completion_trigger(
22731 &self,
22732 buffer: &Entity<Buffer>,
22733 position: language::Anchor,
22734 text: &str,
22735 trigger_in_words: bool,
22736 menu_is_open: bool,
22737 cx: &mut Context<Editor>,
22738 ) -> bool {
22739 let mut chars = text.chars();
22740 let char = if let Some(char) = chars.next() {
22741 char
22742 } else {
22743 return false;
22744 };
22745 if chars.next().is_some() {
22746 return false;
22747 }
22748
22749 let buffer = buffer.read(cx);
22750 let snapshot = buffer.snapshot();
22751 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22752 return false;
22753 }
22754 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22755 if trigger_in_words && classifier.is_word(char) {
22756 return true;
22757 }
22758
22759 buffer.completion_triggers().contains(text)
22760 }
22761}
22762
22763impl SemanticsProvider for Entity<Project> {
22764 fn hover(
22765 &self,
22766 buffer: &Entity<Buffer>,
22767 position: text::Anchor,
22768 cx: &mut App,
22769 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22770 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22771 }
22772
22773 fn document_highlights(
22774 &self,
22775 buffer: &Entity<Buffer>,
22776 position: text::Anchor,
22777 cx: &mut App,
22778 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22779 Some(self.update(cx, |project, cx| {
22780 project.document_highlights(buffer, position, cx)
22781 }))
22782 }
22783
22784 fn definitions(
22785 &self,
22786 buffer: &Entity<Buffer>,
22787 position: text::Anchor,
22788 kind: GotoDefinitionKind,
22789 cx: &mut App,
22790 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22791 Some(self.update(cx, |project, cx| match kind {
22792 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22793 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22794 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22795 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22796 }))
22797 }
22798
22799 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22800 self.update(cx, |project, cx| {
22801 if project
22802 .active_debug_session(cx)
22803 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22804 {
22805 return true;
22806 }
22807
22808 buffer.update(cx, |buffer, cx| {
22809 project.any_language_server_supports_inlay_hints(buffer, cx)
22810 })
22811 })
22812 }
22813
22814 fn inline_values(
22815 &self,
22816 buffer_handle: Entity<Buffer>,
22817 range: Range<text::Anchor>,
22818 cx: &mut App,
22819 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22820 self.update(cx, |project, cx| {
22821 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22822
22823 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22824 })
22825 }
22826
22827 fn inlay_hints(
22828 &self,
22829 buffer_handle: Entity<Buffer>,
22830 range: Range<text::Anchor>,
22831 cx: &mut App,
22832 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22833 Some(self.update(cx, |project, cx| {
22834 project.inlay_hints(buffer_handle, range, cx)
22835 }))
22836 }
22837
22838 fn resolve_inlay_hint(
22839 &self,
22840 hint: InlayHint,
22841 buffer_handle: Entity<Buffer>,
22842 server_id: LanguageServerId,
22843 cx: &mut App,
22844 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22845 Some(self.update(cx, |project, cx| {
22846 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22847 }))
22848 }
22849
22850 fn range_for_rename(
22851 &self,
22852 buffer: &Entity<Buffer>,
22853 position: text::Anchor,
22854 cx: &mut App,
22855 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22856 Some(self.update(cx, |project, cx| {
22857 let buffer = buffer.clone();
22858 let task = project.prepare_rename(buffer.clone(), position, cx);
22859 cx.spawn(async move |_, cx| {
22860 Ok(match task.await? {
22861 PrepareRenameResponse::Success(range) => Some(range),
22862 PrepareRenameResponse::InvalidPosition => None,
22863 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22864 // Fallback on using TreeSitter info to determine identifier range
22865 buffer.read_with(cx, |buffer, _| {
22866 let snapshot = buffer.snapshot();
22867 let (range, kind) = snapshot.surrounding_word(position, false);
22868 if kind != Some(CharKind::Word) {
22869 return None;
22870 }
22871 Some(
22872 snapshot.anchor_before(range.start)
22873 ..snapshot.anchor_after(range.end),
22874 )
22875 })?
22876 }
22877 })
22878 })
22879 }))
22880 }
22881
22882 fn perform_rename(
22883 &self,
22884 buffer: &Entity<Buffer>,
22885 position: text::Anchor,
22886 new_name: String,
22887 cx: &mut App,
22888 ) -> Option<Task<Result<ProjectTransaction>>> {
22889 Some(self.update(cx, |project, cx| {
22890 project.perform_rename(buffer.clone(), position, new_name, cx)
22891 }))
22892 }
22893}
22894
22895fn inlay_hint_settings(
22896 location: Anchor,
22897 snapshot: &MultiBufferSnapshot,
22898 cx: &mut Context<Editor>,
22899) -> InlayHintSettings {
22900 let file = snapshot.file_at(location);
22901 let language = snapshot.language_at(location).map(|l| l.name());
22902 language_settings(language, file, cx).inlay_hints.clone()
22903}
22904
22905fn consume_contiguous_rows(
22906 contiguous_row_selections: &mut Vec<Selection<Point>>,
22907 selection: &Selection<Point>,
22908 display_map: &DisplaySnapshot,
22909 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22910) -> (MultiBufferRow, MultiBufferRow) {
22911 contiguous_row_selections.push(selection.clone());
22912 let start_row = starting_row(selection, display_map);
22913 let mut end_row = ending_row(selection, display_map);
22914
22915 while let Some(next_selection) = selections.peek() {
22916 if next_selection.start.row <= end_row.0 {
22917 end_row = ending_row(next_selection, display_map);
22918 contiguous_row_selections.push(selections.next().unwrap().clone());
22919 } else {
22920 break;
22921 }
22922 }
22923 (start_row, end_row)
22924}
22925
22926fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22927 if selection.start.column > 0 {
22928 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22929 } else {
22930 MultiBufferRow(selection.start.row)
22931 }
22932}
22933
22934fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22935 if next_selection.end.column > 0 || next_selection.is_empty() {
22936 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22937 } else {
22938 MultiBufferRow(next_selection.end.row)
22939 }
22940}
22941
22942impl EditorSnapshot {
22943 pub fn remote_selections_in_range<'a>(
22944 &'a self,
22945 range: &'a Range<Anchor>,
22946 collaboration_hub: &dyn CollaborationHub,
22947 cx: &'a App,
22948 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22949 let participant_names = collaboration_hub.user_names(cx);
22950 let participant_indices = collaboration_hub.user_participant_indices(cx);
22951 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22952 let collaborators_by_replica_id = collaborators_by_peer_id
22953 .values()
22954 .map(|collaborator| (collaborator.replica_id, collaborator))
22955 .collect::<HashMap<_, _>>();
22956 self.buffer_snapshot
22957 .selections_in_range(range, false)
22958 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22959 if replica_id == AGENT_REPLICA_ID {
22960 Some(RemoteSelection {
22961 replica_id,
22962 selection,
22963 cursor_shape,
22964 line_mode,
22965 collaborator_id: CollaboratorId::Agent,
22966 user_name: Some("Agent".into()),
22967 color: cx.theme().players().agent(),
22968 })
22969 } else {
22970 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22971 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22972 let user_name = participant_names.get(&collaborator.user_id).cloned();
22973 Some(RemoteSelection {
22974 replica_id,
22975 selection,
22976 cursor_shape,
22977 line_mode,
22978 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22979 user_name,
22980 color: if let Some(index) = participant_index {
22981 cx.theme().players().color_for_participant(index.0)
22982 } else {
22983 cx.theme().players().absent()
22984 },
22985 })
22986 }
22987 })
22988 }
22989
22990 pub fn hunks_for_ranges(
22991 &self,
22992 ranges: impl IntoIterator<Item = Range<Point>>,
22993 ) -> Vec<MultiBufferDiffHunk> {
22994 let mut hunks = Vec::new();
22995 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22996 HashMap::default();
22997 for query_range in ranges {
22998 let query_rows =
22999 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23000 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23001 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23002 ) {
23003 // Include deleted hunks that are adjacent to the query range, because
23004 // otherwise they would be missed.
23005 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23006 if hunk.status().is_deleted() {
23007 intersects_range |= hunk.row_range.start == query_rows.end;
23008 intersects_range |= hunk.row_range.end == query_rows.start;
23009 }
23010 if intersects_range {
23011 if !processed_buffer_rows
23012 .entry(hunk.buffer_id)
23013 .or_default()
23014 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23015 {
23016 continue;
23017 }
23018 hunks.push(hunk);
23019 }
23020 }
23021 }
23022
23023 hunks
23024 }
23025
23026 fn display_diff_hunks_for_rows<'a>(
23027 &'a self,
23028 display_rows: Range<DisplayRow>,
23029 folded_buffers: &'a HashSet<BufferId>,
23030 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23031 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23032 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23033
23034 self.buffer_snapshot
23035 .diff_hunks_in_range(buffer_start..buffer_end)
23036 .filter_map(|hunk| {
23037 if folded_buffers.contains(&hunk.buffer_id) {
23038 return None;
23039 }
23040
23041 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23042 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23043
23044 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23045 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23046
23047 let display_hunk = if hunk_display_start.column() != 0 {
23048 DisplayDiffHunk::Folded {
23049 display_row: hunk_display_start.row(),
23050 }
23051 } else {
23052 let mut end_row = hunk_display_end.row();
23053 if hunk_display_end.column() > 0 {
23054 end_row.0 += 1;
23055 }
23056 let is_created_file = hunk.is_created_file();
23057 DisplayDiffHunk::Unfolded {
23058 status: hunk.status(),
23059 diff_base_byte_range: hunk.diff_base_byte_range,
23060 display_row_range: hunk_display_start.row()..end_row,
23061 multi_buffer_range: Anchor::range_in_buffer(
23062 hunk.excerpt_id,
23063 hunk.buffer_id,
23064 hunk.buffer_range,
23065 ),
23066 is_created_file,
23067 }
23068 };
23069
23070 Some(display_hunk)
23071 })
23072 }
23073
23074 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23075 self.display_snapshot.buffer_snapshot.language_at(position)
23076 }
23077
23078 pub fn is_focused(&self) -> bool {
23079 self.is_focused
23080 }
23081
23082 pub fn placeholder_text(&self) -> Option<String> {
23083 self.placeholder_display_snapshot
23084 .as_ref()
23085 .map(|display_map| display_map.text())
23086 }
23087
23088 pub fn scroll_position(&self) -> gpui::Point<f32> {
23089 self.scroll_anchor.scroll_position(&self.display_snapshot)
23090 }
23091
23092 fn gutter_dimensions(
23093 &self,
23094 font_id: FontId,
23095 font_size: Pixels,
23096 max_line_number_width: Pixels,
23097 cx: &App,
23098 ) -> Option<GutterDimensions> {
23099 if !self.show_gutter {
23100 return None;
23101 }
23102
23103 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23104 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23105
23106 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23107 matches!(
23108 ProjectSettings::get_global(cx).git.git_gutter,
23109 GitGutterSetting::TrackedFiles
23110 )
23111 });
23112 let gutter_settings = EditorSettings::get_global(cx).gutter;
23113 let show_line_numbers = self
23114 .show_line_numbers
23115 .unwrap_or(gutter_settings.line_numbers);
23116 let line_gutter_width = if show_line_numbers {
23117 // Avoid flicker-like gutter resizes when the line number gains another digit by
23118 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23119 let min_width_for_number_on_gutter =
23120 ch_advance * gutter_settings.min_line_number_digits as f32;
23121 max_line_number_width.max(min_width_for_number_on_gutter)
23122 } else {
23123 0.0.into()
23124 };
23125
23126 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23127 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23128
23129 let git_blame_entries_width =
23130 self.git_blame_gutter_max_author_length
23131 .map(|max_author_length| {
23132 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23133 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23134
23135 /// The number of characters to dedicate to gaps and margins.
23136 const SPACING_WIDTH: usize = 4;
23137
23138 let max_char_count = max_author_length.min(renderer.max_author_length())
23139 + ::git::SHORT_SHA_LENGTH
23140 + MAX_RELATIVE_TIMESTAMP.len()
23141 + SPACING_WIDTH;
23142
23143 ch_advance * max_char_count
23144 });
23145
23146 let is_singleton = self.buffer_snapshot.is_singleton();
23147
23148 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23149 left_padding += if !is_singleton {
23150 ch_width * 4.0
23151 } else if show_runnables || show_breakpoints {
23152 ch_width * 3.0
23153 } else if show_git_gutter && show_line_numbers {
23154 ch_width * 2.0
23155 } else if show_git_gutter || show_line_numbers {
23156 ch_width
23157 } else {
23158 px(0.)
23159 };
23160
23161 let shows_folds = is_singleton && gutter_settings.folds;
23162
23163 let right_padding = if shows_folds && show_line_numbers {
23164 ch_width * 4.0
23165 } else if shows_folds || (!is_singleton && show_line_numbers) {
23166 ch_width * 3.0
23167 } else if show_line_numbers {
23168 ch_width
23169 } else {
23170 px(0.)
23171 };
23172
23173 Some(GutterDimensions {
23174 left_padding,
23175 right_padding,
23176 width: line_gutter_width + left_padding + right_padding,
23177 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23178 git_blame_entries_width,
23179 })
23180 }
23181
23182 pub fn render_crease_toggle(
23183 &self,
23184 buffer_row: MultiBufferRow,
23185 row_contains_cursor: bool,
23186 editor: Entity<Editor>,
23187 window: &mut Window,
23188 cx: &mut App,
23189 ) -> Option<AnyElement> {
23190 let folded = self.is_line_folded(buffer_row);
23191 let mut is_foldable = false;
23192
23193 if let Some(crease) = self
23194 .crease_snapshot
23195 .query_row(buffer_row, &self.buffer_snapshot)
23196 {
23197 is_foldable = true;
23198 match crease {
23199 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23200 if let Some(render_toggle) = render_toggle {
23201 let toggle_callback =
23202 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23203 if folded {
23204 editor.update(cx, |editor, cx| {
23205 editor.fold_at(buffer_row, window, cx)
23206 });
23207 } else {
23208 editor.update(cx, |editor, cx| {
23209 editor.unfold_at(buffer_row, window, cx)
23210 });
23211 }
23212 });
23213 return Some((render_toggle)(
23214 buffer_row,
23215 folded,
23216 toggle_callback,
23217 window,
23218 cx,
23219 ));
23220 }
23221 }
23222 }
23223 }
23224
23225 is_foldable |= self.starts_indent(buffer_row);
23226
23227 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23228 Some(
23229 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23230 .toggle_state(folded)
23231 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23232 if folded {
23233 this.unfold_at(buffer_row, window, cx);
23234 } else {
23235 this.fold_at(buffer_row, window, cx);
23236 }
23237 }))
23238 .into_any_element(),
23239 )
23240 } else {
23241 None
23242 }
23243 }
23244
23245 pub fn render_crease_trailer(
23246 &self,
23247 buffer_row: MultiBufferRow,
23248 window: &mut Window,
23249 cx: &mut App,
23250 ) -> Option<AnyElement> {
23251 let folded = self.is_line_folded(buffer_row);
23252 if let Crease::Inline { render_trailer, .. } = self
23253 .crease_snapshot
23254 .query_row(buffer_row, &self.buffer_snapshot)?
23255 {
23256 let render_trailer = render_trailer.as_ref()?;
23257 Some(render_trailer(buffer_row, folded, window, cx))
23258 } else {
23259 None
23260 }
23261 }
23262}
23263
23264impl Deref for EditorSnapshot {
23265 type Target = DisplaySnapshot;
23266
23267 fn deref(&self) -> &Self::Target {
23268 &self.display_snapshot
23269 }
23270}
23271
23272#[derive(Clone, Debug, PartialEq, Eq)]
23273pub enum EditorEvent {
23274 InputIgnored {
23275 text: Arc<str>,
23276 },
23277 InputHandled {
23278 utf16_range_to_replace: Option<Range<isize>>,
23279 text: Arc<str>,
23280 },
23281 ExcerptsAdded {
23282 buffer: Entity<Buffer>,
23283 predecessor: ExcerptId,
23284 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23285 },
23286 ExcerptsRemoved {
23287 ids: Vec<ExcerptId>,
23288 removed_buffer_ids: Vec<BufferId>,
23289 },
23290 BufferFoldToggled {
23291 ids: Vec<ExcerptId>,
23292 folded: bool,
23293 },
23294 ExcerptsEdited {
23295 ids: Vec<ExcerptId>,
23296 },
23297 ExcerptsExpanded {
23298 ids: Vec<ExcerptId>,
23299 },
23300 BufferEdited,
23301 Edited {
23302 transaction_id: clock::Lamport,
23303 },
23304 Reparsed(BufferId),
23305 Focused,
23306 FocusedIn,
23307 Blurred,
23308 DirtyChanged,
23309 Saved,
23310 TitleChanged,
23311 SelectionsChanged {
23312 local: bool,
23313 },
23314 ScrollPositionChanged {
23315 local: bool,
23316 autoscroll: bool,
23317 },
23318 TransactionUndone {
23319 transaction_id: clock::Lamport,
23320 },
23321 TransactionBegun {
23322 transaction_id: clock::Lamport,
23323 },
23324 CursorShapeChanged,
23325 BreadcrumbsChanged,
23326 PushedToNavHistory {
23327 anchor: Anchor,
23328 is_deactivate: bool,
23329 },
23330}
23331
23332impl EventEmitter<EditorEvent> for Editor {}
23333
23334impl Focusable for Editor {
23335 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23336 self.focus_handle.clone()
23337 }
23338}
23339
23340impl Render for Editor {
23341 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23342 let settings = ThemeSettings::get_global(cx);
23343
23344 let mut text_style = match self.mode {
23345 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23346 color: cx.theme().colors().editor_foreground,
23347 font_family: settings.ui_font.family.clone(),
23348 font_features: settings.ui_font.features.clone(),
23349 font_fallbacks: settings.ui_font.fallbacks.clone(),
23350 font_size: rems(0.875).into(),
23351 font_weight: settings.ui_font.weight,
23352 line_height: relative(settings.buffer_line_height.value()),
23353 ..Default::default()
23354 },
23355 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23356 color: cx.theme().colors().editor_foreground,
23357 font_family: settings.buffer_font.family.clone(),
23358 font_features: settings.buffer_font.features.clone(),
23359 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23360 font_size: settings.buffer_font_size(cx).into(),
23361 font_weight: settings.buffer_font.weight,
23362 line_height: relative(settings.buffer_line_height.value()),
23363 ..Default::default()
23364 },
23365 };
23366 if let Some(text_style_refinement) = &self.text_style_refinement {
23367 text_style.refine(text_style_refinement)
23368 }
23369
23370 let background = match self.mode {
23371 EditorMode::SingleLine => cx.theme().system().transparent,
23372 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23373 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23374 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23375 };
23376
23377 EditorElement::new(
23378 &cx.entity(),
23379 EditorStyle {
23380 background,
23381 border: cx.theme().colors().border,
23382 local_player: cx.theme().players().local(),
23383 text: text_style,
23384 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23385 syntax: cx.theme().syntax().clone(),
23386 status: cx.theme().status().clone(),
23387 inlay_hints_style: make_inlay_hints_style(cx),
23388 edit_prediction_styles: make_suggestion_styles(cx),
23389 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23390 show_underlines: self.diagnostics_enabled(),
23391 },
23392 )
23393 }
23394}
23395
23396impl EntityInputHandler for Editor {
23397 fn text_for_range(
23398 &mut self,
23399 range_utf16: Range<usize>,
23400 adjusted_range: &mut Option<Range<usize>>,
23401 _: &mut Window,
23402 cx: &mut Context<Self>,
23403 ) -> Option<String> {
23404 let snapshot = self.buffer.read(cx).read(cx);
23405 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23406 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23407 if (start.0..end.0) != range_utf16 {
23408 adjusted_range.replace(start.0..end.0);
23409 }
23410 Some(snapshot.text_for_range(start..end).collect())
23411 }
23412
23413 fn selected_text_range(
23414 &mut self,
23415 ignore_disabled_input: bool,
23416 _: &mut Window,
23417 cx: &mut Context<Self>,
23418 ) -> Option<UTF16Selection> {
23419 // Prevent the IME menu from appearing when holding down an alphabetic key
23420 // while input is disabled.
23421 if !ignore_disabled_input && !self.input_enabled {
23422 return None;
23423 }
23424
23425 let selection = self.selections.newest::<OffsetUtf16>(cx);
23426 let range = selection.range();
23427
23428 Some(UTF16Selection {
23429 range: range.start.0..range.end.0,
23430 reversed: selection.reversed,
23431 })
23432 }
23433
23434 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23435 let snapshot = self.buffer.read(cx).read(cx);
23436 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23437 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23438 }
23439
23440 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23441 self.clear_highlights::<InputComposition>(cx);
23442 self.ime_transaction.take();
23443 }
23444
23445 fn replace_text_in_range(
23446 &mut self,
23447 range_utf16: Option<Range<usize>>,
23448 text: &str,
23449 window: &mut Window,
23450 cx: &mut Context<Self>,
23451 ) {
23452 if !self.input_enabled {
23453 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23454 return;
23455 }
23456
23457 self.transact(window, cx, |this, window, cx| {
23458 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23459 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23460 Some(this.selection_replacement_ranges(range_utf16, cx))
23461 } else {
23462 this.marked_text_ranges(cx)
23463 };
23464
23465 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23466 let newest_selection_id = this.selections.newest_anchor().id;
23467 this.selections
23468 .all::<OffsetUtf16>(cx)
23469 .iter()
23470 .zip(ranges_to_replace.iter())
23471 .find_map(|(selection, range)| {
23472 if selection.id == newest_selection_id {
23473 Some(
23474 (range.start.0 as isize - selection.head().0 as isize)
23475 ..(range.end.0 as isize - selection.head().0 as isize),
23476 )
23477 } else {
23478 None
23479 }
23480 })
23481 });
23482
23483 cx.emit(EditorEvent::InputHandled {
23484 utf16_range_to_replace: range_to_replace,
23485 text: text.into(),
23486 });
23487
23488 if let Some(new_selected_ranges) = new_selected_ranges {
23489 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23490 selections.select_ranges(new_selected_ranges)
23491 });
23492 this.backspace(&Default::default(), window, cx);
23493 }
23494
23495 this.handle_input(text, window, cx);
23496 });
23497
23498 if let Some(transaction) = self.ime_transaction {
23499 self.buffer.update(cx, |buffer, cx| {
23500 buffer.group_until_transaction(transaction, cx);
23501 });
23502 }
23503
23504 self.unmark_text(window, cx);
23505 }
23506
23507 fn replace_and_mark_text_in_range(
23508 &mut self,
23509 range_utf16: Option<Range<usize>>,
23510 text: &str,
23511 new_selected_range_utf16: Option<Range<usize>>,
23512 window: &mut Window,
23513 cx: &mut Context<Self>,
23514 ) {
23515 if !self.input_enabled {
23516 return;
23517 }
23518
23519 let transaction = self.transact(window, cx, |this, window, cx| {
23520 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23521 let snapshot = this.buffer.read(cx).read(cx);
23522 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23523 for marked_range in &mut marked_ranges {
23524 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23525 marked_range.start.0 += relative_range_utf16.start;
23526 marked_range.start =
23527 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23528 marked_range.end =
23529 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23530 }
23531 }
23532 Some(marked_ranges)
23533 } else if let Some(range_utf16) = range_utf16 {
23534 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23535 Some(this.selection_replacement_ranges(range_utf16, cx))
23536 } else {
23537 None
23538 };
23539
23540 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23541 let newest_selection_id = this.selections.newest_anchor().id;
23542 this.selections
23543 .all::<OffsetUtf16>(cx)
23544 .iter()
23545 .zip(ranges_to_replace.iter())
23546 .find_map(|(selection, range)| {
23547 if selection.id == newest_selection_id {
23548 Some(
23549 (range.start.0 as isize - selection.head().0 as isize)
23550 ..(range.end.0 as isize - selection.head().0 as isize),
23551 )
23552 } else {
23553 None
23554 }
23555 })
23556 });
23557
23558 cx.emit(EditorEvent::InputHandled {
23559 utf16_range_to_replace: range_to_replace,
23560 text: text.into(),
23561 });
23562
23563 if let Some(ranges) = ranges_to_replace {
23564 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23565 s.select_ranges(ranges)
23566 });
23567 }
23568
23569 let marked_ranges = {
23570 let snapshot = this.buffer.read(cx).read(cx);
23571 this.selections
23572 .disjoint_anchors()
23573 .iter()
23574 .map(|selection| {
23575 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23576 })
23577 .collect::<Vec<_>>()
23578 };
23579
23580 if text.is_empty() {
23581 this.unmark_text(window, cx);
23582 } else {
23583 this.highlight_text::<InputComposition>(
23584 marked_ranges.clone(),
23585 HighlightStyle {
23586 underline: Some(UnderlineStyle {
23587 thickness: px(1.),
23588 color: None,
23589 wavy: false,
23590 }),
23591 ..Default::default()
23592 },
23593 cx,
23594 );
23595 }
23596
23597 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23598 let use_autoclose = this.use_autoclose;
23599 let use_auto_surround = this.use_auto_surround;
23600 this.set_use_autoclose(false);
23601 this.set_use_auto_surround(false);
23602 this.handle_input(text, window, cx);
23603 this.set_use_autoclose(use_autoclose);
23604 this.set_use_auto_surround(use_auto_surround);
23605
23606 if let Some(new_selected_range) = new_selected_range_utf16 {
23607 let snapshot = this.buffer.read(cx).read(cx);
23608 let new_selected_ranges = marked_ranges
23609 .into_iter()
23610 .map(|marked_range| {
23611 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23612 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23613 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23614 snapshot.clip_offset_utf16(new_start, Bias::Left)
23615 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23616 })
23617 .collect::<Vec<_>>();
23618
23619 drop(snapshot);
23620 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23621 selections.select_ranges(new_selected_ranges)
23622 });
23623 }
23624 });
23625
23626 self.ime_transaction = self.ime_transaction.or(transaction);
23627 if let Some(transaction) = self.ime_transaction {
23628 self.buffer.update(cx, |buffer, cx| {
23629 buffer.group_until_transaction(transaction, cx);
23630 });
23631 }
23632
23633 if self.text_highlights::<InputComposition>(cx).is_none() {
23634 self.ime_transaction.take();
23635 }
23636 }
23637
23638 fn bounds_for_range(
23639 &mut self,
23640 range_utf16: Range<usize>,
23641 element_bounds: gpui::Bounds<Pixels>,
23642 window: &mut Window,
23643 cx: &mut Context<Self>,
23644 ) -> Option<gpui::Bounds<Pixels>> {
23645 let text_layout_details = self.text_layout_details(window);
23646 let CharacterDimensions {
23647 em_width,
23648 em_advance,
23649 line_height,
23650 } = self.character_dimensions(window);
23651
23652 let snapshot = self.snapshot(window, cx);
23653 let scroll_position = snapshot.scroll_position();
23654 let scroll_left = scroll_position.x * em_advance;
23655
23656 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23657 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23658 + self.gutter_dimensions.full_width();
23659 let y = line_height * (start.row().as_f32() - scroll_position.y);
23660
23661 Some(Bounds {
23662 origin: element_bounds.origin + point(x, y),
23663 size: size(em_width, line_height),
23664 })
23665 }
23666
23667 fn character_index_for_point(
23668 &mut self,
23669 point: gpui::Point<Pixels>,
23670 _window: &mut Window,
23671 _cx: &mut Context<Self>,
23672 ) -> Option<usize> {
23673 let position_map = self.last_position_map.as_ref()?;
23674 if !position_map.text_hitbox.contains(&point) {
23675 return None;
23676 }
23677 let display_point = position_map.point_for_position(point).previous_valid;
23678 let anchor = position_map
23679 .snapshot
23680 .display_point_to_anchor(display_point, Bias::Left);
23681 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23682 Some(utf16_offset.0)
23683 }
23684}
23685
23686trait SelectionExt {
23687 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23688 fn spanned_rows(
23689 &self,
23690 include_end_if_at_line_start: bool,
23691 map: &DisplaySnapshot,
23692 ) -> Range<MultiBufferRow>;
23693}
23694
23695impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23696 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23697 let start = self
23698 .start
23699 .to_point(&map.buffer_snapshot)
23700 .to_display_point(map);
23701 let end = self
23702 .end
23703 .to_point(&map.buffer_snapshot)
23704 .to_display_point(map);
23705 if self.reversed {
23706 end..start
23707 } else {
23708 start..end
23709 }
23710 }
23711
23712 fn spanned_rows(
23713 &self,
23714 include_end_if_at_line_start: bool,
23715 map: &DisplaySnapshot,
23716 ) -> Range<MultiBufferRow> {
23717 let start = self.start.to_point(&map.buffer_snapshot);
23718 let mut end = self.end.to_point(&map.buffer_snapshot);
23719 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23720 end.row -= 1;
23721 }
23722
23723 let buffer_start = map.prev_line_boundary(start).0;
23724 let buffer_end = map.next_line_boundary(end).0;
23725 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23726 }
23727}
23728
23729impl<T: InvalidationRegion> InvalidationStack<T> {
23730 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23731 where
23732 S: Clone + ToOffset,
23733 {
23734 while let Some(region) = self.last() {
23735 let all_selections_inside_invalidation_ranges =
23736 if selections.len() == region.ranges().len() {
23737 selections
23738 .iter()
23739 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23740 .all(|(selection, invalidation_range)| {
23741 let head = selection.head().to_offset(buffer);
23742 invalidation_range.start <= head && invalidation_range.end >= head
23743 })
23744 } else {
23745 false
23746 };
23747
23748 if all_selections_inside_invalidation_ranges {
23749 break;
23750 } else {
23751 self.pop();
23752 }
23753 }
23754 }
23755}
23756
23757impl<T> Default for InvalidationStack<T> {
23758 fn default() -> Self {
23759 Self(Default::default())
23760 }
23761}
23762
23763impl<T> Deref for InvalidationStack<T> {
23764 type Target = Vec<T>;
23765
23766 fn deref(&self) -> &Self::Target {
23767 &self.0
23768 }
23769}
23770
23771impl<T> DerefMut for InvalidationStack<T> {
23772 fn deref_mut(&mut self) -> &mut Self::Target {
23773 &mut self.0
23774 }
23775}
23776
23777impl InvalidationRegion for SnippetState {
23778 fn ranges(&self) -> &[Range<Anchor>] {
23779 &self.ranges[self.active_index]
23780 }
23781}
23782
23783fn edit_prediction_edit_text(
23784 current_snapshot: &BufferSnapshot,
23785 edits: &[(Range<Anchor>, String)],
23786 edit_preview: &EditPreview,
23787 include_deletions: bool,
23788 cx: &App,
23789) -> HighlightedText {
23790 let edits = edits
23791 .iter()
23792 .map(|(anchor, text)| {
23793 (
23794 anchor.start.text_anchor..anchor.end.text_anchor,
23795 text.clone(),
23796 )
23797 })
23798 .collect::<Vec<_>>();
23799
23800 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23801}
23802
23803fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23804 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23805 // Just show the raw edit text with basic styling
23806 let mut text = String::new();
23807 let mut highlights = Vec::new();
23808
23809 let insertion_highlight_style = HighlightStyle {
23810 color: Some(cx.theme().colors().text),
23811 ..Default::default()
23812 };
23813
23814 for (_, edit_text) in edits {
23815 let start_offset = text.len();
23816 text.push_str(edit_text);
23817 let end_offset = text.len();
23818
23819 if start_offset < end_offset {
23820 highlights.push((start_offset..end_offset, insertion_highlight_style));
23821 }
23822 }
23823
23824 HighlightedText {
23825 text: text.into(),
23826 highlights,
23827 }
23828}
23829
23830pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23831 match severity {
23832 lsp::DiagnosticSeverity::ERROR => colors.error,
23833 lsp::DiagnosticSeverity::WARNING => colors.warning,
23834 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23835 lsp::DiagnosticSeverity::HINT => colors.info,
23836 _ => colors.ignored,
23837 }
23838}
23839
23840pub fn styled_runs_for_code_label<'a>(
23841 label: &'a CodeLabel,
23842 syntax_theme: &'a theme::SyntaxTheme,
23843) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23844 let fade_out = HighlightStyle {
23845 fade_out: Some(0.35),
23846 ..Default::default()
23847 };
23848
23849 let mut prev_end = label.filter_range.end;
23850 label
23851 .runs
23852 .iter()
23853 .enumerate()
23854 .flat_map(move |(ix, (range, highlight_id))| {
23855 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23856 style
23857 } else {
23858 return Default::default();
23859 };
23860 let muted_style = style.highlight(fade_out);
23861
23862 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23863 if range.start >= label.filter_range.end {
23864 if range.start > prev_end {
23865 runs.push((prev_end..range.start, fade_out));
23866 }
23867 runs.push((range.clone(), muted_style));
23868 } else if range.end <= label.filter_range.end {
23869 runs.push((range.clone(), style));
23870 } else {
23871 runs.push((range.start..label.filter_range.end, style));
23872 runs.push((label.filter_range.end..range.end, muted_style));
23873 }
23874 prev_end = cmp::max(prev_end, range.end);
23875
23876 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23877 runs.push((prev_end..label.text.len(), fade_out));
23878 }
23879
23880 runs
23881 })
23882}
23883
23884pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23885 let mut prev_index = 0;
23886 let mut prev_codepoint: Option<char> = None;
23887 text.char_indices()
23888 .chain([(text.len(), '\0')])
23889 .filter_map(move |(index, codepoint)| {
23890 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23891 let is_boundary = index == text.len()
23892 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23893 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23894 if is_boundary {
23895 let chunk = &text[prev_index..index];
23896 prev_index = index;
23897 Some(chunk)
23898 } else {
23899 None
23900 }
23901 })
23902}
23903
23904pub trait RangeToAnchorExt: Sized {
23905 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23906
23907 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23908 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23909 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23910 }
23911}
23912
23913impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23914 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23915 let start_offset = self.start.to_offset(snapshot);
23916 let end_offset = self.end.to_offset(snapshot);
23917 if start_offset == end_offset {
23918 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23919 } else {
23920 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23921 }
23922 }
23923}
23924
23925pub trait RowExt {
23926 fn as_f32(&self) -> f32;
23927
23928 fn next_row(&self) -> Self;
23929
23930 fn previous_row(&self) -> Self;
23931
23932 fn minus(&self, other: Self) -> u32;
23933}
23934
23935impl RowExt for DisplayRow {
23936 fn as_f32(&self) -> f32 {
23937 self.0 as f32
23938 }
23939
23940 fn next_row(&self) -> Self {
23941 Self(self.0 + 1)
23942 }
23943
23944 fn previous_row(&self) -> Self {
23945 Self(self.0.saturating_sub(1))
23946 }
23947
23948 fn minus(&self, other: Self) -> u32 {
23949 self.0 - other.0
23950 }
23951}
23952
23953impl RowExt for MultiBufferRow {
23954 fn as_f32(&self) -> f32 {
23955 self.0 as f32
23956 }
23957
23958 fn next_row(&self) -> Self {
23959 Self(self.0 + 1)
23960 }
23961
23962 fn previous_row(&self) -> Self {
23963 Self(self.0.saturating_sub(1))
23964 }
23965
23966 fn minus(&self, other: Self) -> u32 {
23967 self.0 - other.0
23968 }
23969}
23970
23971trait RowRangeExt {
23972 type Row;
23973
23974 fn len(&self) -> usize;
23975
23976 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23977}
23978
23979impl RowRangeExt for Range<MultiBufferRow> {
23980 type Row = MultiBufferRow;
23981
23982 fn len(&self) -> usize {
23983 (self.end.0 - self.start.0) as usize
23984 }
23985
23986 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23987 (self.start.0..self.end.0).map(MultiBufferRow)
23988 }
23989}
23990
23991impl RowRangeExt for Range<DisplayRow> {
23992 type Row = DisplayRow;
23993
23994 fn len(&self) -> usize {
23995 (self.end.0 - self.start.0) as usize
23996 }
23997
23998 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23999 (self.start.0..self.end.0).map(DisplayRow)
24000 }
24001}
24002
24003/// If select range has more than one line, we
24004/// just point the cursor to range.start.
24005fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24006 if range.start.row == range.end.row {
24007 range
24008 } else {
24009 range.start..range.start
24010 }
24011}
24012pub struct KillRing(ClipboardItem);
24013impl Global for KillRing {}
24014
24015const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24016
24017enum BreakpointPromptEditAction {
24018 Log,
24019 Condition,
24020 HitCondition,
24021}
24022
24023struct BreakpointPromptEditor {
24024 pub(crate) prompt: Entity<Editor>,
24025 editor: WeakEntity<Editor>,
24026 breakpoint_anchor: Anchor,
24027 breakpoint: Breakpoint,
24028 edit_action: BreakpointPromptEditAction,
24029 block_ids: HashSet<CustomBlockId>,
24030 editor_margins: Arc<Mutex<EditorMargins>>,
24031 _subscriptions: Vec<Subscription>,
24032}
24033
24034impl BreakpointPromptEditor {
24035 const MAX_LINES: u8 = 4;
24036
24037 fn new(
24038 editor: WeakEntity<Editor>,
24039 breakpoint_anchor: Anchor,
24040 breakpoint: Breakpoint,
24041 edit_action: BreakpointPromptEditAction,
24042 window: &mut Window,
24043 cx: &mut Context<Self>,
24044 ) -> Self {
24045 let base_text = match edit_action {
24046 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24047 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24048 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24049 }
24050 .map(|msg| msg.to_string())
24051 .unwrap_or_default();
24052
24053 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24054 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24055
24056 let prompt = cx.new(|cx| {
24057 let mut prompt = Editor::new(
24058 EditorMode::AutoHeight {
24059 min_lines: 1,
24060 max_lines: Some(Self::MAX_LINES as usize),
24061 },
24062 buffer,
24063 None,
24064 window,
24065 cx,
24066 );
24067 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24068 prompt.set_show_cursor_when_unfocused(false, cx);
24069 prompt.set_placeholder_text(
24070 match edit_action {
24071 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24072 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24073 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24074 },
24075 window,
24076 cx,
24077 );
24078
24079 prompt
24080 });
24081
24082 Self {
24083 prompt,
24084 editor,
24085 breakpoint_anchor,
24086 breakpoint,
24087 edit_action,
24088 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24089 block_ids: Default::default(),
24090 _subscriptions: vec![],
24091 }
24092 }
24093
24094 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24095 self.block_ids.extend(block_ids)
24096 }
24097
24098 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24099 if let Some(editor) = self.editor.upgrade() {
24100 let message = self
24101 .prompt
24102 .read(cx)
24103 .buffer
24104 .read(cx)
24105 .as_singleton()
24106 .expect("A multi buffer in breakpoint prompt isn't possible")
24107 .read(cx)
24108 .as_rope()
24109 .to_string();
24110
24111 editor.update(cx, |editor, cx| {
24112 editor.edit_breakpoint_at_anchor(
24113 self.breakpoint_anchor,
24114 self.breakpoint.clone(),
24115 match self.edit_action {
24116 BreakpointPromptEditAction::Log => {
24117 BreakpointEditAction::EditLogMessage(message.into())
24118 }
24119 BreakpointPromptEditAction::Condition => {
24120 BreakpointEditAction::EditCondition(message.into())
24121 }
24122 BreakpointPromptEditAction::HitCondition => {
24123 BreakpointEditAction::EditHitCondition(message.into())
24124 }
24125 },
24126 cx,
24127 );
24128
24129 editor.remove_blocks(self.block_ids.clone(), None, cx);
24130 cx.focus_self(window);
24131 });
24132 }
24133 }
24134
24135 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24136 self.editor
24137 .update(cx, |editor, cx| {
24138 editor.remove_blocks(self.block_ids.clone(), None, cx);
24139 window.focus(&editor.focus_handle);
24140 })
24141 .log_err();
24142 }
24143
24144 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24145 let settings = ThemeSettings::get_global(cx);
24146 let text_style = TextStyle {
24147 color: if self.prompt.read(cx).read_only(cx) {
24148 cx.theme().colors().text_disabled
24149 } else {
24150 cx.theme().colors().text
24151 },
24152 font_family: settings.buffer_font.family.clone(),
24153 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24154 font_size: settings.buffer_font_size(cx).into(),
24155 font_weight: settings.buffer_font.weight,
24156 line_height: relative(settings.buffer_line_height.value()),
24157 ..Default::default()
24158 };
24159 EditorElement::new(
24160 &self.prompt,
24161 EditorStyle {
24162 background: cx.theme().colors().editor_background,
24163 local_player: cx.theme().players().local(),
24164 text: text_style,
24165 ..Default::default()
24166 },
24167 )
24168 }
24169}
24170
24171impl Render for BreakpointPromptEditor {
24172 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24173 let editor_margins = *self.editor_margins.lock();
24174 let gutter_dimensions = editor_margins.gutter;
24175 h_flex()
24176 .key_context("Editor")
24177 .bg(cx.theme().colors().editor_background)
24178 .border_y_1()
24179 .border_color(cx.theme().status().info_border)
24180 .size_full()
24181 .py(window.line_height() / 2.5)
24182 .on_action(cx.listener(Self::confirm))
24183 .on_action(cx.listener(Self::cancel))
24184 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24185 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24186 }
24187}
24188
24189impl Focusable for BreakpointPromptEditor {
24190 fn focus_handle(&self, cx: &App) -> FocusHandle {
24191 self.prompt.focus_handle(cx)
24192 }
24193}
24194
24195fn all_edits_insertions_or_deletions(
24196 edits: &Vec<(Range<Anchor>, String)>,
24197 snapshot: &MultiBufferSnapshot,
24198) -> bool {
24199 let mut all_insertions = true;
24200 let mut all_deletions = true;
24201
24202 for (range, new_text) in edits.iter() {
24203 let range_is_empty = range.to_offset(snapshot).is_empty();
24204 let text_is_empty = new_text.is_empty();
24205
24206 if range_is_empty != text_is_empty {
24207 if range_is_empty {
24208 all_deletions = false;
24209 } else {
24210 all_insertions = false;
24211 }
24212 } else {
24213 return false;
24214 }
24215
24216 if !all_insertions && !all_deletions {
24217 return false;
24218 }
24219 }
24220 all_insertions || all_deletions
24221}
24222
24223struct MissingEditPredictionKeybindingTooltip;
24224
24225impl Render for MissingEditPredictionKeybindingTooltip {
24226 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24227 ui::tooltip_container(window, cx, |container, _, cx| {
24228 container
24229 .flex_shrink_0()
24230 .max_w_80()
24231 .min_h(rems_from_px(124.))
24232 .justify_between()
24233 .child(
24234 v_flex()
24235 .flex_1()
24236 .text_ui_sm(cx)
24237 .child(Label::new("Conflict with Accept Keybinding"))
24238 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24239 )
24240 .child(
24241 h_flex()
24242 .pb_1()
24243 .gap_1()
24244 .items_end()
24245 .w_full()
24246 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24247 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24248 }))
24249 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24250 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24251 })),
24252 )
24253 })
24254 }
24255}
24256
24257#[derive(Debug, Clone, Copy, PartialEq)]
24258pub struct LineHighlight {
24259 pub background: Background,
24260 pub border: Option<gpui::Hsla>,
24261 pub include_gutter: bool,
24262 pub type_id: Option<TypeId>,
24263}
24264
24265struct LineManipulationResult {
24266 pub new_text: String,
24267 pub line_count_before: usize,
24268 pub line_count_after: usize,
24269}
24270
24271fn render_diff_hunk_controls(
24272 row: u32,
24273 status: &DiffHunkStatus,
24274 hunk_range: Range<Anchor>,
24275 is_created_file: bool,
24276 line_height: Pixels,
24277 editor: &Entity<Editor>,
24278 _window: &mut Window,
24279 cx: &mut App,
24280) -> AnyElement {
24281 h_flex()
24282 .h(line_height)
24283 .mr_1()
24284 .gap_1()
24285 .px_0p5()
24286 .pb_1()
24287 .border_x_1()
24288 .border_b_1()
24289 .border_color(cx.theme().colors().border_variant)
24290 .rounded_b_lg()
24291 .bg(cx.theme().colors().editor_background)
24292 .gap_1()
24293 .block_mouse_except_scroll()
24294 .shadow_md()
24295 .child(if status.has_secondary_hunk() {
24296 Button::new(("stage", row as u64), "Stage")
24297 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24298 .tooltip({
24299 let focus_handle = editor.focus_handle(cx);
24300 move |window, cx| {
24301 Tooltip::for_action_in(
24302 "Stage Hunk",
24303 &::git::ToggleStaged,
24304 &focus_handle,
24305 window,
24306 cx,
24307 )
24308 }
24309 })
24310 .on_click({
24311 let editor = editor.clone();
24312 move |_event, _window, cx| {
24313 editor.update(cx, |editor, cx| {
24314 editor.stage_or_unstage_diff_hunks(
24315 true,
24316 vec![hunk_range.start..hunk_range.start],
24317 cx,
24318 );
24319 });
24320 }
24321 })
24322 } else {
24323 Button::new(("unstage", row as u64), "Unstage")
24324 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24325 .tooltip({
24326 let focus_handle = editor.focus_handle(cx);
24327 move |window, cx| {
24328 Tooltip::for_action_in(
24329 "Unstage Hunk",
24330 &::git::ToggleStaged,
24331 &focus_handle,
24332 window,
24333 cx,
24334 )
24335 }
24336 })
24337 .on_click({
24338 let editor = editor.clone();
24339 move |_event, _window, cx| {
24340 editor.update(cx, |editor, cx| {
24341 editor.stage_or_unstage_diff_hunks(
24342 false,
24343 vec![hunk_range.start..hunk_range.start],
24344 cx,
24345 );
24346 });
24347 }
24348 })
24349 })
24350 .child(
24351 Button::new(("restore", row as u64), "Restore")
24352 .tooltip({
24353 let focus_handle = editor.focus_handle(cx);
24354 move |window, cx| {
24355 Tooltip::for_action_in(
24356 "Restore Hunk",
24357 &::git::Restore,
24358 &focus_handle,
24359 window,
24360 cx,
24361 )
24362 }
24363 })
24364 .on_click({
24365 let editor = editor.clone();
24366 move |_event, window, cx| {
24367 editor.update(cx, |editor, cx| {
24368 let snapshot = editor.snapshot(window, cx);
24369 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24370 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24371 });
24372 }
24373 })
24374 .disabled(is_created_file),
24375 )
24376 .when(
24377 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24378 |el| {
24379 el.child(
24380 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24381 .shape(IconButtonShape::Square)
24382 .icon_size(IconSize::Small)
24383 // .disabled(!has_multiple_hunks)
24384 .tooltip({
24385 let focus_handle = editor.focus_handle(cx);
24386 move |window, cx| {
24387 Tooltip::for_action_in(
24388 "Next Hunk",
24389 &GoToHunk,
24390 &focus_handle,
24391 window,
24392 cx,
24393 )
24394 }
24395 })
24396 .on_click({
24397 let editor = editor.clone();
24398 move |_event, window, cx| {
24399 editor.update(cx, |editor, cx| {
24400 let snapshot = editor.snapshot(window, cx);
24401 let position =
24402 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24403 editor.go_to_hunk_before_or_after_position(
24404 &snapshot,
24405 position,
24406 Direction::Next,
24407 window,
24408 cx,
24409 );
24410 editor.expand_selected_diff_hunks(cx);
24411 });
24412 }
24413 }),
24414 )
24415 .child(
24416 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24417 .shape(IconButtonShape::Square)
24418 .icon_size(IconSize::Small)
24419 // .disabled(!has_multiple_hunks)
24420 .tooltip({
24421 let focus_handle = editor.focus_handle(cx);
24422 move |window, cx| {
24423 Tooltip::for_action_in(
24424 "Previous Hunk",
24425 &GoToPreviousHunk,
24426 &focus_handle,
24427 window,
24428 cx,
24429 )
24430 }
24431 })
24432 .on_click({
24433 let editor = editor.clone();
24434 move |_event, window, cx| {
24435 editor.update(cx, |editor, cx| {
24436 let snapshot = editor.snapshot(window, cx);
24437 let point =
24438 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24439 editor.go_to_hunk_before_or_after_position(
24440 &snapshot,
24441 point,
24442 Direction::Prev,
24443 window,
24444 cx,
24445 );
24446 editor.expand_selected_diff_hunks(cx);
24447 });
24448 }
24449 }),
24450 )
24451 },
24452 )
24453 .into_any_element()
24454}
24455
24456pub fn multibuffer_context_lines(cx: &App) -> u32 {
24457 EditorSettings::try_get(cx)
24458 .map(|settings| settings.excerpt_context_lines)
24459 .unwrap_or(2)
24460 .min(32)
24461}