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, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, 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::{
164 DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings,
165 },
166};
167use rand::seq::SliceRandom;
168use rpc::{ErrorCode, ErrorExt, proto::PeerId};
169use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
170use selections_collection::{
171 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
172};
173use serde::{Deserialize, Serialize};
174use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
175use smallvec::{SmallVec, smallvec};
176use snippet::Snippet;
177use std::{
178 any::{Any, TypeId},
179 borrow::Cow,
180 cell::{OnceCell, RefCell},
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
186 path::{Path, PathBuf},
187 rc::Rc,
188 sync::Arc,
189 time::{Duration, Instant},
190};
191use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
192use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
193use theme::{
194 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
216};
217
218pub const FILE_HEADER_HEIGHT: u32 = 2;
219pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232
233pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
234pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
235pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
236
237pub type RenderDiffHunkControlsFn = Arc<
238 dyn Fn(
239 u32,
240 &DiffHunkStatus,
241 Range<Anchor>,
242 bool,
243 Pixels,
244 &Entity<Editor>,
245 &mut Window,
246 &mut App,
247 ) -> AnyElement,
248>;
249
250enum ReportEditorEvent {
251 Saved { auto_saved: bool },
252 EditorOpened,
253 Closed,
254}
255
256impl ReportEditorEvent {
257 pub fn event_type(&self) -> &'static str {
258 match self {
259 Self::Saved { .. } => "Editor Saved",
260 Self::EditorOpened => "Editor Opened",
261 Self::Closed => "Editor Closed",
262 }
263 }
264}
265
266struct InlineValueCache {
267 enabled: bool,
268 inlays: Vec<InlayId>,
269 refresh_task: Task<Option<()>>,
270}
271
272impl InlineValueCache {
273 fn new(enabled: bool) -> Self {
274 Self {
275 enabled,
276 inlays: Vec::new(),
277 refresh_task: Task::ready(None),
278 }
279 }
280}
281
282#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
283pub enum InlayId {
284 EditPrediction(usize),
285 DebuggerValue(usize),
286 // LSP
287 Hint(usize),
288 Color(usize),
289}
290
291impl InlayId {
292 fn id(&self) -> usize {
293 match self {
294 Self::EditPrediction(id) => *id,
295 Self::DebuggerValue(id) => *id,
296 Self::Hint(id) => *id,
297 Self::Color(id) => *id,
298 }
299 }
300}
301
302pub enum ActiveDebugLine {}
303pub enum DebugStackFrameLine {}
304enum DocumentHighlightRead {}
305enum DocumentHighlightWrite {}
306enum InputComposition {}
307pub enum PendingInput {}
308enum SelectedTextHighlight {}
309
310pub enum ConflictsOuter {}
311pub enum ConflictsOurs {}
312pub enum ConflictsTheirs {}
313pub enum ConflictsOursMarker {}
314pub enum ConflictsTheirsMarker {}
315
316#[derive(Debug, Copy, Clone, PartialEq, Eq)]
317pub enum Navigated {
318 Yes,
319 No,
320}
321
322impl Navigated {
323 pub fn from_bool(yes: bool) -> Navigated {
324 if yes { Navigated::Yes } else { Navigated::No }
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
329enum DisplayDiffHunk {
330 Folded {
331 display_row: DisplayRow,
332 },
333 Unfolded {
334 is_created_file: bool,
335 diff_base_byte_range: Range<usize>,
336 display_row_range: Range<DisplayRow>,
337 multi_buffer_range: Range<Anchor>,
338 status: DiffHunkStatus,
339 },
340}
341
342pub enum HideMouseCursorOrigin {
343 TypingAction,
344 MovementAction,
345}
346
347pub fn init_settings(cx: &mut App) {
348 EditorSettings::register(cx);
349}
350
351pub fn init(cx: &mut App) {
352 init_settings(cx);
353
354 cx.set_global(GlobalBlameRenderer(Arc::new(())));
355
356 workspace::register_project_item::<Editor>(cx);
357 workspace::FollowableViewRegistry::register::<Editor>(cx);
358 workspace::register_serializable_item::<Editor>(cx);
359
360 cx.observe_new(
361 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
362 workspace.register_action(Editor::new_file);
363 workspace.register_action(Editor::new_file_vertical);
364 workspace.register_action(Editor::new_file_horizontal);
365 workspace.register_action(Editor::cancel_language_server_work);
366 workspace.register_action(Editor::toggle_focus);
367 },
368 )
369 .detach();
370
371 cx.on_action(move |_: &workspace::NewFile, cx| {
372 let app_state = workspace::AppState::global(cx);
373 if let Some(app_state) = app_state.upgrade() {
374 workspace::open_new(
375 Default::default(),
376 app_state,
377 cx,
378 |workspace, window, cx| {
379 Editor::new_file(workspace, &Default::default(), window, cx)
380 },
381 )
382 .detach();
383 }
384 });
385 cx.on_action(move |_: &workspace::NewWindow, cx| {
386 let app_state = workspace::AppState::global(cx);
387 if let Some(app_state) = app_state.upgrade() {
388 workspace::open_new(
389 Default::default(),
390 app_state,
391 cx,
392 |workspace, window, cx| {
393 cx.activate(true);
394 Editor::new_file(workspace, &Default::default(), window, cx)
395 },
396 )
397 .detach();
398 }
399 });
400}
401
402pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
403 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
404}
405
406pub trait DiagnosticRenderer {
407 fn render_group(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntry<Point>>,
410 buffer_id: BufferId,
411 snapshot: EditorSnapshot,
412 editor: WeakEntity<Editor>,
413 cx: &mut App,
414 ) -> Vec<BlockProperties<Anchor>>;
415
416 fn render_hover(
417 &self,
418 diagnostic_group: Vec<DiagnosticEntry<Point>>,
419 range: Range<Point>,
420 buffer_id: BufferId,
421 cx: &mut App,
422 ) -> Option<Entity<markdown::Markdown>>;
423
424 fn open_link(
425 &self,
426 editor: &mut Editor,
427 link: SharedString,
428 window: &mut Window,
429 cx: &mut Context<Editor>,
430 );
431}
432
433pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
434
435impl GlobalDiagnosticRenderer {
436 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
437 cx.try_global::<Self>().map(|g| g.0.clone())
438 }
439}
440
441impl gpui::Global for GlobalDiagnosticRenderer {}
442pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
443 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
444}
445
446pub struct SearchWithinRange;
447
448trait InvalidationRegion {
449 fn ranges(&self) -> &[Range<Anchor>];
450}
451
452#[derive(Clone, Debug, PartialEq)]
453pub enum SelectPhase {
454 Begin {
455 position: DisplayPoint,
456 add: bool,
457 click_count: usize,
458 },
459 BeginColumnar {
460 position: DisplayPoint,
461 reset: bool,
462 mode: ColumnarMode,
463 goal_column: u32,
464 },
465 Extend {
466 position: DisplayPoint,
467 click_count: usize,
468 },
469 Update {
470 position: DisplayPoint,
471 goal_column: u32,
472 scroll_delta: gpui::Point<f32>,
473 },
474 End,
475}
476
477#[derive(Clone, Debug, PartialEq)]
478pub enum ColumnarMode {
479 FromMouse,
480 FromSelection,
481}
482
483#[derive(Clone, Debug)]
484pub enum SelectMode {
485 Character,
486 Word(Range<Anchor>),
487 Line(Range<Anchor>),
488 All,
489}
490
491#[derive(Clone, PartialEq, Eq, Debug)]
492pub enum EditorMode {
493 SingleLine,
494 AutoHeight {
495 min_lines: usize,
496 max_lines: Option<usize>,
497 },
498 Full {
499 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
500 scale_ui_elements_with_buffer_font_size: bool,
501 /// When set to `true`, the editor will render a background for the active line.
502 show_active_line_background: bool,
503 /// When set to `true`, the editor's height will be determined by its content.
504 sized_by_content: bool,
505 },
506 Minimap {
507 parent: WeakEntity<Editor>,
508 },
509}
510
511impl EditorMode {
512 pub fn full() -> Self {
513 Self::Full {
514 scale_ui_elements_with_buffer_font_size: true,
515 show_active_line_background: true,
516 sized_by_content: false,
517 }
518 }
519
520 #[inline]
521 pub fn is_full(&self) -> bool {
522 matches!(self, Self::Full { .. })
523 }
524
525 #[inline]
526 pub fn is_single_line(&self) -> bool {
527 matches!(self, Self::SingleLine { .. })
528 }
529
530 #[inline]
531 fn is_minimap(&self) -> bool {
532 matches!(self, Self::Minimap { .. })
533 }
534}
535
536#[derive(Copy, Clone, Debug)]
537pub enum SoftWrap {
538 /// Prefer not to wrap at all.
539 ///
540 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
541 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
542 GitDiff,
543 /// Prefer a single line generally, unless an overly long line is encountered.
544 None,
545 /// Soft wrap lines that exceed the editor width.
546 EditorWidth,
547 /// Soft wrap lines at the preferred line length.
548 Column(u32),
549 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
550 Bounded(u32),
551}
552
553#[derive(Clone)]
554pub struct EditorStyle {
555 pub background: Hsla,
556 pub border: Hsla,
557 pub local_player: PlayerColor,
558 pub text: TextStyle,
559 pub scrollbar_width: Pixels,
560 pub syntax: Arc<SyntaxTheme>,
561 pub status: StatusColors,
562 pub inlay_hints_style: HighlightStyle,
563 pub edit_prediction_styles: EditPredictionStyles,
564 pub unnecessary_code_fade: f32,
565 pub show_underlines: bool,
566}
567
568impl Default for EditorStyle {
569 fn default() -> Self {
570 Self {
571 background: Hsla::default(),
572 border: Hsla::default(),
573 local_player: PlayerColor::default(),
574 text: TextStyle::default(),
575 scrollbar_width: Pixels::default(),
576 syntax: Default::default(),
577 // HACK: Status colors don't have a real default.
578 // We should look into removing the status colors from the editor
579 // style and retrieve them directly from the theme.
580 status: StatusColors::dark(),
581 inlay_hints_style: HighlightStyle::default(),
582 edit_prediction_styles: EditPredictionStyles {
583 insertion: HighlightStyle::default(),
584 whitespace: HighlightStyle::default(),
585 },
586 unnecessary_code_fade: Default::default(),
587 show_underlines: true,
588 }
589 }
590}
591
592pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
593 let show_background = language_settings::language_settings(None, None, cx)
594 .inlay_hints
595 .show_background;
596
597 HighlightStyle {
598 color: Some(cx.theme().status().hint),
599 background_color: show_background.then(|| cx.theme().status().hint_background),
600 ..HighlightStyle::default()
601 }
602}
603
604pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
605 EditPredictionStyles {
606 insertion: HighlightStyle {
607 color: Some(cx.theme().status().predictive),
608 ..HighlightStyle::default()
609 },
610 whitespace: HighlightStyle {
611 background_color: Some(cx.theme().status().created_background),
612 ..HighlightStyle::default()
613 },
614 }
615}
616
617type CompletionId = usize;
618
619pub(crate) enum EditDisplayMode {
620 TabAccept,
621 DiffPopover,
622 Inline,
623}
624
625enum EditPrediction {
626 Edit {
627 edits: Vec<(Range<Anchor>, String)>,
628 edit_preview: Option<EditPreview>,
629 display_mode: EditDisplayMode,
630 snapshot: BufferSnapshot,
631 },
632 Move {
633 target: Anchor,
634 snapshot: BufferSnapshot,
635 },
636}
637
638struct EditPredictionState {
639 inlay_ids: Vec<InlayId>,
640 completion: EditPrediction,
641 completion_id: Option<SharedString>,
642 invalidation_range: Range<Anchor>,
643}
644
645enum EditPredictionSettings {
646 Disabled,
647 Enabled {
648 show_in_menu: bool,
649 preview_requires_modifier: bool,
650 },
651}
652
653enum EditPredictionHighlight {}
654
655#[derive(Debug, Clone)]
656struct InlineDiagnostic {
657 message: SharedString,
658 group_id: usize,
659 is_primary: bool,
660 start: Point,
661 severity: lsp::DiagnosticSeverity,
662}
663
664pub enum MenuEditPredictionsPolicy {
665 Never,
666 ByProvider,
667}
668
669pub enum EditPredictionPreview {
670 /// Modifier is not pressed
671 Inactive { released_too_fast: bool },
672 /// Modifier pressed
673 Active {
674 since: Instant,
675 previous_scroll_position: Option<ScrollAnchor>,
676 },
677}
678
679impl EditPredictionPreview {
680 pub fn released_too_fast(&self) -> bool {
681 match self {
682 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
683 EditPredictionPreview::Active { .. } => false,
684 }
685 }
686
687 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
688 if let EditPredictionPreview::Active {
689 previous_scroll_position,
690 ..
691 } = self
692 {
693 *previous_scroll_position = scroll_position;
694 }
695 }
696}
697
698pub struct ContextMenuOptions {
699 pub min_entries_visible: usize,
700 pub max_entries_visible: usize,
701 pub placement: Option<ContextMenuPlacement>,
702}
703
704#[derive(Debug, Clone, PartialEq, Eq)]
705pub enum ContextMenuPlacement {
706 Above,
707 Below,
708}
709
710#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
711struct EditorActionId(usize);
712
713impl EditorActionId {
714 pub fn post_inc(&mut self) -> Self {
715 let answer = self.0;
716
717 *self = Self(answer + 1);
718
719 Self(answer)
720 }
721}
722
723// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
724// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
725
726type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
727type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
728
729#[derive(Default)]
730struct ScrollbarMarkerState {
731 scrollbar_size: Size<Pixels>,
732 dirty: bool,
733 markers: Arc<[PaintQuad]>,
734 pending_refresh: Option<Task<Result<()>>>,
735}
736
737impl ScrollbarMarkerState {
738 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
739 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
740 }
741}
742
743#[derive(Clone, Copy, PartialEq, Eq)]
744pub enum MinimapVisibility {
745 Disabled,
746 Enabled {
747 /// The configuration currently present in the users settings.
748 setting_configuration: bool,
749 /// Whether to override the currently set visibility from the users setting.
750 toggle_override: bool,
751 },
752}
753
754impl MinimapVisibility {
755 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
756 if mode.is_full() {
757 Self::Enabled {
758 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
759 toggle_override: false,
760 }
761 } else {
762 Self::Disabled
763 }
764 }
765
766 fn hidden(&self) -> Self {
767 match *self {
768 Self::Enabled {
769 setting_configuration,
770 ..
771 } => Self::Enabled {
772 setting_configuration,
773 toggle_override: setting_configuration,
774 },
775 Self::Disabled => Self::Disabled,
776 }
777 }
778
779 fn disabled(&self) -> bool {
780 matches!(*self, Self::Disabled)
781 }
782
783 fn settings_visibility(&self) -> bool {
784 match *self {
785 Self::Enabled {
786 setting_configuration,
787 ..
788 } => setting_configuration,
789 _ => false,
790 }
791 }
792
793 fn visible(&self) -> bool {
794 match *self {
795 Self::Enabled {
796 setting_configuration,
797 toggle_override,
798 } => setting_configuration ^ toggle_override,
799 _ => false,
800 }
801 }
802
803 fn toggle_visibility(&self) -> Self {
804 match *self {
805 Self::Enabled {
806 toggle_override,
807 setting_configuration,
808 } => Self::Enabled {
809 setting_configuration,
810 toggle_override: !toggle_override,
811 },
812 Self::Disabled => Self::Disabled,
813 }
814 }
815}
816
817#[derive(Clone, Debug)]
818struct RunnableTasks {
819 templates: Vec<(TaskSourceKind, TaskTemplate)>,
820 offset: multi_buffer::Anchor,
821 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
822 column: u32,
823 // Values of all named captures, including those starting with '_'
824 extra_variables: HashMap<String, String>,
825 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
826 context_range: Range<BufferOffset>,
827}
828
829impl RunnableTasks {
830 fn resolve<'a>(
831 &'a self,
832 cx: &'a task::TaskContext,
833 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
834 self.templates.iter().filter_map(|(kind, template)| {
835 template
836 .resolve_task(&kind.to_id_base(), cx)
837 .map(|task| (kind.clone(), task))
838 })
839 }
840}
841
842#[derive(Clone)]
843pub struct ResolvedTasks {
844 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
845 position: Anchor,
846}
847
848#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
849struct BufferOffset(usize);
850
851/// Addons allow storing per-editor state in other crates (e.g. Vim)
852pub trait Addon: 'static {
853 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
854
855 fn render_buffer_header_controls(
856 &self,
857 _: &ExcerptInfo,
858 _: &Window,
859 _: &App,
860 ) -> Option<AnyElement> {
861 None
862 }
863
864 fn to_any(&self) -> &dyn std::any::Any;
865
866 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
867 None
868 }
869}
870
871struct ChangeLocation {
872 current: Option<Vec<Anchor>>,
873 original: Vec<Anchor>,
874}
875impl ChangeLocation {
876 fn locations(&self) -> &[Anchor] {
877 self.current.as_ref().unwrap_or(&self.original)
878 }
879}
880
881/// A set of caret positions, registered when the editor was edited.
882pub struct ChangeList {
883 changes: Vec<ChangeLocation>,
884 /// Currently "selected" change.
885 position: Option<usize>,
886}
887
888impl ChangeList {
889 pub fn new() -> Self {
890 Self {
891 changes: Vec::new(),
892 position: None,
893 }
894 }
895
896 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
897 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
898 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
899 if self.changes.is_empty() {
900 return None;
901 }
902
903 let prev = self.position.unwrap_or(self.changes.len());
904 let next = if direction == Direction::Prev {
905 prev.saturating_sub(count)
906 } else {
907 (prev + count).min(self.changes.len() - 1)
908 };
909 self.position = Some(next);
910 self.changes.get(next).map(|change| change.locations())
911 }
912
913 /// Adds a new change to the list, resetting the change list position.
914 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
915 self.position.take();
916 if let Some(last) = self.changes.last_mut()
917 && group
918 {
919 last.current = Some(new_positions)
920 } else {
921 self.changes.push(ChangeLocation {
922 original: new_positions,
923 current: None,
924 });
925 }
926 }
927
928 pub fn last(&self) -> Option<&[Anchor]> {
929 self.changes.last().map(|change| change.locations())
930 }
931
932 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
933 self.changes.last().map(|change| change.original.as_slice())
934 }
935
936 pub fn invert_last_group(&mut self) {
937 if let Some(last) = self.changes.last_mut()
938 && let Some(current) = last.current.as_mut()
939 {
940 mem::swap(&mut last.original, current);
941 }
942 }
943}
944
945#[derive(Clone)]
946struct InlineBlamePopoverState {
947 scroll_handle: ScrollHandle,
948 commit_message: Option<ParsedCommitMessage>,
949 markdown: Entity<Markdown>,
950}
951
952struct InlineBlamePopover {
953 position: gpui::Point<Pixels>,
954 hide_task: Option<Task<()>>,
955 popover_bounds: Option<Bounds<Pixels>>,
956 popover_state: InlineBlamePopoverState,
957 keyboard_grace: bool,
958}
959
960enum SelectionDragState {
961 /// State when no drag related activity is detected.
962 None,
963 /// State when the mouse is down on a selection that is about to be dragged.
964 ReadyToDrag {
965 selection: Selection<Anchor>,
966 click_position: gpui::Point<Pixels>,
967 mouse_down_time: Instant,
968 },
969 /// State when the mouse is dragging the selection in the editor.
970 Dragging {
971 selection: Selection<Anchor>,
972 drop_cursor: Selection<Anchor>,
973 hide_drop_cursor: bool,
974 },
975}
976
977enum ColumnarSelectionState {
978 FromMouse {
979 selection_tail: Anchor,
980 display_point: Option<DisplayPoint>,
981 },
982 FromSelection {
983 selection_tail: Anchor,
984 },
985}
986
987/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
988/// a breakpoint on them.
989#[derive(Clone, Copy, Debug, PartialEq, Eq)]
990struct PhantomBreakpointIndicator {
991 display_row: DisplayRow,
992 /// There's a small debounce between hovering over the line and showing the indicator.
993 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
994 is_active: bool,
995 collides_with_existing_breakpoint: bool,
996}
997
998/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
999///
1000/// See the [module level documentation](self) for more information.
1001pub struct Editor {
1002 focus_handle: FocusHandle,
1003 last_focused_descendant: Option<WeakFocusHandle>,
1004 /// The text buffer being edited
1005 buffer: Entity<MultiBuffer>,
1006 /// Map of how text in the buffer should be displayed.
1007 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1008 pub display_map: Entity<DisplayMap>,
1009 placeholder_display_map: Option<Entity<DisplayMap>>,
1010 pub selections: SelectionsCollection,
1011 pub scroll_manager: ScrollManager,
1012 /// When inline assist editors are linked, they all render cursors because
1013 /// typing enters text into each of them, even the ones that aren't focused.
1014 pub(crate) show_cursor_when_unfocused: bool,
1015 columnar_selection_state: Option<ColumnarSelectionState>,
1016 add_selections_state: Option<AddSelectionsState>,
1017 select_next_state: Option<SelectNextState>,
1018 select_prev_state: Option<SelectNextState>,
1019 selection_history: SelectionHistory,
1020 defer_selection_effects: bool,
1021 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1022 autoclose_regions: Vec<AutocloseRegion>,
1023 snippet_stack: InvalidationStack<SnippetState>,
1024 select_syntax_node_history: SelectSyntaxNodeHistory,
1025 ime_transaction: Option<TransactionId>,
1026 pub diagnostics_max_severity: DiagnosticSeverity,
1027 active_diagnostics: ActiveDiagnostic,
1028 show_inline_diagnostics: bool,
1029 inline_diagnostics_update: Task<()>,
1030 inline_diagnostics_enabled: bool,
1031 diagnostics_enabled: bool,
1032 word_completions_enabled: bool,
1033 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1034 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1035 hard_wrap: Option<usize>,
1036 project: Option<Entity<Project>>,
1037 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1038 completion_provider: Option<Rc<dyn CompletionProvider>>,
1039 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1040 blink_manager: Entity<BlinkManager>,
1041 show_cursor_names: bool,
1042 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1043 pub show_local_selections: bool,
1044 mode: EditorMode,
1045 show_breadcrumbs: bool,
1046 show_gutter: bool,
1047 show_scrollbars: ScrollbarAxes,
1048 minimap_visibility: MinimapVisibility,
1049 offset_content: bool,
1050 disable_expand_excerpt_buttons: bool,
1051 show_line_numbers: Option<bool>,
1052 use_relative_line_numbers: Option<bool>,
1053 show_git_diff_gutter: Option<bool>,
1054 show_code_actions: Option<bool>,
1055 show_runnables: Option<bool>,
1056 show_breakpoints: Option<bool>,
1057 show_wrap_guides: Option<bool>,
1058 show_indent_guides: Option<bool>,
1059 highlight_order: usize,
1060 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1061 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1062 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1063 scrollbar_marker_state: ScrollbarMarkerState,
1064 active_indent_guides_state: ActiveIndentGuidesState,
1065 nav_history: Option<ItemNavHistory>,
1066 context_menu: RefCell<Option<CodeContextMenu>>,
1067 context_menu_options: Option<ContextMenuOptions>,
1068 mouse_context_menu: Option<MouseContextMenu>,
1069 completion_tasks: Vec<(CompletionId, Task<()>)>,
1070 inline_blame_popover: Option<InlineBlamePopover>,
1071 inline_blame_popover_show_task: Option<Task<()>>,
1072 signature_help_state: SignatureHelpState,
1073 auto_signature_help: Option<bool>,
1074 find_all_references_task_sources: Vec<Anchor>,
1075 next_completion_id: CompletionId,
1076 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1077 code_actions_task: Option<Task<Result<()>>>,
1078 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1079 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1080 document_highlights_task: Option<Task<()>>,
1081 linked_editing_range_task: Option<Task<Option<()>>>,
1082 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1083 pending_rename: Option<RenameState>,
1084 searchable: bool,
1085 cursor_shape: CursorShape,
1086 current_line_highlight: Option<CurrentLineHighlight>,
1087 collapse_matches: bool,
1088 autoindent_mode: Option<AutoindentMode>,
1089 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1090 input_enabled: bool,
1091 use_modal_editing: bool,
1092 read_only: bool,
1093 leader_id: Option<CollaboratorId>,
1094 remote_id: Option<ViewId>,
1095 pub hover_state: HoverState,
1096 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1097 gutter_hovered: bool,
1098 hovered_link_state: Option<HoveredLinkState>,
1099 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1100 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1101 active_edit_prediction: Option<EditPredictionState>,
1102 /// Used to prevent flickering as the user types while the menu is open
1103 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1104 edit_prediction_settings: EditPredictionSettings,
1105 edit_predictions_hidden_for_vim_mode: bool,
1106 show_edit_predictions_override: Option<bool>,
1107 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1108 edit_prediction_preview: EditPredictionPreview,
1109 edit_prediction_indent_conflict: bool,
1110 edit_prediction_requires_modifier_in_indent_conflict: bool,
1111 inlay_hint_cache: InlayHintCache,
1112 next_inlay_id: usize,
1113 _subscriptions: Vec<Subscription>,
1114 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1115 gutter_dimensions: GutterDimensions,
1116 style: Option<EditorStyle>,
1117 text_style_refinement: Option<TextStyleRefinement>,
1118 next_editor_action_id: EditorActionId,
1119 editor_actions: Rc<
1120 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1121 >,
1122 use_autoclose: bool,
1123 use_auto_surround: bool,
1124 auto_replace_emoji_shortcode: bool,
1125 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1126 show_git_blame_gutter: bool,
1127 show_git_blame_inline: bool,
1128 show_git_blame_inline_delay_task: Option<Task<()>>,
1129 git_blame_inline_enabled: bool,
1130 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1131 serialize_dirty_buffers: bool,
1132 show_selection_menu: Option<bool>,
1133 blame: Option<Entity<GitBlame>>,
1134 blame_subscription: Option<Subscription>,
1135 custom_context_menu: Option<
1136 Box<
1137 dyn 'static
1138 + Fn(
1139 &mut Self,
1140 DisplayPoint,
1141 &mut Window,
1142 &mut Context<Self>,
1143 ) -> Option<Entity<ui::ContextMenu>>,
1144 >,
1145 >,
1146 last_bounds: Option<Bounds<Pixels>>,
1147 last_position_map: Option<Rc<PositionMap>>,
1148 expect_bounds_change: Option<Bounds<Pixels>>,
1149 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1150 tasks_update_task: Option<Task<()>>,
1151 breakpoint_store: Option<Entity<BreakpointStore>>,
1152 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1153 hovered_diff_hunk_row: Option<DisplayRow>,
1154 pull_diagnostics_task: Task<()>,
1155 in_project_search: bool,
1156 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1157 breadcrumb_header: Option<String>,
1158 focused_block: Option<FocusedBlock>,
1159 next_scroll_position: NextScrollCursorCenterTopBottom,
1160 addons: HashMap<TypeId, Box<dyn Addon>>,
1161 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1162 load_diff_task: Option<Shared<Task<()>>>,
1163 /// Whether we are temporarily displaying a diff other than git's
1164 temporary_diff_override: bool,
1165 selection_mark_mode: bool,
1166 toggle_fold_multiple_buffers: Task<()>,
1167 _scroll_cursor_center_top_bottom_task: Task<()>,
1168 serialize_selections: Task<()>,
1169 serialize_folds: Task<()>,
1170 mouse_cursor_hidden: bool,
1171 minimap: Option<Entity<Self>>,
1172 hide_mouse_mode: HideMouseMode,
1173 pub change_list: ChangeList,
1174 inline_value_cache: InlineValueCache,
1175 selection_drag_state: SelectionDragState,
1176 next_color_inlay_id: usize,
1177 colors: Option<LspColorData>,
1178 folding_newlines: Task<()>,
1179 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1180}
1181
1182#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1183enum NextScrollCursorCenterTopBottom {
1184 #[default]
1185 Center,
1186 Top,
1187 Bottom,
1188}
1189
1190impl NextScrollCursorCenterTopBottom {
1191 fn next(&self) -> Self {
1192 match self {
1193 Self::Center => Self::Top,
1194 Self::Top => Self::Bottom,
1195 Self::Bottom => Self::Center,
1196 }
1197 }
1198}
1199
1200#[derive(Clone)]
1201pub struct EditorSnapshot {
1202 pub mode: EditorMode,
1203 show_gutter: bool,
1204 show_line_numbers: Option<bool>,
1205 show_git_diff_gutter: Option<bool>,
1206 show_code_actions: Option<bool>,
1207 show_runnables: Option<bool>,
1208 show_breakpoints: Option<bool>,
1209 git_blame_gutter_max_author_length: Option<usize>,
1210 pub display_snapshot: DisplaySnapshot,
1211 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1212 is_focused: bool,
1213 scroll_anchor: ScrollAnchor,
1214 ongoing_scroll: OngoingScroll,
1215 current_line_highlight: CurrentLineHighlight,
1216 gutter_hovered: bool,
1217}
1218
1219#[derive(Default, Debug, Clone, Copy)]
1220pub struct GutterDimensions {
1221 pub left_padding: Pixels,
1222 pub right_padding: Pixels,
1223 pub width: Pixels,
1224 pub margin: Pixels,
1225 pub git_blame_entries_width: Option<Pixels>,
1226}
1227
1228impl GutterDimensions {
1229 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1230 Self {
1231 margin: Self::default_gutter_margin(font_id, font_size, cx),
1232 ..Default::default()
1233 }
1234 }
1235
1236 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1237 -cx.text_system().descent(font_id, font_size)
1238 }
1239 /// The full width of the space taken up by the gutter.
1240 pub fn full_width(&self) -> Pixels {
1241 self.margin + self.width
1242 }
1243
1244 /// The width of the space reserved for the fold indicators,
1245 /// use alongside 'justify_end' and `gutter_width` to
1246 /// right align content with the line numbers
1247 pub fn fold_area_width(&self) -> Pixels {
1248 self.margin + self.right_padding
1249 }
1250}
1251
1252struct CharacterDimensions {
1253 em_width: Pixels,
1254 em_advance: Pixels,
1255 line_height: Pixels,
1256}
1257
1258#[derive(Debug)]
1259pub struct RemoteSelection {
1260 pub replica_id: ReplicaId,
1261 pub selection: Selection<Anchor>,
1262 pub cursor_shape: CursorShape,
1263 pub collaborator_id: CollaboratorId,
1264 pub line_mode: bool,
1265 pub user_name: Option<SharedString>,
1266 pub color: PlayerColor,
1267}
1268
1269#[derive(Clone, Debug)]
1270struct SelectionHistoryEntry {
1271 selections: Arc<[Selection<Anchor>]>,
1272 select_next_state: Option<SelectNextState>,
1273 select_prev_state: Option<SelectNextState>,
1274 add_selections_state: Option<AddSelectionsState>,
1275}
1276
1277#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1278enum SelectionHistoryMode {
1279 Normal,
1280 Undoing,
1281 Redoing,
1282 Skipping,
1283}
1284
1285#[derive(Clone, PartialEq, Eq, Hash)]
1286struct HoveredCursor {
1287 replica_id: u16,
1288 selection_id: usize,
1289}
1290
1291impl Default for SelectionHistoryMode {
1292 fn default() -> Self {
1293 Self::Normal
1294 }
1295}
1296
1297#[derive(Debug)]
1298/// SelectionEffects controls the side-effects of updating the selection.
1299///
1300/// The default behaviour does "what you mostly want":
1301/// - it pushes to the nav history if the cursor moved by >10 lines
1302/// - it re-triggers completion requests
1303/// - it scrolls to fit
1304///
1305/// You might want to modify these behaviours. For example when doing a "jump"
1306/// like go to definition, we always want to add to nav history; but when scrolling
1307/// in vim mode we never do.
1308///
1309/// Similarly, you might want to disable scrolling if you don't want the viewport to
1310/// move.
1311#[derive(Clone)]
1312pub struct SelectionEffects {
1313 nav_history: Option<bool>,
1314 completions: bool,
1315 scroll: Option<Autoscroll>,
1316}
1317
1318impl Default for SelectionEffects {
1319 fn default() -> Self {
1320 Self {
1321 nav_history: None,
1322 completions: true,
1323 scroll: Some(Autoscroll::fit()),
1324 }
1325 }
1326}
1327impl SelectionEffects {
1328 pub fn scroll(scroll: Autoscroll) -> Self {
1329 Self {
1330 scroll: Some(scroll),
1331 ..Default::default()
1332 }
1333 }
1334
1335 pub fn no_scroll() -> Self {
1336 Self {
1337 scroll: None,
1338 ..Default::default()
1339 }
1340 }
1341
1342 pub fn completions(self, completions: bool) -> Self {
1343 Self {
1344 completions,
1345 ..self
1346 }
1347 }
1348
1349 pub fn nav_history(self, nav_history: bool) -> Self {
1350 Self {
1351 nav_history: Some(nav_history),
1352 ..self
1353 }
1354 }
1355}
1356
1357struct DeferredSelectionEffectsState {
1358 changed: bool,
1359 effects: SelectionEffects,
1360 old_cursor_position: Anchor,
1361 history_entry: SelectionHistoryEntry,
1362}
1363
1364#[derive(Default)]
1365struct SelectionHistory {
1366 #[allow(clippy::type_complexity)]
1367 selections_by_transaction:
1368 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1369 mode: SelectionHistoryMode,
1370 undo_stack: VecDeque<SelectionHistoryEntry>,
1371 redo_stack: VecDeque<SelectionHistoryEntry>,
1372}
1373
1374impl SelectionHistory {
1375 #[track_caller]
1376 fn insert_transaction(
1377 &mut self,
1378 transaction_id: TransactionId,
1379 selections: Arc<[Selection<Anchor>]>,
1380 ) {
1381 if selections.is_empty() {
1382 log::error!(
1383 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1384 std::panic::Location::caller()
1385 );
1386 return;
1387 }
1388 self.selections_by_transaction
1389 .insert(transaction_id, (selections, None));
1390 }
1391
1392 #[allow(clippy::type_complexity)]
1393 fn transaction(
1394 &self,
1395 transaction_id: TransactionId,
1396 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1397 self.selections_by_transaction.get(&transaction_id)
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction_mut(
1402 &mut self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get_mut(&transaction_id)
1406 }
1407
1408 fn push(&mut self, entry: SelectionHistoryEntry) {
1409 if !entry.selections.is_empty() {
1410 match self.mode {
1411 SelectionHistoryMode::Normal => {
1412 self.push_undo(entry);
1413 self.redo_stack.clear();
1414 }
1415 SelectionHistoryMode::Undoing => self.push_redo(entry),
1416 SelectionHistoryMode::Redoing => self.push_undo(entry),
1417 SelectionHistoryMode::Skipping => {}
1418 }
1419 }
1420 }
1421
1422 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1423 if self
1424 .undo_stack
1425 .back()
1426 .is_none_or(|e| e.selections != entry.selections)
1427 {
1428 self.undo_stack.push_back(entry);
1429 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1430 self.undo_stack.pop_front();
1431 }
1432 }
1433 }
1434
1435 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1436 if self
1437 .redo_stack
1438 .back()
1439 .is_none_or(|e| e.selections != entry.selections)
1440 {
1441 self.redo_stack.push_back(entry);
1442 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1443 self.redo_stack.pop_front();
1444 }
1445 }
1446 }
1447}
1448
1449#[derive(Clone, Copy)]
1450pub struct RowHighlightOptions {
1451 pub autoscroll: bool,
1452 pub include_gutter: bool,
1453}
1454
1455impl Default for RowHighlightOptions {
1456 fn default() -> Self {
1457 Self {
1458 autoscroll: Default::default(),
1459 include_gutter: true,
1460 }
1461 }
1462}
1463
1464struct RowHighlight {
1465 index: usize,
1466 range: Range<Anchor>,
1467 color: Hsla,
1468 options: RowHighlightOptions,
1469 type_id: TypeId,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsState {
1474 groups: Vec<AddSelectionsGroup>,
1475}
1476
1477#[derive(Clone, Debug)]
1478struct AddSelectionsGroup {
1479 above: bool,
1480 stack: Vec<usize>,
1481}
1482
1483#[derive(Clone)]
1484struct SelectNextState {
1485 query: AhoCorasick,
1486 wordwise: bool,
1487 done: bool,
1488}
1489
1490impl std::fmt::Debug for SelectNextState {
1491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1492 f.debug_struct(std::any::type_name::<Self>())
1493 .field("wordwise", &self.wordwise)
1494 .field("done", &self.done)
1495 .finish()
1496 }
1497}
1498
1499#[derive(Debug)]
1500struct AutocloseRegion {
1501 selection_id: usize,
1502 range: Range<Anchor>,
1503 pair: BracketPair,
1504}
1505
1506#[derive(Debug)]
1507struct SnippetState {
1508 ranges: Vec<Vec<Range<Anchor>>>,
1509 active_index: usize,
1510 choices: Vec<Option<Vec<String>>>,
1511}
1512
1513#[doc(hidden)]
1514pub struct RenameState {
1515 pub range: Range<Anchor>,
1516 pub old_name: Arc<str>,
1517 pub editor: Entity<Editor>,
1518 block_id: CustomBlockId,
1519}
1520
1521struct InvalidationStack<T>(Vec<T>);
1522
1523struct RegisteredEditPredictionProvider {
1524 provider: Arc<dyn EditPredictionProviderHandle>,
1525 _subscription: Subscription,
1526}
1527
1528#[derive(Debug, PartialEq, Eq)]
1529pub struct ActiveDiagnosticGroup {
1530 pub active_range: Range<Anchor>,
1531 pub active_message: String,
1532 pub group_id: usize,
1533 pub blocks: HashSet<CustomBlockId>,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537
1538pub(crate) enum ActiveDiagnostic {
1539 None,
1540 All,
1541 Group(ActiveDiagnosticGroup),
1542}
1543
1544#[derive(Serialize, Deserialize, Clone, Debug)]
1545pub struct ClipboardSelection {
1546 /// The number of bytes in this selection.
1547 pub len: usize,
1548 /// Whether this was a full-line selection.
1549 pub is_entire_line: bool,
1550 /// The indentation of the first line when this content was originally copied.
1551 pub first_line_indent: u32,
1552}
1553
1554// selections, scroll behavior, was newest selection reversed
1555type SelectSyntaxNodeHistoryState = (
1556 Box<[Selection<usize>]>,
1557 SelectSyntaxNodeScrollBehavior,
1558 bool,
1559);
1560
1561#[derive(Default)]
1562struct SelectSyntaxNodeHistory {
1563 stack: Vec<SelectSyntaxNodeHistoryState>,
1564 // disable temporarily to allow changing selections without losing the stack
1565 pub disable_clearing: bool,
1566}
1567
1568impl SelectSyntaxNodeHistory {
1569 pub fn try_clear(&mut self) {
1570 if !self.disable_clearing {
1571 self.stack.clear();
1572 }
1573 }
1574
1575 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1576 self.stack.push(selection);
1577 }
1578
1579 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1580 self.stack.pop()
1581 }
1582}
1583
1584enum SelectSyntaxNodeScrollBehavior {
1585 CursorTop,
1586 FitSelection,
1587 CursorBottom,
1588}
1589
1590#[derive(Debug)]
1591pub(crate) struct NavigationData {
1592 cursor_anchor: Anchor,
1593 cursor_position: Point,
1594 scroll_anchor: ScrollAnchor,
1595 scroll_top_row: u32,
1596}
1597
1598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1599pub enum GotoDefinitionKind {
1600 Symbol,
1601 Declaration,
1602 Type,
1603 Implementation,
1604}
1605
1606#[derive(Debug, Clone)]
1607enum InlayHintRefreshReason {
1608 ModifiersChanged(bool),
1609 Toggle(bool),
1610 SettingsChange(InlayHintSettings),
1611 NewLinesShown,
1612 BufferEdited(HashSet<Arc<Language>>),
1613 RefreshRequested,
1614 ExcerptsRemoved(Vec<ExcerptId>),
1615}
1616
1617impl InlayHintRefreshReason {
1618 fn description(&self) -> &'static str {
1619 match self {
1620 Self::ModifiersChanged(_) => "modifiers changed",
1621 Self::Toggle(_) => "toggle",
1622 Self::SettingsChange(_) => "settings change",
1623 Self::NewLinesShown => "new lines shown",
1624 Self::BufferEdited(_) => "buffer edited",
1625 Self::RefreshRequested => "refresh requested",
1626 Self::ExcerptsRemoved(_) => "excerpts removed",
1627 }
1628 }
1629}
1630
1631pub enum FormatTarget {
1632 Buffers(HashSet<Entity<Buffer>>),
1633 Ranges(Vec<Range<MultiBufferPoint>>),
1634}
1635
1636pub(crate) struct FocusedBlock {
1637 id: BlockId,
1638 focus_handle: WeakFocusHandle,
1639}
1640
1641#[derive(Clone)]
1642enum JumpData {
1643 MultiBufferRow {
1644 row: MultiBufferRow,
1645 line_offset_from_top: u32,
1646 },
1647 MultiBufferPoint {
1648 excerpt_id: ExcerptId,
1649 position: Point,
1650 anchor: text::Anchor,
1651 line_offset_from_top: u32,
1652 },
1653}
1654
1655pub enum MultibufferSelectionMode {
1656 First,
1657 All,
1658}
1659
1660#[derive(Clone, Copy, Debug, Default)]
1661pub struct RewrapOptions {
1662 pub override_language_settings: bool,
1663 pub preserve_existing_whitespace: bool,
1664}
1665
1666impl Editor {
1667 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1671 }
1672
1673 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::full(), buffer, None, window, cx)
1677 }
1678
1679 pub fn auto_height(
1680 min_lines: usize,
1681 max_lines: usize,
1682 window: &mut Window,
1683 cx: &mut Context<Self>,
1684 ) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(
1688 EditorMode::AutoHeight {
1689 min_lines,
1690 max_lines: Some(max_lines),
1691 },
1692 buffer,
1693 None,
1694 window,
1695 cx,
1696 )
1697 }
1698
1699 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1700 /// The editor grows as tall as needed to fit its content.
1701 pub fn auto_height_unbounded(
1702 min_lines: usize,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 let buffer = cx.new(|cx| Buffer::local("", cx));
1707 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1708 Self::new(
1709 EditorMode::AutoHeight {
1710 min_lines,
1711 max_lines: None,
1712 },
1713 buffer,
1714 None,
1715 window,
1716 cx,
1717 )
1718 }
1719
1720 pub fn for_buffer(
1721 buffer: Entity<Buffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1727 Self::new(EditorMode::full(), buffer, project, window, cx)
1728 }
1729
1730 pub fn for_multibuffer(
1731 buffer: Entity<MultiBuffer>,
1732 project: Option<Entity<Project>>,
1733 window: &mut Window,
1734 cx: &mut Context<Self>,
1735 ) -> Self {
1736 Self::new(EditorMode::full(), buffer, project, window, cx)
1737 }
1738
1739 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1740 let mut clone = Self::new(
1741 self.mode.clone(),
1742 self.buffer.clone(),
1743 self.project.clone(),
1744 window,
1745 cx,
1746 );
1747 self.display_map.update(cx, |display_map, cx| {
1748 let snapshot = display_map.snapshot(cx);
1749 clone.display_map.update(cx, |display_map, cx| {
1750 display_map.set_state(&snapshot, cx);
1751 });
1752 });
1753 clone.folds_did_change(cx);
1754 clone.selections.clone_state(&self.selections);
1755 clone.scroll_manager.clone_state(&self.scroll_manager);
1756 clone.searchable = self.searchable;
1757 clone.read_only = self.read_only;
1758 clone
1759 }
1760
1761 pub fn new(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 window: &mut Window,
1766 cx: &mut Context<Self>,
1767 ) -> Self {
1768 Editor::new_internal(mode, buffer, project, None, window, cx)
1769 }
1770
1771 fn new_internal(
1772 mode: EditorMode,
1773 buffer: Entity<MultiBuffer>,
1774 project: Option<Entity<Project>>,
1775 display_map: Option<Entity<DisplayMap>>,
1776 window: &mut Window,
1777 cx: &mut Context<Self>,
1778 ) -> Self {
1779 debug_assert!(
1780 display_map.is_none() || mode.is_minimap(),
1781 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1782 );
1783
1784 let full_mode = mode.is_full();
1785 let is_minimap = mode.is_minimap();
1786 let diagnostics_max_severity = if full_mode {
1787 EditorSettings::get_global(cx)
1788 .diagnostics_max_severity
1789 .unwrap_or(DiagnosticSeverity::Hint)
1790 } else {
1791 DiagnosticSeverity::Off
1792 };
1793 let style = window.text_style();
1794 let font_size = style.font_size.to_pixels(window.rem_size());
1795 let editor = cx.entity().downgrade();
1796 let fold_placeholder = FoldPlaceholder {
1797 constrain_width: false,
1798 render: Arc::new(move |fold_id, fold_range, cx| {
1799 let editor = editor.clone();
1800 div()
1801 .id(fold_id)
1802 .bg(cx.theme().colors().ghost_element_background)
1803 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1804 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1805 .rounded_xs()
1806 .size_full()
1807 .cursor_pointer()
1808 .child("⋯")
1809 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1810 .on_click(move |_, _window, cx| {
1811 editor
1812 .update(cx, |editor, cx| {
1813 editor.unfold_ranges(
1814 &[fold_range.start..fold_range.end],
1815 true,
1816 false,
1817 cx,
1818 );
1819 cx.stop_propagation();
1820 })
1821 .ok();
1822 })
1823 .into_any()
1824 }),
1825 merge_adjacent: true,
1826 ..FoldPlaceholder::default()
1827 };
1828 let display_map = display_map.unwrap_or_else(|| {
1829 cx.new(|cx| {
1830 DisplayMap::new(
1831 buffer.clone(),
1832 style.font(),
1833 font_size,
1834 None,
1835 FILE_HEADER_HEIGHT,
1836 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1837 fold_placeholder,
1838 diagnostics_max_severity,
1839 cx,
1840 )
1841 })
1842 });
1843
1844 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1845
1846 let blink_manager = cx.new(|cx| {
1847 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1848 if is_minimap {
1849 blink_manager.disable(cx);
1850 }
1851 blink_manager
1852 });
1853
1854 let soft_wrap_mode_override =
1855 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1856
1857 let mut project_subscriptions = Vec::new();
1858 if full_mode && let Some(project) = project.as_ref() {
1859 project_subscriptions.push(cx.subscribe_in(
1860 project,
1861 window,
1862 |editor, _, event, window, cx| match event {
1863 project::Event::RefreshCodeLens => {
1864 // we always query lens with actions, without storing them, always refreshing them
1865 }
1866 project::Event::RefreshInlayHints => {
1867 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1868 }
1869 project::Event::LanguageServerAdded(..)
1870 | project::Event::LanguageServerRemoved(..) => {
1871 if editor.tasks_update_task.is_none() {
1872 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1873 }
1874 }
1875 project::Event::SnippetEdit(id, snippet_edits) => {
1876 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1877 let focus_handle = editor.focus_handle(cx);
1878 if focus_handle.is_focused(window) {
1879 let snapshot = buffer.read(cx).snapshot();
1880 for (range, snippet) in snippet_edits {
1881 let editor_range =
1882 language::range_from_lsp(*range).to_offset(&snapshot);
1883 editor
1884 .insert_snippet(
1885 &[editor_range],
1886 snippet.clone(),
1887 window,
1888 cx,
1889 )
1890 .ok();
1891 }
1892 }
1893 }
1894 }
1895 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1896 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1897 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1898 }
1899 }
1900
1901 project::Event::EntryRenamed(transaction) => {
1902 let Some(workspace) = editor.workspace() else {
1903 return;
1904 };
1905 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1906 else {
1907 return;
1908 };
1909 if active_editor.entity_id() == cx.entity_id() {
1910 let edited_buffers_already_open = {
1911 let other_editors: Vec<Entity<Editor>> = workspace
1912 .read(cx)
1913 .panes()
1914 .iter()
1915 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1916 .filter(|editor| editor.entity_id() != cx.entity_id())
1917 .collect();
1918
1919 transaction.0.keys().all(|buffer| {
1920 other_editors.iter().any(|editor| {
1921 let multi_buffer = editor.read(cx).buffer();
1922 multi_buffer.read(cx).is_singleton()
1923 && multi_buffer.read(cx).as_singleton().map_or(
1924 false,
1925 |singleton| {
1926 singleton.entity_id() == buffer.entity_id()
1927 },
1928 )
1929 })
1930 })
1931 };
1932
1933 if !edited_buffers_already_open {
1934 let workspace = workspace.downgrade();
1935 let transaction = transaction.clone();
1936 cx.defer_in(window, move |_, window, cx| {
1937 cx.spawn_in(window, async move |editor, cx| {
1938 Self::open_project_transaction(
1939 &editor,
1940 workspace,
1941 transaction,
1942 "Rename".to_string(),
1943 cx,
1944 )
1945 .await
1946 .ok()
1947 })
1948 .detach();
1949 });
1950 }
1951 }
1952 }
1953
1954 _ => {}
1955 },
1956 ));
1957 if let Some(task_inventory) = project
1958 .read(cx)
1959 .task_store()
1960 .read(cx)
1961 .task_inventory()
1962 .cloned()
1963 {
1964 project_subscriptions.push(cx.observe_in(
1965 &task_inventory,
1966 window,
1967 |editor, _, window, cx| {
1968 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1969 },
1970 ));
1971 };
1972
1973 project_subscriptions.push(cx.subscribe_in(
1974 &project.read(cx).breakpoint_store(),
1975 window,
1976 |editor, _, event, window, cx| match event {
1977 BreakpointStoreEvent::ClearDebugLines => {
1978 editor.clear_row_highlights::<ActiveDebugLine>();
1979 editor.refresh_inline_values(cx);
1980 }
1981 BreakpointStoreEvent::SetDebugLine => {
1982 if editor.go_to_active_debug_line(window, cx) {
1983 cx.stop_propagation();
1984 }
1985
1986 editor.refresh_inline_values(cx);
1987 }
1988 _ => {}
1989 },
1990 ));
1991 let git_store = project.read(cx).git_store().clone();
1992 let project = project.clone();
1993 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1994 if let GitStoreEvent::RepositoryUpdated(
1995 _,
1996 RepositoryEvent::Updated {
1997 new_instance: true, ..
1998 },
1999 _,
2000 ) = event
2001 {
2002 this.load_diff_task = Some(
2003 update_uncommitted_diff_for_buffer(
2004 cx.entity(),
2005 &project,
2006 this.buffer.read(cx).all_buffers(),
2007 this.buffer.clone(),
2008 cx,
2009 )
2010 .shared(),
2011 );
2012 }
2013 }));
2014 }
2015
2016 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2017
2018 let inlay_hint_settings =
2019 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2020 let focus_handle = cx.focus_handle();
2021 if !is_minimap {
2022 cx.on_focus(&focus_handle, window, Self::handle_focus)
2023 .detach();
2024 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2025 .detach();
2026 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2027 .detach();
2028 cx.on_blur(&focus_handle, window, Self::handle_blur)
2029 .detach();
2030 cx.observe_pending_input(window, Self::observe_pending_input)
2031 .detach();
2032 }
2033
2034 let show_indent_guides =
2035 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2036 Some(false)
2037 } else {
2038 None
2039 };
2040
2041 let breakpoint_store = match (&mode, project.as_ref()) {
2042 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2043 _ => None,
2044 };
2045
2046 let mut code_action_providers = Vec::new();
2047 let mut load_uncommitted_diff = None;
2048 if let Some(project) = project.clone() {
2049 load_uncommitted_diff = Some(
2050 update_uncommitted_diff_for_buffer(
2051 cx.entity(),
2052 &project,
2053 buffer.read(cx).all_buffers(),
2054 buffer.clone(),
2055 cx,
2056 )
2057 .shared(),
2058 );
2059 code_action_providers.push(Rc::new(project) as Rc<_>);
2060 }
2061
2062 let mut editor = Self {
2063 focus_handle,
2064 show_cursor_when_unfocused: false,
2065 last_focused_descendant: None,
2066 buffer: buffer.clone(),
2067 display_map: display_map.clone(),
2068 placeholder_display_map: None,
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 highlight_order: 0,
2113 highlighted_rows: HashMap::default(),
2114 background_highlights: HashMap::default(),
2115 gutter_highlights: HashMap::default(),
2116 scrollbar_marker_state: ScrollbarMarkerState::default(),
2117 active_indent_guides_state: ActiveIndentGuidesState::default(),
2118 nav_history: None,
2119 context_menu: RefCell::new(None),
2120 context_menu_options: None,
2121 mouse_context_menu: None,
2122 completion_tasks: Vec::new(),
2123 inline_blame_popover: None,
2124 inline_blame_popover_show_task: None,
2125 signature_help_state: SignatureHelpState::default(),
2126 auto_signature_help: None,
2127 find_all_references_task_sources: Vec::new(),
2128 next_completion_id: 0,
2129 next_inlay_id: 0,
2130 code_action_providers,
2131 available_code_actions: None,
2132 code_actions_task: None,
2133 quick_selection_highlight_task: None,
2134 debounced_selection_highlight_task: None,
2135 document_highlights_task: None,
2136 linked_editing_range_task: None,
2137 pending_rename: None,
2138 searchable: !is_minimap,
2139 cursor_shape: EditorSettings::get_global(cx)
2140 .cursor_shape
2141 .unwrap_or_default(),
2142 current_line_highlight: None,
2143 autoindent_mode: Some(AutoindentMode::EachLine),
2144 collapse_matches: false,
2145 workspace: None,
2146 input_enabled: !is_minimap,
2147 use_modal_editing: full_mode,
2148 read_only: is_minimap,
2149 use_autoclose: true,
2150 use_auto_surround: true,
2151 auto_replace_emoji_shortcode: false,
2152 jsx_tag_auto_close_enabled_in_any_buffer: false,
2153 leader_id: None,
2154 remote_id: None,
2155 hover_state: HoverState::default(),
2156 pending_mouse_down: None,
2157 hovered_link_state: None,
2158 edit_prediction_provider: None,
2159 active_edit_prediction: None,
2160 stale_edit_prediction_in_menu: None,
2161 edit_prediction_preview: EditPredictionPreview::Inactive {
2162 released_too_fast: false,
2163 },
2164 inline_diagnostics_enabled: full_mode,
2165 diagnostics_enabled: full_mode,
2166 word_completions_enabled: full_mode,
2167 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2168 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2169 gutter_hovered: false,
2170 pixel_position_of_newest_cursor: None,
2171 last_bounds: None,
2172 last_position_map: None,
2173 expect_bounds_change: None,
2174 gutter_dimensions: GutterDimensions::default(),
2175 style: None,
2176 show_cursor_names: false,
2177 hovered_cursors: HashMap::default(),
2178 next_editor_action_id: EditorActionId::default(),
2179 editor_actions: Rc::default(),
2180 edit_predictions_hidden_for_vim_mode: false,
2181 show_edit_predictions_override: None,
2182 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2183 edit_prediction_settings: EditPredictionSettings::Disabled,
2184 edit_prediction_indent_conflict: false,
2185 edit_prediction_requires_modifier_in_indent_conflict: true,
2186 custom_context_menu: None,
2187 show_git_blame_gutter: false,
2188 show_git_blame_inline: false,
2189 show_selection_menu: None,
2190 show_git_blame_inline_delay_task: None,
2191 git_blame_inline_enabled: full_mode
2192 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2193 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2194 serialize_dirty_buffers: !is_minimap
2195 && ProjectSettings::get_global(cx)
2196 .session
2197 .restore_unsaved_buffers,
2198 blame: None,
2199 blame_subscription: None,
2200 tasks: BTreeMap::default(),
2201
2202 breakpoint_store,
2203 gutter_breakpoint_indicator: (None, None),
2204 hovered_diff_hunk_row: None,
2205 _subscriptions: (!is_minimap)
2206 .then(|| {
2207 vec![
2208 cx.observe(&buffer, Self::on_buffer_changed),
2209 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2210 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2211 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2212 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2213 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2214 cx.observe_window_activation(window, |editor, window, cx| {
2215 let active = window.is_window_active();
2216 editor.blink_manager.update(cx, |blink_manager, cx| {
2217 if active {
2218 blink_manager.enable(cx);
2219 } else {
2220 blink_manager.disable(cx);
2221 }
2222 });
2223 if active {
2224 editor.show_mouse_cursor(cx);
2225 }
2226 }),
2227 ]
2228 })
2229 .unwrap_or_default(),
2230 tasks_update_task: None,
2231 pull_diagnostics_task: Task::ready(()),
2232 colors: None,
2233 next_color_inlay_id: 0,
2234 linked_edit_ranges: Default::default(),
2235 in_project_search: false,
2236 previous_search_ranges: None,
2237 breadcrumb_header: None,
2238 focused_block: None,
2239 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2240 addons: HashMap::default(),
2241 registered_buffers: HashMap::default(),
2242 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2243 selection_mark_mode: false,
2244 toggle_fold_multiple_buffers: Task::ready(()),
2245 serialize_selections: Task::ready(()),
2246 serialize_folds: Task::ready(()),
2247 text_style_refinement: None,
2248 load_diff_task: load_uncommitted_diff,
2249 temporary_diff_override: false,
2250 mouse_cursor_hidden: false,
2251 minimap: None,
2252 hide_mouse_mode: EditorSettings::get_global(cx)
2253 .hide_mouse
2254 .unwrap_or_default(),
2255 change_list: ChangeList::new(),
2256 mode,
2257 selection_drag_state: SelectionDragState::None,
2258 folding_newlines: Task::ready(()),
2259 lookup_key: None,
2260 };
2261
2262 if is_minimap {
2263 return editor;
2264 }
2265
2266 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2267 editor
2268 ._subscriptions
2269 .push(cx.observe(breakpoints, |_, _, cx| {
2270 cx.notify();
2271 }));
2272 }
2273 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2274 editor._subscriptions.extend(project_subscriptions);
2275
2276 editor._subscriptions.push(cx.subscribe_in(
2277 &cx.entity(),
2278 window,
2279 |editor, _, e: &EditorEvent, window, cx| match e {
2280 EditorEvent::ScrollPositionChanged { local, .. } => {
2281 if *local {
2282 let new_anchor = editor.scroll_manager.anchor();
2283 let snapshot = editor.snapshot(window, cx);
2284 editor.update_restoration_data(cx, move |data| {
2285 data.scroll_position = (
2286 new_anchor.top_row(&snapshot.buffer_snapshot),
2287 new_anchor.offset,
2288 );
2289 });
2290 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2291 editor.inline_blame_popover.take();
2292 }
2293 }
2294 EditorEvent::Edited { .. } => {
2295 if !vim_enabled(cx) {
2296 let (map, selections) = editor.selections.all_adjusted_display(cx);
2297 let pop_state = editor
2298 .change_list
2299 .last()
2300 .map(|previous| {
2301 previous.len() == selections.len()
2302 && previous.iter().enumerate().all(|(ix, p)| {
2303 p.to_display_point(&map).row()
2304 == selections[ix].head().row()
2305 })
2306 })
2307 .unwrap_or(false);
2308 let new_positions = selections
2309 .into_iter()
2310 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2311 .collect();
2312 editor
2313 .change_list
2314 .push_to_change_list(pop_state, new_positions);
2315 }
2316 }
2317 _ => (),
2318 },
2319 ));
2320
2321 if let Some(dap_store) = editor
2322 .project
2323 .as_ref()
2324 .map(|project| project.read(cx).dap_store())
2325 {
2326 let weak_editor = cx.weak_entity();
2327
2328 editor
2329 ._subscriptions
2330 .push(
2331 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2332 let session_entity = cx.entity();
2333 weak_editor
2334 .update(cx, |editor, cx| {
2335 editor._subscriptions.push(
2336 cx.subscribe(&session_entity, Self::on_debug_session_event),
2337 );
2338 })
2339 .ok();
2340 }),
2341 );
2342
2343 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2344 editor
2345 ._subscriptions
2346 .push(cx.subscribe(&session, Self::on_debug_session_event));
2347 }
2348 }
2349
2350 // skip adding the initial selection to selection history
2351 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2352 editor.end_selection(window, cx);
2353 editor.selection_history.mode = SelectionHistoryMode::Normal;
2354
2355 editor.scroll_manager.show_scrollbars(window, cx);
2356 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2357
2358 if full_mode {
2359 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2360 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2361
2362 if editor.git_blame_inline_enabled {
2363 editor.start_git_blame_inline(false, window, cx);
2364 }
2365
2366 editor.go_to_active_debug_line(window, cx);
2367
2368 if let Some(buffer) = buffer.read(cx).as_singleton()
2369 && let Some(project) = editor.project()
2370 {
2371 let handle = project.update(cx, |project, cx| {
2372 project.register_buffer_with_language_servers(&buffer, cx)
2373 });
2374 editor
2375 .registered_buffers
2376 .insert(buffer.read(cx).remote_id(), handle);
2377 }
2378
2379 editor.minimap =
2380 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2381 editor.colors = Some(LspColorData::new(cx));
2382 editor.update_lsp_data(false, None, window, cx);
2383 }
2384
2385 if editor.mode.is_full() {
2386 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2387 }
2388
2389 editor
2390 }
2391
2392 pub fn deploy_mouse_context_menu(
2393 &mut self,
2394 position: gpui::Point<Pixels>,
2395 context_menu: Entity<ContextMenu>,
2396 window: &mut Window,
2397 cx: &mut Context<Self>,
2398 ) {
2399 self.mouse_context_menu = Some(MouseContextMenu::new(
2400 self,
2401 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2402 context_menu,
2403 window,
2404 cx,
2405 ));
2406 }
2407
2408 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2409 self.mouse_context_menu
2410 .as_ref()
2411 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2412 }
2413
2414 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2415 if self
2416 .selections
2417 .pending_anchor()
2418 .is_some_and(|pending_selection| {
2419 let snapshot = self.buffer().read(cx).snapshot(cx);
2420 pending_selection.range().includes(range, &snapshot)
2421 })
2422 {
2423 return true;
2424 }
2425
2426 self.selections
2427 .disjoint_in_range::<usize>(range.clone(), cx)
2428 .into_iter()
2429 .any(|selection| {
2430 // This is needed to cover a corner case, if we just check for an existing
2431 // selection in the fold range, having a cursor at the start of the fold
2432 // marks it as selected. Non-empty selections don't cause this.
2433 let length = selection.end - selection.start;
2434 length > 0
2435 })
2436 }
2437
2438 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2439 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2440 }
2441
2442 fn key_context_internal(
2443 &self,
2444 has_active_edit_prediction: bool,
2445 window: &Window,
2446 cx: &App,
2447 ) -> KeyContext {
2448 let mut key_context = KeyContext::new_with_defaults();
2449 key_context.add("Editor");
2450 let mode = match self.mode {
2451 EditorMode::SingleLine => "single_line",
2452 EditorMode::AutoHeight { .. } => "auto_height",
2453 EditorMode::Minimap { .. } => "minimap",
2454 EditorMode::Full { .. } => "full",
2455 };
2456
2457 if EditorSettings::jupyter_enabled(cx) {
2458 key_context.add("jupyter");
2459 }
2460
2461 key_context.set("mode", mode);
2462 if self.pending_rename.is_some() {
2463 key_context.add("renaming");
2464 }
2465
2466 match self.context_menu.borrow().as_ref() {
2467 Some(CodeContextMenu::Completions(menu)) => {
2468 if menu.visible() {
2469 key_context.add("menu");
2470 key_context.add("showing_completions");
2471 }
2472 }
2473 Some(CodeContextMenu::CodeActions(menu)) => {
2474 if menu.visible() {
2475 key_context.add("menu");
2476 key_context.add("showing_code_actions")
2477 }
2478 }
2479 None => {}
2480 }
2481
2482 if self.signature_help_state.has_multiple_signatures() {
2483 key_context.add("showing_signature_help");
2484 }
2485
2486 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2487 if !self.focus_handle(cx).contains_focused(window, cx)
2488 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2489 {
2490 for addon in self.addons.values() {
2491 addon.extend_key_context(&mut key_context, cx)
2492 }
2493 }
2494
2495 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2496 if let Some(extension) = singleton_buffer
2497 .read(cx)
2498 .file()
2499 .and_then(|file| file.path().extension()?.to_str())
2500 {
2501 key_context.set("extension", extension.to_string());
2502 }
2503 } else {
2504 key_context.add("multibuffer");
2505 }
2506
2507 if has_active_edit_prediction {
2508 if self.edit_prediction_in_conflict() {
2509 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2510 } else {
2511 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2512 key_context.add("copilot_suggestion");
2513 }
2514 }
2515
2516 if self.selection_mark_mode {
2517 key_context.add("selection_mode");
2518 }
2519
2520 key_context
2521 }
2522
2523 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2524 if self.mouse_cursor_hidden {
2525 self.mouse_cursor_hidden = false;
2526 cx.notify();
2527 }
2528 }
2529
2530 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2531 let hide_mouse_cursor = match origin {
2532 HideMouseCursorOrigin::TypingAction => {
2533 matches!(
2534 self.hide_mouse_mode,
2535 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2536 )
2537 }
2538 HideMouseCursorOrigin::MovementAction => {
2539 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2540 }
2541 };
2542 if self.mouse_cursor_hidden != hide_mouse_cursor {
2543 self.mouse_cursor_hidden = hide_mouse_cursor;
2544 cx.notify();
2545 }
2546 }
2547
2548 pub fn edit_prediction_in_conflict(&self) -> bool {
2549 if !self.show_edit_predictions_in_menu() {
2550 return false;
2551 }
2552
2553 let showing_completions = self
2554 .context_menu
2555 .borrow()
2556 .as_ref()
2557 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2558
2559 showing_completions
2560 || self.edit_prediction_requires_modifier()
2561 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2562 // bindings to insert tab characters.
2563 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2564 }
2565
2566 pub fn accept_edit_prediction_keybind(
2567 &self,
2568 accept_partial: bool,
2569 window: &Window,
2570 cx: &App,
2571 ) -> AcceptEditPredictionBinding {
2572 let key_context = self.key_context_internal(true, window, cx);
2573 let in_conflict = self.edit_prediction_in_conflict();
2574
2575 let bindings = if accept_partial {
2576 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2577 } else {
2578 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2579 };
2580
2581 // TODO: if the binding contains multiple keystrokes, display all of them, not
2582 // just the first one.
2583 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2584 !in_conflict
2585 || binding
2586 .keystrokes()
2587 .first()
2588 .is_some_and(|keystroke| keystroke.modifiers().modified())
2589 }))
2590 }
2591
2592 pub fn new_file(
2593 workspace: &mut Workspace,
2594 _: &workspace::NewFile,
2595 window: &mut Window,
2596 cx: &mut Context<Workspace>,
2597 ) {
2598 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2599 "Failed to create buffer",
2600 window,
2601 cx,
2602 |e, _, _| match e.error_code() {
2603 ErrorCode::RemoteUpgradeRequired => Some(format!(
2604 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2605 e.error_tag("required").unwrap_or("the latest version")
2606 )),
2607 _ => None,
2608 },
2609 );
2610 }
2611
2612 pub fn new_in_workspace(
2613 workspace: &mut Workspace,
2614 window: &mut Window,
2615 cx: &mut Context<Workspace>,
2616 ) -> Task<Result<Entity<Editor>>> {
2617 let project = workspace.project().clone();
2618 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2619
2620 cx.spawn_in(window, async move |workspace, cx| {
2621 let buffer = create.await?;
2622 workspace.update_in(cx, |workspace, window, cx| {
2623 let editor =
2624 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2625 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2626 editor
2627 })
2628 })
2629 }
2630
2631 fn new_file_vertical(
2632 workspace: &mut Workspace,
2633 _: &workspace::NewFileSplitVertical,
2634 window: &mut Window,
2635 cx: &mut Context<Workspace>,
2636 ) {
2637 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2638 }
2639
2640 fn new_file_horizontal(
2641 workspace: &mut Workspace,
2642 _: &workspace::NewFileSplitHorizontal,
2643 window: &mut Window,
2644 cx: &mut Context<Workspace>,
2645 ) {
2646 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2647 }
2648
2649 fn new_file_in_direction(
2650 workspace: &mut Workspace,
2651 direction: SplitDirection,
2652 window: &mut Window,
2653 cx: &mut Context<Workspace>,
2654 ) {
2655 let project = workspace.project().clone();
2656 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2657
2658 cx.spawn_in(window, async move |workspace, cx| {
2659 let buffer = create.await?;
2660 workspace.update_in(cx, move |workspace, window, cx| {
2661 workspace.split_item(
2662 direction,
2663 Box::new(
2664 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2665 ),
2666 window,
2667 cx,
2668 )
2669 })?;
2670 anyhow::Ok(())
2671 })
2672 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2673 match e.error_code() {
2674 ErrorCode::RemoteUpgradeRequired => Some(format!(
2675 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2676 e.error_tag("required").unwrap_or("the latest version")
2677 )),
2678 _ => None,
2679 }
2680 });
2681 }
2682
2683 pub fn leader_id(&self) -> Option<CollaboratorId> {
2684 self.leader_id
2685 }
2686
2687 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2688 &self.buffer
2689 }
2690
2691 pub fn project(&self) -> Option<&Entity<Project>> {
2692 self.project.as_ref()
2693 }
2694
2695 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2696 self.workspace.as_ref()?.0.upgrade()
2697 }
2698
2699 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2700 self.buffer().read(cx).title(cx)
2701 }
2702
2703 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2704 let git_blame_gutter_max_author_length = self
2705 .render_git_blame_gutter(cx)
2706 .then(|| {
2707 if let Some(blame) = self.blame.as_ref() {
2708 let max_author_length =
2709 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2710 Some(max_author_length)
2711 } else {
2712 None
2713 }
2714 })
2715 .flatten();
2716
2717 EditorSnapshot {
2718 mode: self.mode.clone(),
2719 show_gutter: self.show_gutter,
2720 show_line_numbers: self.show_line_numbers,
2721 show_git_diff_gutter: self.show_git_diff_gutter,
2722 show_code_actions: self.show_code_actions,
2723 show_runnables: self.show_runnables,
2724 show_breakpoints: self.show_breakpoints,
2725 git_blame_gutter_max_author_length,
2726 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2727 placeholder_display_snapshot: self
2728 .placeholder_display_map
2729 .as_ref()
2730 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2731 scroll_anchor: self.scroll_manager.anchor(),
2732 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2733 is_focused: self.focus_handle.is_focused(window),
2734 current_line_highlight: self
2735 .current_line_highlight
2736 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2737 gutter_hovered: self.gutter_hovered,
2738 }
2739 }
2740
2741 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2742 self.buffer.read(cx).language_at(point, cx)
2743 }
2744
2745 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2746 self.buffer.read(cx).read(cx).file_at(point).cloned()
2747 }
2748
2749 pub fn active_excerpt(
2750 &self,
2751 cx: &App,
2752 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2753 self.buffer
2754 .read(cx)
2755 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2756 }
2757
2758 pub fn mode(&self) -> &EditorMode {
2759 &self.mode
2760 }
2761
2762 pub fn set_mode(&mut self, mode: EditorMode) {
2763 self.mode = mode;
2764 }
2765
2766 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2767 self.collaboration_hub.as_deref()
2768 }
2769
2770 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2771 self.collaboration_hub = Some(hub);
2772 }
2773
2774 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2775 self.in_project_search = in_project_search;
2776 }
2777
2778 pub fn set_custom_context_menu(
2779 &mut self,
2780 f: impl 'static
2781 + Fn(
2782 &mut Self,
2783 DisplayPoint,
2784 &mut Window,
2785 &mut Context<Self>,
2786 ) -> Option<Entity<ui::ContextMenu>>,
2787 ) {
2788 self.custom_context_menu = Some(Box::new(f))
2789 }
2790
2791 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2792 self.completion_provider = provider;
2793 }
2794
2795 #[cfg(any(test, feature = "test-support"))]
2796 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2797 self.completion_provider.clone()
2798 }
2799
2800 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2801 self.semantics_provider.clone()
2802 }
2803
2804 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2805 self.semantics_provider = provider;
2806 }
2807
2808 pub fn set_edit_prediction_provider<T>(
2809 &mut self,
2810 provider: Option<Entity<T>>,
2811 window: &mut Window,
2812 cx: &mut Context<Self>,
2813 ) where
2814 T: EditPredictionProvider,
2815 {
2816 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2817 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2818 if this.focus_handle.is_focused(window) {
2819 this.update_visible_edit_prediction(window, cx);
2820 }
2821 }),
2822 provider: Arc::new(provider),
2823 });
2824 self.update_edit_prediction_settings(cx);
2825 self.refresh_edit_prediction(false, false, window, cx);
2826 }
2827
2828 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2829 self.placeholder_display_map
2830 .as_ref()
2831 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2832 }
2833
2834 pub fn set_placeholder_text(
2835 &mut self,
2836 placeholder_text: &str,
2837 window: &mut Window,
2838 cx: &mut Context<Self>,
2839 ) {
2840 let multibuffer = cx
2841 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2842
2843 let style = window.text_style();
2844
2845 self.placeholder_display_map = Some(cx.new(|cx| {
2846 DisplayMap::new(
2847 multibuffer,
2848 style.font(),
2849 style.font_size.to_pixels(window.rem_size()),
2850 None,
2851 FILE_HEADER_HEIGHT,
2852 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2853 Default::default(),
2854 DiagnosticSeverity::Off,
2855 cx,
2856 )
2857 }));
2858 cx.notify();
2859 }
2860
2861 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2862 self.cursor_shape = cursor_shape;
2863
2864 // Disrupt blink for immediate user feedback that the cursor shape has changed
2865 self.blink_manager.update(cx, BlinkManager::show_cursor);
2866
2867 cx.notify();
2868 }
2869
2870 pub fn set_current_line_highlight(
2871 &mut self,
2872 current_line_highlight: Option<CurrentLineHighlight>,
2873 ) {
2874 self.current_line_highlight = current_line_highlight;
2875 }
2876
2877 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2878 self.collapse_matches = collapse_matches;
2879 }
2880
2881 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2882 let buffers = self.buffer.read(cx).all_buffers();
2883 let Some(project) = self.project.as_ref() else {
2884 return;
2885 };
2886 project.update(cx, |project, cx| {
2887 for buffer in buffers {
2888 self.registered_buffers
2889 .entry(buffer.read(cx).remote_id())
2890 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2891 }
2892 })
2893 }
2894
2895 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2896 if self.collapse_matches {
2897 return range.start..range.start;
2898 }
2899 range.clone()
2900 }
2901
2902 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2903 if self.display_map.read(cx).clip_at_line_ends != clip {
2904 self.display_map
2905 .update(cx, |map, _| map.clip_at_line_ends = clip);
2906 }
2907 }
2908
2909 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2910 self.input_enabled = input_enabled;
2911 }
2912
2913 pub fn set_edit_predictions_hidden_for_vim_mode(
2914 &mut self,
2915 hidden: bool,
2916 window: &mut Window,
2917 cx: &mut Context<Self>,
2918 ) {
2919 if hidden != self.edit_predictions_hidden_for_vim_mode {
2920 self.edit_predictions_hidden_for_vim_mode = hidden;
2921 if hidden {
2922 self.update_visible_edit_prediction(window, cx);
2923 } else {
2924 self.refresh_edit_prediction(true, false, window, cx);
2925 }
2926 }
2927 }
2928
2929 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2930 self.menu_edit_predictions_policy = value;
2931 }
2932
2933 pub fn set_autoindent(&mut self, autoindent: bool) {
2934 if autoindent {
2935 self.autoindent_mode = Some(AutoindentMode::EachLine);
2936 } else {
2937 self.autoindent_mode = None;
2938 }
2939 }
2940
2941 pub fn read_only(&self, cx: &App) -> bool {
2942 self.read_only || self.buffer.read(cx).read_only()
2943 }
2944
2945 pub fn set_read_only(&mut self, read_only: bool) {
2946 self.read_only = read_only;
2947 }
2948
2949 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2950 self.use_autoclose = autoclose;
2951 }
2952
2953 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2954 self.use_auto_surround = auto_surround;
2955 }
2956
2957 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2958 self.auto_replace_emoji_shortcode = auto_replace;
2959 }
2960
2961 pub fn toggle_edit_predictions(
2962 &mut self,
2963 _: &ToggleEditPrediction,
2964 window: &mut Window,
2965 cx: &mut Context<Self>,
2966 ) {
2967 if self.show_edit_predictions_override.is_some() {
2968 self.set_show_edit_predictions(None, window, cx);
2969 } else {
2970 let show_edit_predictions = !self.edit_predictions_enabled();
2971 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2972 }
2973 }
2974
2975 pub fn set_show_edit_predictions(
2976 &mut self,
2977 show_edit_predictions: Option<bool>,
2978 window: &mut Window,
2979 cx: &mut Context<Self>,
2980 ) {
2981 self.show_edit_predictions_override = show_edit_predictions;
2982 self.update_edit_prediction_settings(cx);
2983
2984 if let Some(false) = show_edit_predictions {
2985 self.discard_edit_prediction(false, cx);
2986 } else {
2987 self.refresh_edit_prediction(false, true, window, cx);
2988 }
2989 }
2990
2991 fn edit_predictions_disabled_in_scope(
2992 &self,
2993 buffer: &Entity<Buffer>,
2994 buffer_position: language::Anchor,
2995 cx: &App,
2996 ) -> bool {
2997 let snapshot = buffer.read(cx).snapshot();
2998 let settings = snapshot.settings_at(buffer_position, cx);
2999
3000 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3001 return false;
3002 };
3003
3004 scope.override_name().is_some_and(|scope_name| {
3005 settings
3006 .edit_predictions_disabled_in
3007 .iter()
3008 .any(|s| s == scope_name)
3009 })
3010 }
3011
3012 pub fn set_use_modal_editing(&mut self, to: bool) {
3013 self.use_modal_editing = to;
3014 }
3015
3016 pub fn use_modal_editing(&self) -> bool {
3017 self.use_modal_editing
3018 }
3019
3020 fn selections_did_change(
3021 &mut self,
3022 local: bool,
3023 old_cursor_position: &Anchor,
3024 effects: SelectionEffects,
3025 window: &mut Window,
3026 cx: &mut Context<Self>,
3027 ) {
3028 window.invalidate_character_coordinates();
3029
3030 // Copy selections to primary selection buffer
3031 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3032 if local {
3033 let selections = self.selections.all::<usize>(cx);
3034 let buffer_handle = self.buffer.read(cx).read(cx);
3035
3036 let mut text = String::new();
3037 for (index, selection) in selections.iter().enumerate() {
3038 let text_for_selection = buffer_handle
3039 .text_for_range(selection.start..selection.end)
3040 .collect::<String>();
3041
3042 text.push_str(&text_for_selection);
3043 if index != selections.len() - 1 {
3044 text.push('\n');
3045 }
3046 }
3047
3048 if !text.is_empty() {
3049 cx.write_to_primary(ClipboardItem::new_string(text));
3050 }
3051 }
3052
3053 let selection_anchors = self.selections.disjoint_anchors_arc();
3054
3055 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3056 self.buffer.update(cx, |buffer, cx| {
3057 buffer.set_active_selections(
3058 &selection_anchors,
3059 self.selections.line_mode,
3060 self.cursor_shape,
3061 cx,
3062 )
3063 });
3064 }
3065 let display_map = self
3066 .display_map
3067 .update(cx, |display_map, cx| display_map.snapshot(cx));
3068 let buffer = &display_map.buffer_snapshot;
3069 if self.selections.count() == 1 {
3070 self.add_selections_state = None;
3071 }
3072 self.select_next_state = None;
3073 self.select_prev_state = None;
3074 self.select_syntax_node_history.try_clear();
3075 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3076 self.snippet_stack.invalidate(&selection_anchors, buffer);
3077 self.take_rename(false, window, cx);
3078
3079 let newest_selection = self.selections.newest_anchor();
3080 let new_cursor_position = newest_selection.head();
3081 let selection_start = newest_selection.start;
3082
3083 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3084 self.push_to_nav_history(
3085 *old_cursor_position,
3086 Some(new_cursor_position.to_point(buffer)),
3087 false,
3088 effects.nav_history == Some(true),
3089 cx,
3090 );
3091 }
3092
3093 if local {
3094 if let Some(buffer_id) = new_cursor_position.buffer_id
3095 && !self.registered_buffers.contains_key(&buffer_id)
3096 && let Some(project) = self.project.as_ref()
3097 {
3098 project.update(cx, |project, cx| {
3099 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3100 return;
3101 };
3102 self.registered_buffers.insert(
3103 buffer_id,
3104 project.register_buffer_with_language_servers(&buffer, cx),
3105 );
3106 })
3107 }
3108
3109 let mut context_menu = self.context_menu.borrow_mut();
3110 let completion_menu = match context_menu.as_ref() {
3111 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3112 Some(CodeContextMenu::CodeActions(_)) => {
3113 *context_menu = None;
3114 None
3115 }
3116 None => None,
3117 };
3118 let completion_position = completion_menu.map(|menu| menu.initial_position);
3119 drop(context_menu);
3120
3121 if effects.completions
3122 && let Some(completion_position) = completion_position
3123 {
3124 let start_offset = selection_start.to_offset(buffer);
3125 let position_matches = start_offset == completion_position.to_offset(buffer);
3126 let continue_showing = if position_matches {
3127 if self.snippet_stack.is_empty() {
3128 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3129 } else {
3130 // Snippet choices can be shown even when the cursor is in whitespace.
3131 // Dismissing the menu with actions like backspace is handled by
3132 // invalidation regions.
3133 true
3134 }
3135 } else {
3136 false
3137 };
3138
3139 if continue_showing {
3140 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3141 } else {
3142 self.hide_context_menu(window, cx);
3143 }
3144 }
3145
3146 hide_hover(self, cx);
3147
3148 if old_cursor_position.to_display_point(&display_map).row()
3149 != new_cursor_position.to_display_point(&display_map).row()
3150 {
3151 self.available_code_actions.take();
3152 }
3153 self.refresh_code_actions(window, cx);
3154 self.refresh_document_highlights(cx);
3155 self.refresh_selected_text_highlights(false, window, cx);
3156 refresh_matching_bracket_highlights(self, window, cx);
3157 self.update_visible_edit_prediction(window, cx);
3158 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3159 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3160 self.inline_blame_popover.take();
3161 if self.git_blame_inline_enabled {
3162 self.start_inline_blame_timer(window, cx);
3163 }
3164 }
3165
3166 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3167 cx.emit(EditorEvent::SelectionsChanged { local });
3168
3169 let selections = &self.selections.disjoint_anchors_arc();
3170 if selections.len() == 1 {
3171 cx.emit(SearchEvent::ActiveMatchChanged)
3172 }
3173 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3174 let inmemory_selections = selections
3175 .iter()
3176 .map(|s| {
3177 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3178 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3179 })
3180 .collect();
3181 self.update_restoration_data(cx, |data| {
3182 data.selections = inmemory_selections;
3183 });
3184
3185 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3186 && let Some(workspace_id) =
3187 self.workspace.as_ref().and_then(|workspace| workspace.1)
3188 {
3189 let snapshot = self.buffer().read(cx).snapshot(cx);
3190 let selections = selections.clone();
3191 let background_executor = cx.background_executor().clone();
3192 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3193 self.serialize_selections = cx.background_spawn(async move {
3194 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3195 let db_selections = selections
3196 .iter()
3197 .map(|selection| {
3198 (
3199 selection.start.to_offset(&snapshot),
3200 selection.end.to_offset(&snapshot),
3201 )
3202 })
3203 .collect();
3204
3205 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3206 .await
3207 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3208 .log_err();
3209 });
3210 }
3211 }
3212
3213 cx.notify();
3214 }
3215
3216 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3217 use text::ToOffset as _;
3218 use text::ToPoint as _;
3219
3220 if self.mode.is_minimap()
3221 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3222 {
3223 return;
3224 }
3225
3226 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3227 return;
3228 };
3229
3230 let snapshot = singleton.read(cx).snapshot();
3231 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3232 let display_snapshot = display_map.snapshot(cx);
3233
3234 display_snapshot
3235 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3236 .map(|fold| {
3237 fold.range.start.text_anchor.to_point(&snapshot)
3238 ..fold.range.end.text_anchor.to_point(&snapshot)
3239 })
3240 .collect()
3241 });
3242 self.update_restoration_data(cx, |data| {
3243 data.folds = inmemory_folds;
3244 });
3245
3246 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3247 return;
3248 };
3249 let background_executor = cx.background_executor().clone();
3250 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3251 let db_folds = self.display_map.update(cx, |display_map, cx| {
3252 display_map
3253 .snapshot(cx)
3254 .folds_in_range(0..snapshot.len())
3255 .map(|fold| {
3256 (
3257 fold.range.start.text_anchor.to_offset(&snapshot),
3258 fold.range.end.text_anchor.to_offset(&snapshot),
3259 )
3260 })
3261 .collect()
3262 });
3263 self.serialize_folds = cx.background_spawn(async move {
3264 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3265 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3266 .await
3267 .with_context(|| {
3268 format!(
3269 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3270 )
3271 })
3272 .log_err();
3273 });
3274 }
3275
3276 pub fn sync_selections(
3277 &mut self,
3278 other: Entity<Editor>,
3279 cx: &mut Context<Self>,
3280 ) -> gpui::Subscription {
3281 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3282 self.selections.change_with(cx, |selections| {
3283 selections.select_anchors(other_selections);
3284 });
3285
3286 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3287 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3288 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3289 if other_selections.is_empty() {
3290 return;
3291 }
3292 this.selections.change_with(cx, |selections| {
3293 selections.select_anchors(other_selections);
3294 });
3295 }
3296 });
3297
3298 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3299 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3300 let these_selections = this.selections.disjoint_anchors().to_vec();
3301 if these_selections.is_empty() {
3302 return;
3303 }
3304 other.update(cx, |other_editor, cx| {
3305 other_editor.selections.change_with(cx, |selections| {
3306 selections.select_anchors(these_selections);
3307 })
3308 });
3309 }
3310 });
3311
3312 Subscription::join(other_subscription, this_subscription)
3313 }
3314
3315 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3316 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3317 /// effects of selection change occur at the end of the transaction.
3318 pub fn change_selections<R>(
3319 &mut self,
3320 effects: SelectionEffects,
3321 window: &mut Window,
3322 cx: &mut Context<Self>,
3323 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3324 ) -> R {
3325 if let Some(state) = &mut self.deferred_selection_effects_state {
3326 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3327 state.effects.completions = effects.completions;
3328 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3329 let (changed, result) = self.selections.change_with(cx, change);
3330 state.changed |= changed;
3331 return result;
3332 }
3333 let mut state = DeferredSelectionEffectsState {
3334 changed: false,
3335 effects,
3336 old_cursor_position: self.selections.newest_anchor().head(),
3337 history_entry: SelectionHistoryEntry {
3338 selections: self.selections.disjoint_anchors_arc(),
3339 select_next_state: self.select_next_state.clone(),
3340 select_prev_state: self.select_prev_state.clone(),
3341 add_selections_state: self.add_selections_state.clone(),
3342 },
3343 };
3344 let (changed, result) = self.selections.change_with(cx, change);
3345 state.changed = state.changed || changed;
3346 if self.defer_selection_effects {
3347 self.deferred_selection_effects_state = Some(state);
3348 } else {
3349 self.apply_selection_effects(state, window, cx);
3350 }
3351 result
3352 }
3353
3354 /// Defers the effects of selection change, so that the effects of multiple calls to
3355 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3356 /// to selection history and the state of popovers based on selection position aren't
3357 /// erroneously updated.
3358 pub fn with_selection_effects_deferred<R>(
3359 &mut self,
3360 window: &mut Window,
3361 cx: &mut Context<Self>,
3362 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3363 ) -> R {
3364 let already_deferred = self.defer_selection_effects;
3365 self.defer_selection_effects = true;
3366 let result = update(self, window, cx);
3367 if !already_deferred {
3368 self.defer_selection_effects = false;
3369 if let Some(state) = self.deferred_selection_effects_state.take() {
3370 self.apply_selection_effects(state, window, cx);
3371 }
3372 }
3373 result
3374 }
3375
3376 fn apply_selection_effects(
3377 &mut self,
3378 state: DeferredSelectionEffectsState,
3379 window: &mut Window,
3380 cx: &mut Context<Self>,
3381 ) {
3382 if state.changed {
3383 self.selection_history.push(state.history_entry);
3384
3385 if let Some(autoscroll) = state.effects.scroll {
3386 self.request_autoscroll(autoscroll, cx);
3387 }
3388
3389 let old_cursor_position = &state.old_cursor_position;
3390
3391 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3392
3393 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3394 self.show_signature_help(&ShowSignatureHelp, window, cx);
3395 }
3396 }
3397 }
3398
3399 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3400 where
3401 I: IntoIterator<Item = (Range<S>, T)>,
3402 S: ToOffset,
3403 T: Into<Arc<str>>,
3404 {
3405 if self.read_only(cx) {
3406 return;
3407 }
3408
3409 self.buffer
3410 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3411 }
3412
3413 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3414 where
3415 I: IntoIterator<Item = (Range<S>, T)>,
3416 S: ToOffset,
3417 T: Into<Arc<str>>,
3418 {
3419 if self.read_only(cx) {
3420 return;
3421 }
3422
3423 self.buffer.update(cx, |buffer, cx| {
3424 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3425 });
3426 }
3427
3428 pub fn edit_with_block_indent<I, S, T>(
3429 &mut self,
3430 edits: I,
3431 original_indent_columns: Vec<Option<u32>>,
3432 cx: &mut Context<Self>,
3433 ) where
3434 I: IntoIterator<Item = (Range<S>, T)>,
3435 S: ToOffset,
3436 T: Into<Arc<str>>,
3437 {
3438 if self.read_only(cx) {
3439 return;
3440 }
3441
3442 self.buffer.update(cx, |buffer, cx| {
3443 buffer.edit(
3444 edits,
3445 Some(AutoindentMode::Block {
3446 original_indent_columns,
3447 }),
3448 cx,
3449 )
3450 });
3451 }
3452
3453 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3454 self.hide_context_menu(window, cx);
3455
3456 match phase {
3457 SelectPhase::Begin {
3458 position,
3459 add,
3460 click_count,
3461 } => self.begin_selection(position, add, click_count, window, cx),
3462 SelectPhase::BeginColumnar {
3463 position,
3464 goal_column,
3465 reset,
3466 mode,
3467 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3468 SelectPhase::Extend {
3469 position,
3470 click_count,
3471 } => self.extend_selection(position, click_count, window, cx),
3472 SelectPhase::Update {
3473 position,
3474 goal_column,
3475 scroll_delta,
3476 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3477 SelectPhase::End => self.end_selection(window, cx),
3478 }
3479 }
3480
3481 fn extend_selection(
3482 &mut self,
3483 position: DisplayPoint,
3484 click_count: usize,
3485 window: &mut Window,
3486 cx: &mut Context<Self>,
3487 ) {
3488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3489 let tail = self.selections.newest::<usize>(cx).tail();
3490 self.begin_selection(position, false, click_count, window, cx);
3491
3492 let position = position.to_offset(&display_map, Bias::Left);
3493 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3494
3495 let mut pending_selection = self
3496 .selections
3497 .pending_anchor()
3498 .cloned()
3499 .expect("extend_selection not called with pending selection");
3500 if position >= tail {
3501 pending_selection.start = tail_anchor;
3502 } else {
3503 pending_selection.end = tail_anchor;
3504 pending_selection.reversed = true;
3505 }
3506
3507 let mut pending_mode = self.selections.pending_mode().unwrap();
3508 match &mut pending_mode {
3509 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3510 _ => {}
3511 }
3512
3513 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3514 SelectionEffects::scroll(Autoscroll::fit())
3515 } else {
3516 SelectionEffects::no_scroll()
3517 };
3518
3519 self.change_selections(effects, window, cx, |s| {
3520 s.set_pending(pending_selection.clone(), pending_mode)
3521 });
3522 }
3523
3524 fn begin_selection(
3525 &mut self,
3526 position: DisplayPoint,
3527 add: bool,
3528 click_count: usize,
3529 window: &mut Window,
3530 cx: &mut Context<Self>,
3531 ) {
3532 if !self.focus_handle.is_focused(window) {
3533 self.last_focused_descendant = None;
3534 window.focus(&self.focus_handle);
3535 }
3536
3537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3538 let buffer = &display_map.buffer_snapshot;
3539 let position = display_map.clip_point(position, Bias::Left);
3540
3541 let start;
3542 let end;
3543 let mode;
3544 let mut auto_scroll;
3545 match click_count {
3546 1 => {
3547 start = buffer.anchor_before(position.to_point(&display_map));
3548 end = start;
3549 mode = SelectMode::Character;
3550 auto_scroll = true;
3551 }
3552 2 => {
3553 let position = display_map
3554 .clip_point(position, Bias::Left)
3555 .to_offset(&display_map, Bias::Left);
3556 let (range, _) = buffer.surrounding_word(position, false);
3557 start = buffer.anchor_before(range.start);
3558 end = buffer.anchor_before(range.end);
3559 mode = SelectMode::Word(start..end);
3560 auto_scroll = true;
3561 }
3562 3 => {
3563 let position = display_map
3564 .clip_point(position, Bias::Left)
3565 .to_point(&display_map);
3566 let line_start = display_map.prev_line_boundary(position).0;
3567 let next_line_start = buffer.clip_point(
3568 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3569 Bias::Left,
3570 );
3571 start = buffer.anchor_before(line_start);
3572 end = buffer.anchor_before(next_line_start);
3573 mode = SelectMode::Line(start..end);
3574 auto_scroll = true;
3575 }
3576 _ => {
3577 start = buffer.anchor_before(0);
3578 end = buffer.anchor_before(buffer.len());
3579 mode = SelectMode::All;
3580 auto_scroll = false;
3581 }
3582 }
3583 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3584
3585 let point_to_delete: Option<usize> = {
3586 let selected_points: Vec<Selection<Point>> =
3587 self.selections.disjoint_in_range(start..end, cx);
3588
3589 if !add || click_count > 1 {
3590 None
3591 } else if !selected_points.is_empty() {
3592 Some(selected_points[0].id)
3593 } else {
3594 let clicked_point_already_selected =
3595 self.selections.disjoint_anchors().iter().find(|selection| {
3596 selection.start.to_point(buffer) == start.to_point(buffer)
3597 || selection.end.to_point(buffer) == end.to_point(buffer)
3598 });
3599
3600 clicked_point_already_selected.map(|selection| selection.id)
3601 }
3602 };
3603
3604 let selections_count = self.selections.count();
3605 let effects = if auto_scroll {
3606 SelectionEffects::default()
3607 } else {
3608 SelectionEffects::no_scroll()
3609 };
3610
3611 self.change_selections(effects, window, cx, |s| {
3612 if let Some(point_to_delete) = point_to_delete {
3613 s.delete(point_to_delete);
3614
3615 if selections_count == 1 {
3616 s.set_pending_anchor_range(start..end, mode);
3617 }
3618 } else {
3619 if !add {
3620 s.clear_disjoint();
3621 }
3622
3623 s.set_pending_anchor_range(start..end, mode);
3624 }
3625 });
3626 }
3627
3628 fn begin_columnar_selection(
3629 &mut self,
3630 position: DisplayPoint,
3631 goal_column: u32,
3632 reset: bool,
3633 mode: ColumnarMode,
3634 window: &mut Window,
3635 cx: &mut Context<Self>,
3636 ) {
3637 if !self.focus_handle.is_focused(window) {
3638 self.last_focused_descendant = None;
3639 window.focus(&self.focus_handle);
3640 }
3641
3642 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3643
3644 if reset {
3645 let pointer_position = display_map
3646 .buffer_snapshot
3647 .anchor_before(position.to_point(&display_map));
3648
3649 self.change_selections(
3650 SelectionEffects::scroll(Autoscroll::newest()),
3651 window,
3652 cx,
3653 |s| {
3654 s.clear_disjoint();
3655 s.set_pending_anchor_range(
3656 pointer_position..pointer_position,
3657 SelectMode::Character,
3658 );
3659 },
3660 );
3661 };
3662
3663 let tail = self.selections.newest::<Point>(cx).tail();
3664 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3665 self.columnar_selection_state = match mode {
3666 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3667 selection_tail: selection_anchor,
3668 display_point: if reset {
3669 if position.column() != goal_column {
3670 Some(DisplayPoint::new(position.row(), goal_column))
3671 } else {
3672 None
3673 }
3674 } else {
3675 None
3676 },
3677 }),
3678 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3679 selection_tail: selection_anchor,
3680 }),
3681 };
3682
3683 if !reset {
3684 self.select_columns(position, goal_column, &display_map, window, cx);
3685 }
3686 }
3687
3688 fn update_selection(
3689 &mut self,
3690 position: DisplayPoint,
3691 goal_column: u32,
3692 scroll_delta: gpui::Point<f32>,
3693 window: &mut Window,
3694 cx: &mut Context<Self>,
3695 ) {
3696 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3697
3698 if self.columnar_selection_state.is_some() {
3699 self.select_columns(position, goal_column, &display_map, window, cx);
3700 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3701 let buffer = &display_map.buffer_snapshot;
3702 let head;
3703 let tail;
3704 let mode = self.selections.pending_mode().unwrap();
3705 match &mode {
3706 SelectMode::Character => {
3707 head = position.to_point(&display_map);
3708 tail = pending.tail().to_point(buffer);
3709 }
3710 SelectMode::Word(original_range) => {
3711 let offset = display_map
3712 .clip_point(position, Bias::Left)
3713 .to_offset(&display_map, Bias::Left);
3714 let original_range = original_range.to_offset(buffer);
3715
3716 let head_offset = if buffer.is_inside_word(offset, false)
3717 || original_range.contains(&offset)
3718 {
3719 let (word_range, _) = buffer.surrounding_word(offset, false);
3720 if word_range.start < original_range.start {
3721 word_range.start
3722 } else {
3723 word_range.end
3724 }
3725 } else {
3726 offset
3727 };
3728
3729 head = head_offset.to_point(buffer);
3730 if head_offset <= original_range.start {
3731 tail = original_range.end.to_point(buffer);
3732 } else {
3733 tail = original_range.start.to_point(buffer);
3734 }
3735 }
3736 SelectMode::Line(original_range) => {
3737 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3738
3739 let position = display_map
3740 .clip_point(position, Bias::Left)
3741 .to_point(&display_map);
3742 let line_start = display_map.prev_line_boundary(position).0;
3743 let next_line_start = buffer.clip_point(
3744 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3745 Bias::Left,
3746 );
3747
3748 if line_start < original_range.start {
3749 head = line_start
3750 } else {
3751 head = next_line_start
3752 }
3753
3754 if head <= original_range.start {
3755 tail = original_range.end;
3756 } else {
3757 tail = original_range.start;
3758 }
3759 }
3760 SelectMode::All => {
3761 return;
3762 }
3763 };
3764
3765 if head < tail {
3766 pending.start = buffer.anchor_before(head);
3767 pending.end = buffer.anchor_before(tail);
3768 pending.reversed = true;
3769 } else {
3770 pending.start = buffer.anchor_before(tail);
3771 pending.end = buffer.anchor_before(head);
3772 pending.reversed = false;
3773 }
3774
3775 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3776 s.set_pending(pending.clone(), mode);
3777 });
3778 } else {
3779 log::error!("update_selection dispatched with no pending selection");
3780 return;
3781 }
3782
3783 self.apply_scroll_delta(scroll_delta, window, cx);
3784 cx.notify();
3785 }
3786
3787 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3788 self.columnar_selection_state.take();
3789 if self.selections.pending_anchor().is_some() {
3790 let selections = self.selections.all::<usize>(cx);
3791 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3792 s.select(selections);
3793 s.clear_pending();
3794 });
3795 }
3796 }
3797
3798 fn select_columns(
3799 &mut self,
3800 head: DisplayPoint,
3801 goal_column: u32,
3802 display_map: &DisplaySnapshot,
3803 window: &mut Window,
3804 cx: &mut Context<Self>,
3805 ) {
3806 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3807 return;
3808 };
3809
3810 let tail = match columnar_state {
3811 ColumnarSelectionState::FromMouse {
3812 selection_tail,
3813 display_point,
3814 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3815 ColumnarSelectionState::FromSelection { selection_tail } => {
3816 selection_tail.to_display_point(display_map)
3817 }
3818 };
3819
3820 let start_row = cmp::min(tail.row(), head.row());
3821 let end_row = cmp::max(tail.row(), head.row());
3822 let start_column = cmp::min(tail.column(), goal_column);
3823 let end_column = cmp::max(tail.column(), goal_column);
3824 let reversed = start_column < tail.column();
3825
3826 let selection_ranges = (start_row.0..=end_row.0)
3827 .map(DisplayRow)
3828 .filter_map(|row| {
3829 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3830 || start_column <= display_map.line_len(row))
3831 && !display_map.is_block_line(row)
3832 {
3833 let start = display_map
3834 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3835 .to_point(display_map);
3836 let end = display_map
3837 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3838 .to_point(display_map);
3839 if reversed {
3840 Some(end..start)
3841 } else {
3842 Some(start..end)
3843 }
3844 } else {
3845 None
3846 }
3847 })
3848 .collect::<Vec<_>>();
3849
3850 let ranges = match columnar_state {
3851 ColumnarSelectionState::FromMouse { .. } => {
3852 let mut non_empty_ranges = selection_ranges
3853 .iter()
3854 .filter(|selection_range| selection_range.start != selection_range.end)
3855 .peekable();
3856 if non_empty_ranges.peek().is_some() {
3857 non_empty_ranges.cloned().collect()
3858 } else {
3859 selection_ranges
3860 }
3861 }
3862 _ => selection_ranges,
3863 };
3864
3865 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3866 s.select_ranges(ranges);
3867 });
3868 cx.notify();
3869 }
3870
3871 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3872 self.selections
3873 .all_adjusted(cx)
3874 .iter()
3875 .any(|selection| !selection.is_empty())
3876 }
3877
3878 pub fn has_pending_nonempty_selection(&self) -> bool {
3879 let pending_nonempty_selection = match self.selections.pending_anchor() {
3880 Some(Selection { start, end, .. }) => start != end,
3881 None => false,
3882 };
3883
3884 pending_nonempty_selection
3885 || (self.columnar_selection_state.is_some()
3886 && self.selections.disjoint_anchors().len() > 1)
3887 }
3888
3889 pub fn has_pending_selection(&self) -> bool {
3890 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3891 }
3892
3893 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3894 self.selection_mark_mode = false;
3895 self.selection_drag_state = SelectionDragState::None;
3896
3897 if self.clear_expanded_diff_hunks(cx) {
3898 cx.notify();
3899 return;
3900 }
3901 if self.dismiss_menus_and_popups(true, window, cx) {
3902 return;
3903 }
3904
3905 if self.mode.is_full()
3906 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3907 {
3908 return;
3909 }
3910
3911 cx.propagate();
3912 }
3913
3914 pub fn dismiss_menus_and_popups(
3915 &mut self,
3916 is_user_requested: bool,
3917 window: &mut Window,
3918 cx: &mut Context<Self>,
3919 ) -> bool {
3920 if self.take_rename(false, window, cx).is_some() {
3921 return true;
3922 }
3923
3924 if hide_hover(self, cx) {
3925 return true;
3926 }
3927
3928 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3929 return true;
3930 }
3931
3932 if self.hide_context_menu(window, cx).is_some() {
3933 return true;
3934 }
3935
3936 if self.mouse_context_menu.take().is_some() {
3937 return true;
3938 }
3939
3940 if is_user_requested && self.discard_edit_prediction(true, cx) {
3941 return true;
3942 }
3943
3944 if self.snippet_stack.pop().is_some() {
3945 return true;
3946 }
3947
3948 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3949 self.dismiss_diagnostics(cx);
3950 return true;
3951 }
3952
3953 false
3954 }
3955
3956 fn linked_editing_ranges_for(
3957 &self,
3958 selection: Range<text::Anchor>,
3959 cx: &App,
3960 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3961 if self.linked_edit_ranges.is_empty() {
3962 return None;
3963 }
3964 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3965 selection.end.buffer_id.and_then(|end_buffer_id| {
3966 if selection.start.buffer_id != Some(end_buffer_id) {
3967 return None;
3968 }
3969 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3970 let snapshot = buffer.read(cx).snapshot();
3971 self.linked_edit_ranges
3972 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3973 .map(|ranges| (ranges, snapshot, buffer))
3974 })?;
3975 use text::ToOffset as TO;
3976 // find offset from the start of current range to current cursor position
3977 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3978
3979 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3980 let start_difference = start_offset - start_byte_offset;
3981 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3982 let end_difference = end_offset - start_byte_offset;
3983 // Current range has associated linked ranges.
3984 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3985 for range in linked_ranges.iter() {
3986 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3987 let end_offset = start_offset + end_difference;
3988 let start_offset = start_offset + start_difference;
3989 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3990 continue;
3991 }
3992 if self.selections.disjoint_anchor_ranges().any(|s| {
3993 if s.start.buffer_id != selection.start.buffer_id
3994 || s.end.buffer_id != selection.end.buffer_id
3995 {
3996 return false;
3997 }
3998 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3999 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4000 }) {
4001 continue;
4002 }
4003 let start = buffer_snapshot.anchor_after(start_offset);
4004 let end = buffer_snapshot.anchor_after(end_offset);
4005 linked_edits
4006 .entry(buffer.clone())
4007 .or_default()
4008 .push(start..end);
4009 }
4010 Some(linked_edits)
4011 }
4012
4013 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4014 let text: Arc<str> = text.into();
4015
4016 if self.read_only(cx) {
4017 return;
4018 }
4019
4020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4021
4022 let selections = self.selections.all_adjusted(cx);
4023 let mut bracket_inserted = false;
4024 let mut edits = Vec::new();
4025 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4026 let mut new_selections = Vec::with_capacity(selections.len());
4027 let mut new_autoclose_regions = Vec::new();
4028 let snapshot = self.buffer.read(cx).read(cx);
4029 let mut clear_linked_edit_ranges = false;
4030
4031 for (selection, autoclose_region) in
4032 self.selections_with_autoclose_regions(selections, &snapshot)
4033 {
4034 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4035 // Determine if the inserted text matches the opening or closing
4036 // bracket of any of this language's bracket pairs.
4037 let mut bracket_pair = None;
4038 let mut is_bracket_pair_start = false;
4039 let mut is_bracket_pair_end = false;
4040 if !text.is_empty() {
4041 let mut bracket_pair_matching_end = None;
4042 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4043 // and they are removing the character that triggered IME popup.
4044 for (pair, enabled) in scope.brackets() {
4045 if !pair.close && !pair.surround {
4046 continue;
4047 }
4048
4049 if enabled && pair.start.ends_with(text.as_ref()) {
4050 let prefix_len = pair.start.len() - text.len();
4051 let preceding_text_matches_prefix = prefix_len == 0
4052 || (selection.start.column >= (prefix_len as u32)
4053 && snapshot.contains_str_at(
4054 Point::new(
4055 selection.start.row,
4056 selection.start.column - (prefix_len as u32),
4057 ),
4058 &pair.start[..prefix_len],
4059 ));
4060 if preceding_text_matches_prefix {
4061 bracket_pair = Some(pair.clone());
4062 is_bracket_pair_start = true;
4063 break;
4064 }
4065 }
4066 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4067 {
4068 // take first bracket pair matching end, but don't break in case a later bracket
4069 // pair matches start
4070 bracket_pair_matching_end = Some(pair.clone());
4071 }
4072 }
4073 if let Some(end) = bracket_pair_matching_end
4074 && bracket_pair.is_none()
4075 {
4076 bracket_pair = Some(end);
4077 is_bracket_pair_end = true;
4078 }
4079 }
4080
4081 if let Some(bracket_pair) = bracket_pair {
4082 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4083 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4084 let auto_surround =
4085 self.use_auto_surround && snapshot_settings.use_auto_surround;
4086 if selection.is_empty() {
4087 if is_bracket_pair_start {
4088 // If the inserted text is a suffix of an opening bracket and the
4089 // selection is preceded by the rest of the opening bracket, then
4090 // insert the closing bracket.
4091 let following_text_allows_autoclose = snapshot
4092 .chars_at(selection.start)
4093 .next()
4094 .is_none_or(|c| scope.should_autoclose_before(c));
4095
4096 let preceding_text_allows_autoclose = selection.start.column == 0
4097 || snapshot
4098 .reversed_chars_at(selection.start)
4099 .next()
4100 .is_none_or(|c| {
4101 bracket_pair.start != bracket_pair.end
4102 || !snapshot
4103 .char_classifier_at(selection.start)
4104 .is_word(c)
4105 });
4106
4107 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4108 && bracket_pair.start.len() == 1
4109 {
4110 let target = bracket_pair.start.chars().next().unwrap();
4111 let current_line_count = snapshot
4112 .reversed_chars_at(selection.start)
4113 .take_while(|&c| c != '\n')
4114 .filter(|&c| c == target)
4115 .count();
4116 current_line_count % 2 == 1
4117 } else {
4118 false
4119 };
4120
4121 if autoclose
4122 && bracket_pair.close
4123 && following_text_allows_autoclose
4124 && preceding_text_allows_autoclose
4125 && !is_closing_quote
4126 {
4127 let anchor = snapshot.anchor_before(selection.end);
4128 new_selections.push((selection.map(|_| anchor), text.len()));
4129 new_autoclose_regions.push((
4130 anchor,
4131 text.len(),
4132 selection.id,
4133 bracket_pair.clone(),
4134 ));
4135 edits.push((
4136 selection.range(),
4137 format!("{}{}", text, bracket_pair.end).into(),
4138 ));
4139 bracket_inserted = true;
4140 continue;
4141 }
4142 }
4143
4144 if let Some(region) = autoclose_region {
4145 // If the selection is followed by an auto-inserted closing bracket,
4146 // then don't insert that closing bracket again; just move the selection
4147 // past the closing bracket.
4148 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4149 && text.as_ref() == region.pair.end.as_str()
4150 && snapshot.contains_str_at(region.range.end, text.as_ref());
4151 if should_skip {
4152 let anchor = snapshot.anchor_after(selection.end);
4153 new_selections
4154 .push((selection.map(|_| anchor), region.pair.end.len()));
4155 continue;
4156 }
4157 }
4158
4159 let always_treat_brackets_as_autoclosed = snapshot
4160 .language_settings_at(selection.start, cx)
4161 .always_treat_brackets_as_autoclosed;
4162 if always_treat_brackets_as_autoclosed
4163 && is_bracket_pair_end
4164 && snapshot.contains_str_at(selection.end, text.as_ref())
4165 {
4166 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4167 // and the inserted text is a closing bracket and the selection is followed
4168 // by the closing bracket then move the selection past the closing bracket.
4169 let anchor = snapshot.anchor_after(selection.end);
4170 new_selections.push((selection.map(|_| anchor), text.len()));
4171 continue;
4172 }
4173 }
4174 // If an opening bracket is 1 character long and is typed while
4175 // text is selected, then surround that text with the bracket pair.
4176 else if auto_surround
4177 && bracket_pair.surround
4178 && is_bracket_pair_start
4179 && bracket_pair.start.chars().count() == 1
4180 {
4181 edits.push((selection.start..selection.start, text.clone()));
4182 edits.push((
4183 selection.end..selection.end,
4184 bracket_pair.end.as_str().into(),
4185 ));
4186 bracket_inserted = true;
4187 new_selections.push((
4188 Selection {
4189 id: selection.id,
4190 start: snapshot.anchor_after(selection.start),
4191 end: snapshot.anchor_before(selection.end),
4192 reversed: selection.reversed,
4193 goal: selection.goal,
4194 },
4195 0,
4196 ));
4197 continue;
4198 }
4199 }
4200 }
4201
4202 if self.auto_replace_emoji_shortcode
4203 && selection.is_empty()
4204 && text.as_ref().ends_with(':')
4205 && let Some(possible_emoji_short_code) =
4206 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4207 && !possible_emoji_short_code.is_empty()
4208 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4209 {
4210 let emoji_shortcode_start = Point::new(
4211 selection.start.row,
4212 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4213 );
4214
4215 // Remove shortcode from buffer
4216 edits.push((
4217 emoji_shortcode_start..selection.start,
4218 "".to_string().into(),
4219 ));
4220 new_selections.push((
4221 Selection {
4222 id: selection.id,
4223 start: snapshot.anchor_after(emoji_shortcode_start),
4224 end: snapshot.anchor_before(selection.start),
4225 reversed: selection.reversed,
4226 goal: selection.goal,
4227 },
4228 0,
4229 ));
4230
4231 // Insert emoji
4232 let selection_start_anchor = snapshot.anchor_after(selection.start);
4233 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4234 edits.push((selection.start..selection.end, emoji.to_string().into()));
4235
4236 continue;
4237 }
4238
4239 // If not handling any auto-close operation, then just replace the selected
4240 // text with the given input and move the selection to the end of the
4241 // newly inserted text.
4242 let anchor = snapshot.anchor_after(selection.end);
4243 if !self.linked_edit_ranges.is_empty() {
4244 let start_anchor = snapshot.anchor_before(selection.start);
4245
4246 let is_word_char = text.chars().next().is_none_or(|char| {
4247 let classifier = snapshot
4248 .char_classifier_at(start_anchor.to_offset(&snapshot))
4249 .ignore_punctuation(true);
4250 classifier.is_word(char)
4251 });
4252
4253 if is_word_char {
4254 if let Some(ranges) = self
4255 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4256 {
4257 for (buffer, edits) in ranges {
4258 linked_edits
4259 .entry(buffer.clone())
4260 .or_default()
4261 .extend(edits.into_iter().map(|range| (range, text.clone())));
4262 }
4263 }
4264 } else {
4265 clear_linked_edit_ranges = true;
4266 }
4267 }
4268
4269 new_selections.push((selection.map(|_| anchor), 0));
4270 edits.push((selection.start..selection.end, text.clone()));
4271 }
4272
4273 drop(snapshot);
4274
4275 self.transact(window, cx, |this, window, cx| {
4276 if clear_linked_edit_ranges {
4277 this.linked_edit_ranges.clear();
4278 }
4279 let initial_buffer_versions =
4280 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4281
4282 this.buffer.update(cx, |buffer, cx| {
4283 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4284 });
4285 for (buffer, edits) in linked_edits {
4286 buffer.update(cx, |buffer, cx| {
4287 let snapshot = buffer.snapshot();
4288 let edits = edits
4289 .into_iter()
4290 .map(|(range, text)| {
4291 use text::ToPoint as TP;
4292 let end_point = TP::to_point(&range.end, &snapshot);
4293 let start_point = TP::to_point(&range.start, &snapshot);
4294 (start_point..end_point, text)
4295 })
4296 .sorted_by_key(|(range, _)| range.start);
4297 buffer.edit(edits, None, cx);
4298 })
4299 }
4300 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4301 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4302 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4303 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4304 .zip(new_selection_deltas)
4305 .map(|(selection, delta)| Selection {
4306 id: selection.id,
4307 start: selection.start + delta,
4308 end: selection.end + delta,
4309 reversed: selection.reversed,
4310 goal: SelectionGoal::None,
4311 })
4312 .collect::<Vec<_>>();
4313
4314 let mut i = 0;
4315 for (position, delta, selection_id, pair) in new_autoclose_regions {
4316 let position = position.to_offset(&map.buffer_snapshot) + delta;
4317 let start = map.buffer_snapshot.anchor_before(position);
4318 let end = map.buffer_snapshot.anchor_after(position);
4319 while let Some(existing_state) = this.autoclose_regions.get(i) {
4320 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4321 Ordering::Less => i += 1,
4322 Ordering::Greater => break,
4323 Ordering::Equal => {
4324 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4325 Ordering::Less => i += 1,
4326 Ordering::Equal => break,
4327 Ordering::Greater => break,
4328 }
4329 }
4330 }
4331 }
4332 this.autoclose_regions.insert(
4333 i,
4334 AutocloseRegion {
4335 selection_id,
4336 range: start..end,
4337 pair,
4338 },
4339 );
4340 }
4341
4342 let had_active_edit_prediction = this.has_active_edit_prediction();
4343 this.change_selections(
4344 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4345 window,
4346 cx,
4347 |s| s.select(new_selections),
4348 );
4349
4350 if !bracket_inserted
4351 && let Some(on_type_format_task) =
4352 this.trigger_on_type_formatting(text.to_string(), window, cx)
4353 {
4354 on_type_format_task.detach_and_log_err(cx);
4355 }
4356
4357 let editor_settings = EditorSettings::get_global(cx);
4358 if bracket_inserted
4359 && (editor_settings.auto_signature_help
4360 || editor_settings.show_signature_help_after_edits)
4361 {
4362 this.show_signature_help(&ShowSignatureHelp, window, cx);
4363 }
4364
4365 let trigger_in_words =
4366 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4367 if this.hard_wrap.is_some() {
4368 let latest: Range<Point> = this.selections.newest(cx).range();
4369 if latest.is_empty()
4370 && this
4371 .buffer()
4372 .read(cx)
4373 .snapshot(cx)
4374 .line_len(MultiBufferRow(latest.start.row))
4375 == latest.start.column
4376 {
4377 this.rewrap_impl(
4378 RewrapOptions {
4379 override_language_settings: true,
4380 preserve_existing_whitespace: true,
4381 },
4382 cx,
4383 )
4384 }
4385 }
4386 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4387 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4388 this.refresh_edit_prediction(true, false, window, cx);
4389 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4390 });
4391 }
4392
4393 fn find_possible_emoji_shortcode_at_position(
4394 snapshot: &MultiBufferSnapshot,
4395 position: Point,
4396 ) -> Option<String> {
4397 let mut chars = Vec::new();
4398 let mut found_colon = false;
4399 for char in snapshot.reversed_chars_at(position).take(100) {
4400 // Found a possible emoji shortcode in the middle of the buffer
4401 if found_colon {
4402 if char.is_whitespace() {
4403 chars.reverse();
4404 return Some(chars.iter().collect());
4405 }
4406 // If the previous character is not a whitespace, we are in the middle of a word
4407 // and we only want to complete the shortcode if the word is made up of other emojis
4408 let mut containing_word = String::new();
4409 for ch in snapshot
4410 .reversed_chars_at(position)
4411 .skip(chars.len() + 1)
4412 .take(100)
4413 {
4414 if ch.is_whitespace() {
4415 break;
4416 }
4417 containing_word.push(ch);
4418 }
4419 let containing_word = containing_word.chars().rev().collect::<String>();
4420 if util::word_consists_of_emojis(containing_word.as_str()) {
4421 chars.reverse();
4422 return Some(chars.iter().collect());
4423 }
4424 }
4425
4426 if char.is_whitespace() || !char.is_ascii() {
4427 return None;
4428 }
4429 if char == ':' {
4430 found_colon = true;
4431 } else {
4432 chars.push(char);
4433 }
4434 }
4435 // Found a possible emoji shortcode at the beginning of the buffer
4436 chars.reverse();
4437 Some(chars.iter().collect())
4438 }
4439
4440 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4441 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4442 self.transact(window, cx, |this, window, cx| {
4443 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4444 let selections = this.selections.all::<usize>(cx);
4445 let multi_buffer = this.buffer.read(cx);
4446 let buffer = multi_buffer.snapshot(cx);
4447 selections
4448 .iter()
4449 .map(|selection| {
4450 let start_point = selection.start.to_point(&buffer);
4451 let mut existing_indent =
4452 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4453 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4454 let start = selection.start;
4455 let end = selection.end;
4456 let selection_is_empty = start == end;
4457 let language_scope = buffer.language_scope_at(start);
4458 let (
4459 comment_delimiter,
4460 doc_delimiter,
4461 insert_extra_newline,
4462 indent_on_newline,
4463 indent_on_extra_newline,
4464 ) = if let Some(language) = &language_scope {
4465 let mut insert_extra_newline =
4466 insert_extra_newline_brackets(&buffer, start..end, language)
4467 || insert_extra_newline_tree_sitter(&buffer, start..end);
4468
4469 // Comment extension on newline is allowed only for cursor selections
4470 let comment_delimiter = maybe!({
4471 if !selection_is_empty {
4472 return None;
4473 }
4474
4475 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4476 return None;
4477 }
4478
4479 let delimiters = language.line_comment_prefixes();
4480 let max_len_of_delimiter =
4481 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4482 let (snapshot, range) =
4483 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4484
4485 let num_of_whitespaces = snapshot
4486 .chars_for_range(range.clone())
4487 .take_while(|c| c.is_whitespace())
4488 .count();
4489 let comment_candidate = snapshot
4490 .chars_for_range(range.clone())
4491 .skip(num_of_whitespaces)
4492 .take(max_len_of_delimiter)
4493 .collect::<String>();
4494 let (delimiter, trimmed_len) = delimiters
4495 .iter()
4496 .filter_map(|delimiter| {
4497 let prefix = delimiter.trim_end();
4498 if comment_candidate.starts_with(prefix) {
4499 Some((delimiter, prefix.len()))
4500 } else {
4501 None
4502 }
4503 })
4504 .max_by_key(|(_, len)| *len)?;
4505
4506 if let Some(BlockCommentConfig {
4507 start: block_start, ..
4508 }) = language.block_comment()
4509 {
4510 let block_start_trimmed = block_start.trim_end();
4511 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4512 let line_content = snapshot
4513 .chars_for_range(range)
4514 .skip(num_of_whitespaces)
4515 .take(block_start_trimmed.len())
4516 .collect::<String>();
4517
4518 if line_content.starts_with(block_start_trimmed) {
4519 return None;
4520 }
4521 }
4522 }
4523
4524 let cursor_is_placed_after_comment_marker =
4525 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4526 if cursor_is_placed_after_comment_marker {
4527 Some(delimiter.clone())
4528 } else {
4529 None
4530 }
4531 });
4532
4533 let mut indent_on_newline = IndentSize::spaces(0);
4534 let mut indent_on_extra_newline = IndentSize::spaces(0);
4535
4536 let doc_delimiter = maybe!({
4537 if !selection_is_empty {
4538 return None;
4539 }
4540
4541 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4542 return None;
4543 }
4544
4545 let BlockCommentConfig {
4546 start: start_tag,
4547 end: end_tag,
4548 prefix: delimiter,
4549 tab_size: len,
4550 } = language.documentation_comment()?;
4551 let is_within_block_comment = buffer
4552 .language_scope_at(start_point)
4553 .is_some_and(|scope| scope.override_name() == Some("comment"));
4554 if !is_within_block_comment {
4555 return None;
4556 }
4557
4558 let (snapshot, range) =
4559 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4560
4561 let num_of_whitespaces = snapshot
4562 .chars_for_range(range.clone())
4563 .take_while(|c| c.is_whitespace())
4564 .count();
4565
4566 // 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.
4567 let column = start_point.column;
4568 let cursor_is_after_start_tag = {
4569 let start_tag_len = start_tag.len();
4570 let start_tag_line = snapshot
4571 .chars_for_range(range.clone())
4572 .skip(num_of_whitespaces)
4573 .take(start_tag_len)
4574 .collect::<String>();
4575 if start_tag_line.starts_with(start_tag.as_ref()) {
4576 num_of_whitespaces + start_tag_len <= column as usize
4577 } else {
4578 false
4579 }
4580 };
4581
4582 let cursor_is_after_delimiter = {
4583 let delimiter_trim = delimiter.trim_end();
4584 let delimiter_line = snapshot
4585 .chars_for_range(range.clone())
4586 .skip(num_of_whitespaces)
4587 .take(delimiter_trim.len())
4588 .collect::<String>();
4589 if delimiter_line.starts_with(delimiter_trim) {
4590 num_of_whitespaces + delimiter_trim.len() <= column as usize
4591 } else {
4592 false
4593 }
4594 };
4595
4596 let cursor_is_before_end_tag_if_exists = {
4597 let mut char_position = 0u32;
4598 let mut end_tag_offset = None;
4599
4600 'outer: for chunk in snapshot.text_for_range(range) {
4601 if let Some(byte_pos) = chunk.find(&**end_tag) {
4602 let chars_before_match =
4603 chunk[..byte_pos].chars().count() as u32;
4604 end_tag_offset =
4605 Some(char_position + chars_before_match);
4606 break 'outer;
4607 }
4608 char_position += chunk.chars().count() as u32;
4609 }
4610
4611 if let Some(end_tag_offset) = end_tag_offset {
4612 let cursor_is_before_end_tag = column <= end_tag_offset;
4613 if cursor_is_after_start_tag {
4614 if cursor_is_before_end_tag {
4615 insert_extra_newline = true;
4616 }
4617 let cursor_is_at_start_of_end_tag =
4618 column == end_tag_offset;
4619 if cursor_is_at_start_of_end_tag {
4620 indent_on_extra_newline.len = *len;
4621 }
4622 }
4623 cursor_is_before_end_tag
4624 } else {
4625 true
4626 }
4627 };
4628
4629 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4630 && cursor_is_before_end_tag_if_exists
4631 {
4632 if cursor_is_after_start_tag {
4633 indent_on_newline.len = *len;
4634 }
4635 Some(delimiter.clone())
4636 } else {
4637 None
4638 }
4639 });
4640
4641 (
4642 comment_delimiter,
4643 doc_delimiter,
4644 insert_extra_newline,
4645 indent_on_newline,
4646 indent_on_extra_newline,
4647 )
4648 } else {
4649 (
4650 None,
4651 None,
4652 false,
4653 IndentSize::default(),
4654 IndentSize::default(),
4655 )
4656 };
4657
4658 let prevent_auto_indent = doc_delimiter.is_some();
4659 let delimiter = comment_delimiter.or(doc_delimiter);
4660
4661 let capacity_for_delimiter =
4662 delimiter.as_deref().map(str::len).unwrap_or_default();
4663 let mut new_text = String::with_capacity(
4664 1 + capacity_for_delimiter
4665 + existing_indent.len as usize
4666 + indent_on_newline.len as usize
4667 + indent_on_extra_newline.len as usize,
4668 );
4669 new_text.push('\n');
4670 new_text.extend(existing_indent.chars());
4671 new_text.extend(indent_on_newline.chars());
4672
4673 if let Some(delimiter) = &delimiter {
4674 new_text.push_str(delimiter);
4675 }
4676
4677 if insert_extra_newline {
4678 new_text.push('\n');
4679 new_text.extend(existing_indent.chars());
4680 new_text.extend(indent_on_extra_newline.chars());
4681 }
4682
4683 let anchor = buffer.anchor_after(end);
4684 let new_selection = selection.map(|_| anchor);
4685 (
4686 ((start..end, new_text), prevent_auto_indent),
4687 (insert_extra_newline, new_selection),
4688 )
4689 })
4690 .unzip()
4691 };
4692
4693 let mut auto_indent_edits = Vec::new();
4694 let mut edits = Vec::new();
4695 for (edit, prevent_auto_indent) in edits_with_flags {
4696 if prevent_auto_indent {
4697 edits.push(edit);
4698 } else {
4699 auto_indent_edits.push(edit);
4700 }
4701 }
4702 if !edits.is_empty() {
4703 this.edit(edits, cx);
4704 }
4705 if !auto_indent_edits.is_empty() {
4706 this.edit_with_autoindent(auto_indent_edits, cx);
4707 }
4708
4709 let buffer = this.buffer.read(cx).snapshot(cx);
4710 let new_selections = selection_info
4711 .into_iter()
4712 .map(|(extra_newline_inserted, new_selection)| {
4713 let mut cursor = new_selection.end.to_point(&buffer);
4714 if extra_newline_inserted {
4715 cursor.row -= 1;
4716 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4717 }
4718 new_selection.map(|_| cursor)
4719 })
4720 .collect();
4721
4722 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4723 this.refresh_edit_prediction(true, false, window, cx);
4724 });
4725 }
4726
4727 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4729
4730 let buffer = self.buffer.read(cx);
4731 let snapshot = buffer.snapshot(cx);
4732
4733 let mut edits = Vec::new();
4734 let mut rows = Vec::new();
4735
4736 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4737 let cursor = selection.head();
4738 let row = cursor.row;
4739
4740 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4741
4742 let newline = "\n".to_string();
4743 edits.push((start_of_line..start_of_line, newline));
4744
4745 rows.push(row + rows_inserted as u32);
4746 }
4747
4748 self.transact(window, cx, |editor, window, cx| {
4749 editor.edit(edits, cx);
4750
4751 editor.change_selections(Default::default(), window, cx, |s| {
4752 let mut index = 0;
4753 s.move_cursors_with(|map, _, _| {
4754 let row = rows[index];
4755 index += 1;
4756
4757 let point = Point::new(row, 0);
4758 let boundary = map.next_line_boundary(point).1;
4759 let clipped = map.clip_point(boundary, Bias::Left);
4760
4761 (clipped, SelectionGoal::None)
4762 });
4763 });
4764
4765 let mut indent_edits = Vec::new();
4766 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4767 for row in rows {
4768 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4769 for (row, indent) in indents {
4770 if indent.len == 0 {
4771 continue;
4772 }
4773
4774 let text = match indent.kind {
4775 IndentKind::Space => " ".repeat(indent.len as usize),
4776 IndentKind::Tab => "\t".repeat(indent.len as usize),
4777 };
4778 let point = Point::new(row.0, 0);
4779 indent_edits.push((point..point, text));
4780 }
4781 }
4782 editor.edit(indent_edits, cx);
4783 });
4784 }
4785
4786 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4787 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4788
4789 let buffer = self.buffer.read(cx);
4790 let snapshot = buffer.snapshot(cx);
4791
4792 let mut edits = Vec::new();
4793 let mut rows = Vec::new();
4794 let mut rows_inserted = 0;
4795
4796 for selection in self.selections.all_adjusted(cx) {
4797 let cursor = selection.head();
4798 let row = cursor.row;
4799
4800 let point = Point::new(row + 1, 0);
4801 let start_of_line = snapshot.clip_point(point, Bias::Left);
4802
4803 let newline = "\n".to_string();
4804 edits.push((start_of_line..start_of_line, newline));
4805
4806 rows_inserted += 1;
4807 rows.push(row + rows_inserted);
4808 }
4809
4810 self.transact(window, cx, |editor, window, cx| {
4811 editor.edit(edits, cx);
4812
4813 editor.change_selections(Default::default(), window, cx, |s| {
4814 let mut index = 0;
4815 s.move_cursors_with(|map, _, _| {
4816 let row = rows[index];
4817 index += 1;
4818
4819 let point = Point::new(row, 0);
4820 let boundary = map.next_line_boundary(point).1;
4821 let clipped = map.clip_point(boundary, Bias::Left);
4822
4823 (clipped, SelectionGoal::None)
4824 });
4825 });
4826
4827 let mut indent_edits = Vec::new();
4828 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4829 for row in rows {
4830 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4831 for (row, indent) in indents {
4832 if indent.len == 0 {
4833 continue;
4834 }
4835
4836 let text = match indent.kind {
4837 IndentKind::Space => " ".repeat(indent.len as usize),
4838 IndentKind::Tab => "\t".repeat(indent.len as usize),
4839 };
4840 let point = Point::new(row.0, 0);
4841 indent_edits.push((point..point, text));
4842 }
4843 }
4844 editor.edit(indent_edits, cx);
4845 });
4846 }
4847
4848 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4849 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4850 original_indent_columns: Vec::new(),
4851 });
4852 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4853 }
4854
4855 fn insert_with_autoindent_mode(
4856 &mut self,
4857 text: &str,
4858 autoindent_mode: Option<AutoindentMode>,
4859 window: &mut Window,
4860 cx: &mut Context<Self>,
4861 ) {
4862 if self.read_only(cx) {
4863 return;
4864 }
4865
4866 let text: Arc<str> = text.into();
4867 self.transact(window, cx, |this, window, cx| {
4868 let old_selections = this.selections.all_adjusted(cx);
4869 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4870 let anchors = {
4871 let snapshot = buffer.read(cx);
4872 old_selections
4873 .iter()
4874 .map(|s| {
4875 let anchor = snapshot.anchor_after(s.head());
4876 s.map(|_| anchor)
4877 })
4878 .collect::<Vec<_>>()
4879 };
4880 buffer.edit(
4881 old_selections
4882 .iter()
4883 .map(|s| (s.start..s.end, text.clone())),
4884 autoindent_mode,
4885 cx,
4886 );
4887 anchors
4888 });
4889
4890 this.change_selections(Default::default(), window, cx, |s| {
4891 s.select_anchors(selection_anchors);
4892 });
4893
4894 cx.notify();
4895 });
4896 }
4897
4898 fn trigger_completion_on_input(
4899 &mut self,
4900 text: &str,
4901 trigger_in_words: bool,
4902 window: &mut Window,
4903 cx: &mut Context<Self>,
4904 ) {
4905 let completions_source = self
4906 .context_menu
4907 .borrow()
4908 .as_ref()
4909 .and_then(|menu| match menu {
4910 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4911 CodeContextMenu::CodeActions(_) => None,
4912 });
4913
4914 match completions_source {
4915 Some(CompletionsMenuSource::Words { .. }) => {
4916 self.open_or_update_completions_menu(
4917 Some(CompletionsMenuSource::Words {
4918 ignore_threshold: false,
4919 }),
4920 None,
4921 window,
4922 cx,
4923 );
4924 }
4925 Some(CompletionsMenuSource::Normal)
4926 | Some(CompletionsMenuSource::SnippetChoices)
4927 | None
4928 if self.is_completion_trigger(
4929 text,
4930 trigger_in_words,
4931 completions_source.is_some(),
4932 cx,
4933 ) =>
4934 {
4935 self.show_completions(
4936 &ShowCompletions {
4937 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4938 },
4939 window,
4940 cx,
4941 )
4942 }
4943 _ => {
4944 self.hide_context_menu(window, cx);
4945 }
4946 }
4947 }
4948
4949 fn is_completion_trigger(
4950 &self,
4951 text: &str,
4952 trigger_in_words: bool,
4953 menu_is_open: bool,
4954 cx: &mut Context<Self>,
4955 ) -> bool {
4956 let position = self.selections.newest_anchor().head();
4957 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4958 return false;
4959 };
4960
4961 if let Some(completion_provider) = &self.completion_provider {
4962 completion_provider.is_completion_trigger(
4963 &buffer,
4964 position.text_anchor,
4965 text,
4966 trigger_in_words,
4967 menu_is_open,
4968 cx,
4969 )
4970 } else {
4971 false
4972 }
4973 }
4974
4975 /// If any empty selections is touching the start of its innermost containing autoclose
4976 /// region, expand it to select the brackets.
4977 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4978 let selections = self.selections.all::<usize>(cx);
4979 let buffer = self.buffer.read(cx).read(cx);
4980 let new_selections = self
4981 .selections_with_autoclose_regions(selections, &buffer)
4982 .map(|(mut selection, region)| {
4983 if !selection.is_empty() {
4984 return selection;
4985 }
4986
4987 if let Some(region) = region {
4988 let mut range = region.range.to_offset(&buffer);
4989 if selection.start == range.start && range.start >= region.pair.start.len() {
4990 range.start -= region.pair.start.len();
4991 if buffer.contains_str_at(range.start, ®ion.pair.start)
4992 && buffer.contains_str_at(range.end, ®ion.pair.end)
4993 {
4994 range.end += region.pair.end.len();
4995 selection.start = range.start;
4996 selection.end = range.end;
4997
4998 return selection;
4999 }
5000 }
5001 }
5002
5003 let always_treat_brackets_as_autoclosed = buffer
5004 .language_settings_at(selection.start, cx)
5005 .always_treat_brackets_as_autoclosed;
5006
5007 if !always_treat_brackets_as_autoclosed {
5008 return selection;
5009 }
5010
5011 if let Some(scope) = buffer.language_scope_at(selection.start) {
5012 for (pair, enabled) in scope.brackets() {
5013 if !enabled || !pair.close {
5014 continue;
5015 }
5016
5017 if buffer.contains_str_at(selection.start, &pair.end) {
5018 let pair_start_len = pair.start.len();
5019 if buffer.contains_str_at(
5020 selection.start.saturating_sub(pair_start_len),
5021 &pair.start,
5022 ) {
5023 selection.start -= pair_start_len;
5024 selection.end += pair.end.len();
5025
5026 return selection;
5027 }
5028 }
5029 }
5030 }
5031
5032 selection
5033 })
5034 .collect();
5035
5036 drop(buffer);
5037 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5038 selections.select(new_selections)
5039 });
5040 }
5041
5042 /// Iterate the given selections, and for each one, find the smallest surrounding
5043 /// autoclose region. This uses the ordering of the selections and the autoclose
5044 /// regions to avoid repeated comparisons.
5045 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5046 &'a self,
5047 selections: impl IntoIterator<Item = Selection<D>>,
5048 buffer: &'a MultiBufferSnapshot,
5049 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5050 let mut i = 0;
5051 let mut regions = self.autoclose_regions.as_slice();
5052 selections.into_iter().map(move |selection| {
5053 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5054
5055 let mut enclosing = None;
5056 while let Some(pair_state) = regions.get(i) {
5057 if pair_state.range.end.to_offset(buffer) < range.start {
5058 regions = ®ions[i + 1..];
5059 i = 0;
5060 } else if pair_state.range.start.to_offset(buffer) > range.end {
5061 break;
5062 } else {
5063 if pair_state.selection_id == selection.id {
5064 enclosing = Some(pair_state);
5065 }
5066 i += 1;
5067 }
5068 }
5069
5070 (selection, enclosing)
5071 })
5072 }
5073
5074 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5075 fn invalidate_autoclose_regions(
5076 &mut self,
5077 mut selections: &[Selection<Anchor>],
5078 buffer: &MultiBufferSnapshot,
5079 ) {
5080 self.autoclose_regions.retain(|state| {
5081 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5082 return false;
5083 }
5084
5085 let mut i = 0;
5086 while let Some(selection) = selections.get(i) {
5087 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5088 selections = &selections[1..];
5089 continue;
5090 }
5091 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5092 break;
5093 }
5094 if selection.id == state.selection_id {
5095 return true;
5096 } else {
5097 i += 1;
5098 }
5099 }
5100 false
5101 });
5102 }
5103
5104 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5105 let offset = position.to_offset(buffer);
5106 let (word_range, kind) = buffer.surrounding_word(offset, true);
5107 if offset > word_range.start && kind == Some(CharKind::Word) {
5108 Some(
5109 buffer
5110 .text_for_range(word_range.start..offset)
5111 .collect::<String>(),
5112 )
5113 } else {
5114 None
5115 }
5116 }
5117
5118 pub fn toggle_inline_values(
5119 &mut self,
5120 _: &ToggleInlineValues,
5121 _: &mut Window,
5122 cx: &mut Context<Self>,
5123 ) {
5124 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5125
5126 self.refresh_inline_values(cx);
5127 }
5128
5129 pub fn toggle_inlay_hints(
5130 &mut self,
5131 _: &ToggleInlayHints,
5132 _: &mut Window,
5133 cx: &mut Context<Self>,
5134 ) {
5135 self.refresh_inlay_hints(
5136 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5137 cx,
5138 );
5139 }
5140
5141 pub fn inlay_hints_enabled(&self) -> bool {
5142 self.inlay_hint_cache.enabled
5143 }
5144
5145 pub fn inline_values_enabled(&self) -> bool {
5146 self.inline_value_cache.enabled
5147 }
5148
5149 #[cfg(any(test, feature = "test-support"))]
5150 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5151 self.display_map
5152 .read(cx)
5153 .current_inlays()
5154 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5155 .cloned()
5156 .collect()
5157 }
5158
5159 #[cfg(any(test, feature = "test-support"))]
5160 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5161 self.display_map
5162 .read(cx)
5163 .current_inlays()
5164 .cloned()
5165 .collect()
5166 }
5167
5168 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5169 if self.semantics_provider.is_none() || !self.mode.is_full() {
5170 return;
5171 }
5172
5173 let reason_description = reason.description();
5174 let ignore_debounce = matches!(
5175 reason,
5176 InlayHintRefreshReason::SettingsChange(_)
5177 | InlayHintRefreshReason::Toggle(_)
5178 | InlayHintRefreshReason::ExcerptsRemoved(_)
5179 | InlayHintRefreshReason::ModifiersChanged(_)
5180 );
5181 let (invalidate_cache, required_languages) = match reason {
5182 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5183 match self.inlay_hint_cache.modifiers_override(enabled) {
5184 Some(enabled) => {
5185 if enabled {
5186 (InvalidationStrategy::RefreshRequested, None)
5187 } else {
5188 self.splice_inlays(
5189 &self
5190 .visible_inlay_hints(cx)
5191 .iter()
5192 .map(|inlay| inlay.id)
5193 .collect::<Vec<InlayId>>(),
5194 Vec::new(),
5195 cx,
5196 );
5197 return;
5198 }
5199 }
5200 None => return,
5201 }
5202 }
5203 InlayHintRefreshReason::Toggle(enabled) => {
5204 if self.inlay_hint_cache.toggle(enabled) {
5205 if enabled {
5206 (InvalidationStrategy::RefreshRequested, None)
5207 } else {
5208 self.splice_inlays(
5209 &self
5210 .visible_inlay_hints(cx)
5211 .iter()
5212 .map(|inlay| inlay.id)
5213 .collect::<Vec<InlayId>>(),
5214 Vec::new(),
5215 cx,
5216 );
5217 return;
5218 }
5219 } else {
5220 return;
5221 }
5222 }
5223 InlayHintRefreshReason::SettingsChange(new_settings) => {
5224 match self.inlay_hint_cache.update_settings(
5225 &self.buffer,
5226 new_settings,
5227 self.visible_inlay_hints(cx),
5228 cx,
5229 ) {
5230 ControlFlow::Break(Some(InlaySplice {
5231 to_remove,
5232 to_insert,
5233 })) => {
5234 self.splice_inlays(&to_remove, to_insert, cx);
5235 return;
5236 }
5237 ControlFlow::Break(None) => return,
5238 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5239 }
5240 }
5241 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5242 if let Some(InlaySplice {
5243 to_remove,
5244 to_insert,
5245 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5246 {
5247 self.splice_inlays(&to_remove, to_insert, cx);
5248 }
5249 self.display_map.update(cx, |display_map, _| {
5250 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5251 });
5252 return;
5253 }
5254 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5255 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5256 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5257 }
5258 InlayHintRefreshReason::RefreshRequested => {
5259 (InvalidationStrategy::RefreshRequested, None)
5260 }
5261 };
5262
5263 if let Some(InlaySplice {
5264 to_remove,
5265 to_insert,
5266 }) = self.inlay_hint_cache.spawn_hint_refresh(
5267 reason_description,
5268 self.visible_excerpts(required_languages.as_ref(), cx),
5269 invalidate_cache,
5270 ignore_debounce,
5271 cx,
5272 ) {
5273 self.splice_inlays(&to_remove, to_insert, cx);
5274 }
5275 }
5276
5277 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5278 self.display_map
5279 .read(cx)
5280 .current_inlays()
5281 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5282 .cloned()
5283 .collect()
5284 }
5285
5286 pub fn visible_excerpts(
5287 &self,
5288 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5289 cx: &mut Context<Editor>,
5290 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5291 let Some(project) = self.project() else {
5292 return HashMap::default();
5293 };
5294 let project = project.read(cx);
5295 let multi_buffer = self.buffer().read(cx);
5296 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5297 let multi_buffer_visible_start = self
5298 .scroll_manager
5299 .anchor()
5300 .anchor
5301 .to_point(&multi_buffer_snapshot);
5302 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5303 multi_buffer_visible_start
5304 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5305 Bias::Left,
5306 );
5307 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5308 multi_buffer_snapshot
5309 .range_to_buffer_ranges(multi_buffer_visible_range)
5310 .into_iter()
5311 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5312 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5313 let buffer_file = project::File::from_dyn(buffer.file())?;
5314 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5315 let worktree_entry = buffer_worktree
5316 .read(cx)
5317 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5318 if worktree_entry.is_ignored {
5319 return None;
5320 }
5321
5322 let language = buffer.language()?;
5323 if let Some(restrict_to_languages) = restrict_to_languages
5324 && !restrict_to_languages.contains(language)
5325 {
5326 return None;
5327 }
5328 Some((
5329 excerpt_id,
5330 (
5331 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5332 buffer.version().clone(),
5333 excerpt_visible_range,
5334 ),
5335 ))
5336 })
5337 .collect()
5338 }
5339
5340 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5341 TextLayoutDetails {
5342 text_system: window.text_system().clone(),
5343 editor_style: self.style.clone().unwrap(),
5344 rem_size: window.rem_size(),
5345 scroll_anchor: self.scroll_manager.anchor(),
5346 visible_rows: self.visible_line_count(),
5347 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5348 }
5349 }
5350
5351 pub fn splice_inlays(
5352 &self,
5353 to_remove: &[InlayId],
5354 to_insert: Vec<Inlay>,
5355 cx: &mut Context<Self>,
5356 ) {
5357 self.display_map.update(cx, |display_map, cx| {
5358 display_map.splice_inlays(to_remove, to_insert, cx)
5359 });
5360 cx.notify();
5361 }
5362
5363 fn trigger_on_type_formatting(
5364 &self,
5365 input: String,
5366 window: &mut Window,
5367 cx: &mut Context<Self>,
5368 ) -> Option<Task<Result<()>>> {
5369 if input.len() != 1 {
5370 return None;
5371 }
5372
5373 let project = self.project()?;
5374 let position = self.selections.newest_anchor().head();
5375 let (buffer, buffer_position) = self
5376 .buffer
5377 .read(cx)
5378 .text_anchor_for_position(position, cx)?;
5379
5380 let settings = language_settings::language_settings(
5381 buffer
5382 .read(cx)
5383 .language_at(buffer_position)
5384 .map(|l| l.name()),
5385 buffer.read(cx).file(),
5386 cx,
5387 );
5388 if !settings.use_on_type_format {
5389 return None;
5390 }
5391
5392 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5393 // hence we do LSP request & edit on host side only — add formats to host's history.
5394 let push_to_lsp_host_history = true;
5395 // If this is not the host, append its history with new edits.
5396 let push_to_client_history = project.read(cx).is_via_collab();
5397
5398 let on_type_formatting = project.update(cx, |project, cx| {
5399 project.on_type_format(
5400 buffer.clone(),
5401 buffer_position,
5402 input,
5403 push_to_lsp_host_history,
5404 cx,
5405 )
5406 });
5407 Some(cx.spawn_in(window, async move |editor, cx| {
5408 if let Some(transaction) = on_type_formatting.await? {
5409 if push_to_client_history {
5410 buffer
5411 .update(cx, |buffer, _| {
5412 buffer.push_transaction(transaction, Instant::now());
5413 buffer.finalize_last_transaction();
5414 })
5415 .ok();
5416 }
5417 editor.update(cx, |editor, cx| {
5418 editor.refresh_document_highlights(cx);
5419 })?;
5420 }
5421 Ok(())
5422 }))
5423 }
5424
5425 pub fn show_word_completions(
5426 &mut self,
5427 _: &ShowWordCompletions,
5428 window: &mut Window,
5429 cx: &mut Context<Self>,
5430 ) {
5431 self.open_or_update_completions_menu(
5432 Some(CompletionsMenuSource::Words {
5433 ignore_threshold: true,
5434 }),
5435 None,
5436 window,
5437 cx,
5438 );
5439 }
5440
5441 pub fn show_completions(
5442 &mut self,
5443 options: &ShowCompletions,
5444 window: &mut Window,
5445 cx: &mut Context<Self>,
5446 ) {
5447 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5448 }
5449
5450 fn open_or_update_completions_menu(
5451 &mut self,
5452 requested_source: Option<CompletionsMenuSource>,
5453 trigger: Option<&str>,
5454 window: &mut Window,
5455 cx: &mut Context<Self>,
5456 ) {
5457 if self.pending_rename.is_some() {
5458 return;
5459 }
5460
5461 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5462
5463 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5464 // inserted and selected. To handle that case, the start of the selection is used so that
5465 // the menu starts with all choices.
5466 let position = self
5467 .selections
5468 .newest_anchor()
5469 .start
5470 .bias_right(&multibuffer_snapshot);
5471 if position.diff_base_anchor.is_some() {
5472 return;
5473 }
5474 let buffer_position = multibuffer_snapshot.anchor_before(position);
5475 let Some(buffer) = buffer_position
5476 .buffer_id
5477 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5478 else {
5479 return;
5480 };
5481 let buffer_snapshot = buffer.read(cx).snapshot();
5482
5483 let query: Option<Arc<String>> =
5484 Self::completion_query(&multibuffer_snapshot, buffer_position)
5485 .map(|query| query.into());
5486
5487 drop(multibuffer_snapshot);
5488
5489 // Hide the current completions menu when query is empty. Without this, cached
5490 // completions from before the trigger char may be reused (#32774).
5491 if query.is_none() {
5492 let menu_is_open = matches!(
5493 self.context_menu.borrow().as_ref(),
5494 Some(CodeContextMenu::Completions(_))
5495 );
5496 if menu_is_open {
5497 self.hide_context_menu(window, cx);
5498 }
5499 }
5500
5501 let mut ignore_word_threshold = false;
5502 let provider = match requested_source {
5503 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5504 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5505 ignore_word_threshold = ignore_threshold;
5506 None
5507 }
5508 Some(CompletionsMenuSource::SnippetChoices) => {
5509 log::error!("bug: SnippetChoices requested_source is not handled");
5510 None
5511 }
5512 };
5513
5514 let sort_completions = provider
5515 .as_ref()
5516 .is_some_and(|provider| provider.sort_completions());
5517
5518 let filter_completions = provider
5519 .as_ref()
5520 .is_none_or(|provider| provider.filter_completions());
5521
5522 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5523 if filter_completions {
5524 menu.filter(query.clone(), provider.clone(), window, cx);
5525 }
5526 // When `is_incomplete` is false, no need to re-query completions when the current query
5527 // is a suffix of the initial query.
5528 if !menu.is_incomplete {
5529 // If the new query is a suffix of the old query (typing more characters) and
5530 // the previous result was complete, the existing completions can be filtered.
5531 //
5532 // Note that this is always true for snippet completions.
5533 let query_matches = match (&menu.initial_query, &query) {
5534 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5535 (None, _) => true,
5536 _ => false,
5537 };
5538 if query_matches {
5539 let position_matches = if menu.initial_position == position {
5540 true
5541 } else {
5542 let snapshot = self.buffer.read(cx).read(cx);
5543 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5544 };
5545 if position_matches {
5546 return;
5547 }
5548 }
5549 }
5550 };
5551
5552 let trigger_kind = match trigger {
5553 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5554 CompletionTriggerKind::TRIGGER_CHARACTER
5555 }
5556 _ => CompletionTriggerKind::INVOKED,
5557 };
5558 let completion_context = CompletionContext {
5559 trigger_character: trigger.and_then(|trigger| {
5560 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5561 Some(String::from(trigger))
5562 } else {
5563 None
5564 }
5565 }),
5566 trigger_kind,
5567 };
5568
5569 let Anchor {
5570 excerpt_id: buffer_excerpt_id,
5571 text_anchor: buffer_position,
5572 ..
5573 } = buffer_position;
5574
5575 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5576 buffer_snapshot.surrounding_word(buffer_position, false)
5577 {
5578 let word_to_exclude = buffer_snapshot
5579 .text_for_range(word_range.clone())
5580 .collect::<String>();
5581 (
5582 buffer_snapshot.anchor_before(word_range.start)
5583 ..buffer_snapshot.anchor_after(buffer_position),
5584 Some(word_to_exclude),
5585 )
5586 } else {
5587 (buffer_position..buffer_position, None)
5588 };
5589
5590 let language = buffer_snapshot
5591 .language_at(buffer_position)
5592 .map(|language| language.name());
5593
5594 let completion_settings =
5595 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5596
5597 let show_completion_documentation = buffer_snapshot
5598 .settings_at(buffer_position, cx)
5599 .show_completion_documentation;
5600
5601 // The document can be large, so stay in reasonable bounds when searching for words,
5602 // otherwise completion pop-up might be slow to appear.
5603 const WORD_LOOKUP_ROWS: u32 = 5_000;
5604 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5605 let min_word_search = buffer_snapshot.clip_point(
5606 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5607 Bias::Left,
5608 );
5609 let max_word_search = buffer_snapshot.clip_point(
5610 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5611 Bias::Right,
5612 );
5613 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5614 ..buffer_snapshot.point_to_offset(max_word_search);
5615
5616 let skip_digits = query
5617 .as_ref()
5618 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5619
5620 let omit_word_completions = !self.word_completions_enabled
5621 || (!ignore_word_threshold
5622 && match &query {
5623 Some(query) => query.chars().count() < completion_settings.words_min_length,
5624 None => completion_settings.words_min_length != 0,
5625 });
5626
5627 let (mut words, provider_responses) = match &provider {
5628 Some(provider) => {
5629 let provider_responses = provider.completions(
5630 buffer_excerpt_id,
5631 &buffer,
5632 buffer_position,
5633 completion_context,
5634 window,
5635 cx,
5636 );
5637
5638 let words = match (omit_word_completions, completion_settings.words) {
5639 (true, _) | (_, WordsCompletionMode::Disabled) => {
5640 Task::ready(BTreeMap::default())
5641 }
5642 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5643 .background_spawn(async move {
5644 buffer_snapshot.words_in_range(WordsQuery {
5645 fuzzy_contents: None,
5646 range: word_search_range,
5647 skip_digits,
5648 })
5649 }),
5650 };
5651
5652 (words, provider_responses)
5653 }
5654 None => {
5655 let words = if omit_word_completions {
5656 Task::ready(BTreeMap::default())
5657 } else {
5658 cx.background_spawn(async move {
5659 buffer_snapshot.words_in_range(WordsQuery {
5660 fuzzy_contents: None,
5661 range: word_search_range,
5662 skip_digits,
5663 })
5664 })
5665 };
5666 (words, Task::ready(Ok(Vec::new())))
5667 }
5668 };
5669
5670 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5671
5672 let id = post_inc(&mut self.next_completion_id);
5673 let task = cx.spawn_in(window, async move |editor, cx| {
5674 let Ok(()) = editor.update(cx, |this, _| {
5675 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5676 }) else {
5677 return;
5678 };
5679
5680 // TODO: Ideally completions from different sources would be selectively re-queried, so
5681 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5682 let mut completions = Vec::new();
5683 let mut is_incomplete = false;
5684 let mut display_options: Option<CompletionDisplayOptions> = None;
5685 if let Some(provider_responses) = provider_responses.await.log_err()
5686 && !provider_responses.is_empty()
5687 {
5688 for response in provider_responses {
5689 completions.extend(response.completions);
5690 is_incomplete = is_incomplete || response.is_incomplete;
5691 match display_options.as_mut() {
5692 None => {
5693 display_options = Some(response.display_options);
5694 }
5695 Some(options) => options.merge(&response.display_options),
5696 }
5697 }
5698 if completion_settings.words == WordsCompletionMode::Fallback {
5699 words = Task::ready(BTreeMap::default());
5700 }
5701 }
5702 let display_options = display_options.unwrap_or_default();
5703
5704 let mut words = words.await;
5705 if let Some(word_to_exclude) = &word_to_exclude {
5706 words.remove(word_to_exclude);
5707 }
5708 for lsp_completion in &completions {
5709 words.remove(&lsp_completion.new_text);
5710 }
5711 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5712 replace_range: word_replace_range.clone(),
5713 new_text: word.clone(),
5714 label: CodeLabel::plain(word, None),
5715 icon_path: None,
5716 documentation: None,
5717 source: CompletionSource::BufferWord {
5718 word_range,
5719 resolved: false,
5720 },
5721 insert_text_mode: Some(InsertTextMode::AS_IS),
5722 confirm: None,
5723 }));
5724
5725 let menu = if completions.is_empty() {
5726 None
5727 } else {
5728 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5729 let languages = editor
5730 .workspace
5731 .as_ref()
5732 .and_then(|(workspace, _)| workspace.upgrade())
5733 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5734 let menu = CompletionsMenu::new(
5735 id,
5736 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5737 sort_completions,
5738 show_completion_documentation,
5739 position,
5740 query.clone(),
5741 is_incomplete,
5742 buffer.clone(),
5743 completions.into(),
5744 display_options,
5745 snippet_sort_order,
5746 languages,
5747 language,
5748 cx,
5749 );
5750
5751 let query = if filter_completions { query } else { None };
5752 let matches_task = if let Some(query) = query {
5753 menu.do_async_filtering(query, cx)
5754 } else {
5755 Task::ready(menu.unfiltered_matches())
5756 };
5757 (menu, matches_task)
5758 }) else {
5759 return;
5760 };
5761
5762 let matches = matches_task.await;
5763
5764 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5765 // Newer menu already set, so exit.
5766 if let Some(CodeContextMenu::Completions(prev_menu)) =
5767 editor.context_menu.borrow().as_ref()
5768 && prev_menu.id > id
5769 {
5770 return;
5771 };
5772
5773 // Only valid to take prev_menu because it the new menu is immediately set
5774 // below, or the menu is hidden.
5775 if let Some(CodeContextMenu::Completions(prev_menu)) =
5776 editor.context_menu.borrow_mut().take()
5777 {
5778 let position_matches =
5779 if prev_menu.initial_position == menu.initial_position {
5780 true
5781 } else {
5782 let snapshot = editor.buffer.read(cx).read(cx);
5783 prev_menu.initial_position.to_offset(&snapshot)
5784 == menu.initial_position.to_offset(&snapshot)
5785 };
5786 if position_matches {
5787 // Preserve markdown cache before `set_filter_results` because it will
5788 // try to populate the documentation cache.
5789 menu.preserve_markdown_cache(prev_menu);
5790 }
5791 };
5792
5793 menu.set_filter_results(matches, provider, window, cx);
5794 }) else {
5795 return;
5796 };
5797
5798 menu.visible().then_some(menu)
5799 };
5800
5801 editor
5802 .update_in(cx, |editor, window, cx| {
5803 if editor.focus_handle.is_focused(window)
5804 && let Some(menu) = menu
5805 {
5806 *editor.context_menu.borrow_mut() =
5807 Some(CodeContextMenu::Completions(menu));
5808
5809 crate::hover_popover::hide_hover(editor, cx);
5810 if editor.show_edit_predictions_in_menu() {
5811 editor.update_visible_edit_prediction(window, cx);
5812 } else {
5813 editor.discard_edit_prediction(false, cx);
5814 }
5815
5816 cx.notify();
5817 return;
5818 }
5819
5820 if editor.completion_tasks.len() <= 1 {
5821 // If there are no more completion tasks and the last menu was empty, we should hide it.
5822 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5823 // If it was already hidden and we don't show edit predictions in the menu,
5824 // we should also show the edit prediction when available.
5825 if was_hidden && editor.show_edit_predictions_in_menu() {
5826 editor.update_visible_edit_prediction(window, cx);
5827 }
5828 }
5829 })
5830 .ok();
5831 });
5832
5833 self.completion_tasks.push((id, task));
5834 }
5835
5836 #[cfg(feature = "test-support")]
5837 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5838 let menu = self.context_menu.borrow();
5839 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5840 let completions = menu.completions.borrow();
5841 Some(completions.to_vec())
5842 } else {
5843 None
5844 }
5845 }
5846
5847 pub fn with_completions_menu_matching_id<R>(
5848 &self,
5849 id: CompletionId,
5850 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5851 ) -> R {
5852 let mut context_menu = self.context_menu.borrow_mut();
5853 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5854 return f(None);
5855 };
5856 if completions_menu.id != id {
5857 return f(None);
5858 }
5859 f(Some(completions_menu))
5860 }
5861
5862 pub fn confirm_completion(
5863 &mut self,
5864 action: &ConfirmCompletion,
5865 window: &mut Window,
5866 cx: &mut Context<Self>,
5867 ) -> Option<Task<Result<()>>> {
5868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5869 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5870 }
5871
5872 pub fn confirm_completion_insert(
5873 &mut self,
5874 _: &ConfirmCompletionInsert,
5875 window: &mut Window,
5876 cx: &mut Context<Self>,
5877 ) -> Option<Task<Result<()>>> {
5878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5879 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5880 }
5881
5882 pub fn confirm_completion_replace(
5883 &mut self,
5884 _: &ConfirmCompletionReplace,
5885 window: &mut Window,
5886 cx: &mut Context<Self>,
5887 ) -> Option<Task<Result<()>>> {
5888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5889 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5890 }
5891
5892 pub fn compose_completion(
5893 &mut self,
5894 action: &ComposeCompletion,
5895 window: &mut Window,
5896 cx: &mut Context<Self>,
5897 ) -> Option<Task<Result<()>>> {
5898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5899 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5900 }
5901
5902 fn do_completion(
5903 &mut self,
5904 item_ix: Option<usize>,
5905 intent: CompletionIntent,
5906 window: &mut Window,
5907 cx: &mut Context<Editor>,
5908 ) -> Option<Task<Result<()>>> {
5909 use language::ToOffset as _;
5910
5911 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5912 else {
5913 return None;
5914 };
5915
5916 let candidate_id = {
5917 let entries = completions_menu.entries.borrow();
5918 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5919 if self.show_edit_predictions_in_menu() {
5920 self.discard_edit_prediction(true, cx);
5921 }
5922 mat.candidate_id
5923 };
5924
5925 let completion = completions_menu
5926 .completions
5927 .borrow()
5928 .get(candidate_id)?
5929 .clone();
5930 cx.stop_propagation();
5931
5932 let buffer_handle = completions_menu.buffer.clone();
5933
5934 let CompletionEdit {
5935 new_text,
5936 snippet,
5937 replace_range,
5938 } = process_completion_for_edit(
5939 &completion,
5940 intent,
5941 &buffer_handle,
5942 &completions_menu.initial_position.text_anchor,
5943 cx,
5944 );
5945
5946 let buffer = buffer_handle.read(cx);
5947 let snapshot = self.buffer.read(cx).snapshot(cx);
5948 let newest_anchor = self.selections.newest_anchor();
5949 let replace_range_multibuffer = {
5950 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5951 let multibuffer_anchor = snapshot
5952 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5953 .unwrap()
5954 ..snapshot
5955 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5956 .unwrap();
5957 multibuffer_anchor.start.to_offset(&snapshot)
5958 ..multibuffer_anchor.end.to_offset(&snapshot)
5959 };
5960 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5961 return None;
5962 }
5963
5964 let old_text = buffer
5965 .text_for_range(replace_range.clone())
5966 .collect::<String>();
5967 let lookbehind = newest_anchor
5968 .start
5969 .text_anchor
5970 .to_offset(buffer)
5971 .saturating_sub(replace_range.start);
5972 let lookahead = replace_range
5973 .end
5974 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5975 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5976 let suffix = &old_text[lookbehind.min(old_text.len())..];
5977
5978 let selections = self.selections.all::<usize>(cx);
5979 let mut ranges = Vec::new();
5980 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5981
5982 for selection in &selections {
5983 let range = if selection.id == newest_anchor.id {
5984 replace_range_multibuffer.clone()
5985 } else {
5986 let mut range = selection.range();
5987
5988 // if prefix is present, don't duplicate it
5989 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5990 range.start = range.start.saturating_sub(lookbehind);
5991
5992 // if suffix is also present, mimic the newest cursor and replace it
5993 if selection.id != newest_anchor.id
5994 && snapshot.contains_str_at(range.end, suffix)
5995 {
5996 range.end += lookahead;
5997 }
5998 }
5999 range
6000 };
6001
6002 ranges.push(range.clone());
6003
6004 if !self.linked_edit_ranges.is_empty() {
6005 let start_anchor = snapshot.anchor_before(range.start);
6006 let end_anchor = snapshot.anchor_after(range.end);
6007 if let Some(ranges) = self
6008 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6009 {
6010 for (buffer, edits) in ranges {
6011 linked_edits
6012 .entry(buffer.clone())
6013 .or_default()
6014 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6015 }
6016 }
6017 }
6018 }
6019
6020 let common_prefix_len = old_text
6021 .chars()
6022 .zip(new_text.chars())
6023 .take_while(|(a, b)| a == b)
6024 .map(|(a, _)| a.len_utf8())
6025 .sum::<usize>();
6026
6027 cx.emit(EditorEvent::InputHandled {
6028 utf16_range_to_replace: None,
6029 text: new_text[common_prefix_len..].into(),
6030 });
6031
6032 self.transact(window, cx, |editor, window, cx| {
6033 if let Some(mut snippet) = snippet {
6034 snippet.text = new_text.to_string();
6035 editor
6036 .insert_snippet(&ranges, snippet, window, cx)
6037 .log_err();
6038 } else {
6039 editor.buffer.update(cx, |multi_buffer, cx| {
6040 let auto_indent = match completion.insert_text_mode {
6041 Some(InsertTextMode::AS_IS) => None,
6042 _ => editor.autoindent_mode.clone(),
6043 };
6044 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6045 multi_buffer.edit(edits, auto_indent, cx);
6046 });
6047 }
6048 for (buffer, edits) in linked_edits {
6049 buffer.update(cx, |buffer, cx| {
6050 let snapshot = buffer.snapshot();
6051 let edits = edits
6052 .into_iter()
6053 .map(|(range, text)| {
6054 use text::ToPoint as TP;
6055 let end_point = TP::to_point(&range.end, &snapshot);
6056 let start_point = TP::to_point(&range.start, &snapshot);
6057 (start_point..end_point, text)
6058 })
6059 .sorted_by_key(|(range, _)| range.start);
6060 buffer.edit(edits, None, cx);
6061 })
6062 }
6063
6064 editor.refresh_edit_prediction(true, false, window, cx);
6065 });
6066 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6067
6068 let show_new_completions_on_confirm = completion
6069 .confirm
6070 .as_ref()
6071 .is_some_and(|confirm| confirm(intent, window, cx));
6072 if show_new_completions_on_confirm {
6073 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6074 }
6075
6076 let provider = self.completion_provider.as_ref()?;
6077 drop(completion);
6078 let apply_edits = provider.apply_additional_edits_for_completion(
6079 buffer_handle,
6080 completions_menu.completions.clone(),
6081 candidate_id,
6082 true,
6083 cx,
6084 );
6085
6086 let editor_settings = EditorSettings::get_global(cx);
6087 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6088 // After the code completion is finished, users often want to know what signatures are needed.
6089 // so we should automatically call signature_help
6090 self.show_signature_help(&ShowSignatureHelp, window, cx);
6091 }
6092
6093 Some(cx.foreground_executor().spawn(async move {
6094 apply_edits.await?;
6095 Ok(())
6096 }))
6097 }
6098
6099 pub fn toggle_code_actions(
6100 &mut self,
6101 action: &ToggleCodeActions,
6102 window: &mut Window,
6103 cx: &mut Context<Self>,
6104 ) {
6105 let quick_launch = action.quick_launch;
6106 let mut context_menu = self.context_menu.borrow_mut();
6107 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6108 if code_actions.deployed_from == action.deployed_from {
6109 // Toggle if we're selecting the same one
6110 *context_menu = None;
6111 cx.notify();
6112 return;
6113 } else {
6114 // Otherwise, clear it and start a new one
6115 *context_menu = None;
6116 cx.notify();
6117 }
6118 }
6119 drop(context_menu);
6120 let snapshot = self.snapshot(window, cx);
6121 let deployed_from = action.deployed_from.clone();
6122 let action = action.clone();
6123 self.completion_tasks.clear();
6124 self.discard_edit_prediction(false, cx);
6125
6126 let multibuffer_point = match &action.deployed_from {
6127 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6128 DisplayPoint::new(*row, 0).to_point(&snapshot)
6129 }
6130 _ => self.selections.newest::<Point>(cx).head(),
6131 };
6132 let Some((buffer, buffer_row)) = snapshot
6133 .buffer_snapshot
6134 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6135 .and_then(|(buffer_snapshot, range)| {
6136 self.buffer()
6137 .read(cx)
6138 .buffer(buffer_snapshot.remote_id())
6139 .map(|buffer| (buffer, range.start.row))
6140 })
6141 else {
6142 return;
6143 };
6144 let buffer_id = buffer.read(cx).remote_id();
6145 let tasks = self
6146 .tasks
6147 .get(&(buffer_id, buffer_row))
6148 .map(|t| Arc::new(t.to_owned()));
6149
6150 if !self.focus_handle.is_focused(window) {
6151 return;
6152 }
6153 let project = self.project.clone();
6154
6155 let code_actions_task = match deployed_from {
6156 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6157 _ => self.code_actions(buffer_row, window, cx),
6158 };
6159
6160 let runnable_task = match deployed_from {
6161 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6162 _ => {
6163 let mut task_context_task = Task::ready(None);
6164 if let Some(tasks) = &tasks
6165 && let Some(project) = project
6166 {
6167 task_context_task =
6168 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6169 }
6170
6171 cx.spawn_in(window, {
6172 let buffer = buffer.clone();
6173 async move |editor, cx| {
6174 let task_context = task_context_task.await;
6175
6176 let resolved_tasks =
6177 tasks
6178 .zip(task_context.clone())
6179 .map(|(tasks, task_context)| ResolvedTasks {
6180 templates: tasks.resolve(&task_context).collect(),
6181 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6182 multibuffer_point.row,
6183 tasks.column,
6184 )),
6185 });
6186 let debug_scenarios = editor
6187 .update(cx, |editor, cx| {
6188 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6189 })?
6190 .await;
6191 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6192 }
6193 })
6194 }
6195 };
6196
6197 cx.spawn_in(window, async move |editor, cx| {
6198 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6199 let code_actions = code_actions_task.await;
6200 let spawn_straight_away = quick_launch
6201 && resolved_tasks
6202 .as_ref()
6203 .is_some_and(|tasks| tasks.templates.len() == 1)
6204 && code_actions
6205 .as_ref()
6206 .is_none_or(|actions| actions.is_empty())
6207 && debug_scenarios.is_empty();
6208
6209 editor.update_in(cx, |editor, window, cx| {
6210 crate::hover_popover::hide_hover(editor, cx);
6211 let actions = CodeActionContents::new(
6212 resolved_tasks,
6213 code_actions,
6214 debug_scenarios,
6215 task_context.unwrap_or_default(),
6216 );
6217
6218 // Don't show the menu if there are no actions available
6219 if actions.is_empty() {
6220 cx.notify();
6221 return Task::ready(Ok(()));
6222 }
6223
6224 *editor.context_menu.borrow_mut() =
6225 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6226 buffer,
6227 actions,
6228 selected_item: Default::default(),
6229 scroll_handle: UniformListScrollHandle::default(),
6230 deployed_from,
6231 }));
6232 cx.notify();
6233 if spawn_straight_away
6234 && let Some(task) = editor.confirm_code_action(
6235 &ConfirmCodeAction { item_ix: Some(0) },
6236 window,
6237 cx,
6238 )
6239 {
6240 return task;
6241 }
6242
6243 Task::ready(Ok(()))
6244 })
6245 })
6246 .detach_and_log_err(cx);
6247 }
6248
6249 fn debug_scenarios(
6250 &mut self,
6251 resolved_tasks: &Option<ResolvedTasks>,
6252 buffer: &Entity<Buffer>,
6253 cx: &mut App,
6254 ) -> Task<Vec<task::DebugScenario>> {
6255 maybe!({
6256 let project = self.project()?;
6257 let dap_store = project.read(cx).dap_store();
6258 let mut scenarios = vec![];
6259 let resolved_tasks = resolved_tasks.as_ref()?;
6260 let buffer = buffer.read(cx);
6261 let language = buffer.language()?;
6262 let file = buffer.file();
6263 let debug_adapter = language_settings(language.name().into(), file, cx)
6264 .debuggers
6265 .first()
6266 .map(SharedString::from)
6267 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6268
6269 dap_store.update(cx, |dap_store, cx| {
6270 for (_, task) in &resolved_tasks.templates {
6271 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6272 task.original_task().clone(),
6273 debug_adapter.clone().into(),
6274 task.display_label().to_owned().into(),
6275 cx,
6276 );
6277 scenarios.push(maybe_scenario);
6278 }
6279 });
6280 Some(cx.background_spawn(async move {
6281 futures::future::join_all(scenarios)
6282 .await
6283 .into_iter()
6284 .flatten()
6285 .collect::<Vec<_>>()
6286 }))
6287 })
6288 .unwrap_or_else(|| Task::ready(vec![]))
6289 }
6290
6291 fn code_actions(
6292 &mut self,
6293 buffer_row: u32,
6294 window: &mut Window,
6295 cx: &mut Context<Self>,
6296 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6297 let mut task = self.code_actions_task.take();
6298 cx.spawn_in(window, async move |editor, cx| {
6299 while let Some(prev_task) = task {
6300 prev_task.await.log_err();
6301 task = editor
6302 .update(cx, |this, _| this.code_actions_task.take())
6303 .ok()?;
6304 }
6305
6306 editor
6307 .update(cx, |editor, cx| {
6308 editor
6309 .available_code_actions
6310 .clone()
6311 .and_then(|(location, code_actions)| {
6312 let snapshot = location.buffer.read(cx).snapshot();
6313 let point_range = location.range.to_point(&snapshot);
6314 let point_range = point_range.start.row..=point_range.end.row;
6315 if point_range.contains(&buffer_row) {
6316 Some(code_actions)
6317 } else {
6318 None
6319 }
6320 })
6321 })
6322 .ok()
6323 .flatten()
6324 })
6325 }
6326
6327 pub fn confirm_code_action(
6328 &mut self,
6329 action: &ConfirmCodeAction,
6330 window: &mut Window,
6331 cx: &mut Context<Self>,
6332 ) -> Option<Task<Result<()>>> {
6333 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6334
6335 let actions_menu =
6336 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6337 menu
6338 } else {
6339 return None;
6340 };
6341
6342 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6343 let action = actions_menu.actions.get(action_ix)?;
6344 let title = action.label();
6345 let buffer = actions_menu.buffer;
6346 let workspace = self.workspace()?;
6347
6348 match action {
6349 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6350 workspace.update(cx, |workspace, cx| {
6351 workspace.schedule_resolved_task(
6352 task_source_kind,
6353 resolved_task,
6354 false,
6355 window,
6356 cx,
6357 );
6358
6359 Some(Task::ready(Ok(())))
6360 })
6361 }
6362 CodeActionsItem::CodeAction {
6363 excerpt_id,
6364 action,
6365 provider,
6366 } => {
6367 let apply_code_action =
6368 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6369 let workspace = workspace.downgrade();
6370 Some(cx.spawn_in(window, async move |editor, cx| {
6371 let project_transaction = apply_code_action.await?;
6372 Self::open_project_transaction(
6373 &editor,
6374 workspace,
6375 project_transaction,
6376 title,
6377 cx,
6378 )
6379 .await
6380 }))
6381 }
6382 CodeActionsItem::DebugScenario(scenario) => {
6383 let context = actions_menu.actions.context;
6384
6385 workspace.update(cx, |workspace, cx| {
6386 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6387 workspace.start_debug_session(
6388 scenario,
6389 context,
6390 Some(buffer),
6391 None,
6392 window,
6393 cx,
6394 );
6395 });
6396 Some(Task::ready(Ok(())))
6397 }
6398 }
6399 }
6400
6401 pub async fn open_project_transaction(
6402 editor: &WeakEntity<Editor>,
6403 workspace: WeakEntity<Workspace>,
6404 transaction: ProjectTransaction,
6405 title: String,
6406 cx: &mut AsyncWindowContext,
6407 ) -> Result<()> {
6408 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6409 cx.update(|_, cx| {
6410 entries.sort_unstable_by_key(|(buffer, _)| {
6411 buffer.read(cx).file().map(|f| f.path().clone())
6412 });
6413 })?;
6414
6415 // If the project transaction's edits are all contained within this editor, then
6416 // avoid opening a new editor to display them.
6417
6418 if let Some((buffer, transaction)) = entries.first() {
6419 if entries.len() == 1 {
6420 let excerpt = editor.update(cx, |editor, cx| {
6421 editor
6422 .buffer()
6423 .read(cx)
6424 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6425 })?;
6426 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6427 && excerpted_buffer == *buffer
6428 {
6429 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6430 let excerpt_range = excerpt_range.to_offset(buffer);
6431 buffer
6432 .edited_ranges_for_transaction::<usize>(transaction)
6433 .all(|range| {
6434 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6435 })
6436 })?;
6437
6438 if all_edits_within_excerpt {
6439 return Ok(());
6440 }
6441 }
6442 }
6443 } else {
6444 return Ok(());
6445 }
6446
6447 let mut ranges_to_highlight = Vec::new();
6448 let excerpt_buffer = cx.new(|cx| {
6449 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6450 for (buffer_handle, transaction) in &entries {
6451 let edited_ranges = buffer_handle
6452 .read(cx)
6453 .edited_ranges_for_transaction::<Point>(transaction)
6454 .collect::<Vec<_>>();
6455 let (ranges, _) = multibuffer.set_excerpts_for_path(
6456 PathKey::for_buffer(buffer_handle, cx),
6457 buffer_handle.clone(),
6458 edited_ranges,
6459 multibuffer_context_lines(cx),
6460 cx,
6461 );
6462
6463 ranges_to_highlight.extend(ranges);
6464 }
6465 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6466 multibuffer
6467 })?;
6468
6469 workspace.update_in(cx, |workspace, window, cx| {
6470 let project = workspace.project().clone();
6471 let editor =
6472 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6473 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6474 editor.update(cx, |editor, cx| {
6475 editor.highlight_background::<Self>(
6476 &ranges_to_highlight,
6477 |theme| theme.colors().editor_highlighted_line_background,
6478 cx,
6479 );
6480 });
6481 })?;
6482
6483 Ok(())
6484 }
6485
6486 pub fn clear_code_action_providers(&mut self) {
6487 self.code_action_providers.clear();
6488 self.available_code_actions.take();
6489 }
6490
6491 pub fn add_code_action_provider(
6492 &mut self,
6493 provider: Rc<dyn CodeActionProvider>,
6494 window: &mut Window,
6495 cx: &mut Context<Self>,
6496 ) {
6497 if self
6498 .code_action_providers
6499 .iter()
6500 .any(|existing_provider| existing_provider.id() == provider.id())
6501 {
6502 return;
6503 }
6504
6505 self.code_action_providers.push(provider);
6506 self.refresh_code_actions(window, cx);
6507 }
6508
6509 pub fn remove_code_action_provider(
6510 &mut self,
6511 id: Arc<str>,
6512 window: &mut Window,
6513 cx: &mut Context<Self>,
6514 ) {
6515 self.code_action_providers
6516 .retain(|provider| provider.id() != id);
6517 self.refresh_code_actions(window, cx);
6518 }
6519
6520 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6521 !self.code_action_providers.is_empty()
6522 && EditorSettings::get_global(cx).toolbar.code_actions
6523 }
6524
6525 pub fn has_available_code_actions(&self) -> bool {
6526 self.available_code_actions
6527 .as_ref()
6528 .is_some_and(|(_, actions)| !actions.is_empty())
6529 }
6530
6531 fn render_inline_code_actions(
6532 &self,
6533 icon_size: ui::IconSize,
6534 display_row: DisplayRow,
6535 is_active: bool,
6536 cx: &mut Context<Self>,
6537 ) -> AnyElement {
6538 let show_tooltip = !self.context_menu_visible();
6539 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6540 .icon_size(icon_size)
6541 .shape(ui::IconButtonShape::Square)
6542 .icon_color(ui::Color::Hidden)
6543 .toggle_state(is_active)
6544 .when(show_tooltip, |this| {
6545 this.tooltip({
6546 let focus_handle = self.focus_handle.clone();
6547 move |window, cx| {
6548 Tooltip::for_action_in(
6549 "Toggle Code Actions",
6550 &ToggleCodeActions {
6551 deployed_from: None,
6552 quick_launch: false,
6553 },
6554 &focus_handle,
6555 window,
6556 cx,
6557 )
6558 }
6559 })
6560 })
6561 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6562 window.focus(&editor.focus_handle(cx));
6563 editor.toggle_code_actions(
6564 &crate::actions::ToggleCodeActions {
6565 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6566 display_row,
6567 )),
6568 quick_launch: false,
6569 },
6570 window,
6571 cx,
6572 );
6573 }))
6574 .into_any_element()
6575 }
6576
6577 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6578 &self.context_menu
6579 }
6580
6581 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6582 let newest_selection = self.selections.newest_anchor().clone();
6583 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6584 let buffer = self.buffer.read(cx);
6585 if newest_selection.head().diff_base_anchor.is_some() {
6586 return None;
6587 }
6588 let (start_buffer, start) =
6589 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6590 let (end_buffer, end) =
6591 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6592 if start_buffer != end_buffer {
6593 return None;
6594 }
6595
6596 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6597 cx.background_executor()
6598 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6599 .await;
6600
6601 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6602 let providers = this.code_action_providers.clone();
6603 let tasks = this
6604 .code_action_providers
6605 .iter()
6606 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6607 .collect::<Vec<_>>();
6608 (providers, tasks)
6609 })?;
6610
6611 let mut actions = Vec::new();
6612 for (provider, provider_actions) in
6613 providers.into_iter().zip(future::join_all(tasks).await)
6614 {
6615 if let Some(provider_actions) = provider_actions.log_err() {
6616 actions.extend(provider_actions.into_iter().map(|action| {
6617 AvailableCodeAction {
6618 excerpt_id: newest_selection.start.excerpt_id,
6619 action,
6620 provider: provider.clone(),
6621 }
6622 }));
6623 }
6624 }
6625
6626 this.update(cx, |this, cx| {
6627 this.available_code_actions = if actions.is_empty() {
6628 None
6629 } else {
6630 Some((
6631 Location {
6632 buffer: start_buffer,
6633 range: start..end,
6634 },
6635 actions.into(),
6636 ))
6637 };
6638 cx.notify();
6639 })
6640 }));
6641 None
6642 }
6643
6644 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6645 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6646 self.show_git_blame_inline = false;
6647
6648 self.show_git_blame_inline_delay_task =
6649 Some(cx.spawn_in(window, async move |this, cx| {
6650 cx.background_executor().timer(delay).await;
6651
6652 this.update(cx, |this, cx| {
6653 this.show_git_blame_inline = true;
6654 cx.notify();
6655 })
6656 .log_err();
6657 }));
6658 }
6659 }
6660
6661 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6662 let snapshot = self.snapshot(window, cx);
6663 let cursor = self.selections.newest::<Point>(cx).head();
6664 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6665 else {
6666 return;
6667 };
6668
6669 let Some(blame) = self.blame.as_ref() else {
6670 return;
6671 };
6672
6673 let row_info = RowInfo {
6674 buffer_id: Some(buffer.remote_id()),
6675 buffer_row: Some(point.row),
6676 ..Default::default()
6677 };
6678 let Some((buffer, blame_entry)) = blame
6679 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6680 .flatten()
6681 else {
6682 return;
6683 };
6684
6685 let anchor = self.selections.newest_anchor().head();
6686 let position = self.to_pixel_point(anchor, &snapshot, window);
6687 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6688 self.show_blame_popover(
6689 buffer,
6690 &blame_entry,
6691 position + last_bounds.origin,
6692 true,
6693 cx,
6694 );
6695 };
6696 }
6697
6698 fn show_blame_popover(
6699 &mut self,
6700 buffer: BufferId,
6701 blame_entry: &BlameEntry,
6702 position: gpui::Point<Pixels>,
6703 ignore_timeout: bool,
6704 cx: &mut Context<Self>,
6705 ) {
6706 if let Some(state) = &mut self.inline_blame_popover {
6707 state.hide_task.take();
6708 } else {
6709 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6710 let blame_entry = blame_entry.clone();
6711 let show_task = cx.spawn(async move |editor, cx| {
6712 if !ignore_timeout {
6713 cx.background_executor()
6714 .timer(std::time::Duration::from_millis(blame_popover_delay))
6715 .await;
6716 }
6717 editor
6718 .update(cx, |editor, cx| {
6719 editor.inline_blame_popover_show_task.take();
6720 let Some(blame) = editor.blame.as_ref() else {
6721 return;
6722 };
6723 let blame = blame.read(cx);
6724 let details = blame.details_for_entry(buffer, &blame_entry);
6725 let markdown = cx.new(|cx| {
6726 Markdown::new(
6727 details
6728 .as_ref()
6729 .map(|message| message.message.clone())
6730 .unwrap_or_default(),
6731 None,
6732 None,
6733 cx,
6734 )
6735 });
6736 editor.inline_blame_popover = Some(InlineBlamePopover {
6737 position,
6738 hide_task: None,
6739 popover_bounds: None,
6740 popover_state: InlineBlamePopoverState {
6741 scroll_handle: ScrollHandle::new(),
6742 commit_message: details,
6743 markdown,
6744 },
6745 keyboard_grace: ignore_timeout,
6746 });
6747 cx.notify();
6748 })
6749 .ok();
6750 });
6751 self.inline_blame_popover_show_task = Some(show_task);
6752 }
6753 }
6754
6755 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6756 self.inline_blame_popover_show_task.take();
6757 if let Some(state) = &mut self.inline_blame_popover {
6758 let hide_task = cx.spawn(async move |editor, cx| {
6759 cx.background_executor()
6760 .timer(std::time::Duration::from_millis(100))
6761 .await;
6762 editor
6763 .update(cx, |editor, cx| {
6764 editor.inline_blame_popover.take();
6765 cx.notify();
6766 })
6767 .ok();
6768 });
6769 state.hide_task = Some(hide_task);
6770 }
6771 }
6772
6773 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6774 if self.pending_rename.is_some() {
6775 return None;
6776 }
6777
6778 let provider = self.semantics_provider.clone()?;
6779 let buffer = self.buffer.read(cx);
6780 let newest_selection = self.selections.newest_anchor().clone();
6781 let cursor_position = newest_selection.head();
6782 let (cursor_buffer, cursor_buffer_position) =
6783 buffer.text_anchor_for_position(cursor_position, cx)?;
6784 let (tail_buffer, tail_buffer_position) =
6785 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6786 if cursor_buffer != tail_buffer {
6787 return None;
6788 }
6789
6790 let snapshot = cursor_buffer.read(cx).snapshot();
6791 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6792 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6793 if start_word_range != end_word_range {
6794 self.document_highlights_task.take();
6795 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6796 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6797 return None;
6798 }
6799
6800 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6801 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6802 cx.background_executor()
6803 .timer(Duration::from_millis(debounce))
6804 .await;
6805
6806 let highlights = if let Some(highlights) = cx
6807 .update(|cx| {
6808 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6809 })
6810 .ok()
6811 .flatten()
6812 {
6813 highlights.await.log_err()
6814 } else {
6815 None
6816 };
6817
6818 if let Some(highlights) = highlights {
6819 this.update(cx, |this, cx| {
6820 if this.pending_rename.is_some() {
6821 return;
6822 }
6823
6824 let buffer = this.buffer.read(cx);
6825 if buffer
6826 .text_anchor_for_position(cursor_position, cx)
6827 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6828 {
6829 return;
6830 }
6831
6832 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6833 let mut write_ranges = Vec::new();
6834 let mut read_ranges = Vec::new();
6835 for highlight in highlights {
6836 let buffer_id = cursor_buffer.read(cx).remote_id();
6837 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6838 {
6839 let start = highlight
6840 .range
6841 .start
6842 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6843 let end = highlight
6844 .range
6845 .end
6846 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6847 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6848 continue;
6849 }
6850
6851 let range = Anchor {
6852 buffer_id: Some(buffer_id),
6853 excerpt_id,
6854 text_anchor: start,
6855 diff_base_anchor: None,
6856 }..Anchor {
6857 buffer_id: Some(buffer_id),
6858 excerpt_id,
6859 text_anchor: end,
6860 diff_base_anchor: None,
6861 };
6862 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6863 write_ranges.push(range);
6864 } else {
6865 read_ranges.push(range);
6866 }
6867 }
6868 }
6869
6870 this.highlight_background::<DocumentHighlightRead>(
6871 &read_ranges,
6872 |theme| theme.colors().editor_document_highlight_read_background,
6873 cx,
6874 );
6875 this.highlight_background::<DocumentHighlightWrite>(
6876 &write_ranges,
6877 |theme| theme.colors().editor_document_highlight_write_background,
6878 cx,
6879 );
6880 cx.notify();
6881 })
6882 .log_err();
6883 }
6884 }));
6885 None
6886 }
6887
6888 fn prepare_highlight_query_from_selection(
6889 &mut self,
6890 cx: &mut Context<Editor>,
6891 ) -> Option<(String, Range<Anchor>)> {
6892 if matches!(self.mode, EditorMode::SingleLine) {
6893 return None;
6894 }
6895 if !EditorSettings::get_global(cx).selection_highlight {
6896 return None;
6897 }
6898 if self.selections.count() != 1 || self.selections.line_mode {
6899 return None;
6900 }
6901 let selection = self.selections.newest::<Point>(cx);
6902 if selection.is_empty() || selection.start.row != selection.end.row {
6903 return None;
6904 }
6905 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6906 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6907 let query = multi_buffer_snapshot
6908 .text_for_range(selection_anchor_range.clone())
6909 .collect::<String>();
6910 if query.trim().is_empty() {
6911 return None;
6912 }
6913 Some((query, selection_anchor_range))
6914 }
6915
6916 fn update_selection_occurrence_highlights(
6917 &mut self,
6918 query_text: String,
6919 query_range: Range<Anchor>,
6920 multi_buffer_range_to_query: Range<Point>,
6921 use_debounce: bool,
6922 window: &mut Window,
6923 cx: &mut Context<Editor>,
6924 ) -> Task<()> {
6925 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6926 cx.spawn_in(window, async move |editor, cx| {
6927 if use_debounce {
6928 cx.background_executor()
6929 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6930 .await;
6931 }
6932 let match_task = cx.background_spawn(async move {
6933 let buffer_ranges = multi_buffer_snapshot
6934 .range_to_buffer_ranges(multi_buffer_range_to_query)
6935 .into_iter()
6936 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6937 let mut match_ranges = Vec::new();
6938 let Ok(regex) = project::search::SearchQuery::text(
6939 query_text.clone(),
6940 false,
6941 false,
6942 false,
6943 Default::default(),
6944 Default::default(),
6945 false,
6946 None,
6947 ) else {
6948 return Vec::default();
6949 };
6950 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6951 match_ranges.extend(
6952 regex
6953 .search(buffer_snapshot, Some(search_range.clone()))
6954 .await
6955 .into_iter()
6956 .filter_map(|match_range| {
6957 let match_start = buffer_snapshot
6958 .anchor_after(search_range.start + match_range.start);
6959 let match_end = buffer_snapshot
6960 .anchor_before(search_range.start + match_range.end);
6961 let match_anchor_range = Anchor::range_in_buffer(
6962 excerpt_id,
6963 buffer_snapshot.remote_id(),
6964 match_start..match_end,
6965 );
6966 (match_anchor_range != query_range).then_some(match_anchor_range)
6967 }),
6968 );
6969 }
6970 match_ranges
6971 });
6972 let match_ranges = match_task.await;
6973 editor
6974 .update_in(cx, |editor, _, cx| {
6975 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6976 if !match_ranges.is_empty() {
6977 editor.highlight_background::<SelectedTextHighlight>(
6978 &match_ranges,
6979 |theme| theme.colors().editor_document_highlight_bracket_background,
6980 cx,
6981 )
6982 }
6983 })
6984 .log_err();
6985 })
6986 }
6987
6988 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6989 struct NewlineFold;
6990 let type_id = std::any::TypeId::of::<NewlineFold>();
6991 if !self.mode.is_single_line() {
6992 return;
6993 }
6994 let snapshot = self.snapshot(window, cx);
6995 if snapshot.buffer_snapshot.max_point().row == 0 {
6996 return;
6997 }
6998 let task = cx.background_spawn(async move {
6999 let new_newlines = snapshot
7000 .buffer_chars_at(0)
7001 .filter_map(|(c, i)| {
7002 if c == '\n' {
7003 Some(
7004 snapshot.buffer_snapshot.anchor_after(i)
7005 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7006 )
7007 } else {
7008 None
7009 }
7010 })
7011 .collect::<Vec<_>>();
7012 let existing_newlines = snapshot
7013 .folds_in_range(0..snapshot.buffer_snapshot.len())
7014 .filter_map(|fold| {
7015 if fold.placeholder.type_tag == Some(type_id) {
7016 Some(fold.range.start..fold.range.end)
7017 } else {
7018 None
7019 }
7020 })
7021 .collect::<Vec<_>>();
7022
7023 (new_newlines, existing_newlines)
7024 });
7025 self.folding_newlines = cx.spawn(async move |this, cx| {
7026 let (new_newlines, existing_newlines) = task.await;
7027 if new_newlines == existing_newlines {
7028 return;
7029 }
7030 let placeholder = FoldPlaceholder {
7031 render: Arc::new(move |_, _, cx| {
7032 div()
7033 .bg(cx.theme().status().hint_background)
7034 .border_b_1()
7035 .size_full()
7036 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7037 .border_color(cx.theme().status().hint)
7038 .child("\\n")
7039 .into_any()
7040 }),
7041 constrain_width: false,
7042 merge_adjacent: false,
7043 type_tag: Some(type_id),
7044 };
7045 let creases = new_newlines
7046 .into_iter()
7047 .map(|range| Crease::simple(range, placeholder.clone()))
7048 .collect();
7049 this.update(cx, |this, cx| {
7050 this.display_map.update(cx, |display_map, cx| {
7051 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7052 display_map.fold(creases, cx);
7053 });
7054 })
7055 .ok();
7056 });
7057 }
7058
7059 fn refresh_selected_text_highlights(
7060 &mut self,
7061 on_buffer_edit: bool,
7062 window: &mut Window,
7063 cx: &mut Context<Editor>,
7064 ) {
7065 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7066 else {
7067 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7068 self.quick_selection_highlight_task.take();
7069 self.debounced_selection_highlight_task.take();
7070 return;
7071 };
7072 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7073 if on_buffer_edit
7074 || self
7075 .quick_selection_highlight_task
7076 .as_ref()
7077 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7078 {
7079 let multi_buffer_visible_start = self
7080 .scroll_manager
7081 .anchor()
7082 .anchor
7083 .to_point(&multi_buffer_snapshot);
7084 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7085 multi_buffer_visible_start
7086 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7087 Bias::Left,
7088 );
7089 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7090 self.quick_selection_highlight_task = Some((
7091 query_range.clone(),
7092 self.update_selection_occurrence_highlights(
7093 query_text.clone(),
7094 query_range.clone(),
7095 multi_buffer_visible_range,
7096 false,
7097 window,
7098 cx,
7099 ),
7100 ));
7101 }
7102 if on_buffer_edit
7103 || self
7104 .debounced_selection_highlight_task
7105 .as_ref()
7106 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7107 {
7108 let multi_buffer_start = multi_buffer_snapshot
7109 .anchor_before(0)
7110 .to_point(&multi_buffer_snapshot);
7111 let multi_buffer_end = multi_buffer_snapshot
7112 .anchor_after(multi_buffer_snapshot.len())
7113 .to_point(&multi_buffer_snapshot);
7114 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7115 self.debounced_selection_highlight_task = Some((
7116 query_range.clone(),
7117 self.update_selection_occurrence_highlights(
7118 query_text,
7119 query_range,
7120 multi_buffer_full_range,
7121 true,
7122 window,
7123 cx,
7124 ),
7125 ));
7126 }
7127 }
7128
7129 pub fn refresh_edit_prediction(
7130 &mut self,
7131 debounce: bool,
7132 user_requested: bool,
7133 window: &mut Window,
7134 cx: &mut Context<Self>,
7135 ) -> Option<()> {
7136 if DisableAiSettings::get_global(cx).disable_ai {
7137 return None;
7138 }
7139
7140 let provider = self.edit_prediction_provider()?;
7141 let cursor = self.selections.newest_anchor().head();
7142 let (buffer, cursor_buffer_position) =
7143 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7144
7145 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7146 self.discard_edit_prediction(false, cx);
7147 return None;
7148 }
7149
7150 if !user_requested
7151 && (!self.should_show_edit_predictions()
7152 || !self.is_focused(window)
7153 || buffer.read(cx).is_empty())
7154 {
7155 self.discard_edit_prediction(false, cx);
7156 return None;
7157 }
7158
7159 self.update_visible_edit_prediction(window, cx);
7160 provider.refresh(
7161 self.project.clone(),
7162 buffer,
7163 cursor_buffer_position,
7164 debounce,
7165 cx,
7166 );
7167 Some(())
7168 }
7169
7170 fn show_edit_predictions_in_menu(&self) -> bool {
7171 match self.edit_prediction_settings {
7172 EditPredictionSettings::Disabled => false,
7173 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7174 }
7175 }
7176
7177 pub fn edit_predictions_enabled(&self) -> bool {
7178 match self.edit_prediction_settings {
7179 EditPredictionSettings::Disabled => false,
7180 EditPredictionSettings::Enabled { .. } => true,
7181 }
7182 }
7183
7184 fn edit_prediction_requires_modifier(&self) -> bool {
7185 match self.edit_prediction_settings {
7186 EditPredictionSettings::Disabled => false,
7187 EditPredictionSettings::Enabled {
7188 preview_requires_modifier,
7189 ..
7190 } => preview_requires_modifier,
7191 }
7192 }
7193
7194 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7195 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7196 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7197 self.discard_edit_prediction(false, cx);
7198 } else {
7199 let selection = self.selections.newest_anchor();
7200 let cursor = selection.head();
7201
7202 if let Some((buffer, cursor_buffer_position)) =
7203 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7204 {
7205 self.edit_prediction_settings =
7206 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7207 }
7208 }
7209 }
7210
7211 fn edit_prediction_settings_at_position(
7212 &self,
7213 buffer: &Entity<Buffer>,
7214 buffer_position: language::Anchor,
7215 cx: &App,
7216 ) -> EditPredictionSettings {
7217 if !self.mode.is_full()
7218 || !self.show_edit_predictions_override.unwrap_or(true)
7219 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7220 {
7221 return EditPredictionSettings::Disabled;
7222 }
7223
7224 let buffer = buffer.read(cx);
7225
7226 let file = buffer.file();
7227
7228 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7229 return EditPredictionSettings::Disabled;
7230 };
7231
7232 let by_provider = matches!(
7233 self.menu_edit_predictions_policy,
7234 MenuEditPredictionsPolicy::ByProvider
7235 );
7236
7237 let show_in_menu = by_provider
7238 && self
7239 .edit_prediction_provider
7240 .as_ref()
7241 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7242
7243 let preview_requires_modifier =
7244 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7245
7246 EditPredictionSettings::Enabled {
7247 show_in_menu,
7248 preview_requires_modifier,
7249 }
7250 }
7251
7252 fn should_show_edit_predictions(&self) -> bool {
7253 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7254 }
7255
7256 pub fn edit_prediction_preview_is_active(&self) -> bool {
7257 matches!(
7258 self.edit_prediction_preview,
7259 EditPredictionPreview::Active { .. }
7260 )
7261 }
7262
7263 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7264 let cursor = self.selections.newest_anchor().head();
7265 if let Some((buffer, cursor_position)) =
7266 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7267 {
7268 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7269 } else {
7270 false
7271 }
7272 }
7273
7274 pub fn supports_minimap(&self, cx: &App) -> bool {
7275 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7276 }
7277
7278 fn edit_predictions_enabled_in_buffer(
7279 &self,
7280 buffer: &Entity<Buffer>,
7281 buffer_position: language::Anchor,
7282 cx: &App,
7283 ) -> bool {
7284 maybe!({
7285 if self.read_only(cx) {
7286 return Some(false);
7287 }
7288 let provider = self.edit_prediction_provider()?;
7289 if !provider.is_enabled(buffer, buffer_position, cx) {
7290 return Some(false);
7291 }
7292 let buffer = buffer.read(cx);
7293 let Some(file) = buffer.file() else {
7294 return Some(true);
7295 };
7296 let settings = all_language_settings(Some(file), cx);
7297 Some(settings.edit_predictions_enabled_for_file(file, cx))
7298 })
7299 .unwrap_or(false)
7300 }
7301
7302 fn cycle_edit_prediction(
7303 &mut self,
7304 direction: Direction,
7305 window: &mut Window,
7306 cx: &mut Context<Self>,
7307 ) -> Option<()> {
7308 let provider = self.edit_prediction_provider()?;
7309 let cursor = self.selections.newest_anchor().head();
7310 let (buffer, cursor_buffer_position) =
7311 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7312 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7313 return None;
7314 }
7315
7316 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7317 self.update_visible_edit_prediction(window, cx);
7318
7319 Some(())
7320 }
7321
7322 pub fn show_edit_prediction(
7323 &mut self,
7324 _: &ShowEditPrediction,
7325 window: &mut Window,
7326 cx: &mut Context<Self>,
7327 ) {
7328 if !self.has_active_edit_prediction() {
7329 self.refresh_edit_prediction(false, true, window, cx);
7330 return;
7331 }
7332
7333 self.update_visible_edit_prediction(window, cx);
7334 }
7335
7336 pub fn display_cursor_names(
7337 &mut self,
7338 _: &DisplayCursorNames,
7339 window: &mut Window,
7340 cx: &mut Context<Self>,
7341 ) {
7342 self.show_cursor_names(window, cx);
7343 }
7344
7345 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7346 self.show_cursor_names = true;
7347 cx.notify();
7348 cx.spawn_in(window, async move |this, cx| {
7349 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7350 this.update(cx, |this, cx| {
7351 this.show_cursor_names = false;
7352 cx.notify()
7353 })
7354 .ok()
7355 })
7356 .detach();
7357 }
7358
7359 pub fn next_edit_prediction(
7360 &mut self,
7361 _: &NextEditPrediction,
7362 window: &mut Window,
7363 cx: &mut Context<Self>,
7364 ) {
7365 if self.has_active_edit_prediction() {
7366 self.cycle_edit_prediction(Direction::Next, window, cx);
7367 } else {
7368 let is_copilot_disabled = self
7369 .refresh_edit_prediction(false, true, window, cx)
7370 .is_none();
7371 if is_copilot_disabled {
7372 cx.propagate();
7373 }
7374 }
7375 }
7376
7377 pub fn previous_edit_prediction(
7378 &mut self,
7379 _: &PreviousEditPrediction,
7380 window: &mut Window,
7381 cx: &mut Context<Self>,
7382 ) {
7383 if self.has_active_edit_prediction() {
7384 self.cycle_edit_prediction(Direction::Prev, window, cx);
7385 } else {
7386 let is_copilot_disabled = self
7387 .refresh_edit_prediction(false, true, window, cx)
7388 .is_none();
7389 if is_copilot_disabled {
7390 cx.propagate();
7391 }
7392 }
7393 }
7394
7395 pub fn accept_edit_prediction(
7396 &mut self,
7397 _: &AcceptEditPrediction,
7398 window: &mut Window,
7399 cx: &mut Context<Self>,
7400 ) {
7401 if self.show_edit_predictions_in_menu() {
7402 self.hide_context_menu(window, cx);
7403 }
7404
7405 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7406 return;
7407 };
7408
7409 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7410
7411 match &active_edit_prediction.completion {
7412 EditPrediction::Move { target, .. } => {
7413 let target = *target;
7414
7415 if let Some(position_map) = &self.last_position_map {
7416 if position_map
7417 .visible_row_range
7418 .contains(&target.to_display_point(&position_map.snapshot).row())
7419 || !self.edit_prediction_requires_modifier()
7420 {
7421 self.unfold_ranges(&[target..target], true, false, cx);
7422 // Note that this is also done in vim's handler of the Tab action.
7423 self.change_selections(
7424 SelectionEffects::scroll(Autoscroll::newest()),
7425 window,
7426 cx,
7427 |selections| {
7428 selections.select_anchor_ranges([target..target]);
7429 },
7430 );
7431 self.clear_row_highlights::<EditPredictionPreview>();
7432
7433 self.edit_prediction_preview
7434 .set_previous_scroll_position(None);
7435 } else {
7436 self.edit_prediction_preview
7437 .set_previous_scroll_position(Some(
7438 position_map.snapshot.scroll_anchor,
7439 ));
7440
7441 self.highlight_rows::<EditPredictionPreview>(
7442 target..target,
7443 cx.theme().colors().editor_highlighted_line_background,
7444 RowHighlightOptions {
7445 autoscroll: true,
7446 ..Default::default()
7447 },
7448 cx,
7449 );
7450 self.request_autoscroll(Autoscroll::fit(), cx);
7451 }
7452 }
7453 }
7454 EditPrediction::Edit { edits, .. } => {
7455 if let Some(provider) = self.edit_prediction_provider() {
7456 provider.accept(cx);
7457 }
7458
7459 // Store the transaction ID and selections before applying the edit
7460 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7461
7462 let snapshot = self.buffer.read(cx).snapshot(cx);
7463 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7464
7465 self.buffer.update(cx, |buffer, cx| {
7466 buffer.edit(edits.iter().cloned(), None, cx)
7467 });
7468
7469 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7470 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7471 });
7472
7473 let selections = self.selections.disjoint_anchors_arc();
7474 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7475 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7476 if has_new_transaction {
7477 self.selection_history
7478 .insert_transaction(transaction_id_now, selections);
7479 }
7480 }
7481
7482 self.update_visible_edit_prediction(window, cx);
7483 if self.active_edit_prediction.is_none() {
7484 self.refresh_edit_prediction(true, true, window, cx);
7485 }
7486
7487 cx.notify();
7488 }
7489 }
7490
7491 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7492 }
7493
7494 pub fn accept_partial_edit_prediction(
7495 &mut self,
7496 _: &AcceptPartialEditPrediction,
7497 window: &mut Window,
7498 cx: &mut Context<Self>,
7499 ) {
7500 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7501 return;
7502 };
7503 if self.selections.count() != 1 {
7504 return;
7505 }
7506
7507 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7508
7509 match &active_edit_prediction.completion {
7510 EditPrediction::Move { target, .. } => {
7511 let target = *target;
7512 self.change_selections(
7513 SelectionEffects::scroll(Autoscroll::newest()),
7514 window,
7515 cx,
7516 |selections| {
7517 selections.select_anchor_ranges([target..target]);
7518 },
7519 );
7520 }
7521 EditPrediction::Edit { edits, .. } => {
7522 // Find an insertion that starts at the cursor position.
7523 let snapshot = self.buffer.read(cx).snapshot(cx);
7524 let cursor_offset = self.selections.newest::<usize>(cx).head();
7525 let insertion = edits.iter().find_map(|(range, text)| {
7526 let range = range.to_offset(&snapshot);
7527 if range.is_empty() && range.start == cursor_offset {
7528 Some(text)
7529 } else {
7530 None
7531 }
7532 });
7533
7534 if let Some(text) = insertion {
7535 let mut partial_completion = text
7536 .chars()
7537 .by_ref()
7538 .take_while(|c| c.is_alphabetic())
7539 .collect::<String>();
7540 if partial_completion.is_empty() {
7541 partial_completion = text
7542 .chars()
7543 .by_ref()
7544 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7545 .collect::<String>();
7546 }
7547
7548 cx.emit(EditorEvent::InputHandled {
7549 utf16_range_to_replace: None,
7550 text: partial_completion.clone().into(),
7551 });
7552
7553 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7554
7555 self.refresh_edit_prediction(true, true, window, cx);
7556 cx.notify();
7557 } else {
7558 self.accept_edit_prediction(&Default::default(), window, cx);
7559 }
7560 }
7561 }
7562 }
7563
7564 fn discard_edit_prediction(
7565 &mut self,
7566 should_report_edit_prediction_event: bool,
7567 cx: &mut Context<Self>,
7568 ) -> bool {
7569 if should_report_edit_prediction_event {
7570 let completion_id = self
7571 .active_edit_prediction
7572 .as_ref()
7573 .and_then(|active_completion| active_completion.completion_id.clone());
7574
7575 self.report_edit_prediction_event(completion_id, false, cx);
7576 }
7577
7578 if let Some(provider) = self.edit_prediction_provider() {
7579 provider.discard(cx);
7580 }
7581
7582 self.take_active_edit_prediction(cx)
7583 }
7584
7585 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7586 let Some(provider) = self.edit_prediction_provider() else {
7587 return;
7588 };
7589
7590 let Some((_, buffer, _)) = self
7591 .buffer
7592 .read(cx)
7593 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7594 else {
7595 return;
7596 };
7597
7598 let extension = buffer
7599 .read(cx)
7600 .file()
7601 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7602
7603 let event_type = match accepted {
7604 true => "Edit Prediction Accepted",
7605 false => "Edit Prediction Discarded",
7606 };
7607 telemetry::event!(
7608 event_type,
7609 provider = provider.name(),
7610 prediction_id = id,
7611 suggestion_accepted = accepted,
7612 file_extension = extension,
7613 );
7614 }
7615
7616 pub fn has_active_edit_prediction(&self) -> bool {
7617 self.active_edit_prediction.is_some()
7618 }
7619
7620 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7621 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7622 return false;
7623 };
7624
7625 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7626 self.clear_highlights::<EditPredictionHighlight>(cx);
7627 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7628 true
7629 }
7630
7631 /// Returns true when we're displaying the edit prediction popover below the cursor
7632 /// like we are not previewing and the LSP autocomplete menu is visible
7633 /// or we are in `when_holding_modifier` mode.
7634 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7635 if self.edit_prediction_preview_is_active()
7636 || !self.show_edit_predictions_in_menu()
7637 || !self.edit_predictions_enabled()
7638 {
7639 return false;
7640 }
7641
7642 if self.has_visible_completions_menu() {
7643 return true;
7644 }
7645
7646 has_completion && self.edit_prediction_requires_modifier()
7647 }
7648
7649 fn handle_modifiers_changed(
7650 &mut self,
7651 modifiers: Modifiers,
7652 position_map: &PositionMap,
7653 window: &mut Window,
7654 cx: &mut Context<Self>,
7655 ) {
7656 if self.show_edit_predictions_in_menu() {
7657 self.update_edit_prediction_preview(&modifiers, window, cx);
7658 }
7659
7660 self.update_selection_mode(&modifiers, position_map, window, cx);
7661
7662 let mouse_position = window.mouse_position();
7663 if !position_map.text_hitbox.is_hovered(window) {
7664 return;
7665 }
7666
7667 self.update_hovered_link(
7668 position_map.point_for_position(mouse_position),
7669 &position_map.snapshot,
7670 modifiers,
7671 window,
7672 cx,
7673 )
7674 }
7675
7676 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7677 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7678 if invert {
7679 match multi_cursor_setting {
7680 MultiCursorModifier::Alt => modifiers.alt,
7681 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7682 }
7683 } else {
7684 match multi_cursor_setting {
7685 MultiCursorModifier::Alt => modifiers.secondary(),
7686 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7687 }
7688 }
7689 }
7690
7691 fn columnar_selection_mode(
7692 modifiers: &Modifiers,
7693 cx: &mut Context<Self>,
7694 ) -> Option<ColumnarMode> {
7695 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7696 if Self::multi_cursor_modifier(false, modifiers, cx) {
7697 Some(ColumnarMode::FromMouse)
7698 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7699 Some(ColumnarMode::FromSelection)
7700 } else {
7701 None
7702 }
7703 } else {
7704 None
7705 }
7706 }
7707
7708 fn update_selection_mode(
7709 &mut self,
7710 modifiers: &Modifiers,
7711 position_map: &PositionMap,
7712 window: &mut Window,
7713 cx: &mut Context<Self>,
7714 ) {
7715 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7716 return;
7717 };
7718 if self.selections.pending_anchor().is_none() {
7719 return;
7720 }
7721
7722 let mouse_position = window.mouse_position();
7723 let point_for_position = position_map.point_for_position(mouse_position);
7724 let position = point_for_position.previous_valid;
7725
7726 self.select(
7727 SelectPhase::BeginColumnar {
7728 position,
7729 reset: false,
7730 mode,
7731 goal_column: point_for_position.exact_unclipped.column(),
7732 },
7733 window,
7734 cx,
7735 );
7736 }
7737
7738 fn update_edit_prediction_preview(
7739 &mut self,
7740 modifiers: &Modifiers,
7741 window: &mut Window,
7742 cx: &mut Context<Self>,
7743 ) {
7744 let mut modifiers_held = false;
7745 if let Some(accept_keystroke) = self
7746 .accept_edit_prediction_keybind(false, window, cx)
7747 .keystroke()
7748 {
7749 modifiers_held = modifiers_held
7750 || (accept_keystroke.modifiers() == modifiers
7751 && accept_keystroke.modifiers().modified());
7752 };
7753 if let Some(accept_partial_keystroke) = self
7754 .accept_edit_prediction_keybind(true, window, cx)
7755 .keystroke()
7756 {
7757 modifiers_held = modifiers_held
7758 || (accept_partial_keystroke.modifiers() == modifiers
7759 && accept_partial_keystroke.modifiers().modified());
7760 }
7761
7762 if modifiers_held {
7763 if matches!(
7764 self.edit_prediction_preview,
7765 EditPredictionPreview::Inactive { .. }
7766 ) {
7767 self.edit_prediction_preview = EditPredictionPreview::Active {
7768 previous_scroll_position: None,
7769 since: Instant::now(),
7770 };
7771
7772 self.update_visible_edit_prediction(window, cx);
7773 cx.notify();
7774 }
7775 } else if let EditPredictionPreview::Active {
7776 previous_scroll_position,
7777 since,
7778 } = self.edit_prediction_preview
7779 {
7780 if let (Some(previous_scroll_position), Some(position_map)) =
7781 (previous_scroll_position, self.last_position_map.as_ref())
7782 {
7783 self.set_scroll_position(
7784 previous_scroll_position
7785 .scroll_position(&position_map.snapshot.display_snapshot),
7786 window,
7787 cx,
7788 );
7789 }
7790
7791 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7792 released_too_fast: since.elapsed() < Duration::from_millis(200),
7793 };
7794 self.clear_row_highlights::<EditPredictionPreview>();
7795 self.update_visible_edit_prediction(window, cx);
7796 cx.notify();
7797 }
7798 }
7799
7800 fn update_visible_edit_prediction(
7801 &mut self,
7802 _window: &mut Window,
7803 cx: &mut Context<Self>,
7804 ) -> Option<()> {
7805 if DisableAiSettings::get_global(cx).disable_ai {
7806 return None;
7807 }
7808
7809 if self.ime_transaction.is_some() {
7810 self.discard_edit_prediction(false, cx);
7811 return None;
7812 }
7813
7814 let selection = self.selections.newest_anchor();
7815 let cursor = selection.head();
7816 let multibuffer = self.buffer.read(cx).snapshot(cx);
7817 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7818 let excerpt_id = cursor.excerpt_id;
7819
7820 let show_in_menu = self.show_edit_predictions_in_menu();
7821 let completions_menu_has_precedence = !show_in_menu
7822 && (self.context_menu.borrow().is_some()
7823 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7824
7825 if completions_menu_has_precedence
7826 || !offset_selection.is_empty()
7827 || self
7828 .active_edit_prediction
7829 .as_ref()
7830 .is_some_and(|completion| {
7831 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7832 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7833 !invalidation_range.contains(&offset_selection.head())
7834 })
7835 {
7836 self.discard_edit_prediction(false, cx);
7837 return None;
7838 }
7839
7840 self.take_active_edit_prediction(cx);
7841 let Some(provider) = self.edit_prediction_provider() else {
7842 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7843 return None;
7844 };
7845
7846 let (buffer, cursor_buffer_position) =
7847 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7848
7849 self.edit_prediction_settings =
7850 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7851
7852 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7853 self.discard_edit_prediction(false, cx);
7854 return None;
7855 };
7856
7857 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7858
7859 if self.edit_prediction_indent_conflict {
7860 let cursor_point = cursor.to_point(&multibuffer);
7861
7862 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7863
7864 if let Some((_, indent)) = indents.iter().next()
7865 && indent.len == cursor_point.column
7866 {
7867 self.edit_prediction_indent_conflict = false;
7868 }
7869 }
7870
7871 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7872 let edits = edit_prediction
7873 .edits
7874 .into_iter()
7875 .flat_map(|(range, new_text)| {
7876 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7877 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7878 Some((start..end, new_text))
7879 })
7880 .collect::<Vec<_>>();
7881 if edits.is_empty() {
7882 return None;
7883 }
7884
7885 let first_edit_start = edits.first().unwrap().0.start;
7886 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7887 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7888
7889 let last_edit_end = edits.last().unwrap().0.end;
7890 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7891 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7892
7893 let cursor_row = cursor.to_point(&multibuffer).row;
7894
7895 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7896
7897 let mut inlay_ids = Vec::new();
7898 let invalidation_row_range;
7899 let move_invalidation_row_range = if cursor_row < edit_start_row {
7900 Some(cursor_row..edit_end_row)
7901 } else if cursor_row > edit_end_row {
7902 Some(edit_start_row..cursor_row)
7903 } else {
7904 None
7905 };
7906 let supports_jump = self
7907 .edit_prediction_provider
7908 .as_ref()
7909 .map(|provider| provider.provider.supports_jump_to_edit())
7910 .unwrap_or(true);
7911
7912 let is_move = supports_jump
7913 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7914 let completion = if is_move {
7915 invalidation_row_range =
7916 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7917 let target = first_edit_start;
7918 EditPrediction::Move { target, snapshot }
7919 } else {
7920 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7921 && !self.edit_predictions_hidden_for_vim_mode;
7922
7923 if show_completions_in_buffer {
7924 if edits
7925 .iter()
7926 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7927 {
7928 let mut inlays = Vec::new();
7929 for (range, new_text) in &edits {
7930 let inlay = Inlay::edit_prediction(
7931 post_inc(&mut self.next_inlay_id),
7932 range.start,
7933 new_text.as_str(),
7934 );
7935 inlay_ids.push(inlay.id);
7936 inlays.push(inlay);
7937 }
7938
7939 self.splice_inlays(&[], inlays, cx);
7940 } else {
7941 let background_color = cx.theme().status().deleted_background;
7942 self.highlight_text::<EditPredictionHighlight>(
7943 edits.iter().map(|(range, _)| range.clone()).collect(),
7944 HighlightStyle {
7945 background_color: Some(background_color),
7946 ..Default::default()
7947 },
7948 cx,
7949 );
7950 }
7951 }
7952
7953 invalidation_row_range = edit_start_row..edit_end_row;
7954
7955 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7956 if provider.show_tab_accept_marker() {
7957 EditDisplayMode::TabAccept
7958 } else {
7959 EditDisplayMode::Inline
7960 }
7961 } else {
7962 EditDisplayMode::DiffPopover
7963 };
7964
7965 EditPrediction::Edit {
7966 edits,
7967 edit_preview: edit_prediction.edit_preview,
7968 display_mode,
7969 snapshot,
7970 }
7971 };
7972
7973 let invalidation_range = multibuffer
7974 .anchor_before(Point::new(invalidation_row_range.start, 0))
7975 ..multibuffer.anchor_after(Point::new(
7976 invalidation_row_range.end,
7977 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7978 ));
7979
7980 self.stale_edit_prediction_in_menu = None;
7981 self.active_edit_prediction = Some(EditPredictionState {
7982 inlay_ids,
7983 completion,
7984 completion_id: edit_prediction.id,
7985 invalidation_range,
7986 });
7987
7988 cx.notify();
7989
7990 Some(())
7991 }
7992
7993 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7994 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7995 }
7996
7997 fn clear_tasks(&mut self) {
7998 self.tasks.clear()
7999 }
8000
8001 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8002 if self.tasks.insert(key, value).is_some() {
8003 // This case should hopefully be rare, but just in case...
8004 log::error!(
8005 "multiple different run targets found on a single line, only the last target will be rendered"
8006 )
8007 }
8008 }
8009
8010 /// Get all display points of breakpoints that will be rendered within editor
8011 ///
8012 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8013 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8014 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8015 fn active_breakpoints(
8016 &self,
8017 range: Range<DisplayRow>,
8018 window: &mut Window,
8019 cx: &mut Context<Self>,
8020 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8021 let mut breakpoint_display_points = HashMap::default();
8022
8023 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8024 return breakpoint_display_points;
8025 };
8026
8027 let snapshot = self.snapshot(window, cx);
8028
8029 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8030 let Some(project) = self.project() else {
8031 return breakpoint_display_points;
8032 };
8033
8034 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8035 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8036
8037 for (buffer_snapshot, range, excerpt_id) in
8038 multi_buffer_snapshot.range_to_buffer_ranges(range)
8039 {
8040 let Some(buffer) = project
8041 .read(cx)
8042 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8043 else {
8044 continue;
8045 };
8046 let breakpoints = breakpoint_store.read(cx).breakpoints(
8047 &buffer,
8048 Some(
8049 buffer_snapshot.anchor_before(range.start)
8050 ..buffer_snapshot.anchor_after(range.end),
8051 ),
8052 buffer_snapshot,
8053 cx,
8054 );
8055 for (breakpoint, state) in breakpoints {
8056 let multi_buffer_anchor =
8057 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8058 let position = multi_buffer_anchor
8059 .to_point(multi_buffer_snapshot)
8060 .to_display_point(&snapshot);
8061
8062 breakpoint_display_points.insert(
8063 position.row(),
8064 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8065 );
8066 }
8067 }
8068
8069 breakpoint_display_points
8070 }
8071
8072 fn breakpoint_context_menu(
8073 &self,
8074 anchor: Anchor,
8075 window: &mut Window,
8076 cx: &mut Context<Self>,
8077 ) -> Entity<ui::ContextMenu> {
8078 let weak_editor = cx.weak_entity();
8079 let focus_handle = self.focus_handle(cx);
8080
8081 let row = self
8082 .buffer
8083 .read(cx)
8084 .snapshot(cx)
8085 .summary_for_anchor::<Point>(&anchor)
8086 .row;
8087
8088 let breakpoint = self
8089 .breakpoint_at_row(row, window, cx)
8090 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8091
8092 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8093 "Edit Log Breakpoint"
8094 } else {
8095 "Set Log Breakpoint"
8096 };
8097
8098 let condition_breakpoint_msg = if breakpoint
8099 .as_ref()
8100 .is_some_and(|bp| bp.1.condition.is_some())
8101 {
8102 "Edit Condition Breakpoint"
8103 } else {
8104 "Set Condition Breakpoint"
8105 };
8106
8107 let hit_condition_breakpoint_msg = if breakpoint
8108 .as_ref()
8109 .is_some_and(|bp| bp.1.hit_condition.is_some())
8110 {
8111 "Edit Hit Condition Breakpoint"
8112 } else {
8113 "Set Hit Condition Breakpoint"
8114 };
8115
8116 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8117 "Unset Breakpoint"
8118 } else {
8119 "Set Breakpoint"
8120 };
8121
8122 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8123
8124 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8125 BreakpointState::Enabled => Some("Disable"),
8126 BreakpointState::Disabled => Some("Enable"),
8127 });
8128
8129 let (anchor, breakpoint) =
8130 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8131
8132 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8133 menu.on_blur_subscription(Subscription::new(|| {}))
8134 .context(focus_handle)
8135 .when(run_to_cursor, |this| {
8136 let weak_editor = weak_editor.clone();
8137 this.entry("Run to cursor", None, move |window, cx| {
8138 weak_editor
8139 .update(cx, |editor, cx| {
8140 editor.change_selections(
8141 SelectionEffects::no_scroll(),
8142 window,
8143 cx,
8144 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8145 );
8146 })
8147 .ok();
8148
8149 window.dispatch_action(Box::new(RunToCursor), cx);
8150 })
8151 .separator()
8152 })
8153 .when_some(toggle_state_msg, |this, msg| {
8154 this.entry(msg, None, {
8155 let weak_editor = weak_editor.clone();
8156 let breakpoint = breakpoint.clone();
8157 move |_window, cx| {
8158 weak_editor
8159 .update(cx, |this, cx| {
8160 this.edit_breakpoint_at_anchor(
8161 anchor,
8162 breakpoint.as_ref().clone(),
8163 BreakpointEditAction::InvertState,
8164 cx,
8165 );
8166 })
8167 .log_err();
8168 }
8169 })
8170 })
8171 .entry(set_breakpoint_msg, None, {
8172 let weak_editor = weak_editor.clone();
8173 let breakpoint = breakpoint.clone();
8174 move |_window, cx| {
8175 weak_editor
8176 .update(cx, |this, cx| {
8177 this.edit_breakpoint_at_anchor(
8178 anchor,
8179 breakpoint.as_ref().clone(),
8180 BreakpointEditAction::Toggle,
8181 cx,
8182 );
8183 })
8184 .log_err();
8185 }
8186 })
8187 .entry(log_breakpoint_msg, None, {
8188 let breakpoint = breakpoint.clone();
8189 let weak_editor = weak_editor.clone();
8190 move |window, cx| {
8191 weak_editor
8192 .update(cx, |this, cx| {
8193 this.add_edit_breakpoint_block(
8194 anchor,
8195 breakpoint.as_ref(),
8196 BreakpointPromptEditAction::Log,
8197 window,
8198 cx,
8199 );
8200 })
8201 .log_err();
8202 }
8203 })
8204 .entry(condition_breakpoint_msg, None, {
8205 let breakpoint = breakpoint.clone();
8206 let weak_editor = weak_editor.clone();
8207 move |window, cx| {
8208 weak_editor
8209 .update(cx, |this, cx| {
8210 this.add_edit_breakpoint_block(
8211 anchor,
8212 breakpoint.as_ref(),
8213 BreakpointPromptEditAction::Condition,
8214 window,
8215 cx,
8216 );
8217 })
8218 .log_err();
8219 }
8220 })
8221 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8222 weak_editor
8223 .update(cx, |this, cx| {
8224 this.add_edit_breakpoint_block(
8225 anchor,
8226 breakpoint.as_ref(),
8227 BreakpointPromptEditAction::HitCondition,
8228 window,
8229 cx,
8230 );
8231 })
8232 .log_err();
8233 })
8234 })
8235 }
8236
8237 fn render_breakpoint(
8238 &self,
8239 position: Anchor,
8240 row: DisplayRow,
8241 breakpoint: &Breakpoint,
8242 state: Option<BreakpointSessionState>,
8243 cx: &mut Context<Self>,
8244 ) -> IconButton {
8245 let is_rejected = state.is_some_and(|s| !s.verified);
8246 // Is it a breakpoint that shows up when hovering over gutter?
8247 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8248 (false, false),
8249 |PhantomBreakpointIndicator {
8250 is_active,
8251 display_row,
8252 collides_with_existing_breakpoint,
8253 }| {
8254 (
8255 is_active && display_row == row,
8256 collides_with_existing_breakpoint,
8257 )
8258 },
8259 );
8260
8261 let (color, icon) = {
8262 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8263 (false, false) => ui::IconName::DebugBreakpoint,
8264 (true, false) => ui::IconName::DebugLogBreakpoint,
8265 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8266 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8267 };
8268
8269 let color = if is_phantom {
8270 Color::Hint
8271 } else if is_rejected {
8272 Color::Disabled
8273 } else {
8274 Color::Debugger
8275 };
8276
8277 (color, icon)
8278 };
8279
8280 let breakpoint = Arc::from(breakpoint.clone());
8281
8282 let alt_as_text = gpui::Keystroke {
8283 modifiers: Modifiers::secondary_key(),
8284 ..Default::default()
8285 };
8286 let primary_action_text = if breakpoint.is_disabled() {
8287 "Enable breakpoint"
8288 } else if is_phantom && !collides_with_existing {
8289 "Set breakpoint"
8290 } else {
8291 "Unset breakpoint"
8292 };
8293 let focus_handle = self.focus_handle.clone();
8294
8295 let meta = if is_rejected {
8296 SharedString::from("No executable code is associated with this line.")
8297 } else if collides_with_existing && !breakpoint.is_disabled() {
8298 SharedString::from(format!(
8299 "{alt_as_text}-click to disable,\nright-click for more options."
8300 ))
8301 } else {
8302 SharedString::from("Right-click for more options.")
8303 };
8304 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8305 .icon_size(IconSize::XSmall)
8306 .size(ui::ButtonSize::None)
8307 .when(is_rejected, |this| {
8308 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8309 })
8310 .icon_color(color)
8311 .style(ButtonStyle::Transparent)
8312 .on_click(cx.listener({
8313 move |editor, event: &ClickEvent, window, cx| {
8314 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8315 BreakpointEditAction::InvertState
8316 } else {
8317 BreakpointEditAction::Toggle
8318 };
8319
8320 window.focus(&editor.focus_handle(cx));
8321 editor.edit_breakpoint_at_anchor(
8322 position,
8323 breakpoint.as_ref().clone(),
8324 edit_action,
8325 cx,
8326 );
8327 }
8328 }))
8329 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8330 editor.set_breakpoint_context_menu(
8331 row,
8332 Some(position),
8333 event.position(),
8334 window,
8335 cx,
8336 );
8337 }))
8338 .tooltip(move |window, cx| {
8339 Tooltip::with_meta_in(
8340 primary_action_text,
8341 Some(&ToggleBreakpoint),
8342 meta.clone(),
8343 &focus_handle,
8344 window,
8345 cx,
8346 )
8347 })
8348 }
8349
8350 fn build_tasks_context(
8351 project: &Entity<Project>,
8352 buffer: &Entity<Buffer>,
8353 buffer_row: u32,
8354 tasks: &Arc<RunnableTasks>,
8355 cx: &mut Context<Self>,
8356 ) -> Task<Option<task::TaskContext>> {
8357 let position = Point::new(buffer_row, tasks.column);
8358 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8359 let location = Location {
8360 buffer: buffer.clone(),
8361 range: range_start..range_start,
8362 };
8363 // Fill in the environmental variables from the tree-sitter captures
8364 let mut captured_task_variables = TaskVariables::default();
8365 for (capture_name, value) in tasks.extra_variables.clone() {
8366 captured_task_variables.insert(
8367 task::VariableName::Custom(capture_name.into()),
8368 value.clone(),
8369 );
8370 }
8371 project.update(cx, |project, cx| {
8372 project.task_store().update(cx, |task_store, cx| {
8373 task_store.task_context_for_location(captured_task_variables, location, cx)
8374 })
8375 })
8376 }
8377
8378 pub fn spawn_nearest_task(
8379 &mut self,
8380 action: &SpawnNearestTask,
8381 window: &mut Window,
8382 cx: &mut Context<Self>,
8383 ) {
8384 let Some((workspace, _)) = self.workspace.clone() else {
8385 return;
8386 };
8387 let Some(project) = self.project.clone() else {
8388 return;
8389 };
8390
8391 // Try to find a closest, enclosing node using tree-sitter that has a task
8392 let Some((buffer, buffer_row, tasks)) = self
8393 .find_enclosing_node_task(cx)
8394 // Or find the task that's closest in row-distance.
8395 .or_else(|| self.find_closest_task(cx))
8396 else {
8397 return;
8398 };
8399
8400 let reveal_strategy = action.reveal;
8401 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8402 cx.spawn_in(window, async move |_, cx| {
8403 let context = task_context.await?;
8404 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8405
8406 let resolved = &mut resolved_task.resolved;
8407 resolved.reveal = reveal_strategy;
8408
8409 workspace
8410 .update_in(cx, |workspace, window, cx| {
8411 workspace.schedule_resolved_task(
8412 task_source_kind,
8413 resolved_task,
8414 false,
8415 window,
8416 cx,
8417 );
8418 })
8419 .ok()
8420 })
8421 .detach();
8422 }
8423
8424 fn find_closest_task(
8425 &mut self,
8426 cx: &mut Context<Self>,
8427 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8428 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8429
8430 let ((buffer_id, row), tasks) = self
8431 .tasks
8432 .iter()
8433 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8434
8435 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8436 let tasks = Arc::new(tasks.to_owned());
8437 Some((buffer, *row, tasks))
8438 }
8439
8440 fn find_enclosing_node_task(
8441 &mut self,
8442 cx: &mut Context<Self>,
8443 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8444 let snapshot = self.buffer.read(cx).snapshot(cx);
8445 let offset = self.selections.newest::<usize>(cx).head();
8446 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8447 let buffer_id = excerpt.buffer().remote_id();
8448
8449 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8450 let mut cursor = layer.node().walk();
8451
8452 while cursor.goto_first_child_for_byte(offset).is_some() {
8453 if cursor.node().end_byte() == offset {
8454 cursor.goto_next_sibling();
8455 }
8456 }
8457
8458 // Ascend to the smallest ancestor that contains the range and has a task.
8459 loop {
8460 let node = cursor.node();
8461 let node_range = node.byte_range();
8462 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8463
8464 // Check if this node contains our offset
8465 if node_range.start <= offset && node_range.end >= offset {
8466 // If it contains offset, check for task
8467 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8468 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8469 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8470 }
8471 }
8472
8473 if !cursor.goto_parent() {
8474 break;
8475 }
8476 }
8477 None
8478 }
8479
8480 fn render_run_indicator(
8481 &self,
8482 _style: &EditorStyle,
8483 is_active: bool,
8484 row: DisplayRow,
8485 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8486 cx: &mut Context<Self>,
8487 ) -> IconButton {
8488 let color = Color::Muted;
8489 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8490
8491 IconButton::new(
8492 ("run_indicator", row.0 as usize),
8493 ui::IconName::PlayOutlined,
8494 )
8495 .shape(ui::IconButtonShape::Square)
8496 .icon_size(IconSize::XSmall)
8497 .icon_color(color)
8498 .toggle_state(is_active)
8499 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8500 let quick_launch = match e {
8501 ClickEvent::Keyboard(_) => true,
8502 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8503 };
8504
8505 window.focus(&editor.focus_handle(cx));
8506 editor.toggle_code_actions(
8507 &ToggleCodeActions {
8508 deployed_from: Some(CodeActionSource::RunMenu(row)),
8509 quick_launch,
8510 },
8511 window,
8512 cx,
8513 );
8514 }))
8515 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8516 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8517 }))
8518 }
8519
8520 pub fn context_menu_visible(&self) -> bool {
8521 !self.edit_prediction_preview_is_active()
8522 && self
8523 .context_menu
8524 .borrow()
8525 .as_ref()
8526 .is_some_and(|menu| menu.visible())
8527 }
8528
8529 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8530 self.context_menu
8531 .borrow()
8532 .as_ref()
8533 .map(|menu| menu.origin())
8534 }
8535
8536 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8537 self.context_menu_options = Some(options);
8538 }
8539
8540 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8541 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8542
8543 fn render_edit_prediction_popover(
8544 &mut self,
8545 text_bounds: &Bounds<Pixels>,
8546 content_origin: gpui::Point<Pixels>,
8547 right_margin: Pixels,
8548 editor_snapshot: &EditorSnapshot,
8549 visible_row_range: Range<DisplayRow>,
8550 scroll_top: f32,
8551 scroll_bottom: f32,
8552 line_layouts: &[LineWithInvisibles],
8553 line_height: Pixels,
8554 scroll_pixel_position: gpui::Point<Pixels>,
8555 newest_selection_head: Option<DisplayPoint>,
8556 editor_width: Pixels,
8557 style: &EditorStyle,
8558 window: &mut Window,
8559 cx: &mut App,
8560 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8561 if self.mode().is_minimap() {
8562 return None;
8563 }
8564 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8565
8566 if self.edit_prediction_visible_in_cursor_popover(true) {
8567 return None;
8568 }
8569
8570 match &active_edit_prediction.completion {
8571 EditPrediction::Move { target, .. } => {
8572 let target_display_point = target.to_display_point(editor_snapshot);
8573
8574 if self.edit_prediction_requires_modifier() {
8575 if !self.edit_prediction_preview_is_active() {
8576 return None;
8577 }
8578
8579 self.render_edit_prediction_modifier_jump_popover(
8580 text_bounds,
8581 content_origin,
8582 visible_row_range,
8583 line_layouts,
8584 line_height,
8585 scroll_pixel_position,
8586 newest_selection_head,
8587 target_display_point,
8588 window,
8589 cx,
8590 )
8591 } else {
8592 self.render_edit_prediction_eager_jump_popover(
8593 text_bounds,
8594 content_origin,
8595 editor_snapshot,
8596 visible_row_range,
8597 scroll_top,
8598 scroll_bottom,
8599 line_height,
8600 scroll_pixel_position,
8601 target_display_point,
8602 editor_width,
8603 window,
8604 cx,
8605 )
8606 }
8607 }
8608 EditPrediction::Edit {
8609 display_mode: EditDisplayMode::Inline,
8610 ..
8611 } => None,
8612 EditPrediction::Edit {
8613 display_mode: EditDisplayMode::TabAccept,
8614 edits,
8615 ..
8616 } => {
8617 let range = &edits.first()?.0;
8618 let target_display_point = range.end.to_display_point(editor_snapshot);
8619
8620 self.render_edit_prediction_end_of_line_popover(
8621 "Accept",
8622 editor_snapshot,
8623 visible_row_range,
8624 target_display_point,
8625 line_height,
8626 scroll_pixel_position,
8627 content_origin,
8628 editor_width,
8629 window,
8630 cx,
8631 )
8632 }
8633 EditPrediction::Edit {
8634 edits,
8635 edit_preview,
8636 display_mode: EditDisplayMode::DiffPopover,
8637 snapshot,
8638 } => self.render_edit_prediction_diff_popover(
8639 text_bounds,
8640 content_origin,
8641 right_margin,
8642 editor_snapshot,
8643 visible_row_range,
8644 line_layouts,
8645 line_height,
8646 scroll_pixel_position,
8647 newest_selection_head,
8648 editor_width,
8649 style,
8650 edits,
8651 edit_preview,
8652 snapshot,
8653 window,
8654 cx,
8655 ),
8656 }
8657 }
8658
8659 fn render_edit_prediction_modifier_jump_popover(
8660 &mut self,
8661 text_bounds: &Bounds<Pixels>,
8662 content_origin: gpui::Point<Pixels>,
8663 visible_row_range: Range<DisplayRow>,
8664 line_layouts: &[LineWithInvisibles],
8665 line_height: Pixels,
8666 scroll_pixel_position: gpui::Point<Pixels>,
8667 newest_selection_head: Option<DisplayPoint>,
8668 target_display_point: DisplayPoint,
8669 window: &mut Window,
8670 cx: &mut App,
8671 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8672 let scrolled_content_origin =
8673 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8674
8675 const SCROLL_PADDING_Y: Pixels = px(12.);
8676
8677 if target_display_point.row() < visible_row_range.start {
8678 return self.render_edit_prediction_scroll_popover(
8679 |_| SCROLL_PADDING_Y,
8680 IconName::ArrowUp,
8681 visible_row_range,
8682 line_layouts,
8683 newest_selection_head,
8684 scrolled_content_origin,
8685 window,
8686 cx,
8687 );
8688 } else if target_display_point.row() >= visible_row_range.end {
8689 return self.render_edit_prediction_scroll_popover(
8690 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8691 IconName::ArrowDown,
8692 visible_row_range,
8693 line_layouts,
8694 newest_selection_head,
8695 scrolled_content_origin,
8696 window,
8697 cx,
8698 );
8699 }
8700
8701 const POLE_WIDTH: Pixels = px(2.);
8702
8703 let line_layout =
8704 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8705 let target_column = target_display_point.column() as usize;
8706
8707 let target_x = line_layout.x_for_index(target_column);
8708 let target_y =
8709 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8710
8711 let flag_on_right = target_x < text_bounds.size.width / 2.;
8712
8713 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8714 border_color.l += 0.001;
8715
8716 let mut element = v_flex()
8717 .items_end()
8718 .when(flag_on_right, |el| el.items_start())
8719 .child(if flag_on_right {
8720 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8721 .rounded_bl(px(0.))
8722 .rounded_tl(px(0.))
8723 .border_l_2()
8724 .border_color(border_color)
8725 } else {
8726 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8727 .rounded_br(px(0.))
8728 .rounded_tr(px(0.))
8729 .border_r_2()
8730 .border_color(border_color)
8731 })
8732 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8733 .into_any();
8734
8735 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8736
8737 let mut origin = scrolled_content_origin + point(target_x, target_y)
8738 - point(
8739 if flag_on_right {
8740 POLE_WIDTH
8741 } else {
8742 size.width - POLE_WIDTH
8743 },
8744 size.height - line_height,
8745 );
8746
8747 origin.x = origin.x.max(content_origin.x);
8748
8749 element.prepaint_at(origin, window, cx);
8750
8751 Some((element, origin))
8752 }
8753
8754 fn render_edit_prediction_scroll_popover(
8755 &mut self,
8756 to_y: impl Fn(Size<Pixels>) -> Pixels,
8757 scroll_icon: IconName,
8758 visible_row_range: Range<DisplayRow>,
8759 line_layouts: &[LineWithInvisibles],
8760 newest_selection_head: Option<DisplayPoint>,
8761 scrolled_content_origin: gpui::Point<Pixels>,
8762 window: &mut Window,
8763 cx: &mut App,
8764 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8765 let mut element = self
8766 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8767 .into_any();
8768
8769 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8770
8771 let cursor = newest_selection_head?;
8772 let cursor_row_layout =
8773 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8774 let cursor_column = cursor.column() as usize;
8775
8776 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8777
8778 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8779
8780 element.prepaint_at(origin, window, cx);
8781 Some((element, origin))
8782 }
8783
8784 fn render_edit_prediction_eager_jump_popover(
8785 &mut self,
8786 text_bounds: &Bounds<Pixels>,
8787 content_origin: gpui::Point<Pixels>,
8788 editor_snapshot: &EditorSnapshot,
8789 visible_row_range: Range<DisplayRow>,
8790 scroll_top: f32,
8791 scroll_bottom: f32,
8792 line_height: Pixels,
8793 scroll_pixel_position: gpui::Point<Pixels>,
8794 target_display_point: DisplayPoint,
8795 editor_width: Pixels,
8796 window: &mut Window,
8797 cx: &mut App,
8798 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8799 if target_display_point.row().as_f32() < scroll_top {
8800 let mut element = self
8801 .render_edit_prediction_line_popover(
8802 "Jump to Edit",
8803 Some(IconName::ArrowUp),
8804 window,
8805 cx,
8806 )?
8807 .into_any();
8808
8809 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8810 let offset = point(
8811 (text_bounds.size.width - size.width) / 2.,
8812 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8813 );
8814
8815 let origin = text_bounds.origin + offset;
8816 element.prepaint_at(origin, window, cx);
8817 Some((element, origin))
8818 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8819 let mut element = self
8820 .render_edit_prediction_line_popover(
8821 "Jump to Edit",
8822 Some(IconName::ArrowDown),
8823 window,
8824 cx,
8825 )?
8826 .into_any();
8827
8828 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8829 let offset = point(
8830 (text_bounds.size.width - size.width) / 2.,
8831 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8832 );
8833
8834 let origin = text_bounds.origin + offset;
8835 element.prepaint_at(origin, window, cx);
8836 Some((element, origin))
8837 } else {
8838 self.render_edit_prediction_end_of_line_popover(
8839 "Jump to Edit",
8840 editor_snapshot,
8841 visible_row_range,
8842 target_display_point,
8843 line_height,
8844 scroll_pixel_position,
8845 content_origin,
8846 editor_width,
8847 window,
8848 cx,
8849 )
8850 }
8851 }
8852
8853 fn render_edit_prediction_end_of_line_popover(
8854 self: &mut Editor,
8855 label: &'static str,
8856 editor_snapshot: &EditorSnapshot,
8857 visible_row_range: Range<DisplayRow>,
8858 target_display_point: DisplayPoint,
8859 line_height: Pixels,
8860 scroll_pixel_position: gpui::Point<Pixels>,
8861 content_origin: gpui::Point<Pixels>,
8862 editor_width: Pixels,
8863 window: &mut Window,
8864 cx: &mut App,
8865 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8866 let target_line_end = DisplayPoint::new(
8867 target_display_point.row(),
8868 editor_snapshot.line_len(target_display_point.row()),
8869 );
8870
8871 let mut element = self
8872 .render_edit_prediction_line_popover(label, None, window, cx)?
8873 .into_any();
8874
8875 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8876
8877 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8878
8879 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8880 let mut origin = start_point
8881 + line_origin
8882 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8883 origin.x = origin.x.max(content_origin.x);
8884
8885 let max_x = content_origin.x + editor_width - size.width;
8886
8887 if origin.x > max_x {
8888 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8889
8890 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8891 origin.y += offset;
8892 IconName::ArrowUp
8893 } else {
8894 origin.y -= offset;
8895 IconName::ArrowDown
8896 };
8897
8898 element = self
8899 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8900 .into_any();
8901
8902 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8903
8904 origin.x = content_origin.x + editor_width - size.width - px(2.);
8905 }
8906
8907 element.prepaint_at(origin, window, cx);
8908 Some((element, origin))
8909 }
8910
8911 fn render_edit_prediction_diff_popover(
8912 self: &Editor,
8913 text_bounds: &Bounds<Pixels>,
8914 content_origin: gpui::Point<Pixels>,
8915 right_margin: Pixels,
8916 editor_snapshot: &EditorSnapshot,
8917 visible_row_range: Range<DisplayRow>,
8918 line_layouts: &[LineWithInvisibles],
8919 line_height: Pixels,
8920 scroll_pixel_position: gpui::Point<Pixels>,
8921 newest_selection_head: Option<DisplayPoint>,
8922 editor_width: Pixels,
8923 style: &EditorStyle,
8924 edits: &Vec<(Range<Anchor>, String)>,
8925 edit_preview: &Option<language::EditPreview>,
8926 snapshot: &language::BufferSnapshot,
8927 window: &mut Window,
8928 cx: &mut App,
8929 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8930 let edit_start = edits
8931 .first()
8932 .unwrap()
8933 .0
8934 .start
8935 .to_display_point(editor_snapshot);
8936 let edit_end = edits
8937 .last()
8938 .unwrap()
8939 .0
8940 .end
8941 .to_display_point(editor_snapshot);
8942
8943 let is_visible = visible_row_range.contains(&edit_start.row())
8944 || visible_row_range.contains(&edit_end.row());
8945 if !is_visible {
8946 return None;
8947 }
8948
8949 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8950 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8951 } else {
8952 // Fallback for providers without edit_preview
8953 crate::edit_prediction_fallback_text(edits, cx)
8954 };
8955
8956 let styled_text = highlighted_edits.to_styled_text(&style.text);
8957 let line_count = highlighted_edits.text.lines().count();
8958
8959 const BORDER_WIDTH: Pixels = px(1.);
8960
8961 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8962 let has_keybind = keybind.is_some();
8963
8964 let mut element = h_flex()
8965 .items_start()
8966 .child(
8967 h_flex()
8968 .bg(cx.theme().colors().editor_background)
8969 .border(BORDER_WIDTH)
8970 .shadow_xs()
8971 .border_color(cx.theme().colors().border)
8972 .rounded_l_lg()
8973 .when(line_count > 1, |el| el.rounded_br_lg())
8974 .pr_1()
8975 .child(styled_text),
8976 )
8977 .child(
8978 h_flex()
8979 .h(line_height + BORDER_WIDTH * 2.)
8980 .px_1p5()
8981 .gap_1()
8982 // Workaround: For some reason, there's a gap if we don't do this
8983 .ml(-BORDER_WIDTH)
8984 .shadow(vec![gpui::BoxShadow {
8985 color: gpui::black().opacity(0.05),
8986 offset: point(px(1.), px(1.)),
8987 blur_radius: px(2.),
8988 spread_radius: px(0.),
8989 }])
8990 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8991 .border(BORDER_WIDTH)
8992 .border_color(cx.theme().colors().border)
8993 .rounded_r_lg()
8994 .id("edit_prediction_diff_popover_keybind")
8995 .when(!has_keybind, |el| {
8996 let status_colors = cx.theme().status();
8997
8998 el.bg(status_colors.error_background)
8999 .border_color(status_colors.error.opacity(0.6))
9000 .child(Icon::new(IconName::Info).color(Color::Error))
9001 .cursor_default()
9002 .hoverable_tooltip(move |_window, cx| {
9003 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9004 })
9005 })
9006 .children(keybind),
9007 )
9008 .into_any();
9009
9010 let longest_row =
9011 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9012 let longest_line_width = if visible_row_range.contains(&longest_row) {
9013 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9014 } else {
9015 layout_line(
9016 longest_row,
9017 editor_snapshot,
9018 style,
9019 editor_width,
9020 |_| false,
9021 window,
9022 cx,
9023 )
9024 .width
9025 };
9026
9027 let viewport_bounds =
9028 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9029 right: -right_margin,
9030 ..Default::default()
9031 });
9032
9033 let x_after_longest =
9034 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9035 - scroll_pixel_position.x;
9036
9037 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9038
9039 // Fully visible if it can be displayed within the window (allow overlapping other
9040 // panes). However, this is only allowed if the popover starts within text_bounds.
9041 let can_position_to_the_right = x_after_longest < text_bounds.right()
9042 && x_after_longest + element_bounds.width < viewport_bounds.right();
9043
9044 let mut origin = if can_position_to_the_right {
9045 point(
9046 x_after_longest,
9047 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9048 - scroll_pixel_position.y,
9049 )
9050 } else {
9051 let cursor_row = newest_selection_head.map(|head| head.row());
9052 let above_edit = edit_start
9053 .row()
9054 .0
9055 .checked_sub(line_count as u32)
9056 .map(DisplayRow);
9057 let below_edit = Some(edit_end.row() + 1);
9058 let above_cursor =
9059 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9060 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9061
9062 // Place the edit popover adjacent to the edit if there is a location
9063 // available that is onscreen and does not obscure the cursor. Otherwise,
9064 // place it adjacent to the cursor.
9065 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9066 .into_iter()
9067 .flatten()
9068 .find(|&start_row| {
9069 let end_row = start_row + line_count as u32;
9070 visible_row_range.contains(&start_row)
9071 && visible_row_range.contains(&end_row)
9072 && cursor_row
9073 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9074 })?;
9075
9076 content_origin
9077 + point(
9078 -scroll_pixel_position.x,
9079 row_target.as_f32() * line_height - scroll_pixel_position.y,
9080 )
9081 };
9082
9083 origin.x -= BORDER_WIDTH;
9084
9085 window.defer_draw(element, origin, 1);
9086
9087 // Do not return an element, since it will already be drawn due to defer_draw.
9088 None
9089 }
9090
9091 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9092 px(30.)
9093 }
9094
9095 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9096 if self.read_only(cx) {
9097 cx.theme().players().read_only()
9098 } else {
9099 self.style.as_ref().unwrap().local_player
9100 }
9101 }
9102
9103 fn render_edit_prediction_accept_keybind(
9104 &self,
9105 window: &mut Window,
9106 cx: &App,
9107 ) -> Option<AnyElement> {
9108 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9109 let accept_keystroke = accept_binding.keystroke()?;
9110
9111 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9112
9113 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9114 Color::Accent
9115 } else {
9116 Color::Muted
9117 };
9118
9119 h_flex()
9120 .px_0p5()
9121 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9122 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9123 .text_size(TextSize::XSmall.rems(cx))
9124 .child(h_flex().children(ui::render_modifiers(
9125 accept_keystroke.modifiers(),
9126 PlatformStyle::platform(),
9127 Some(modifiers_color),
9128 Some(IconSize::XSmall.rems().into()),
9129 true,
9130 )))
9131 .when(is_platform_style_mac, |parent| {
9132 parent.child(accept_keystroke.key().to_string())
9133 })
9134 .when(!is_platform_style_mac, |parent| {
9135 parent.child(
9136 Key::new(
9137 util::capitalize(accept_keystroke.key()),
9138 Some(Color::Default),
9139 )
9140 .size(Some(IconSize::XSmall.rems().into())),
9141 )
9142 })
9143 .into_any()
9144 .into()
9145 }
9146
9147 fn render_edit_prediction_line_popover(
9148 &self,
9149 label: impl Into<SharedString>,
9150 icon: Option<IconName>,
9151 window: &mut Window,
9152 cx: &App,
9153 ) -> Option<Stateful<Div>> {
9154 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9155
9156 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9157 let has_keybind = keybind.is_some();
9158
9159 let result = h_flex()
9160 .id("ep-line-popover")
9161 .py_0p5()
9162 .pl_1()
9163 .pr(padding_right)
9164 .gap_1()
9165 .rounded_md()
9166 .border_1()
9167 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9168 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9169 .shadow_xs()
9170 .when(!has_keybind, |el| {
9171 let status_colors = cx.theme().status();
9172
9173 el.bg(status_colors.error_background)
9174 .border_color(status_colors.error.opacity(0.6))
9175 .pl_2()
9176 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9177 .cursor_default()
9178 .hoverable_tooltip(move |_window, cx| {
9179 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9180 })
9181 })
9182 .children(keybind)
9183 .child(
9184 Label::new(label)
9185 .size(LabelSize::Small)
9186 .when(!has_keybind, |el| {
9187 el.color(cx.theme().status().error.into()).strikethrough()
9188 }),
9189 )
9190 .when(!has_keybind, |el| {
9191 el.child(
9192 h_flex().ml_1().child(
9193 Icon::new(IconName::Info)
9194 .size(IconSize::Small)
9195 .color(cx.theme().status().error.into()),
9196 ),
9197 )
9198 })
9199 .when_some(icon, |element, icon| {
9200 element.child(
9201 div()
9202 .mt(px(1.5))
9203 .child(Icon::new(icon).size(IconSize::Small)),
9204 )
9205 });
9206
9207 Some(result)
9208 }
9209
9210 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9211 let accent_color = cx.theme().colors().text_accent;
9212 let editor_bg_color = cx.theme().colors().editor_background;
9213 editor_bg_color.blend(accent_color.opacity(0.1))
9214 }
9215
9216 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9217 let accent_color = cx.theme().colors().text_accent;
9218 let editor_bg_color = cx.theme().colors().editor_background;
9219 editor_bg_color.blend(accent_color.opacity(0.6))
9220 }
9221 fn get_prediction_provider_icon_name(
9222 provider: &Option<RegisteredEditPredictionProvider>,
9223 ) -> IconName {
9224 match provider {
9225 Some(provider) => match provider.provider.name() {
9226 "copilot" => IconName::Copilot,
9227 "supermaven" => IconName::Supermaven,
9228 _ => IconName::ZedPredict,
9229 },
9230 None => IconName::ZedPredict,
9231 }
9232 }
9233
9234 fn render_edit_prediction_cursor_popover(
9235 &self,
9236 min_width: Pixels,
9237 max_width: Pixels,
9238 cursor_point: Point,
9239 style: &EditorStyle,
9240 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9241 _window: &Window,
9242 cx: &mut Context<Editor>,
9243 ) -> Option<AnyElement> {
9244 let provider = self.edit_prediction_provider.as_ref()?;
9245 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9246
9247 let is_refreshing = provider.provider.is_refreshing(cx);
9248
9249 fn pending_completion_container(icon: IconName) -> Div {
9250 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9251 }
9252
9253 let completion = match &self.active_edit_prediction {
9254 Some(prediction) => {
9255 if !self.has_visible_completions_menu() {
9256 const RADIUS: Pixels = px(6.);
9257 const BORDER_WIDTH: Pixels = px(1.);
9258
9259 return Some(
9260 h_flex()
9261 .elevation_2(cx)
9262 .border(BORDER_WIDTH)
9263 .border_color(cx.theme().colors().border)
9264 .when(accept_keystroke.is_none(), |el| {
9265 el.border_color(cx.theme().status().error)
9266 })
9267 .rounded(RADIUS)
9268 .rounded_tl(px(0.))
9269 .overflow_hidden()
9270 .child(div().px_1p5().child(match &prediction.completion {
9271 EditPrediction::Move { target, snapshot } => {
9272 use text::ToPoint as _;
9273 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9274 {
9275 Icon::new(IconName::ZedPredictDown)
9276 } else {
9277 Icon::new(IconName::ZedPredictUp)
9278 }
9279 }
9280 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9281 }))
9282 .child(
9283 h_flex()
9284 .gap_1()
9285 .py_1()
9286 .px_2()
9287 .rounded_r(RADIUS - BORDER_WIDTH)
9288 .border_l_1()
9289 .border_color(cx.theme().colors().border)
9290 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9291 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9292 el.child(
9293 Label::new("Hold")
9294 .size(LabelSize::Small)
9295 .when(accept_keystroke.is_none(), |el| {
9296 el.strikethrough()
9297 })
9298 .line_height_style(LineHeightStyle::UiLabel),
9299 )
9300 })
9301 .id("edit_prediction_cursor_popover_keybind")
9302 .when(accept_keystroke.is_none(), |el| {
9303 let status_colors = cx.theme().status();
9304
9305 el.bg(status_colors.error_background)
9306 .border_color(status_colors.error.opacity(0.6))
9307 .child(Icon::new(IconName::Info).color(Color::Error))
9308 .cursor_default()
9309 .hoverable_tooltip(move |_window, cx| {
9310 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9311 .into()
9312 })
9313 })
9314 .when_some(
9315 accept_keystroke.as_ref(),
9316 |el, accept_keystroke| {
9317 el.child(h_flex().children(ui::render_modifiers(
9318 accept_keystroke.modifiers(),
9319 PlatformStyle::platform(),
9320 Some(Color::Default),
9321 Some(IconSize::XSmall.rems().into()),
9322 false,
9323 )))
9324 },
9325 ),
9326 )
9327 .into_any(),
9328 );
9329 }
9330
9331 self.render_edit_prediction_cursor_popover_preview(
9332 prediction,
9333 cursor_point,
9334 style,
9335 cx,
9336 )?
9337 }
9338
9339 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9340 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9341 stale_completion,
9342 cursor_point,
9343 style,
9344 cx,
9345 )?,
9346
9347 None => pending_completion_container(provider_icon)
9348 .child(Label::new("...").size(LabelSize::Small)),
9349 },
9350
9351 None => pending_completion_container(provider_icon)
9352 .child(Label::new("...").size(LabelSize::Small)),
9353 };
9354
9355 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9356 completion
9357 .with_animation(
9358 "loading-completion",
9359 Animation::new(Duration::from_secs(2))
9360 .repeat()
9361 .with_easing(pulsating_between(0.4, 0.8)),
9362 |label, delta| label.opacity(delta),
9363 )
9364 .into_any_element()
9365 } else {
9366 completion.into_any_element()
9367 };
9368
9369 let has_completion = self.active_edit_prediction.is_some();
9370
9371 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9372 Some(
9373 h_flex()
9374 .min_w(min_width)
9375 .max_w(max_width)
9376 .flex_1()
9377 .elevation_2(cx)
9378 .border_color(cx.theme().colors().border)
9379 .child(
9380 div()
9381 .flex_1()
9382 .py_1()
9383 .px_2()
9384 .overflow_hidden()
9385 .child(completion),
9386 )
9387 .when_some(accept_keystroke, |el, accept_keystroke| {
9388 if !accept_keystroke.modifiers().modified() {
9389 return el;
9390 }
9391
9392 el.child(
9393 h_flex()
9394 .h_full()
9395 .border_l_1()
9396 .rounded_r_lg()
9397 .border_color(cx.theme().colors().border)
9398 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9399 .gap_1()
9400 .py_1()
9401 .px_2()
9402 .child(
9403 h_flex()
9404 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9405 .when(is_platform_style_mac, |parent| parent.gap_1())
9406 .child(h_flex().children(ui::render_modifiers(
9407 accept_keystroke.modifiers(),
9408 PlatformStyle::platform(),
9409 Some(if !has_completion {
9410 Color::Muted
9411 } else {
9412 Color::Default
9413 }),
9414 None,
9415 false,
9416 ))),
9417 )
9418 .child(Label::new("Preview").into_any_element())
9419 .opacity(if has_completion { 1.0 } else { 0.4 }),
9420 )
9421 })
9422 .into_any(),
9423 )
9424 }
9425
9426 fn render_edit_prediction_cursor_popover_preview(
9427 &self,
9428 completion: &EditPredictionState,
9429 cursor_point: Point,
9430 style: &EditorStyle,
9431 cx: &mut Context<Editor>,
9432 ) -> Option<Div> {
9433 use text::ToPoint as _;
9434
9435 fn render_relative_row_jump(
9436 prefix: impl Into<String>,
9437 current_row: u32,
9438 target_row: u32,
9439 ) -> Div {
9440 let (row_diff, arrow) = if target_row < current_row {
9441 (current_row - target_row, IconName::ArrowUp)
9442 } else {
9443 (target_row - current_row, IconName::ArrowDown)
9444 };
9445
9446 h_flex()
9447 .child(
9448 Label::new(format!("{}{}", prefix.into(), row_diff))
9449 .color(Color::Muted)
9450 .size(LabelSize::Small),
9451 )
9452 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9453 }
9454
9455 let supports_jump = self
9456 .edit_prediction_provider
9457 .as_ref()
9458 .map(|provider| provider.provider.supports_jump_to_edit())
9459 .unwrap_or(true);
9460
9461 match &completion.completion {
9462 EditPrediction::Move {
9463 target, snapshot, ..
9464 } => {
9465 if !supports_jump {
9466 return None;
9467 }
9468
9469 Some(
9470 h_flex()
9471 .px_2()
9472 .gap_2()
9473 .flex_1()
9474 .child(
9475 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9476 Icon::new(IconName::ZedPredictDown)
9477 } else {
9478 Icon::new(IconName::ZedPredictUp)
9479 },
9480 )
9481 .child(Label::new("Jump to Edit")),
9482 )
9483 }
9484
9485 EditPrediction::Edit {
9486 edits,
9487 edit_preview,
9488 snapshot,
9489 display_mode: _,
9490 } => {
9491 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9492
9493 let (highlighted_edits, has_more_lines) =
9494 if let Some(edit_preview) = edit_preview.as_ref() {
9495 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9496 .first_line_preview()
9497 } else {
9498 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9499 };
9500
9501 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9502 .with_default_highlights(&style.text, highlighted_edits.highlights);
9503
9504 let preview = h_flex()
9505 .gap_1()
9506 .min_w_16()
9507 .child(styled_text)
9508 .when(has_more_lines, |parent| parent.child("…"));
9509
9510 let left = if supports_jump && first_edit_row != cursor_point.row {
9511 render_relative_row_jump("", cursor_point.row, first_edit_row)
9512 .into_any_element()
9513 } else {
9514 let icon_name =
9515 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9516 Icon::new(icon_name).into_any_element()
9517 };
9518
9519 Some(
9520 h_flex()
9521 .h_full()
9522 .flex_1()
9523 .gap_2()
9524 .pr_1()
9525 .overflow_x_hidden()
9526 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9527 .child(left)
9528 .child(preview),
9529 )
9530 }
9531 }
9532 }
9533
9534 pub fn render_context_menu(
9535 &self,
9536 style: &EditorStyle,
9537 max_height_in_lines: u32,
9538 window: &mut Window,
9539 cx: &mut Context<Editor>,
9540 ) -> Option<AnyElement> {
9541 let menu = self.context_menu.borrow();
9542 let menu = menu.as_ref()?;
9543 if !menu.visible() {
9544 return None;
9545 };
9546 Some(menu.render(style, max_height_in_lines, window, cx))
9547 }
9548
9549 fn render_context_menu_aside(
9550 &mut self,
9551 max_size: Size<Pixels>,
9552 window: &mut Window,
9553 cx: &mut Context<Editor>,
9554 ) -> Option<AnyElement> {
9555 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9556 if menu.visible() {
9557 menu.render_aside(max_size, window, cx)
9558 } else {
9559 None
9560 }
9561 })
9562 }
9563
9564 fn hide_context_menu(
9565 &mut self,
9566 window: &mut Window,
9567 cx: &mut Context<Self>,
9568 ) -> Option<CodeContextMenu> {
9569 cx.notify();
9570 self.completion_tasks.clear();
9571 let context_menu = self.context_menu.borrow_mut().take();
9572 self.stale_edit_prediction_in_menu.take();
9573 self.update_visible_edit_prediction(window, cx);
9574 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9575 && let Some(completion_provider) = &self.completion_provider
9576 {
9577 completion_provider.selection_changed(None, window, cx);
9578 }
9579 context_menu
9580 }
9581
9582 fn show_snippet_choices(
9583 &mut self,
9584 choices: &Vec<String>,
9585 selection: Range<Anchor>,
9586 cx: &mut Context<Self>,
9587 ) {
9588 let Some((_, buffer, _)) = self
9589 .buffer()
9590 .read(cx)
9591 .excerpt_containing(selection.start, cx)
9592 else {
9593 return;
9594 };
9595 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9596 else {
9597 return;
9598 };
9599 if buffer != end_buffer {
9600 log::error!("expected anchor range to have matching buffer IDs");
9601 return;
9602 }
9603
9604 let id = post_inc(&mut self.next_completion_id);
9605 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9606 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9607 CompletionsMenu::new_snippet_choices(
9608 id,
9609 true,
9610 choices,
9611 selection,
9612 buffer,
9613 snippet_sort_order,
9614 ),
9615 ));
9616 }
9617
9618 pub fn insert_snippet(
9619 &mut self,
9620 insertion_ranges: &[Range<usize>],
9621 snippet: Snippet,
9622 window: &mut Window,
9623 cx: &mut Context<Self>,
9624 ) -> Result<()> {
9625 struct Tabstop<T> {
9626 is_end_tabstop: bool,
9627 ranges: Vec<Range<T>>,
9628 choices: Option<Vec<String>>,
9629 }
9630
9631 let tabstops = self.buffer.update(cx, |buffer, cx| {
9632 let snippet_text: Arc<str> = snippet.text.clone().into();
9633 let edits = insertion_ranges
9634 .iter()
9635 .cloned()
9636 .map(|range| (range, snippet_text.clone()));
9637 let autoindent_mode = AutoindentMode::Block {
9638 original_indent_columns: Vec::new(),
9639 };
9640 buffer.edit(edits, Some(autoindent_mode), cx);
9641
9642 let snapshot = &*buffer.read(cx);
9643 let snippet = &snippet;
9644 snippet
9645 .tabstops
9646 .iter()
9647 .map(|tabstop| {
9648 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9649 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9650 });
9651 let mut tabstop_ranges = tabstop
9652 .ranges
9653 .iter()
9654 .flat_map(|tabstop_range| {
9655 let mut delta = 0_isize;
9656 insertion_ranges.iter().map(move |insertion_range| {
9657 let insertion_start = insertion_range.start as isize + delta;
9658 delta +=
9659 snippet.text.len() as isize - insertion_range.len() as isize;
9660
9661 let start = ((insertion_start + tabstop_range.start) as usize)
9662 .min(snapshot.len());
9663 let end = ((insertion_start + tabstop_range.end) as usize)
9664 .min(snapshot.len());
9665 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9666 })
9667 })
9668 .collect::<Vec<_>>();
9669 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9670
9671 Tabstop {
9672 is_end_tabstop,
9673 ranges: tabstop_ranges,
9674 choices: tabstop.choices.clone(),
9675 }
9676 })
9677 .collect::<Vec<_>>()
9678 });
9679 if let Some(tabstop) = tabstops.first() {
9680 self.change_selections(Default::default(), window, cx, |s| {
9681 // Reverse order so that the first range is the newest created selection.
9682 // Completions will use it and autoscroll will prioritize it.
9683 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9684 });
9685
9686 if let Some(choices) = &tabstop.choices
9687 && let Some(selection) = tabstop.ranges.first()
9688 {
9689 self.show_snippet_choices(choices, selection.clone(), cx)
9690 }
9691
9692 // If we're already at the last tabstop and it's at the end of the snippet,
9693 // we're done, we don't need to keep the state around.
9694 if !tabstop.is_end_tabstop {
9695 let choices = tabstops
9696 .iter()
9697 .map(|tabstop| tabstop.choices.clone())
9698 .collect();
9699
9700 let ranges = tabstops
9701 .into_iter()
9702 .map(|tabstop| tabstop.ranges)
9703 .collect::<Vec<_>>();
9704
9705 self.snippet_stack.push(SnippetState {
9706 active_index: 0,
9707 ranges,
9708 choices,
9709 });
9710 }
9711
9712 // Check whether the just-entered snippet ends with an auto-closable bracket.
9713 if self.autoclose_regions.is_empty() {
9714 let snapshot = self.buffer.read(cx).snapshot(cx);
9715 let mut all_selections = self.selections.all::<Point>(cx);
9716 for selection in &mut all_selections {
9717 let selection_head = selection.head();
9718 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9719 continue;
9720 };
9721
9722 let mut bracket_pair = None;
9723 let max_lookup_length = scope
9724 .brackets()
9725 .map(|(pair, _)| {
9726 pair.start
9727 .as_str()
9728 .chars()
9729 .count()
9730 .max(pair.end.as_str().chars().count())
9731 })
9732 .max();
9733 if let Some(max_lookup_length) = max_lookup_length {
9734 let next_text = snapshot
9735 .chars_at(selection_head)
9736 .take(max_lookup_length)
9737 .collect::<String>();
9738 let prev_text = snapshot
9739 .reversed_chars_at(selection_head)
9740 .take(max_lookup_length)
9741 .collect::<String>();
9742
9743 for (pair, enabled) in scope.brackets() {
9744 if enabled
9745 && pair.close
9746 && prev_text.starts_with(pair.start.as_str())
9747 && next_text.starts_with(pair.end.as_str())
9748 {
9749 bracket_pair = Some(pair.clone());
9750 break;
9751 }
9752 }
9753 }
9754
9755 if let Some(pair) = bracket_pair {
9756 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9757 let autoclose_enabled =
9758 self.use_autoclose && snapshot_settings.use_autoclose;
9759 if autoclose_enabled {
9760 let start = snapshot.anchor_after(selection_head);
9761 let end = snapshot.anchor_after(selection_head);
9762 self.autoclose_regions.push(AutocloseRegion {
9763 selection_id: selection.id,
9764 range: start..end,
9765 pair,
9766 });
9767 }
9768 }
9769 }
9770 }
9771 }
9772 Ok(())
9773 }
9774
9775 pub fn move_to_next_snippet_tabstop(
9776 &mut self,
9777 window: &mut Window,
9778 cx: &mut Context<Self>,
9779 ) -> bool {
9780 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9781 }
9782
9783 pub fn move_to_prev_snippet_tabstop(
9784 &mut self,
9785 window: &mut Window,
9786 cx: &mut Context<Self>,
9787 ) -> bool {
9788 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9789 }
9790
9791 pub fn move_to_snippet_tabstop(
9792 &mut self,
9793 bias: Bias,
9794 window: &mut Window,
9795 cx: &mut Context<Self>,
9796 ) -> bool {
9797 if let Some(mut snippet) = self.snippet_stack.pop() {
9798 match bias {
9799 Bias::Left => {
9800 if snippet.active_index > 0 {
9801 snippet.active_index -= 1;
9802 } else {
9803 self.snippet_stack.push(snippet);
9804 return false;
9805 }
9806 }
9807 Bias::Right => {
9808 if snippet.active_index + 1 < snippet.ranges.len() {
9809 snippet.active_index += 1;
9810 } else {
9811 self.snippet_stack.push(snippet);
9812 return false;
9813 }
9814 }
9815 }
9816 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9817 self.change_selections(Default::default(), window, cx, |s| {
9818 // Reverse order so that the first range is the newest created selection.
9819 // Completions will use it and autoscroll will prioritize it.
9820 s.select_ranges(current_ranges.iter().rev().cloned())
9821 });
9822
9823 if let Some(choices) = &snippet.choices[snippet.active_index]
9824 && let Some(selection) = current_ranges.first()
9825 {
9826 self.show_snippet_choices(choices, selection.clone(), cx);
9827 }
9828
9829 // If snippet state is not at the last tabstop, push it back on the stack
9830 if snippet.active_index + 1 < snippet.ranges.len() {
9831 self.snippet_stack.push(snippet);
9832 }
9833 return true;
9834 }
9835 }
9836
9837 false
9838 }
9839
9840 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9841 self.transact(window, cx, |this, window, cx| {
9842 this.select_all(&SelectAll, window, cx);
9843 this.insert("", window, cx);
9844 });
9845 }
9846
9847 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9848 if self.read_only(cx) {
9849 return;
9850 }
9851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9852 self.transact(window, cx, |this, window, cx| {
9853 this.select_autoclose_pair(window, cx);
9854 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9855 if !this.linked_edit_ranges.is_empty() {
9856 let selections = this.selections.all::<MultiBufferPoint>(cx);
9857 let snapshot = this.buffer.read(cx).snapshot(cx);
9858
9859 for selection in selections.iter() {
9860 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9861 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9862 if selection_start.buffer_id != selection_end.buffer_id {
9863 continue;
9864 }
9865 if let Some(ranges) =
9866 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9867 {
9868 for (buffer, entries) in ranges {
9869 linked_ranges.entry(buffer).or_default().extend(entries);
9870 }
9871 }
9872 }
9873 }
9874
9875 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9876 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9877 for selection in &mut selections {
9878 if selection.is_empty() {
9879 let old_head = selection.head();
9880 let mut new_head =
9881 movement::left(&display_map, old_head.to_display_point(&display_map))
9882 .to_point(&display_map);
9883 if let Some((buffer, line_buffer_range)) = display_map
9884 .buffer_snapshot
9885 .buffer_line_for_row(MultiBufferRow(old_head.row))
9886 {
9887 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9888 let indent_len = match indent_size.kind {
9889 IndentKind::Space => {
9890 buffer.settings_at(line_buffer_range.start, cx).tab_size
9891 }
9892 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9893 };
9894 if old_head.column <= indent_size.len && old_head.column > 0 {
9895 let indent_len = indent_len.get();
9896 new_head = cmp::min(
9897 new_head,
9898 MultiBufferPoint::new(
9899 old_head.row,
9900 ((old_head.column - 1) / indent_len) * indent_len,
9901 ),
9902 );
9903 }
9904 }
9905
9906 selection.set_head(new_head, SelectionGoal::None);
9907 }
9908 }
9909
9910 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9911 this.insert("", window, cx);
9912 let empty_str: Arc<str> = Arc::from("");
9913 for (buffer, edits) in linked_ranges {
9914 let snapshot = buffer.read(cx).snapshot();
9915 use text::ToPoint as TP;
9916
9917 let edits = edits
9918 .into_iter()
9919 .map(|range| {
9920 let end_point = TP::to_point(&range.end, &snapshot);
9921 let mut start_point = TP::to_point(&range.start, &snapshot);
9922
9923 if end_point == start_point {
9924 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9925 .saturating_sub(1);
9926 start_point =
9927 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9928 };
9929
9930 (start_point..end_point, empty_str.clone())
9931 })
9932 .sorted_by_key(|(range, _)| range.start)
9933 .collect::<Vec<_>>();
9934 buffer.update(cx, |this, cx| {
9935 this.edit(edits, None, cx);
9936 })
9937 }
9938 this.refresh_edit_prediction(true, false, window, cx);
9939 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9940 });
9941 }
9942
9943 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9944 if self.read_only(cx) {
9945 return;
9946 }
9947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9948 self.transact(window, cx, |this, window, cx| {
9949 this.change_selections(Default::default(), window, cx, |s| {
9950 s.move_with(|map, selection| {
9951 if selection.is_empty() {
9952 let cursor = movement::right(map, selection.head());
9953 selection.end = cursor;
9954 selection.reversed = true;
9955 selection.goal = SelectionGoal::None;
9956 }
9957 })
9958 });
9959 this.insert("", window, cx);
9960 this.refresh_edit_prediction(true, false, window, cx);
9961 });
9962 }
9963
9964 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9965 if self.mode.is_single_line() {
9966 cx.propagate();
9967 return;
9968 }
9969
9970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9971 if self.move_to_prev_snippet_tabstop(window, cx) {
9972 return;
9973 }
9974 self.outdent(&Outdent, window, cx);
9975 }
9976
9977 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9978 if self.mode.is_single_line() {
9979 cx.propagate();
9980 return;
9981 }
9982
9983 if self.move_to_next_snippet_tabstop(window, cx) {
9984 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9985 return;
9986 }
9987 if self.read_only(cx) {
9988 return;
9989 }
9990 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9991 let mut selections = self.selections.all_adjusted(cx);
9992 let buffer = self.buffer.read(cx);
9993 let snapshot = buffer.snapshot(cx);
9994 let rows_iter = selections.iter().map(|s| s.head().row);
9995 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9996
9997 let has_some_cursor_in_whitespace = selections
9998 .iter()
9999 .filter(|selection| selection.is_empty())
10000 .any(|selection| {
10001 let cursor = selection.head();
10002 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10003 cursor.column < current_indent.len
10004 });
10005
10006 let mut edits = Vec::new();
10007 let mut prev_edited_row = 0;
10008 let mut row_delta = 0;
10009 for selection in &mut selections {
10010 if selection.start.row != prev_edited_row {
10011 row_delta = 0;
10012 }
10013 prev_edited_row = selection.end.row;
10014
10015 // If the selection is non-empty, then increase the indentation of the selected lines.
10016 if !selection.is_empty() {
10017 row_delta =
10018 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10019 continue;
10020 }
10021
10022 let cursor = selection.head();
10023 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10024 if let Some(suggested_indent) =
10025 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10026 {
10027 // Don't do anything if already at suggested indent
10028 // and there is any other cursor which is not
10029 if has_some_cursor_in_whitespace
10030 && cursor.column == current_indent.len
10031 && current_indent.len == suggested_indent.len
10032 {
10033 continue;
10034 }
10035
10036 // Adjust line and move cursor to suggested indent
10037 // if cursor is not at suggested indent
10038 if cursor.column < suggested_indent.len
10039 && cursor.column <= current_indent.len
10040 && current_indent.len <= suggested_indent.len
10041 {
10042 selection.start = Point::new(cursor.row, suggested_indent.len);
10043 selection.end = selection.start;
10044 if row_delta == 0 {
10045 edits.extend(Buffer::edit_for_indent_size_adjustment(
10046 cursor.row,
10047 current_indent,
10048 suggested_indent,
10049 ));
10050 row_delta = suggested_indent.len - current_indent.len;
10051 }
10052 continue;
10053 }
10054
10055 // If current indent is more than suggested indent
10056 // only move cursor to current indent and skip indent
10057 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10058 selection.start = Point::new(cursor.row, current_indent.len);
10059 selection.end = selection.start;
10060 continue;
10061 }
10062 }
10063
10064 // Otherwise, insert a hard or soft tab.
10065 let settings = buffer.language_settings_at(cursor, cx);
10066 let tab_size = if settings.hard_tabs {
10067 IndentSize::tab()
10068 } else {
10069 let tab_size = settings.tab_size.get();
10070 let indent_remainder = snapshot
10071 .text_for_range(Point::new(cursor.row, 0)..cursor)
10072 .flat_map(str::chars)
10073 .fold(row_delta % tab_size, |counter: u32, c| {
10074 if c == '\t' {
10075 0
10076 } else {
10077 (counter + 1) % tab_size
10078 }
10079 });
10080
10081 let chars_to_next_tab_stop = tab_size - indent_remainder;
10082 IndentSize::spaces(chars_to_next_tab_stop)
10083 };
10084 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10085 selection.end = selection.start;
10086 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10087 row_delta += tab_size.len;
10088 }
10089
10090 self.transact(window, cx, |this, window, cx| {
10091 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10092 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10093 this.refresh_edit_prediction(true, false, window, cx);
10094 });
10095 }
10096
10097 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10098 if self.read_only(cx) {
10099 return;
10100 }
10101 if self.mode.is_single_line() {
10102 cx.propagate();
10103 return;
10104 }
10105
10106 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10107 let mut selections = self.selections.all::<Point>(cx);
10108 let mut prev_edited_row = 0;
10109 let mut row_delta = 0;
10110 let mut edits = Vec::new();
10111 let buffer = self.buffer.read(cx);
10112 let snapshot = buffer.snapshot(cx);
10113 for selection in &mut selections {
10114 if selection.start.row != prev_edited_row {
10115 row_delta = 0;
10116 }
10117 prev_edited_row = selection.end.row;
10118
10119 row_delta =
10120 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10121 }
10122
10123 self.transact(window, cx, |this, window, cx| {
10124 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10125 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10126 });
10127 }
10128
10129 fn indent_selection(
10130 buffer: &MultiBuffer,
10131 snapshot: &MultiBufferSnapshot,
10132 selection: &mut Selection<Point>,
10133 edits: &mut Vec<(Range<Point>, String)>,
10134 delta_for_start_row: u32,
10135 cx: &App,
10136 ) -> u32 {
10137 let settings = buffer.language_settings_at(selection.start, cx);
10138 let tab_size = settings.tab_size.get();
10139 let indent_kind = if settings.hard_tabs {
10140 IndentKind::Tab
10141 } else {
10142 IndentKind::Space
10143 };
10144 let mut start_row = selection.start.row;
10145 let mut end_row = selection.end.row + 1;
10146
10147 // If a selection ends at the beginning of a line, don't indent
10148 // that last line.
10149 if selection.end.column == 0 && selection.end.row > selection.start.row {
10150 end_row -= 1;
10151 }
10152
10153 // Avoid re-indenting a row that has already been indented by a
10154 // previous selection, but still update this selection's column
10155 // to reflect that indentation.
10156 if delta_for_start_row > 0 {
10157 start_row += 1;
10158 selection.start.column += delta_for_start_row;
10159 if selection.end.row == selection.start.row {
10160 selection.end.column += delta_for_start_row;
10161 }
10162 }
10163
10164 let mut delta_for_end_row = 0;
10165 let has_multiple_rows = start_row + 1 != end_row;
10166 for row in start_row..end_row {
10167 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10168 let indent_delta = match (current_indent.kind, indent_kind) {
10169 (IndentKind::Space, IndentKind::Space) => {
10170 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10171 IndentSize::spaces(columns_to_next_tab_stop)
10172 }
10173 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10174 (_, IndentKind::Tab) => IndentSize::tab(),
10175 };
10176
10177 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10178 0
10179 } else {
10180 selection.start.column
10181 };
10182 let row_start = Point::new(row, start);
10183 edits.push((
10184 row_start..row_start,
10185 indent_delta.chars().collect::<String>(),
10186 ));
10187
10188 // Update this selection's endpoints to reflect the indentation.
10189 if row == selection.start.row {
10190 selection.start.column += indent_delta.len;
10191 }
10192 if row == selection.end.row {
10193 selection.end.column += indent_delta.len;
10194 delta_for_end_row = indent_delta.len;
10195 }
10196 }
10197
10198 if selection.start.row == selection.end.row {
10199 delta_for_start_row + delta_for_end_row
10200 } else {
10201 delta_for_end_row
10202 }
10203 }
10204
10205 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10206 if self.read_only(cx) {
10207 return;
10208 }
10209 if self.mode.is_single_line() {
10210 cx.propagate();
10211 return;
10212 }
10213
10214 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10215 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10216 let selections = self.selections.all::<Point>(cx);
10217 let mut deletion_ranges = Vec::new();
10218 let mut last_outdent = None;
10219 {
10220 let buffer = self.buffer.read(cx);
10221 let snapshot = buffer.snapshot(cx);
10222 for selection in &selections {
10223 let settings = buffer.language_settings_at(selection.start, cx);
10224 let tab_size = settings.tab_size.get();
10225 let mut rows = selection.spanned_rows(false, &display_map);
10226
10227 // Avoid re-outdenting a row that has already been outdented by a
10228 // previous selection.
10229 if let Some(last_row) = last_outdent
10230 && last_row == rows.start
10231 {
10232 rows.start = rows.start.next_row();
10233 }
10234 let has_multiple_rows = rows.len() > 1;
10235 for row in rows.iter_rows() {
10236 let indent_size = snapshot.indent_size_for_line(row);
10237 if indent_size.len > 0 {
10238 let deletion_len = match indent_size.kind {
10239 IndentKind::Space => {
10240 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10241 if columns_to_prev_tab_stop == 0 {
10242 tab_size
10243 } else {
10244 columns_to_prev_tab_stop
10245 }
10246 }
10247 IndentKind::Tab => 1,
10248 };
10249 let start = if has_multiple_rows
10250 || deletion_len > selection.start.column
10251 || indent_size.len < selection.start.column
10252 {
10253 0
10254 } else {
10255 selection.start.column - deletion_len
10256 };
10257 deletion_ranges.push(
10258 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10259 );
10260 last_outdent = Some(row);
10261 }
10262 }
10263 }
10264 }
10265
10266 self.transact(window, cx, |this, window, cx| {
10267 this.buffer.update(cx, |buffer, cx| {
10268 let empty_str: Arc<str> = Arc::default();
10269 buffer.edit(
10270 deletion_ranges
10271 .into_iter()
10272 .map(|range| (range, empty_str.clone())),
10273 None,
10274 cx,
10275 );
10276 });
10277 let selections = this.selections.all::<usize>(cx);
10278 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10279 });
10280 }
10281
10282 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10283 if self.read_only(cx) {
10284 return;
10285 }
10286 if self.mode.is_single_line() {
10287 cx.propagate();
10288 return;
10289 }
10290
10291 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10292 let selections = self
10293 .selections
10294 .all::<usize>(cx)
10295 .into_iter()
10296 .map(|s| s.range());
10297
10298 self.transact(window, cx, |this, window, cx| {
10299 this.buffer.update(cx, |buffer, cx| {
10300 buffer.autoindent_ranges(selections, cx);
10301 });
10302 let selections = this.selections.all::<usize>(cx);
10303 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10304 });
10305 }
10306
10307 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10308 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10310 let selections = self.selections.all::<Point>(cx);
10311
10312 let mut new_cursors = Vec::new();
10313 let mut edit_ranges = Vec::new();
10314 let mut selections = selections.iter().peekable();
10315 while let Some(selection) = selections.next() {
10316 let mut rows = selection.spanned_rows(false, &display_map);
10317 let goal_display_column = selection.head().to_display_point(&display_map).column();
10318
10319 // Accumulate contiguous regions of rows that we want to delete.
10320 while let Some(next_selection) = selections.peek() {
10321 let next_rows = next_selection.spanned_rows(false, &display_map);
10322 if next_rows.start <= rows.end {
10323 rows.end = next_rows.end;
10324 selections.next().unwrap();
10325 } else {
10326 break;
10327 }
10328 }
10329
10330 let buffer = &display_map.buffer_snapshot;
10331 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10332 let edit_end;
10333 let cursor_buffer_row;
10334 if buffer.max_point().row >= rows.end.0 {
10335 // If there's a line after the range, delete the \n from the end of the row range
10336 // and position the cursor on the next line.
10337 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10338 cursor_buffer_row = rows.end;
10339 } else {
10340 // If there isn't a line after the range, delete the \n from the line before the
10341 // start of the row range and position the cursor there.
10342 edit_start = edit_start.saturating_sub(1);
10343 edit_end = buffer.len();
10344 cursor_buffer_row = rows.start.previous_row();
10345 }
10346
10347 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10348 *cursor.column_mut() =
10349 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10350
10351 new_cursors.push((
10352 selection.id,
10353 buffer.anchor_after(cursor.to_point(&display_map)),
10354 ));
10355 edit_ranges.push(edit_start..edit_end);
10356 }
10357
10358 self.transact(window, cx, |this, window, cx| {
10359 let buffer = this.buffer.update(cx, |buffer, cx| {
10360 let empty_str: Arc<str> = Arc::default();
10361 buffer.edit(
10362 edit_ranges
10363 .into_iter()
10364 .map(|range| (range, empty_str.clone())),
10365 None,
10366 cx,
10367 );
10368 buffer.snapshot(cx)
10369 });
10370 let new_selections = new_cursors
10371 .into_iter()
10372 .map(|(id, cursor)| {
10373 let cursor = cursor.to_point(&buffer);
10374 Selection {
10375 id,
10376 start: cursor,
10377 end: cursor,
10378 reversed: false,
10379 goal: SelectionGoal::None,
10380 }
10381 })
10382 .collect();
10383
10384 this.change_selections(Default::default(), window, cx, |s| {
10385 s.select(new_selections);
10386 });
10387 });
10388 }
10389
10390 pub fn join_lines_impl(
10391 &mut self,
10392 insert_whitespace: bool,
10393 window: &mut Window,
10394 cx: &mut Context<Self>,
10395 ) {
10396 if self.read_only(cx) {
10397 return;
10398 }
10399 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10400 for selection in self.selections.all::<Point>(cx) {
10401 let start = MultiBufferRow(selection.start.row);
10402 // Treat single line selections as if they include the next line. Otherwise this action
10403 // would do nothing for single line selections individual cursors.
10404 let end = if selection.start.row == selection.end.row {
10405 MultiBufferRow(selection.start.row + 1)
10406 } else {
10407 MultiBufferRow(selection.end.row)
10408 };
10409
10410 if let Some(last_row_range) = row_ranges.last_mut()
10411 && start <= last_row_range.end
10412 {
10413 last_row_range.end = end;
10414 continue;
10415 }
10416 row_ranges.push(start..end);
10417 }
10418
10419 let snapshot = self.buffer.read(cx).snapshot(cx);
10420 let mut cursor_positions = Vec::new();
10421 for row_range in &row_ranges {
10422 let anchor = snapshot.anchor_before(Point::new(
10423 row_range.end.previous_row().0,
10424 snapshot.line_len(row_range.end.previous_row()),
10425 ));
10426 cursor_positions.push(anchor..anchor);
10427 }
10428
10429 self.transact(window, cx, |this, window, cx| {
10430 for row_range in row_ranges.into_iter().rev() {
10431 for row in row_range.iter_rows().rev() {
10432 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10433 let next_line_row = row.next_row();
10434 let indent = snapshot.indent_size_for_line(next_line_row);
10435 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10436
10437 let replace =
10438 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10439 " "
10440 } else {
10441 ""
10442 };
10443
10444 this.buffer.update(cx, |buffer, cx| {
10445 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10446 });
10447 }
10448 }
10449
10450 this.change_selections(Default::default(), window, cx, |s| {
10451 s.select_anchor_ranges(cursor_positions)
10452 });
10453 });
10454 }
10455
10456 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10458 self.join_lines_impl(true, window, cx);
10459 }
10460
10461 pub fn sort_lines_case_sensitive(
10462 &mut self,
10463 _: &SortLinesCaseSensitive,
10464 window: &mut Window,
10465 cx: &mut Context<Self>,
10466 ) {
10467 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10468 }
10469
10470 pub fn sort_lines_by_length(
10471 &mut self,
10472 _: &SortLinesByLength,
10473 window: &mut Window,
10474 cx: &mut Context<Self>,
10475 ) {
10476 self.manipulate_immutable_lines(window, cx, |lines| {
10477 lines.sort_by_key(|&line| line.chars().count())
10478 })
10479 }
10480
10481 pub fn sort_lines_case_insensitive(
10482 &mut self,
10483 _: &SortLinesCaseInsensitive,
10484 window: &mut Window,
10485 cx: &mut Context<Self>,
10486 ) {
10487 self.manipulate_immutable_lines(window, cx, |lines| {
10488 lines.sort_by_key(|line| line.to_lowercase())
10489 })
10490 }
10491
10492 pub fn unique_lines_case_insensitive(
10493 &mut self,
10494 _: &UniqueLinesCaseInsensitive,
10495 window: &mut Window,
10496 cx: &mut Context<Self>,
10497 ) {
10498 self.manipulate_immutable_lines(window, cx, |lines| {
10499 let mut seen = HashSet::default();
10500 lines.retain(|line| seen.insert(line.to_lowercase()));
10501 })
10502 }
10503
10504 pub fn unique_lines_case_sensitive(
10505 &mut self,
10506 _: &UniqueLinesCaseSensitive,
10507 window: &mut Window,
10508 cx: &mut Context<Self>,
10509 ) {
10510 self.manipulate_immutable_lines(window, cx, |lines| {
10511 let mut seen = HashSet::default();
10512 lines.retain(|line| seen.insert(*line));
10513 })
10514 }
10515
10516 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10517 let snapshot = self.buffer.read(cx).snapshot(cx);
10518 for selection in self.selections.disjoint_anchors_arc().iter() {
10519 if snapshot
10520 .language_at(selection.start)
10521 .and_then(|lang| lang.config().wrap_characters.as_ref())
10522 .is_some()
10523 {
10524 return true;
10525 }
10526 }
10527 false
10528 }
10529
10530 fn wrap_selections_in_tag(
10531 &mut self,
10532 _: &WrapSelectionsInTag,
10533 window: &mut Window,
10534 cx: &mut Context<Self>,
10535 ) {
10536 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10537
10538 let snapshot = self.buffer.read(cx).snapshot(cx);
10539
10540 let mut edits = Vec::new();
10541 let mut boundaries = Vec::new();
10542
10543 for selection in self.selections.all::<Point>(cx).iter() {
10544 let Some(wrap_config) = snapshot
10545 .language_at(selection.start)
10546 .and_then(|lang| lang.config().wrap_characters.clone())
10547 else {
10548 continue;
10549 };
10550
10551 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10552 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10553
10554 let start_before = snapshot.anchor_before(selection.start);
10555 let end_after = snapshot.anchor_after(selection.end);
10556
10557 edits.push((start_before..start_before, open_tag));
10558 edits.push((end_after..end_after, close_tag));
10559
10560 boundaries.push((
10561 start_before,
10562 end_after,
10563 wrap_config.start_prefix.len(),
10564 wrap_config.end_suffix.len(),
10565 ));
10566 }
10567
10568 if edits.is_empty() {
10569 return;
10570 }
10571
10572 self.transact(window, cx, |this, window, cx| {
10573 let buffer = this.buffer.update(cx, |buffer, cx| {
10574 buffer.edit(edits, None, cx);
10575 buffer.snapshot(cx)
10576 });
10577
10578 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10579 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10580 boundaries.into_iter()
10581 {
10582 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10583 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10584 new_selections.push(open_offset..open_offset);
10585 new_selections.push(close_offset..close_offset);
10586 }
10587
10588 this.change_selections(Default::default(), window, cx, |s| {
10589 s.select_ranges(new_selections);
10590 });
10591
10592 this.request_autoscroll(Autoscroll::fit(), cx);
10593 });
10594 }
10595
10596 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10597 let Some(project) = self.project.clone() else {
10598 return;
10599 };
10600 self.reload(project, window, cx)
10601 .detach_and_notify_err(window, cx);
10602 }
10603
10604 pub fn restore_file(
10605 &mut self,
10606 _: &::git::RestoreFile,
10607 window: &mut Window,
10608 cx: &mut Context<Self>,
10609 ) {
10610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10611 let mut buffer_ids = HashSet::default();
10612 let snapshot = self.buffer().read(cx).snapshot(cx);
10613 for selection in self.selections.all::<usize>(cx) {
10614 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10615 }
10616
10617 let buffer = self.buffer().read(cx);
10618 let ranges = buffer_ids
10619 .into_iter()
10620 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10621 .collect::<Vec<_>>();
10622
10623 self.restore_hunks_in_ranges(ranges, window, cx);
10624 }
10625
10626 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10627 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10628 let selections = self
10629 .selections
10630 .all(cx)
10631 .into_iter()
10632 .map(|s| s.range())
10633 .collect();
10634 self.restore_hunks_in_ranges(selections, window, cx);
10635 }
10636
10637 pub fn restore_hunks_in_ranges(
10638 &mut self,
10639 ranges: Vec<Range<Point>>,
10640 window: &mut Window,
10641 cx: &mut Context<Editor>,
10642 ) {
10643 let mut revert_changes = HashMap::default();
10644 let chunk_by = self
10645 .snapshot(window, cx)
10646 .hunks_for_ranges(ranges)
10647 .into_iter()
10648 .chunk_by(|hunk| hunk.buffer_id);
10649 for (buffer_id, hunks) in &chunk_by {
10650 let hunks = hunks.collect::<Vec<_>>();
10651 for hunk in &hunks {
10652 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10653 }
10654 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10655 }
10656 drop(chunk_by);
10657 if !revert_changes.is_empty() {
10658 self.transact(window, cx, |editor, window, cx| {
10659 editor.restore(revert_changes, window, cx);
10660 });
10661 }
10662 }
10663
10664 pub fn open_active_item_in_terminal(
10665 &mut self,
10666 _: &OpenInTerminal,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10671 let project_path = buffer.read(cx).project_path(cx)?;
10672 let project = self.project()?.read(cx);
10673 let entry = project.entry_for_path(&project_path, cx)?;
10674 let parent = match &entry.canonical_path {
10675 Some(canonical_path) => canonical_path.to_path_buf(),
10676 None => project.absolute_path(&project_path, cx)?,
10677 }
10678 .parent()?
10679 .to_path_buf();
10680 Some(parent)
10681 }) {
10682 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10683 }
10684 }
10685
10686 fn set_breakpoint_context_menu(
10687 &mut self,
10688 display_row: DisplayRow,
10689 position: Option<Anchor>,
10690 clicked_point: gpui::Point<Pixels>,
10691 window: &mut Window,
10692 cx: &mut Context<Self>,
10693 ) {
10694 let source = self
10695 .buffer
10696 .read(cx)
10697 .snapshot(cx)
10698 .anchor_before(Point::new(display_row.0, 0u32));
10699
10700 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10701
10702 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10703 self,
10704 source,
10705 clicked_point,
10706 context_menu,
10707 window,
10708 cx,
10709 );
10710 }
10711
10712 fn add_edit_breakpoint_block(
10713 &mut self,
10714 anchor: Anchor,
10715 breakpoint: &Breakpoint,
10716 edit_action: BreakpointPromptEditAction,
10717 window: &mut Window,
10718 cx: &mut Context<Self>,
10719 ) {
10720 let weak_editor = cx.weak_entity();
10721 let bp_prompt = cx.new(|cx| {
10722 BreakpointPromptEditor::new(
10723 weak_editor,
10724 anchor,
10725 breakpoint.clone(),
10726 edit_action,
10727 window,
10728 cx,
10729 )
10730 });
10731
10732 let height = bp_prompt.update(cx, |this, cx| {
10733 this.prompt
10734 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10735 });
10736 let cloned_prompt = bp_prompt.clone();
10737 let blocks = vec![BlockProperties {
10738 style: BlockStyle::Sticky,
10739 placement: BlockPlacement::Above(anchor),
10740 height: Some(height),
10741 render: Arc::new(move |cx| {
10742 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10743 cloned_prompt.clone().into_any_element()
10744 }),
10745 priority: 0,
10746 }];
10747
10748 let focus_handle = bp_prompt.focus_handle(cx);
10749 window.focus(&focus_handle);
10750
10751 let block_ids = self.insert_blocks(blocks, None, cx);
10752 bp_prompt.update(cx, |prompt, _| {
10753 prompt.add_block_ids(block_ids);
10754 });
10755 }
10756
10757 pub(crate) fn breakpoint_at_row(
10758 &self,
10759 row: u32,
10760 window: &mut Window,
10761 cx: &mut Context<Self>,
10762 ) -> Option<(Anchor, Breakpoint)> {
10763 let snapshot = self.snapshot(window, cx);
10764 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10765
10766 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10767 }
10768
10769 pub(crate) fn breakpoint_at_anchor(
10770 &self,
10771 breakpoint_position: Anchor,
10772 snapshot: &EditorSnapshot,
10773 cx: &mut Context<Self>,
10774 ) -> Option<(Anchor, Breakpoint)> {
10775 let buffer = self
10776 .buffer
10777 .read(cx)
10778 .buffer_for_anchor(breakpoint_position, cx)?;
10779
10780 let enclosing_excerpt = breakpoint_position.excerpt_id;
10781 let buffer_snapshot = buffer.read(cx).snapshot();
10782
10783 let row = buffer_snapshot
10784 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10785 .row;
10786
10787 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10788 let anchor_end = snapshot
10789 .buffer_snapshot
10790 .anchor_after(Point::new(row, line_len));
10791
10792 self.breakpoint_store
10793 .as_ref()?
10794 .read_with(cx, |breakpoint_store, cx| {
10795 breakpoint_store
10796 .breakpoints(
10797 &buffer,
10798 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10799 &buffer_snapshot,
10800 cx,
10801 )
10802 .next()
10803 .and_then(|(bp, _)| {
10804 let breakpoint_row = buffer_snapshot
10805 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10806 .row;
10807
10808 if breakpoint_row == row {
10809 snapshot
10810 .buffer_snapshot
10811 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10812 .map(|position| (position, bp.bp.clone()))
10813 } else {
10814 None
10815 }
10816 })
10817 })
10818 }
10819
10820 pub fn edit_log_breakpoint(
10821 &mut self,
10822 _: &EditLogBreakpoint,
10823 window: &mut Window,
10824 cx: &mut Context<Self>,
10825 ) {
10826 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10827 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10828 message: None,
10829 state: BreakpointState::Enabled,
10830 condition: None,
10831 hit_condition: None,
10832 });
10833
10834 self.add_edit_breakpoint_block(
10835 anchor,
10836 &breakpoint,
10837 BreakpointPromptEditAction::Log,
10838 window,
10839 cx,
10840 );
10841 }
10842 }
10843
10844 fn breakpoints_at_cursors(
10845 &self,
10846 window: &mut Window,
10847 cx: &mut Context<Self>,
10848 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10849 let snapshot = self.snapshot(window, cx);
10850 let cursors = self
10851 .selections
10852 .disjoint_anchors_arc()
10853 .iter()
10854 .map(|selection| {
10855 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10856
10857 let breakpoint_position = self
10858 .breakpoint_at_row(cursor_position.row, window, cx)
10859 .map(|bp| bp.0)
10860 .unwrap_or_else(|| {
10861 snapshot
10862 .display_snapshot
10863 .buffer_snapshot
10864 .anchor_after(Point::new(cursor_position.row, 0))
10865 });
10866
10867 let breakpoint = self
10868 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10869 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10870
10871 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10872 })
10873 // 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.
10874 .collect::<HashMap<Anchor, _>>();
10875
10876 cursors.into_iter().collect()
10877 }
10878
10879 pub fn enable_breakpoint(
10880 &mut self,
10881 _: &crate::actions::EnableBreakpoint,
10882 window: &mut Window,
10883 cx: &mut Context<Self>,
10884 ) {
10885 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10886 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10887 continue;
10888 };
10889 self.edit_breakpoint_at_anchor(
10890 anchor,
10891 breakpoint,
10892 BreakpointEditAction::InvertState,
10893 cx,
10894 );
10895 }
10896 }
10897
10898 pub fn disable_breakpoint(
10899 &mut self,
10900 _: &crate::actions::DisableBreakpoint,
10901 window: &mut Window,
10902 cx: &mut Context<Self>,
10903 ) {
10904 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10905 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10906 continue;
10907 };
10908 self.edit_breakpoint_at_anchor(
10909 anchor,
10910 breakpoint,
10911 BreakpointEditAction::InvertState,
10912 cx,
10913 );
10914 }
10915 }
10916
10917 pub fn toggle_breakpoint(
10918 &mut self,
10919 _: &crate::actions::ToggleBreakpoint,
10920 window: &mut Window,
10921 cx: &mut Context<Self>,
10922 ) {
10923 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10924 if let Some(breakpoint) = breakpoint {
10925 self.edit_breakpoint_at_anchor(
10926 anchor,
10927 breakpoint,
10928 BreakpointEditAction::Toggle,
10929 cx,
10930 );
10931 } else {
10932 self.edit_breakpoint_at_anchor(
10933 anchor,
10934 Breakpoint::new_standard(),
10935 BreakpointEditAction::Toggle,
10936 cx,
10937 );
10938 }
10939 }
10940 }
10941
10942 pub fn edit_breakpoint_at_anchor(
10943 &mut self,
10944 breakpoint_position: Anchor,
10945 breakpoint: Breakpoint,
10946 edit_action: BreakpointEditAction,
10947 cx: &mut Context<Self>,
10948 ) {
10949 let Some(breakpoint_store) = &self.breakpoint_store else {
10950 return;
10951 };
10952
10953 let Some(buffer) = self
10954 .buffer
10955 .read(cx)
10956 .buffer_for_anchor(breakpoint_position, cx)
10957 else {
10958 return;
10959 };
10960
10961 breakpoint_store.update(cx, |breakpoint_store, cx| {
10962 breakpoint_store.toggle_breakpoint(
10963 buffer,
10964 BreakpointWithPosition {
10965 position: breakpoint_position.text_anchor,
10966 bp: breakpoint,
10967 },
10968 edit_action,
10969 cx,
10970 );
10971 });
10972
10973 cx.notify();
10974 }
10975
10976 #[cfg(any(test, feature = "test-support"))]
10977 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10978 self.breakpoint_store.clone()
10979 }
10980
10981 pub fn prepare_restore_change(
10982 &self,
10983 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10984 hunk: &MultiBufferDiffHunk,
10985 cx: &mut App,
10986 ) -> Option<()> {
10987 if hunk.is_created_file() {
10988 return None;
10989 }
10990 let buffer = self.buffer.read(cx);
10991 let diff = buffer.diff_for(hunk.buffer_id)?;
10992 let buffer = buffer.buffer(hunk.buffer_id)?;
10993 let buffer = buffer.read(cx);
10994 let original_text = diff
10995 .read(cx)
10996 .base_text()
10997 .as_rope()
10998 .slice(hunk.diff_base_byte_range.clone());
10999 let buffer_snapshot = buffer.snapshot();
11000 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11001 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11002 probe
11003 .0
11004 .start
11005 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11006 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11007 }) {
11008 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11009 Some(())
11010 } else {
11011 None
11012 }
11013 }
11014
11015 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11016 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11017 }
11018
11019 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11020 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11021 }
11022
11023 fn manipulate_lines<M>(
11024 &mut self,
11025 window: &mut Window,
11026 cx: &mut Context<Self>,
11027 mut manipulate: M,
11028 ) where
11029 M: FnMut(&str) -> LineManipulationResult,
11030 {
11031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11032
11033 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11034 let buffer = self.buffer.read(cx).snapshot(cx);
11035
11036 let mut edits = Vec::new();
11037
11038 let selections = self.selections.all::<Point>(cx);
11039 let mut selections = selections.iter().peekable();
11040 let mut contiguous_row_selections = Vec::new();
11041 let mut new_selections = Vec::new();
11042 let mut added_lines = 0;
11043 let mut removed_lines = 0;
11044
11045 while let Some(selection) = selections.next() {
11046 let (start_row, end_row) = consume_contiguous_rows(
11047 &mut contiguous_row_selections,
11048 selection,
11049 &display_map,
11050 &mut selections,
11051 );
11052
11053 let start_point = Point::new(start_row.0, 0);
11054 let end_point = Point::new(
11055 end_row.previous_row().0,
11056 buffer.line_len(end_row.previous_row()),
11057 );
11058 let text = buffer
11059 .text_for_range(start_point..end_point)
11060 .collect::<String>();
11061
11062 let LineManipulationResult {
11063 new_text,
11064 line_count_before,
11065 line_count_after,
11066 } = manipulate(&text);
11067
11068 edits.push((start_point..end_point, new_text));
11069
11070 // Selections must change based on added and removed line count
11071 let start_row =
11072 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11073 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11074 new_selections.push(Selection {
11075 id: selection.id,
11076 start: start_row,
11077 end: end_row,
11078 goal: SelectionGoal::None,
11079 reversed: selection.reversed,
11080 });
11081
11082 if line_count_after > line_count_before {
11083 added_lines += line_count_after - line_count_before;
11084 } else if line_count_before > line_count_after {
11085 removed_lines += line_count_before - line_count_after;
11086 }
11087 }
11088
11089 self.transact(window, cx, |this, window, cx| {
11090 let buffer = this.buffer.update(cx, |buffer, cx| {
11091 buffer.edit(edits, None, cx);
11092 buffer.snapshot(cx)
11093 });
11094
11095 // Recalculate offsets on newly edited buffer
11096 let new_selections = new_selections
11097 .iter()
11098 .map(|s| {
11099 let start_point = Point::new(s.start.0, 0);
11100 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11101 Selection {
11102 id: s.id,
11103 start: buffer.point_to_offset(start_point),
11104 end: buffer.point_to_offset(end_point),
11105 goal: s.goal,
11106 reversed: s.reversed,
11107 }
11108 })
11109 .collect();
11110
11111 this.change_selections(Default::default(), window, cx, |s| {
11112 s.select(new_selections);
11113 });
11114
11115 this.request_autoscroll(Autoscroll::fit(), cx);
11116 });
11117 }
11118
11119 fn manipulate_immutable_lines<Fn>(
11120 &mut self,
11121 window: &mut Window,
11122 cx: &mut Context<Self>,
11123 mut callback: Fn,
11124 ) where
11125 Fn: FnMut(&mut Vec<&str>),
11126 {
11127 self.manipulate_lines(window, cx, |text| {
11128 let mut lines: Vec<&str> = text.split('\n').collect();
11129 let line_count_before = lines.len();
11130
11131 callback(&mut lines);
11132
11133 LineManipulationResult {
11134 new_text: lines.join("\n"),
11135 line_count_before,
11136 line_count_after: lines.len(),
11137 }
11138 });
11139 }
11140
11141 fn manipulate_mutable_lines<Fn>(
11142 &mut self,
11143 window: &mut Window,
11144 cx: &mut Context<Self>,
11145 mut callback: Fn,
11146 ) where
11147 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11148 {
11149 self.manipulate_lines(window, cx, |text| {
11150 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11151 let line_count_before = lines.len();
11152
11153 callback(&mut lines);
11154
11155 LineManipulationResult {
11156 new_text: lines.join("\n"),
11157 line_count_before,
11158 line_count_after: lines.len(),
11159 }
11160 });
11161 }
11162
11163 pub fn convert_indentation_to_spaces(
11164 &mut self,
11165 _: &ConvertIndentationToSpaces,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 let settings = self.buffer.read(cx).language_settings(cx);
11170 let tab_size = settings.tab_size.get() as usize;
11171
11172 self.manipulate_mutable_lines(window, cx, |lines| {
11173 // Allocates a reasonably sized scratch buffer once for the whole loop
11174 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11175 // Avoids recomputing spaces that could be inserted many times
11176 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11177 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11178 .collect();
11179
11180 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11181 let mut chars = line.as_ref().chars();
11182 let mut col = 0;
11183 let mut changed = false;
11184
11185 for ch in chars.by_ref() {
11186 match ch {
11187 ' ' => {
11188 reindented_line.push(' ');
11189 col += 1;
11190 }
11191 '\t' => {
11192 // \t are converted to spaces depending on the current column
11193 let spaces_len = tab_size - (col % tab_size);
11194 reindented_line.extend(&space_cache[spaces_len - 1]);
11195 col += spaces_len;
11196 changed = true;
11197 }
11198 _ => {
11199 // If we dont append before break, the character is consumed
11200 reindented_line.push(ch);
11201 break;
11202 }
11203 }
11204 }
11205
11206 if !changed {
11207 reindented_line.clear();
11208 continue;
11209 }
11210 // Append the rest of the line and replace old reference with new one
11211 reindented_line.extend(chars);
11212 *line = Cow::Owned(reindented_line.clone());
11213 reindented_line.clear();
11214 }
11215 });
11216 }
11217
11218 pub fn convert_indentation_to_tabs(
11219 &mut self,
11220 _: &ConvertIndentationToTabs,
11221 window: &mut Window,
11222 cx: &mut Context<Self>,
11223 ) {
11224 let settings = self.buffer.read(cx).language_settings(cx);
11225 let tab_size = settings.tab_size.get() as usize;
11226
11227 self.manipulate_mutable_lines(window, cx, |lines| {
11228 // Allocates a reasonably sized buffer once for the whole loop
11229 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11230 // Avoids recomputing spaces that could be inserted many times
11231 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11232 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11233 .collect();
11234
11235 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11236 let mut chars = line.chars();
11237 let mut spaces_count = 0;
11238 let mut first_non_indent_char = None;
11239 let mut changed = false;
11240
11241 for ch in chars.by_ref() {
11242 match ch {
11243 ' ' => {
11244 // Keep track of spaces. Append \t when we reach tab_size
11245 spaces_count += 1;
11246 changed = true;
11247 if spaces_count == tab_size {
11248 reindented_line.push('\t');
11249 spaces_count = 0;
11250 }
11251 }
11252 '\t' => {
11253 reindented_line.push('\t');
11254 spaces_count = 0;
11255 }
11256 _ => {
11257 // Dont append it yet, we might have remaining spaces
11258 first_non_indent_char = Some(ch);
11259 break;
11260 }
11261 }
11262 }
11263
11264 if !changed {
11265 reindented_line.clear();
11266 continue;
11267 }
11268 // Remaining spaces that didn't make a full tab stop
11269 if spaces_count > 0 {
11270 reindented_line.extend(&space_cache[spaces_count - 1]);
11271 }
11272 // If we consume an extra character that was not indentation, add it back
11273 if let Some(extra_char) = first_non_indent_char {
11274 reindented_line.push(extra_char);
11275 }
11276 // Append the rest of the line and replace old reference with new one
11277 reindented_line.extend(chars);
11278 *line = Cow::Owned(reindented_line.clone());
11279 reindented_line.clear();
11280 }
11281 });
11282 }
11283
11284 pub fn convert_to_upper_case(
11285 &mut self,
11286 _: &ConvertToUpperCase,
11287 window: &mut Window,
11288 cx: &mut Context<Self>,
11289 ) {
11290 self.manipulate_text(window, cx, |text| text.to_uppercase())
11291 }
11292
11293 pub fn convert_to_lower_case(
11294 &mut self,
11295 _: &ConvertToLowerCase,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 ) {
11299 self.manipulate_text(window, cx, |text| text.to_lowercase())
11300 }
11301
11302 pub fn convert_to_title_case(
11303 &mut self,
11304 _: &ConvertToTitleCase,
11305 window: &mut Window,
11306 cx: &mut Context<Self>,
11307 ) {
11308 self.manipulate_text(window, cx, |text| {
11309 text.split('\n')
11310 .map(|line| line.to_case(Case::Title))
11311 .join("\n")
11312 })
11313 }
11314
11315 pub fn convert_to_snake_case(
11316 &mut self,
11317 _: &ConvertToSnakeCase,
11318 window: &mut Window,
11319 cx: &mut Context<Self>,
11320 ) {
11321 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11322 }
11323
11324 pub fn convert_to_kebab_case(
11325 &mut self,
11326 _: &ConvertToKebabCase,
11327 window: &mut Window,
11328 cx: &mut Context<Self>,
11329 ) {
11330 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11331 }
11332
11333 pub fn convert_to_upper_camel_case(
11334 &mut self,
11335 _: &ConvertToUpperCamelCase,
11336 window: &mut Window,
11337 cx: &mut Context<Self>,
11338 ) {
11339 self.manipulate_text(window, cx, |text| {
11340 text.split('\n')
11341 .map(|line| line.to_case(Case::UpperCamel))
11342 .join("\n")
11343 })
11344 }
11345
11346 pub fn convert_to_lower_camel_case(
11347 &mut self,
11348 _: &ConvertToLowerCamelCase,
11349 window: &mut Window,
11350 cx: &mut Context<Self>,
11351 ) {
11352 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11353 }
11354
11355 pub fn convert_to_opposite_case(
11356 &mut self,
11357 _: &ConvertToOppositeCase,
11358 window: &mut Window,
11359 cx: &mut Context<Self>,
11360 ) {
11361 self.manipulate_text(window, cx, |text| {
11362 text.chars()
11363 .fold(String::with_capacity(text.len()), |mut t, c| {
11364 if c.is_uppercase() {
11365 t.extend(c.to_lowercase());
11366 } else {
11367 t.extend(c.to_uppercase());
11368 }
11369 t
11370 })
11371 })
11372 }
11373
11374 pub fn convert_to_sentence_case(
11375 &mut self,
11376 _: &ConvertToSentenceCase,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 ) {
11380 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11381 }
11382
11383 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11384 self.manipulate_text(window, cx, |text| {
11385 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11386 if has_upper_case_characters {
11387 text.to_lowercase()
11388 } else {
11389 text.to_uppercase()
11390 }
11391 })
11392 }
11393
11394 pub fn convert_to_rot13(
11395 &mut self,
11396 _: &ConvertToRot13,
11397 window: &mut Window,
11398 cx: &mut Context<Self>,
11399 ) {
11400 self.manipulate_text(window, cx, |text| {
11401 text.chars()
11402 .map(|c| match c {
11403 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11404 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11405 _ => c,
11406 })
11407 .collect()
11408 })
11409 }
11410
11411 pub fn convert_to_rot47(
11412 &mut self,
11413 _: &ConvertToRot47,
11414 window: &mut Window,
11415 cx: &mut Context<Self>,
11416 ) {
11417 self.manipulate_text(window, cx, |text| {
11418 text.chars()
11419 .map(|c| {
11420 let code_point = c as u32;
11421 if code_point >= 33 && code_point <= 126 {
11422 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11423 }
11424 c
11425 })
11426 .collect()
11427 })
11428 }
11429
11430 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11431 where
11432 Fn: FnMut(&str) -> String,
11433 {
11434 let buffer = self.buffer.read(cx).snapshot(cx);
11435
11436 let mut new_selections = Vec::new();
11437 let mut edits = Vec::new();
11438 let mut selection_adjustment = 0i32;
11439
11440 for selection in self.selections.all_adjusted(cx) {
11441 let selection_is_empty = selection.is_empty();
11442
11443 let (start, end) = if selection_is_empty {
11444 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11445 (word_range.start, word_range.end)
11446 } else {
11447 (
11448 buffer.point_to_offset(selection.start),
11449 buffer.point_to_offset(selection.end),
11450 )
11451 };
11452
11453 let text = buffer.text_for_range(start..end).collect::<String>();
11454 let old_length = text.len() as i32;
11455 let text = callback(&text);
11456
11457 new_selections.push(Selection {
11458 start: (start as i32 - selection_adjustment) as usize,
11459 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11460 goal: SelectionGoal::None,
11461 id: selection.id,
11462 reversed: selection.reversed,
11463 });
11464
11465 selection_adjustment += old_length - text.len() as i32;
11466
11467 edits.push((start..end, text));
11468 }
11469
11470 self.transact(window, cx, |this, window, cx| {
11471 this.buffer.update(cx, |buffer, cx| {
11472 buffer.edit(edits, None, cx);
11473 });
11474
11475 this.change_selections(Default::default(), window, cx, |s| {
11476 s.select(new_selections);
11477 });
11478
11479 this.request_autoscroll(Autoscroll::fit(), cx);
11480 });
11481 }
11482
11483 pub fn move_selection_on_drop(
11484 &mut self,
11485 selection: &Selection<Anchor>,
11486 target: DisplayPoint,
11487 is_cut: bool,
11488 window: &mut Window,
11489 cx: &mut Context<Self>,
11490 ) {
11491 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11492 let buffer = &display_map.buffer_snapshot;
11493 let mut edits = Vec::new();
11494 let insert_point = display_map
11495 .clip_point(target, Bias::Left)
11496 .to_point(&display_map);
11497 let text = buffer
11498 .text_for_range(selection.start..selection.end)
11499 .collect::<String>();
11500 if is_cut {
11501 edits.push(((selection.start..selection.end), String::new()));
11502 }
11503 let insert_anchor = buffer.anchor_before(insert_point);
11504 edits.push(((insert_anchor..insert_anchor), text));
11505 let last_edit_start = insert_anchor.bias_left(buffer);
11506 let last_edit_end = insert_anchor.bias_right(buffer);
11507 self.transact(window, cx, |this, window, cx| {
11508 this.buffer.update(cx, |buffer, cx| {
11509 buffer.edit(edits, None, cx);
11510 });
11511 this.change_selections(Default::default(), window, cx, |s| {
11512 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11513 });
11514 });
11515 }
11516
11517 pub fn clear_selection_drag_state(&mut self) {
11518 self.selection_drag_state = SelectionDragState::None;
11519 }
11520
11521 pub fn duplicate(
11522 &mut self,
11523 upwards: bool,
11524 whole_lines: bool,
11525 window: &mut Window,
11526 cx: &mut Context<Self>,
11527 ) {
11528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11529
11530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11531 let buffer = &display_map.buffer_snapshot;
11532 let selections = self.selections.all::<Point>(cx);
11533
11534 let mut edits = Vec::new();
11535 let mut selections_iter = selections.iter().peekable();
11536 while let Some(selection) = selections_iter.next() {
11537 let mut rows = selection.spanned_rows(false, &display_map);
11538 // duplicate line-wise
11539 if whole_lines || selection.start == selection.end {
11540 // Avoid duplicating the same lines twice.
11541 while let Some(next_selection) = selections_iter.peek() {
11542 let next_rows = next_selection.spanned_rows(false, &display_map);
11543 if next_rows.start < rows.end {
11544 rows.end = next_rows.end;
11545 selections_iter.next().unwrap();
11546 } else {
11547 break;
11548 }
11549 }
11550
11551 // Copy the text from the selected row region and splice it either at the start
11552 // or end of the region.
11553 let start = Point::new(rows.start.0, 0);
11554 let end = Point::new(
11555 rows.end.previous_row().0,
11556 buffer.line_len(rows.end.previous_row()),
11557 );
11558 let text = buffer
11559 .text_for_range(start..end)
11560 .chain(Some("\n"))
11561 .collect::<String>();
11562 let insert_location = if upwards {
11563 Point::new(rows.end.0, 0)
11564 } else {
11565 start
11566 };
11567 edits.push((insert_location..insert_location, text));
11568 } else {
11569 // duplicate character-wise
11570 let start = selection.start;
11571 let end = selection.end;
11572 let text = buffer.text_for_range(start..end).collect::<String>();
11573 edits.push((selection.end..selection.end, text));
11574 }
11575 }
11576
11577 self.transact(window, cx, |this, _, cx| {
11578 this.buffer.update(cx, |buffer, cx| {
11579 buffer.edit(edits, None, cx);
11580 });
11581
11582 this.request_autoscroll(Autoscroll::fit(), cx);
11583 });
11584 }
11585
11586 pub fn duplicate_line_up(
11587 &mut self,
11588 _: &DuplicateLineUp,
11589 window: &mut Window,
11590 cx: &mut Context<Self>,
11591 ) {
11592 self.duplicate(true, true, window, cx);
11593 }
11594
11595 pub fn duplicate_line_down(
11596 &mut self,
11597 _: &DuplicateLineDown,
11598 window: &mut Window,
11599 cx: &mut Context<Self>,
11600 ) {
11601 self.duplicate(false, true, window, cx);
11602 }
11603
11604 pub fn duplicate_selection(
11605 &mut self,
11606 _: &DuplicateSelection,
11607 window: &mut Window,
11608 cx: &mut Context<Self>,
11609 ) {
11610 self.duplicate(false, false, window, cx);
11611 }
11612
11613 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11614 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11615 if self.mode.is_single_line() {
11616 cx.propagate();
11617 return;
11618 }
11619
11620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11621 let buffer = self.buffer.read(cx).snapshot(cx);
11622
11623 let mut edits = Vec::new();
11624 let mut unfold_ranges = Vec::new();
11625 let mut refold_creases = Vec::new();
11626
11627 let selections = self.selections.all::<Point>(cx);
11628 let mut selections = selections.iter().peekable();
11629 let mut contiguous_row_selections = Vec::new();
11630 let mut new_selections = Vec::new();
11631
11632 while let Some(selection) = selections.next() {
11633 // Find all the selections that span a contiguous row range
11634 let (start_row, end_row) = consume_contiguous_rows(
11635 &mut contiguous_row_selections,
11636 selection,
11637 &display_map,
11638 &mut selections,
11639 );
11640
11641 // Move the text spanned by the row range to be before the line preceding the row range
11642 if start_row.0 > 0 {
11643 let range_to_move = Point::new(
11644 start_row.previous_row().0,
11645 buffer.line_len(start_row.previous_row()),
11646 )
11647 ..Point::new(
11648 end_row.previous_row().0,
11649 buffer.line_len(end_row.previous_row()),
11650 );
11651 let insertion_point = display_map
11652 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11653 .0;
11654
11655 // Don't move lines across excerpts
11656 if buffer
11657 .excerpt_containing(insertion_point..range_to_move.end)
11658 .is_some()
11659 {
11660 let text = buffer
11661 .text_for_range(range_to_move.clone())
11662 .flat_map(|s| s.chars())
11663 .skip(1)
11664 .chain(['\n'])
11665 .collect::<String>();
11666
11667 edits.push((
11668 buffer.anchor_after(range_to_move.start)
11669 ..buffer.anchor_before(range_to_move.end),
11670 String::new(),
11671 ));
11672 let insertion_anchor = buffer.anchor_after(insertion_point);
11673 edits.push((insertion_anchor..insertion_anchor, text));
11674
11675 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11676
11677 // Move selections up
11678 new_selections.extend(contiguous_row_selections.drain(..).map(
11679 |mut selection| {
11680 selection.start.row -= row_delta;
11681 selection.end.row -= row_delta;
11682 selection
11683 },
11684 ));
11685
11686 // Move folds up
11687 unfold_ranges.push(range_to_move.clone());
11688 for fold in display_map.folds_in_range(
11689 buffer.anchor_before(range_to_move.start)
11690 ..buffer.anchor_after(range_to_move.end),
11691 ) {
11692 let mut start = fold.range.start.to_point(&buffer);
11693 let mut end = fold.range.end.to_point(&buffer);
11694 start.row -= row_delta;
11695 end.row -= row_delta;
11696 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11697 }
11698 }
11699 }
11700
11701 // If we didn't move line(s), preserve the existing selections
11702 new_selections.append(&mut contiguous_row_selections);
11703 }
11704
11705 self.transact(window, cx, |this, window, cx| {
11706 this.unfold_ranges(&unfold_ranges, true, true, cx);
11707 this.buffer.update(cx, |buffer, cx| {
11708 for (range, text) in edits {
11709 buffer.edit([(range, text)], None, cx);
11710 }
11711 });
11712 this.fold_creases(refold_creases, true, window, cx);
11713 this.change_selections(Default::default(), window, cx, |s| {
11714 s.select(new_selections);
11715 })
11716 });
11717 }
11718
11719 pub fn move_line_down(
11720 &mut self,
11721 _: &MoveLineDown,
11722 window: &mut Window,
11723 cx: &mut Context<Self>,
11724 ) {
11725 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11726 if self.mode.is_single_line() {
11727 cx.propagate();
11728 return;
11729 }
11730
11731 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11732 let buffer = self.buffer.read(cx).snapshot(cx);
11733
11734 let mut edits = Vec::new();
11735 let mut unfold_ranges = Vec::new();
11736 let mut refold_creases = Vec::new();
11737
11738 let selections = self.selections.all::<Point>(cx);
11739 let mut selections = selections.iter().peekable();
11740 let mut contiguous_row_selections = Vec::new();
11741 let mut new_selections = Vec::new();
11742
11743 while let Some(selection) = selections.next() {
11744 // Find all the selections that span a contiguous row range
11745 let (start_row, end_row) = consume_contiguous_rows(
11746 &mut contiguous_row_selections,
11747 selection,
11748 &display_map,
11749 &mut selections,
11750 );
11751
11752 // Move the text spanned by the row range to be after the last line of the row range
11753 if end_row.0 <= buffer.max_point().row {
11754 let range_to_move =
11755 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11756 let insertion_point = display_map
11757 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11758 .0;
11759
11760 // Don't move lines across excerpt boundaries
11761 if buffer
11762 .excerpt_containing(range_to_move.start..insertion_point)
11763 .is_some()
11764 {
11765 let mut text = String::from("\n");
11766 text.extend(buffer.text_for_range(range_to_move.clone()));
11767 text.pop(); // Drop trailing newline
11768 edits.push((
11769 buffer.anchor_after(range_to_move.start)
11770 ..buffer.anchor_before(range_to_move.end),
11771 String::new(),
11772 ));
11773 let insertion_anchor = buffer.anchor_after(insertion_point);
11774 edits.push((insertion_anchor..insertion_anchor, text));
11775
11776 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11777
11778 // Move selections down
11779 new_selections.extend(contiguous_row_selections.drain(..).map(
11780 |mut selection| {
11781 selection.start.row += row_delta;
11782 selection.end.row += row_delta;
11783 selection
11784 },
11785 ));
11786
11787 // Move folds down
11788 unfold_ranges.push(range_to_move.clone());
11789 for fold in display_map.folds_in_range(
11790 buffer.anchor_before(range_to_move.start)
11791 ..buffer.anchor_after(range_to_move.end),
11792 ) {
11793 let mut start = fold.range.start.to_point(&buffer);
11794 let mut end = fold.range.end.to_point(&buffer);
11795 start.row += row_delta;
11796 end.row += row_delta;
11797 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11798 }
11799 }
11800 }
11801
11802 // If we didn't move line(s), preserve the existing selections
11803 new_selections.append(&mut contiguous_row_selections);
11804 }
11805
11806 self.transact(window, cx, |this, window, cx| {
11807 this.unfold_ranges(&unfold_ranges, true, true, cx);
11808 this.buffer.update(cx, |buffer, cx| {
11809 for (range, text) in edits {
11810 buffer.edit([(range, text)], None, cx);
11811 }
11812 });
11813 this.fold_creases(refold_creases, true, window, cx);
11814 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11815 });
11816 }
11817
11818 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11820 let text_layout_details = &self.text_layout_details(window);
11821 self.transact(window, cx, |this, window, cx| {
11822 let edits = this.change_selections(Default::default(), window, cx, |s| {
11823 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11824 s.move_with(|display_map, selection| {
11825 if !selection.is_empty() {
11826 return;
11827 }
11828
11829 let mut head = selection.head();
11830 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11831 if head.column() == display_map.line_len(head.row()) {
11832 transpose_offset = display_map
11833 .buffer_snapshot
11834 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11835 }
11836
11837 if transpose_offset == 0 {
11838 return;
11839 }
11840
11841 *head.column_mut() += 1;
11842 head = display_map.clip_point(head, Bias::Right);
11843 let goal = SelectionGoal::HorizontalPosition(
11844 display_map
11845 .x_for_display_point(head, text_layout_details)
11846 .into(),
11847 );
11848 selection.collapse_to(head, goal);
11849
11850 let transpose_start = display_map
11851 .buffer_snapshot
11852 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11853 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11854 let transpose_end = display_map
11855 .buffer_snapshot
11856 .clip_offset(transpose_offset + 1, Bias::Right);
11857 if let Some(ch) =
11858 display_map.buffer_snapshot.chars_at(transpose_start).next()
11859 {
11860 edits.push((transpose_start..transpose_offset, String::new()));
11861 edits.push((transpose_end..transpose_end, ch.to_string()));
11862 }
11863 }
11864 });
11865 edits
11866 });
11867 this.buffer
11868 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11869 let selections = this.selections.all::<usize>(cx);
11870 this.change_selections(Default::default(), window, cx, |s| {
11871 s.select(selections);
11872 });
11873 });
11874 }
11875
11876 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11878 if self.mode.is_single_line() {
11879 cx.propagate();
11880 return;
11881 }
11882
11883 self.rewrap_impl(RewrapOptions::default(), cx)
11884 }
11885
11886 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11887 let buffer = self.buffer.read(cx).snapshot(cx);
11888 let selections = self.selections.all::<Point>(cx);
11889
11890 #[derive(Clone, Debug, PartialEq)]
11891 enum CommentFormat {
11892 /// single line comment, with prefix for line
11893 Line(String),
11894 /// single line within a block comment, with prefix for line
11895 BlockLine(String),
11896 /// a single line of a block comment that includes the initial delimiter
11897 BlockCommentWithStart(BlockCommentConfig),
11898 /// a single line of a block comment that includes the ending delimiter
11899 BlockCommentWithEnd(BlockCommentConfig),
11900 }
11901
11902 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11903 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11904 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11905 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11906 .peekable();
11907
11908 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11909 row
11910 } else {
11911 return Vec::new();
11912 };
11913
11914 let language_settings = buffer.language_settings_at(selection.head(), cx);
11915 let language_scope = buffer.language_scope_at(selection.head());
11916
11917 let indent_and_prefix_for_row =
11918 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11919 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11920 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11921 &language_scope
11922 {
11923 let indent_end = Point::new(row, indent.len);
11924 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11925 let line_text_after_indent = buffer
11926 .text_for_range(indent_end..line_end)
11927 .collect::<String>();
11928
11929 let is_within_comment_override = buffer
11930 .language_scope_at(indent_end)
11931 .is_some_and(|scope| scope.override_name() == Some("comment"));
11932 let comment_delimiters = if is_within_comment_override {
11933 // we are within a comment syntax node, but we don't
11934 // yet know what kind of comment: block, doc or line
11935 match (
11936 language_scope.documentation_comment(),
11937 language_scope.block_comment(),
11938 ) {
11939 (Some(config), _) | (_, Some(config))
11940 if buffer.contains_str_at(indent_end, &config.start) =>
11941 {
11942 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11943 }
11944 (Some(config), _) | (_, Some(config))
11945 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11946 {
11947 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11948 }
11949 (Some(config), _) | (_, Some(config))
11950 if buffer.contains_str_at(indent_end, &config.prefix) =>
11951 {
11952 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11953 }
11954 (_, _) => language_scope
11955 .line_comment_prefixes()
11956 .iter()
11957 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11958 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11959 }
11960 } else {
11961 // we not in an overridden comment node, but we may
11962 // be within a non-overridden line comment node
11963 language_scope
11964 .line_comment_prefixes()
11965 .iter()
11966 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11967 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11968 };
11969
11970 let rewrap_prefix = language_scope
11971 .rewrap_prefixes()
11972 .iter()
11973 .find_map(|prefix_regex| {
11974 prefix_regex.find(&line_text_after_indent).map(|mat| {
11975 if mat.start() == 0 {
11976 Some(mat.as_str().to_string())
11977 } else {
11978 None
11979 }
11980 })
11981 })
11982 .flatten();
11983 (comment_delimiters, rewrap_prefix)
11984 } else {
11985 (None, None)
11986 };
11987 (indent, comment_prefix, rewrap_prefix)
11988 };
11989
11990 let mut ranges = Vec::new();
11991 let from_empty_selection = selection.is_empty();
11992
11993 let mut current_range_start = first_row;
11994 let mut prev_row = first_row;
11995 let (
11996 mut current_range_indent,
11997 mut current_range_comment_delimiters,
11998 mut current_range_rewrap_prefix,
11999 ) = indent_and_prefix_for_row(first_row);
12000
12001 for row in non_blank_rows_iter.skip(1) {
12002 let has_paragraph_break = row > prev_row + 1;
12003
12004 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12005 indent_and_prefix_for_row(row);
12006
12007 let has_indent_change = row_indent != current_range_indent;
12008 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12009
12010 let has_boundary_change = has_comment_change
12011 || row_rewrap_prefix.is_some()
12012 || (has_indent_change && current_range_comment_delimiters.is_some());
12013
12014 if has_paragraph_break || has_boundary_change {
12015 ranges.push((
12016 language_settings.clone(),
12017 Point::new(current_range_start, 0)
12018 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12019 current_range_indent,
12020 current_range_comment_delimiters.clone(),
12021 current_range_rewrap_prefix.clone(),
12022 from_empty_selection,
12023 ));
12024 current_range_start = row;
12025 current_range_indent = row_indent;
12026 current_range_comment_delimiters = row_comment_delimiters;
12027 current_range_rewrap_prefix = row_rewrap_prefix;
12028 }
12029 prev_row = row;
12030 }
12031
12032 ranges.push((
12033 language_settings.clone(),
12034 Point::new(current_range_start, 0)
12035 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12036 current_range_indent,
12037 current_range_comment_delimiters,
12038 current_range_rewrap_prefix,
12039 from_empty_selection,
12040 ));
12041
12042 ranges
12043 });
12044
12045 let mut edits = Vec::new();
12046 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12047
12048 for (
12049 language_settings,
12050 wrap_range,
12051 mut indent_size,
12052 comment_prefix,
12053 rewrap_prefix,
12054 from_empty_selection,
12055 ) in wrap_ranges
12056 {
12057 let mut start_row = wrap_range.start.row;
12058 let mut end_row = wrap_range.end.row;
12059
12060 // Skip selections that overlap with a range that has already been rewrapped.
12061 let selection_range = start_row..end_row;
12062 if rewrapped_row_ranges
12063 .iter()
12064 .any(|range| range.overlaps(&selection_range))
12065 {
12066 continue;
12067 }
12068
12069 let tab_size = language_settings.tab_size;
12070
12071 let (line_prefix, inside_comment) = match &comment_prefix {
12072 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12073 (Some(prefix.as_str()), true)
12074 }
12075 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12076 (Some(prefix.as_ref()), true)
12077 }
12078 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12079 start: _,
12080 end: _,
12081 prefix,
12082 tab_size,
12083 })) => {
12084 indent_size.len += tab_size;
12085 (Some(prefix.as_ref()), true)
12086 }
12087 None => (None, false),
12088 };
12089 let indent_prefix = indent_size.chars().collect::<String>();
12090 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12091
12092 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12093 RewrapBehavior::InComments => inside_comment,
12094 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12095 RewrapBehavior::Anywhere => true,
12096 };
12097
12098 let should_rewrap = options.override_language_settings
12099 || allow_rewrap_based_on_language
12100 || self.hard_wrap.is_some();
12101 if !should_rewrap {
12102 continue;
12103 }
12104
12105 if from_empty_selection {
12106 'expand_upwards: while start_row > 0 {
12107 let prev_row = start_row - 1;
12108 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12109 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12110 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12111 {
12112 start_row = prev_row;
12113 } else {
12114 break 'expand_upwards;
12115 }
12116 }
12117
12118 'expand_downwards: while end_row < buffer.max_point().row {
12119 let next_row = end_row + 1;
12120 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12121 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12122 && !buffer.is_line_blank(MultiBufferRow(next_row))
12123 {
12124 end_row = next_row;
12125 } else {
12126 break 'expand_downwards;
12127 }
12128 }
12129 }
12130
12131 let start = Point::new(start_row, 0);
12132 let start_offset = start.to_offset(&buffer);
12133 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12134 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12135 let mut first_line_delimiter = None;
12136 let mut last_line_delimiter = None;
12137 let Some(lines_without_prefixes) = selection_text
12138 .lines()
12139 .enumerate()
12140 .map(|(ix, line)| {
12141 let line_trimmed = line.trim_start();
12142 if rewrap_prefix.is_some() && ix > 0 {
12143 Ok(line_trimmed)
12144 } else if let Some(
12145 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12146 start,
12147 prefix,
12148 end,
12149 tab_size,
12150 })
12151 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12152 start,
12153 prefix,
12154 end,
12155 tab_size,
12156 }),
12157 ) = &comment_prefix
12158 {
12159 let line_trimmed = line_trimmed
12160 .strip_prefix(start.as_ref())
12161 .map(|s| {
12162 let mut indent_size = indent_size;
12163 indent_size.len -= tab_size;
12164 let indent_prefix: String = indent_size.chars().collect();
12165 first_line_delimiter = Some((indent_prefix, start));
12166 s.trim_start()
12167 })
12168 .unwrap_or(line_trimmed);
12169 let line_trimmed = line_trimmed
12170 .strip_suffix(end.as_ref())
12171 .map(|s| {
12172 last_line_delimiter = Some(end);
12173 s.trim_end()
12174 })
12175 .unwrap_or(line_trimmed);
12176 let line_trimmed = line_trimmed
12177 .strip_prefix(prefix.as_ref())
12178 .unwrap_or(line_trimmed);
12179 Ok(line_trimmed)
12180 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12181 line_trimmed.strip_prefix(prefix).with_context(|| {
12182 format!("line did not start with prefix {prefix:?}: {line:?}")
12183 })
12184 } else {
12185 line_trimmed
12186 .strip_prefix(&line_prefix.trim_start())
12187 .with_context(|| {
12188 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12189 })
12190 }
12191 })
12192 .collect::<Result<Vec<_>, _>>()
12193 .log_err()
12194 else {
12195 continue;
12196 };
12197
12198 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12199 buffer
12200 .language_settings_at(Point::new(start_row, 0), cx)
12201 .preferred_line_length as usize
12202 });
12203
12204 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12205 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12206 } else {
12207 line_prefix.clone()
12208 };
12209
12210 let wrapped_text = {
12211 let mut wrapped_text = wrap_with_prefix(
12212 line_prefix,
12213 subsequent_lines_prefix,
12214 lines_without_prefixes.join("\n"),
12215 wrap_column,
12216 tab_size,
12217 options.preserve_existing_whitespace,
12218 );
12219
12220 if let Some((indent, delimiter)) = first_line_delimiter {
12221 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12222 }
12223 if let Some(last_line) = last_line_delimiter {
12224 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12225 }
12226
12227 wrapped_text
12228 };
12229
12230 // TODO: should always use char-based diff while still supporting cursor behavior that
12231 // matches vim.
12232 let mut diff_options = DiffOptions::default();
12233 if options.override_language_settings {
12234 diff_options.max_word_diff_len = 0;
12235 diff_options.max_word_diff_line_count = 0;
12236 } else {
12237 diff_options.max_word_diff_len = usize::MAX;
12238 diff_options.max_word_diff_line_count = usize::MAX;
12239 }
12240
12241 for (old_range, new_text) in
12242 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12243 {
12244 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12245 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12246 edits.push((edit_start..edit_end, new_text));
12247 }
12248
12249 rewrapped_row_ranges.push(start_row..=end_row);
12250 }
12251
12252 self.buffer
12253 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12254 }
12255
12256 pub fn cut_common(
12257 &mut self,
12258 cut_no_selection_line: bool,
12259 window: &mut Window,
12260 cx: &mut Context<Self>,
12261 ) -> ClipboardItem {
12262 let mut text = String::new();
12263 let buffer = self.buffer.read(cx).snapshot(cx);
12264 let mut selections = self.selections.all::<Point>(cx);
12265 let mut clipboard_selections = Vec::with_capacity(selections.len());
12266 {
12267 let max_point = buffer.max_point();
12268 let mut is_first = true;
12269 for selection in &mut selections {
12270 let is_entire_line =
12271 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12272 if is_entire_line {
12273 selection.start = Point::new(selection.start.row, 0);
12274 if !selection.is_empty() && selection.end.column == 0 {
12275 selection.end = cmp::min(max_point, selection.end);
12276 } else {
12277 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12278 }
12279 selection.goal = SelectionGoal::None;
12280 }
12281 if is_first {
12282 is_first = false;
12283 } else {
12284 text += "\n";
12285 }
12286 let mut len = 0;
12287 for chunk in buffer.text_for_range(selection.start..selection.end) {
12288 text.push_str(chunk);
12289 len += chunk.len();
12290 }
12291 clipboard_selections.push(ClipboardSelection {
12292 len,
12293 is_entire_line,
12294 first_line_indent: buffer
12295 .indent_size_for_line(MultiBufferRow(selection.start.row))
12296 .len,
12297 });
12298 }
12299 }
12300
12301 self.transact(window, cx, |this, window, cx| {
12302 this.change_selections(Default::default(), window, cx, |s| {
12303 s.select(selections);
12304 });
12305 this.insert("", window, cx);
12306 });
12307 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12308 }
12309
12310 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12311 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12312 let item = self.cut_common(true, window, cx);
12313 cx.write_to_clipboard(item);
12314 }
12315
12316 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12318 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12319 s.move_with(|snapshot, sel| {
12320 if sel.is_empty() {
12321 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12322 }
12323 if sel.is_empty() {
12324 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12325 }
12326 });
12327 });
12328 let item = self.cut_common(true, window, cx);
12329 cx.set_global(KillRing(item))
12330 }
12331
12332 pub fn kill_ring_yank(
12333 &mut self,
12334 _: &KillRingYank,
12335 window: &mut Window,
12336 cx: &mut Context<Self>,
12337 ) {
12338 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12339 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12340 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12341 (kill_ring.text().to_string(), kill_ring.metadata_json())
12342 } else {
12343 return;
12344 }
12345 } else {
12346 return;
12347 };
12348 self.do_paste(&text, metadata, false, window, cx);
12349 }
12350
12351 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12352 self.do_copy(true, cx);
12353 }
12354
12355 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12356 self.do_copy(false, cx);
12357 }
12358
12359 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12360 let selections = self.selections.all::<Point>(cx);
12361 let buffer = self.buffer.read(cx).read(cx);
12362 let mut text = String::new();
12363
12364 let mut clipboard_selections = Vec::with_capacity(selections.len());
12365 {
12366 let max_point = buffer.max_point();
12367 let mut is_first = true;
12368 for selection in &selections {
12369 let mut start = selection.start;
12370 let mut end = selection.end;
12371 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12372 if is_entire_line {
12373 start = Point::new(start.row, 0);
12374 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12375 }
12376
12377 let mut trimmed_selections = Vec::new();
12378 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12379 let row = MultiBufferRow(start.row);
12380 let first_indent = buffer.indent_size_for_line(row);
12381 if first_indent.len == 0 || start.column > first_indent.len {
12382 trimmed_selections.push(start..end);
12383 } else {
12384 trimmed_selections.push(
12385 Point::new(row.0, first_indent.len)
12386 ..Point::new(row.0, buffer.line_len(row)),
12387 );
12388 for row in start.row + 1..=end.row {
12389 let mut line_len = buffer.line_len(MultiBufferRow(row));
12390 if row == end.row {
12391 line_len = end.column;
12392 }
12393 if line_len == 0 {
12394 trimmed_selections
12395 .push(Point::new(row, 0)..Point::new(row, line_len));
12396 continue;
12397 }
12398 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12399 if row_indent_size.len >= first_indent.len {
12400 trimmed_selections.push(
12401 Point::new(row, first_indent.len)..Point::new(row, line_len),
12402 );
12403 } else {
12404 trimmed_selections.clear();
12405 trimmed_selections.push(start..end);
12406 break;
12407 }
12408 }
12409 }
12410 } else {
12411 trimmed_selections.push(start..end);
12412 }
12413
12414 for trimmed_range in trimmed_selections {
12415 if is_first {
12416 is_first = false;
12417 } else {
12418 text += "\n";
12419 }
12420 let mut len = 0;
12421 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12422 text.push_str(chunk);
12423 len += chunk.len();
12424 }
12425 clipboard_selections.push(ClipboardSelection {
12426 len,
12427 is_entire_line,
12428 first_line_indent: buffer
12429 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12430 .len,
12431 });
12432 }
12433 }
12434 }
12435
12436 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12437 text,
12438 clipboard_selections,
12439 ));
12440 }
12441
12442 pub fn do_paste(
12443 &mut self,
12444 text: &String,
12445 clipboard_selections: Option<Vec<ClipboardSelection>>,
12446 handle_entire_lines: bool,
12447 window: &mut Window,
12448 cx: &mut Context<Self>,
12449 ) {
12450 if self.read_only(cx) {
12451 return;
12452 }
12453
12454 let clipboard_text = Cow::Borrowed(text);
12455
12456 self.transact(window, cx, |this, window, cx| {
12457 let had_active_edit_prediction = this.has_active_edit_prediction();
12458
12459 if let Some(mut clipboard_selections) = clipboard_selections {
12460 let old_selections = this.selections.all::<usize>(cx);
12461 let all_selections_were_entire_line =
12462 clipboard_selections.iter().all(|s| s.is_entire_line);
12463 let first_selection_indent_column =
12464 clipboard_selections.first().map(|s| s.first_line_indent);
12465 if clipboard_selections.len() != old_selections.len() {
12466 clipboard_selections.drain(..);
12467 }
12468 let cursor_offset = this.selections.last::<usize>(cx).head();
12469 let mut auto_indent_on_paste = true;
12470
12471 this.buffer.update(cx, |buffer, cx| {
12472 let snapshot = buffer.read(cx);
12473 auto_indent_on_paste = snapshot
12474 .language_settings_at(cursor_offset, cx)
12475 .auto_indent_on_paste;
12476
12477 let mut start_offset = 0;
12478 let mut edits = Vec::new();
12479 let mut original_indent_columns = Vec::new();
12480 for (ix, selection) in old_selections.iter().enumerate() {
12481 let to_insert;
12482 let entire_line;
12483 let original_indent_column;
12484 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12485 let end_offset = start_offset + clipboard_selection.len;
12486 to_insert = &clipboard_text[start_offset..end_offset];
12487 entire_line = clipboard_selection.is_entire_line;
12488 start_offset = end_offset + 1;
12489 original_indent_column = Some(clipboard_selection.first_line_indent);
12490 } else {
12491 to_insert = clipboard_text.as_str();
12492 entire_line = all_selections_were_entire_line;
12493 original_indent_column = first_selection_indent_column
12494 }
12495
12496 // If the corresponding selection was empty when this slice of the
12497 // clipboard text was written, then the entire line containing the
12498 // selection was copied. If this selection is also currently empty,
12499 // then paste the line before the current line of the buffer.
12500 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12501 let column = selection.start.to_point(&snapshot).column as usize;
12502 let line_start = selection.start - column;
12503 line_start..line_start
12504 } else {
12505 selection.range()
12506 };
12507
12508 edits.push((range, to_insert));
12509 original_indent_columns.push(original_indent_column);
12510 }
12511 drop(snapshot);
12512
12513 buffer.edit(
12514 edits,
12515 if auto_indent_on_paste {
12516 Some(AutoindentMode::Block {
12517 original_indent_columns,
12518 })
12519 } else {
12520 None
12521 },
12522 cx,
12523 );
12524 });
12525
12526 let selections = this.selections.all::<usize>(cx);
12527 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12528 } else {
12529 this.insert(&clipboard_text, window, cx);
12530 }
12531
12532 let trigger_in_words =
12533 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12534
12535 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12536 });
12537 }
12538
12539 pub fn diff_clipboard_with_selection(
12540 &mut self,
12541 _: &DiffClipboardWithSelection,
12542 window: &mut Window,
12543 cx: &mut Context<Self>,
12544 ) {
12545 let selections = self.selections.all::<usize>(cx);
12546
12547 if selections.is_empty() {
12548 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12549 return;
12550 };
12551
12552 let clipboard_text = match cx.read_from_clipboard() {
12553 Some(item) => match item.entries().first() {
12554 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12555 _ => None,
12556 },
12557 None => None,
12558 };
12559
12560 let Some(clipboard_text) = clipboard_text else {
12561 log::warn!("Clipboard doesn't contain text.");
12562 return;
12563 };
12564
12565 window.dispatch_action(
12566 Box::new(DiffClipboardWithSelectionData {
12567 clipboard_text,
12568 editor: cx.entity(),
12569 }),
12570 cx,
12571 );
12572 }
12573
12574 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12576 if let Some(item) = cx.read_from_clipboard() {
12577 let entries = item.entries();
12578
12579 match entries.first() {
12580 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12581 // of all the pasted entries.
12582 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12583 .do_paste(
12584 clipboard_string.text(),
12585 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12586 true,
12587 window,
12588 cx,
12589 ),
12590 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12591 }
12592 }
12593 }
12594
12595 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12596 if self.read_only(cx) {
12597 return;
12598 }
12599
12600 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12601
12602 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12603 if let Some((selections, _)) =
12604 self.selection_history.transaction(transaction_id).cloned()
12605 {
12606 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12607 s.select_anchors(selections.to_vec());
12608 });
12609 } else {
12610 log::error!(
12611 "No entry in selection_history found for undo. \
12612 This may correspond to a bug where undo does not update the selection. \
12613 If this is occurring, please add details to \
12614 https://github.com/zed-industries/zed/issues/22692"
12615 );
12616 }
12617 self.request_autoscroll(Autoscroll::fit(), cx);
12618 self.unmark_text(window, cx);
12619 self.refresh_edit_prediction(true, false, window, cx);
12620 cx.emit(EditorEvent::Edited { transaction_id });
12621 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12622 }
12623 }
12624
12625 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12626 if self.read_only(cx) {
12627 return;
12628 }
12629
12630 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12631
12632 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12633 if let Some((_, Some(selections))) =
12634 self.selection_history.transaction(transaction_id).cloned()
12635 {
12636 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12637 s.select_anchors(selections.to_vec());
12638 });
12639 } else {
12640 log::error!(
12641 "No entry in selection_history found for redo. \
12642 This may correspond to a bug where undo does not update the selection. \
12643 If this is occurring, please add details to \
12644 https://github.com/zed-industries/zed/issues/22692"
12645 );
12646 }
12647 self.request_autoscroll(Autoscroll::fit(), cx);
12648 self.unmark_text(window, cx);
12649 self.refresh_edit_prediction(true, false, window, cx);
12650 cx.emit(EditorEvent::Edited { transaction_id });
12651 }
12652 }
12653
12654 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12655 self.buffer
12656 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12657 }
12658
12659 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12660 self.buffer
12661 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12662 }
12663
12664 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12666 self.change_selections(Default::default(), window, cx, |s| {
12667 s.move_with(|map, selection| {
12668 let cursor = if selection.is_empty() {
12669 movement::left(map, selection.start)
12670 } else {
12671 selection.start
12672 };
12673 selection.collapse_to(cursor, SelectionGoal::None);
12674 });
12675 })
12676 }
12677
12678 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12680 self.change_selections(Default::default(), window, cx, |s| {
12681 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12682 })
12683 }
12684
12685 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12686 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12687 self.change_selections(Default::default(), window, cx, |s| {
12688 s.move_with(|map, selection| {
12689 let cursor = if selection.is_empty() {
12690 movement::right(map, selection.end)
12691 } else {
12692 selection.end
12693 };
12694 selection.collapse_to(cursor, SelectionGoal::None)
12695 });
12696 })
12697 }
12698
12699 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12701 self.change_selections(Default::default(), window, cx, |s| {
12702 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12703 })
12704 }
12705
12706 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12707 if self.take_rename(true, window, cx).is_some() {
12708 return;
12709 }
12710
12711 if self.mode.is_single_line() {
12712 cx.propagate();
12713 return;
12714 }
12715
12716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12717
12718 let text_layout_details = &self.text_layout_details(window);
12719 let selection_count = self.selections.count();
12720 let first_selection = self.selections.first_anchor();
12721
12722 self.change_selections(Default::default(), window, cx, |s| {
12723 s.move_with(|map, selection| {
12724 if !selection.is_empty() {
12725 selection.goal = SelectionGoal::None;
12726 }
12727 let (cursor, goal) = movement::up(
12728 map,
12729 selection.start,
12730 selection.goal,
12731 false,
12732 text_layout_details,
12733 );
12734 selection.collapse_to(cursor, goal);
12735 });
12736 });
12737
12738 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12739 {
12740 cx.propagate();
12741 }
12742 }
12743
12744 pub fn move_up_by_lines(
12745 &mut self,
12746 action: &MoveUpByLines,
12747 window: &mut Window,
12748 cx: &mut Context<Self>,
12749 ) {
12750 if self.take_rename(true, window, cx).is_some() {
12751 return;
12752 }
12753
12754 if self.mode.is_single_line() {
12755 cx.propagate();
12756 return;
12757 }
12758
12759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12760
12761 let text_layout_details = &self.text_layout_details(window);
12762
12763 self.change_selections(Default::default(), window, cx, |s| {
12764 s.move_with(|map, selection| {
12765 if !selection.is_empty() {
12766 selection.goal = SelectionGoal::None;
12767 }
12768 let (cursor, goal) = movement::up_by_rows(
12769 map,
12770 selection.start,
12771 action.lines,
12772 selection.goal,
12773 false,
12774 text_layout_details,
12775 );
12776 selection.collapse_to(cursor, goal);
12777 });
12778 })
12779 }
12780
12781 pub fn move_down_by_lines(
12782 &mut self,
12783 action: &MoveDownByLines,
12784 window: &mut Window,
12785 cx: &mut Context<Self>,
12786 ) {
12787 if self.take_rename(true, window, cx).is_some() {
12788 return;
12789 }
12790
12791 if self.mode.is_single_line() {
12792 cx.propagate();
12793 return;
12794 }
12795
12796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12797
12798 let text_layout_details = &self.text_layout_details(window);
12799
12800 self.change_selections(Default::default(), window, cx, |s| {
12801 s.move_with(|map, selection| {
12802 if !selection.is_empty() {
12803 selection.goal = SelectionGoal::None;
12804 }
12805 let (cursor, goal) = movement::down_by_rows(
12806 map,
12807 selection.start,
12808 action.lines,
12809 selection.goal,
12810 false,
12811 text_layout_details,
12812 );
12813 selection.collapse_to(cursor, goal);
12814 });
12815 })
12816 }
12817
12818 pub fn select_down_by_lines(
12819 &mut self,
12820 action: &SelectDownByLines,
12821 window: &mut Window,
12822 cx: &mut Context<Self>,
12823 ) {
12824 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12825 let text_layout_details = &self.text_layout_details(window);
12826 self.change_selections(Default::default(), window, cx, |s| {
12827 s.move_heads_with(|map, head, goal| {
12828 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12829 })
12830 })
12831 }
12832
12833 pub fn select_up_by_lines(
12834 &mut self,
12835 action: &SelectUpByLines,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) {
12839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12840 let text_layout_details = &self.text_layout_details(window);
12841 self.change_selections(Default::default(), window, cx, |s| {
12842 s.move_heads_with(|map, head, goal| {
12843 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12844 })
12845 })
12846 }
12847
12848 pub fn select_page_up(
12849 &mut self,
12850 _: &SelectPageUp,
12851 window: &mut Window,
12852 cx: &mut Context<Self>,
12853 ) {
12854 let Some(row_count) = self.visible_row_count() else {
12855 return;
12856 };
12857
12858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12859
12860 let text_layout_details = &self.text_layout_details(window);
12861
12862 self.change_selections(Default::default(), window, cx, |s| {
12863 s.move_heads_with(|map, head, goal| {
12864 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12865 })
12866 })
12867 }
12868
12869 pub fn move_page_up(
12870 &mut self,
12871 action: &MovePageUp,
12872 window: &mut Window,
12873 cx: &mut Context<Self>,
12874 ) {
12875 if self.take_rename(true, window, cx).is_some() {
12876 return;
12877 }
12878
12879 if self
12880 .context_menu
12881 .borrow_mut()
12882 .as_mut()
12883 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12884 .unwrap_or(false)
12885 {
12886 return;
12887 }
12888
12889 if matches!(self.mode, EditorMode::SingleLine) {
12890 cx.propagate();
12891 return;
12892 }
12893
12894 let Some(row_count) = self.visible_row_count() else {
12895 return;
12896 };
12897
12898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12899
12900 let effects = if action.center_cursor {
12901 SelectionEffects::scroll(Autoscroll::center())
12902 } else {
12903 SelectionEffects::default()
12904 };
12905
12906 let text_layout_details = &self.text_layout_details(window);
12907
12908 self.change_selections(effects, window, cx, |s| {
12909 s.move_with(|map, selection| {
12910 if !selection.is_empty() {
12911 selection.goal = SelectionGoal::None;
12912 }
12913 let (cursor, goal) = movement::up_by_rows(
12914 map,
12915 selection.end,
12916 row_count,
12917 selection.goal,
12918 false,
12919 text_layout_details,
12920 );
12921 selection.collapse_to(cursor, goal);
12922 });
12923 });
12924 }
12925
12926 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12928 let text_layout_details = &self.text_layout_details(window);
12929 self.change_selections(Default::default(), window, cx, |s| {
12930 s.move_heads_with(|map, head, goal| {
12931 movement::up(map, head, goal, false, text_layout_details)
12932 })
12933 })
12934 }
12935
12936 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12937 self.take_rename(true, window, cx);
12938
12939 if self.mode.is_single_line() {
12940 cx.propagate();
12941 return;
12942 }
12943
12944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12945
12946 let text_layout_details = &self.text_layout_details(window);
12947 let selection_count = self.selections.count();
12948 let first_selection = self.selections.first_anchor();
12949
12950 self.change_selections(Default::default(), window, cx, |s| {
12951 s.move_with(|map, selection| {
12952 if !selection.is_empty() {
12953 selection.goal = SelectionGoal::None;
12954 }
12955 let (cursor, goal) = movement::down(
12956 map,
12957 selection.end,
12958 selection.goal,
12959 false,
12960 text_layout_details,
12961 );
12962 selection.collapse_to(cursor, goal);
12963 });
12964 });
12965
12966 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12967 {
12968 cx.propagate();
12969 }
12970 }
12971
12972 pub fn select_page_down(
12973 &mut self,
12974 _: &SelectPageDown,
12975 window: &mut Window,
12976 cx: &mut Context<Self>,
12977 ) {
12978 let Some(row_count) = self.visible_row_count() else {
12979 return;
12980 };
12981
12982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12983
12984 let text_layout_details = &self.text_layout_details(window);
12985
12986 self.change_selections(Default::default(), window, cx, |s| {
12987 s.move_heads_with(|map, head, goal| {
12988 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12989 })
12990 })
12991 }
12992
12993 pub fn move_page_down(
12994 &mut self,
12995 action: &MovePageDown,
12996 window: &mut Window,
12997 cx: &mut Context<Self>,
12998 ) {
12999 if self.take_rename(true, window, cx).is_some() {
13000 return;
13001 }
13002
13003 if self
13004 .context_menu
13005 .borrow_mut()
13006 .as_mut()
13007 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13008 .unwrap_or(false)
13009 {
13010 return;
13011 }
13012
13013 if matches!(self.mode, EditorMode::SingleLine) {
13014 cx.propagate();
13015 return;
13016 }
13017
13018 let Some(row_count) = self.visible_row_count() else {
13019 return;
13020 };
13021
13022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13023
13024 let effects = if action.center_cursor {
13025 SelectionEffects::scroll(Autoscroll::center())
13026 } else {
13027 SelectionEffects::default()
13028 };
13029
13030 let text_layout_details = &self.text_layout_details(window);
13031 self.change_selections(effects, window, cx, |s| {
13032 s.move_with(|map, selection| {
13033 if !selection.is_empty() {
13034 selection.goal = SelectionGoal::None;
13035 }
13036 let (cursor, goal) = movement::down_by_rows(
13037 map,
13038 selection.end,
13039 row_count,
13040 selection.goal,
13041 false,
13042 text_layout_details,
13043 );
13044 selection.collapse_to(cursor, goal);
13045 });
13046 });
13047 }
13048
13049 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13051 let text_layout_details = &self.text_layout_details(window);
13052 self.change_selections(Default::default(), window, cx, |s| {
13053 s.move_heads_with(|map, head, goal| {
13054 movement::down(map, head, goal, false, text_layout_details)
13055 })
13056 });
13057 }
13058
13059 pub fn context_menu_first(
13060 &mut self,
13061 _: &ContextMenuFirst,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13066 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13067 }
13068 }
13069
13070 pub fn context_menu_prev(
13071 &mut self,
13072 _: &ContextMenuPrevious,
13073 window: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13077 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13078 }
13079 }
13080
13081 pub fn context_menu_next(
13082 &mut self,
13083 _: &ContextMenuNext,
13084 window: &mut Window,
13085 cx: &mut Context<Self>,
13086 ) {
13087 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13088 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13089 }
13090 }
13091
13092 pub fn context_menu_last(
13093 &mut self,
13094 _: &ContextMenuLast,
13095 window: &mut Window,
13096 cx: &mut Context<Self>,
13097 ) {
13098 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13099 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13100 }
13101 }
13102
13103 pub fn signature_help_prev(
13104 &mut self,
13105 _: &SignatureHelpPrevious,
13106 _: &mut Window,
13107 cx: &mut Context<Self>,
13108 ) {
13109 if let Some(popover) = self.signature_help_state.popover_mut() {
13110 if popover.current_signature == 0 {
13111 popover.current_signature = popover.signatures.len() - 1;
13112 } else {
13113 popover.current_signature -= 1;
13114 }
13115 cx.notify();
13116 }
13117 }
13118
13119 pub fn signature_help_next(
13120 &mut self,
13121 _: &SignatureHelpNext,
13122 _: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 if let Some(popover) = self.signature_help_state.popover_mut() {
13126 if popover.current_signature + 1 == popover.signatures.len() {
13127 popover.current_signature = 0;
13128 } else {
13129 popover.current_signature += 1;
13130 }
13131 cx.notify();
13132 }
13133 }
13134
13135 pub fn move_to_previous_word_start(
13136 &mut self,
13137 _: &MoveToPreviousWordStart,
13138 window: &mut Window,
13139 cx: &mut Context<Self>,
13140 ) {
13141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13142 self.change_selections(Default::default(), window, cx, |s| {
13143 s.move_cursors_with(|map, head, _| {
13144 (
13145 movement::previous_word_start(map, head),
13146 SelectionGoal::None,
13147 )
13148 });
13149 })
13150 }
13151
13152 pub fn move_to_previous_subword_start(
13153 &mut self,
13154 _: &MoveToPreviousSubwordStart,
13155 window: &mut Window,
13156 cx: &mut Context<Self>,
13157 ) {
13158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13159 self.change_selections(Default::default(), window, cx, |s| {
13160 s.move_cursors_with(|map, head, _| {
13161 (
13162 movement::previous_subword_start(map, head),
13163 SelectionGoal::None,
13164 )
13165 });
13166 })
13167 }
13168
13169 pub fn select_to_previous_word_start(
13170 &mut self,
13171 _: &SelectToPreviousWordStart,
13172 window: &mut Window,
13173 cx: &mut Context<Self>,
13174 ) {
13175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13176 self.change_selections(Default::default(), window, cx, |s| {
13177 s.move_heads_with(|map, head, _| {
13178 (
13179 movement::previous_word_start(map, head),
13180 SelectionGoal::None,
13181 )
13182 });
13183 })
13184 }
13185
13186 pub fn select_to_previous_subword_start(
13187 &mut self,
13188 _: &SelectToPreviousSubwordStart,
13189 window: &mut Window,
13190 cx: &mut Context<Self>,
13191 ) {
13192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13193 self.change_selections(Default::default(), window, cx, |s| {
13194 s.move_heads_with(|map, head, _| {
13195 (
13196 movement::previous_subword_start(map, head),
13197 SelectionGoal::None,
13198 )
13199 });
13200 })
13201 }
13202
13203 pub fn delete_to_previous_word_start(
13204 &mut self,
13205 action: &DeleteToPreviousWordStart,
13206 window: &mut Window,
13207 cx: &mut Context<Self>,
13208 ) {
13209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13210 self.transact(window, cx, |this, window, cx| {
13211 this.select_autoclose_pair(window, cx);
13212 this.change_selections(Default::default(), window, cx, |s| {
13213 s.move_with(|map, selection| {
13214 if selection.is_empty() {
13215 let mut cursor = if action.ignore_newlines {
13216 movement::previous_word_start(map, selection.head())
13217 } else {
13218 movement::previous_word_start_or_newline(map, selection.head())
13219 };
13220 cursor = movement::adjust_greedy_deletion(
13221 map,
13222 selection.head(),
13223 cursor,
13224 action.ignore_brackets,
13225 );
13226 selection.set_head(cursor, SelectionGoal::None);
13227 }
13228 });
13229 });
13230 this.insert("", window, cx);
13231 });
13232 }
13233
13234 pub fn delete_to_previous_subword_start(
13235 &mut self,
13236 _: &DeleteToPreviousSubwordStart,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) {
13240 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13241 self.transact(window, cx, |this, window, cx| {
13242 this.select_autoclose_pair(window, cx);
13243 this.change_selections(Default::default(), window, cx, |s| {
13244 s.move_with(|map, selection| {
13245 if selection.is_empty() {
13246 let mut cursor = movement::previous_subword_start(map, selection.head());
13247 cursor =
13248 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13249 selection.set_head(cursor, SelectionGoal::None);
13250 }
13251 });
13252 });
13253 this.insert("", window, cx);
13254 });
13255 }
13256
13257 pub fn move_to_next_word_end(
13258 &mut self,
13259 _: &MoveToNextWordEnd,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) {
13263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13264 self.change_selections(Default::default(), window, cx, |s| {
13265 s.move_cursors_with(|map, head, _| {
13266 (movement::next_word_end(map, head), SelectionGoal::None)
13267 });
13268 })
13269 }
13270
13271 pub fn move_to_next_subword_end(
13272 &mut self,
13273 _: &MoveToNextSubwordEnd,
13274 window: &mut Window,
13275 cx: &mut Context<Self>,
13276 ) {
13277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13278 self.change_selections(Default::default(), window, cx, |s| {
13279 s.move_cursors_with(|map, head, _| {
13280 (movement::next_subword_end(map, head), SelectionGoal::None)
13281 });
13282 })
13283 }
13284
13285 pub fn select_to_next_word_end(
13286 &mut self,
13287 _: &SelectToNextWordEnd,
13288 window: &mut Window,
13289 cx: &mut Context<Self>,
13290 ) {
13291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13292 self.change_selections(Default::default(), window, cx, |s| {
13293 s.move_heads_with(|map, head, _| {
13294 (movement::next_word_end(map, head), SelectionGoal::None)
13295 });
13296 })
13297 }
13298
13299 pub fn select_to_next_subword_end(
13300 &mut self,
13301 _: &SelectToNextSubwordEnd,
13302 window: &mut Window,
13303 cx: &mut Context<Self>,
13304 ) {
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13306 self.change_selections(Default::default(), window, cx, |s| {
13307 s.move_heads_with(|map, head, _| {
13308 (movement::next_subword_end(map, head), SelectionGoal::None)
13309 });
13310 })
13311 }
13312
13313 pub fn delete_to_next_word_end(
13314 &mut self,
13315 action: &DeleteToNextWordEnd,
13316 window: &mut Window,
13317 cx: &mut Context<Self>,
13318 ) {
13319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13320 self.transact(window, cx, |this, window, cx| {
13321 this.change_selections(Default::default(), window, cx, |s| {
13322 s.move_with(|map, selection| {
13323 if selection.is_empty() {
13324 let mut cursor = if action.ignore_newlines {
13325 movement::next_word_end(map, selection.head())
13326 } else {
13327 movement::next_word_end_or_newline(map, selection.head())
13328 };
13329 cursor = movement::adjust_greedy_deletion(
13330 map,
13331 selection.head(),
13332 cursor,
13333 action.ignore_brackets,
13334 );
13335 selection.set_head(cursor, SelectionGoal::None);
13336 }
13337 });
13338 });
13339 this.insert("", window, cx);
13340 });
13341 }
13342
13343 pub fn delete_to_next_subword_end(
13344 &mut self,
13345 _: &DeleteToNextSubwordEnd,
13346 window: &mut Window,
13347 cx: &mut Context<Self>,
13348 ) {
13349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13350 self.transact(window, cx, |this, window, cx| {
13351 this.change_selections(Default::default(), window, cx, |s| {
13352 s.move_with(|map, selection| {
13353 if selection.is_empty() {
13354 let mut cursor = movement::next_subword_end(map, selection.head());
13355 cursor =
13356 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13357 selection.set_head(cursor, SelectionGoal::None);
13358 }
13359 });
13360 });
13361 this.insert("", window, cx);
13362 });
13363 }
13364
13365 pub fn move_to_beginning_of_line(
13366 &mut self,
13367 action: &MoveToBeginningOfLine,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13372 self.change_selections(Default::default(), window, cx, |s| {
13373 s.move_cursors_with(|map, head, _| {
13374 (
13375 movement::indented_line_beginning(
13376 map,
13377 head,
13378 action.stop_at_soft_wraps,
13379 action.stop_at_indent,
13380 ),
13381 SelectionGoal::None,
13382 )
13383 });
13384 })
13385 }
13386
13387 pub fn select_to_beginning_of_line(
13388 &mut self,
13389 action: &SelectToBeginningOfLine,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13394 self.change_selections(Default::default(), window, cx, |s| {
13395 s.move_heads_with(|map, head, _| {
13396 (
13397 movement::indented_line_beginning(
13398 map,
13399 head,
13400 action.stop_at_soft_wraps,
13401 action.stop_at_indent,
13402 ),
13403 SelectionGoal::None,
13404 )
13405 });
13406 });
13407 }
13408
13409 pub fn delete_to_beginning_of_line(
13410 &mut self,
13411 action: &DeleteToBeginningOfLine,
13412 window: &mut Window,
13413 cx: &mut Context<Self>,
13414 ) {
13415 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13416 self.transact(window, cx, |this, window, cx| {
13417 this.change_selections(Default::default(), window, cx, |s| {
13418 s.move_with(|_, selection| {
13419 selection.reversed = true;
13420 });
13421 });
13422
13423 this.select_to_beginning_of_line(
13424 &SelectToBeginningOfLine {
13425 stop_at_soft_wraps: false,
13426 stop_at_indent: action.stop_at_indent,
13427 },
13428 window,
13429 cx,
13430 );
13431 this.backspace(&Backspace, window, cx);
13432 });
13433 }
13434
13435 pub fn move_to_end_of_line(
13436 &mut self,
13437 action: &MoveToEndOfLine,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13442 self.change_selections(Default::default(), window, cx, |s| {
13443 s.move_cursors_with(|map, head, _| {
13444 (
13445 movement::line_end(map, head, action.stop_at_soft_wraps),
13446 SelectionGoal::None,
13447 )
13448 });
13449 })
13450 }
13451
13452 pub fn select_to_end_of_line(
13453 &mut self,
13454 action: &SelectToEndOfLine,
13455 window: &mut Window,
13456 cx: &mut Context<Self>,
13457 ) {
13458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13459 self.change_selections(Default::default(), window, cx, |s| {
13460 s.move_heads_with(|map, head, _| {
13461 (
13462 movement::line_end(map, head, action.stop_at_soft_wraps),
13463 SelectionGoal::None,
13464 )
13465 });
13466 })
13467 }
13468
13469 pub fn delete_to_end_of_line(
13470 &mut self,
13471 _: &DeleteToEndOfLine,
13472 window: &mut Window,
13473 cx: &mut Context<Self>,
13474 ) {
13475 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13476 self.transact(window, cx, |this, window, cx| {
13477 this.select_to_end_of_line(
13478 &SelectToEndOfLine {
13479 stop_at_soft_wraps: false,
13480 },
13481 window,
13482 cx,
13483 );
13484 this.delete(&Delete, window, cx);
13485 });
13486 }
13487
13488 pub fn cut_to_end_of_line(
13489 &mut self,
13490 action: &CutToEndOfLine,
13491 window: &mut Window,
13492 cx: &mut Context<Self>,
13493 ) {
13494 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13495 self.transact(window, cx, |this, window, cx| {
13496 this.select_to_end_of_line(
13497 &SelectToEndOfLine {
13498 stop_at_soft_wraps: false,
13499 },
13500 window,
13501 cx,
13502 );
13503 if !action.stop_at_newlines {
13504 this.change_selections(Default::default(), window, cx, |s| {
13505 s.move_with(|_, sel| {
13506 if sel.is_empty() {
13507 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13508 }
13509 });
13510 });
13511 }
13512 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13513 let item = this.cut_common(false, window, cx);
13514 cx.write_to_clipboard(item);
13515 });
13516 }
13517
13518 pub fn move_to_start_of_paragraph(
13519 &mut self,
13520 _: &MoveToStartOfParagraph,
13521 window: &mut Window,
13522 cx: &mut Context<Self>,
13523 ) {
13524 if matches!(self.mode, EditorMode::SingleLine) {
13525 cx.propagate();
13526 return;
13527 }
13528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13529 self.change_selections(Default::default(), window, cx, |s| {
13530 s.move_with(|map, selection| {
13531 selection.collapse_to(
13532 movement::start_of_paragraph(map, selection.head(), 1),
13533 SelectionGoal::None,
13534 )
13535 });
13536 })
13537 }
13538
13539 pub fn move_to_end_of_paragraph(
13540 &mut self,
13541 _: &MoveToEndOfParagraph,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) {
13545 if matches!(self.mode, EditorMode::SingleLine) {
13546 cx.propagate();
13547 return;
13548 }
13549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13550 self.change_selections(Default::default(), window, cx, |s| {
13551 s.move_with(|map, selection| {
13552 selection.collapse_to(
13553 movement::end_of_paragraph(map, selection.head(), 1),
13554 SelectionGoal::None,
13555 )
13556 });
13557 })
13558 }
13559
13560 pub fn select_to_start_of_paragraph(
13561 &mut self,
13562 _: &SelectToStartOfParagraph,
13563 window: &mut Window,
13564 cx: &mut Context<Self>,
13565 ) {
13566 if matches!(self.mode, EditorMode::SingleLine) {
13567 cx.propagate();
13568 return;
13569 }
13570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13571 self.change_selections(Default::default(), window, cx, |s| {
13572 s.move_heads_with(|map, head, _| {
13573 (
13574 movement::start_of_paragraph(map, head, 1),
13575 SelectionGoal::None,
13576 )
13577 });
13578 })
13579 }
13580
13581 pub fn select_to_end_of_paragraph(
13582 &mut self,
13583 _: &SelectToEndOfParagraph,
13584 window: &mut Window,
13585 cx: &mut Context<Self>,
13586 ) {
13587 if matches!(self.mode, EditorMode::SingleLine) {
13588 cx.propagate();
13589 return;
13590 }
13591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13592 self.change_selections(Default::default(), window, cx, |s| {
13593 s.move_heads_with(|map, head, _| {
13594 (
13595 movement::end_of_paragraph(map, head, 1),
13596 SelectionGoal::None,
13597 )
13598 });
13599 })
13600 }
13601
13602 pub fn move_to_start_of_excerpt(
13603 &mut self,
13604 _: &MoveToStartOfExcerpt,
13605 window: &mut Window,
13606 cx: &mut Context<Self>,
13607 ) {
13608 if matches!(self.mode, EditorMode::SingleLine) {
13609 cx.propagate();
13610 return;
13611 }
13612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13613 self.change_selections(Default::default(), window, cx, |s| {
13614 s.move_with(|map, selection| {
13615 selection.collapse_to(
13616 movement::start_of_excerpt(
13617 map,
13618 selection.head(),
13619 workspace::searchable::Direction::Prev,
13620 ),
13621 SelectionGoal::None,
13622 )
13623 });
13624 })
13625 }
13626
13627 pub fn move_to_start_of_next_excerpt(
13628 &mut self,
13629 _: &MoveToStartOfNextExcerpt,
13630 window: &mut Window,
13631 cx: &mut Context<Self>,
13632 ) {
13633 if matches!(self.mode, EditorMode::SingleLine) {
13634 cx.propagate();
13635 return;
13636 }
13637
13638 self.change_selections(Default::default(), window, cx, |s| {
13639 s.move_with(|map, selection| {
13640 selection.collapse_to(
13641 movement::start_of_excerpt(
13642 map,
13643 selection.head(),
13644 workspace::searchable::Direction::Next,
13645 ),
13646 SelectionGoal::None,
13647 )
13648 });
13649 })
13650 }
13651
13652 pub fn move_to_end_of_excerpt(
13653 &mut self,
13654 _: &MoveToEndOfExcerpt,
13655 window: &mut Window,
13656 cx: &mut Context<Self>,
13657 ) {
13658 if matches!(self.mode, EditorMode::SingleLine) {
13659 cx.propagate();
13660 return;
13661 }
13662 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13663 self.change_selections(Default::default(), window, cx, |s| {
13664 s.move_with(|map, selection| {
13665 selection.collapse_to(
13666 movement::end_of_excerpt(
13667 map,
13668 selection.head(),
13669 workspace::searchable::Direction::Next,
13670 ),
13671 SelectionGoal::None,
13672 )
13673 });
13674 })
13675 }
13676
13677 pub fn move_to_end_of_previous_excerpt(
13678 &mut self,
13679 _: &MoveToEndOfPreviousExcerpt,
13680 window: &mut Window,
13681 cx: &mut Context<Self>,
13682 ) {
13683 if matches!(self.mode, EditorMode::SingleLine) {
13684 cx.propagate();
13685 return;
13686 }
13687 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13688 self.change_selections(Default::default(), window, cx, |s| {
13689 s.move_with(|map, selection| {
13690 selection.collapse_to(
13691 movement::end_of_excerpt(
13692 map,
13693 selection.head(),
13694 workspace::searchable::Direction::Prev,
13695 ),
13696 SelectionGoal::None,
13697 )
13698 });
13699 })
13700 }
13701
13702 pub fn select_to_start_of_excerpt(
13703 &mut self,
13704 _: &SelectToStartOfExcerpt,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 if matches!(self.mode, EditorMode::SingleLine) {
13709 cx.propagate();
13710 return;
13711 }
13712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13713 self.change_selections(Default::default(), window, cx, |s| {
13714 s.move_heads_with(|map, head, _| {
13715 (
13716 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13717 SelectionGoal::None,
13718 )
13719 });
13720 })
13721 }
13722
13723 pub fn select_to_start_of_next_excerpt(
13724 &mut self,
13725 _: &SelectToStartOfNextExcerpt,
13726 window: &mut Window,
13727 cx: &mut Context<Self>,
13728 ) {
13729 if matches!(self.mode, EditorMode::SingleLine) {
13730 cx.propagate();
13731 return;
13732 }
13733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13734 self.change_selections(Default::default(), window, cx, |s| {
13735 s.move_heads_with(|map, head, _| {
13736 (
13737 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13738 SelectionGoal::None,
13739 )
13740 });
13741 })
13742 }
13743
13744 pub fn select_to_end_of_excerpt(
13745 &mut self,
13746 _: &SelectToEndOfExcerpt,
13747 window: &mut Window,
13748 cx: &mut Context<Self>,
13749 ) {
13750 if matches!(self.mode, EditorMode::SingleLine) {
13751 cx.propagate();
13752 return;
13753 }
13754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13755 self.change_selections(Default::default(), window, cx, |s| {
13756 s.move_heads_with(|map, head, _| {
13757 (
13758 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13759 SelectionGoal::None,
13760 )
13761 });
13762 })
13763 }
13764
13765 pub fn select_to_end_of_previous_excerpt(
13766 &mut self,
13767 _: &SelectToEndOfPreviousExcerpt,
13768 window: &mut Window,
13769 cx: &mut Context<Self>,
13770 ) {
13771 if matches!(self.mode, EditorMode::SingleLine) {
13772 cx.propagate();
13773 return;
13774 }
13775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13776 self.change_selections(Default::default(), window, cx, |s| {
13777 s.move_heads_with(|map, head, _| {
13778 (
13779 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13780 SelectionGoal::None,
13781 )
13782 });
13783 })
13784 }
13785
13786 pub fn move_to_beginning(
13787 &mut self,
13788 _: &MoveToBeginning,
13789 window: &mut Window,
13790 cx: &mut Context<Self>,
13791 ) {
13792 if matches!(self.mode, EditorMode::SingleLine) {
13793 cx.propagate();
13794 return;
13795 }
13796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13797 self.change_selections(Default::default(), window, cx, |s| {
13798 s.select_ranges(vec![0..0]);
13799 });
13800 }
13801
13802 pub fn select_to_beginning(
13803 &mut self,
13804 _: &SelectToBeginning,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) {
13808 let mut selection = self.selections.last::<Point>(cx);
13809 selection.set_head(Point::zero(), SelectionGoal::None);
13810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13811 self.change_selections(Default::default(), window, cx, |s| {
13812 s.select(vec![selection]);
13813 });
13814 }
13815
13816 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13817 if matches!(self.mode, EditorMode::SingleLine) {
13818 cx.propagate();
13819 return;
13820 }
13821 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13822 let cursor = self.buffer.read(cx).read(cx).len();
13823 self.change_selections(Default::default(), window, cx, |s| {
13824 s.select_ranges(vec![cursor..cursor])
13825 });
13826 }
13827
13828 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13829 self.nav_history = nav_history;
13830 }
13831
13832 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13833 self.nav_history.as_ref()
13834 }
13835
13836 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13837 self.push_to_nav_history(
13838 self.selections.newest_anchor().head(),
13839 None,
13840 false,
13841 true,
13842 cx,
13843 );
13844 }
13845
13846 fn push_to_nav_history(
13847 &mut self,
13848 cursor_anchor: Anchor,
13849 new_position: Option<Point>,
13850 is_deactivate: bool,
13851 always: bool,
13852 cx: &mut Context<Self>,
13853 ) {
13854 if let Some(nav_history) = self.nav_history.as_mut() {
13855 let buffer = self.buffer.read(cx).read(cx);
13856 let cursor_position = cursor_anchor.to_point(&buffer);
13857 let scroll_state = self.scroll_manager.anchor();
13858 let scroll_top_row = scroll_state.top_row(&buffer);
13859 drop(buffer);
13860
13861 if let Some(new_position) = new_position {
13862 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13863 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13864 return;
13865 }
13866 }
13867
13868 nav_history.push(
13869 Some(NavigationData {
13870 cursor_anchor,
13871 cursor_position,
13872 scroll_anchor: scroll_state,
13873 scroll_top_row,
13874 }),
13875 cx,
13876 );
13877 cx.emit(EditorEvent::PushedToNavHistory {
13878 anchor: cursor_anchor,
13879 is_deactivate,
13880 })
13881 }
13882 }
13883
13884 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13886 let buffer = self.buffer.read(cx).snapshot(cx);
13887 let mut selection = self.selections.first::<usize>(cx);
13888 selection.set_head(buffer.len(), SelectionGoal::None);
13889 self.change_selections(Default::default(), window, cx, |s| {
13890 s.select(vec![selection]);
13891 });
13892 }
13893
13894 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13896 let end = self.buffer.read(cx).read(cx).len();
13897 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13898 s.select_ranges(vec![0..end]);
13899 });
13900 }
13901
13902 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13904 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13905 let mut selections = self.selections.all::<Point>(cx);
13906 let max_point = display_map.buffer_snapshot.max_point();
13907 for selection in &mut selections {
13908 let rows = selection.spanned_rows(true, &display_map);
13909 selection.start = Point::new(rows.start.0, 0);
13910 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13911 selection.reversed = false;
13912 }
13913 self.change_selections(Default::default(), window, cx, |s| {
13914 s.select(selections);
13915 });
13916 }
13917
13918 pub fn split_selection_into_lines(
13919 &mut self,
13920 action: &SplitSelectionIntoLines,
13921 window: &mut Window,
13922 cx: &mut Context<Self>,
13923 ) {
13924 let selections = self
13925 .selections
13926 .all::<Point>(cx)
13927 .into_iter()
13928 .map(|selection| selection.start..selection.end)
13929 .collect::<Vec<_>>();
13930 self.unfold_ranges(&selections, true, true, cx);
13931
13932 let mut new_selection_ranges = Vec::new();
13933 {
13934 let buffer = self.buffer.read(cx).read(cx);
13935 for selection in selections {
13936 for row in selection.start.row..selection.end.row {
13937 let line_start = Point::new(row, 0);
13938 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13939
13940 if action.keep_selections {
13941 // Keep the selection range for each line
13942 let selection_start = if row == selection.start.row {
13943 selection.start
13944 } else {
13945 line_start
13946 };
13947 new_selection_ranges.push(selection_start..line_end);
13948 } else {
13949 // Collapse to cursor at end of line
13950 new_selection_ranges.push(line_end..line_end);
13951 }
13952 }
13953
13954 let is_multiline_selection = selection.start.row != selection.end.row;
13955 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13956 // so this action feels more ergonomic when paired with other selection operations
13957 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13958 if !should_skip_last {
13959 if action.keep_selections {
13960 if is_multiline_selection {
13961 let line_start = Point::new(selection.end.row, 0);
13962 new_selection_ranges.push(line_start..selection.end);
13963 } else {
13964 new_selection_ranges.push(selection.start..selection.end);
13965 }
13966 } else {
13967 new_selection_ranges.push(selection.end..selection.end);
13968 }
13969 }
13970 }
13971 }
13972 self.change_selections(Default::default(), window, cx, |s| {
13973 s.select_ranges(new_selection_ranges);
13974 });
13975 }
13976
13977 pub fn add_selection_above(
13978 &mut self,
13979 _: &AddSelectionAbove,
13980 window: &mut Window,
13981 cx: &mut Context<Self>,
13982 ) {
13983 self.add_selection(true, window, cx);
13984 }
13985
13986 pub fn add_selection_below(
13987 &mut self,
13988 _: &AddSelectionBelow,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 self.add_selection(false, window, cx);
13993 }
13994
13995 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13997
13998 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13999 let all_selections = self.selections.all::<Point>(cx);
14000 let text_layout_details = self.text_layout_details(window);
14001
14002 let (mut columnar_selections, new_selections_to_columnarize) = {
14003 if let Some(state) = self.add_selections_state.as_ref() {
14004 let columnar_selection_ids: HashSet<_> = state
14005 .groups
14006 .iter()
14007 .flat_map(|group| group.stack.iter())
14008 .copied()
14009 .collect();
14010
14011 all_selections
14012 .into_iter()
14013 .partition(|s| columnar_selection_ids.contains(&s.id))
14014 } else {
14015 (Vec::new(), all_selections)
14016 }
14017 };
14018
14019 let mut state = self
14020 .add_selections_state
14021 .take()
14022 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14023
14024 for selection in new_selections_to_columnarize {
14025 let range = selection.display_range(&display_map).sorted();
14026 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14027 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14028 let positions = start_x.min(end_x)..start_x.max(end_x);
14029 let mut stack = Vec::new();
14030 for row in range.start.row().0..=range.end.row().0 {
14031 if let Some(selection) = self.selections.build_columnar_selection(
14032 &display_map,
14033 DisplayRow(row),
14034 &positions,
14035 selection.reversed,
14036 &text_layout_details,
14037 ) {
14038 stack.push(selection.id);
14039 columnar_selections.push(selection);
14040 }
14041 }
14042 if !stack.is_empty() {
14043 if above {
14044 stack.reverse();
14045 }
14046 state.groups.push(AddSelectionsGroup { above, stack });
14047 }
14048 }
14049
14050 let mut final_selections = Vec::new();
14051 let end_row = if above {
14052 DisplayRow(0)
14053 } else {
14054 display_map.max_point().row()
14055 };
14056
14057 let mut last_added_item_per_group = HashMap::default();
14058 for group in state.groups.iter_mut() {
14059 if let Some(last_id) = group.stack.last() {
14060 last_added_item_per_group.insert(*last_id, group);
14061 }
14062 }
14063
14064 for selection in columnar_selections {
14065 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14066 if above == group.above {
14067 let range = selection.display_range(&display_map).sorted();
14068 debug_assert_eq!(range.start.row(), range.end.row());
14069 let mut row = range.start.row();
14070 let positions =
14071 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14072 px(start)..px(end)
14073 } else {
14074 let start_x =
14075 display_map.x_for_display_point(range.start, &text_layout_details);
14076 let end_x =
14077 display_map.x_for_display_point(range.end, &text_layout_details);
14078 start_x.min(end_x)..start_x.max(end_x)
14079 };
14080
14081 let mut maybe_new_selection = None;
14082 while row != end_row {
14083 if above {
14084 row.0 -= 1;
14085 } else {
14086 row.0 += 1;
14087 }
14088 if let Some(new_selection) = self.selections.build_columnar_selection(
14089 &display_map,
14090 row,
14091 &positions,
14092 selection.reversed,
14093 &text_layout_details,
14094 ) {
14095 maybe_new_selection = Some(new_selection);
14096 break;
14097 }
14098 }
14099
14100 if let Some(new_selection) = maybe_new_selection {
14101 group.stack.push(new_selection.id);
14102 if above {
14103 final_selections.push(new_selection);
14104 final_selections.push(selection);
14105 } else {
14106 final_selections.push(selection);
14107 final_selections.push(new_selection);
14108 }
14109 } else {
14110 final_selections.push(selection);
14111 }
14112 } else {
14113 group.stack.pop();
14114 }
14115 } else {
14116 final_selections.push(selection);
14117 }
14118 }
14119
14120 self.change_selections(Default::default(), window, cx, |s| {
14121 s.select(final_selections);
14122 });
14123
14124 let final_selection_ids: HashSet<_> = self
14125 .selections
14126 .all::<Point>(cx)
14127 .iter()
14128 .map(|s| s.id)
14129 .collect();
14130 state.groups.retain_mut(|group| {
14131 // selections might get merged above so we remove invalid items from stacks
14132 group.stack.retain(|id| final_selection_ids.contains(id));
14133
14134 // single selection in stack can be treated as initial state
14135 group.stack.len() > 1
14136 });
14137
14138 if !state.groups.is_empty() {
14139 self.add_selections_state = Some(state);
14140 }
14141 }
14142
14143 fn select_match_ranges(
14144 &mut self,
14145 range: Range<usize>,
14146 reversed: bool,
14147 replace_newest: bool,
14148 auto_scroll: Option<Autoscroll>,
14149 window: &mut Window,
14150 cx: &mut Context<Editor>,
14151 ) {
14152 self.unfold_ranges(
14153 std::slice::from_ref(&range),
14154 false,
14155 auto_scroll.is_some(),
14156 cx,
14157 );
14158 let effects = if let Some(scroll) = auto_scroll {
14159 SelectionEffects::scroll(scroll)
14160 } else {
14161 SelectionEffects::no_scroll()
14162 };
14163 self.change_selections(effects, window, cx, |s| {
14164 if replace_newest {
14165 s.delete(s.newest_anchor().id);
14166 }
14167 if reversed {
14168 s.insert_range(range.end..range.start);
14169 } else {
14170 s.insert_range(range);
14171 }
14172 });
14173 }
14174
14175 pub fn select_next_match_internal(
14176 &mut self,
14177 display_map: &DisplaySnapshot,
14178 replace_newest: bool,
14179 autoscroll: Option<Autoscroll>,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) -> Result<()> {
14183 let buffer = &display_map.buffer_snapshot;
14184 let mut selections = self.selections.all::<usize>(cx);
14185 if let Some(mut select_next_state) = self.select_next_state.take() {
14186 let query = &select_next_state.query;
14187 if !select_next_state.done {
14188 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14189 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14190 let mut next_selected_range = None;
14191
14192 let bytes_after_last_selection =
14193 buffer.bytes_in_range(last_selection.end..buffer.len());
14194 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14195 let query_matches = query
14196 .stream_find_iter(bytes_after_last_selection)
14197 .map(|result| (last_selection.end, result))
14198 .chain(
14199 query
14200 .stream_find_iter(bytes_before_first_selection)
14201 .map(|result| (0, result)),
14202 );
14203
14204 for (start_offset, query_match) in query_matches {
14205 let query_match = query_match.unwrap(); // can only fail due to I/O
14206 let offset_range =
14207 start_offset + query_match.start()..start_offset + query_match.end();
14208
14209 if !select_next_state.wordwise
14210 || (!buffer.is_inside_word(offset_range.start, false)
14211 && !buffer.is_inside_word(offset_range.end, false))
14212 {
14213 // TODO: This is n^2, because we might check all the selections
14214 if !selections
14215 .iter()
14216 .any(|selection| selection.range().overlaps(&offset_range))
14217 {
14218 next_selected_range = Some(offset_range);
14219 break;
14220 }
14221 }
14222 }
14223
14224 if let Some(next_selected_range) = next_selected_range {
14225 self.select_match_ranges(
14226 next_selected_range,
14227 last_selection.reversed,
14228 replace_newest,
14229 autoscroll,
14230 window,
14231 cx,
14232 );
14233 } else {
14234 select_next_state.done = true;
14235 }
14236 }
14237
14238 self.select_next_state = Some(select_next_state);
14239 } else {
14240 let mut only_carets = true;
14241 let mut same_text_selected = true;
14242 let mut selected_text = None;
14243
14244 let mut selections_iter = selections.iter().peekable();
14245 while let Some(selection) = selections_iter.next() {
14246 if selection.start != selection.end {
14247 only_carets = false;
14248 }
14249
14250 if same_text_selected {
14251 if selected_text.is_none() {
14252 selected_text =
14253 Some(buffer.text_for_range(selection.range()).collect::<String>());
14254 }
14255
14256 if let Some(next_selection) = selections_iter.peek() {
14257 if next_selection.range().len() == selection.range().len() {
14258 let next_selected_text = buffer
14259 .text_for_range(next_selection.range())
14260 .collect::<String>();
14261 if Some(next_selected_text) != selected_text {
14262 same_text_selected = false;
14263 selected_text = None;
14264 }
14265 } else {
14266 same_text_selected = false;
14267 selected_text = None;
14268 }
14269 }
14270 }
14271 }
14272
14273 if only_carets {
14274 for selection in &mut selections {
14275 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14276 selection.start = word_range.start;
14277 selection.end = word_range.end;
14278 selection.goal = SelectionGoal::None;
14279 selection.reversed = false;
14280 self.select_match_ranges(
14281 selection.start..selection.end,
14282 selection.reversed,
14283 replace_newest,
14284 autoscroll,
14285 window,
14286 cx,
14287 );
14288 }
14289
14290 if selections.len() == 1 {
14291 let selection = selections
14292 .last()
14293 .expect("ensured that there's only one selection");
14294 let query = buffer
14295 .text_for_range(selection.start..selection.end)
14296 .collect::<String>();
14297 let is_empty = query.is_empty();
14298 let select_state = SelectNextState {
14299 query: AhoCorasick::new(&[query])?,
14300 wordwise: true,
14301 done: is_empty,
14302 };
14303 self.select_next_state = Some(select_state);
14304 } else {
14305 self.select_next_state = None;
14306 }
14307 } else if let Some(selected_text) = selected_text {
14308 self.select_next_state = Some(SelectNextState {
14309 query: AhoCorasick::new(&[selected_text])?,
14310 wordwise: false,
14311 done: false,
14312 });
14313 self.select_next_match_internal(
14314 display_map,
14315 replace_newest,
14316 autoscroll,
14317 window,
14318 cx,
14319 )?;
14320 }
14321 }
14322 Ok(())
14323 }
14324
14325 pub fn select_all_matches(
14326 &mut self,
14327 _action: &SelectAllMatches,
14328 window: &mut Window,
14329 cx: &mut Context<Self>,
14330 ) -> Result<()> {
14331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14332
14333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14334
14335 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14336 let Some(select_next_state) = self.select_next_state.as_mut() else {
14337 return Ok(());
14338 };
14339 if select_next_state.done {
14340 return Ok(());
14341 }
14342
14343 let mut new_selections = Vec::new();
14344
14345 let reversed = self.selections.oldest::<usize>(cx).reversed;
14346 let buffer = &display_map.buffer_snapshot;
14347 let query_matches = select_next_state
14348 .query
14349 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14350
14351 for query_match in query_matches.into_iter() {
14352 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14353 let offset_range = if reversed {
14354 query_match.end()..query_match.start()
14355 } else {
14356 query_match.start()..query_match.end()
14357 };
14358
14359 if !select_next_state.wordwise
14360 || (!buffer.is_inside_word(offset_range.start, false)
14361 && !buffer.is_inside_word(offset_range.end, false))
14362 {
14363 new_selections.push(offset_range.start..offset_range.end);
14364 }
14365 }
14366
14367 select_next_state.done = true;
14368
14369 if new_selections.is_empty() {
14370 log::error!("bug: new_selections is empty in select_all_matches");
14371 return Ok(());
14372 }
14373
14374 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14375 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14376 selections.select_ranges(new_selections)
14377 });
14378
14379 Ok(())
14380 }
14381
14382 pub fn select_next(
14383 &mut self,
14384 action: &SelectNext,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) -> Result<()> {
14388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14390 self.select_next_match_internal(
14391 &display_map,
14392 action.replace_newest,
14393 Some(Autoscroll::newest()),
14394 window,
14395 cx,
14396 )?;
14397 Ok(())
14398 }
14399
14400 pub fn select_previous(
14401 &mut self,
14402 action: &SelectPrevious,
14403 window: &mut Window,
14404 cx: &mut Context<Self>,
14405 ) -> Result<()> {
14406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14407 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14408 let buffer = &display_map.buffer_snapshot;
14409 let mut selections = self.selections.all::<usize>(cx);
14410 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14411 let query = &select_prev_state.query;
14412 if !select_prev_state.done {
14413 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14414 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14415 let mut next_selected_range = None;
14416 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14417 let bytes_before_last_selection =
14418 buffer.reversed_bytes_in_range(0..last_selection.start);
14419 let bytes_after_first_selection =
14420 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14421 let query_matches = query
14422 .stream_find_iter(bytes_before_last_selection)
14423 .map(|result| (last_selection.start, result))
14424 .chain(
14425 query
14426 .stream_find_iter(bytes_after_first_selection)
14427 .map(|result| (buffer.len(), result)),
14428 );
14429 for (end_offset, query_match) in query_matches {
14430 let query_match = query_match.unwrap(); // can only fail due to I/O
14431 let offset_range =
14432 end_offset - query_match.end()..end_offset - query_match.start();
14433
14434 if !select_prev_state.wordwise
14435 || (!buffer.is_inside_word(offset_range.start, false)
14436 && !buffer.is_inside_word(offset_range.end, false))
14437 {
14438 next_selected_range = Some(offset_range);
14439 break;
14440 }
14441 }
14442
14443 if let Some(next_selected_range) = next_selected_range {
14444 self.select_match_ranges(
14445 next_selected_range,
14446 last_selection.reversed,
14447 action.replace_newest,
14448 Some(Autoscroll::newest()),
14449 window,
14450 cx,
14451 );
14452 } else {
14453 select_prev_state.done = true;
14454 }
14455 }
14456
14457 self.select_prev_state = Some(select_prev_state);
14458 } else {
14459 let mut only_carets = true;
14460 let mut same_text_selected = true;
14461 let mut selected_text = None;
14462
14463 let mut selections_iter = selections.iter().peekable();
14464 while let Some(selection) = selections_iter.next() {
14465 if selection.start != selection.end {
14466 only_carets = false;
14467 }
14468
14469 if same_text_selected {
14470 if selected_text.is_none() {
14471 selected_text =
14472 Some(buffer.text_for_range(selection.range()).collect::<String>());
14473 }
14474
14475 if let Some(next_selection) = selections_iter.peek() {
14476 if next_selection.range().len() == selection.range().len() {
14477 let next_selected_text = buffer
14478 .text_for_range(next_selection.range())
14479 .collect::<String>();
14480 if Some(next_selected_text) != selected_text {
14481 same_text_selected = false;
14482 selected_text = None;
14483 }
14484 } else {
14485 same_text_selected = false;
14486 selected_text = None;
14487 }
14488 }
14489 }
14490 }
14491
14492 if only_carets {
14493 for selection in &mut selections {
14494 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14495 selection.start = word_range.start;
14496 selection.end = word_range.end;
14497 selection.goal = SelectionGoal::None;
14498 selection.reversed = false;
14499 self.select_match_ranges(
14500 selection.start..selection.end,
14501 selection.reversed,
14502 action.replace_newest,
14503 Some(Autoscroll::newest()),
14504 window,
14505 cx,
14506 );
14507 }
14508 if selections.len() == 1 {
14509 let selection = selections
14510 .last()
14511 .expect("ensured that there's only one selection");
14512 let query = buffer
14513 .text_for_range(selection.start..selection.end)
14514 .collect::<String>();
14515 let is_empty = query.is_empty();
14516 let select_state = SelectNextState {
14517 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14518 wordwise: true,
14519 done: is_empty,
14520 };
14521 self.select_prev_state = Some(select_state);
14522 } else {
14523 self.select_prev_state = None;
14524 }
14525 } else if let Some(selected_text) = selected_text {
14526 self.select_prev_state = Some(SelectNextState {
14527 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14528 wordwise: false,
14529 done: false,
14530 });
14531 self.select_previous(action, window, cx)?;
14532 }
14533 }
14534 Ok(())
14535 }
14536
14537 pub fn find_next_match(
14538 &mut self,
14539 _: &FindNextMatch,
14540 window: &mut Window,
14541 cx: &mut Context<Self>,
14542 ) -> Result<()> {
14543 let selections = self.selections.disjoint_anchors_arc();
14544 match selections.first() {
14545 Some(first) if selections.len() >= 2 => {
14546 self.change_selections(Default::default(), window, cx, |s| {
14547 s.select_ranges([first.range()]);
14548 });
14549 }
14550 _ => self.select_next(
14551 &SelectNext {
14552 replace_newest: true,
14553 },
14554 window,
14555 cx,
14556 )?,
14557 }
14558 Ok(())
14559 }
14560
14561 pub fn find_previous_match(
14562 &mut self,
14563 _: &FindPreviousMatch,
14564 window: &mut Window,
14565 cx: &mut Context<Self>,
14566 ) -> Result<()> {
14567 let selections = self.selections.disjoint_anchors_arc();
14568 match selections.last() {
14569 Some(last) if selections.len() >= 2 => {
14570 self.change_selections(Default::default(), window, cx, |s| {
14571 s.select_ranges([last.range()]);
14572 });
14573 }
14574 _ => self.select_previous(
14575 &SelectPrevious {
14576 replace_newest: true,
14577 },
14578 window,
14579 cx,
14580 )?,
14581 }
14582 Ok(())
14583 }
14584
14585 pub fn toggle_comments(
14586 &mut self,
14587 action: &ToggleComments,
14588 window: &mut Window,
14589 cx: &mut Context<Self>,
14590 ) {
14591 if self.read_only(cx) {
14592 return;
14593 }
14594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14595 let text_layout_details = &self.text_layout_details(window);
14596 self.transact(window, cx, |this, window, cx| {
14597 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14598 let mut edits = Vec::new();
14599 let mut selection_edit_ranges = Vec::new();
14600 let mut last_toggled_row = None;
14601 let snapshot = this.buffer.read(cx).read(cx);
14602 let empty_str: Arc<str> = Arc::default();
14603 let mut suffixes_inserted = Vec::new();
14604 let ignore_indent = action.ignore_indent;
14605
14606 fn comment_prefix_range(
14607 snapshot: &MultiBufferSnapshot,
14608 row: MultiBufferRow,
14609 comment_prefix: &str,
14610 comment_prefix_whitespace: &str,
14611 ignore_indent: bool,
14612 ) -> Range<Point> {
14613 let indent_size = if ignore_indent {
14614 0
14615 } else {
14616 snapshot.indent_size_for_line(row).len
14617 };
14618
14619 let start = Point::new(row.0, indent_size);
14620
14621 let mut line_bytes = snapshot
14622 .bytes_in_range(start..snapshot.max_point())
14623 .flatten()
14624 .copied();
14625
14626 // If this line currently begins with the line comment prefix, then record
14627 // the range containing the prefix.
14628 if line_bytes
14629 .by_ref()
14630 .take(comment_prefix.len())
14631 .eq(comment_prefix.bytes())
14632 {
14633 // Include any whitespace that matches the comment prefix.
14634 let matching_whitespace_len = line_bytes
14635 .zip(comment_prefix_whitespace.bytes())
14636 .take_while(|(a, b)| a == b)
14637 .count() as u32;
14638 let end = Point::new(
14639 start.row,
14640 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14641 );
14642 start..end
14643 } else {
14644 start..start
14645 }
14646 }
14647
14648 fn comment_suffix_range(
14649 snapshot: &MultiBufferSnapshot,
14650 row: MultiBufferRow,
14651 comment_suffix: &str,
14652 comment_suffix_has_leading_space: bool,
14653 ) -> Range<Point> {
14654 let end = Point::new(row.0, snapshot.line_len(row));
14655 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14656
14657 let mut line_end_bytes = snapshot
14658 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14659 .flatten()
14660 .copied();
14661
14662 let leading_space_len = if suffix_start_column > 0
14663 && line_end_bytes.next() == Some(b' ')
14664 && comment_suffix_has_leading_space
14665 {
14666 1
14667 } else {
14668 0
14669 };
14670
14671 // If this line currently begins with the line comment prefix, then record
14672 // the range containing the prefix.
14673 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14674 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14675 start..end
14676 } else {
14677 end..end
14678 }
14679 }
14680
14681 // TODO: Handle selections that cross excerpts
14682 for selection in &mut selections {
14683 let start_column = snapshot
14684 .indent_size_for_line(MultiBufferRow(selection.start.row))
14685 .len;
14686 let language = if let Some(language) =
14687 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14688 {
14689 language
14690 } else {
14691 continue;
14692 };
14693
14694 selection_edit_ranges.clear();
14695
14696 // If multiple selections contain a given row, avoid processing that
14697 // row more than once.
14698 let mut start_row = MultiBufferRow(selection.start.row);
14699 if last_toggled_row == Some(start_row) {
14700 start_row = start_row.next_row();
14701 }
14702 let end_row =
14703 if selection.end.row > selection.start.row && selection.end.column == 0 {
14704 MultiBufferRow(selection.end.row - 1)
14705 } else {
14706 MultiBufferRow(selection.end.row)
14707 };
14708 last_toggled_row = Some(end_row);
14709
14710 if start_row > end_row {
14711 continue;
14712 }
14713
14714 // If the language has line comments, toggle those.
14715 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14716
14717 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14718 if ignore_indent {
14719 full_comment_prefixes = full_comment_prefixes
14720 .into_iter()
14721 .map(|s| Arc::from(s.trim_end()))
14722 .collect();
14723 }
14724
14725 if !full_comment_prefixes.is_empty() {
14726 let first_prefix = full_comment_prefixes
14727 .first()
14728 .expect("prefixes is non-empty");
14729 let prefix_trimmed_lengths = full_comment_prefixes
14730 .iter()
14731 .map(|p| p.trim_end_matches(' ').len())
14732 .collect::<SmallVec<[usize; 4]>>();
14733
14734 let mut all_selection_lines_are_comments = true;
14735
14736 for row in start_row.0..=end_row.0 {
14737 let row = MultiBufferRow(row);
14738 if start_row < end_row && snapshot.is_line_blank(row) {
14739 continue;
14740 }
14741
14742 let prefix_range = full_comment_prefixes
14743 .iter()
14744 .zip(prefix_trimmed_lengths.iter().copied())
14745 .map(|(prefix, trimmed_prefix_len)| {
14746 comment_prefix_range(
14747 snapshot.deref(),
14748 row,
14749 &prefix[..trimmed_prefix_len],
14750 &prefix[trimmed_prefix_len..],
14751 ignore_indent,
14752 )
14753 })
14754 .max_by_key(|range| range.end.column - range.start.column)
14755 .expect("prefixes is non-empty");
14756
14757 if prefix_range.is_empty() {
14758 all_selection_lines_are_comments = false;
14759 }
14760
14761 selection_edit_ranges.push(prefix_range);
14762 }
14763
14764 if all_selection_lines_are_comments {
14765 edits.extend(
14766 selection_edit_ranges
14767 .iter()
14768 .cloned()
14769 .map(|range| (range, empty_str.clone())),
14770 );
14771 } else {
14772 let min_column = selection_edit_ranges
14773 .iter()
14774 .map(|range| range.start.column)
14775 .min()
14776 .unwrap_or(0);
14777 edits.extend(selection_edit_ranges.iter().map(|range| {
14778 let position = Point::new(range.start.row, min_column);
14779 (position..position, first_prefix.clone())
14780 }));
14781 }
14782 } else if let Some(BlockCommentConfig {
14783 start: full_comment_prefix,
14784 end: comment_suffix,
14785 ..
14786 }) = language.block_comment()
14787 {
14788 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14789 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14790 let prefix_range = comment_prefix_range(
14791 snapshot.deref(),
14792 start_row,
14793 comment_prefix,
14794 comment_prefix_whitespace,
14795 ignore_indent,
14796 );
14797 let suffix_range = comment_suffix_range(
14798 snapshot.deref(),
14799 end_row,
14800 comment_suffix.trim_start_matches(' '),
14801 comment_suffix.starts_with(' '),
14802 );
14803
14804 if prefix_range.is_empty() || suffix_range.is_empty() {
14805 edits.push((
14806 prefix_range.start..prefix_range.start,
14807 full_comment_prefix.clone(),
14808 ));
14809 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14810 suffixes_inserted.push((end_row, comment_suffix.len()));
14811 } else {
14812 edits.push((prefix_range, empty_str.clone()));
14813 edits.push((suffix_range, empty_str.clone()));
14814 }
14815 } else {
14816 continue;
14817 }
14818 }
14819
14820 drop(snapshot);
14821 this.buffer.update(cx, |buffer, cx| {
14822 buffer.edit(edits, None, cx);
14823 });
14824
14825 // Adjust selections so that they end before any comment suffixes that
14826 // were inserted.
14827 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14828 let mut selections = this.selections.all::<Point>(cx);
14829 let snapshot = this.buffer.read(cx).read(cx);
14830 for selection in &mut selections {
14831 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14832 match row.cmp(&MultiBufferRow(selection.end.row)) {
14833 Ordering::Less => {
14834 suffixes_inserted.next();
14835 continue;
14836 }
14837 Ordering::Greater => break,
14838 Ordering::Equal => {
14839 if selection.end.column == snapshot.line_len(row) {
14840 if selection.is_empty() {
14841 selection.start.column -= suffix_len as u32;
14842 }
14843 selection.end.column -= suffix_len as u32;
14844 }
14845 break;
14846 }
14847 }
14848 }
14849 }
14850
14851 drop(snapshot);
14852 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14853
14854 let selections = this.selections.all::<Point>(cx);
14855 let selections_on_single_row = selections.windows(2).all(|selections| {
14856 selections[0].start.row == selections[1].start.row
14857 && selections[0].end.row == selections[1].end.row
14858 && selections[0].start.row == selections[0].end.row
14859 });
14860 let selections_selecting = selections
14861 .iter()
14862 .any(|selection| selection.start != selection.end);
14863 let advance_downwards = action.advance_downwards
14864 && selections_on_single_row
14865 && !selections_selecting
14866 && !matches!(this.mode, EditorMode::SingleLine);
14867
14868 if advance_downwards {
14869 let snapshot = this.buffer.read(cx).snapshot(cx);
14870
14871 this.change_selections(Default::default(), window, cx, |s| {
14872 s.move_cursors_with(|display_snapshot, display_point, _| {
14873 let mut point = display_point.to_point(display_snapshot);
14874 point.row += 1;
14875 point = snapshot.clip_point(point, Bias::Left);
14876 let display_point = point.to_display_point(display_snapshot);
14877 let goal = SelectionGoal::HorizontalPosition(
14878 display_snapshot
14879 .x_for_display_point(display_point, text_layout_details)
14880 .into(),
14881 );
14882 (display_point, goal)
14883 })
14884 });
14885 }
14886 });
14887 }
14888
14889 pub fn select_enclosing_symbol(
14890 &mut self,
14891 _: &SelectEnclosingSymbol,
14892 window: &mut Window,
14893 cx: &mut Context<Self>,
14894 ) {
14895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14896
14897 let buffer = self.buffer.read(cx).snapshot(cx);
14898 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14899
14900 fn update_selection(
14901 selection: &Selection<usize>,
14902 buffer_snap: &MultiBufferSnapshot,
14903 ) -> Option<Selection<usize>> {
14904 let cursor = selection.head();
14905 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14906 for symbol in symbols.iter().rev() {
14907 let start = symbol.range.start.to_offset(buffer_snap);
14908 let end = symbol.range.end.to_offset(buffer_snap);
14909 let new_range = start..end;
14910 if start < selection.start || end > selection.end {
14911 return Some(Selection {
14912 id: selection.id,
14913 start: new_range.start,
14914 end: new_range.end,
14915 goal: SelectionGoal::None,
14916 reversed: selection.reversed,
14917 });
14918 }
14919 }
14920 None
14921 }
14922
14923 let mut selected_larger_symbol = false;
14924 let new_selections = old_selections
14925 .iter()
14926 .map(|selection| match update_selection(selection, &buffer) {
14927 Some(new_selection) => {
14928 if new_selection.range() != selection.range() {
14929 selected_larger_symbol = true;
14930 }
14931 new_selection
14932 }
14933 None => selection.clone(),
14934 })
14935 .collect::<Vec<_>>();
14936
14937 if selected_larger_symbol {
14938 self.change_selections(Default::default(), window, cx, |s| {
14939 s.select(new_selections);
14940 });
14941 }
14942 }
14943
14944 pub fn select_larger_syntax_node(
14945 &mut self,
14946 _: &SelectLargerSyntaxNode,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) {
14950 let Some(visible_row_count) = self.visible_row_count() else {
14951 return;
14952 };
14953 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14954 if old_selections.is_empty() {
14955 return;
14956 }
14957
14958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14959
14960 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14961 let buffer = self.buffer.read(cx).snapshot(cx);
14962
14963 let mut selected_larger_node = false;
14964 let mut new_selections = old_selections
14965 .iter()
14966 .map(|selection| {
14967 let old_range = selection.start..selection.end;
14968
14969 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14970 // manually select word at selection
14971 if ["string_content", "inline"].contains(&node.kind()) {
14972 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14973 // ignore if word is already selected
14974 if !word_range.is_empty() && old_range != word_range {
14975 let (last_word_range, _) =
14976 buffer.surrounding_word(old_range.end, false);
14977 // only select word if start and end point belongs to same word
14978 if word_range == last_word_range {
14979 selected_larger_node = true;
14980 return Selection {
14981 id: selection.id,
14982 start: word_range.start,
14983 end: word_range.end,
14984 goal: SelectionGoal::None,
14985 reversed: selection.reversed,
14986 };
14987 }
14988 }
14989 }
14990 }
14991
14992 let mut new_range = old_range.clone();
14993 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
14994 {
14995 new_range = match containing_range {
14996 MultiOrSingleBufferOffsetRange::Single(_) => break,
14997 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14998 };
14999 if !node.is_named() {
15000 continue;
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 let child = if selection.is_empty()
15125 && let Some((_, ancestor_range)) =
15126 buffer.syntax_ancestor(selection.start..selection.end)
15127 {
15128 match ancestor_range {
15129 MultiOrSingleBufferOffsetRange::Single(range) => range,
15130 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15131 }
15132 } else {
15133 selection.range()
15134 };
15135
15136 let mut parent = child.clone();
15137 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15138 parent = match ancestor_range {
15139 MultiOrSingleBufferOffsetRange::Single(range) => range,
15140 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15141 };
15142 if parent.start < child.start || parent.end > child.end {
15143 break;
15144 }
15145 }
15146
15147 if parent == child {
15148 return None;
15149 }
15150 let text = buffer.text_for_range(child).collect::<String>();
15151 Some((selection.id, parent, text))
15152 })
15153 .collect::<Vec<_>>();
15154 if full_edits.is_empty() {
15155 return;
15156 }
15157
15158 self.transact(window, cx, |this, window, cx| {
15159 this.buffer.update(cx, |buffer, cx| {
15160 buffer.edit(
15161 full_edits
15162 .iter()
15163 .map(|(_, p, t)| (p.clone(), t.clone()))
15164 .collect::<Vec<_>>(),
15165 None,
15166 cx,
15167 );
15168 });
15169 this.change_selections(Default::default(), window, cx, |s| {
15170 let mut offset = 0;
15171 let mut selections = vec![];
15172 for (id, parent, text) in full_edits {
15173 let start = parent.start - offset;
15174 offset += parent.len() - text.len();
15175 selections.push(Selection {
15176 id,
15177 start,
15178 end: start + text.len(),
15179 reversed: false,
15180 goal: Default::default(),
15181 });
15182 }
15183 s.select(selections);
15184 });
15185 });
15186 }
15187
15188 pub fn select_next_syntax_node(
15189 &mut self,
15190 _: &SelectNextSyntaxNode,
15191 window: &mut Window,
15192 cx: &mut Context<Self>,
15193 ) {
15194 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15195 if old_selections.is_empty() {
15196 return;
15197 }
15198
15199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15200
15201 let buffer = self.buffer.read(cx).snapshot(cx);
15202 let mut selected_sibling = false;
15203
15204 let new_selections = old_selections
15205 .iter()
15206 .map(|selection| {
15207 let old_range = selection.start..selection.end;
15208
15209 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15210 let new_range = node.byte_range();
15211 selected_sibling = true;
15212 Selection {
15213 id: selection.id,
15214 start: new_range.start,
15215 end: new_range.end,
15216 goal: SelectionGoal::None,
15217 reversed: selection.reversed,
15218 }
15219 } else {
15220 selection.clone()
15221 }
15222 })
15223 .collect::<Vec<_>>();
15224
15225 if selected_sibling {
15226 self.change_selections(
15227 SelectionEffects::scroll(Autoscroll::fit()),
15228 window,
15229 cx,
15230 |s| {
15231 s.select(new_selections);
15232 },
15233 );
15234 }
15235 }
15236
15237 pub fn select_prev_syntax_node(
15238 &mut self,
15239 _: &SelectPreviousSyntaxNode,
15240 window: &mut Window,
15241 cx: &mut Context<Self>,
15242 ) {
15243 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15244 if old_selections.is_empty() {
15245 return;
15246 }
15247
15248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15249
15250 let buffer = self.buffer.read(cx).snapshot(cx);
15251 let mut selected_sibling = false;
15252
15253 let new_selections = old_selections
15254 .iter()
15255 .map(|selection| {
15256 let old_range = selection.start..selection.end;
15257
15258 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15259 let new_range = node.byte_range();
15260 selected_sibling = true;
15261 Selection {
15262 id: selection.id,
15263 start: new_range.start,
15264 end: new_range.end,
15265 goal: SelectionGoal::None,
15266 reversed: selection.reversed,
15267 }
15268 } else {
15269 selection.clone()
15270 }
15271 })
15272 .collect::<Vec<_>>();
15273
15274 if selected_sibling {
15275 self.change_selections(
15276 SelectionEffects::scroll(Autoscroll::fit()),
15277 window,
15278 cx,
15279 |s| {
15280 s.select(new_selections);
15281 },
15282 );
15283 }
15284 }
15285
15286 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15287 if !EditorSettings::get_global(cx).gutter.runnables {
15288 self.clear_tasks();
15289 return Task::ready(());
15290 }
15291 let project = self.project().map(Entity::downgrade);
15292 let task_sources = self.lsp_task_sources(cx);
15293 let multi_buffer = self.buffer.downgrade();
15294 cx.spawn_in(window, async move |editor, cx| {
15295 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15296 let Some(project) = project.and_then(|p| p.upgrade()) else {
15297 return;
15298 };
15299 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15300 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15301 }) else {
15302 return;
15303 };
15304
15305 let hide_runnables = project
15306 .update(cx, |project, _| project.is_via_collab())
15307 .unwrap_or(true);
15308 if hide_runnables {
15309 return;
15310 }
15311 let new_rows =
15312 cx.background_spawn({
15313 let snapshot = display_snapshot.clone();
15314 async move {
15315 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15316 }
15317 })
15318 .await;
15319 let Ok(lsp_tasks) =
15320 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15321 else {
15322 return;
15323 };
15324 let lsp_tasks = lsp_tasks.await;
15325
15326 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15327 lsp_tasks
15328 .into_iter()
15329 .flat_map(|(kind, tasks)| {
15330 tasks.into_iter().filter_map(move |(location, task)| {
15331 Some((kind.clone(), location?, task))
15332 })
15333 })
15334 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15335 let buffer = location.target.buffer;
15336 let buffer_snapshot = buffer.read(cx).snapshot();
15337 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15338 |(excerpt_id, snapshot, _)| {
15339 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15340 display_snapshot
15341 .buffer_snapshot
15342 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15343 } else {
15344 None
15345 }
15346 },
15347 );
15348 if let Some(offset) = offset {
15349 let task_buffer_range =
15350 location.target.range.to_point(&buffer_snapshot);
15351 let context_buffer_range =
15352 task_buffer_range.to_offset(&buffer_snapshot);
15353 let context_range = BufferOffset(context_buffer_range.start)
15354 ..BufferOffset(context_buffer_range.end);
15355
15356 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15357 .or_insert_with(|| RunnableTasks {
15358 templates: Vec::new(),
15359 offset,
15360 column: task_buffer_range.start.column,
15361 extra_variables: HashMap::default(),
15362 context_range,
15363 })
15364 .templates
15365 .push((kind, task.original_task().clone()));
15366 }
15367
15368 acc
15369 })
15370 }) else {
15371 return;
15372 };
15373
15374 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15375 buffer.language_settings(cx).tasks.prefer_lsp
15376 }) else {
15377 return;
15378 };
15379
15380 let rows = Self::runnable_rows(
15381 project,
15382 display_snapshot,
15383 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15384 new_rows,
15385 cx.clone(),
15386 )
15387 .await;
15388 editor
15389 .update(cx, |editor, _| {
15390 editor.clear_tasks();
15391 for (key, mut value) in rows {
15392 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15393 value.templates.extend(lsp_tasks.templates);
15394 }
15395
15396 editor.insert_tasks(key, value);
15397 }
15398 for (key, value) in lsp_tasks_by_rows {
15399 editor.insert_tasks(key, value);
15400 }
15401 })
15402 .ok();
15403 })
15404 }
15405 fn fetch_runnable_ranges(
15406 snapshot: &DisplaySnapshot,
15407 range: Range<Anchor>,
15408 ) -> Vec<language::RunnableRange> {
15409 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15410 }
15411
15412 fn runnable_rows(
15413 project: Entity<Project>,
15414 snapshot: DisplaySnapshot,
15415 prefer_lsp: bool,
15416 runnable_ranges: Vec<RunnableRange>,
15417 cx: AsyncWindowContext,
15418 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15419 cx.spawn(async move |cx| {
15420 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15421 for mut runnable in runnable_ranges {
15422 let Some(tasks) = cx
15423 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15424 .ok()
15425 else {
15426 continue;
15427 };
15428 let mut tasks = tasks.await;
15429
15430 if prefer_lsp {
15431 tasks.retain(|(task_kind, _)| {
15432 !matches!(task_kind, TaskSourceKind::Language { .. })
15433 });
15434 }
15435 if tasks.is_empty() {
15436 continue;
15437 }
15438
15439 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15440 let Some(row) = snapshot
15441 .buffer_snapshot
15442 .buffer_line_for_row(MultiBufferRow(point.row))
15443 .map(|(_, range)| range.start.row)
15444 else {
15445 continue;
15446 };
15447
15448 let context_range =
15449 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15450 runnable_rows.push((
15451 (runnable.buffer_id, row),
15452 RunnableTasks {
15453 templates: tasks,
15454 offset: snapshot
15455 .buffer_snapshot
15456 .anchor_before(runnable.run_range.start),
15457 context_range,
15458 column: point.column,
15459 extra_variables: runnable.extra_captures,
15460 },
15461 ));
15462 }
15463 runnable_rows
15464 })
15465 }
15466
15467 fn templates_with_tags(
15468 project: &Entity<Project>,
15469 runnable: &mut Runnable,
15470 cx: &mut App,
15471 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15472 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15473 let (worktree_id, file) = project
15474 .buffer_for_id(runnable.buffer, cx)
15475 .and_then(|buffer| buffer.read(cx).file())
15476 .map(|file| (file.worktree_id(cx), file.clone()))
15477 .unzip();
15478
15479 (
15480 project.task_store().read(cx).task_inventory().cloned(),
15481 worktree_id,
15482 file,
15483 )
15484 });
15485
15486 let tags = mem::take(&mut runnable.tags);
15487 let language = runnable.language.clone();
15488 cx.spawn(async move |cx| {
15489 let mut templates_with_tags = Vec::new();
15490 if let Some(inventory) = inventory {
15491 for RunnableTag(tag) in tags {
15492 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15493 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15494 }) else {
15495 return templates_with_tags;
15496 };
15497 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15498 move |(_, template)| {
15499 template.tags.iter().any(|source_tag| source_tag == &tag)
15500 },
15501 ));
15502 }
15503 }
15504 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15505
15506 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15507 // Strongest source wins; if we have worktree tag binding, prefer that to
15508 // global and language bindings;
15509 // if we have a global binding, prefer that to language binding.
15510 let first_mismatch = templates_with_tags
15511 .iter()
15512 .position(|(tag_source, _)| tag_source != leading_tag_source);
15513 if let Some(index) = first_mismatch {
15514 templates_with_tags.truncate(index);
15515 }
15516 }
15517
15518 templates_with_tags
15519 })
15520 }
15521
15522 pub fn move_to_enclosing_bracket(
15523 &mut self,
15524 _: &MoveToEnclosingBracket,
15525 window: &mut Window,
15526 cx: &mut Context<Self>,
15527 ) {
15528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15529 self.change_selections(Default::default(), window, cx, |s| {
15530 s.move_offsets_with(|snapshot, selection| {
15531 let Some(enclosing_bracket_ranges) =
15532 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15533 else {
15534 return;
15535 };
15536
15537 let mut best_length = usize::MAX;
15538 let mut best_inside = false;
15539 let mut best_in_bracket_range = false;
15540 let mut best_destination = None;
15541 for (open, close) in enclosing_bracket_ranges {
15542 let close = close.to_inclusive();
15543 let length = close.end() - open.start;
15544 let inside = selection.start >= open.end && selection.end <= *close.start();
15545 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15546 || close.contains(&selection.head());
15547
15548 // If best is next to a bracket and current isn't, skip
15549 if !in_bracket_range && best_in_bracket_range {
15550 continue;
15551 }
15552
15553 // Prefer smaller lengths unless best is inside and current isn't
15554 if length > best_length && (best_inside || !inside) {
15555 continue;
15556 }
15557
15558 best_length = length;
15559 best_inside = inside;
15560 best_in_bracket_range = in_bracket_range;
15561 best_destination = Some(
15562 if close.contains(&selection.start) && close.contains(&selection.end) {
15563 if inside { open.end } else { open.start }
15564 } else if inside {
15565 *close.start()
15566 } else {
15567 *close.end()
15568 },
15569 );
15570 }
15571
15572 if let Some(destination) = best_destination {
15573 selection.collapse_to(destination, SelectionGoal::None);
15574 }
15575 })
15576 });
15577 }
15578
15579 pub fn undo_selection(
15580 &mut self,
15581 _: &UndoSelection,
15582 window: &mut Window,
15583 cx: &mut Context<Self>,
15584 ) {
15585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15586 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15587 self.selection_history.mode = SelectionHistoryMode::Undoing;
15588 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15589 this.end_selection(window, cx);
15590 this.change_selections(
15591 SelectionEffects::scroll(Autoscroll::newest()),
15592 window,
15593 cx,
15594 |s| s.select_anchors(entry.selections.to_vec()),
15595 );
15596 });
15597 self.selection_history.mode = SelectionHistoryMode::Normal;
15598
15599 self.select_next_state = entry.select_next_state;
15600 self.select_prev_state = entry.select_prev_state;
15601 self.add_selections_state = entry.add_selections_state;
15602 }
15603 }
15604
15605 pub fn redo_selection(
15606 &mut self,
15607 _: &RedoSelection,
15608 window: &mut Window,
15609 cx: &mut Context<Self>,
15610 ) {
15611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15612 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15613 self.selection_history.mode = SelectionHistoryMode::Redoing;
15614 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15615 this.end_selection(window, cx);
15616 this.change_selections(
15617 SelectionEffects::scroll(Autoscroll::newest()),
15618 window,
15619 cx,
15620 |s| s.select_anchors(entry.selections.to_vec()),
15621 );
15622 });
15623 self.selection_history.mode = SelectionHistoryMode::Normal;
15624
15625 self.select_next_state = entry.select_next_state;
15626 self.select_prev_state = entry.select_prev_state;
15627 self.add_selections_state = entry.add_selections_state;
15628 }
15629 }
15630
15631 pub fn expand_excerpts(
15632 &mut self,
15633 action: &ExpandExcerpts,
15634 _: &mut Window,
15635 cx: &mut Context<Self>,
15636 ) {
15637 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15638 }
15639
15640 pub fn expand_excerpts_down(
15641 &mut self,
15642 action: &ExpandExcerptsDown,
15643 _: &mut Window,
15644 cx: &mut Context<Self>,
15645 ) {
15646 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15647 }
15648
15649 pub fn expand_excerpts_up(
15650 &mut self,
15651 action: &ExpandExcerptsUp,
15652 _: &mut Window,
15653 cx: &mut Context<Self>,
15654 ) {
15655 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15656 }
15657
15658 pub fn expand_excerpts_for_direction(
15659 &mut self,
15660 lines: u32,
15661 direction: ExpandExcerptDirection,
15662
15663 cx: &mut Context<Self>,
15664 ) {
15665 let selections = self.selections.disjoint_anchors_arc();
15666
15667 let lines = if lines == 0 {
15668 EditorSettings::get_global(cx).expand_excerpt_lines
15669 } else {
15670 lines
15671 };
15672
15673 self.buffer.update(cx, |buffer, cx| {
15674 let snapshot = buffer.snapshot(cx);
15675 let mut excerpt_ids = selections
15676 .iter()
15677 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15678 .collect::<Vec<_>>();
15679 excerpt_ids.sort();
15680 excerpt_ids.dedup();
15681 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15682 })
15683 }
15684
15685 pub fn expand_excerpt(
15686 &mut self,
15687 excerpt: ExcerptId,
15688 direction: ExpandExcerptDirection,
15689 window: &mut Window,
15690 cx: &mut Context<Self>,
15691 ) {
15692 let current_scroll_position = self.scroll_position(cx);
15693 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15694 let mut should_scroll_up = false;
15695
15696 if direction == ExpandExcerptDirection::Down {
15697 let multi_buffer = self.buffer.read(cx);
15698 let snapshot = multi_buffer.snapshot(cx);
15699 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15700 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15701 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15702 {
15703 let buffer_snapshot = buffer.read(cx).snapshot();
15704 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15705 let last_row = buffer_snapshot.max_point().row;
15706 let lines_below = last_row.saturating_sub(excerpt_end_row);
15707 should_scroll_up = lines_below >= lines_to_expand;
15708 }
15709 }
15710
15711 self.buffer.update(cx, |buffer, cx| {
15712 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15713 });
15714
15715 if should_scroll_up {
15716 let new_scroll_position =
15717 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15718 self.set_scroll_position(new_scroll_position, window, cx);
15719 }
15720 }
15721
15722 pub fn go_to_singleton_buffer_point(
15723 &mut self,
15724 point: Point,
15725 window: &mut Window,
15726 cx: &mut Context<Self>,
15727 ) {
15728 self.go_to_singleton_buffer_range(point..point, window, cx);
15729 }
15730
15731 pub fn go_to_singleton_buffer_range(
15732 &mut self,
15733 range: Range<Point>,
15734 window: &mut Window,
15735 cx: &mut Context<Self>,
15736 ) {
15737 let multibuffer = self.buffer().read(cx);
15738 let Some(buffer) = multibuffer.as_singleton() else {
15739 return;
15740 };
15741 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15742 return;
15743 };
15744 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15745 return;
15746 };
15747 self.change_selections(
15748 SelectionEffects::default().nav_history(true),
15749 window,
15750 cx,
15751 |s| s.select_anchor_ranges([start..end]),
15752 );
15753 }
15754
15755 pub fn go_to_diagnostic(
15756 &mut self,
15757 action: &GoToDiagnostic,
15758 window: &mut Window,
15759 cx: &mut Context<Self>,
15760 ) {
15761 if !self.diagnostics_enabled() {
15762 return;
15763 }
15764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15765 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15766 }
15767
15768 pub fn go_to_prev_diagnostic(
15769 &mut self,
15770 action: &GoToPreviousDiagnostic,
15771 window: &mut Window,
15772 cx: &mut Context<Self>,
15773 ) {
15774 if !self.diagnostics_enabled() {
15775 return;
15776 }
15777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15778 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15779 }
15780
15781 pub fn go_to_diagnostic_impl(
15782 &mut self,
15783 direction: Direction,
15784 severity: GoToDiagnosticSeverityFilter,
15785 window: &mut Window,
15786 cx: &mut Context<Self>,
15787 ) {
15788 let buffer = self.buffer.read(cx).snapshot(cx);
15789 let selection = self.selections.newest::<usize>(cx);
15790
15791 let mut active_group_id = None;
15792 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15793 && active_group.active_range.start.to_offset(&buffer) == selection.start
15794 {
15795 active_group_id = Some(active_group.group_id);
15796 }
15797
15798 fn filtered(
15799 snapshot: EditorSnapshot,
15800 severity: GoToDiagnosticSeverityFilter,
15801 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15802 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15803 diagnostics
15804 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15805 .filter(|entry| entry.range.start != entry.range.end)
15806 .filter(|entry| !entry.diagnostic.is_unnecessary)
15807 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15808 }
15809
15810 let snapshot = self.snapshot(window, cx);
15811 let before = filtered(
15812 snapshot.clone(),
15813 severity,
15814 buffer
15815 .diagnostics_in_range(0..selection.start)
15816 .filter(|entry| entry.range.start <= selection.start),
15817 );
15818 let after = filtered(
15819 snapshot,
15820 severity,
15821 buffer
15822 .diagnostics_in_range(selection.start..buffer.len())
15823 .filter(|entry| entry.range.start >= selection.start),
15824 );
15825
15826 let mut found: Option<DiagnosticEntry<usize>> = None;
15827 if direction == Direction::Prev {
15828 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15829 {
15830 for diagnostic in prev_diagnostics.into_iter().rev() {
15831 if diagnostic.range.start != selection.start
15832 || active_group_id
15833 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15834 {
15835 found = Some(diagnostic);
15836 break 'outer;
15837 }
15838 }
15839 }
15840 } else {
15841 for diagnostic in after.chain(before) {
15842 if diagnostic.range.start != selection.start
15843 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15844 {
15845 found = Some(diagnostic);
15846 break;
15847 }
15848 }
15849 }
15850 let Some(next_diagnostic) = found else {
15851 return;
15852 };
15853
15854 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15855 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15856 return;
15857 };
15858 self.change_selections(Default::default(), window, cx, |s| {
15859 s.select_ranges(vec![
15860 next_diagnostic.range.start..next_diagnostic.range.start,
15861 ])
15862 });
15863 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15864 self.refresh_edit_prediction(false, true, window, cx);
15865 }
15866
15867 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15869 let snapshot = self.snapshot(window, cx);
15870 let selection = self.selections.newest::<Point>(cx);
15871 self.go_to_hunk_before_or_after_position(
15872 &snapshot,
15873 selection.head(),
15874 Direction::Next,
15875 window,
15876 cx,
15877 );
15878 }
15879
15880 pub fn go_to_hunk_before_or_after_position(
15881 &mut self,
15882 snapshot: &EditorSnapshot,
15883 position: Point,
15884 direction: Direction,
15885 window: &mut Window,
15886 cx: &mut Context<Editor>,
15887 ) {
15888 let row = if direction == Direction::Next {
15889 self.hunk_after_position(snapshot, position)
15890 .map(|hunk| hunk.row_range.start)
15891 } else {
15892 self.hunk_before_position(snapshot, position)
15893 };
15894
15895 if let Some(row) = row {
15896 let destination = Point::new(row.0, 0);
15897 let autoscroll = Autoscroll::center();
15898
15899 self.unfold_ranges(&[destination..destination], false, false, cx);
15900 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15901 s.select_ranges([destination..destination]);
15902 });
15903 }
15904 }
15905
15906 fn hunk_after_position(
15907 &mut self,
15908 snapshot: &EditorSnapshot,
15909 position: Point,
15910 ) -> Option<MultiBufferDiffHunk> {
15911 snapshot
15912 .buffer_snapshot
15913 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15914 .find(|hunk| hunk.row_range.start.0 > position.row)
15915 .or_else(|| {
15916 snapshot
15917 .buffer_snapshot
15918 .diff_hunks_in_range(Point::zero()..position)
15919 .find(|hunk| hunk.row_range.end.0 < position.row)
15920 })
15921 }
15922
15923 fn go_to_prev_hunk(
15924 &mut self,
15925 _: &GoToPreviousHunk,
15926 window: &mut Window,
15927 cx: &mut Context<Self>,
15928 ) {
15929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15930 let snapshot = self.snapshot(window, cx);
15931 let selection = self.selections.newest::<Point>(cx);
15932 self.go_to_hunk_before_or_after_position(
15933 &snapshot,
15934 selection.head(),
15935 Direction::Prev,
15936 window,
15937 cx,
15938 );
15939 }
15940
15941 fn hunk_before_position(
15942 &mut self,
15943 snapshot: &EditorSnapshot,
15944 position: Point,
15945 ) -> Option<MultiBufferRow> {
15946 snapshot
15947 .buffer_snapshot
15948 .diff_hunk_before(position)
15949 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15950 }
15951
15952 fn go_to_next_change(
15953 &mut self,
15954 _: &GoToNextChange,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) {
15958 if let Some(selections) = self
15959 .change_list
15960 .next_change(1, Direction::Next)
15961 .map(|s| s.to_vec())
15962 {
15963 self.change_selections(Default::default(), window, cx, |s| {
15964 let map = s.display_map();
15965 s.select_display_ranges(selections.iter().map(|a| {
15966 let point = a.to_display_point(&map);
15967 point..point
15968 }))
15969 })
15970 }
15971 }
15972
15973 fn go_to_previous_change(
15974 &mut self,
15975 _: &GoToPreviousChange,
15976 window: &mut Window,
15977 cx: &mut Context<Self>,
15978 ) {
15979 if let Some(selections) = self
15980 .change_list
15981 .next_change(1, Direction::Prev)
15982 .map(|s| s.to_vec())
15983 {
15984 self.change_selections(Default::default(), window, cx, |s| {
15985 let map = s.display_map();
15986 s.select_display_ranges(selections.iter().map(|a| {
15987 let point = a.to_display_point(&map);
15988 point..point
15989 }))
15990 })
15991 }
15992 }
15993
15994 pub fn go_to_next_document_highlight(
15995 &mut self,
15996 _: &GoToNextDocumentHighlight,
15997 window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16001 }
16002
16003 pub fn go_to_prev_document_highlight(
16004 &mut self,
16005 _: &GoToPreviousDocumentHighlight,
16006 window: &mut Window,
16007 cx: &mut Context<Self>,
16008 ) {
16009 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16010 }
16011
16012 pub fn go_to_document_highlight_before_or_after_position(
16013 &mut self,
16014 direction: Direction,
16015 window: &mut Window,
16016 cx: &mut Context<Editor>,
16017 ) {
16018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16019 let snapshot = self.snapshot(window, cx);
16020 let buffer = &snapshot.buffer_snapshot;
16021 let position = self.selections.newest::<Point>(cx).head();
16022 let anchor_position = buffer.anchor_after(position);
16023
16024 // Get all document highlights (both read and write)
16025 let mut all_highlights = Vec::new();
16026
16027 if let Some((_, read_highlights)) = self
16028 .background_highlights
16029 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16030 {
16031 all_highlights.extend(read_highlights.iter());
16032 }
16033
16034 if let Some((_, write_highlights)) = self
16035 .background_highlights
16036 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16037 {
16038 all_highlights.extend(write_highlights.iter());
16039 }
16040
16041 if all_highlights.is_empty() {
16042 return;
16043 }
16044
16045 // Sort highlights by position
16046 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16047
16048 let target_highlight = match direction {
16049 Direction::Next => {
16050 // Find the first highlight after the current position
16051 all_highlights
16052 .iter()
16053 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16054 }
16055 Direction::Prev => {
16056 // Find the last highlight before the current position
16057 all_highlights
16058 .iter()
16059 .rev()
16060 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16061 }
16062 };
16063
16064 if let Some(highlight) = target_highlight {
16065 let destination = highlight.start.to_point(buffer);
16066 let autoscroll = Autoscroll::center();
16067
16068 self.unfold_ranges(&[destination..destination], false, false, cx);
16069 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16070 s.select_ranges([destination..destination]);
16071 });
16072 }
16073 }
16074
16075 fn go_to_line<T: 'static>(
16076 &mut self,
16077 position: Anchor,
16078 highlight_color: Option<Hsla>,
16079 window: &mut Window,
16080 cx: &mut Context<Self>,
16081 ) {
16082 let snapshot = self.snapshot(window, cx).display_snapshot;
16083 let position = position.to_point(&snapshot.buffer_snapshot);
16084 let start = snapshot
16085 .buffer_snapshot
16086 .clip_point(Point::new(position.row, 0), Bias::Left);
16087 let end = start + Point::new(1, 0);
16088 let start = snapshot.buffer_snapshot.anchor_before(start);
16089 let end = snapshot.buffer_snapshot.anchor_before(end);
16090
16091 self.highlight_rows::<T>(
16092 start..end,
16093 highlight_color
16094 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16095 Default::default(),
16096 cx,
16097 );
16098
16099 if self.buffer.read(cx).is_singleton() {
16100 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16101 }
16102 }
16103
16104 pub fn go_to_definition(
16105 &mut self,
16106 _: &GoToDefinition,
16107 window: &mut Window,
16108 cx: &mut Context<Self>,
16109 ) -> Task<Result<Navigated>> {
16110 let definition =
16111 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16112 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16113 cx.spawn_in(window, async move |editor, cx| {
16114 if definition.await? == Navigated::Yes {
16115 return Ok(Navigated::Yes);
16116 }
16117 match fallback_strategy {
16118 GoToDefinitionFallback::None => Ok(Navigated::No),
16119 GoToDefinitionFallback::FindAllReferences => {
16120 match editor.update_in(cx, |editor, window, cx| {
16121 editor.find_all_references(&FindAllReferences, window, cx)
16122 })? {
16123 Some(references) => references.await,
16124 None => Ok(Navigated::No),
16125 }
16126 }
16127 }
16128 })
16129 }
16130
16131 pub fn go_to_declaration(
16132 &mut self,
16133 _: &GoToDeclaration,
16134 window: &mut Window,
16135 cx: &mut Context<Self>,
16136 ) -> Task<Result<Navigated>> {
16137 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16138 }
16139
16140 pub fn go_to_declaration_split(
16141 &mut self,
16142 _: &GoToDeclaration,
16143 window: &mut Window,
16144 cx: &mut Context<Self>,
16145 ) -> Task<Result<Navigated>> {
16146 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16147 }
16148
16149 pub fn go_to_implementation(
16150 &mut self,
16151 _: &GoToImplementation,
16152 window: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) -> Task<Result<Navigated>> {
16155 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16156 }
16157
16158 pub fn go_to_implementation_split(
16159 &mut self,
16160 _: &GoToImplementationSplit,
16161 window: &mut Window,
16162 cx: &mut Context<Self>,
16163 ) -> Task<Result<Navigated>> {
16164 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16165 }
16166
16167 pub fn go_to_type_definition(
16168 &mut self,
16169 _: &GoToTypeDefinition,
16170 window: &mut Window,
16171 cx: &mut Context<Self>,
16172 ) -> Task<Result<Navigated>> {
16173 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16174 }
16175
16176 pub fn go_to_definition_split(
16177 &mut self,
16178 _: &GoToDefinitionSplit,
16179 window: &mut Window,
16180 cx: &mut Context<Self>,
16181 ) -> Task<Result<Navigated>> {
16182 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16183 }
16184
16185 pub fn go_to_type_definition_split(
16186 &mut self,
16187 _: &GoToTypeDefinitionSplit,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) -> Task<Result<Navigated>> {
16191 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16192 }
16193
16194 fn go_to_definition_of_kind(
16195 &mut self,
16196 kind: GotoDefinitionKind,
16197 split: bool,
16198 window: &mut Window,
16199 cx: &mut Context<Self>,
16200 ) -> Task<Result<Navigated>> {
16201 let Some(provider) = self.semantics_provider.clone() else {
16202 return Task::ready(Ok(Navigated::No));
16203 };
16204 let head = self.selections.newest::<usize>(cx).head();
16205 let buffer = self.buffer.read(cx);
16206 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16207 return Task::ready(Ok(Navigated::No));
16208 };
16209 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16210 return Task::ready(Ok(Navigated::No));
16211 };
16212
16213 cx.spawn_in(window, async move |editor, cx| {
16214 let Some(definitions) = definitions.await? else {
16215 return Ok(Navigated::No);
16216 };
16217 let navigated = editor
16218 .update_in(cx, |editor, window, cx| {
16219 editor.navigate_to_hover_links(
16220 Some(kind),
16221 definitions
16222 .into_iter()
16223 .filter(|location| {
16224 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16225 })
16226 .map(HoverLink::Text)
16227 .collect::<Vec<_>>(),
16228 split,
16229 window,
16230 cx,
16231 )
16232 })?
16233 .await?;
16234 anyhow::Ok(navigated)
16235 })
16236 }
16237
16238 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16239 let selection = self.selections.newest_anchor();
16240 let head = selection.head();
16241 let tail = selection.tail();
16242
16243 let Some((buffer, start_position)) =
16244 self.buffer.read(cx).text_anchor_for_position(head, cx)
16245 else {
16246 return;
16247 };
16248
16249 let end_position = if head != tail {
16250 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16251 return;
16252 };
16253 Some(pos)
16254 } else {
16255 None
16256 };
16257
16258 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16259 let url = if let Some(end_pos) = end_position {
16260 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16261 } else {
16262 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16263 };
16264
16265 if let Some(url) = url {
16266 editor.update(cx, |_, cx| {
16267 cx.open_url(&url);
16268 })
16269 } else {
16270 Ok(())
16271 }
16272 });
16273
16274 url_finder.detach();
16275 }
16276
16277 pub fn open_selected_filename(
16278 &mut self,
16279 _: &OpenSelectedFilename,
16280 window: &mut Window,
16281 cx: &mut Context<Self>,
16282 ) {
16283 let Some(workspace) = self.workspace() else {
16284 return;
16285 };
16286
16287 let position = self.selections.newest_anchor().head();
16288
16289 let Some((buffer, buffer_position)) =
16290 self.buffer.read(cx).text_anchor_for_position(position, cx)
16291 else {
16292 return;
16293 };
16294
16295 let project = self.project.clone();
16296
16297 cx.spawn_in(window, async move |_, cx| {
16298 let result = find_file(&buffer, project, buffer_position, cx).await;
16299
16300 if let Some((_, path)) = result {
16301 workspace
16302 .update_in(cx, |workspace, window, cx| {
16303 workspace.open_resolved_path(path, window, cx)
16304 })?
16305 .await?;
16306 }
16307 anyhow::Ok(())
16308 })
16309 .detach();
16310 }
16311
16312 pub(crate) fn navigate_to_hover_links(
16313 &mut self,
16314 kind: Option<GotoDefinitionKind>,
16315 definitions: Vec<HoverLink>,
16316 split: bool,
16317 window: &mut Window,
16318 cx: &mut Context<Editor>,
16319 ) -> Task<Result<Navigated>> {
16320 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16321 let mut first_url_or_file = None;
16322 let definitions: Vec<_> = definitions
16323 .into_iter()
16324 .filter_map(|def| match def {
16325 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16326 HoverLink::InlayHint(lsp_location, server_id) => {
16327 let computation =
16328 self.compute_target_location(lsp_location, server_id, window, cx);
16329 Some(cx.background_spawn(computation))
16330 }
16331 HoverLink::Url(url) => {
16332 first_url_or_file = Some(Either::Left(url));
16333 None
16334 }
16335 HoverLink::File(path) => {
16336 first_url_or_file = Some(Either::Right(path));
16337 None
16338 }
16339 })
16340 .collect();
16341
16342 let workspace = self.workspace();
16343
16344 cx.spawn_in(window, async move |editor, acx| {
16345 let mut locations: Vec<Location> = future::join_all(definitions)
16346 .await
16347 .into_iter()
16348 .filter_map(|location| location.transpose())
16349 .collect::<Result<_>>()
16350 .context("location tasks")?;
16351
16352 if locations.len() > 1 {
16353 let Some(workspace) = workspace else {
16354 return Ok(Navigated::No);
16355 };
16356
16357 let tab_kind = match kind {
16358 Some(GotoDefinitionKind::Implementation) => "Implementations",
16359 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16360 Some(GotoDefinitionKind::Declaration) => "Declarations",
16361 Some(GotoDefinitionKind::Type) => "Types",
16362 };
16363 let title = editor
16364 .update_in(acx, |_, _, cx| {
16365 let target = locations
16366 .iter()
16367 .map(|location| {
16368 location
16369 .buffer
16370 .read(cx)
16371 .text_for_range(location.range.clone())
16372 .collect::<String>()
16373 })
16374 .filter(|text| !text.contains('\n'))
16375 .unique()
16376 .take(3)
16377 .join(", ");
16378 if target.is_empty() {
16379 tab_kind.to_owned()
16380 } else {
16381 format!("{tab_kind} for {target}")
16382 }
16383 })
16384 .context("buffer title")?;
16385
16386 let opened = workspace
16387 .update_in(acx, |workspace, window, cx| {
16388 Self::open_locations_in_multibuffer(
16389 workspace,
16390 locations,
16391 title,
16392 split,
16393 MultibufferSelectionMode::First,
16394 window,
16395 cx,
16396 )
16397 })
16398 .is_ok();
16399
16400 anyhow::Ok(Navigated::from_bool(opened))
16401 } else if locations.is_empty() {
16402 // If there is one url or file, open it directly
16403 match first_url_or_file {
16404 Some(Either::Left(url)) => {
16405 acx.update(|_, cx| cx.open_url(&url))?;
16406 Ok(Navigated::Yes)
16407 }
16408 Some(Either::Right(path)) => {
16409 let Some(workspace) = workspace else {
16410 return Ok(Navigated::No);
16411 };
16412
16413 workspace
16414 .update_in(acx, |workspace, window, cx| {
16415 workspace.open_resolved_path(path, window, cx)
16416 })?
16417 .await?;
16418 Ok(Navigated::Yes)
16419 }
16420 None => Ok(Navigated::No),
16421 }
16422 } else {
16423 let Some(workspace) = workspace else {
16424 return Ok(Navigated::No);
16425 };
16426
16427 let target = locations.pop().unwrap();
16428 editor.update_in(acx, |editor, window, cx| {
16429 let range = target.range.to_point(target.buffer.read(cx));
16430 let range = editor.range_for_match(&range);
16431 let range = collapse_multiline_range(range);
16432
16433 if !split
16434 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16435 {
16436 editor.go_to_singleton_buffer_range(range, window, cx);
16437 } else {
16438 let pane = workspace.read(cx).active_pane().clone();
16439 window.defer(cx, move |window, cx| {
16440 let target_editor: Entity<Self> =
16441 workspace.update(cx, |workspace, cx| {
16442 let pane = if split {
16443 workspace.adjacent_pane(window, cx)
16444 } else {
16445 workspace.active_pane().clone()
16446 };
16447
16448 workspace.open_project_item(
16449 pane,
16450 target.buffer.clone(),
16451 true,
16452 true,
16453 window,
16454 cx,
16455 )
16456 });
16457 target_editor.update(cx, |target_editor, cx| {
16458 // When selecting a definition in a different buffer, disable the nav history
16459 // to avoid creating a history entry at the previous cursor location.
16460 pane.update(cx, |pane, _| pane.disable_history());
16461 target_editor.go_to_singleton_buffer_range(range, window, cx);
16462 pane.update(cx, |pane, _| pane.enable_history());
16463 });
16464 });
16465 }
16466 Navigated::Yes
16467 })
16468 }
16469 })
16470 }
16471
16472 fn compute_target_location(
16473 &self,
16474 lsp_location: lsp::Location,
16475 server_id: LanguageServerId,
16476 window: &mut Window,
16477 cx: &mut Context<Self>,
16478 ) -> Task<anyhow::Result<Option<Location>>> {
16479 let Some(project) = self.project.clone() else {
16480 return Task::ready(Ok(None));
16481 };
16482
16483 cx.spawn_in(window, async move |editor, cx| {
16484 let location_task = editor.update(cx, |_, cx| {
16485 project.update(cx, |project, cx| {
16486 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16487 })
16488 })?;
16489 let location = Some({
16490 let target_buffer_handle = location_task.await.context("open local buffer")?;
16491 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16492 let target_start = target_buffer
16493 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16494 let target_end = target_buffer
16495 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16496 target_buffer.anchor_after(target_start)
16497 ..target_buffer.anchor_before(target_end)
16498 })?;
16499 Location {
16500 buffer: target_buffer_handle,
16501 range,
16502 }
16503 });
16504 Ok(location)
16505 })
16506 }
16507
16508 pub fn find_all_references(
16509 &mut self,
16510 _: &FindAllReferences,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) -> Option<Task<Result<Navigated>>> {
16514 let selection = self.selections.newest::<usize>(cx);
16515 let multi_buffer = self.buffer.read(cx);
16516 let head = selection.head();
16517
16518 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16519 let head_anchor = multi_buffer_snapshot.anchor_at(
16520 head,
16521 if head < selection.tail() {
16522 Bias::Right
16523 } else {
16524 Bias::Left
16525 },
16526 );
16527
16528 match self
16529 .find_all_references_task_sources
16530 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16531 {
16532 Ok(_) => {
16533 log::info!(
16534 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16535 );
16536 return None;
16537 }
16538 Err(i) => {
16539 self.find_all_references_task_sources.insert(i, head_anchor);
16540 }
16541 }
16542
16543 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16544 let workspace = self.workspace()?;
16545 let project = workspace.read(cx).project().clone();
16546 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16547 Some(cx.spawn_in(window, async move |editor, cx| {
16548 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16549 if let Ok(i) = editor
16550 .find_all_references_task_sources
16551 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16552 {
16553 editor.find_all_references_task_sources.remove(i);
16554 }
16555 });
16556
16557 let Some(locations) = references.await? else {
16558 return anyhow::Ok(Navigated::No);
16559 };
16560 if locations.is_empty() {
16561 return anyhow::Ok(Navigated::No);
16562 }
16563
16564 workspace.update_in(cx, |workspace, window, cx| {
16565 let target = locations
16566 .iter()
16567 .map(|location| {
16568 location
16569 .buffer
16570 .read(cx)
16571 .text_for_range(location.range.clone())
16572 .collect::<String>()
16573 })
16574 .filter(|text| !text.contains('\n'))
16575 .unique()
16576 .take(3)
16577 .join(", ");
16578 let title = if target.is_empty() {
16579 "References".to_owned()
16580 } else {
16581 format!("References to {target}")
16582 };
16583 Self::open_locations_in_multibuffer(
16584 workspace,
16585 locations,
16586 title,
16587 false,
16588 MultibufferSelectionMode::First,
16589 window,
16590 cx,
16591 );
16592 Navigated::Yes
16593 })
16594 }))
16595 }
16596
16597 /// Opens a multibuffer with the given project locations in it
16598 pub fn open_locations_in_multibuffer(
16599 workspace: &mut Workspace,
16600 mut locations: Vec<Location>,
16601 title: String,
16602 split: bool,
16603 multibuffer_selection_mode: MultibufferSelectionMode,
16604 window: &mut Window,
16605 cx: &mut Context<Workspace>,
16606 ) {
16607 if locations.is_empty() {
16608 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16609 return;
16610 }
16611
16612 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16613
16614 let mut locations = locations.into_iter().peekable();
16615 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16616 let capability = workspace.project().read(cx).capability();
16617
16618 // a key to find existing multibuffer editors with the same set of locations
16619 // to prevent us from opening more and more multibuffer tabs for searches and the like
16620 let mut key = (title.clone(), vec![]);
16621 let excerpt_buffer = cx.new(|cx| {
16622 let key = &mut key.1;
16623 let mut multibuffer = MultiBuffer::new(capability);
16624 while let Some(location) = locations.next() {
16625 let buffer = location.buffer.read(cx);
16626 let mut ranges_for_buffer = Vec::new();
16627 let range = location.range.to_point(buffer);
16628 ranges_for_buffer.push(range.clone());
16629
16630 while let Some(next_location) =
16631 locations.next_if(|next_location| next_location.buffer == location.buffer)
16632 {
16633 ranges_for_buffer.push(next_location.range.to_point(buffer));
16634 }
16635
16636 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16637 key.push((
16638 location.buffer.read(cx).remote_id(),
16639 ranges_for_buffer.clone(),
16640 ));
16641 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16642 PathKey::for_buffer(&location.buffer, cx),
16643 location.buffer.clone(),
16644 ranges_for_buffer,
16645 multibuffer_context_lines(cx),
16646 cx,
16647 );
16648 ranges.extend(new_ranges)
16649 }
16650
16651 multibuffer.with_title(title)
16652 });
16653 let existing = workspace.active_pane().update(cx, |pane, cx| {
16654 pane.items()
16655 .filter_map(|item| item.downcast::<Editor>())
16656 .find(|editor| {
16657 editor
16658 .read(cx)
16659 .lookup_key
16660 .as_ref()
16661 .and_then(|it| {
16662 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16663 })
16664 .is_some_and(|it| *it == key)
16665 })
16666 });
16667 let editor = existing.unwrap_or_else(|| {
16668 cx.new(|cx| {
16669 let mut editor = Editor::for_multibuffer(
16670 excerpt_buffer,
16671 Some(workspace.project().clone()),
16672 window,
16673 cx,
16674 );
16675 editor.lookup_key = Some(Box::new(key));
16676 editor
16677 })
16678 });
16679 editor.update(cx, |editor, cx| {
16680 match multibuffer_selection_mode {
16681 MultibufferSelectionMode::First => {
16682 if let Some(first_range) = ranges.first() {
16683 editor.change_selections(
16684 SelectionEffects::no_scroll(),
16685 window,
16686 cx,
16687 |selections| {
16688 selections.clear_disjoint();
16689 selections
16690 .select_anchor_ranges(std::iter::once(first_range.clone()));
16691 },
16692 );
16693 }
16694 editor.highlight_background::<Self>(
16695 &ranges,
16696 |theme| theme.colors().editor_highlighted_line_background,
16697 cx,
16698 );
16699 }
16700 MultibufferSelectionMode::All => {
16701 editor.change_selections(
16702 SelectionEffects::no_scroll(),
16703 window,
16704 cx,
16705 |selections| {
16706 selections.clear_disjoint();
16707 selections.select_anchor_ranges(ranges);
16708 },
16709 );
16710 }
16711 }
16712 editor.register_buffers_with_language_servers(cx);
16713 });
16714
16715 let item = Box::new(editor);
16716 let item_id = item.item_id();
16717
16718 if split {
16719 workspace.split_item(SplitDirection::Right, item, window, cx);
16720 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16721 let (preview_item_id, preview_item_idx) =
16722 workspace.active_pane().read_with(cx, |pane, _| {
16723 (pane.preview_item_id(), pane.preview_item_idx())
16724 });
16725
16726 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16727
16728 if let Some(preview_item_id) = preview_item_id {
16729 workspace.active_pane().update(cx, |pane, cx| {
16730 pane.remove_item(preview_item_id, false, false, window, cx);
16731 });
16732 }
16733 } else {
16734 workspace.add_item_to_active_pane(item, None, true, window, cx);
16735 }
16736 workspace.active_pane().update(cx, |pane, cx| {
16737 pane.set_preview_item_id(Some(item_id), cx);
16738 });
16739 }
16740
16741 pub fn rename(
16742 &mut self,
16743 _: &Rename,
16744 window: &mut Window,
16745 cx: &mut Context<Self>,
16746 ) -> Option<Task<Result<()>>> {
16747 use language::ToOffset as _;
16748
16749 let provider = self.semantics_provider.clone()?;
16750 let selection = self.selections.newest_anchor().clone();
16751 let (cursor_buffer, cursor_buffer_position) = self
16752 .buffer
16753 .read(cx)
16754 .text_anchor_for_position(selection.head(), cx)?;
16755 let (tail_buffer, cursor_buffer_position_end) = self
16756 .buffer
16757 .read(cx)
16758 .text_anchor_for_position(selection.tail(), cx)?;
16759 if tail_buffer != cursor_buffer {
16760 return None;
16761 }
16762
16763 let snapshot = cursor_buffer.read(cx).snapshot();
16764 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16765 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16766 let prepare_rename = provider
16767 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16768 .unwrap_or_else(|| Task::ready(Ok(None)));
16769 drop(snapshot);
16770
16771 Some(cx.spawn_in(window, async move |this, cx| {
16772 let rename_range = if let Some(range) = prepare_rename.await? {
16773 Some(range)
16774 } else {
16775 this.update(cx, |this, cx| {
16776 let buffer = this.buffer.read(cx).snapshot(cx);
16777 let mut buffer_highlights = this
16778 .document_highlights_for_position(selection.head(), &buffer)
16779 .filter(|highlight| {
16780 highlight.start.excerpt_id == selection.head().excerpt_id
16781 && highlight.end.excerpt_id == selection.head().excerpt_id
16782 });
16783 buffer_highlights
16784 .next()
16785 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16786 })?
16787 };
16788 if let Some(rename_range) = rename_range {
16789 this.update_in(cx, |this, window, cx| {
16790 let snapshot = cursor_buffer.read(cx).snapshot();
16791 let rename_buffer_range = rename_range.to_offset(&snapshot);
16792 let cursor_offset_in_rename_range =
16793 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16794 let cursor_offset_in_rename_range_end =
16795 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16796
16797 this.take_rename(false, window, cx);
16798 let buffer = this.buffer.read(cx).read(cx);
16799 let cursor_offset = selection.head().to_offset(&buffer);
16800 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16801 let rename_end = rename_start + rename_buffer_range.len();
16802 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16803 let mut old_highlight_id = None;
16804 let old_name: Arc<str> = buffer
16805 .chunks(rename_start..rename_end, true)
16806 .map(|chunk| {
16807 if old_highlight_id.is_none() {
16808 old_highlight_id = chunk.syntax_highlight_id;
16809 }
16810 chunk.text
16811 })
16812 .collect::<String>()
16813 .into();
16814
16815 drop(buffer);
16816
16817 // Position the selection in the rename editor so that it matches the current selection.
16818 this.show_local_selections = false;
16819 let rename_editor = cx.new(|cx| {
16820 let mut editor = Editor::single_line(window, cx);
16821 editor.buffer.update(cx, |buffer, cx| {
16822 buffer.edit([(0..0, old_name.clone())], None, cx)
16823 });
16824 let rename_selection_range = match cursor_offset_in_rename_range
16825 .cmp(&cursor_offset_in_rename_range_end)
16826 {
16827 Ordering::Equal => {
16828 editor.select_all(&SelectAll, window, cx);
16829 return editor;
16830 }
16831 Ordering::Less => {
16832 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16833 }
16834 Ordering::Greater => {
16835 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16836 }
16837 };
16838 if rename_selection_range.end > old_name.len() {
16839 editor.select_all(&SelectAll, window, cx);
16840 } else {
16841 editor.change_selections(Default::default(), window, cx, |s| {
16842 s.select_ranges([rename_selection_range]);
16843 });
16844 }
16845 editor
16846 });
16847 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16848 if e == &EditorEvent::Focused {
16849 cx.emit(EditorEvent::FocusedIn)
16850 }
16851 })
16852 .detach();
16853
16854 let write_highlights =
16855 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16856 let read_highlights =
16857 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16858 let ranges = write_highlights
16859 .iter()
16860 .flat_map(|(_, ranges)| ranges.iter())
16861 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16862 .cloned()
16863 .collect();
16864
16865 this.highlight_text::<Rename>(
16866 ranges,
16867 HighlightStyle {
16868 fade_out: Some(0.6),
16869 ..Default::default()
16870 },
16871 cx,
16872 );
16873 let rename_focus_handle = rename_editor.focus_handle(cx);
16874 window.focus(&rename_focus_handle);
16875 let block_id = this.insert_blocks(
16876 [BlockProperties {
16877 style: BlockStyle::Flex,
16878 placement: BlockPlacement::Below(range.start),
16879 height: Some(1),
16880 render: Arc::new({
16881 let rename_editor = rename_editor.clone();
16882 move |cx: &mut BlockContext| {
16883 let mut text_style = cx.editor_style.text.clone();
16884 if let Some(highlight_style) = old_highlight_id
16885 .and_then(|h| h.style(&cx.editor_style.syntax))
16886 {
16887 text_style = text_style.highlight(highlight_style);
16888 }
16889 div()
16890 .block_mouse_except_scroll()
16891 .pl(cx.anchor_x)
16892 .child(EditorElement::new(
16893 &rename_editor,
16894 EditorStyle {
16895 background: cx.theme().system().transparent,
16896 local_player: cx.editor_style.local_player,
16897 text: text_style,
16898 scrollbar_width: cx.editor_style.scrollbar_width,
16899 syntax: cx.editor_style.syntax.clone(),
16900 status: cx.editor_style.status.clone(),
16901 inlay_hints_style: HighlightStyle {
16902 font_weight: Some(FontWeight::BOLD),
16903 ..make_inlay_hints_style(cx.app)
16904 },
16905 edit_prediction_styles: make_suggestion_styles(
16906 cx.app,
16907 ),
16908 ..EditorStyle::default()
16909 },
16910 ))
16911 .into_any_element()
16912 }
16913 }),
16914 priority: 0,
16915 }],
16916 Some(Autoscroll::fit()),
16917 cx,
16918 )[0];
16919 this.pending_rename = Some(RenameState {
16920 range,
16921 old_name,
16922 editor: rename_editor,
16923 block_id,
16924 });
16925 })?;
16926 }
16927
16928 Ok(())
16929 }))
16930 }
16931
16932 pub fn confirm_rename(
16933 &mut self,
16934 _: &ConfirmRename,
16935 window: &mut Window,
16936 cx: &mut Context<Self>,
16937 ) -> Option<Task<Result<()>>> {
16938 let rename = self.take_rename(false, window, cx)?;
16939 let workspace = self.workspace()?.downgrade();
16940 let (buffer, start) = self
16941 .buffer
16942 .read(cx)
16943 .text_anchor_for_position(rename.range.start, cx)?;
16944 let (end_buffer, _) = self
16945 .buffer
16946 .read(cx)
16947 .text_anchor_for_position(rename.range.end, cx)?;
16948 if buffer != end_buffer {
16949 return None;
16950 }
16951
16952 let old_name = rename.old_name;
16953 let new_name = rename.editor.read(cx).text(cx);
16954
16955 let rename = self.semantics_provider.as_ref()?.perform_rename(
16956 &buffer,
16957 start,
16958 new_name.clone(),
16959 cx,
16960 )?;
16961
16962 Some(cx.spawn_in(window, async move |editor, cx| {
16963 let project_transaction = rename.await?;
16964 Self::open_project_transaction(
16965 &editor,
16966 workspace,
16967 project_transaction,
16968 format!("Rename: {} → {}", old_name, new_name),
16969 cx,
16970 )
16971 .await?;
16972
16973 editor.update(cx, |editor, cx| {
16974 editor.refresh_document_highlights(cx);
16975 })?;
16976 Ok(())
16977 }))
16978 }
16979
16980 fn take_rename(
16981 &mut self,
16982 moving_cursor: bool,
16983 window: &mut Window,
16984 cx: &mut Context<Self>,
16985 ) -> Option<RenameState> {
16986 let rename = self.pending_rename.take()?;
16987 if rename.editor.focus_handle(cx).is_focused(window) {
16988 window.focus(&self.focus_handle);
16989 }
16990
16991 self.remove_blocks(
16992 [rename.block_id].into_iter().collect(),
16993 Some(Autoscroll::fit()),
16994 cx,
16995 );
16996 self.clear_highlights::<Rename>(cx);
16997 self.show_local_selections = true;
16998
16999 if moving_cursor {
17000 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17001 editor.selections.newest::<usize>(cx).head()
17002 });
17003
17004 // Update the selection to match the position of the selection inside
17005 // the rename editor.
17006 let snapshot = self.buffer.read(cx).read(cx);
17007 let rename_range = rename.range.to_offset(&snapshot);
17008 let cursor_in_editor = snapshot
17009 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17010 .min(rename_range.end);
17011 drop(snapshot);
17012
17013 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17014 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17015 });
17016 } else {
17017 self.refresh_document_highlights(cx);
17018 }
17019
17020 Some(rename)
17021 }
17022
17023 pub fn pending_rename(&self) -> Option<&RenameState> {
17024 self.pending_rename.as_ref()
17025 }
17026
17027 fn format(
17028 &mut self,
17029 _: &Format,
17030 window: &mut Window,
17031 cx: &mut Context<Self>,
17032 ) -> Option<Task<Result<()>>> {
17033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17034
17035 let project = match &self.project {
17036 Some(project) => project.clone(),
17037 None => return None,
17038 };
17039
17040 Some(self.perform_format(
17041 project,
17042 FormatTrigger::Manual,
17043 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17044 window,
17045 cx,
17046 ))
17047 }
17048
17049 fn format_selections(
17050 &mut self,
17051 _: &FormatSelections,
17052 window: &mut Window,
17053 cx: &mut Context<Self>,
17054 ) -> Option<Task<Result<()>>> {
17055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17056
17057 let project = match &self.project {
17058 Some(project) => project.clone(),
17059 None => return None,
17060 };
17061
17062 let ranges = self
17063 .selections
17064 .all_adjusted(cx)
17065 .into_iter()
17066 .map(|selection| selection.range())
17067 .collect_vec();
17068
17069 Some(self.perform_format(
17070 project,
17071 FormatTrigger::Manual,
17072 FormatTarget::Ranges(ranges),
17073 window,
17074 cx,
17075 ))
17076 }
17077
17078 fn perform_format(
17079 &mut self,
17080 project: Entity<Project>,
17081 trigger: FormatTrigger,
17082 target: FormatTarget,
17083 window: &mut Window,
17084 cx: &mut Context<Self>,
17085 ) -> Task<Result<()>> {
17086 let buffer = self.buffer.clone();
17087 let (buffers, target) = match target {
17088 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17089 FormatTarget::Ranges(selection_ranges) => {
17090 let multi_buffer = buffer.read(cx);
17091 let snapshot = multi_buffer.read(cx);
17092 let mut buffers = HashSet::default();
17093 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17094 BTreeMap::new();
17095 for selection_range in selection_ranges {
17096 for (buffer, buffer_range, _) in
17097 snapshot.range_to_buffer_ranges(selection_range)
17098 {
17099 let buffer_id = buffer.remote_id();
17100 let start = buffer.anchor_before(buffer_range.start);
17101 let end = buffer.anchor_after(buffer_range.end);
17102 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17103 buffer_id_to_ranges
17104 .entry(buffer_id)
17105 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17106 .or_insert_with(|| vec![start..end]);
17107 }
17108 }
17109 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17110 }
17111 };
17112
17113 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17114 let selections_prev = transaction_id_prev
17115 .and_then(|transaction_id_prev| {
17116 // default to selections as they were after the last edit, if we have them,
17117 // instead of how they are now.
17118 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17119 // will take you back to where you made the last edit, instead of staying where you scrolled
17120 self.selection_history
17121 .transaction(transaction_id_prev)
17122 .map(|t| t.0.clone())
17123 })
17124 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17125
17126 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17127 let format = project.update(cx, |project, cx| {
17128 project.format(buffers, target, true, trigger, cx)
17129 });
17130
17131 cx.spawn_in(window, async move |editor, cx| {
17132 let transaction = futures::select_biased! {
17133 transaction = format.log_err().fuse() => transaction,
17134 () = timeout => {
17135 log::warn!("timed out waiting for formatting");
17136 None
17137 }
17138 };
17139
17140 buffer
17141 .update(cx, |buffer, cx| {
17142 if let Some(transaction) = transaction
17143 && !buffer.is_singleton()
17144 {
17145 buffer.push_transaction(&transaction.0, cx);
17146 }
17147 cx.notify();
17148 })
17149 .ok();
17150
17151 if let Some(transaction_id_now) =
17152 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17153 {
17154 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17155 if has_new_transaction {
17156 _ = editor.update(cx, |editor, _| {
17157 editor
17158 .selection_history
17159 .insert_transaction(transaction_id_now, selections_prev);
17160 });
17161 }
17162 }
17163
17164 Ok(())
17165 })
17166 }
17167
17168 fn organize_imports(
17169 &mut self,
17170 _: &OrganizeImports,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) -> Option<Task<Result<()>>> {
17174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17175 let project = match &self.project {
17176 Some(project) => project.clone(),
17177 None => return None,
17178 };
17179 Some(self.perform_code_action_kind(
17180 project,
17181 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17182 window,
17183 cx,
17184 ))
17185 }
17186
17187 fn perform_code_action_kind(
17188 &mut self,
17189 project: Entity<Project>,
17190 kind: CodeActionKind,
17191 window: &mut Window,
17192 cx: &mut Context<Self>,
17193 ) -> Task<Result<()>> {
17194 let buffer = self.buffer.clone();
17195 let buffers = buffer.read(cx).all_buffers();
17196 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17197 let apply_action = project.update(cx, |project, cx| {
17198 project.apply_code_action_kind(buffers, kind, true, cx)
17199 });
17200 cx.spawn_in(window, async move |_, cx| {
17201 let transaction = futures::select_biased! {
17202 () = timeout => {
17203 log::warn!("timed out waiting for executing code action");
17204 None
17205 }
17206 transaction = apply_action.log_err().fuse() => transaction,
17207 };
17208 buffer
17209 .update(cx, |buffer, cx| {
17210 // check if we need this
17211 if let Some(transaction) = transaction
17212 && !buffer.is_singleton()
17213 {
17214 buffer.push_transaction(&transaction.0, cx);
17215 }
17216 cx.notify();
17217 })
17218 .ok();
17219 Ok(())
17220 })
17221 }
17222
17223 pub fn restart_language_server(
17224 &mut self,
17225 _: &RestartLanguageServer,
17226 _: &mut Window,
17227 cx: &mut Context<Self>,
17228 ) {
17229 if let Some(project) = self.project.clone() {
17230 self.buffer.update(cx, |multi_buffer, cx| {
17231 project.update(cx, |project, cx| {
17232 project.restart_language_servers_for_buffers(
17233 multi_buffer.all_buffers().into_iter().collect(),
17234 HashSet::default(),
17235 cx,
17236 );
17237 });
17238 })
17239 }
17240 }
17241
17242 pub fn stop_language_server(
17243 &mut self,
17244 _: &StopLanguageServer,
17245 _: &mut Window,
17246 cx: &mut Context<Self>,
17247 ) {
17248 if let Some(project) = self.project.clone() {
17249 self.buffer.update(cx, |multi_buffer, cx| {
17250 project.update(cx, |project, cx| {
17251 project.stop_language_servers_for_buffers(
17252 multi_buffer.all_buffers().into_iter().collect(),
17253 HashSet::default(),
17254 cx,
17255 );
17256 cx.emit(project::Event::RefreshInlayHints);
17257 });
17258 });
17259 }
17260 }
17261
17262 fn cancel_language_server_work(
17263 workspace: &mut Workspace,
17264 _: &actions::CancelLanguageServerWork,
17265 _: &mut Window,
17266 cx: &mut Context<Workspace>,
17267 ) {
17268 let project = workspace.project();
17269 let buffers = workspace
17270 .active_item(cx)
17271 .and_then(|item| item.act_as::<Editor>(cx))
17272 .map_or(HashSet::default(), |editor| {
17273 editor.read(cx).buffer.read(cx).all_buffers()
17274 });
17275 project.update(cx, |project, cx| {
17276 project.cancel_language_server_work_for_buffers(buffers, cx);
17277 });
17278 }
17279
17280 fn show_character_palette(
17281 &mut self,
17282 _: &ShowCharacterPalette,
17283 window: &mut Window,
17284 _: &mut Context<Self>,
17285 ) {
17286 window.show_character_palette();
17287 }
17288
17289 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17290 if !self.diagnostics_enabled() {
17291 return;
17292 }
17293
17294 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17295 let buffer = self.buffer.read(cx).snapshot(cx);
17296 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17297 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17298 let is_valid = buffer
17299 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17300 .any(|entry| {
17301 entry.diagnostic.is_primary
17302 && !entry.range.is_empty()
17303 && entry.range.start == primary_range_start
17304 && entry.diagnostic.message == active_diagnostics.active_message
17305 });
17306
17307 if !is_valid {
17308 self.dismiss_diagnostics(cx);
17309 }
17310 }
17311 }
17312
17313 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17314 match &self.active_diagnostics {
17315 ActiveDiagnostic::Group(group) => Some(group),
17316 _ => None,
17317 }
17318 }
17319
17320 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17321 if !self.diagnostics_enabled() {
17322 return;
17323 }
17324 self.dismiss_diagnostics(cx);
17325 self.active_diagnostics = ActiveDiagnostic::All;
17326 }
17327
17328 fn activate_diagnostics(
17329 &mut self,
17330 buffer_id: BufferId,
17331 diagnostic: DiagnosticEntry<usize>,
17332 window: &mut Window,
17333 cx: &mut Context<Self>,
17334 ) {
17335 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17336 return;
17337 }
17338 self.dismiss_diagnostics(cx);
17339 let snapshot = self.snapshot(window, cx);
17340 let buffer = self.buffer.read(cx).snapshot(cx);
17341 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17342 return;
17343 };
17344
17345 let diagnostic_group = buffer
17346 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17347 .collect::<Vec<_>>();
17348
17349 let blocks =
17350 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17351
17352 let blocks = self.display_map.update(cx, |display_map, cx| {
17353 display_map.insert_blocks(blocks, cx).into_iter().collect()
17354 });
17355 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17356 active_range: buffer.anchor_before(diagnostic.range.start)
17357 ..buffer.anchor_after(diagnostic.range.end),
17358 active_message: diagnostic.diagnostic.message.clone(),
17359 group_id: diagnostic.diagnostic.group_id,
17360 blocks,
17361 });
17362 cx.notify();
17363 }
17364
17365 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17366 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17367 return;
17368 };
17369
17370 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17371 if let ActiveDiagnostic::Group(group) = prev {
17372 self.display_map.update(cx, |display_map, cx| {
17373 display_map.remove_blocks(group.blocks, cx);
17374 });
17375 cx.notify();
17376 }
17377 }
17378
17379 /// Disable inline diagnostics rendering for this editor.
17380 pub fn disable_inline_diagnostics(&mut self) {
17381 self.inline_diagnostics_enabled = false;
17382 self.inline_diagnostics_update = Task::ready(());
17383 self.inline_diagnostics.clear();
17384 }
17385
17386 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17387 self.diagnostics_enabled = false;
17388 self.dismiss_diagnostics(cx);
17389 self.inline_diagnostics_update = Task::ready(());
17390 self.inline_diagnostics.clear();
17391 }
17392
17393 pub fn disable_word_completions(&mut self) {
17394 self.word_completions_enabled = false;
17395 }
17396
17397 pub fn diagnostics_enabled(&self) -> bool {
17398 self.diagnostics_enabled && self.mode.is_full()
17399 }
17400
17401 pub fn inline_diagnostics_enabled(&self) -> bool {
17402 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17403 }
17404
17405 pub fn show_inline_diagnostics(&self) -> bool {
17406 self.show_inline_diagnostics
17407 }
17408
17409 pub fn toggle_inline_diagnostics(
17410 &mut self,
17411 _: &ToggleInlineDiagnostics,
17412 window: &mut Window,
17413 cx: &mut Context<Editor>,
17414 ) {
17415 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17416 self.refresh_inline_diagnostics(false, window, cx);
17417 }
17418
17419 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17420 self.diagnostics_max_severity = severity;
17421 self.display_map.update(cx, |display_map, _| {
17422 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17423 });
17424 }
17425
17426 pub fn toggle_diagnostics(
17427 &mut self,
17428 _: &ToggleDiagnostics,
17429 window: &mut Window,
17430 cx: &mut Context<Editor>,
17431 ) {
17432 if !self.diagnostics_enabled() {
17433 return;
17434 }
17435
17436 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17437 EditorSettings::get_global(cx)
17438 .diagnostics_max_severity
17439 .filter(|severity| severity != &DiagnosticSeverity::Off)
17440 .unwrap_or(DiagnosticSeverity::Hint)
17441 } else {
17442 DiagnosticSeverity::Off
17443 };
17444 self.set_max_diagnostics_severity(new_severity, cx);
17445 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17446 self.active_diagnostics = ActiveDiagnostic::None;
17447 self.inline_diagnostics_update = Task::ready(());
17448 self.inline_diagnostics.clear();
17449 } else {
17450 self.refresh_inline_diagnostics(false, window, cx);
17451 }
17452
17453 cx.notify();
17454 }
17455
17456 pub fn toggle_minimap(
17457 &mut self,
17458 _: &ToggleMinimap,
17459 window: &mut Window,
17460 cx: &mut Context<Editor>,
17461 ) {
17462 if self.supports_minimap(cx) {
17463 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17464 }
17465 }
17466
17467 fn refresh_inline_diagnostics(
17468 &mut self,
17469 debounce: bool,
17470 window: &mut Window,
17471 cx: &mut Context<Self>,
17472 ) {
17473 let max_severity = ProjectSettings::get_global(cx)
17474 .diagnostics
17475 .inline
17476 .max_severity
17477 .unwrap_or(self.diagnostics_max_severity);
17478
17479 if !self.inline_diagnostics_enabled()
17480 || !self.show_inline_diagnostics
17481 || max_severity == DiagnosticSeverity::Off
17482 {
17483 self.inline_diagnostics_update = Task::ready(());
17484 self.inline_diagnostics.clear();
17485 return;
17486 }
17487
17488 let debounce_ms = ProjectSettings::get_global(cx)
17489 .diagnostics
17490 .inline
17491 .update_debounce_ms;
17492 let debounce = if debounce && debounce_ms > 0 {
17493 Some(Duration::from_millis(debounce_ms))
17494 } else {
17495 None
17496 };
17497 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17498 if let Some(debounce) = debounce {
17499 cx.background_executor().timer(debounce).await;
17500 }
17501 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17502 editor
17503 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17504 .ok()
17505 }) else {
17506 return;
17507 };
17508
17509 let new_inline_diagnostics = cx
17510 .background_spawn(async move {
17511 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17512 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17513 let message = diagnostic_entry
17514 .diagnostic
17515 .message
17516 .split_once('\n')
17517 .map(|(line, _)| line)
17518 .map(SharedString::new)
17519 .unwrap_or_else(|| {
17520 SharedString::from(diagnostic_entry.diagnostic.message)
17521 });
17522 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17523 let (Ok(i) | Err(i)) = inline_diagnostics
17524 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17525 inline_diagnostics.insert(
17526 i,
17527 (
17528 start_anchor,
17529 InlineDiagnostic {
17530 message,
17531 group_id: diagnostic_entry.diagnostic.group_id,
17532 start: diagnostic_entry.range.start.to_point(&snapshot),
17533 is_primary: diagnostic_entry.diagnostic.is_primary,
17534 severity: diagnostic_entry.diagnostic.severity,
17535 },
17536 ),
17537 );
17538 }
17539 inline_diagnostics
17540 })
17541 .await;
17542
17543 editor
17544 .update(cx, |editor, cx| {
17545 editor.inline_diagnostics = new_inline_diagnostics;
17546 cx.notify();
17547 })
17548 .ok();
17549 });
17550 }
17551
17552 fn pull_diagnostics(
17553 &mut self,
17554 buffer_id: Option<BufferId>,
17555 window: &Window,
17556 cx: &mut Context<Self>,
17557 ) -> Option<()> {
17558 if !self.mode().is_full() {
17559 return None;
17560 }
17561 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17562 .diagnostics
17563 .lsp_pull_diagnostics;
17564 if !pull_diagnostics_settings.enabled {
17565 return None;
17566 }
17567 let project = self.project()?.downgrade();
17568 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17569 let mut buffers = self.buffer.read(cx).all_buffers();
17570 if let Some(buffer_id) = buffer_id {
17571 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17572 }
17573
17574 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17575 cx.background_executor().timer(debounce).await;
17576
17577 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17578 buffers
17579 .into_iter()
17580 .filter_map(|buffer| {
17581 project
17582 .update(cx, |project, cx| {
17583 project.lsp_store().update(cx, |lsp_store, cx| {
17584 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17585 })
17586 })
17587 .ok()
17588 })
17589 .collect::<FuturesUnordered<_>>()
17590 }) else {
17591 return;
17592 };
17593
17594 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17595 match pull_task {
17596 Ok(()) => {
17597 if editor
17598 .update_in(cx, |editor, window, cx| {
17599 editor.update_diagnostics_state(window, cx);
17600 })
17601 .is_err()
17602 {
17603 return;
17604 }
17605 }
17606 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17607 }
17608 }
17609 });
17610
17611 Some(())
17612 }
17613
17614 pub fn set_selections_from_remote(
17615 &mut self,
17616 selections: Vec<Selection<Anchor>>,
17617 pending_selection: Option<Selection<Anchor>>,
17618 window: &mut Window,
17619 cx: &mut Context<Self>,
17620 ) {
17621 let old_cursor_position = self.selections.newest_anchor().head();
17622 self.selections.change_with(cx, |s| {
17623 s.select_anchors(selections);
17624 if let Some(pending_selection) = pending_selection {
17625 s.set_pending(pending_selection, SelectMode::Character);
17626 } else {
17627 s.clear_pending();
17628 }
17629 });
17630 self.selections_did_change(
17631 false,
17632 &old_cursor_position,
17633 SelectionEffects::default(),
17634 window,
17635 cx,
17636 );
17637 }
17638
17639 pub fn transact(
17640 &mut self,
17641 window: &mut Window,
17642 cx: &mut Context<Self>,
17643 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17644 ) -> Option<TransactionId> {
17645 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17646 this.start_transaction_at(Instant::now(), window, cx);
17647 update(this, window, cx);
17648 this.end_transaction_at(Instant::now(), cx)
17649 })
17650 }
17651
17652 pub fn start_transaction_at(
17653 &mut self,
17654 now: Instant,
17655 window: &mut Window,
17656 cx: &mut Context<Self>,
17657 ) -> Option<TransactionId> {
17658 self.end_selection(window, cx);
17659 if let Some(tx_id) = self
17660 .buffer
17661 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17662 {
17663 self.selection_history
17664 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17665 cx.emit(EditorEvent::TransactionBegun {
17666 transaction_id: tx_id,
17667 });
17668 Some(tx_id)
17669 } else {
17670 None
17671 }
17672 }
17673
17674 pub fn end_transaction_at(
17675 &mut self,
17676 now: Instant,
17677 cx: &mut Context<Self>,
17678 ) -> Option<TransactionId> {
17679 if let Some(transaction_id) = self
17680 .buffer
17681 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17682 {
17683 if let Some((_, end_selections)) =
17684 self.selection_history.transaction_mut(transaction_id)
17685 {
17686 *end_selections = Some(self.selections.disjoint_anchors_arc());
17687 } else {
17688 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17689 }
17690
17691 cx.emit(EditorEvent::Edited { transaction_id });
17692 Some(transaction_id)
17693 } else {
17694 None
17695 }
17696 }
17697
17698 pub fn modify_transaction_selection_history(
17699 &mut self,
17700 transaction_id: TransactionId,
17701 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17702 ) -> bool {
17703 self.selection_history
17704 .transaction_mut(transaction_id)
17705 .map(modify)
17706 .is_some()
17707 }
17708
17709 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17710 if self.selection_mark_mode {
17711 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17712 s.move_with(|_, sel| {
17713 sel.collapse_to(sel.head(), SelectionGoal::None);
17714 });
17715 })
17716 }
17717 self.selection_mark_mode = true;
17718 cx.notify();
17719 }
17720
17721 pub fn swap_selection_ends(
17722 &mut self,
17723 _: &actions::SwapSelectionEnds,
17724 window: &mut Window,
17725 cx: &mut Context<Self>,
17726 ) {
17727 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17728 s.move_with(|_, sel| {
17729 if sel.start != sel.end {
17730 sel.reversed = !sel.reversed
17731 }
17732 });
17733 });
17734 self.request_autoscroll(Autoscroll::newest(), cx);
17735 cx.notify();
17736 }
17737
17738 pub fn toggle_focus(
17739 workspace: &mut Workspace,
17740 _: &actions::ToggleFocus,
17741 window: &mut Window,
17742 cx: &mut Context<Workspace>,
17743 ) {
17744 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17745 return;
17746 };
17747 workspace.activate_item(&item, true, true, window, cx);
17748 }
17749
17750 pub fn toggle_fold(
17751 &mut self,
17752 _: &actions::ToggleFold,
17753 window: &mut Window,
17754 cx: &mut Context<Self>,
17755 ) {
17756 if self.is_singleton(cx) {
17757 let selection = self.selections.newest::<Point>(cx);
17758
17759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17760 let range = if selection.is_empty() {
17761 let point = selection.head().to_display_point(&display_map);
17762 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17763 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17764 .to_point(&display_map);
17765 start..end
17766 } else {
17767 selection.range()
17768 };
17769 if display_map.folds_in_range(range).next().is_some() {
17770 self.unfold_lines(&Default::default(), window, cx)
17771 } else {
17772 self.fold(&Default::default(), window, cx)
17773 }
17774 } else {
17775 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17776 let buffer_ids: HashSet<_> = self
17777 .selections
17778 .disjoint_anchor_ranges()
17779 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17780 .collect();
17781
17782 let should_unfold = buffer_ids
17783 .iter()
17784 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17785
17786 for buffer_id in buffer_ids {
17787 if should_unfold {
17788 self.unfold_buffer(buffer_id, cx);
17789 } else {
17790 self.fold_buffer(buffer_id, cx);
17791 }
17792 }
17793 }
17794 }
17795
17796 pub fn toggle_fold_recursive(
17797 &mut self,
17798 _: &actions::ToggleFoldRecursive,
17799 window: &mut Window,
17800 cx: &mut Context<Self>,
17801 ) {
17802 let selection = self.selections.newest::<Point>(cx);
17803
17804 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17805 let range = if selection.is_empty() {
17806 let point = selection.head().to_display_point(&display_map);
17807 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17808 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17809 .to_point(&display_map);
17810 start..end
17811 } else {
17812 selection.range()
17813 };
17814 if display_map.folds_in_range(range).next().is_some() {
17815 self.unfold_recursive(&Default::default(), window, cx)
17816 } else {
17817 self.fold_recursive(&Default::default(), window, cx)
17818 }
17819 }
17820
17821 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17822 if self.is_singleton(cx) {
17823 let mut to_fold = Vec::new();
17824 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17825 let selections = self.selections.all_adjusted(cx);
17826
17827 for selection in selections {
17828 let range = selection.range().sorted();
17829 let buffer_start_row = range.start.row;
17830
17831 if range.start.row != range.end.row {
17832 let mut found = false;
17833 let mut row = range.start.row;
17834 while row <= range.end.row {
17835 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17836 {
17837 found = true;
17838 row = crease.range().end.row + 1;
17839 to_fold.push(crease);
17840 } else {
17841 row += 1
17842 }
17843 }
17844 if found {
17845 continue;
17846 }
17847 }
17848
17849 for row in (0..=range.start.row).rev() {
17850 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17851 && crease.range().end.row >= buffer_start_row
17852 {
17853 to_fold.push(crease);
17854 if row <= range.start.row {
17855 break;
17856 }
17857 }
17858 }
17859 }
17860
17861 self.fold_creases(to_fold, true, window, cx);
17862 } else {
17863 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17864 let buffer_ids = self
17865 .selections
17866 .disjoint_anchor_ranges()
17867 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17868 .collect::<HashSet<_>>();
17869 for buffer_id in buffer_ids {
17870 self.fold_buffer(buffer_id, cx);
17871 }
17872 }
17873 }
17874
17875 pub fn toggle_fold_all(
17876 &mut self,
17877 _: &actions::ToggleFoldAll,
17878 window: &mut Window,
17879 cx: &mut Context<Self>,
17880 ) {
17881 if self.buffer.read(cx).is_singleton() {
17882 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17883 let has_folds = display_map
17884 .folds_in_range(0..display_map.buffer_snapshot.len())
17885 .next()
17886 .is_some();
17887
17888 if has_folds {
17889 self.unfold_all(&actions::UnfoldAll, window, cx);
17890 } else {
17891 self.fold_all(&actions::FoldAll, window, cx);
17892 }
17893 } else {
17894 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17895 let should_unfold = buffer_ids
17896 .iter()
17897 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17898
17899 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17900 editor
17901 .update_in(cx, |editor, _, cx| {
17902 for buffer_id in buffer_ids {
17903 if should_unfold {
17904 editor.unfold_buffer(buffer_id, cx);
17905 } else {
17906 editor.fold_buffer(buffer_id, cx);
17907 }
17908 }
17909 })
17910 .ok();
17911 });
17912 }
17913 }
17914
17915 fn fold_at_level(
17916 &mut self,
17917 fold_at: &FoldAtLevel,
17918 window: &mut Window,
17919 cx: &mut Context<Self>,
17920 ) {
17921 if !self.buffer.read(cx).is_singleton() {
17922 return;
17923 }
17924
17925 let fold_at_level = fold_at.0;
17926 let snapshot = self.buffer.read(cx).snapshot(cx);
17927 let mut to_fold = Vec::new();
17928 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17929
17930 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17931 while start_row < end_row {
17932 match self
17933 .snapshot(window, cx)
17934 .crease_for_buffer_row(MultiBufferRow(start_row))
17935 {
17936 Some(crease) => {
17937 let nested_start_row = crease.range().start.row + 1;
17938 let nested_end_row = crease.range().end.row;
17939
17940 if current_level < fold_at_level {
17941 stack.push((nested_start_row, nested_end_row, current_level + 1));
17942 } else if current_level == fold_at_level {
17943 to_fold.push(crease);
17944 }
17945
17946 start_row = nested_end_row + 1;
17947 }
17948 None => start_row += 1,
17949 }
17950 }
17951 }
17952
17953 self.fold_creases(to_fold, true, window, cx);
17954 }
17955
17956 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17957 if self.buffer.read(cx).is_singleton() {
17958 let mut fold_ranges = Vec::new();
17959 let snapshot = self.buffer.read(cx).snapshot(cx);
17960
17961 for row in 0..snapshot.max_row().0 {
17962 if let Some(foldable_range) = self
17963 .snapshot(window, cx)
17964 .crease_for_buffer_row(MultiBufferRow(row))
17965 {
17966 fold_ranges.push(foldable_range);
17967 }
17968 }
17969
17970 self.fold_creases(fold_ranges, true, window, cx);
17971 } else {
17972 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17973 editor
17974 .update_in(cx, |editor, _, cx| {
17975 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17976 editor.fold_buffer(buffer_id, cx);
17977 }
17978 })
17979 .ok();
17980 });
17981 }
17982 }
17983
17984 pub fn fold_function_bodies(
17985 &mut self,
17986 _: &actions::FoldFunctionBodies,
17987 window: &mut Window,
17988 cx: &mut Context<Self>,
17989 ) {
17990 let snapshot = self.buffer.read(cx).snapshot(cx);
17991
17992 let ranges = snapshot
17993 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17994 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17995 .collect::<Vec<_>>();
17996
17997 let creases = ranges
17998 .into_iter()
17999 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18000 .collect();
18001
18002 self.fold_creases(creases, true, window, cx);
18003 }
18004
18005 pub fn fold_recursive(
18006 &mut self,
18007 _: &actions::FoldRecursive,
18008 window: &mut Window,
18009 cx: &mut Context<Self>,
18010 ) {
18011 let mut to_fold = Vec::new();
18012 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18013 let selections = self.selections.all_adjusted(cx);
18014
18015 for selection in selections {
18016 let range = selection.range().sorted();
18017 let buffer_start_row = range.start.row;
18018
18019 if range.start.row != range.end.row {
18020 let mut found = false;
18021 for row in range.start.row..=range.end.row {
18022 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18023 found = true;
18024 to_fold.push(crease);
18025 }
18026 }
18027 if found {
18028 continue;
18029 }
18030 }
18031
18032 for row in (0..=range.start.row).rev() {
18033 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18034 if crease.range().end.row >= buffer_start_row {
18035 to_fold.push(crease);
18036 } else {
18037 break;
18038 }
18039 }
18040 }
18041 }
18042
18043 self.fold_creases(to_fold, true, window, cx);
18044 }
18045
18046 pub fn fold_at(
18047 &mut self,
18048 buffer_row: MultiBufferRow,
18049 window: &mut Window,
18050 cx: &mut Context<Self>,
18051 ) {
18052 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18053
18054 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18055 let autoscroll = self
18056 .selections
18057 .all::<Point>(cx)
18058 .iter()
18059 .any(|selection| crease.range().overlaps(&selection.range()));
18060
18061 self.fold_creases(vec![crease], autoscroll, window, cx);
18062 }
18063 }
18064
18065 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18066 if self.is_singleton(cx) {
18067 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18068 let buffer = &display_map.buffer_snapshot;
18069 let selections = self.selections.all::<Point>(cx);
18070 let ranges = selections
18071 .iter()
18072 .map(|s| {
18073 let range = s.display_range(&display_map).sorted();
18074 let mut start = range.start.to_point(&display_map);
18075 let mut end = range.end.to_point(&display_map);
18076 start.column = 0;
18077 end.column = buffer.line_len(MultiBufferRow(end.row));
18078 start..end
18079 })
18080 .collect::<Vec<_>>();
18081
18082 self.unfold_ranges(&ranges, true, true, cx);
18083 } else {
18084 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18085 let buffer_ids = self
18086 .selections
18087 .disjoint_anchor_ranges()
18088 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18089 .collect::<HashSet<_>>();
18090 for buffer_id in buffer_ids {
18091 self.unfold_buffer(buffer_id, cx);
18092 }
18093 }
18094 }
18095
18096 pub fn unfold_recursive(
18097 &mut self,
18098 _: &UnfoldRecursive,
18099 _window: &mut Window,
18100 cx: &mut Context<Self>,
18101 ) {
18102 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18103 let selections = self.selections.all::<Point>(cx);
18104 let ranges = selections
18105 .iter()
18106 .map(|s| {
18107 let mut range = s.display_range(&display_map).sorted();
18108 *range.start.column_mut() = 0;
18109 *range.end.column_mut() = display_map.line_len(range.end.row());
18110 let start = range.start.to_point(&display_map);
18111 let end = range.end.to_point(&display_map);
18112 start..end
18113 })
18114 .collect::<Vec<_>>();
18115
18116 self.unfold_ranges(&ranges, true, true, cx);
18117 }
18118
18119 pub fn unfold_at(
18120 &mut self,
18121 buffer_row: MultiBufferRow,
18122 _window: &mut Window,
18123 cx: &mut Context<Self>,
18124 ) {
18125 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18126
18127 let intersection_range = Point::new(buffer_row.0, 0)
18128 ..Point::new(
18129 buffer_row.0,
18130 display_map.buffer_snapshot.line_len(buffer_row),
18131 );
18132
18133 let autoscroll = self
18134 .selections
18135 .all::<Point>(cx)
18136 .iter()
18137 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18138
18139 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18140 }
18141
18142 pub fn unfold_all(
18143 &mut self,
18144 _: &actions::UnfoldAll,
18145 _window: &mut Window,
18146 cx: &mut Context<Self>,
18147 ) {
18148 if self.buffer.read(cx).is_singleton() {
18149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18150 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18151 } else {
18152 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18153 editor
18154 .update(cx, |editor, cx| {
18155 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18156 editor.unfold_buffer(buffer_id, cx);
18157 }
18158 })
18159 .ok();
18160 });
18161 }
18162 }
18163
18164 pub fn fold_selected_ranges(
18165 &mut self,
18166 _: &FoldSelectedRanges,
18167 window: &mut Window,
18168 cx: &mut Context<Self>,
18169 ) {
18170 let selections = self.selections.all_adjusted(cx);
18171 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18172 let ranges = selections
18173 .into_iter()
18174 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18175 .collect::<Vec<_>>();
18176 self.fold_creases(ranges, true, window, cx);
18177 }
18178
18179 pub fn fold_ranges<T: ToOffset + Clone>(
18180 &mut self,
18181 ranges: Vec<Range<T>>,
18182 auto_scroll: bool,
18183 window: &mut Window,
18184 cx: &mut Context<Self>,
18185 ) {
18186 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18187 let ranges = ranges
18188 .into_iter()
18189 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18190 .collect::<Vec<_>>();
18191 self.fold_creases(ranges, auto_scroll, window, cx);
18192 }
18193
18194 pub fn fold_creases<T: ToOffset + Clone>(
18195 &mut self,
18196 creases: Vec<Crease<T>>,
18197 auto_scroll: bool,
18198 _window: &mut Window,
18199 cx: &mut Context<Self>,
18200 ) {
18201 if creases.is_empty() {
18202 return;
18203 }
18204
18205 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18206
18207 if auto_scroll {
18208 self.request_autoscroll(Autoscroll::fit(), cx);
18209 }
18210
18211 cx.notify();
18212
18213 self.scrollbar_marker_state.dirty = true;
18214 self.folds_did_change(cx);
18215 }
18216
18217 /// Removes any folds whose ranges intersect any of the given ranges.
18218 pub fn unfold_ranges<T: ToOffset + Clone>(
18219 &mut self,
18220 ranges: &[Range<T>],
18221 inclusive: bool,
18222 auto_scroll: bool,
18223 cx: &mut Context<Self>,
18224 ) {
18225 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18226 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18227 });
18228 self.folds_did_change(cx);
18229 }
18230
18231 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18232 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18233 return;
18234 }
18235 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18236 self.display_map.update(cx, |display_map, cx| {
18237 display_map.fold_buffers([buffer_id], cx)
18238 });
18239 cx.emit(EditorEvent::BufferFoldToggled {
18240 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18241 folded: true,
18242 });
18243 cx.notify();
18244 }
18245
18246 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18247 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18248 return;
18249 }
18250 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18251 self.display_map.update(cx, |display_map, cx| {
18252 display_map.unfold_buffers([buffer_id], cx);
18253 });
18254 cx.emit(EditorEvent::BufferFoldToggled {
18255 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18256 folded: false,
18257 });
18258 cx.notify();
18259 }
18260
18261 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18262 self.display_map.read(cx).is_buffer_folded(buffer)
18263 }
18264
18265 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18266 self.display_map.read(cx).folded_buffers()
18267 }
18268
18269 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18270 self.display_map.update(cx, |display_map, cx| {
18271 display_map.disable_header_for_buffer(buffer_id, cx);
18272 });
18273 cx.notify();
18274 }
18275
18276 /// Removes any folds with the given ranges.
18277 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18278 &mut self,
18279 ranges: &[Range<T>],
18280 type_id: TypeId,
18281 auto_scroll: bool,
18282 cx: &mut Context<Self>,
18283 ) {
18284 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18285 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18286 });
18287 self.folds_did_change(cx);
18288 }
18289
18290 fn remove_folds_with<T: ToOffset + Clone>(
18291 &mut self,
18292 ranges: &[Range<T>],
18293 auto_scroll: bool,
18294 cx: &mut Context<Self>,
18295 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18296 ) {
18297 if ranges.is_empty() {
18298 return;
18299 }
18300
18301 let mut buffers_affected = HashSet::default();
18302 let multi_buffer = self.buffer().read(cx);
18303 for range in ranges {
18304 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18305 buffers_affected.insert(buffer.read(cx).remote_id());
18306 };
18307 }
18308
18309 self.display_map.update(cx, update);
18310
18311 if auto_scroll {
18312 self.request_autoscroll(Autoscroll::fit(), cx);
18313 }
18314
18315 cx.notify();
18316 self.scrollbar_marker_state.dirty = true;
18317 self.active_indent_guides_state.dirty = true;
18318 }
18319
18320 pub fn update_renderer_widths(
18321 &mut self,
18322 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18323 cx: &mut Context<Self>,
18324 ) -> bool {
18325 self.display_map
18326 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18327 }
18328
18329 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18330 self.display_map.read(cx).fold_placeholder.clone()
18331 }
18332
18333 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18334 self.buffer.update(cx, |buffer, cx| {
18335 buffer.set_all_diff_hunks_expanded(cx);
18336 });
18337 }
18338
18339 pub fn expand_all_diff_hunks(
18340 &mut self,
18341 _: &ExpandAllDiffHunks,
18342 _window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) {
18345 self.buffer.update(cx, |buffer, cx| {
18346 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18347 });
18348 }
18349
18350 pub fn toggle_selected_diff_hunks(
18351 &mut self,
18352 _: &ToggleSelectedDiffHunks,
18353 _window: &mut Window,
18354 cx: &mut Context<Self>,
18355 ) {
18356 let ranges: Vec<_> = self
18357 .selections
18358 .disjoint_anchors()
18359 .iter()
18360 .map(|s| s.range())
18361 .collect();
18362 self.toggle_diff_hunks_in_ranges(ranges, cx);
18363 }
18364
18365 pub fn diff_hunks_in_ranges<'a>(
18366 &'a self,
18367 ranges: &'a [Range<Anchor>],
18368 buffer: &'a MultiBufferSnapshot,
18369 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18370 ranges.iter().flat_map(move |range| {
18371 let end_excerpt_id = range.end.excerpt_id;
18372 let range = range.to_point(buffer);
18373 let mut peek_end = range.end;
18374 if range.end.row < buffer.max_row().0 {
18375 peek_end = Point::new(range.end.row + 1, 0);
18376 }
18377 buffer
18378 .diff_hunks_in_range(range.start..peek_end)
18379 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18380 })
18381 }
18382
18383 pub fn has_stageable_diff_hunks_in_ranges(
18384 &self,
18385 ranges: &[Range<Anchor>],
18386 snapshot: &MultiBufferSnapshot,
18387 ) -> bool {
18388 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18389 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18390 }
18391
18392 pub fn toggle_staged_selected_diff_hunks(
18393 &mut self,
18394 _: &::git::ToggleStaged,
18395 _: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) {
18398 let snapshot = self.buffer.read(cx).snapshot(cx);
18399 let ranges: Vec<_> = self
18400 .selections
18401 .disjoint_anchors()
18402 .iter()
18403 .map(|s| s.range())
18404 .collect();
18405 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18406 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18407 }
18408
18409 pub fn set_render_diff_hunk_controls(
18410 &mut self,
18411 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18412 cx: &mut Context<Self>,
18413 ) {
18414 self.render_diff_hunk_controls = render_diff_hunk_controls;
18415 cx.notify();
18416 }
18417
18418 pub fn stage_and_next(
18419 &mut self,
18420 _: &::git::StageAndNext,
18421 window: &mut Window,
18422 cx: &mut Context<Self>,
18423 ) {
18424 self.do_stage_or_unstage_and_next(true, window, cx);
18425 }
18426
18427 pub fn unstage_and_next(
18428 &mut self,
18429 _: &::git::UnstageAndNext,
18430 window: &mut Window,
18431 cx: &mut Context<Self>,
18432 ) {
18433 self.do_stage_or_unstage_and_next(false, window, cx);
18434 }
18435
18436 pub fn stage_or_unstage_diff_hunks(
18437 &mut self,
18438 stage: bool,
18439 ranges: Vec<Range<Anchor>>,
18440 cx: &mut Context<Self>,
18441 ) {
18442 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18443 cx.spawn(async move |this, cx| {
18444 task.await?;
18445 this.update(cx, |this, cx| {
18446 let snapshot = this.buffer.read(cx).snapshot(cx);
18447 let chunk_by = this
18448 .diff_hunks_in_ranges(&ranges, &snapshot)
18449 .chunk_by(|hunk| hunk.buffer_id);
18450 for (buffer_id, hunks) in &chunk_by {
18451 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18452 }
18453 })
18454 })
18455 .detach_and_log_err(cx);
18456 }
18457
18458 fn save_buffers_for_ranges_if_needed(
18459 &mut self,
18460 ranges: &[Range<Anchor>],
18461 cx: &mut Context<Editor>,
18462 ) -> Task<Result<()>> {
18463 let multibuffer = self.buffer.read(cx);
18464 let snapshot = multibuffer.read(cx);
18465 let buffer_ids: HashSet<_> = ranges
18466 .iter()
18467 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18468 .collect();
18469 drop(snapshot);
18470
18471 let mut buffers = HashSet::default();
18472 for buffer_id in buffer_ids {
18473 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18474 let buffer = buffer_entity.read(cx);
18475 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18476 {
18477 buffers.insert(buffer_entity);
18478 }
18479 }
18480 }
18481
18482 if let Some(project) = &self.project {
18483 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18484 } else {
18485 Task::ready(Ok(()))
18486 }
18487 }
18488
18489 fn do_stage_or_unstage_and_next(
18490 &mut self,
18491 stage: bool,
18492 window: &mut Window,
18493 cx: &mut Context<Self>,
18494 ) {
18495 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18496
18497 if ranges.iter().any(|range| range.start != range.end) {
18498 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18499 return;
18500 }
18501
18502 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18503 let snapshot = self.snapshot(window, cx);
18504 let position = self.selections.newest::<Point>(cx).head();
18505 let mut row = snapshot
18506 .buffer_snapshot
18507 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18508 .find(|hunk| hunk.row_range.start.0 > position.row)
18509 .map(|hunk| hunk.row_range.start);
18510
18511 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18512 // Outside of the project diff editor, wrap around to the beginning.
18513 if !all_diff_hunks_expanded {
18514 row = row.or_else(|| {
18515 snapshot
18516 .buffer_snapshot
18517 .diff_hunks_in_range(Point::zero()..position)
18518 .find(|hunk| hunk.row_range.end.0 < position.row)
18519 .map(|hunk| hunk.row_range.start)
18520 });
18521 }
18522
18523 if let Some(row) = row {
18524 let destination = Point::new(row.0, 0);
18525 let autoscroll = Autoscroll::center();
18526
18527 self.unfold_ranges(&[destination..destination], false, false, cx);
18528 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18529 s.select_ranges([destination..destination]);
18530 });
18531 }
18532 }
18533
18534 fn do_stage_or_unstage(
18535 &self,
18536 stage: bool,
18537 buffer_id: BufferId,
18538 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18539 cx: &mut App,
18540 ) -> Option<()> {
18541 let project = self.project()?;
18542 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18543 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18544 let buffer_snapshot = buffer.read(cx).snapshot();
18545 let file_exists = buffer_snapshot
18546 .file()
18547 .is_some_and(|file| file.disk_state().exists());
18548 diff.update(cx, |diff, cx| {
18549 diff.stage_or_unstage_hunks(
18550 stage,
18551 &hunks
18552 .map(|hunk| buffer_diff::DiffHunk {
18553 buffer_range: hunk.buffer_range,
18554 diff_base_byte_range: hunk.diff_base_byte_range,
18555 secondary_status: hunk.secondary_status,
18556 range: Point::zero()..Point::zero(), // unused
18557 })
18558 .collect::<Vec<_>>(),
18559 &buffer_snapshot,
18560 file_exists,
18561 cx,
18562 )
18563 });
18564 None
18565 }
18566
18567 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18568 let ranges: Vec<_> = self
18569 .selections
18570 .disjoint_anchors()
18571 .iter()
18572 .map(|s| s.range())
18573 .collect();
18574 self.buffer
18575 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18576 }
18577
18578 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18579 self.buffer.update(cx, |buffer, cx| {
18580 let ranges = vec![Anchor::min()..Anchor::max()];
18581 if !buffer.all_diff_hunks_expanded()
18582 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18583 {
18584 buffer.collapse_diff_hunks(ranges, cx);
18585 true
18586 } else {
18587 false
18588 }
18589 })
18590 }
18591
18592 fn toggle_diff_hunks_in_ranges(
18593 &mut self,
18594 ranges: Vec<Range<Anchor>>,
18595 cx: &mut Context<Editor>,
18596 ) {
18597 self.buffer.update(cx, |buffer, cx| {
18598 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18599 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18600 })
18601 }
18602
18603 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18604 self.buffer.update(cx, |buffer, cx| {
18605 let snapshot = buffer.snapshot(cx);
18606 let excerpt_id = range.end.excerpt_id;
18607 let point_range = range.to_point(&snapshot);
18608 let expand = !buffer.single_hunk_is_expanded(range, cx);
18609 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18610 })
18611 }
18612
18613 pub(crate) fn apply_all_diff_hunks(
18614 &mut self,
18615 _: &ApplyAllDiffHunks,
18616 window: &mut Window,
18617 cx: &mut Context<Self>,
18618 ) {
18619 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18620
18621 let buffers = self.buffer.read(cx).all_buffers();
18622 for branch_buffer in buffers {
18623 branch_buffer.update(cx, |branch_buffer, cx| {
18624 branch_buffer.merge_into_base(Vec::new(), cx);
18625 });
18626 }
18627
18628 if let Some(project) = self.project.clone() {
18629 self.save(
18630 SaveOptions {
18631 format: true,
18632 autosave: false,
18633 },
18634 project,
18635 window,
18636 cx,
18637 )
18638 .detach_and_log_err(cx);
18639 }
18640 }
18641
18642 pub(crate) fn apply_selected_diff_hunks(
18643 &mut self,
18644 _: &ApplyDiffHunk,
18645 window: &mut Window,
18646 cx: &mut Context<Self>,
18647 ) {
18648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18649 let snapshot = self.snapshot(window, cx);
18650 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18651 let mut ranges_by_buffer = HashMap::default();
18652 self.transact(window, cx, |editor, _window, cx| {
18653 for hunk in hunks {
18654 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18655 ranges_by_buffer
18656 .entry(buffer.clone())
18657 .or_insert_with(Vec::new)
18658 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18659 }
18660 }
18661
18662 for (buffer, ranges) in ranges_by_buffer {
18663 buffer.update(cx, |buffer, cx| {
18664 buffer.merge_into_base(ranges, cx);
18665 });
18666 }
18667 });
18668
18669 if let Some(project) = self.project.clone() {
18670 self.save(
18671 SaveOptions {
18672 format: true,
18673 autosave: false,
18674 },
18675 project,
18676 window,
18677 cx,
18678 )
18679 .detach_and_log_err(cx);
18680 }
18681 }
18682
18683 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18684 if hovered != self.gutter_hovered {
18685 self.gutter_hovered = hovered;
18686 cx.notify();
18687 }
18688 }
18689
18690 pub fn insert_blocks(
18691 &mut self,
18692 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18693 autoscroll: Option<Autoscroll>,
18694 cx: &mut Context<Self>,
18695 ) -> Vec<CustomBlockId> {
18696 let blocks = self
18697 .display_map
18698 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18699 if let Some(autoscroll) = autoscroll {
18700 self.request_autoscroll(autoscroll, cx);
18701 }
18702 cx.notify();
18703 blocks
18704 }
18705
18706 pub fn resize_blocks(
18707 &mut self,
18708 heights: HashMap<CustomBlockId, u32>,
18709 autoscroll: Option<Autoscroll>,
18710 cx: &mut Context<Self>,
18711 ) {
18712 self.display_map
18713 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18714 if let Some(autoscroll) = autoscroll {
18715 self.request_autoscroll(autoscroll, cx);
18716 }
18717 cx.notify();
18718 }
18719
18720 pub fn replace_blocks(
18721 &mut self,
18722 renderers: HashMap<CustomBlockId, RenderBlock>,
18723 autoscroll: Option<Autoscroll>,
18724 cx: &mut Context<Self>,
18725 ) {
18726 self.display_map
18727 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18728 if let Some(autoscroll) = autoscroll {
18729 self.request_autoscroll(autoscroll, cx);
18730 }
18731 cx.notify();
18732 }
18733
18734 pub fn remove_blocks(
18735 &mut self,
18736 block_ids: HashSet<CustomBlockId>,
18737 autoscroll: Option<Autoscroll>,
18738 cx: &mut Context<Self>,
18739 ) {
18740 self.display_map.update(cx, |display_map, cx| {
18741 display_map.remove_blocks(block_ids, cx)
18742 });
18743 if let Some(autoscroll) = autoscroll {
18744 self.request_autoscroll(autoscroll, cx);
18745 }
18746 cx.notify();
18747 }
18748
18749 pub fn row_for_block(
18750 &self,
18751 block_id: CustomBlockId,
18752 cx: &mut Context<Self>,
18753 ) -> Option<DisplayRow> {
18754 self.display_map
18755 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18756 }
18757
18758 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18759 self.focused_block = Some(focused_block);
18760 }
18761
18762 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18763 self.focused_block.take()
18764 }
18765
18766 pub fn insert_creases(
18767 &mut self,
18768 creases: impl IntoIterator<Item = Crease<Anchor>>,
18769 cx: &mut Context<Self>,
18770 ) -> Vec<CreaseId> {
18771 self.display_map
18772 .update(cx, |map, cx| map.insert_creases(creases, cx))
18773 }
18774
18775 pub fn remove_creases(
18776 &mut self,
18777 ids: impl IntoIterator<Item = CreaseId>,
18778 cx: &mut Context<Self>,
18779 ) -> Vec<(CreaseId, Range<Anchor>)> {
18780 self.display_map
18781 .update(cx, |map, cx| map.remove_creases(ids, cx))
18782 }
18783
18784 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18785 self.display_map
18786 .update(cx, |map, cx| map.snapshot(cx))
18787 .longest_row()
18788 }
18789
18790 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18791 self.display_map
18792 .update(cx, |map, cx| map.snapshot(cx))
18793 .max_point()
18794 }
18795
18796 pub fn text(&self, cx: &App) -> String {
18797 self.buffer.read(cx).read(cx).text()
18798 }
18799
18800 pub fn is_empty(&self, cx: &App) -> bool {
18801 self.buffer.read(cx).read(cx).is_empty()
18802 }
18803
18804 pub fn text_option(&self, cx: &App) -> Option<String> {
18805 let text = self.text(cx);
18806 let text = text.trim();
18807
18808 if text.is_empty() {
18809 return None;
18810 }
18811
18812 Some(text.to_string())
18813 }
18814
18815 pub fn set_text(
18816 &mut self,
18817 text: impl Into<Arc<str>>,
18818 window: &mut Window,
18819 cx: &mut Context<Self>,
18820 ) {
18821 self.transact(window, cx, |this, _, cx| {
18822 this.buffer
18823 .read(cx)
18824 .as_singleton()
18825 .expect("you can only call set_text on editors for singleton buffers")
18826 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18827 });
18828 }
18829
18830 pub fn display_text(&self, cx: &mut App) -> String {
18831 self.display_map
18832 .update(cx, |map, cx| map.snapshot(cx))
18833 .text()
18834 }
18835
18836 fn create_minimap(
18837 &self,
18838 minimap_settings: MinimapSettings,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) -> Option<Entity<Self>> {
18842 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18843 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18844 }
18845
18846 fn initialize_new_minimap(
18847 &self,
18848 minimap_settings: MinimapSettings,
18849 window: &mut Window,
18850 cx: &mut Context<Self>,
18851 ) -> Entity<Self> {
18852 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18853
18854 let mut minimap = Editor::new_internal(
18855 EditorMode::Minimap {
18856 parent: cx.weak_entity(),
18857 },
18858 self.buffer.clone(),
18859 None,
18860 Some(self.display_map.clone()),
18861 window,
18862 cx,
18863 );
18864 minimap.scroll_manager.clone_state(&self.scroll_manager);
18865 minimap.set_text_style_refinement(TextStyleRefinement {
18866 font_size: Some(MINIMAP_FONT_SIZE),
18867 font_weight: Some(MINIMAP_FONT_WEIGHT),
18868 ..Default::default()
18869 });
18870 minimap.update_minimap_configuration(minimap_settings, cx);
18871 cx.new(|_| minimap)
18872 }
18873
18874 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18875 let current_line_highlight = minimap_settings
18876 .current_line_highlight
18877 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18878 self.set_current_line_highlight(Some(current_line_highlight));
18879 }
18880
18881 pub fn minimap(&self) -> Option<&Entity<Self>> {
18882 self.minimap
18883 .as_ref()
18884 .filter(|_| self.minimap_visibility.visible())
18885 }
18886
18887 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18888 let mut wrap_guides = smallvec![];
18889
18890 if self.show_wrap_guides == Some(false) {
18891 return wrap_guides;
18892 }
18893
18894 let settings = self.buffer.read(cx).language_settings(cx);
18895 if settings.show_wrap_guides {
18896 match self.soft_wrap_mode(cx) {
18897 SoftWrap::Column(soft_wrap) => {
18898 wrap_guides.push((soft_wrap as usize, true));
18899 }
18900 SoftWrap::Bounded(soft_wrap) => {
18901 wrap_guides.push((soft_wrap as usize, true));
18902 }
18903 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18904 }
18905 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18906 }
18907
18908 wrap_guides
18909 }
18910
18911 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18912 let settings = self.buffer.read(cx).language_settings(cx);
18913 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18914 match mode {
18915 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18916 SoftWrap::None
18917 }
18918 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18919 language_settings::SoftWrap::PreferredLineLength => {
18920 SoftWrap::Column(settings.preferred_line_length)
18921 }
18922 language_settings::SoftWrap::Bounded => {
18923 SoftWrap::Bounded(settings.preferred_line_length)
18924 }
18925 }
18926 }
18927
18928 pub fn set_soft_wrap_mode(
18929 &mut self,
18930 mode: language_settings::SoftWrap,
18931
18932 cx: &mut Context<Self>,
18933 ) {
18934 self.soft_wrap_mode_override = Some(mode);
18935 cx.notify();
18936 }
18937
18938 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18939 self.hard_wrap = hard_wrap;
18940 cx.notify();
18941 }
18942
18943 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18944 self.text_style_refinement = Some(style);
18945 }
18946
18947 /// called by the Element so we know what style we were most recently rendered with.
18948 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
18949 // We intentionally do not inform the display map about the minimap style
18950 // so that wrapping is not recalculated and stays consistent for the editor
18951 // and its linked minimap.
18952 if !self.mode.is_minimap() {
18953 let font = style.text.font();
18954 let font_size = style.text.font_size.to_pixels(window.rem_size());
18955 let display_map = self
18956 .placeholder_display_map
18957 .as_ref()
18958 .filter(|_| self.is_empty(cx))
18959 .unwrap_or(&self.display_map);
18960
18961 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
18962 }
18963 self.style = Some(style);
18964 }
18965
18966 pub fn style(&self) -> Option<&EditorStyle> {
18967 self.style.as_ref()
18968 }
18969
18970 // Called by the element. This method is not designed to be called outside of the editor
18971 // element's layout code because it does not notify when rewrapping is computed synchronously.
18972 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18973 if self.is_empty(cx) {
18974 self.placeholder_display_map
18975 .as_ref()
18976 .map_or(false, |display_map| {
18977 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
18978 })
18979 } else {
18980 self.display_map
18981 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18982 }
18983 }
18984
18985 pub fn set_soft_wrap(&mut self) {
18986 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18987 }
18988
18989 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18990 if self.soft_wrap_mode_override.is_some() {
18991 self.soft_wrap_mode_override.take();
18992 } else {
18993 let soft_wrap = match self.soft_wrap_mode(cx) {
18994 SoftWrap::GitDiff => return,
18995 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18996 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18997 language_settings::SoftWrap::None
18998 }
18999 };
19000 self.soft_wrap_mode_override = Some(soft_wrap);
19001 }
19002 cx.notify();
19003 }
19004
19005 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19006 let Some(workspace) = self.workspace() else {
19007 return;
19008 };
19009 let fs = workspace.read(cx).app_state().fs.clone();
19010 let current_show = TabBarSettings::get_global(cx).show;
19011 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
19012 setting.show = Some(!current_show);
19013 });
19014 }
19015
19016 pub fn toggle_indent_guides(
19017 &mut self,
19018 _: &ToggleIndentGuides,
19019 _: &mut Window,
19020 cx: &mut Context<Self>,
19021 ) {
19022 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19023 self.buffer
19024 .read(cx)
19025 .language_settings(cx)
19026 .indent_guides
19027 .enabled
19028 });
19029 self.show_indent_guides = Some(!currently_enabled);
19030 cx.notify();
19031 }
19032
19033 fn should_show_indent_guides(&self) -> Option<bool> {
19034 self.show_indent_guides
19035 }
19036
19037 pub fn toggle_line_numbers(
19038 &mut self,
19039 _: &ToggleLineNumbers,
19040 _: &mut Window,
19041 cx: &mut Context<Self>,
19042 ) {
19043 let mut editor_settings = EditorSettings::get_global(cx).clone();
19044 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19045 EditorSettings::override_global(editor_settings, cx);
19046 }
19047
19048 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19049 if let Some(show_line_numbers) = self.show_line_numbers {
19050 return show_line_numbers;
19051 }
19052 EditorSettings::get_global(cx).gutter.line_numbers
19053 }
19054
19055 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19056 self.use_relative_line_numbers
19057 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19058 }
19059
19060 pub fn toggle_relative_line_numbers(
19061 &mut self,
19062 _: &ToggleRelativeLineNumbers,
19063 _: &mut Window,
19064 cx: &mut Context<Self>,
19065 ) {
19066 let is_relative = self.should_use_relative_line_numbers(cx);
19067 self.set_relative_line_number(Some(!is_relative), cx)
19068 }
19069
19070 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19071 self.use_relative_line_numbers = is_relative;
19072 cx.notify();
19073 }
19074
19075 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19076 self.show_gutter = show_gutter;
19077 cx.notify();
19078 }
19079
19080 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19081 self.show_scrollbars = ScrollbarAxes {
19082 horizontal: show,
19083 vertical: show,
19084 };
19085 cx.notify();
19086 }
19087
19088 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19089 self.show_scrollbars.vertical = show;
19090 cx.notify();
19091 }
19092
19093 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19094 self.show_scrollbars.horizontal = show;
19095 cx.notify();
19096 }
19097
19098 pub fn set_minimap_visibility(
19099 &mut self,
19100 minimap_visibility: MinimapVisibility,
19101 window: &mut Window,
19102 cx: &mut Context<Self>,
19103 ) {
19104 if self.minimap_visibility != minimap_visibility {
19105 if minimap_visibility.visible() && self.minimap.is_none() {
19106 let minimap_settings = EditorSettings::get_global(cx).minimap;
19107 self.minimap =
19108 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19109 }
19110 self.minimap_visibility = minimap_visibility;
19111 cx.notify();
19112 }
19113 }
19114
19115 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19116 self.set_show_scrollbars(false, cx);
19117 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19118 }
19119
19120 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19121 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19122 }
19123
19124 /// Normally the text in full mode and auto height editors is padded on the
19125 /// left side by roughly half a character width for improved hit testing.
19126 ///
19127 /// Use this method to disable this for cases where this is not wanted (e.g.
19128 /// if you want to align the editor text with some other text above or below)
19129 /// or if you want to add this padding to single-line editors.
19130 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19131 self.offset_content = offset_content;
19132 cx.notify();
19133 }
19134
19135 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19136 self.show_line_numbers = Some(show_line_numbers);
19137 cx.notify();
19138 }
19139
19140 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19141 self.disable_expand_excerpt_buttons = true;
19142 cx.notify();
19143 }
19144
19145 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19146 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19147 cx.notify();
19148 }
19149
19150 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19151 self.show_code_actions = Some(show_code_actions);
19152 cx.notify();
19153 }
19154
19155 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19156 self.show_runnables = Some(show_runnables);
19157 cx.notify();
19158 }
19159
19160 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19161 self.show_breakpoints = Some(show_breakpoints);
19162 cx.notify();
19163 }
19164
19165 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19166 if self.display_map.read(cx).masked != masked {
19167 self.display_map.update(cx, |map, _| map.masked = masked);
19168 }
19169 cx.notify()
19170 }
19171
19172 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19173 self.show_wrap_guides = Some(show_wrap_guides);
19174 cx.notify();
19175 }
19176
19177 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19178 self.show_indent_guides = Some(show_indent_guides);
19179 cx.notify();
19180 }
19181
19182 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19183 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19184 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19185 && let Some(dir) = file.abs_path(cx).parent()
19186 {
19187 return Some(dir.to_owned());
19188 }
19189
19190 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19191 return Some(project_path.path.to_path_buf());
19192 }
19193 }
19194
19195 None
19196 }
19197
19198 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19199 self.active_excerpt(cx)?
19200 .1
19201 .read(cx)
19202 .file()
19203 .and_then(|f| f.as_local())
19204 }
19205
19206 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19207 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19208 let buffer = buffer.read(cx);
19209 if let Some(project_path) = buffer.project_path(cx) {
19210 let project = self.project()?.read(cx);
19211 project.absolute_path(&project_path, cx)
19212 } else {
19213 buffer
19214 .file()
19215 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19216 }
19217 })
19218 }
19219
19220 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19221 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19222 let project_path = buffer.read(cx).project_path(cx)?;
19223 let project = self.project()?.read(cx);
19224 let entry = project.entry_for_path(&project_path, cx)?;
19225 let path = entry.path.to_path_buf();
19226 Some(path)
19227 })
19228 }
19229
19230 pub fn reveal_in_finder(
19231 &mut self,
19232 _: &RevealInFileManager,
19233 _window: &mut Window,
19234 cx: &mut Context<Self>,
19235 ) {
19236 if let Some(target) = self.target_file(cx) {
19237 cx.reveal_path(&target.abs_path(cx));
19238 }
19239 }
19240
19241 pub fn copy_path(
19242 &mut self,
19243 _: &zed_actions::workspace::CopyPath,
19244 _window: &mut Window,
19245 cx: &mut Context<Self>,
19246 ) {
19247 if let Some(path) = self.target_file_abs_path(cx)
19248 && let Some(path) = path.to_str()
19249 {
19250 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19251 }
19252 }
19253
19254 pub fn copy_relative_path(
19255 &mut self,
19256 _: &zed_actions::workspace::CopyRelativePath,
19257 _window: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) {
19260 if let Some(path) = self.target_file_path(cx)
19261 && let Some(path) = path.to_str()
19262 {
19263 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19264 }
19265 }
19266
19267 /// Returns the project path for the editor's buffer, if any buffer is
19268 /// opened in the editor.
19269 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19270 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19271 buffer.read(cx).project_path(cx)
19272 } else {
19273 None
19274 }
19275 }
19276
19277 // Returns true if the editor handled a go-to-line request
19278 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19279 maybe!({
19280 let breakpoint_store = self.breakpoint_store.as_ref()?;
19281
19282 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19283 else {
19284 self.clear_row_highlights::<ActiveDebugLine>();
19285 return None;
19286 };
19287
19288 let position = active_stack_frame.position;
19289 let buffer_id = position.buffer_id?;
19290 let snapshot = self
19291 .project
19292 .as_ref()?
19293 .read(cx)
19294 .buffer_for_id(buffer_id, cx)?
19295 .read(cx)
19296 .snapshot();
19297
19298 let mut handled = false;
19299 for (id, ExcerptRange { context, .. }) in
19300 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19301 {
19302 if context.start.cmp(&position, &snapshot).is_ge()
19303 || context.end.cmp(&position, &snapshot).is_lt()
19304 {
19305 continue;
19306 }
19307 let snapshot = self.buffer.read(cx).snapshot(cx);
19308 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19309
19310 handled = true;
19311 self.clear_row_highlights::<ActiveDebugLine>();
19312
19313 self.go_to_line::<ActiveDebugLine>(
19314 multibuffer_anchor,
19315 Some(cx.theme().colors().editor_debugger_active_line_background),
19316 window,
19317 cx,
19318 );
19319
19320 cx.notify();
19321 }
19322
19323 handled.then_some(())
19324 })
19325 .is_some()
19326 }
19327
19328 pub fn copy_file_name_without_extension(
19329 &mut self,
19330 _: &CopyFileNameWithoutExtension,
19331 _: &mut Window,
19332 cx: &mut Context<Self>,
19333 ) {
19334 if let Some(file) = self.target_file(cx)
19335 && let Some(file_stem) = file.path().file_stem()
19336 && let Some(name) = file_stem.to_str()
19337 {
19338 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19339 }
19340 }
19341
19342 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19343 if let Some(file) = self.target_file(cx)
19344 && let Some(file_name) = file.path().file_name()
19345 && let Some(name) = file_name.to_str()
19346 {
19347 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19348 }
19349 }
19350
19351 pub fn toggle_git_blame(
19352 &mut self,
19353 _: &::git::Blame,
19354 window: &mut Window,
19355 cx: &mut Context<Self>,
19356 ) {
19357 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19358
19359 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19360 self.start_git_blame(true, window, cx);
19361 }
19362
19363 cx.notify();
19364 }
19365
19366 pub fn toggle_git_blame_inline(
19367 &mut self,
19368 _: &ToggleGitBlameInline,
19369 window: &mut Window,
19370 cx: &mut Context<Self>,
19371 ) {
19372 self.toggle_git_blame_inline_internal(true, window, cx);
19373 cx.notify();
19374 }
19375
19376 pub fn open_git_blame_commit(
19377 &mut self,
19378 _: &OpenGitBlameCommit,
19379 window: &mut Window,
19380 cx: &mut Context<Self>,
19381 ) {
19382 self.open_git_blame_commit_internal(window, cx);
19383 }
19384
19385 fn open_git_blame_commit_internal(
19386 &mut self,
19387 window: &mut Window,
19388 cx: &mut Context<Self>,
19389 ) -> Option<()> {
19390 let blame = self.blame.as_ref()?;
19391 let snapshot = self.snapshot(window, cx);
19392 let cursor = self.selections.newest::<Point>(cx).head();
19393 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19394 let (_, blame_entry) = blame
19395 .update(cx, |blame, cx| {
19396 blame
19397 .blame_for_rows(
19398 &[RowInfo {
19399 buffer_id: Some(buffer.remote_id()),
19400 buffer_row: Some(point.row),
19401 ..Default::default()
19402 }],
19403 cx,
19404 )
19405 .next()
19406 })
19407 .flatten()?;
19408 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19409 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19410 let workspace = self.workspace()?.downgrade();
19411 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19412 None
19413 }
19414
19415 pub fn git_blame_inline_enabled(&self) -> bool {
19416 self.git_blame_inline_enabled
19417 }
19418
19419 pub fn toggle_selection_menu(
19420 &mut self,
19421 _: &ToggleSelectionMenu,
19422 _: &mut Window,
19423 cx: &mut Context<Self>,
19424 ) {
19425 self.show_selection_menu = self
19426 .show_selection_menu
19427 .map(|show_selections_menu| !show_selections_menu)
19428 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19429
19430 cx.notify();
19431 }
19432
19433 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19434 self.show_selection_menu
19435 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19436 }
19437
19438 fn start_git_blame(
19439 &mut self,
19440 user_triggered: bool,
19441 window: &mut Window,
19442 cx: &mut Context<Self>,
19443 ) {
19444 if let Some(project) = self.project() {
19445 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19446 && buffer.read(cx).file().is_none()
19447 {
19448 return;
19449 }
19450
19451 let focused = self.focus_handle(cx).contains_focused(window, cx);
19452
19453 let project = project.clone();
19454 let blame = cx
19455 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19456 self.blame_subscription =
19457 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19458 self.blame = Some(blame);
19459 }
19460 }
19461
19462 fn toggle_git_blame_inline_internal(
19463 &mut self,
19464 user_triggered: bool,
19465 window: &mut Window,
19466 cx: &mut Context<Self>,
19467 ) {
19468 if self.git_blame_inline_enabled {
19469 self.git_blame_inline_enabled = false;
19470 self.show_git_blame_inline = false;
19471 self.show_git_blame_inline_delay_task.take();
19472 } else {
19473 self.git_blame_inline_enabled = true;
19474 self.start_git_blame_inline(user_triggered, window, cx);
19475 }
19476
19477 cx.notify();
19478 }
19479
19480 fn start_git_blame_inline(
19481 &mut self,
19482 user_triggered: bool,
19483 window: &mut Window,
19484 cx: &mut Context<Self>,
19485 ) {
19486 self.start_git_blame(user_triggered, window, cx);
19487
19488 if ProjectSettings::get_global(cx)
19489 .git
19490 .inline_blame_delay()
19491 .is_some()
19492 {
19493 self.start_inline_blame_timer(window, cx);
19494 } else {
19495 self.show_git_blame_inline = true
19496 }
19497 }
19498
19499 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19500 self.blame.as_ref()
19501 }
19502
19503 pub fn show_git_blame_gutter(&self) -> bool {
19504 self.show_git_blame_gutter
19505 }
19506
19507 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19508 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19509 }
19510
19511 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19512 self.show_git_blame_inline
19513 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19514 && !self.newest_selection_head_on_empty_line(cx)
19515 && self.has_blame_entries(cx)
19516 }
19517
19518 fn has_blame_entries(&self, cx: &App) -> bool {
19519 self.blame()
19520 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19521 }
19522
19523 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19524 let cursor_anchor = self.selections.newest_anchor().head();
19525
19526 let snapshot = self.buffer.read(cx).snapshot(cx);
19527 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19528
19529 snapshot.line_len(buffer_row) == 0
19530 }
19531
19532 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19533 let buffer_and_selection = maybe!({
19534 let selection = self.selections.newest::<Point>(cx);
19535 let selection_range = selection.range();
19536
19537 let multi_buffer = self.buffer().read(cx);
19538 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19539 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19540
19541 let (buffer, range, _) = if selection.reversed {
19542 buffer_ranges.first()
19543 } else {
19544 buffer_ranges.last()
19545 }?;
19546
19547 let selection = text::ToPoint::to_point(&range.start, buffer).row
19548 ..text::ToPoint::to_point(&range.end, buffer).row;
19549 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19550 });
19551
19552 let Some((buffer, selection)) = buffer_and_selection else {
19553 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19554 };
19555
19556 let Some(project) = self.project() else {
19557 return Task::ready(Err(anyhow!("editor does not have project")));
19558 };
19559
19560 project.update(cx, |project, cx| {
19561 project.get_permalink_to_line(&buffer, selection, cx)
19562 })
19563 }
19564
19565 pub fn copy_permalink_to_line(
19566 &mut self,
19567 _: &CopyPermalinkToLine,
19568 window: &mut Window,
19569 cx: &mut Context<Self>,
19570 ) {
19571 let permalink_task = self.get_permalink_to_line(cx);
19572 let workspace = self.workspace();
19573
19574 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19575 Ok(permalink) => {
19576 cx.update(|_, cx| {
19577 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19578 })
19579 .ok();
19580 }
19581 Err(err) => {
19582 let message = format!("Failed to copy permalink: {err}");
19583
19584 anyhow::Result::<()>::Err(err).log_err();
19585
19586 if let Some(workspace) = workspace {
19587 workspace
19588 .update_in(cx, |workspace, _, cx| {
19589 struct CopyPermalinkToLine;
19590
19591 workspace.show_toast(
19592 Toast::new(
19593 NotificationId::unique::<CopyPermalinkToLine>(),
19594 message,
19595 ),
19596 cx,
19597 )
19598 })
19599 .ok();
19600 }
19601 }
19602 })
19603 .detach();
19604 }
19605
19606 pub fn copy_file_location(
19607 &mut self,
19608 _: &CopyFileLocation,
19609 _: &mut Window,
19610 cx: &mut Context<Self>,
19611 ) {
19612 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19613 if let Some(file) = self.target_file(cx)
19614 && let Some(path) = file.path().to_str()
19615 {
19616 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19617 }
19618 }
19619
19620 pub fn open_permalink_to_line(
19621 &mut self,
19622 _: &OpenPermalinkToLine,
19623 window: &mut Window,
19624 cx: &mut Context<Self>,
19625 ) {
19626 let permalink_task = self.get_permalink_to_line(cx);
19627 let workspace = self.workspace();
19628
19629 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19630 Ok(permalink) => {
19631 cx.update(|_, cx| {
19632 cx.open_url(permalink.as_ref());
19633 })
19634 .ok();
19635 }
19636 Err(err) => {
19637 let message = format!("Failed to open permalink: {err}");
19638
19639 anyhow::Result::<()>::Err(err).log_err();
19640
19641 if let Some(workspace) = workspace {
19642 workspace
19643 .update(cx, |workspace, cx| {
19644 struct OpenPermalinkToLine;
19645
19646 workspace.show_toast(
19647 Toast::new(
19648 NotificationId::unique::<OpenPermalinkToLine>(),
19649 message,
19650 ),
19651 cx,
19652 )
19653 })
19654 .ok();
19655 }
19656 }
19657 })
19658 .detach();
19659 }
19660
19661 pub fn insert_uuid_v4(
19662 &mut self,
19663 _: &InsertUuidV4,
19664 window: &mut Window,
19665 cx: &mut Context<Self>,
19666 ) {
19667 self.insert_uuid(UuidVersion::V4, window, cx);
19668 }
19669
19670 pub fn insert_uuid_v7(
19671 &mut self,
19672 _: &InsertUuidV7,
19673 window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 self.insert_uuid(UuidVersion::V7, window, cx);
19677 }
19678
19679 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19681 self.transact(window, cx, |this, window, cx| {
19682 let edits = this
19683 .selections
19684 .all::<Point>(cx)
19685 .into_iter()
19686 .map(|selection| {
19687 let uuid = match version {
19688 UuidVersion::V4 => uuid::Uuid::new_v4(),
19689 UuidVersion::V7 => uuid::Uuid::now_v7(),
19690 };
19691
19692 (selection.range(), uuid.to_string())
19693 });
19694 this.edit(edits, cx);
19695 this.refresh_edit_prediction(true, false, window, cx);
19696 });
19697 }
19698
19699 pub fn open_selections_in_multibuffer(
19700 &mut self,
19701 _: &OpenSelectionsInMultibuffer,
19702 window: &mut Window,
19703 cx: &mut Context<Self>,
19704 ) {
19705 let multibuffer = self.buffer.read(cx);
19706
19707 let Some(buffer) = multibuffer.as_singleton() else {
19708 return;
19709 };
19710
19711 let Some(workspace) = self.workspace() else {
19712 return;
19713 };
19714
19715 let title = multibuffer.title(cx).to_string();
19716
19717 let locations = self
19718 .selections
19719 .all_anchors(cx)
19720 .iter()
19721 .map(|selection| Location {
19722 buffer: buffer.clone(),
19723 range: selection.start.text_anchor..selection.end.text_anchor,
19724 })
19725 .collect::<Vec<_>>();
19726
19727 cx.spawn_in(window, async move |_, cx| {
19728 workspace.update_in(cx, |workspace, window, cx| {
19729 Self::open_locations_in_multibuffer(
19730 workspace,
19731 locations,
19732 format!("Selections for '{title}'"),
19733 false,
19734 MultibufferSelectionMode::All,
19735 window,
19736 cx,
19737 );
19738 })
19739 })
19740 .detach();
19741 }
19742
19743 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19744 /// last highlight added will be used.
19745 ///
19746 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19747 pub fn highlight_rows<T: 'static>(
19748 &mut self,
19749 range: Range<Anchor>,
19750 color: Hsla,
19751 options: RowHighlightOptions,
19752 cx: &mut Context<Self>,
19753 ) {
19754 let snapshot = self.buffer().read(cx).snapshot(cx);
19755 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19756 let ix = row_highlights.binary_search_by(|highlight| {
19757 Ordering::Equal
19758 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19759 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19760 });
19761
19762 if let Err(mut ix) = ix {
19763 let index = post_inc(&mut self.highlight_order);
19764
19765 // If this range intersects with the preceding highlight, then merge it with
19766 // the preceding highlight. Otherwise insert a new highlight.
19767 let mut merged = false;
19768 if ix > 0 {
19769 let prev_highlight = &mut row_highlights[ix - 1];
19770 if prev_highlight
19771 .range
19772 .end
19773 .cmp(&range.start, &snapshot)
19774 .is_ge()
19775 {
19776 ix -= 1;
19777 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19778 prev_highlight.range.end = range.end;
19779 }
19780 merged = true;
19781 prev_highlight.index = index;
19782 prev_highlight.color = color;
19783 prev_highlight.options = options;
19784 }
19785 }
19786
19787 if !merged {
19788 row_highlights.insert(
19789 ix,
19790 RowHighlight {
19791 range,
19792 index,
19793 color,
19794 options,
19795 type_id: TypeId::of::<T>(),
19796 },
19797 );
19798 }
19799
19800 // If any of the following highlights intersect with this one, merge them.
19801 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19802 let highlight = &row_highlights[ix];
19803 if next_highlight
19804 .range
19805 .start
19806 .cmp(&highlight.range.end, &snapshot)
19807 .is_le()
19808 {
19809 if next_highlight
19810 .range
19811 .end
19812 .cmp(&highlight.range.end, &snapshot)
19813 .is_gt()
19814 {
19815 row_highlights[ix].range.end = next_highlight.range.end;
19816 }
19817 row_highlights.remove(ix + 1);
19818 } else {
19819 break;
19820 }
19821 }
19822 }
19823 }
19824
19825 /// Remove any highlighted row ranges of the given type that intersect the
19826 /// given ranges.
19827 pub fn remove_highlighted_rows<T: 'static>(
19828 &mut self,
19829 ranges_to_remove: Vec<Range<Anchor>>,
19830 cx: &mut Context<Self>,
19831 ) {
19832 let snapshot = self.buffer().read(cx).snapshot(cx);
19833 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19834 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19835 row_highlights.retain(|highlight| {
19836 while let Some(range_to_remove) = ranges_to_remove.peek() {
19837 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19838 Ordering::Less | Ordering::Equal => {
19839 ranges_to_remove.next();
19840 }
19841 Ordering::Greater => {
19842 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19843 Ordering::Less | Ordering::Equal => {
19844 return false;
19845 }
19846 Ordering::Greater => break,
19847 }
19848 }
19849 }
19850 }
19851
19852 true
19853 })
19854 }
19855
19856 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19857 pub fn clear_row_highlights<T: 'static>(&mut self) {
19858 self.highlighted_rows.remove(&TypeId::of::<T>());
19859 }
19860
19861 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19862 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19863 self.highlighted_rows
19864 .get(&TypeId::of::<T>())
19865 .map_or(&[] as &[_], |vec| vec.as_slice())
19866 .iter()
19867 .map(|highlight| (highlight.range.clone(), highlight.color))
19868 }
19869
19870 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19871 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19872 /// Allows to ignore certain kinds of highlights.
19873 pub fn highlighted_display_rows(
19874 &self,
19875 window: &mut Window,
19876 cx: &mut App,
19877 ) -> BTreeMap<DisplayRow, LineHighlight> {
19878 let snapshot = self.snapshot(window, cx);
19879 let mut used_highlight_orders = HashMap::default();
19880 self.highlighted_rows
19881 .iter()
19882 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19883 .fold(
19884 BTreeMap::<DisplayRow, LineHighlight>::new(),
19885 |mut unique_rows, highlight| {
19886 let start = highlight.range.start.to_display_point(&snapshot);
19887 let end = highlight.range.end.to_display_point(&snapshot);
19888 let start_row = start.row().0;
19889 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19890 && end.column() == 0
19891 {
19892 end.row().0.saturating_sub(1)
19893 } else {
19894 end.row().0
19895 };
19896 for row in start_row..=end_row {
19897 let used_index =
19898 used_highlight_orders.entry(row).or_insert(highlight.index);
19899 if highlight.index >= *used_index {
19900 *used_index = highlight.index;
19901 unique_rows.insert(
19902 DisplayRow(row),
19903 LineHighlight {
19904 include_gutter: highlight.options.include_gutter,
19905 border: None,
19906 background: highlight.color.into(),
19907 type_id: Some(highlight.type_id),
19908 },
19909 );
19910 }
19911 }
19912 unique_rows
19913 },
19914 )
19915 }
19916
19917 pub fn highlighted_display_row_for_autoscroll(
19918 &self,
19919 snapshot: &DisplaySnapshot,
19920 ) -> Option<DisplayRow> {
19921 self.highlighted_rows
19922 .values()
19923 .flat_map(|highlighted_rows| highlighted_rows.iter())
19924 .filter_map(|highlight| {
19925 if highlight.options.autoscroll {
19926 Some(highlight.range.start.to_display_point(snapshot).row())
19927 } else {
19928 None
19929 }
19930 })
19931 .min()
19932 }
19933
19934 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19935 self.highlight_background::<SearchWithinRange>(
19936 ranges,
19937 |colors| colors.colors().editor_document_highlight_read_background,
19938 cx,
19939 )
19940 }
19941
19942 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19943 self.breadcrumb_header = Some(new_header);
19944 }
19945
19946 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19947 self.clear_background_highlights::<SearchWithinRange>(cx);
19948 }
19949
19950 pub fn highlight_background<T: 'static>(
19951 &mut self,
19952 ranges: &[Range<Anchor>],
19953 color_fetcher: fn(&Theme) -> Hsla,
19954 cx: &mut Context<Self>,
19955 ) {
19956 self.background_highlights.insert(
19957 HighlightKey::Type(TypeId::of::<T>()),
19958 (color_fetcher, Arc::from(ranges)),
19959 );
19960 self.scrollbar_marker_state.dirty = true;
19961 cx.notify();
19962 }
19963
19964 pub fn highlight_background_key<T: 'static>(
19965 &mut self,
19966 key: usize,
19967 ranges: &[Range<Anchor>],
19968 color_fetcher: fn(&Theme) -> Hsla,
19969 cx: &mut Context<Self>,
19970 ) {
19971 self.background_highlights.insert(
19972 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19973 (color_fetcher, Arc::from(ranges)),
19974 );
19975 self.scrollbar_marker_state.dirty = true;
19976 cx.notify();
19977 }
19978
19979 pub fn clear_background_highlights<T: 'static>(
19980 &mut self,
19981 cx: &mut Context<Self>,
19982 ) -> Option<BackgroundHighlight> {
19983 let text_highlights = self
19984 .background_highlights
19985 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19986 if !text_highlights.1.is_empty() {
19987 self.scrollbar_marker_state.dirty = true;
19988 cx.notify();
19989 }
19990 Some(text_highlights)
19991 }
19992
19993 pub fn highlight_gutter<T: 'static>(
19994 &mut self,
19995 ranges: impl Into<Vec<Range<Anchor>>>,
19996 color_fetcher: fn(&App) -> Hsla,
19997 cx: &mut Context<Self>,
19998 ) {
19999 self.gutter_highlights
20000 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20001 cx.notify();
20002 }
20003
20004 pub fn clear_gutter_highlights<T: 'static>(
20005 &mut self,
20006 cx: &mut Context<Self>,
20007 ) -> Option<GutterHighlight> {
20008 cx.notify();
20009 self.gutter_highlights.remove(&TypeId::of::<T>())
20010 }
20011
20012 pub fn insert_gutter_highlight<T: 'static>(
20013 &mut self,
20014 range: Range<Anchor>,
20015 color_fetcher: fn(&App) -> Hsla,
20016 cx: &mut Context<Self>,
20017 ) {
20018 let snapshot = self.buffer().read(cx).snapshot(cx);
20019 let mut highlights = self
20020 .gutter_highlights
20021 .remove(&TypeId::of::<T>())
20022 .map(|(_, highlights)| highlights)
20023 .unwrap_or_default();
20024 let ix = highlights.binary_search_by(|highlight| {
20025 Ordering::Equal
20026 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20027 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20028 });
20029 if let Err(ix) = ix {
20030 highlights.insert(ix, range);
20031 }
20032 self.gutter_highlights
20033 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20034 }
20035
20036 pub fn remove_gutter_highlights<T: 'static>(
20037 &mut self,
20038 ranges_to_remove: Vec<Range<Anchor>>,
20039 cx: &mut Context<Self>,
20040 ) {
20041 let snapshot = self.buffer().read(cx).snapshot(cx);
20042 let Some((color_fetcher, mut gutter_highlights)) =
20043 self.gutter_highlights.remove(&TypeId::of::<T>())
20044 else {
20045 return;
20046 };
20047 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20048 gutter_highlights.retain(|highlight| {
20049 while let Some(range_to_remove) = ranges_to_remove.peek() {
20050 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20051 Ordering::Less | Ordering::Equal => {
20052 ranges_to_remove.next();
20053 }
20054 Ordering::Greater => {
20055 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20056 Ordering::Less | Ordering::Equal => {
20057 return false;
20058 }
20059 Ordering::Greater => break,
20060 }
20061 }
20062 }
20063 }
20064
20065 true
20066 });
20067 self.gutter_highlights
20068 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20069 }
20070
20071 #[cfg(feature = "test-support")]
20072 pub fn all_text_highlights(
20073 &self,
20074 window: &mut Window,
20075 cx: &mut Context<Self>,
20076 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20077 let snapshot = self.snapshot(window, cx);
20078 self.display_map.update(cx, |display_map, _| {
20079 display_map
20080 .all_text_highlights()
20081 .map(|highlight| {
20082 let (style, ranges) = highlight.as_ref();
20083 (
20084 *style,
20085 ranges
20086 .iter()
20087 .map(|range| range.clone().to_display_points(&snapshot))
20088 .collect(),
20089 )
20090 })
20091 .collect()
20092 })
20093 }
20094
20095 #[cfg(feature = "test-support")]
20096 pub fn all_text_background_highlights(
20097 &self,
20098 window: &mut Window,
20099 cx: &mut Context<Self>,
20100 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20101 let snapshot = self.snapshot(window, cx);
20102 let buffer = &snapshot.buffer_snapshot;
20103 let start = buffer.anchor_before(0);
20104 let end = buffer.anchor_after(buffer.len());
20105 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20106 }
20107
20108 #[cfg(any(test, feature = "test-support"))]
20109 pub fn sorted_background_highlights_in_range(
20110 &self,
20111 search_range: Range<Anchor>,
20112 display_snapshot: &DisplaySnapshot,
20113 theme: &Theme,
20114 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20115 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20116 res.sort_by(|a, b| {
20117 a.0.start
20118 .cmp(&b.0.start)
20119 .then_with(|| a.0.end.cmp(&b.0.end))
20120 .then_with(|| a.1.cmp(&b.1))
20121 });
20122 res
20123 }
20124
20125 #[cfg(feature = "test-support")]
20126 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20127 let snapshot = self.buffer().read(cx).snapshot(cx);
20128
20129 let highlights = self
20130 .background_highlights
20131 .get(&HighlightKey::Type(TypeId::of::<
20132 items::BufferSearchHighlights,
20133 >()));
20134
20135 if let Some((_color, ranges)) = highlights {
20136 ranges
20137 .iter()
20138 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20139 .collect_vec()
20140 } else {
20141 vec![]
20142 }
20143 }
20144
20145 fn document_highlights_for_position<'a>(
20146 &'a self,
20147 position: Anchor,
20148 buffer: &'a MultiBufferSnapshot,
20149 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20150 let read_highlights = self
20151 .background_highlights
20152 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20153 .map(|h| &h.1);
20154 let write_highlights = self
20155 .background_highlights
20156 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20157 .map(|h| &h.1);
20158 let left_position = position.bias_left(buffer);
20159 let right_position = position.bias_right(buffer);
20160 read_highlights
20161 .into_iter()
20162 .chain(write_highlights)
20163 .flat_map(move |ranges| {
20164 let start_ix = match ranges.binary_search_by(|probe| {
20165 let cmp = probe.end.cmp(&left_position, buffer);
20166 if cmp.is_ge() {
20167 Ordering::Greater
20168 } else {
20169 Ordering::Less
20170 }
20171 }) {
20172 Ok(i) | Err(i) => i,
20173 };
20174
20175 ranges[start_ix..]
20176 .iter()
20177 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20178 })
20179 }
20180
20181 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20182 self.background_highlights
20183 .get(&HighlightKey::Type(TypeId::of::<T>()))
20184 .is_some_and(|(_, highlights)| !highlights.is_empty())
20185 }
20186
20187 /// Returns all background highlights for a given range.
20188 ///
20189 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20190 pub fn background_highlights_in_range(
20191 &self,
20192 search_range: Range<Anchor>,
20193 display_snapshot: &DisplaySnapshot,
20194 theme: &Theme,
20195 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20196 let mut results = Vec::new();
20197 for (color_fetcher, ranges) in self.background_highlights.values() {
20198 let color = color_fetcher(theme);
20199 let start_ix = match ranges.binary_search_by(|probe| {
20200 let cmp = probe
20201 .end
20202 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20203 if cmp.is_gt() {
20204 Ordering::Greater
20205 } else {
20206 Ordering::Less
20207 }
20208 }) {
20209 Ok(i) | Err(i) => i,
20210 };
20211 for range in &ranges[start_ix..] {
20212 if range
20213 .start
20214 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20215 .is_ge()
20216 {
20217 break;
20218 }
20219
20220 let start = range.start.to_display_point(display_snapshot);
20221 let end = range.end.to_display_point(display_snapshot);
20222 results.push((start..end, color))
20223 }
20224 }
20225 results
20226 }
20227
20228 pub fn gutter_highlights_in_range(
20229 &self,
20230 search_range: Range<Anchor>,
20231 display_snapshot: &DisplaySnapshot,
20232 cx: &App,
20233 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20234 let mut results = Vec::new();
20235 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20236 let color = color_fetcher(cx);
20237 let start_ix = match ranges.binary_search_by(|probe| {
20238 let cmp = probe
20239 .end
20240 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20241 if cmp.is_gt() {
20242 Ordering::Greater
20243 } else {
20244 Ordering::Less
20245 }
20246 }) {
20247 Ok(i) | Err(i) => i,
20248 };
20249 for range in &ranges[start_ix..] {
20250 if range
20251 .start
20252 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20253 .is_ge()
20254 {
20255 break;
20256 }
20257
20258 let start = range.start.to_display_point(display_snapshot);
20259 let end = range.end.to_display_point(display_snapshot);
20260 results.push((start..end, color))
20261 }
20262 }
20263 results
20264 }
20265
20266 /// Get the text ranges corresponding to the redaction query
20267 pub fn redacted_ranges(
20268 &self,
20269 search_range: Range<Anchor>,
20270 display_snapshot: &DisplaySnapshot,
20271 cx: &App,
20272 ) -> Vec<Range<DisplayPoint>> {
20273 display_snapshot
20274 .buffer_snapshot
20275 .redacted_ranges(search_range, |file| {
20276 if let Some(file) = file {
20277 file.is_private()
20278 && EditorSettings::get(
20279 Some(SettingsLocation {
20280 worktree_id: file.worktree_id(cx),
20281 path: file.path().as_ref(),
20282 }),
20283 cx,
20284 )
20285 .redact_private_values
20286 } else {
20287 false
20288 }
20289 })
20290 .map(|range| {
20291 range.start.to_display_point(display_snapshot)
20292 ..range.end.to_display_point(display_snapshot)
20293 })
20294 .collect()
20295 }
20296
20297 pub fn highlight_text_key<T: 'static>(
20298 &mut self,
20299 key: usize,
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(
20306 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20307 ranges,
20308 style,
20309 );
20310 });
20311 cx.notify();
20312 }
20313
20314 pub fn highlight_text<T: 'static>(
20315 &mut self,
20316 ranges: Vec<Range<Anchor>>,
20317 style: HighlightStyle,
20318 cx: &mut Context<Self>,
20319 ) {
20320 self.display_map.update(cx, |map, _| {
20321 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20322 });
20323 cx.notify();
20324 }
20325
20326 pub(crate) fn highlight_inlays<T: 'static>(
20327 &mut self,
20328 highlights: Vec<InlayHighlight>,
20329 style: HighlightStyle,
20330 cx: &mut Context<Self>,
20331 ) {
20332 self.display_map.update(cx, |map, _| {
20333 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20334 });
20335 cx.notify();
20336 }
20337
20338 pub fn text_highlights<'a, T: 'static>(
20339 &'a self,
20340 cx: &'a App,
20341 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20342 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20343 }
20344
20345 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20346 let cleared = self
20347 .display_map
20348 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20349 if cleared {
20350 cx.notify();
20351 }
20352 }
20353
20354 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20355 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20356 && self.focus_handle.is_focused(window)
20357 }
20358
20359 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20360 self.show_cursor_when_unfocused = is_enabled;
20361 cx.notify();
20362 }
20363
20364 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20365 cx.notify();
20366 }
20367
20368 fn on_debug_session_event(
20369 &mut self,
20370 _session: Entity<Session>,
20371 event: &SessionEvent,
20372 cx: &mut Context<Self>,
20373 ) {
20374 if let SessionEvent::InvalidateInlineValue = event {
20375 self.refresh_inline_values(cx);
20376 }
20377 }
20378
20379 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20380 let Some(project) = self.project.clone() else {
20381 return;
20382 };
20383
20384 if !self.inline_value_cache.enabled {
20385 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20386 self.splice_inlays(&inlays, Vec::new(), cx);
20387 return;
20388 }
20389
20390 let current_execution_position = self
20391 .highlighted_rows
20392 .get(&TypeId::of::<ActiveDebugLine>())
20393 .and_then(|lines| lines.last().map(|line| line.range.end));
20394
20395 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20396 let inline_values = editor
20397 .update(cx, |editor, cx| {
20398 let Some(current_execution_position) = current_execution_position else {
20399 return Some(Task::ready(Ok(Vec::new())));
20400 };
20401
20402 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20403 let snapshot = buffer.snapshot(cx);
20404
20405 let excerpt = snapshot.excerpt_containing(
20406 current_execution_position..current_execution_position,
20407 )?;
20408
20409 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20410 })?;
20411
20412 let range =
20413 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20414
20415 project.inline_values(buffer, range, cx)
20416 })
20417 .ok()
20418 .flatten()?
20419 .await
20420 .context("refreshing debugger inlays")
20421 .log_err()?;
20422
20423 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20424
20425 for (buffer_id, inline_value) in inline_values
20426 .into_iter()
20427 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20428 {
20429 buffer_inline_values
20430 .entry(buffer_id)
20431 .or_default()
20432 .push(inline_value);
20433 }
20434
20435 editor
20436 .update(cx, |editor, cx| {
20437 let snapshot = editor.buffer.read(cx).snapshot(cx);
20438 let mut new_inlays = Vec::default();
20439
20440 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20441 let buffer_id = buffer_snapshot.remote_id();
20442 buffer_inline_values
20443 .get(&buffer_id)
20444 .into_iter()
20445 .flatten()
20446 .for_each(|hint| {
20447 let inlay = Inlay::debugger(
20448 post_inc(&mut editor.next_inlay_id),
20449 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20450 hint.text(),
20451 );
20452 if !inlay.text.chars().contains(&'\n') {
20453 new_inlays.push(inlay);
20454 }
20455 });
20456 }
20457
20458 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20459 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20460
20461 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20462 })
20463 .ok()?;
20464 Some(())
20465 });
20466 }
20467
20468 fn on_buffer_event(
20469 &mut self,
20470 multibuffer: &Entity<MultiBuffer>,
20471 event: &multi_buffer::Event,
20472 window: &mut Window,
20473 cx: &mut Context<Self>,
20474 ) {
20475 match event {
20476 multi_buffer::Event::Edited {
20477 singleton_buffer_edited,
20478 edited_buffer,
20479 } => {
20480 self.scrollbar_marker_state.dirty = true;
20481 self.active_indent_guides_state.dirty = true;
20482 self.refresh_active_diagnostics(cx);
20483 self.refresh_code_actions(window, cx);
20484 self.refresh_selected_text_highlights(true, window, cx);
20485 self.refresh_single_line_folds(window, cx);
20486 refresh_matching_bracket_highlights(self, window, cx);
20487 if self.has_active_edit_prediction() {
20488 self.update_visible_edit_prediction(window, cx);
20489 }
20490 if let Some(project) = self.project.as_ref()
20491 && let Some(edited_buffer) = edited_buffer
20492 {
20493 project.update(cx, |project, cx| {
20494 self.registered_buffers
20495 .entry(edited_buffer.read(cx).remote_id())
20496 .or_insert_with(|| {
20497 project.register_buffer_with_language_servers(edited_buffer, cx)
20498 });
20499 });
20500 }
20501 cx.emit(EditorEvent::BufferEdited);
20502 cx.emit(SearchEvent::MatchesInvalidated);
20503
20504 if let Some(buffer) = edited_buffer {
20505 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20506 }
20507
20508 if *singleton_buffer_edited {
20509 if let Some(buffer) = edited_buffer
20510 && buffer.read(cx).file().is_none()
20511 {
20512 cx.emit(EditorEvent::TitleChanged);
20513 }
20514 if let Some(project) = &self.project {
20515 #[allow(clippy::mutable_key_type)]
20516 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20517 multibuffer
20518 .all_buffers()
20519 .into_iter()
20520 .filter_map(|buffer| {
20521 buffer.update(cx, |buffer, cx| {
20522 let language = buffer.language()?;
20523 let should_discard = project.update(cx, |project, cx| {
20524 project.is_local()
20525 && !project.has_language_servers_for(buffer, cx)
20526 });
20527 should_discard.not().then_some(language.clone())
20528 })
20529 })
20530 .collect::<HashSet<_>>()
20531 });
20532 if !languages_affected.is_empty() {
20533 self.refresh_inlay_hints(
20534 InlayHintRefreshReason::BufferEdited(languages_affected),
20535 cx,
20536 );
20537 }
20538 }
20539 }
20540
20541 let Some(project) = &self.project else { return };
20542 let (telemetry, is_via_ssh) = {
20543 let project = project.read(cx);
20544 let telemetry = project.client().telemetry().clone();
20545 let is_via_ssh = project.is_via_remote_server();
20546 (telemetry, is_via_ssh)
20547 };
20548 refresh_linked_ranges(self, window, cx);
20549 telemetry.log_edit_event("editor", is_via_ssh);
20550 }
20551 multi_buffer::Event::ExcerptsAdded {
20552 buffer,
20553 predecessor,
20554 excerpts,
20555 } => {
20556 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20557 let buffer_id = buffer.read(cx).remote_id();
20558 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20559 && let Some(project) = &self.project
20560 {
20561 update_uncommitted_diff_for_buffer(
20562 cx.entity(),
20563 project,
20564 [buffer.clone()],
20565 self.buffer.clone(),
20566 cx,
20567 )
20568 .detach();
20569 }
20570 if self.active_diagnostics != ActiveDiagnostic::All {
20571 self.update_lsp_data(false, Some(buffer_id), window, cx);
20572 }
20573 cx.emit(EditorEvent::ExcerptsAdded {
20574 buffer: buffer.clone(),
20575 predecessor: *predecessor,
20576 excerpts: excerpts.clone(),
20577 });
20578 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20579 }
20580 multi_buffer::Event::ExcerptsRemoved {
20581 ids,
20582 removed_buffer_ids,
20583 } => {
20584 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20585 let buffer = self.buffer.read(cx);
20586 self.registered_buffers
20587 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20588 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20589 cx.emit(EditorEvent::ExcerptsRemoved {
20590 ids: ids.clone(),
20591 removed_buffer_ids: removed_buffer_ids.clone(),
20592 });
20593 }
20594 multi_buffer::Event::ExcerptsEdited {
20595 excerpt_ids,
20596 buffer_ids,
20597 } => {
20598 self.display_map.update(cx, |map, cx| {
20599 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20600 });
20601 cx.emit(EditorEvent::ExcerptsEdited {
20602 ids: excerpt_ids.clone(),
20603 });
20604 }
20605 multi_buffer::Event::ExcerptsExpanded { ids } => {
20606 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20607 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20608 }
20609 multi_buffer::Event::Reparsed(buffer_id) => {
20610 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20611 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20612
20613 cx.emit(EditorEvent::Reparsed(*buffer_id));
20614 }
20615 multi_buffer::Event::DiffHunksToggled => {
20616 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20617 }
20618 multi_buffer::Event::LanguageChanged(buffer_id) => {
20619 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20620 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20621 cx.emit(EditorEvent::Reparsed(*buffer_id));
20622 cx.notify();
20623 }
20624 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20625 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20626 multi_buffer::Event::FileHandleChanged
20627 | multi_buffer::Event::Reloaded
20628 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20629 multi_buffer::Event::DiagnosticsUpdated => {
20630 self.update_diagnostics_state(window, cx);
20631 }
20632 _ => {}
20633 };
20634 }
20635
20636 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20637 if !self.diagnostics_enabled() {
20638 return;
20639 }
20640 self.refresh_active_diagnostics(cx);
20641 self.refresh_inline_diagnostics(true, window, cx);
20642 self.scrollbar_marker_state.dirty = true;
20643 cx.notify();
20644 }
20645
20646 pub fn start_temporary_diff_override(&mut self) {
20647 self.load_diff_task.take();
20648 self.temporary_diff_override = true;
20649 }
20650
20651 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20652 self.temporary_diff_override = false;
20653 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20654 self.buffer.update(cx, |buffer, cx| {
20655 buffer.set_all_diff_hunks_collapsed(cx);
20656 });
20657
20658 if let Some(project) = self.project.clone() {
20659 self.load_diff_task = Some(
20660 update_uncommitted_diff_for_buffer(
20661 cx.entity(),
20662 &project,
20663 self.buffer.read(cx).all_buffers(),
20664 self.buffer.clone(),
20665 cx,
20666 )
20667 .shared(),
20668 );
20669 }
20670 }
20671
20672 fn on_display_map_changed(
20673 &mut self,
20674 _: Entity<DisplayMap>,
20675 _: &mut Window,
20676 cx: &mut Context<Self>,
20677 ) {
20678 cx.notify();
20679 }
20680
20681 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20682 if self.diagnostics_enabled() {
20683 let new_severity = EditorSettings::get_global(cx)
20684 .diagnostics_max_severity
20685 .unwrap_or(DiagnosticSeverity::Hint);
20686 self.set_max_diagnostics_severity(new_severity, cx);
20687 }
20688 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20689 self.update_edit_prediction_settings(cx);
20690 self.refresh_edit_prediction(true, false, window, cx);
20691 self.refresh_inline_values(cx);
20692 self.refresh_inlay_hints(
20693 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20694 self.selections.newest_anchor().head(),
20695 &self.buffer.read(cx).snapshot(cx),
20696 cx,
20697 )),
20698 cx,
20699 );
20700
20701 let old_cursor_shape = self.cursor_shape;
20702 let old_show_breadcrumbs = self.show_breadcrumbs;
20703
20704 {
20705 let editor_settings = EditorSettings::get_global(cx);
20706 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20707 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20708 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20709 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20710 }
20711
20712 if old_cursor_shape != self.cursor_shape {
20713 cx.emit(EditorEvent::CursorShapeChanged);
20714 }
20715
20716 if old_show_breadcrumbs != self.show_breadcrumbs {
20717 cx.emit(EditorEvent::BreadcrumbsChanged);
20718 }
20719
20720 let project_settings = ProjectSettings::get_global(cx);
20721 self.serialize_dirty_buffers =
20722 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20723
20724 if self.mode.is_full() {
20725 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20726 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20727 if self.show_inline_diagnostics != show_inline_diagnostics {
20728 self.show_inline_diagnostics = show_inline_diagnostics;
20729 self.refresh_inline_diagnostics(false, window, cx);
20730 }
20731
20732 if self.git_blame_inline_enabled != inline_blame_enabled {
20733 self.toggle_git_blame_inline_internal(false, window, cx);
20734 }
20735
20736 let minimap_settings = EditorSettings::get_global(cx).minimap;
20737 if self.minimap_visibility != MinimapVisibility::Disabled {
20738 if self.minimap_visibility.settings_visibility()
20739 != minimap_settings.minimap_enabled()
20740 {
20741 self.set_minimap_visibility(
20742 MinimapVisibility::for_mode(self.mode(), cx),
20743 window,
20744 cx,
20745 );
20746 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20747 minimap_entity.update(cx, |minimap_editor, cx| {
20748 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20749 })
20750 }
20751 }
20752 }
20753
20754 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20755 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20756 }) {
20757 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20758 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20759 }
20760 self.refresh_colors(false, None, window, cx);
20761 }
20762
20763 cx.notify();
20764 }
20765
20766 pub fn set_searchable(&mut self, searchable: bool) {
20767 self.searchable = searchable;
20768 }
20769
20770 pub fn searchable(&self) -> bool {
20771 self.searchable
20772 }
20773
20774 fn open_proposed_changes_editor(
20775 &mut self,
20776 _: &OpenProposedChangesEditor,
20777 window: &mut Window,
20778 cx: &mut Context<Self>,
20779 ) {
20780 let Some(workspace) = self.workspace() else {
20781 cx.propagate();
20782 return;
20783 };
20784
20785 let selections = self.selections.all::<usize>(cx);
20786 let multi_buffer = self.buffer.read(cx);
20787 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20788 let mut new_selections_by_buffer = HashMap::default();
20789 for selection in selections {
20790 for (buffer, range, _) in
20791 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20792 {
20793 let mut range = range.to_point(buffer);
20794 range.start.column = 0;
20795 range.end.column = buffer.line_len(range.end.row);
20796 new_selections_by_buffer
20797 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20798 .or_insert(Vec::new())
20799 .push(range)
20800 }
20801 }
20802
20803 let proposed_changes_buffers = new_selections_by_buffer
20804 .into_iter()
20805 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20806 .collect::<Vec<_>>();
20807 let proposed_changes_editor = cx.new(|cx| {
20808 ProposedChangesEditor::new(
20809 "Proposed changes",
20810 proposed_changes_buffers,
20811 self.project.clone(),
20812 window,
20813 cx,
20814 )
20815 });
20816
20817 window.defer(cx, move |window, cx| {
20818 workspace.update(cx, |workspace, cx| {
20819 workspace.active_pane().update(cx, |pane, cx| {
20820 pane.add_item(
20821 Box::new(proposed_changes_editor),
20822 true,
20823 true,
20824 None,
20825 window,
20826 cx,
20827 );
20828 });
20829 });
20830 });
20831 }
20832
20833 pub fn open_excerpts_in_split(
20834 &mut self,
20835 _: &OpenExcerptsSplit,
20836 window: &mut Window,
20837 cx: &mut Context<Self>,
20838 ) {
20839 self.open_excerpts_common(None, true, window, cx)
20840 }
20841
20842 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20843 self.open_excerpts_common(None, false, window, cx)
20844 }
20845
20846 fn open_excerpts_common(
20847 &mut self,
20848 jump_data: Option<JumpData>,
20849 split: bool,
20850 window: &mut Window,
20851 cx: &mut Context<Self>,
20852 ) {
20853 let Some(workspace) = self.workspace() else {
20854 cx.propagate();
20855 return;
20856 };
20857
20858 if self.buffer.read(cx).is_singleton() {
20859 cx.propagate();
20860 return;
20861 }
20862
20863 let mut new_selections_by_buffer = HashMap::default();
20864 match &jump_data {
20865 Some(JumpData::MultiBufferPoint {
20866 excerpt_id,
20867 position,
20868 anchor,
20869 line_offset_from_top,
20870 }) => {
20871 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20872 if let Some(buffer) = multi_buffer_snapshot
20873 .buffer_id_for_excerpt(*excerpt_id)
20874 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20875 {
20876 let buffer_snapshot = buffer.read(cx).snapshot();
20877 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20878 language::ToPoint::to_point(anchor, &buffer_snapshot)
20879 } else {
20880 buffer_snapshot.clip_point(*position, Bias::Left)
20881 };
20882 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20883 new_selections_by_buffer.insert(
20884 buffer,
20885 (
20886 vec![jump_to_offset..jump_to_offset],
20887 Some(*line_offset_from_top),
20888 ),
20889 );
20890 }
20891 }
20892 Some(JumpData::MultiBufferRow {
20893 row,
20894 line_offset_from_top,
20895 }) => {
20896 let point = MultiBufferPoint::new(row.0, 0);
20897 if let Some((buffer, buffer_point, _)) =
20898 self.buffer.read(cx).point_to_buffer_point(point, cx)
20899 {
20900 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20901 new_selections_by_buffer
20902 .entry(buffer)
20903 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20904 .0
20905 .push(buffer_offset..buffer_offset)
20906 }
20907 }
20908 None => {
20909 let selections = self.selections.all::<usize>(cx);
20910 let multi_buffer = self.buffer.read(cx);
20911 for selection in selections {
20912 for (snapshot, range, _, anchor) in multi_buffer
20913 .snapshot(cx)
20914 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20915 {
20916 if let Some(anchor) = anchor {
20917 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20918 else {
20919 continue;
20920 };
20921 let offset = text::ToOffset::to_offset(
20922 &anchor.text_anchor,
20923 &buffer_handle.read(cx).snapshot(),
20924 );
20925 let range = offset..offset;
20926 new_selections_by_buffer
20927 .entry(buffer_handle)
20928 .or_insert((Vec::new(), None))
20929 .0
20930 .push(range)
20931 } else {
20932 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20933 else {
20934 continue;
20935 };
20936 new_selections_by_buffer
20937 .entry(buffer_handle)
20938 .or_insert((Vec::new(), None))
20939 .0
20940 .push(range)
20941 }
20942 }
20943 }
20944 }
20945 }
20946
20947 new_selections_by_buffer
20948 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20949
20950 if new_selections_by_buffer.is_empty() {
20951 return;
20952 }
20953
20954 // We defer the pane interaction because we ourselves are a workspace item
20955 // and activating a new item causes the pane to call a method on us reentrantly,
20956 // which panics if we're on the stack.
20957 window.defer(cx, move |window, cx| {
20958 workspace.update(cx, |workspace, cx| {
20959 let pane = if split {
20960 workspace.adjacent_pane(window, cx)
20961 } else {
20962 workspace.active_pane().clone()
20963 };
20964
20965 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20966 let editor = buffer
20967 .read(cx)
20968 .file()
20969 .is_none()
20970 .then(|| {
20971 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20972 // so `workspace.open_project_item` will never find them, always opening a new editor.
20973 // Instead, we try to activate the existing editor in the pane first.
20974 let (editor, pane_item_index) =
20975 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20976 let editor = item.downcast::<Editor>()?;
20977 let singleton_buffer =
20978 editor.read(cx).buffer().read(cx).as_singleton()?;
20979 if singleton_buffer == buffer {
20980 Some((editor, i))
20981 } else {
20982 None
20983 }
20984 })?;
20985 pane.update(cx, |pane, cx| {
20986 pane.activate_item(pane_item_index, true, true, window, cx)
20987 });
20988 Some(editor)
20989 })
20990 .flatten()
20991 .unwrap_or_else(|| {
20992 workspace.open_project_item::<Self>(
20993 pane.clone(),
20994 buffer,
20995 true,
20996 true,
20997 window,
20998 cx,
20999 )
21000 });
21001
21002 editor.update(cx, |editor, cx| {
21003 let autoscroll = match scroll_offset {
21004 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21005 None => Autoscroll::newest(),
21006 };
21007 let nav_history = editor.nav_history.take();
21008 editor.change_selections(
21009 SelectionEffects::scroll(autoscroll),
21010 window,
21011 cx,
21012 |s| {
21013 s.select_ranges(ranges);
21014 },
21015 );
21016 editor.nav_history = nav_history;
21017 });
21018 }
21019 })
21020 });
21021 }
21022
21023 // For now, don't allow opening excerpts in buffers that aren't backed by
21024 // regular project files.
21025 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21026 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21027 }
21028
21029 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21030 let snapshot = self.buffer.read(cx).read(cx);
21031 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21032 Some(
21033 ranges
21034 .iter()
21035 .map(move |range| {
21036 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21037 })
21038 .collect(),
21039 )
21040 }
21041
21042 fn selection_replacement_ranges(
21043 &self,
21044 range: Range<OffsetUtf16>,
21045 cx: &mut App,
21046 ) -> Vec<Range<OffsetUtf16>> {
21047 let selections = self.selections.all::<OffsetUtf16>(cx);
21048 let newest_selection = selections
21049 .iter()
21050 .max_by_key(|selection| selection.id)
21051 .unwrap();
21052 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21053 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21054 let snapshot = self.buffer.read(cx).read(cx);
21055 selections
21056 .into_iter()
21057 .map(|mut selection| {
21058 selection.start.0 =
21059 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21060 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21061 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21062 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21063 })
21064 .collect()
21065 }
21066
21067 fn report_editor_event(
21068 &self,
21069 reported_event: ReportEditorEvent,
21070 file_extension: Option<String>,
21071 cx: &App,
21072 ) {
21073 if cfg!(any(test, feature = "test-support")) {
21074 return;
21075 }
21076
21077 let Some(project) = &self.project else { return };
21078
21079 // If None, we are in a file without an extension
21080 let file = self
21081 .buffer
21082 .read(cx)
21083 .as_singleton()
21084 .and_then(|b| b.read(cx).file());
21085 let file_extension = file_extension.or(file
21086 .as_ref()
21087 .and_then(|file| Path::new(file.file_name(cx)).extension())
21088 .and_then(|e| e.to_str())
21089 .map(|a| a.to_string()));
21090
21091 let vim_mode = vim_enabled(cx);
21092
21093 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21094 let copilot_enabled = edit_predictions_provider
21095 == language::language_settings::EditPredictionProvider::Copilot;
21096 let copilot_enabled_for_language = self
21097 .buffer
21098 .read(cx)
21099 .language_settings(cx)
21100 .show_edit_predictions;
21101
21102 let project = project.read(cx);
21103 let event_type = reported_event.event_type();
21104
21105 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21106 telemetry::event!(
21107 event_type,
21108 type = if auto_saved {"autosave"} else {"manual"},
21109 file_extension,
21110 vim_mode,
21111 copilot_enabled,
21112 copilot_enabled_for_language,
21113 edit_predictions_provider,
21114 is_via_ssh = project.is_via_remote_server(),
21115 );
21116 } else {
21117 telemetry::event!(
21118 event_type,
21119 file_extension,
21120 vim_mode,
21121 copilot_enabled,
21122 copilot_enabled_for_language,
21123 edit_predictions_provider,
21124 is_via_ssh = project.is_via_remote_server(),
21125 );
21126 };
21127 }
21128
21129 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21130 /// with each line being an array of {text, highlight} objects.
21131 fn copy_highlight_json(
21132 &mut self,
21133 _: &CopyHighlightJson,
21134 window: &mut Window,
21135 cx: &mut Context<Self>,
21136 ) {
21137 #[derive(Serialize)]
21138 struct Chunk<'a> {
21139 text: String,
21140 highlight: Option<&'a str>,
21141 }
21142
21143 let snapshot = self.buffer.read(cx).snapshot(cx);
21144 let range = self
21145 .selected_text_range(false, window, cx)
21146 .and_then(|selection| {
21147 if selection.range.is_empty() {
21148 None
21149 } else {
21150 Some(selection.range)
21151 }
21152 })
21153 .unwrap_or_else(|| 0..snapshot.len());
21154
21155 let chunks = snapshot.chunks(range, true);
21156 let mut lines = Vec::new();
21157 let mut line: VecDeque<Chunk> = VecDeque::new();
21158
21159 let Some(style) = self.style.as_ref() else {
21160 return;
21161 };
21162
21163 for chunk in chunks {
21164 let highlight = chunk
21165 .syntax_highlight_id
21166 .and_then(|id| id.name(&style.syntax));
21167 let mut chunk_lines = chunk.text.split('\n').peekable();
21168 while let Some(text) = chunk_lines.next() {
21169 let mut merged_with_last_token = false;
21170 if let Some(last_token) = line.back_mut()
21171 && last_token.highlight == highlight
21172 {
21173 last_token.text.push_str(text);
21174 merged_with_last_token = true;
21175 }
21176
21177 if !merged_with_last_token {
21178 line.push_back(Chunk {
21179 text: text.into(),
21180 highlight,
21181 });
21182 }
21183
21184 if chunk_lines.peek().is_some() {
21185 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21186 line.pop_front();
21187 }
21188 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21189 line.pop_back();
21190 }
21191
21192 lines.push(mem::take(&mut line));
21193 }
21194 }
21195 }
21196
21197 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21198 return;
21199 };
21200 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21201 }
21202
21203 pub fn open_context_menu(
21204 &mut self,
21205 _: &OpenContextMenu,
21206 window: &mut Window,
21207 cx: &mut Context<Self>,
21208 ) {
21209 self.request_autoscroll(Autoscroll::newest(), cx);
21210 let position = self.selections.newest_display(cx).start;
21211 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21212 }
21213
21214 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21215 &self.inlay_hint_cache
21216 }
21217
21218 pub fn replay_insert_event(
21219 &mut self,
21220 text: &str,
21221 relative_utf16_range: Option<Range<isize>>,
21222 window: &mut Window,
21223 cx: &mut Context<Self>,
21224 ) {
21225 if !self.input_enabled {
21226 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21227 return;
21228 }
21229 if let Some(relative_utf16_range) = relative_utf16_range {
21230 let selections = self.selections.all::<OffsetUtf16>(cx);
21231 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21232 let new_ranges = selections.into_iter().map(|range| {
21233 let start = OffsetUtf16(
21234 range
21235 .head()
21236 .0
21237 .saturating_add_signed(relative_utf16_range.start),
21238 );
21239 let end = OffsetUtf16(
21240 range
21241 .head()
21242 .0
21243 .saturating_add_signed(relative_utf16_range.end),
21244 );
21245 start..end
21246 });
21247 s.select_ranges(new_ranges);
21248 });
21249 }
21250
21251 self.handle_input(text, window, cx);
21252 }
21253
21254 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21255 let Some(provider) = self.semantics_provider.as_ref() else {
21256 return false;
21257 };
21258
21259 let mut supports = false;
21260 self.buffer().update(cx, |this, cx| {
21261 this.for_each_buffer(|buffer| {
21262 supports |= provider.supports_inlay_hints(buffer, cx);
21263 });
21264 });
21265
21266 supports
21267 }
21268
21269 pub fn is_focused(&self, window: &Window) -> bool {
21270 self.focus_handle.is_focused(window)
21271 }
21272
21273 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21274 cx.emit(EditorEvent::Focused);
21275
21276 if let Some(descendant) = self
21277 .last_focused_descendant
21278 .take()
21279 .and_then(|descendant| descendant.upgrade())
21280 {
21281 window.focus(&descendant);
21282 } else {
21283 if let Some(blame) = self.blame.as_ref() {
21284 blame.update(cx, GitBlame::focus)
21285 }
21286
21287 self.blink_manager.update(cx, BlinkManager::enable);
21288 self.show_cursor_names(window, cx);
21289 self.buffer.update(cx, |buffer, cx| {
21290 buffer.finalize_last_transaction(cx);
21291 if self.leader_id.is_none() {
21292 buffer.set_active_selections(
21293 &self.selections.disjoint_anchors_arc(),
21294 self.selections.line_mode,
21295 self.cursor_shape,
21296 cx,
21297 );
21298 }
21299 });
21300 }
21301 }
21302
21303 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21304 cx.emit(EditorEvent::FocusedIn)
21305 }
21306
21307 fn handle_focus_out(
21308 &mut self,
21309 event: FocusOutEvent,
21310 _window: &mut Window,
21311 cx: &mut Context<Self>,
21312 ) {
21313 if event.blurred != self.focus_handle {
21314 self.last_focused_descendant = Some(event.blurred);
21315 }
21316 self.selection_drag_state = SelectionDragState::None;
21317 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21318 }
21319
21320 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21321 self.blink_manager.update(cx, BlinkManager::disable);
21322 self.buffer
21323 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21324
21325 if let Some(blame) = self.blame.as_ref() {
21326 blame.update(cx, GitBlame::blur)
21327 }
21328 if !self.hover_state.focused(window, cx) {
21329 hide_hover(self, cx);
21330 }
21331 if !self
21332 .context_menu
21333 .borrow()
21334 .as_ref()
21335 .is_some_and(|context_menu| context_menu.focused(window, cx))
21336 {
21337 self.hide_context_menu(window, cx);
21338 }
21339 self.discard_edit_prediction(false, cx);
21340 cx.emit(EditorEvent::Blurred);
21341 cx.notify();
21342 }
21343
21344 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21345 let mut pending: String = window
21346 .pending_input_keystrokes()
21347 .into_iter()
21348 .flatten()
21349 .filter_map(|keystroke| {
21350 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21351 keystroke.key_char.clone()
21352 } else {
21353 None
21354 }
21355 })
21356 .collect();
21357
21358 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21359 pending = "".to_string();
21360 }
21361
21362 let existing_pending = self
21363 .text_highlights::<PendingInput>(cx)
21364 .map(|(_, ranges)| ranges.to_vec());
21365 if existing_pending.is_none() && pending.is_empty() {
21366 return;
21367 }
21368 let transaction =
21369 self.transact(window, cx, |this, window, cx| {
21370 let selections = this.selections.all::<usize>(cx);
21371 let edits = selections
21372 .iter()
21373 .map(|selection| (selection.end..selection.end, pending.clone()));
21374 this.edit(edits, cx);
21375 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21376 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21377 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21378 }));
21379 });
21380 if let Some(existing_ranges) = existing_pending {
21381 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21382 this.edit(edits, cx);
21383 }
21384 });
21385
21386 let snapshot = self.snapshot(window, cx);
21387 let ranges = self
21388 .selections
21389 .all::<usize>(cx)
21390 .into_iter()
21391 .map(|selection| {
21392 snapshot.buffer_snapshot.anchor_after(selection.end)
21393 ..snapshot
21394 .buffer_snapshot
21395 .anchor_before(selection.end + pending.len())
21396 })
21397 .collect();
21398
21399 if pending.is_empty() {
21400 self.clear_highlights::<PendingInput>(cx);
21401 } else {
21402 self.highlight_text::<PendingInput>(
21403 ranges,
21404 HighlightStyle {
21405 underline: Some(UnderlineStyle {
21406 thickness: px(1.),
21407 color: None,
21408 wavy: false,
21409 }),
21410 ..Default::default()
21411 },
21412 cx,
21413 );
21414 }
21415
21416 self.ime_transaction = self.ime_transaction.or(transaction);
21417 if let Some(transaction) = self.ime_transaction {
21418 self.buffer.update(cx, |buffer, cx| {
21419 buffer.group_until_transaction(transaction, cx);
21420 });
21421 }
21422
21423 if self.text_highlights::<PendingInput>(cx).is_none() {
21424 self.ime_transaction.take();
21425 }
21426 }
21427
21428 pub fn register_action_renderer(
21429 &mut self,
21430 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21431 ) -> Subscription {
21432 let id = self.next_editor_action_id.post_inc();
21433 self.editor_actions
21434 .borrow_mut()
21435 .insert(id, Box::new(listener));
21436
21437 let editor_actions = self.editor_actions.clone();
21438 Subscription::new(move || {
21439 editor_actions.borrow_mut().remove(&id);
21440 })
21441 }
21442
21443 pub fn register_action<A: Action>(
21444 &mut self,
21445 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21446 ) -> Subscription {
21447 let id = self.next_editor_action_id.post_inc();
21448 let listener = Arc::new(listener);
21449 self.editor_actions.borrow_mut().insert(
21450 id,
21451 Box::new(move |_, window, _| {
21452 let listener = listener.clone();
21453 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21454 let action = action.downcast_ref().unwrap();
21455 if phase == DispatchPhase::Bubble {
21456 listener(action, window, cx)
21457 }
21458 })
21459 }),
21460 );
21461
21462 let editor_actions = self.editor_actions.clone();
21463 Subscription::new(move || {
21464 editor_actions.borrow_mut().remove(&id);
21465 })
21466 }
21467
21468 pub fn file_header_size(&self) -> u32 {
21469 FILE_HEADER_HEIGHT
21470 }
21471
21472 pub fn restore(
21473 &mut self,
21474 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21475 window: &mut Window,
21476 cx: &mut Context<Self>,
21477 ) {
21478 let workspace = self.workspace();
21479 let project = self.project();
21480 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21481 let mut tasks = Vec::new();
21482 for (buffer_id, changes) in revert_changes {
21483 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21484 buffer.update(cx, |buffer, cx| {
21485 buffer.edit(
21486 changes
21487 .into_iter()
21488 .map(|(range, text)| (range, text.to_string())),
21489 None,
21490 cx,
21491 );
21492 });
21493
21494 if let Some(project) =
21495 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21496 {
21497 project.update(cx, |project, cx| {
21498 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21499 })
21500 }
21501 }
21502 }
21503 tasks
21504 });
21505 cx.spawn_in(window, async move |_, cx| {
21506 for (buffer, task) in save_tasks {
21507 let result = task.await;
21508 if result.is_err() {
21509 let Some(path) = buffer
21510 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21511 .ok()
21512 else {
21513 continue;
21514 };
21515 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21516 let Some(task) = cx
21517 .update_window_entity(workspace, |workspace, window, cx| {
21518 workspace
21519 .open_path_preview(path, None, false, false, false, window, cx)
21520 })
21521 .ok()
21522 else {
21523 continue;
21524 };
21525 task.await.log_err();
21526 }
21527 }
21528 }
21529 })
21530 .detach();
21531 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21532 selections.refresh()
21533 });
21534 }
21535
21536 pub fn to_pixel_point(
21537 &self,
21538 source: multi_buffer::Anchor,
21539 editor_snapshot: &EditorSnapshot,
21540 window: &mut Window,
21541 ) -> Option<gpui::Point<Pixels>> {
21542 let source_point = source.to_display_point(editor_snapshot);
21543 self.display_to_pixel_point(source_point, editor_snapshot, window)
21544 }
21545
21546 pub fn display_to_pixel_point(
21547 &self,
21548 source: DisplayPoint,
21549 editor_snapshot: &EditorSnapshot,
21550 window: &mut Window,
21551 ) -> Option<gpui::Point<Pixels>> {
21552 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21553 let text_layout_details = self.text_layout_details(window);
21554 let scroll_top = text_layout_details
21555 .scroll_anchor
21556 .scroll_position(editor_snapshot)
21557 .y;
21558
21559 if source.row().as_f32() < scroll_top.floor() {
21560 return None;
21561 }
21562 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21563 let source_y = line_height * (source.row().as_f32() - scroll_top);
21564 Some(gpui::Point::new(source_x, source_y))
21565 }
21566
21567 pub fn has_visible_completions_menu(&self) -> bool {
21568 !self.edit_prediction_preview_is_active()
21569 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21570 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21571 })
21572 }
21573
21574 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21575 if self.mode.is_minimap() {
21576 return;
21577 }
21578 self.addons
21579 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21580 }
21581
21582 pub fn unregister_addon<T: Addon>(&mut self) {
21583 self.addons.remove(&std::any::TypeId::of::<T>());
21584 }
21585
21586 pub fn addon<T: Addon>(&self) -> Option<&T> {
21587 let type_id = std::any::TypeId::of::<T>();
21588 self.addons
21589 .get(&type_id)
21590 .and_then(|item| item.to_any().downcast_ref::<T>())
21591 }
21592
21593 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21594 let type_id = std::any::TypeId::of::<T>();
21595 self.addons
21596 .get_mut(&type_id)
21597 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21598 }
21599
21600 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21601 let text_layout_details = self.text_layout_details(window);
21602 let style = &text_layout_details.editor_style;
21603 let font_id = window.text_system().resolve_font(&style.text.font());
21604 let font_size = style.text.font_size.to_pixels(window.rem_size());
21605 let line_height = style.text.line_height_in_pixels(window.rem_size());
21606 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21607 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21608
21609 CharacterDimensions {
21610 em_width,
21611 em_advance,
21612 line_height,
21613 }
21614 }
21615
21616 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21617 self.load_diff_task.clone()
21618 }
21619
21620 fn read_metadata_from_db(
21621 &mut self,
21622 item_id: u64,
21623 workspace_id: WorkspaceId,
21624 window: &mut Window,
21625 cx: &mut Context<Editor>,
21626 ) {
21627 if self.is_singleton(cx)
21628 && !self.mode.is_minimap()
21629 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21630 {
21631 let buffer_snapshot = OnceCell::new();
21632
21633 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21634 && !folds.is_empty()
21635 {
21636 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21637 self.fold_ranges(
21638 folds
21639 .into_iter()
21640 .map(|(start, end)| {
21641 snapshot.clip_offset(start, Bias::Left)
21642 ..snapshot.clip_offset(end, Bias::Right)
21643 })
21644 .collect(),
21645 false,
21646 window,
21647 cx,
21648 );
21649 }
21650
21651 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21652 && !selections.is_empty()
21653 {
21654 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21655 // skip adding the initial selection to selection history
21656 self.selection_history.mode = SelectionHistoryMode::Skipping;
21657 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21658 s.select_ranges(selections.into_iter().map(|(start, end)| {
21659 snapshot.clip_offset(start, Bias::Left)
21660 ..snapshot.clip_offset(end, Bias::Right)
21661 }));
21662 });
21663 self.selection_history.mode = SelectionHistoryMode::Normal;
21664 };
21665 }
21666
21667 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21668 }
21669
21670 fn update_lsp_data(
21671 &mut self,
21672 ignore_cache: bool,
21673 for_buffer: Option<BufferId>,
21674 window: &mut Window,
21675 cx: &mut Context<'_, Self>,
21676 ) {
21677 self.pull_diagnostics(for_buffer, window, cx);
21678 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21679 }
21680}
21681
21682fn vim_enabled(cx: &App) -> bool {
21683 cx.global::<SettingsStore>()
21684 .raw_user_settings()
21685 .get("vim_mode")
21686 == Some(&serde_json::Value::Bool(true))
21687}
21688
21689fn process_completion_for_edit(
21690 completion: &Completion,
21691 intent: CompletionIntent,
21692 buffer: &Entity<Buffer>,
21693 cursor_position: &text::Anchor,
21694 cx: &mut Context<Editor>,
21695) -> CompletionEdit {
21696 let buffer = buffer.read(cx);
21697 let buffer_snapshot = buffer.snapshot();
21698 let (snippet, new_text) = if completion.is_snippet() {
21699 // Workaround for typescript language server issues so that methods don't expand within
21700 // strings and functions with type expressions. The previous point is used because the query
21701 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21702 let mut snippet_source = completion.new_text.clone();
21703 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21704 previous_point.column = previous_point.column.saturating_sub(1);
21705 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21706 && scope.prefers_label_for_snippet_in_completion()
21707 && let Some(label) = completion.label()
21708 && matches!(
21709 completion.kind(),
21710 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21711 )
21712 {
21713 snippet_source = label;
21714 }
21715 match Snippet::parse(&snippet_source).log_err() {
21716 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21717 None => (None, completion.new_text.clone()),
21718 }
21719 } else {
21720 (None, completion.new_text.clone())
21721 };
21722
21723 let mut range_to_replace = {
21724 let replace_range = &completion.replace_range;
21725 if let CompletionSource::Lsp {
21726 insert_range: Some(insert_range),
21727 ..
21728 } = &completion.source
21729 {
21730 debug_assert_eq!(
21731 insert_range.start, replace_range.start,
21732 "insert_range and replace_range should start at the same position"
21733 );
21734 debug_assert!(
21735 insert_range
21736 .start
21737 .cmp(cursor_position, &buffer_snapshot)
21738 .is_le(),
21739 "insert_range should start before or at cursor position"
21740 );
21741 debug_assert!(
21742 replace_range
21743 .start
21744 .cmp(cursor_position, &buffer_snapshot)
21745 .is_le(),
21746 "replace_range should start before or at cursor position"
21747 );
21748
21749 let should_replace = match intent {
21750 CompletionIntent::CompleteWithInsert => false,
21751 CompletionIntent::CompleteWithReplace => true,
21752 CompletionIntent::Complete | CompletionIntent::Compose => {
21753 let insert_mode =
21754 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21755 .completions
21756 .lsp_insert_mode;
21757 match insert_mode {
21758 LspInsertMode::Insert => false,
21759 LspInsertMode::Replace => true,
21760 LspInsertMode::ReplaceSubsequence => {
21761 let mut text_to_replace = buffer.chars_for_range(
21762 buffer.anchor_before(replace_range.start)
21763 ..buffer.anchor_after(replace_range.end),
21764 );
21765 let mut current_needle = text_to_replace.next();
21766 for haystack_ch in completion.label.text.chars() {
21767 if let Some(needle_ch) = current_needle
21768 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21769 {
21770 current_needle = text_to_replace.next();
21771 }
21772 }
21773 current_needle.is_none()
21774 }
21775 LspInsertMode::ReplaceSuffix => {
21776 if replace_range
21777 .end
21778 .cmp(cursor_position, &buffer_snapshot)
21779 .is_gt()
21780 {
21781 let range_after_cursor = *cursor_position..replace_range.end;
21782 let text_after_cursor = buffer
21783 .text_for_range(
21784 buffer.anchor_before(range_after_cursor.start)
21785 ..buffer.anchor_after(range_after_cursor.end),
21786 )
21787 .collect::<String>()
21788 .to_ascii_lowercase();
21789 completion
21790 .label
21791 .text
21792 .to_ascii_lowercase()
21793 .ends_with(&text_after_cursor)
21794 } else {
21795 true
21796 }
21797 }
21798 }
21799 }
21800 };
21801
21802 if should_replace {
21803 replace_range.clone()
21804 } else {
21805 insert_range.clone()
21806 }
21807 } else {
21808 replace_range.clone()
21809 }
21810 };
21811
21812 if range_to_replace
21813 .end
21814 .cmp(cursor_position, &buffer_snapshot)
21815 .is_lt()
21816 {
21817 range_to_replace.end = *cursor_position;
21818 }
21819
21820 CompletionEdit {
21821 new_text,
21822 replace_range: range_to_replace.to_offset(buffer),
21823 snippet,
21824 }
21825}
21826
21827struct CompletionEdit {
21828 new_text: String,
21829 replace_range: Range<usize>,
21830 snippet: Option<Snippet>,
21831}
21832
21833fn insert_extra_newline_brackets(
21834 buffer: &MultiBufferSnapshot,
21835 range: Range<usize>,
21836 language: &language::LanguageScope,
21837) -> bool {
21838 let leading_whitespace_len = buffer
21839 .reversed_chars_at(range.start)
21840 .take_while(|c| c.is_whitespace() && *c != '\n')
21841 .map(|c| c.len_utf8())
21842 .sum::<usize>();
21843 let trailing_whitespace_len = buffer
21844 .chars_at(range.end)
21845 .take_while(|c| c.is_whitespace() && *c != '\n')
21846 .map(|c| c.len_utf8())
21847 .sum::<usize>();
21848 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21849
21850 language.brackets().any(|(pair, enabled)| {
21851 let pair_start = pair.start.trim_end();
21852 let pair_end = pair.end.trim_start();
21853
21854 enabled
21855 && pair.newline
21856 && buffer.contains_str_at(range.end, pair_end)
21857 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21858 })
21859}
21860
21861fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21862 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21863 [(buffer, range, _)] => (*buffer, range.clone()),
21864 _ => return false,
21865 };
21866 let pair = {
21867 let mut result: Option<BracketMatch> = None;
21868
21869 for pair in buffer
21870 .all_bracket_ranges(range.clone())
21871 .filter(move |pair| {
21872 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21873 })
21874 {
21875 let len = pair.close_range.end - pair.open_range.start;
21876
21877 if let Some(existing) = &result {
21878 let existing_len = existing.close_range.end - existing.open_range.start;
21879 if len > existing_len {
21880 continue;
21881 }
21882 }
21883
21884 result = Some(pair);
21885 }
21886
21887 result
21888 };
21889 let Some(pair) = pair else {
21890 return false;
21891 };
21892 pair.newline_only
21893 && buffer
21894 .chars_for_range(pair.open_range.end..range.start)
21895 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21896 .all(|c| c.is_whitespace() && c != '\n')
21897}
21898
21899fn update_uncommitted_diff_for_buffer(
21900 editor: Entity<Editor>,
21901 project: &Entity<Project>,
21902 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21903 buffer: Entity<MultiBuffer>,
21904 cx: &mut App,
21905) -> Task<()> {
21906 let mut tasks = Vec::new();
21907 project.update(cx, |project, cx| {
21908 for buffer in buffers {
21909 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21910 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21911 }
21912 }
21913 });
21914 cx.spawn(async move |cx| {
21915 let diffs = future::join_all(tasks).await;
21916 if editor
21917 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21918 .unwrap_or(false)
21919 {
21920 return;
21921 }
21922
21923 buffer
21924 .update(cx, |buffer, cx| {
21925 for diff in diffs.into_iter().flatten() {
21926 buffer.add_diff(diff, cx);
21927 }
21928 })
21929 .ok();
21930 })
21931}
21932
21933fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21934 let tab_size = tab_size.get() as usize;
21935 let mut width = offset;
21936
21937 for ch in text.chars() {
21938 width += if ch == '\t' {
21939 tab_size - (width % tab_size)
21940 } else {
21941 1
21942 };
21943 }
21944
21945 width - offset
21946}
21947
21948#[cfg(test)]
21949mod tests {
21950 use super::*;
21951
21952 #[test]
21953 fn test_string_size_with_expanded_tabs() {
21954 let nz = |val| NonZeroU32::new(val).unwrap();
21955 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21956 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21957 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21958 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21959 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21960 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21961 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21962 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21963 }
21964}
21965
21966/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21967struct WordBreakingTokenizer<'a> {
21968 input: &'a str,
21969}
21970
21971impl<'a> WordBreakingTokenizer<'a> {
21972 fn new(input: &'a str) -> Self {
21973 Self { input }
21974 }
21975}
21976
21977fn is_char_ideographic(ch: char) -> bool {
21978 use unicode_script::Script::*;
21979 use unicode_script::UnicodeScript;
21980 matches!(ch.script(), Han | Tangut | Yi)
21981}
21982
21983fn is_grapheme_ideographic(text: &str) -> bool {
21984 text.chars().any(is_char_ideographic)
21985}
21986
21987fn is_grapheme_whitespace(text: &str) -> bool {
21988 text.chars().any(|x| x.is_whitespace())
21989}
21990
21991fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21992 text.chars()
21993 .next()
21994 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21995}
21996
21997#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21998enum WordBreakToken<'a> {
21999 Word { token: &'a str, grapheme_len: usize },
22000 InlineWhitespace { token: &'a str, grapheme_len: usize },
22001 Newline,
22002}
22003
22004impl<'a> Iterator for WordBreakingTokenizer<'a> {
22005 /// Yields a span, the count of graphemes in the token, and whether it was
22006 /// whitespace. Note that it also breaks at word boundaries.
22007 type Item = WordBreakToken<'a>;
22008
22009 fn next(&mut self) -> Option<Self::Item> {
22010 use unicode_segmentation::UnicodeSegmentation;
22011 if self.input.is_empty() {
22012 return None;
22013 }
22014
22015 let mut iter = self.input.graphemes(true).peekable();
22016 let mut offset = 0;
22017 let mut grapheme_len = 0;
22018 if let Some(first_grapheme) = iter.next() {
22019 let is_newline = first_grapheme == "\n";
22020 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22021 offset += first_grapheme.len();
22022 grapheme_len += 1;
22023 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22024 if let Some(grapheme) = iter.peek().copied()
22025 && should_stay_with_preceding_ideograph(grapheme)
22026 {
22027 offset += grapheme.len();
22028 grapheme_len += 1;
22029 }
22030 } else {
22031 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22032 let mut next_word_bound = words.peek().copied();
22033 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22034 next_word_bound = words.next();
22035 }
22036 while let Some(grapheme) = iter.peek().copied() {
22037 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22038 break;
22039 };
22040 if is_grapheme_whitespace(grapheme) != is_whitespace
22041 || (grapheme == "\n") != is_newline
22042 {
22043 break;
22044 };
22045 offset += grapheme.len();
22046 grapheme_len += 1;
22047 iter.next();
22048 }
22049 }
22050 let token = &self.input[..offset];
22051 self.input = &self.input[offset..];
22052 if token == "\n" {
22053 Some(WordBreakToken::Newline)
22054 } else if is_whitespace {
22055 Some(WordBreakToken::InlineWhitespace {
22056 token,
22057 grapheme_len,
22058 })
22059 } else {
22060 Some(WordBreakToken::Word {
22061 token,
22062 grapheme_len,
22063 })
22064 }
22065 } else {
22066 None
22067 }
22068 }
22069}
22070
22071#[test]
22072fn test_word_breaking_tokenizer() {
22073 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22074 ("", &[]),
22075 (" ", &[whitespace(" ", 2)]),
22076 ("Ʒ", &[word("Ʒ", 1)]),
22077 ("Ǽ", &[word("Ǽ", 1)]),
22078 ("⋑", &[word("⋑", 1)]),
22079 ("⋑⋑", &[word("⋑⋑", 2)]),
22080 (
22081 "原理,进而",
22082 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22083 ),
22084 (
22085 "hello world",
22086 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22087 ),
22088 (
22089 "hello, world",
22090 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22091 ),
22092 (
22093 " hello world",
22094 &[
22095 whitespace(" ", 2),
22096 word("hello", 5),
22097 whitespace(" ", 1),
22098 word("world", 5),
22099 ],
22100 ),
22101 (
22102 "这是什么 \n 钢笔",
22103 &[
22104 word("这", 1),
22105 word("是", 1),
22106 word("什", 1),
22107 word("么", 1),
22108 whitespace(" ", 1),
22109 newline(),
22110 whitespace(" ", 1),
22111 word("钢", 1),
22112 word("笔", 1),
22113 ],
22114 ),
22115 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22116 ];
22117
22118 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22119 WordBreakToken::Word {
22120 token,
22121 grapheme_len,
22122 }
22123 }
22124
22125 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22126 WordBreakToken::InlineWhitespace {
22127 token,
22128 grapheme_len,
22129 }
22130 }
22131
22132 fn newline() -> WordBreakToken<'static> {
22133 WordBreakToken::Newline
22134 }
22135
22136 for (input, result) in tests {
22137 assert_eq!(
22138 WordBreakingTokenizer::new(input)
22139 .collect::<Vec<_>>()
22140 .as_slice(),
22141 *result,
22142 );
22143 }
22144}
22145
22146fn wrap_with_prefix(
22147 first_line_prefix: String,
22148 subsequent_lines_prefix: String,
22149 unwrapped_text: String,
22150 wrap_column: usize,
22151 tab_size: NonZeroU32,
22152 preserve_existing_whitespace: bool,
22153) -> String {
22154 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22155 let subsequent_lines_prefix_len =
22156 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22157 let mut wrapped_text = String::new();
22158 let mut current_line = first_line_prefix;
22159 let mut is_first_line = true;
22160
22161 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22162 let mut current_line_len = first_line_prefix_len;
22163 let mut in_whitespace = false;
22164 for token in tokenizer {
22165 let have_preceding_whitespace = in_whitespace;
22166 match token {
22167 WordBreakToken::Word {
22168 token,
22169 grapheme_len,
22170 } => {
22171 in_whitespace = false;
22172 let current_prefix_len = if is_first_line {
22173 first_line_prefix_len
22174 } else {
22175 subsequent_lines_prefix_len
22176 };
22177 if current_line_len + grapheme_len > wrap_column
22178 && current_line_len != current_prefix_len
22179 {
22180 wrapped_text.push_str(current_line.trim_end());
22181 wrapped_text.push('\n');
22182 is_first_line = false;
22183 current_line = subsequent_lines_prefix.clone();
22184 current_line_len = subsequent_lines_prefix_len;
22185 }
22186 current_line.push_str(token);
22187 current_line_len += grapheme_len;
22188 }
22189 WordBreakToken::InlineWhitespace {
22190 mut token,
22191 mut grapheme_len,
22192 } => {
22193 in_whitespace = true;
22194 if have_preceding_whitespace && !preserve_existing_whitespace {
22195 continue;
22196 }
22197 if !preserve_existing_whitespace {
22198 token = " ";
22199 grapheme_len = 1;
22200 }
22201 let current_prefix_len = if is_first_line {
22202 first_line_prefix_len
22203 } else {
22204 subsequent_lines_prefix_len
22205 };
22206 if current_line_len + grapheme_len > wrap_column {
22207 wrapped_text.push_str(current_line.trim_end());
22208 wrapped_text.push('\n');
22209 is_first_line = false;
22210 current_line = subsequent_lines_prefix.clone();
22211 current_line_len = subsequent_lines_prefix_len;
22212 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22213 current_line.push_str(token);
22214 current_line_len += grapheme_len;
22215 }
22216 }
22217 WordBreakToken::Newline => {
22218 in_whitespace = true;
22219 let current_prefix_len = if is_first_line {
22220 first_line_prefix_len
22221 } else {
22222 subsequent_lines_prefix_len
22223 };
22224 if preserve_existing_whitespace {
22225 wrapped_text.push_str(current_line.trim_end());
22226 wrapped_text.push('\n');
22227 is_first_line = false;
22228 current_line = subsequent_lines_prefix.clone();
22229 current_line_len = subsequent_lines_prefix_len;
22230 } else if have_preceding_whitespace {
22231 continue;
22232 } else if current_line_len + 1 > wrap_column
22233 && current_line_len != current_prefix_len
22234 {
22235 wrapped_text.push_str(current_line.trim_end());
22236 wrapped_text.push('\n');
22237 is_first_line = false;
22238 current_line = subsequent_lines_prefix.clone();
22239 current_line_len = subsequent_lines_prefix_len;
22240 } else if current_line_len != current_prefix_len {
22241 current_line.push(' ');
22242 current_line_len += 1;
22243 }
22244 }
22245 }
22246 }
22247
22248 if !current_line.is_empty() {
22249 wrapped_text.push_str(¤t_line);
22250 }
22251 wrapped_text
22252}
22253
22254#[test]
22255fn test_wrap_with_prefix() {
22256 assert_eq!(
22257 wrap_with_prefix(
22258 "# ".to_string(),
22259 "# ".to_string(),
22260 "abcdefg".to_string(),
22261 4,
22262 NonZeroU32::new(4).unwrap(),
22263 false,
22264 ),
22265 "# abcdefg"
22266 );
22267 assert_eq!(
22268 wrap_with_prefix(
22269 "".to_string(),
22270 "".to_string(),
22271 "\thello world".to_string(),
22272 8,
22273 NonZeroU32::new(4).unwrap(),
22274 false,
22275 ),
22276 "hello\nworld"
22277 );
22278 assert_eq!(
22279 wrap_with_prefix(
22280 "// ".to_string(),
22281 "// ".to_string(),
22282 "xx \nyy zz aa bb cc".to_string(),
22283 12,
22284 NonZeroU32::new(4).unwrap(),
22285 false,
22286 ),
22287 "// xx yy zz\n// aa bb cc"
22288 );
22289 assert_eq!(
22290 wrap_with_prefix(
22291 String::new(),
22292 String::new(),
22293 "这是什么 \n 钢笔".to_string(),
22294 3,
22295 NonZeroU32::new(4).unwrap(),
22296 false,
22297 ),
22298 "这是什\n么 钢\n笔"
22299 );
22300}
22301
22302pub trait CollaborationHub {
22303 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22304 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22305 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22306}
22307
22308impl CollaborationHub for Entity<Project> {
22309 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22310 self.read(cx).collaborators()
22311 }
22312
22313 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22314 self.read(cx).user_store().read(cx).participant_indices()
22315 }
22316
22317 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22318 let this = self.read(cx);
22319 let user_ids = this.collaborators().values().map(|c| c.user_id);
22320 this.user_store().read(cx).participant_names(user_ids, cx)
22321 }
22322}
22323
22324pub trait SemanticsProvider {
22325 fn hover(
22326 &self,
22327 buffer: &Entity<Buffer>,
22328 position: text::Anchor,
22329 cx: &mut App,
22330 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22331
22332 fn inline_values(
22333 &self,
22334 buffer_handle: Entity<Buffer>,
22335 range: Range<text::Anchor>,
22336 cx: &mut App,
22337 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22338
22339 fn inlay_hints(
22340 &self,
22341 buffer_handle: Entity<Buffer>,
22342 range: Range<text::Anchor>,
22343 cx: &mut App,
22344 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22345
22346 fn resolve_inlay_hint(
22347 &self,
22348 hint: InlayHint,
22349 buffer_handle: Entity<Buffer>,
22350 server_id: LanguageServerId,
22351 cx: &mut App,
22352 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22353
22354 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22355
22356 fn document_highlights(
22357 &self,
22358 buffer: &Entity<Buffer>,
22359 position: text::Anchor,
22360 cx: &mut App,
22361 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22362
22363 fn definitions(
22364 &self,
22365 buffer: &Entity<Buffer>,
22366 position: text::Anchor,
22367 kind: GotoDefinitionKind,
22368 cx: &mut App,
22369 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22370
22371 fn range_for_rename(
22372 &self,
22373 buffer: &Entity<Buffer>,
22374 position: text::Anchor,
22375 cx: &mut App,
22376 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22377
22378 fn perform_rename(
22379 &self,
22380 buffer: &Entity<Buffer>,
22381 position: text::Anchor,
22382 new_name: String,
22383 cx: &mut App,
22384 ) -> Option<Task<Result<ProjectTransaction>>>;
22385}
22386
22387pub trait CompletionProvider {
22388 fn completions(
22389 &self,
22390 excerpt_id: ExcerptId,
22391 buffer: &Entity<Buffer>,
22392 buffer_position: text::Anchor,
22393 trigger: CompletionContext,
22394 window: &mut Window,
22395 cx: &mut Context<Editor>,
22396 ) -> Task<Result<Vec<CompletionResponse>>>;
22397
22398 fn resolve_completions(
22399 &self,
22400 _buffer: Entity<Buffer>,
22401 _completion_indices: Vec<usize>,
22402 _completions: Rc<RefCell<Box<[Completion]>>>,
22403 _cx: &mut Context<Editor>,
22404 ) -> Task<Result<bool>> {
22405 Task::ready(Ok(false))
22406 }
22407
22408 fn apply_additional_edits_for_completion(
22409 &self,
22410 _buffer: Entity<Buffer>,
22411 _completions: Rc<RefCell<Box<[Completion]>>>,
22412 _completion_index: usize,
22413 _push_to_history: bool,
22414 _cx: &mut Context<Editor>,
22415 ) -> Task<Result<Option<language::Transaction>>> {
22416 Task::ready(Ok(None))
22417 }
22418
22419 fn is_completion_trigger(
22420 &self,
22421 buffer: &Entity<Buffer>,
22422 position: language::Anchor,
22423 text: &str,
22424 trigger_in_words: bool,
22425 menu_is_open: bool,
22426 cx: &mut Context<Editor>,
22427 ) -> bool;
22428
22429 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22430
22431 fn sort_completions(&self) -> bool {
22432 true
22433 }
22434
22435 fn filter_completions(&self) -> bool {
22436 true
22437 }
22438}
22439
22440pub trait CodeActionProvider {
22441 fn id(&self) -> Arc<str>;
22442
22443 fn code_actions(
22444 &self,
22445 buffer: &Entity<Buffer>,
22446 range: Range<text::Anchor>,
22447 window: &mut Window,
22448 cx: &mut App,
22449 ) -> Task<Result<Vec<CodeAction>>>;
22450
22451 fn apply_code_action(
22452 &self,
22453 buffer_handle: Entity<Buffer>,
22454 action: CodeAction,
22455 excerpt_id: ExcerptId,
22456 push_to_history: bool,
22457 window: &mut Window,
22458 cx: &mut App,
22459 ) -> Task<Result<ProjectTransaction>>;
22460}
22461
22462impl CodeActionProvider for Entity<Project> {
22463 fn id(&self) -> Arc<str> {
22464 "project".into()
22465 }
22466
22467 fn code_actions(
22468 &self,
22469 buffer: &Entity<Buffer>,
22470 range: Range<text::Anchor>,
22471 _window: &mut Window,
22472 cx: &mut App,
22473 ) -> Task<Result<Vec<CodeAction>>> {
22474 self.update(cx, |project, cx| {
22475 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22476 let code_actions = project.code_actions(buffer, range, None, cx);
22477 cx.background_spawn(async move {
22478 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22479 Ok(code_lens_actions
22480 .context("code lens fetch")?
22481 .into_iter()
22482 .flatten()
22483 .chain(
22484 code_actions
22485 .context("code action fetch")?
22486 .into_iter()
22487 .flatten(),
22488 )
22489 .collect())
22490 })
22491 })
22492 }
22493
22494 fn apply_code_action(
22495 &self,
22496 buffer_handle: Entity<Buffer>,
22497 action: CodeAction,
22498 _excerpt_id: ExcerptId,
22499 push_to_history: bool,
22500 _window: &mut Window,
22501 cx: &mut App,
22502 ) -> Task<Result<ProjectTransaction>> {
22503 self.update(cx, |project, cx| {
22504 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22505 })
22506 }
22507}
22508
22509fn snippet_completions(
22510 project: &Project,
22511 buffer: &Entity<Buffer>,
22512 buffer_position: text::Anchor,
22513 cx: &mut App,
22514) -> Task<Result<CompletionResponse>> {
22515 let languages = buffer.read(cx).languages_at(buffer_position);
22516 let snippet_store = project.snippets().read(cx);
22517
22518 let scopes: Vec<_> = languages
22519 .iter()
22520 .filter_map(|language| {
22521 let language_name = language.lsp_id();
22522 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22523
22524 if snippets.is_empty() {
22525 None
22526 } else {
22527 Some((language.default_scope(), snippets))
22528 }
22529 })
22530 .collect();
22531
22532 if scopes.is_empty() {
22533 return Task::ready(Ok(CompletionResponse {
22534 completions: vec![],
22535 display_options: CompletionDisplayOptions::default(),
22536 is_incomplete: false,
22537 }));
22538 }
22539
22540 let snapshot = buffer.read(cx).text_snapshot();
22541 let chars: String = snapshot
22542 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22543 .collect();
22544 let executor = cx.background_executor().clone();
22545
22546 cx.background_spawn(async move {
22547 let mut is_incomplete = false;
22548 let mut completions: Vec<Completion> = Vec::new();
22549 for (scope, snippets) in scopes.into_iter() {
22550 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22551 let mut last_word = chars
22552 .chars()
22553 .take_while(|c| classifier.is_word(*c))
22554 .collect::<String>();
22555 last_word = last_word.chars().rev().collect();
22556
22557 if last_word.is_empty() {
22558 return Ok(CompletionResponse {
22559 completions: vec![],
22560 display_options: CompletionDisplayOptions::default(),
22561 is_incomplete: true,
22562 });
22563 }
22564
22565 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22566 let to_lsp = |point: &text::Anchor| {
22567 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22568 point_to_lsp(end)
22569 };
22570 let lsp_end = to_lsp(&buffer_position);
22571
22572 let candidates = snippets
22573 .iter()
22574 .enumerate()
22575 .flat_map(|(ix, snippet)| {
22576 snippet
22577 .prefix
22578 .iter()
22579 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22580 })
22581 .collect::<Vec<StringMatchCandidate>>();
22582
22583 const MAX_RESULTS: usize = 100;
22584 let mut matches = fuzzy::match_strings(
22585 &candidates,
22586 &last_word,
22587 last_word.chars().any(|c| c.is_uppercase()),
22588 true,
22589 MAX_RESULTS,
22590 &Default::default(),
22591 executor.clone(),
22592 )
22593 .await;
22594
22595 if matches.len() >= MAX_RESULTS {
22596 is_incomplete = true;
22597 }
22598
22599 // Remove all candidates where the query's start does not match the start of any word in the candidate
22600 if let Some(query_start) = last_word.chars().next() {
22601 matches.retain(|string_match| {
22602 split_words(&string_match.string).any(|word| {
22603 // Check that the first codepoint of the word as lowercase matches the first
22604 // codepoint of the query as lowercase
22605 word.chars()
22606 .flat_map(|codepoint| codepoint.to_lowercase())
22607 .zip(query_start.to_lowercase())
22608 .all(|(word_cp, query_cp)| word_cp == query_cp)
22609 })
22610 });
22611 }
22612
22613 let matched_strings = matches
22614 .into_iter()
22615 .map(|m| m.string)
22616 .collect::<HashSet<_>>();
22617
22618 completions.extend(snippets.iter().filter_map(|snippet| {
22619 let matching_prefix = snippet
22620 .prefix
22621 .iter()
22622 .find(|prefix| matched_strings.contains(*prefix))?;
22623 let start = as_offset - last_word.len();
22624 let start = snapshot.anchor_before(start);
22625 let range = start..buffer_position;
22626 let lsp_start = to_lsp(&start);
22627 let lsp_range = lsp::Range {
22628 start: lsp_start,
22629 end: lsp_end,
22630 };
22631 Some(Completion {
22632 replace_range: range,
22633 new_text: snippet.body.clone(),
22634 source: CompletionSource::Lsp {
22635 insert_range: None,
22636 server_id: LanguageServerId(usize::MAX),
22637 resolved: true,
22638 lsp_completion: Box::new(lsp::CompletionItem {
22639 label: snippet.prefix.first().unwrap().clone(),
22640 kind: Some(CompletionItemKind::SNIPPET),
22641 label_details: snippet.description.as_ref().map(|description| {
22642 lsp::CompletionItemLabelDetails {
22643 detail: Some(description.clone()),
22644 description: None,
22645 }
22646 }),
22647 insert_text_format: Some(InsertTextFormat::SNIPPET),
22648 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22649 lsp::InsertReplaceEdit {
22650 new_text: snippet.body.clone(),
22651 insert: lsp_range,
22652 replace: lsp_range,
22653 },
22654 )),
22655 filter_text: Some(snippet.body.clone()),
22656 sort_text: Some(char::MAX.to_string()),
22657 ..lsp::CompletionItem::default()
22658 }),
22659 lsp_defaults: None,
22660 },
22661 label: CodeLabel {
22662 text: matching_prefix.clone(),
22663 runs: Vec::new(),
22664 filter_range: 0..matching_prefix.len(),
22665 },
22666 icon_path: None,
22667 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22668 single_line: snippet.name.clone().into(),
22669 plain_text: snippet
22670 .description
22671 .clone()
22672 .map(|description| description.into()),
22673 }),
22674 insert_text_mode: None,
22675 confirm: None,
22676 })
22677 }))
22678 }
22679
22680 Ok(CompletionResponse {
22681 completions,
22682 display_options: CompletionDisplayOptions::default(),
22683 is_incomplete,
22684 })
22685 })
22686}
22687
22688impl CompletionProvider for Entity<Project> {
22689 fn completions(
22690 &self,
22691 _excerpt_id: ExcerptId,
22692 buffer: &Entity<Buffer>,
22693 buffer_position: text::Anchor,
22694 options: CompletionContext,
22695 _window: &mut Window,
22696 cx: &mut Context<Editor>,
22697 ) -> Task<Result<Vec<CompletionResponse>>> {
22698 self.update(cx, |project, cx| {
22699 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22700 let project_completions = project.completions(buffer, buffer_position, options, cx);
22701 cx.background_spawn(async move {
22702 let mut responses = project_completions.await?;
22703 let snippets = snippets.await?;
22704 if !snippets.completions.is_empty() {
22705 responses.push(snippets);
22706 }
22707 Ok(responses)
22708 })
22709 })
22710 }
22711
22712 fn resolve_completions(
22713 &self,
22714 buffer: Entity<Buffer>,
22715 completion_indices: Vec<usize>,
22716 completions: Rc<RefCell<Box<[Completion]>>>,
22717 cx: &mut Context<Editor>,
22718 ) -> Task<Result<bool>> {
22719 self.update(cx, |project, cx| {
22720 project.lsp_store().update(cx, |lsp_store, cx| {
22721 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22722 })
22723 })
22724 }
22725
22726 fn apply_additional_edits_for_completion(
22727 &self,
22728 buffer: Entity<Buffer>,
22729 completions: Rc<RefCell<Box<[Completion]>>>,
22730 completion_index: usize,
22731 push_to_history: bool,
22732 cx: &mut Context<Editor>,
22733 ) -> Task<Result<Option<language::Transaction>>> {
22734 self.update(cx, |project, cx| {
22735 project.lsp_store().update(cx, |lsp_store, cx| {
22736 lsp_store.apply_additional_edits_for_completion(
22737 buffer,
22738 completions,
22739 completion_index,
22740 push_to_history,
22741 cx,
22742 )
22743 })
22744 })
22745 }
22746
22747 fn is_completion_trigger(
22748 &self,
22749 buffer: &Entity<Buffer>,
22750 position: language::Anchor,
22751 text: &str,
22752 trigger_in_words: bool,
22753 menu_is_open: bool,
22754 cx: &mut Context<Editor>,
22755 ) -> bool {
22756 let mut chars = text.chars();
22757 let char = if let Some(char) = chars.next() {
22758 char
22759 } else {
22760 return false;
22761 };
22762 if chars.next().is_some() {
22763 return false;
22764 }
22765
22766 let buffer = buffer.read(cx);
22767 let snapshot = buffer.snapshot();
22768 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22769 return false;
22770 }
22771 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22772 if trigger_in_words && classifier.is_word(char) {
22773 return true;
22774 }
22775
22776 buffer.completion_triggers().contains(text)
22777 }
22778}
22779
22780impl SemanticsProvider for Entity<Project> {
22781 fn hover(
22782 &self,
22783 buffer: &Entity<Buffer>,
22784 position: text::Anchor,
22785 cx: &mut App,
22786 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22787 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22788 }
22789
22790 fn document_highlights(
22791 &self,
22792 buffer: &Entity<Buffer>,
22793 position: text::Anchor,
22794 cx: &mut App,
22795 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22796 Some(self.update(cx, |project, cx| {
22797 project.document_highlights(buffer, position, cx)
22798 }))
22799 }
22800
22801 fn definitions(
22802 &self,
22803 buffer: &Entity<Buffer>,
22804 position: text::Anchor,
22805 kind: GotoDefinitionKind,
22806 cx: &mut App,
22807 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22808 Some(self.update(cx, |project, cx| match kind {
22809 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22810 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22811 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22812 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22813 }))
22814 }
22815
22816 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22817 self.update(cx, |project, cx| {
22818 if project
22819 .active_debug_session(cx)
22820 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22821 {
22822 return true;
22823 }
22824
22825 buffer.update(cx, |buffer, cx| {
22826 project.any_language_server_supports_inlay_hints(buffer, cx)
22827 })
22828 })
22829 }
22830
22831 fn inline_values(
22832 &self,
22833 buffer_handle: Entity<Buffer>,
22834 range: Range<text::Anchor>,
22835 cx: &mut App,
22836 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22837 self.update(cx, |project, cx| {
22838 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22839
22840 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22841 })
22842 }
22843
22844 fn inlay_hints(
22845 &self,
22846 buffer_handle: Entity<Buffer>,
22847 range: Range<text::Anchor>,
22848 cx: &mut App,
22849 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22850 Some(self.update(cx, |project, cx| {
22851 project.inlay_hints(buffer_handle, range, cx)
22852 }))
22853 }
22854
22855 fn resolve_inlay_hint(
22856 &self,
22857 hint: InlayHint,
22858 buffer_handle: Entity<Buffer>,
22859 server_id: LanguageServerId,
22860 cx: &mut App,
22861 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22862 Some(self.update(cx, |project, cx| {
22863 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22864 }))
22865 }
22866
22867 fn range_for_rename(
22868 &self,
22869 buffer: &Entity<Buffer>,
22870 position: text::Anchor,
22871 cx: &mut App,
22872 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22873 Some(self.update(cx, |project, cx| {
22874 let buffer = buffer.clone();
22875 let task = project.prepare_rename(buffer.clone(), position, cx);
22876 cx.spawn(async move |_, cx| {
22877 Ok(match task.await? {
22878 PrepareRenameResponse::Success(range) => Some(range),
22879 PrepareRenameResponse::InvalidPosition => None,
22880 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22881 // Fallback on using TreeSitter info to determine identifier range
22882 buffer.read_with(cx, |buffer, _| {
22883 let snapshot = buffer.snapshot();
22884 let (range, kind) = snapshot.surrounding_word(position, false);
22885 if kind != Some(CharKind::Word) {
22886 return None;
22887 }
22888 Some(
22889 snapshot.anchor_before(range.start)
22890 ..snapshot.anchor_after(range.end),
22891 )
22892 })?
22893 }
22894 })
22895 })
22896 }))
22897 }
22898
22899 fn perform_rename(
22900 &self,
22901 buffer: &Entity<Buffer>,
22902 position: text::Anchor,
22903 new_name: String,
22904 cx: &mut App,
22905 ) -> Option<Task<Result<ProjectTransaction>>> {
22906 Some(self.update(cx, |project, cx| {
22907 project.perform_rename(buffer.clone(), position, new_name, cx)
22908 }))
22909 }
22910}
22911
22912fn inlay_hint_settings(
22913 location: Anchor,
22914 snapshot: &MultiBufferSnapshot,
22915 cx: &mut Context<Editor>,
22916) -> InlayHintSettings {
22917 let file = snapshot.file_at(location);
22918 let language = snapshot.language_at(location).map(|l| l.name());
22919 language_settings(language, file, cx).inlay_hints
22920}
22921
22922fn consume_contiguous_rows(
22923 contiguous_row_selections: &mut Vec<Selection<Point>>,
22924 selection: &Selection<Point>,
22925 display_map: &DisplaySnapshot,
22926 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22927) -> (MultiBufferRow, MultiBufferRow) {
22928 contiguous_row_selections.push(selection.clone());
22929 let start_row = starting_row(selection, display_map);
22930 let mut end_row = ending_row(selection, display_map);
22931
22932 while let Some(next_selection) = selections.peek() {
22933 if next_selection.start.row <= end_row.0 {
22934 end_row = ending_row(next_selection, display_map);
22935 contiguous_row_selections.push(selections.next().unwrap().clone());
22936 } else {
22937 break;
22938 }
22939 }
22940 (start_row, end_row)
22941}
22942
22943fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22944 if selection.start.column > 0 {
22945 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22946 } else {
22947 MultiBufferRow(selection.start.row)
22948 }
22949}
22950
22951fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22952 if next_selection.end.column > 0 || next_selection.is_empty() {
22953 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22954 } else {
22955 MultiBufferRow(next_selection.end.row)
22956 }
22957}
22958
22959impl EditorSnapshot {
22960 pub fn remote_selections_in_range<'a>(
22961 &'a self,
22962 range: &'a Range<Anchor>,
22963 collaboration_hub: &dyn CollaborationHub,
22964 cx: &'a App,
22965 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22966 let participant_names = collaboration_hub.user_names(cx);
22967 let participant_indices = collaboration_hub.user_participant_indices(cx);
22968 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22969 let collaborators_by_replica_id = collaborators_by_peer_id
22970 .values()
22971 .map(|collaborator| (collaborator.replica_id, collaborator))
22972 .collect::<HashMap<_, _>>();
22973 self.buffer_snapshot
22974 .selections_in_range(range, false)
22975 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22976 if replica_id == AGENT_REPLICA_ID {
22977 Some(RemoteSelection {
22978 replica_id,
22979 selection,
22980 cursor_shape,
22981 line_mode,
22982 collaborator_id: CollaboratorId::Agent,
22983 user_name: Some("Agent".into()),
22984 color: cx.theme().players().agent(),
22985 })
22986 } else {
22987 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22988 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22989 let user_name = participant_names.get(&collaborator.user_id).cloned();
22990 Some(RemoteSelection {
22991 replica_id,
22992 selection,
22993 cursor_shape,
22994 line_mode,
22995 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22996 user_name,
22997 color: if let Some(index) = participant_index {
22998 cx.theme().players().color_for_participant(index.0)
22999 } else {
23000 cx.theme().players().absent()
23001 },
23002 })
23003 }
23004 })
23005 }
23006
23007 pub fn hunks_for_ranges(
23008 &self,
23009 ranges: impl IntoIterator<Item = Range<Point>>,
23010 ) -> Vec<MultiBufferDiffHunk> {
23011 let mut hunks = Vec::new();
23012 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23013 HashMap::default();
23014 for query_range in ranges {
23015 let query_rows =
23016 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23017 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23018 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23019 ) {
23020 // Include deleted hunks that are adjacent to the query range, because
23021 // otherwise they would be missed.
23022 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23023 if hunk.status().is_deleted() {
23024 intersects_range |= hunk.row_range.start == query_rows.end;
23025 intersects_range |= hunk.row_range.end == query_rows.start;
23026 }
23027 if intersects_range {
23028 if !processed_buffer_rows
23029 .entry(hunk.buffer_id)
23030 .or_default()
23031 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23032 {
23033 continue;
23034 }
23035 hunks.push(hunk);
23036 }
23037 }
23038 }
23039
23040 hunks
23041 }
23042
23043 fn display_diff_hunks_for_rows<'a>(
23044 &'a self,
23045 display_rows: Range<DisplayRow>,
23046 folded_buffers: &'a HashSet<BufferId>,
23047 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23048 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23049 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23050
23051 self.buffer_snapshot
23052 .diff_hunks_in_range(buffer_start..buffer_end)
23053 .filter_map(|hunk| {
23054 if folded_buffers.contains(&hunk.buffer_id) {
23055 return None;
23056 }
23057
23058 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23059 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23060
23061 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23062 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23063
23064 let display_hunk = if hunk_display_start.column() != 0 {
23065 DisplayDiffHunk::Folded {
23066 display_row: hunk_display_start.row(),
23067 }
23068 } else {
23069 let mut end_row = hunk_display_end.row();
23070 if hunk_display_end.column() > 0 {
23071 end_row.0 += 1;
23072 }
23073 let is_created_file = hunk.is_created_file();
23074 DisplayDiffHunk::Unfolded {
23075 status: hunk.status(),
23076 diff_base_byte_range: hunk.diff_base_byte_range,
23077 display_row_range: hunk_display_start.row()..end_row,
23078 multi_buffer_range: Anchor::range_in_buffer(
23079 hunk.excerpt_id,
23080 hunk.buffer_id,
23081 hunk.buffer_range,
23082 ),
23083 is_created_file,
23084 }
23085 };
23086
23087 Some(display_hunk)
23088 })
23089 }
23090
23091 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23092 self.display_snapshot.buffer_snapshot.language_at(position)
23093 }
23094
23095 pub fn is_focused(&self) -> bool {
23096 self.is_focused
23097 }
23098
23099 pub fn placeholder_text(&self) -> Option<String> {
23100 self.placeholder_display_snapshot
23101 .as_ref()
23102 .map(|display_map| display_map.text())
23103 }
23104
23105 pub fn scroll_position(&self) -> gpui::Point<f32> {
23106 self.scroll_anchor.scroll_position(&self.display_snapshot)
23107 }
23108
23109 fn gutter_dimensions(
23110 &self,
23111 font_id: FontId,
23112 font_size: Pixels,
23113 max_line_number_width: Pixels,
23114 cx: &App,
23115 ) -> Option<GutterDimensions> {
23116 if !self.show_gutter {
23117 return None;
23118 }
23119
23120 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23121 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23122
23123 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23124 matches!(
23125 ProjectSettings::get_global(cx).git.git_gutter,
23126 Some(GitGutterSetting::TrackedFiles)
23127 )
23128 });
23129 let gutter_settings = EditorSettings::get_global(cx).gutter;
23130 let show_line_numbers = self
23131 .show_line_numbers
23132 .unwrap_or(gutter_settings.line_numbers);
23133 let line_gutter_width = if show_line_numbers {
23134 // Avoid flicker-like gutter resizes when the line number gains another digit by
23135 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23136 let min_width_for_number_on_gutter =
23137 ch_advance * gutter_settings.min_line_number_digits as f32;
23138 max_line_number_width.max(min_width_for_number_on_gutter)
23139 } else {
23140 0.0.into()
23141 };
23142
23143 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23144 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23145
23146 let git_blame_entries_width =
23147 self.git_blame_gutter_max_author_length
23148 .map(|max_author_length| {
23149 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23150 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23151
23152 /// The number of characters to dedicate to gaps and margins.
23153 const SPACING_WIDTH: usize = 4;
23154
23155 let max_char_count = max_author_length.min(renderer.max_author_length())
23156 + ::git::SHORT_SHA_LENGTH
23157 + MAX_RELATIVE_TIMESTAMP.len()
23158 + SPACING_WIDTH;
23159
23160 ch_advance * max_char_count
23161 });
23162
23163 let is_singleton = self.buffer_snapshot.is_singleton();
23164
23165 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23166 left_padding += if !is_singleton {
23167 ch_width * 4.0
23168 } else if show_runnables || show_breakpoints {
23169 ch_width * 3.0
23170 } else if show_git_gutter && show_line_numbers {
23171 ch_width * 2.0
23172 } else if show_git_gutter || show_line_numbers {
23173 ch_width
23174 } else {
23175 px(0.)
23176 };
23177
23178 let shows_folds = is_singleton && gutter_settings.folds;
23179
23180 let right_padding = if shows_folds && show_line_numbers {
23181 ch_width * 4.0
23182 } else if shows_folds || (!is_singleton && show_line_numbers) {
23183 ch_width * 3.0
23184 } else if show_line_numbers {
23185 ch_width
23186 } else {
23187 px(0.)
23188 };
23189
23190 Some(GutterDimensions {
23191 left_padding,
23192 right_padding,
23193 width: line_gutter_width + left_padding + right_padding,
23194 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23195 git_blame_entries_width,
23196 })
23197 }
23198
23199 pub fn render_crease_toggle(
23200 &self,
23201 buffer_row: MultiBufferRow,
23202 row_contains_cursor: bool,
23203 editor: Entity<Editor>,
23204 window: &mut Window,
23205 cx: &mut App,
23206 ) -> Option<AnyElement> {
23207 let folded = self.is_line_folded(buffer_row);
23208 let mut is_foldable = false;
23209
23210 if let Some(crease) = self
23211 .crease_snapshot
23212 .query_row(buffer_row, &self.buffer_snapshot)
23213 {
23214 is_foldable = true;
23215 match crease {
23216 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23217 if let Some(render_toggle) = render_toggle {
23218 let toggle_callback =
23219 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23220 if folded {
23221 editor.update(cx, |editor, cx| {
23222 editor.fold_at(buffer_row, window, cx)
23223 });
23224 } else {
23225 editor.update(cx, |editor, cx| {
23226 editor.unfold_at(buffer_row, window, cx)
23227 });
23228 }
23229 });
23230 return Some((render_toggle)(
23231 buffer_row,
23232 folded,
23233 toggle_callback,
23234 window,
23235 cx,
23236 ));
23237 }
23238 }
23239 }
23240 }
23241
23242 is_foldable |= self.starts_indent(buffer_row);
23243
23244 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23245 Some(
23246 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23247 .toggle_state(folded)
23248 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23249 if folded {
23250 this.unfold_at(buffer_row, window, cx);
23251 } else {
23252 this.fold_at(buffer_row, window, cx);
23253 }
23254 }))
23255 .into_any_element(),
23256 )
23257 } else {
23258 None
23259 }
23260 }
23261
23262 pub fn render_crease_trailer(
23263 &self,
23264 buffer_row: MultiBufferRow,
23265 window: &mut Window,
23266 cx: &mut App,
23267 ) -> Option<AnyElement> {
23268 let folded = self.is_line_folded(buffer_row);
23269 if let Crease::Inline { render_trailer, .. } = self
23270 .crease_snapshot
23271 .query_row(buffer_row, &self.buffer_snapshot)?
23272 {
23273 let render_trailer = render_trailer.as_ref()?;
23274 Some(render_trailer(buffer_row, folded, window, cx))
23275 } else {
23276 None
23277 }
23278 }
23279}
23280
23281impl Deref for EditorSnapshot {
23282 type Target = DisplaySnapshot;
23283
23284 fn deref(&self) -> &Self::Target {
23285 &self.display_snapshot
23286 }
23287}
23288
23289#[derive(Clone, Debug, PartialEq, Eq)]
23290pub enum EditorEvent {
23291 InputIgnored {
23292 text: Arc<str>,
23293 },
23294 InputHandled {
23295 utf16_range_to_replace: Option<Range<isize>>,
23296 text: Arc<str>,
23297 },
23298 ExcerptsAdded {
23299 buffer: Entity<Buffer>,
23300 predecessor: ExcerptId,
23301 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23302 },
23303 ExcerptsRemoved {
23304 ids: Vec<ExcerptId>,
23305 removed_buffer_ids: Vec<BufferId>,
23306 },
23307 BufferFoldToggled {
23308 ids: Vec<ExcerptId>,
23309 folded: bool,
23310 },
23311 ExcerptsEdited {
23312 ids: Vec<ExcerptId>,
23313 },
23314 ExcerptsExpanded {
23315 ids: Vec<ExcerptId>,
23316 },
23317 BufferEdited,
23318 Edited {
23319 transaction_id: clock::Lamport,
23320 },
23321 Reparsed(BufferId),
23322 Focused,
23323 FocusedIn,
23324 Blurred,
23325 DirtyChanged,
23326 Saved,
23327 TitleChanged,
23328 SelectionsChanged {
23329 local: bool,
23330 },
23331 ScrollPositionChanged {
23332 local: bool,
23333 autoscroll: bool,
23334 },
23335 TransactionUndone {
23336 transaction_id: clock::Lamport,
23337 },
23338 TransactionBegun {
23339 transaction_id: clock::Lamport,
23340 },
23341 CursorShapeChanged,
23342 BreadcrumbsChanged,
23343 PushedToNavHistory {
23344 anchor: Anchor,
23345 is_deactivate: bool,
23346 },
23347}
23348
23349impl EventEmitter<EditorEvent> for Editor {}
23350
23351impl Focusable for Editor {
23352 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23353 self.focus_handle.clone()
23354 }
23355}
23356
23357impl Render for Editor {
23358 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23359 let settings = ThemeSettings::get_global(cx);
23360
23361 let mut text_style = match self.mode {
23362 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23363 color: cx.theme().colors().editor_foreground,
23364 font_family: settings.ui_font.family.clone(),
23365 font_features: settings.ui_font.features.clone(),
23366 font_fallbacks: settings.ui_font.fallbacks.clone(),
23367 font_size: rems(0.875).into(),
23368 font_weight: settings.ui_font.weight,
23369 line_height: relative(settings.buffer_line_height.value()),
23370 ..Default::default()
23371 },
23372 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23373 color: cx.theme().colors().editor_foreground,
23374 font_family: settings.buffer_font.family.clone(),
23375 font_features: settings.buffer_font.features.clone(),
23376 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23377 font_size: settings.buffer_font_size(cx).into(),
23378 font_weight: settings.buffer_font.weight,
23379 line_height: relative(settings.buffer_line_height.value()),
23380 ..Default::default()
23381 },
23382 };
23383 if let Some(text_style_refinement) = &self.text_style_refinement {
23384 text_style.refine(text_style_refinement)
23385 }
23386
23387 let background = match self.mode {
23388 EditorMode::SingleLine => cx.theme().system().transparent,
23389 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23390 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23391 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23392 };
23393
23394 EditorElement::new(
23395 &cx.entity(),
23396 EditorStyle {
23397 background,
23398 border: cx.theme().colors().border,
23399 local_player: cx.theme().players().local(),
23400 text: text_style,
23401 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23402 syntax: cx.theme().syntax().clone(),
23403 status: cx.theme().status().clone(),
23404 inlay_hints_style: make_inlay_hints_style(cx),
23405 edit_prediction_styles: make_suggestion_styles(cx),
23406 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23407 show_underlines: self.diagnostics_enabled(),
23408 },
23409 )
23410 }
23411}
23412
23413impl EntityInputHandler for Editor {
23414 fn text_for_range(
23415 &mut self,
23416 range_utf16: Range<usize>,
23417 adjusted_range: &mut Option<Range<usize>>,
23418 _: &mut Window,
23419 cx: &mut Context<Self>,
23420 ) -> Option<String> {
23421 let snapshot = self.buffer.read(cx).read(cx);
23422 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23423 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23424 if (start.0..end.0) != range_utf16 {
23425 adjusted_range.replace(start.0..end.0);
23426 }
23427 Some(snapshot.text_for_range(start..end).collect())
23428 }
23429
23430 fn selected_text_range(
23431 &mut self,
23432 ignore_disabled_input: bool,
23433 _: &mut Window,
23434 cx: &mut Context<Self>,
23435 ) -> Option<UTF16Selection> {
23436 // Prevent the IME menu from appearing when holding down an alphabetic key
23437 // while input is disabled.
23438 if !ignore_disabled_input && !self.input_enabled {
23439 return None;
23440 }
23441
23442 let selection = self.selections.newest::<OffsetUtf16>(cx);
23443 let range = selection.range();
23444
23445 Some(UTF16Selection {
23446 range: range.start.0..range.end.0,
23447 reversed: selection.reversed,
23448 })
23449 }
23450
23451 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23452 let snapshot = self.buffer.read(cx).read(cx);
23453 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23454 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23455 }
23456
23457 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23458 self.clear_highlights::<InputComposition>(cx);
23459 self.ime_transaction.take();
23460 }
23461
23462 fn replace_text_in_range(
23463 &mut self,
23464 range_utf16: Option<Range<usize>>,
23465 text: &str,
23466 window: &mut Window,
23467 cx: &mut Context<Self>,
23468 ) {
23469 if !self.input_enabled {
23470 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23471 return;
23472 }
23473
23474 self.transact(window, cx, |this, window, cx| {
23475 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23476 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23477 Some(this.selection_replacement_ranges(range_utf16, cx))
23478 } else {
23479 this.marked_text_ranges(cx)
23480 };
23481
23482 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23483 let newest_selection_id = this.selections.newest_anchor().id;
23484 this.selections
23485 .all::<OffsetUtf16>(cx)
23486 .iter()
23487 .zip(ranges_to_replace.iter())
23488 .find_map(|(selection, range)| {
23489 if selection.id == newest_selection_id {
23490 Some(
23491 (range.start.0 as isize - selection.head().0 as isize)
23492 ..(range.end.0 as isize - selection.head().0 as isize),
23493 )
23494 } else {
23495 None
23496 }
23497 })
23498 });
23499
23500 cx.emit(EditorEvent::InputHandled {
23501 utf16_range_to_replace: range_to_replace,
23502 text: text.into(),
23503 });
23504
23505 if let Some(new_selected_ranges) = new_selected_ranges {
23506 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23507 selections.select_ranges(new_selected_ranges)
23508 });
23509 this.backspace(&Default::default(), window, cx);
23510 }
23511
23512 this.handle_input(text, window, cx);
23513 });
23514
23515 if let Some(transaction) = self.ime_transaction {
23516 self.buffer.update(cx, |buffer, cx| {
23517 buffer.group_until_transaction(transaction, cx);
23518 });
23519 }
23520
23521 self.unmark_text(window, cx);
23522 }
23523
23524 fn replace_and_mark_text_in_range(
23525 &mut self,
23526 range_utf16: Option<Range<usize>>,
23527 text: &str,
23528 new_selected_range_utf16: Option<Range<usize>>,
23529 window: &mut Window,
23530 cx: &mut Context<Self>,
23531 ) {
23532 if !self.input_enabled {
23533 return;
23534 }
23535
23536 let transaction = self.transact(window, cx, |this, window, cx| {
23537 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23538 let snapshot = this.buffer.read(cx).read(cx);
23539 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23540 for marked_range in &mut marked_ranges {
23541 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23542 marked_range.start.0 += relative_range_utf16.start;
23543 marked_range.start =
23544 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23545 marked_range.end =
23546 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23547 }
23548 }
23549 Some(marked_ranges)
23550 } else if let Some(range_utf16) = range_utf16 {
23551 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23552 Some(this.selection_replacement_ranges(range_utf16, cx))
23553 } else {
23554 None
23555 };
23556
23557 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23558 let newest_selection_id = this.selections.newest_anchor().id;
23559 this.selections
23560 .all::<OffsetUtf16>(cx)
23561 .iter()
23562 .zip(ranges_to_replace.iter())
23563 .find_map(|(selection, range)| {
23564 if selection.id == newest_selection_id {
23565 Some(
23566 (range.start.0 as isize - selection.head().0 as isize)
23567 ..(range.end.0 as isize - selection.head().0 as isize),
23568 )
23569 } else {
23570 None
23571 }
23572 })
23573 });
23574
23575 cx.emit(EditorEvent::InputHandled {
23576 utf16_range_to_replace: range_to_replace,
23577 text: text.into(),
23578 });
23579
23580 if let Some(ranges) = ranges_to_replace {
23581 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23582 s.select_ranges(ranges)
23583 });
23584 }
23585
23586 let marked_ranges = {
23587 let snapshot = this.buffer.read(cx).read(cx);
23588 this.selections
23589 .disjoint_anchors_arc()
23590 .iter()
23591 .map(|selection| {
23592 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23593 })
23594 .collect::<Vec<_>>()
23595 };
23596
23597 if text.is_empty() {
23598 this.unmark_text(window, cx);
23599 } else {
23600 this.highlight_text::<InputComposition>(
23601 marked_ranges.clone(),
23602 HighlightStyle {
23603 underline: Some(UnderlineStyle {
23604 thickness: px(1.),
23605 color: None,
23606 wavy: false,
23607 }),
23608 ..Default::default()
23609 },
23610 cx,
23611 );
23612 }
23613
23614 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23615 let use_autoclose = this.use_autoclose;
23616 let use_auto_surround = this.use_auto_surround;
23617 this.set_use_autoclose(false);
23618 this.set_use_auto_surround(false);
23619 this.handle_input(text, window, cx);
23620 this.set_use_autoclose(use_autoclose);
23621 this.set_use_auto_surround(use_auto_surround);
23622
23623 if let Some(new_selected_range) = new_selected_range_utf16 {
23624 let snapshot = this.buffer.read(cx).read(cx);
23625 let new_selected_ranges = marked_ranges
23626 .into_iter()
23627 .map(|marked_range| {
23628 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23629 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23630 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23631 snapshot.clip_offset_utf16(new_start, Bias::Left)
23632 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23633 })
23634 .collect::<Vec<_>>();
23635
23636 drop(snapshot);
23637 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23638 selections.select_ranges(new_selected_ranges)
23639 });
23640 }
23641 });
23642
23643 self.ime_transaction = self.ime_transaction.or(transaction);
23644 if let Some(transaction) = self.ime_transaction {
23645 self.buffer.update(cx, |buffer, cx| {
23646 buffer.group_until_transaction(transaction, cx);
23647 });
23648 }
23649
23650 if self.text_highlights::<InputComposition>(cx).is_none() {
23651 self.ime_transaction.take();
23652 }
23653 }
23654
23655 fn bounds_for_range(
23656 &mut self,
23657 range_utf16: Range<usize>,
23658 element_bounds: gpui::Bounds<Pixels>,
23659 window: &mut Window,
23660 cx: &mut Context<Self>,
23661 ) -> Option<gpui::Bounds<Pixels>> {
23662 let text_layout_details = self.text_layout_details(window);
23663 let CharacterDimensions {
23664 em_width,
23665 em_advance,
23666 line_height,
23667 } = self.character_dimensions(window);
23668
23669 let snapshot = self.snapshot(window, cx);
23670 let scroll_position = snapshot.scroll_position();
23671 let scroll_left = scroll_position.x * em_advance;
23672
23673 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23674 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23675 + self.gutter_dimensions.full_width();
23676 let y = line_height * (start.row().as_f32() - scroll_position.y);
23677
23678 Some(Bounds {
23679 origin: element_bounds.origin + point(x, y),
23680 size: size(em_width, line_height),
23681 })
23682 }
23683
23684 fn character_index_for_point(
23685 &mut self,
23686 point: gpui::Point<Pixels>,
23687 _window: &mut Window,
23688 _cx: &mut Context<Self>,
23689 ) -> Option<usize> {
23690 let position_map = self.last_position_map.as_ref()?;
23691 if !position_map.text_hitbox.contains(&point) {
23692 return None;
23693 }
23694 let display_point = position_map.point_for_position(point).previous_valid;
23695 let anchor = position_map
23696 .snapshot
23697 .display_point_to_anchor(display_point, Bias::Left);
23698 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23699 Some(utf16_offset.0)
23700 }
23701}
23702
23703trait SelectionExt {
23704 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23705 fn spanned_rows(
23706 &self,
23707 include_end_if_at_line_start: bool,
23708 map: &DisplaySnapshot,
23709 ) -> Range<MultiBufferRow>;
23710}
23711
23712impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23713 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23714 let start = self
23715 .start
23716 .to_point(&map.buffer_snapshot)
23717 .to_display_point(map);
23718 let end = self
23719 .end
23720 .to_point(&map.buffer_snapshot)
23721 .to_display_point(map);
23722 if self.reversed {
23723 end..start
23724 } else {
23725 start..end
23726 }
23727 }
23728
23729 fn spanned_rows(
23730 &self,
23731 include_end_if_at_line_start: bool,
23732 map: &DisplaySnapshot,
23733 ) -> Range<MultiBufferRow> {
23734 let start = self.start.to_point(&map.buffer_snapshot);
23735 let mut end = self.end.to_point(&map.buffer_snapshot);
23736 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23737 end.row -= 1;
23738 }
23739
23740 let buffer_start = map.prev_line_boundary(start).0;
23741 let buffer_end = map.next_line_boundary(end).0;
23742 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23743 }
23744}
23745
23746impl<T: InvalidationRegion> InvalidationStack<T> {
23747 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23748 where
23749 S: Clone + ToOffset,
23750 {
23751 while let Some(region) = self.last() {
23752 let all_selections_inside_invalidation_ranges =
23753 if selections.len() == region.ranges().len() {
23754 selections
23755 .iter()
23756 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23757 .all(|(selection, invalidation_range)| {
23758 let head = selection.head().to_offset(buffer);
23759 invalidation_range.start <= head && invalidation_range.end >= head
23760 })
23761 } else {
23762 false
23763 };
23764
23765 if all_selections_inside_invalidation_ranges {
23766 break;
23767 } else {
23768 self.pop();
23769 }
23770 }
23771 }
23772}
23773
23774impl<T> Default for InvalidationStack<T> {
23775 fn default() -> Self {
23776 Self(Default::default())
23777 }
23778}
23779
23780impl<T> Deref for InvalidationStack<T> {
23781 type Target = Vec<T>;
23782
23783 fn deref(&self) -> &Self::Target {
23784 &self.0
23785 }
23786}
23787
23788impl<T> DerefMut for InvalidationStack<T> {
23789 fn deref_mut(&mut self) -> &mut Self::Target {
23790 &mut self.0
23791 }
23792}
23793
23794impl InvalidationRegion for SnippetState {
23795 fn ranges(&self) -> &[Range<Anchor>] {
23796 &self.ranges[self.active_index]
23797 }
23798}
23799
23800fn edit_prediction_edit_text(
23801 current_snapshot: &BufferSnapshot,
23802 edits: &[(Range<Anchor>, String)],
23803 edit_preview: &EditPreview,
23804 include_deletions: bool,
23805 cx: &App,
23806) -> HighlightedText {
23807 let edits = edits
23808 .iter()
23809 .map(|(anchor, text)| {
23810 (
23811 anchor.start.text_anchor..anchor.end.text_anchor,
23812 text.clone(),
23813 )
23814 })
23815 .collect::<Vec<_>>();
23816
23817 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23818}
23819
23820fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23821 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23822 // Just show the raw edit text with basic styling
23823 let mut text = String::new();
23824 let mut highlights = Vec::new();
23825
23826 let insertion_highlight_style = HighlightStyle {
23827 color: Some(cx.theme().colors().text),
23828 ..Default::default()
23829 };
23830
23831 for (_, edit_text) in edits {
23832 let start_offset = text.len();
23833 text.push_str(edit_text);
23834 let end_offset = text.len();
23835
23836 if start_offset < end_offset {
23837 highlights.push((start_offset..end_offset, insertion_highlight_style));
23838 }
23839 }
23840
23841 HighlightedText {
23842 text: text.into(),
23843 highlights,
23844 }
23845}
23846
23847pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23848 match severity {
23849 lsp::DiagnosticSeverity::ERROR => colors.error,
23850 lsp::DiagnosticSeverity::WARNING => colors.warning,
23851 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23852 lsp::DiagnosticSeverity::HINT => colors.info,
23853 _ => colors.ignored,
23854 }
23855}
23856
23857pub fn styled_runs_for_code_label<'a>(
23858 label: &'a CodeLabel,
23859 syntax_theme: &'a theme::SyntaxTheme,
23860) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23861 let fade_out = HighlightStyle {
23862 fade_out: Some(0.35),
23863 ..Default::default()
23864 };
23865
23866 let mut prev_end = label.filter_range.end;
23867 label
23868 .runs
23869 .iter()
23870 .enumerate()
23871 .flat_map(move |(ix, (range, highlight_id))| {
23872 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23873 style
23874 } else {
23875 return Default::default();
23876 };
23877 let muted_style = style.highlight(fade_out);
23878
23879 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23880 if range.start >= label.filter_range.end {
23881 if range.start > prev_end {
23882 runs.push((prev_end..range.start, fade_out));
23883 }
23884 runs.push((range.clone(), muted_style));
23885 } else if range.end <= label.filter_range.end {
23886 runs.push((range.clone(), style));
23887 } else {
23888 runs.push((range.start..label.filter_range.end, style));
23889 runs.push((label.filter_range.end..range.end, muted_style));
23890 }
23891 prev_end = cmp::max(prev_end, range.end);
23892
23893 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23894 runs.push((prev_end..label.text.len(), fade_out));
23895 }
23896
23897 runs
23898 })
23899}
23900
23901pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23902 let mut prev_index = 0;
23903 let mut prev_codepoint: Option<char> = None;
23904 text.char_indices()
23905 .chain([(text.len(), '\0')])
23906 .filter_map(move |(index, codepoint)| {
23907 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23908 let is_boundary = index == text.len()
23909 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23910 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23911 if is_boundary {
23912 let chunk = &text[prev_index..index];
23913 prev_index = index;
23914 Some(chunk)
23915 } else {
23916 None
23917 }
23918 })
23919}
23920
23921pub trait RangeToAnchorExt: Sized {
23922 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23923
23924 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23925 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23926 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23927 }
23928}
23929
23930impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23931 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23932 let start_offset = self.start.to_offset(snapshot);
23933 let end_offset = self.end.to_offset(snapshot);
23934 if start_offset == end_offset {
23935 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23936 } else {
23937 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23938 }
23939 }
23940}
23941
23942pub trait RowExt {
23943 fn as_f32(&self) -> f32;
23944
23945 fn next_row(&self) -> Self;
23946
23947 fn previous_row(&self) -> Self;
23948
23949 fn minus(&self, other: Self) -> u32;
23950}
23951
23952impl RowExt for DisplayRow {
23953 fn as_f32(&self) -> f32 {
23954 self.0 as f32
23955 }
23956
23957 fn next_row(&self) -> Self {
23958 Self(self.0 + 1)
23959 }
23960
23961 fn previous_row(&self) -> Self {
23962 Self(self.0.saturating_sub(1))
23963 }
23964
23965 fn minus(&self, other: Self) -> u32 {
23966 self.0 - other.0
23967 }
23968}
23969
23970impl RowExt for MultiBufferRow {
23971 fn as_f32(&self) -> f32 {
23972 self.0 as f32
23973 }
23974
23975 fn next_row(&self) -> Self {
23976 Self(self.0 + 1)
23977 }
23978
23979 fn previous_row(&self) -> Self {
23980 Self(self.0.saturating_sub(1))
23981 }
23982
23983 fn minus(&self, other: Self) -> u32 {
23984 self.0 - other.0
23985 }
23986}
23987
23988trait RowRangeExt {
23989 type Row;
23990
23991 fn len(&self) -> usize;
23992
23993 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23994}
23995
23996impl RowRangeExt for Range<MultiBufferRow> {
23997 type Row = MultiBufferRow;
23998
23999 fn len(&self) -> usize {
24000 (self.end.0 - self.start.0) as usize
24001 }
24002
24003 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24004 (self.start.0..self.end.0).map(MultiBufferRow)
24005 }
24006}
24007
24008impl RowRangeExt for Range<DisplayRow> {
24009 type Row = DisplayRow;
24010
24011 fn len(&self) -> usize {
24012 (self.end.0 - self.start.0) as usize
24013 }
24014
24015 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24016 (self.start.0..self.end.0).map(DisplayRow)
24017 }
24018}
24019
24020/// If select range has more than one line, we
24021/// just point the cursor to range.start.
24022fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24023 if range.start.row == range.end.row {
24024 range
24025 } else {
24026 range.start..range.start
24027 }
24028}
24029pub struct KillRing(ClipboardItem);
24030impl Global for KillRing {}
24031
24032const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24033
24034enum BreakpointPromptEditAction {
24035 Log,
24036 Condition,
24037 HitCondition,
24038}
24039
24040struct BreakpointPromptEditor {
24041 pub(crate) prompt: Entity<Editor>,
24042 editor: WeakEntity<Editor>,
24043 breakpoint_anchor: Anchor,
24044 breakpoint: Breakpoint,
24045 edit_action: BreakpointPromptEditAction,
24046 block_ids: HashSet<CustomBlockId>,
24047 editor_margins: Arc<Mutex<EditorMargins>>,
24048 _subscriptions: Vec<Subscription>,
24049}
24050
24051impl BreakpointPromptEditor {
24052 const MAX_LINES: u8 = 4;
24053
24054 fn new(
24055 editor: WeakEntity<Editor>,
24056 breakpoint_anchor: Anchor,
24057 breakpoint: Breakpoint,
24058 edit_action: BreakpointPromptEditAction,
24059 window: &mut Window,
24060 cx: &mut Context<Self>,
24061 ) -> Self {
24062 let base_text = match edit_action {
24063 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24064 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24065 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24066 }
24067 .map(|msg| msg.to_string())
24068 .unwrap_or_default();
24069
24070 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24071 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24072
24073 let prompt = cx.new(|cx| {
24074 let mut prompt = Editor::new(
24075 EditorMode::AutoHeight {
24076 min_lines: 1,
24077 max_lines: Some(Self::MAX_LINES as usize),
24078 },
24079 buffer,
24080 None,
24081 window,
24082 cx,
24083 );
24084 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24085 prompt.set_show_cursor_when_unfocused(false, cx);
24086 prompt.set_placeholder_text(
24087 match edit_action {
24088 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24089 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24090 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24091 },
24092 window,
24093 cx,
24094 );
24095
24096 prompt
24097 });
24098
24099 Self {
24100 prompt,
24101 editor,
24102 breakpoint_anchor,
24103 breakpoint,
24104 edit_action,
24105 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24106 block_ids: Default::default(),
24107 _subscriptions: vec![],
24108 }
24109 }
24110
24111 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24112 self.block_ids.extend(block_ids)
24113 }
24114
24115 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24116 if let Some(editor) = self.editor.upgrade() {
24117 let message = self
24118 .prompt
24119 .read(cx)
24120 .buffer
24121 .read(cx)
24122 .as_singleton()
24123 .expect("A multi buffer in breakpoint prompt isn't possible")
24124 .read(cx)
24125 .as_rope()
24126 .to_string();
24127
24128 editor.update(cx, |editor, cx| {
24129 editor.edit_breakpoint_at_anchor(
24130 self.breakpoint_anchor,
24131 self.breakpoint.clone(),
24132 match self.edit_action {
24133 BreakpointPromptEditAction::Log => {
24134 BreakpointEditAction::EditLogMessage(message.into())
24135 }
24136 BreakpointPromptEditAction::Condition => {
24137 BreakpointEditAction::EditCondition(message.into())
24138 }
24139 BreakpointPromptEditAction::HitCondition => {
24140 BreakpointEditAction::EditHitCondition(message.into())
24141 }
24142 },
24143 cx,
24144 );
24145
24146 editor.remove_blocks(self.block_ids.clone(), None, cx);
24147 cx.focus_self(window);
24148 });
24149 }
24150 }
24151
24152 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24153 self.editor
24154 .update(cx, |editor, cx| {
24155 editor.remove_blocks(self.block_ids.clone(), None, cx);
24156 window.focus(&editor.focus_handle);
24157 })
24158 .log_err();
24159 }
24160
24161 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24162 let settings = ThemeSettings::get_global(cx);
24163 let text_style = TextStyle {
24164 color: if self.prompt.read(cx).read_only(cx) {
24165 cx.theme().colors().text_disabled
24166 } else {
24167 cx.theme().colors().text
24168 },
24169 font_family: settings.buffer_font.family.clone(),
24170 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24171 font_size: settings.buffer_font_size(cx).into(),
24172 font_weight: settings.buffer_font.weight,
24173 line_height: relative(settings.buffer_line_height.value()),
24174 ..Default::default()
24175 };
24176 EditorElement::new(
24177 &self.prompt,
24178 EditorStyle {
24179 background: cx.theme().colors().editor_background,
24180 local_player: cx.theme().players().local(),
24181 text: text_style,
24182 ..Default::default()
24183 },
24184 )
24185 }
24186}
24187
24188impl Render for BreakpointPromptEditor {
24189 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24190 let editor_margins = *self.editor_margins.lock();
24191 let gutter_dimensions = editor_margins.gutter;
24192 h_flex()
24193 .key_context("Editor")
24194 .bg(cx.theme().colors().editor_background)
24195 .border_y_1()
24196 .border_color(cx.theme().status().info_border)
24197 .size_full()
24198 .py(window.line_height() / 2.5)
24199 .on_action(cx.listener(Self::confirm))
24200 .on_action(cx.listener(Self::cancel))
24201 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24202 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24203 }
24204}
24205
24206impl Focusable for BreakpointPromptEditor {
24207 fn focus_handle(&self, cx: &App) -> FocusHandle {
24208 self.prompt.focus_handle(cx)
24209 }
24210}
24211
24212fn all_edits_insertions_or_deletions(
24213 edits: &Vec<(Range<Anchor>, String)>,
24214 snapshot: &MultiBufferSnapshot,
24215) -> bool {
24216 let mut all_insertions = true;
24217 let mut all_deletions = true;
24218
24219 for (range, new_text) in edits.iter() {
24220 let range_is_empty = range.to_offset(snapshot).is_empty();
24221 let text_is_empty = new_text.is_empty();
24222
24223 if range_is_empty != text_is_empty {
24224 if range_is_empty {
24225 all_deletions = false;
24226 } else {
24227 all_insertions = false;
24228 }
24229 } else {
24230 return false;
24231 }
24232
24233 if !all_insertions && !all_deletions {
24234 return false;
24235 }
24236 }
24237 all_insertions || all_deletions
24238}
24239
24240struct MissingEditPredictionKeybindingTooltip;
24241
24242impl Render for MissingEditPredictionKeybindingTooltip {
24243 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24244 ui::tooltip_container(window, cx, |container, _, cx| {
24245 container
24246 .flex_shrink_0()
24247 .max_w_80()
24248 .min_h(rems_from_px(124.))
24249 .justify_between()
24250 .child(
24251 v_flex()
24252 .flex_1()
24253 .text_ui_sm(cx)
24254 .child(Label::new("Conflict with Accept Keybinding"))
24255 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24256 )
24257 .child(
24258 h_flex()
24259 .pb_1()
24260 .gap_1()
24261 .items_end()
24262 .w_full()
24263 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24264 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24265 }))
24266 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24267 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24268 })),
24269 )
24270 })
24271 }
24272}
24273
24274#[derive(Debug, Clone, Copy, PartialEq)]
24275pub struct LineHighlight {
24276 pub background: Background,
24277 pub border: Option<gpui::Hsla>,
24278 pub include_gutter: bool,
24279 pub type_id: Option<TypeId>,
24280}
24281
24282struct LineManipulationResult {
24283 pub new_text: String,
24284 pub line_count_before: usize,
24285 pub line_count_after: usize,
24286}
24287
24288fn render_diff_hunk_controls(
24289 row: u32,
24290 status: &DiffHunkStatus,
24291 hunk_range: Range<Anchor>,
24292 is_created_file: bool,
24293 line_height: Pixels,
24294 editor: &Entity<Editor>,
24295 _window: &mut Window,
24296 cx: &mut App,
24297) -> AnyElement {
24298 h_flex()
24299 .h(line_height)
24300 .mr_1()
24301 .gap_1()
24302 .px_0p5()
24303 .pb_1()
24304 .border_x_1()
24305 .border_b_1()
24306 .border_color(cx.theme().colors().border_variant)
24307 .rounded_b_lg()
24308 .bg(cx.theme().colors().editor_background)
24309 .gap_1()
24310 .block_mouse_except_scroll()
24311 .shadow_md()
24312 .child(if status.has_secondary_hunk() {
24313 Button::new(("stage", row as u64), "Stage")
24314 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24315 .tooltip({
24316 let focus_handle = editor.focus_handle(cx);
24317 move |window, cx| {
24318 Tooltip::for_action_in(
24319 "Stage Hunk",
24320 &::git::ToggleStaged,
24321 &focus_handle,
24322 window,
24323 cx,
24324 )
24325 }
24326 })
24327 .on_click({
24328 let editor = editor.clone();
24329 move |_event, _window, cx| {
24330 editor.update(cx, |editor, cx| {
24331 editor.stage_or_unstage_diff_hunks(
24332 true,
24333 vec![hunk_range.start..hunk_range.start],
24334 cx,
24335 );
24336 });
24337 }
24338 })
24339 } else {
24340 Button::new(("unstage", row as u64), "Unstage")
24341 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24342 .tooltip({
24343 let focus_handle = editor.focus_handle(cx);
24344 move |window, cx| {
24345 Tooltip::for_action_in(
24346 "Unstage Hunk",
24347 &::git::ToggleStaged,
24348 &focus_handle,
24349 window,
24350 cx,
24351 )
24352 }
24353 })
24354 .on_click({
24355 let editor = editor.clone();
24356 move |_event, _window, cx| {
24357 editor.update(cx, |editor, cx| {
24358 editor.stage_or_unstage_diff_hunks(
24359 false,
24360 vec![hunk_range.start..hunk_range.start],
24361 cx,
24362 );
24363 });
24364 }
24365 })
24366 })
24367 .child(
24368 Button::new(("restore", row as u64), "Restore")
24369 .tooltip({
24370 let focus_handle = editor.focus_handle(cx);
24371 move |window, cx| {
24372 Tooltip::for_action_in(
24373 "Restore Hunk",
24374 &::git::Restore,
24375 &focus_handle,
24376 window,
24377 cx,
24378 )
24379 }
24380 })
24381 .on_click({
24382 let editor = editor.clone();
24383 move |_event, window, cx| {
24384 editor.update(cx, |editor, cx| {
24385 let snapshot = editor.snapshot(window, cx);
24386 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24387 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24388 });
24389 }
24390 })
24391 .disabled(is_created_file),
24392 )
24393 .when(
24394 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24395 |el| {
24396 el.child(
24397 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24398 .shape(IconButtonShape::Square)
24399 .icon_size(IconSize::Small)
24400 // .disabled(!has_multiple_hunks)
24401 .tooltip({
24402 let focus_handle = editor.focus_handle(cx);
24403 move |window, cx| {
24404 Tooltip::for_action_in(
24405 "Next Hunk",
24406 &GoToHunk,
24407 &focus_handle,
24408 window,
24409 cx,
24410 )
24411 }
24412 })
24413 .on_click({
24414 let editor = editor.clone();
24415 move |_event, window, cx| {
24416 editor.update(cx, |editor, cx| {
24417 let snapshot = editor.snapshot(window, cx);
24418 let position =
24419 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24420 editor.go_to_hunk_before_or_after_position(
24421 &snapshot,
24422 position,
24423 Direction::Next,
24424 window,
24425 cx,
24426 );
24427 editor.expand_selected_diff_hunks(cx);
24428 });
24429 }
24430 }),
24431 )
24432 .child(
24433 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24434 .shape(IconButtonShape::Square)
24435 .icon_size(IconSize::Small)
24436 // .disabled(!has_multiple_hunks)
24437 .tooltip({
24438 let focus_handle = editor.focus_handle(cx);
24439 move |window, cx| {
24440 Tooltip::for_action_in(
24441 "Previous Hunk",
24442 &GoToPreviousHunk,
24443 &focus_handle,
24444 window,
24445 cx,
24446 )
24447 }
24448 })
24449 .on_click({
24450 let editor = editor.clone();
24451 move |_event, window, cx| {
24452 editor.update(cx, |editor, cx| {
24453 let snapshot = editor.snapshot(window, cx);
24454 let point =
24455 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24456 editor.go_to_hunk_before_or_after_position(
24457 &snapshot,
24458 point,
24459 Direction::Prev,
24460 window,
24461 cx,
24462 );
24463 editor.expand_selected_diff_hunks(cx);
24464 });
24465 }
24466 }),
24467 )
24468 },
24469 )
24470 .into_any_element()
24471}
24472
24473pub fn multibuffer_context_lines(cx: &App) -> u32 {
24474 EditorSettings::try_get(cx)
24475 .map(|settings| settings.excerpt_context_lines)
24476 .unwrap_or(2)
24477 .min(32)
24478}