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 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
2418 .as_ref()
2419 .is_some_and(|pending_selection| {
2420 let snapshot = self.buffer().read(cx).snapshot(cx);
2421 pending_selection
2422 .selection
2423 .range()
2424 .includes(range, &snapshot)
2425 })
2426 {
2427 return true;
2428 }
2429
2430 self.selections
2431 .disjoint_in_range::<usize>(range.clone(), cx)
2432 .into_iter()
2433 .any(|selection| {
2434 // This is needed to cover a corner case, if we just check for an existing
2435 // selection in the fold range, having a cursor at the start of the fold
2436 // marks it as selected. Non-empty selections don't cause this.
2437 let length = selection.end - selection.start;
2438 length > 0
2439 })
2440 }
2441
2442 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2443 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2444 }
2445
2446 fn key_context_internal(
2447 &self,
2448 has_active_edit_prediction: bool,
2449 window: &Window,
2450 cx: &App,
2451 ) -> KeyContext {
2452 let mut key_context = KeyContext::new_with_defaults();
2453 key_context.add("Editor");
2454 let mode = match self.mode {
2455 EditorMode::SingleLine => "single_line",
2456 EditorMode::AutoHeight { .. } => "auto_height",
2457 EditorMode::Minimap { .. } => "minimap",
2458 EditorMode::Full { .. } => "full",
2459 };
2460
2461 if EditorSettings::jupyter_enabled(cx) {
2462 key_context.add("jupyter");
2463 }
2464
2465 key_context.set("mode", mode);
2466 if self.pending_rename.is_some() {
2467 key_context.add("renaming");
2468 }
2469
2470 match self.context_menu.borrow().as_ref() {
2471 Some(CodeContextMenu::Completions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_completions");
2475 }
2476 }
2477 Some(CodeContextMenu::CodeActions(menu)) => {
2478 if menu.visible() {
2479 key_context.add("menu");
2480 key_context.add("showing_code_actions")
2481 }
2482 }
2483 None => {}
2484 }
2485
2486 if self.signature_help_state.has_multiple_signatures() {
2487 key_context.add("showing_signature_help");
2488 }
2489
2490 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2491 if !self.focus_handle(cx).contains_focused(window, cx)
2492 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2493 {
2494 for addon in self.addons.values() {
2495 addon.extend_key_context(&mut key_context, cx)
2496 }
2497 }
2498
2499 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2500 if let Some(extension) = singleton_buffer
2501 .read(cx)
2502 .file()
2503 .and_then(|file| file.path().extension()?.to_str())
2504 {
2505 key_context.set("extension", extension.to_string());
2506 }
2507 } else {
2508 key_context.add("multibuffer");
2509 }
2510
2511 if has_active_edit_prediction {
2512 if self.edit_prediction_in_conflict() {
2513 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2514 } else {
2515 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2516 key_context.add("copilot_suggestion");
2517 }
2518 }
2519
2520 if self.selection_mark_mode {
2521 key_context.add("selection_mode");
2522 }
2523
2524 key_context
2525 }
2526
2527 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2528 if self.mouse_cursor_hidden {
2529 self.mouse_cursor_hidden = false;
2530 cx.notify();
2531 }
2532 }
2533
2534 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2535 let hide_mouse_cursor = match origin {
2536 HideMouseCursorOrigin::TypingAction => {
2537 matches!(
2538 self.hide_mouse_mode,
2539 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2540 )
2541 }
2542 HideMouseCursorOrigin::MovementAction => {
2543 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2544 }
2545 };
2546 if self.mouse_cursor_hidden != hide_mouse_cursor {
2547 self.mouse_cursor_hidden = hide_mouse_cursor;
2548 cx.notify();
2549 }
2550 }
2551
2552 pub fn edit_prediction_in_conflict(&self) -> bool {
2553 if !self.show_edit_predictions_in_menu() {
2554 return false;
2555 }
2556
2557 let showing_completions = self
2558 .context_menu
2559 .borrow()
2560 .as_ref()
2561 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2562
2563 showing_completions
2564 || self.edit_prediction_requires_modifier()
2565 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2566 // bindings to insert tab characters.
2567 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2568 }
2569
2570 pub fn accept_edit_prediction_keybind(
2571 &self,
2572 accept_partial: bool,
2573 window: &Window,
2574 cx: &App,
2575 ) -> AcceptEditPredictionBinding {
2576 let key_context = self.key_context_internal(true, window, cx);
2577 let in_conflict = self.edit_prediction_in_conflict();
2578
2579 let bindings = if accept_partial {
2580 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2581 } else {
2582 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2583 };
2584
2585 // TODO: if the binding contains multiple keystrokes, display all of them, not
2586 // just the first one.
2587 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2588 !in_conflict
2589 || binding
2590 .keystrokes()
2591 .first()
2592 .is_some_and(|keystroke| keystroke.modifiers().modified())
2593 }))
2594 }
2595
2596 pub fn new_file(
2597 workspace: &mut Workspace,
2598 _: &workspace::NewFile,
2599 window: &mut Window,
2600 cx: &mut Context<Workspace>,
2601 ) {
2602 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2603 "Failed to create buffer",
2604 window,
2605 cx,
2606 |e, _, _| match e.error_code() {
2607 ErrorCode::RemoteUpgradeRequired => Some(format!(
2608 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2609 e.error_tag("required").unwrap_or("the latest version")
2610 )),
2611 _ => None,
2612 },
2613 );
2614 }
2615
2616 pub fn new_in_workspace(
2617 workspace: &mut Workspace,
2618 window: &mut Window,
2619 cx: &mut Context<Workspace>,
2620 ) -> Task<Result<Entity<Editor>>> {
2621 let project = workspace.project().clone();
2622 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2623
2624 cx.spawn_in(window, async move |workspace, cx| {
2625 let buffer = create.await?;
2626 workspace.update_in(cx, |workspace, window, cx| {
2627 let editor =
2628 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2629 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2630 editor
2631 })
2632 })
2633 }
2634
2635 fn new_file_vertical(
2636 workspace: &mut Workspace,
2637 _: &workspace::NewFileSplitVertical,
2638 window: &mut Window,
2639 cx: &mut Context<Workspace>,
2640 ) {
2641 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2642 }
2643
2644 fn new_file_horizontal(
2645 workspace: &mut Workspace,
2646 _: &workspace::NewFileSplitHorizontal,
2647 window: &mut Window,
2648 cx: &mut Context<Workspace>,
2649 ) {
2650 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2651 }
2652
2653 fn new_file_in_direction(
2654 workspace: &mut Workspace,
2655 direction: SplitDirection,
2656 window: &mut Window,
2657 cx: &mut Context<Workspace>,
2658 ) {
2659 let project = workspace.project().clone();
2660 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2661
2662 cx.spawn_in(window, async move |workspace, cx| {
2663 let buffer = create.await?;
2664 workspace.update_in(cx, move |workspace, window, cx| {
2665 workspace.split_item(
2666 direction,
2667 Box::new(
2668 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2669 ),
2670 window,
2671 cx,
2672 )
2673 })?;
2674 anyhow::Ok(())
2675 })
2676 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2677 match e.error_code() {
2678 ErrorCode::RemoteUpgradeRequired => Some(format!(
2679 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2680 e.error_tag("required").unwrap_or("the latest version")
2681 )),
2682 _ => None,
2683 }
2684 });
2685 }
2686
2687 pub fn leader_id(&self) -> Option<CollaboratorId> {
2688 self.leader_id
2689 }
2690
2691 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2692 &self.buffer
2693 }
2694
2695 pub fn project(&self) -> Option<&Entity<Project>> {
2696 self.project.as_ref()
2697 }
2698
2699 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2700 self.workspace.as_ref()?.0.upgrade()
2701 }
2702
2703 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2704 self.buffer().read(cx).title(cx)
2705 }
2706
2707 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2708 let git_blame_gutter_max_author_length = self
2709 .render_git_blame_gutter(cx)
2710 .then(|| {
2711 if let Some(blame) = self.blame.as_ref() {
2712 let max_author_length =
2713 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2714 Some(max_author_length)
2715 } else {
2716 None
2717 }
2718 })
2719 .flatten();
2720
2721 EditorSnapshot {
2722 mode: self.mode.clone(),
2723 show_gutter: self.show_gutter,
2724 show_line_numbers: self.show_line_numbers,
2725 show_git_diff_gutter: self.show_git_diff_gutter,
2726 show_code_actions: self.show_code_actions,
2727 show_runnables: self.show_runnables,
2728 show_breakpoints: self.show_breakpoints,
2729 git_blame_gutter_max_author_length,
2730 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2731 placeholder_display_snapshot: self
2732 .placeholder_display_map
2733 .as_ref()
2734 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2735 scroll_anchor: self.scroll_manager.anchor(),
2736 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2737 is_focused: self.focus_handle.is_focused(window),
2738 current_line_highlight: self
2739 .current_line_highlight
2740 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2741 gutter_hovered: self.gutter_hovered,
2742 }
2743 }
2744
2745 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2746 self.buffer.read(cx).language_at(point, cx)
2747 }
2748
2749 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2750 self.buffer.read(cx).read(cx).file_at(point).cloned()
2751 }
2752
2753 pub fn active_excerpt(
2754 &self,
2755 cx: &App,
2756 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2757 self.buffer
2758 .read(cx)
2759 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2760 }
2761
2762 pub fn mode(&self) -> &EditorMode {
2763 &self.mode
2764 }
2765
2766 pub fn set_mode(&mut self, mode: EditorMode) {
2767 self.mode = mode;
2768 }
2769
2770 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2771 self.collaboration_hub.as_deref()
2772 }
2773
2774 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2775 self.collaboration_hub = Some(hub);
2776 }
2777
2778 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2779 self.in_project_search = in_project_search;
2780 }
2781
2782 pub fn set_custom_context_menu(
2783 &mut self,
2784 f: impl 'static
2785 + Fn(
2786 &mut Self,
2787 DisplayPoint,
2788 &mut Window,
2789 &mut Context<Self>,
2790 ) -> Option<Entity<ui::ContextMenu>>,
2791 ) {
2792 self.custom_context_menu = Some(Box::new(f))
2793 }
2794
2795 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2796 self.completion_provider = provider;
2797 }
2798
2799 #[cfg(any(test, feature = "test-support"))]
2800 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2801 self.completion_provider.clone()
2802 }
2803
2804 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2805 self.semantics_provider.clone()
2806 }
2807
2808 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2809 self.semantics_provider = provider;
2810 }
2811
2812 pub fn set_edit_prediction_provider<T>(
2813 &mut self,
2814 provider: Option<Entity<T>>,
2815 window: &mut Window,
2816 cx: &mut Context<Self>,
2817 ) where
2818 T: EditPredictionProvider,
2819 {
2820 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2821 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2822 if this.focus_handle.is_focused(window) {
2823 this.update_visible_edit_prediction(window, cx);
2824 }
2825 }),
2826 provider: Arc::new(provider),
2827 });
2828 self.update_edit_prediction_settings(cx);
2829 self.refresh_edit_prediction(false, false, window, cx);
2830 }
2831
2832 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2833 self.placeholder_display_map
2834 .as_ref()
2835 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2836 }
2837
2838 pub fn set_placeholder_text(
2839 &mut self,
2840 placeholder_text: &str,
2841 window: &mut Window,
2842 cx: &mut Context<Self>,
2843 ) {
2844 let multibuffer = cx
2845 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2846
2847 let style = window.text_style();
2848
2849 self.placeholder_display_map = Some(cx.new(|cx| {
2850 DisplayMap::new(
2851 multibuffer,
2852 style.font(),
2853 style.font_size.to_pixels(window.rem_size()),
2854 None,
2855 FILE_HEADER_HEIGHT,
2856 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2857 Default::default(),
2858 DiagnosticSeverity::Off,
2859 cx,
2860 )
2861 }));
2862 cx.notify();
2863 }
2864
2865 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2866 self.cursor_shape = cursor_shape;
2867
2868 // Disrupt blink for immediate user feedback that the cursor shape has changed
2869 self.blink_manager.update(cx, BlinkManager::show_cursor);
2870
2871 cx.notify();
2872 }
2873
2874 pub fn set_current_line_highlight(
2875 &mut self,
2876 current_line_highlight: Option<CurrentLineHighlight>,
2877 ) {
2878 self.current_line_highlight = current_line_highlight;
2879 }
2880
2881 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2882 self.collapse_matches = collapse_matches;
2883 }
2884
2885 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2886 let buffers = self.buffer.read(cx).all_buffers();
2887 let Some(project) = self.project.as_ref() else {
2888 return;
2889 };
2890 project.update(cx, |project, cx| {
2891 for buffer in buffers {
2892 self.registered_buffers
2893 .entry(buffer.read(cx).remote_id())
2894 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2895 }
2896 })
2897 }
2898
2899 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2900 if self.collapse_matches {
2901 return range.start..range.start;
2902 }
2903 range.clone()
2904 }
2905
2906 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2907 if self.display_map.read(cx).clip_at_line_ends != clip {
2908 self.display_map
2909 .update(cx, |map, _| map.clip_at_line_ends = clip);
2910 }
2911 }
2912
2913 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2914 self.input_enabled = input_enabled;
2915 }
2916
2917 pub fn set_edit_predictions_hidden_for_vim_mode(
2918 &mut self,
2919 hidden: bool,
2920 window: &mut Window,
2921 cx: &mut Context<Self>,
2922 ) {
2923 if hidden != self.edit_predictions_hidden_for_vim_mode {
2924 self.edit_predictions_hidden_for_vim_mode = hidden;
2925 if hidden {
2926 self.update_visible_edit_prediction(window, cx);
2927 } else {
2928 self.refresh_edit_prediction(true, false, window, cx);
2929 }
2930 }
2931 }
2932
2933 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2934 self.menu_edit_predictions_policy = value;
2935 }
2936
2937 pub fn set_autoindent(&mut self, autoindent: bool) {
2938 if autoindent {
2939 self.autoindent_mode = Some(AutoindentMode::EachLine);
2940 } else {
2941 self.autoindent_mode = None;
2942 }
2943 }
2944
2945 pub fn read_only(&self, cx: &App) -> bool {
2946 self.read_only || self.buffer.read(cx).read_only()
2947 }
2948
2949 pub fn set_read_only(&mut self, read_only: bool) {
2950 self.read_only = read_only;
2951 }
2952
2953 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2954 self.use_autoclose = autoclose;
2955 }
2956
2957 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2958 self.use_auto_surround = auto_surround;
2959 }
2960
2961 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2962 self.auto_replace_emoji_shortcode = auto_replace;
2963 }
2964
2965 pub fn toggle_edit_predictions(
2966 &mut self,
2967 _: &ToggleEditPrediction,
2968 window: &mut Window,
2969 cx: &mut Context<Self>,
2970 ) {
2971 if self.show_edit_predictions_override.is_some() {
2972 self.set_show_edit_predictions(None, window, cx);
2973 } else {
2974 let show_edit_predictions = !self.edit_predictions_enabled();
2975 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2976 }
2977 }
2978
2979 pub fn set_show_edit_predictions(
2980 &mut self,
2981 show_edit_predictions: Option<bool>,
2982 window: &mut Window,
2983 cx: &mut Context<Self>,
2984 ) {
2985 self.show_edit_predictions_override = show_edit_predictions;
2986 self.update_edit_prediction_settings(cx);
2987
2988 if let Some(false) = show_edit_predictions {
2989 self.discard_edit_prediction(false, cx);
2990 } else {
2991 self.refresh_edit_prediction(false, true, window, cx);
2992 }
2993 }
2994
2995 fn edit_predictions_disabled_in_scope(
2996 &self,
2997 buffer: &Entity<Buffer>,
2998 buffer_position: language::Anchor,
2999 cx: &App,
3000 ) -> bool {
3001 let snapshot = buffer.read(cx).snapshot();
3002 let settings = snapshot.settings_at(buffer_position, cx);
3003
3004 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3005 return false;
3006 };
3007
3008 scope.override_name().is_some_and(|scope_name| {
3009 settings
3010 .edit_predictions_disabled_in
3011 .iter()
3012 .any(|s| s == scope_name)
3013 })
3014 }
3015
3016 pub fn set_use_modal_editing(&mut self, to: bool) {
3017 self.use_modal_editing = to;
3018 }
3019
3020 pub fn use_modal_editing(&self) -> bool {
3021 self.use_modal_editing
3022 }
3023
3024 fn selections_did_change(
3025 &mut self,
3026 local: bool,
3027 old_cursor_position: &Anchor,
3028 effects: SelectionEffects,
3029 window: &mut Window,
3030 cx: &mut Context<Self>,
3031 ) {
3032 window.invalidate_character_coordinates();
3033
3034 // Copy selections to primary selection buffer
3035 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3036 if local {
3037 let selections = self.selections.all::<usize>(cx);
3038 let buffer_handle = self.buffer.read(cx).read(cx);
3039
3040 let mut text = String::new();
3041 for (index, selection) in selections.iter().enumerate() {
3042 let text_for_selection = buffer_handle
3043 .text_for_range(selection.start..selection.end)
3044 .collect::<String>();
3045
3046 text.push_str(&text_for_selection);
3047 if index != selections.len() - 1 {
3048 text.push('\n');
3049 }
3050 }
3051
3052 if !text.is_empty() {
3053 cx.write_to_primary(ClipboardItem::new_string(text));
3054 }
3055 }
3056
3057 let selection_anchors = self.selections.disjoint_anchors();
3058
3059 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3060 self.buffer.update(cx, |buffer, cx| {
3061 buffer.set_active_selections(
3062 &selection_anchors,
3063 self.selections.line_mode,
3064 self.cursor_shape,
3065 cx,
3066 )
3067 });
3068 }
3069 let display_map = self
3070 .display_map
3071 .update(cx, |display_map, cx| display_map.snapshot(cx));
3072 let buffer = &display_map.buffer_snapshot;
3073 if self.selections.count() == 1 {
3074 self.add_selections_state = None;
3075 }
3076 self.select_next_state = None;
3077 self.select_prev_state = None;
3078 self.select_syntax_node_history.try_clear();
3079 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3080 self.snippet_stack.invalidate(&selection_anchors, buffer);
3081 self.take_rename(false, window, cx);
3082
3083 let newest_selection = self.selections.newest_anchor();
3084 let new_cursor_position = newest_selection.head();
3085 let selection_start = newest_selection.start;
3086
3087 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3088 self.push_to_nav_history(
3089 *old_cursor_position,
3090 Some(new_cursor_position.to_point(buffer)),
3091 false,
3092 effects.nav_history == Some(true),
3093 cx,
3094 );
3095 }
3096
3097 if local {
3098 if let Some(buffer_id) = new_cursor_position.buffer_id
3099 && !self.registered_buffers.contains_key(&buffer_id)
3100 && let Some(project) = self.project.as_ref()
3101 {
3102 project.update(cx, |project, cx| {
3103 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3104 return;
3105 };
3106 self.registered_buffers.insert(
3107 buffer_id,
3108 project.register_buffer_with_language_servers(&buffer, cx),
3109 );
3110 })
3111 }
3112
3113 let mut context_menu = self.context_menu.borrow_mut();
3114 let completion_menu = match context_menu.as_ref() {
3115 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3116 Some(CodeContextMenu::CodeActions(_)) => {
3117 *context_menu = None;
3118 None
3119 }
3120 None => None,
3121 };
3122 let completion_position = completion_menu.map(|menu| menu.initial_position);
3123 drop(context_menu);
3124
3125 if effects.completions
3126 && let Some(completion_position) = completion_position
3127 {
3128 let start_offset = selection_start.to_offset(buffer);
3129 let position_matches = start_offset == completion_position.to_offset(buffer);
3130 let continue_showing = if position_matches {
3131 if self.snippet_stack.is_empty() {
3132 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3133 } else {
3134 // Snippet choices can be shown even when the cursor is in whitespace.
3135 // Dismissing the menu with actions like backspace is handled by
3136 // invalidation regions.
3137 true
3138 }
3139 } else {
3140 false
3141 };
3142
3143 if continue_showing {
3144 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3145 } else {
3146 self.hide_context_menu(window, cx);
3147 }
3148 }
3149
3150 hide_hover(self, cx);
3151
3152 if old_cursor_position.to_display_point(&display_map).row()
3153 != new_cursor_position.to_display_point(&display_map).row()
3154 {
3155 self.available_code_actions.take();
3156 }
3157 self.refresh_code_actions(window, cx);
3158 self.refresh_document_highlights(cx);
3159 self.refresh_selected_text_highlights(false, window, cx);
3160 refresh_matching_bracket_highlights(self, window, cx);
3161 self.update_visible_edit_prediction(window, cx);
3162 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3163 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3164 self.inline_blame_popover.take();
3165 if self.git_blame_inline_enabled {
3166 self.start_inline_blame_timer(window, cx);
3167 }
3168 }
3169
3170 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3171 cx.emit(EditorEvent::SelectionsChanged { local });
3172
3173 let selections = &self.selections.disjoint;
3174 if selections.len() == 1 {
3175 cx.emit(SearchEvent::ActiveMatchChanged)
3176 }
3177 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3178 let inmemory_selections = selections
3179 .iter()
3180 .map(|s| {
3181 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3182 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3183 })
3184 .collect();
3185 self.update_restoration_data(cx, |data| {
3186 data.selections = inmemory_selections;
3187 });
3188
3189 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3190 && let Some(workspace_id) =
3191 self.workspace.as_ref().and_then(|workspace| workspace.1)
3192 {
3193 let snapshot = self.buffer().read(cx).snapshot(cx);
3194 let selections = selections.clone();
3195 let background_executor = cx.background_executor().clone();
3196 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3197 self.serialize_selections = cx.background_spawn(async move {
3198 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3199 let db_selections = selections
3200 .iter()
3201 .map(|selection| {
3202 (
3203 selection.start.to_offset(&snapshot),
3204 selection.end.to_offset(&snapshot),
3205 )
3206 })
3207 .collect();
3208
3209 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3210 .await
3211 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3212 .log_err();
3213 });
3214 }
3215 }
3216
3217 cx.notify();
3218 }
3219
3220 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3221 use text::ToOffset as _;
3222 use text::ToPoint as _;
3223
3224 if self.mode.is_minimap()
3225 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3226 {
3227 return;
3228 }
3229
3230 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3231 return;
3232 };
3233
3234 let snapshot = singleton.read(cx).snapshot();
3235 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3236 let display_snapshot = display_map.snapshot(cx);
3237
3238 display_snapshot
3239 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3240 .map(|fold| {
3241 fold.range.start.text_anchor.to_point(&snapshot)
3242 ..fold.range.end.text_anchor.to_point(&snapshot)
3243 })
3244 .collect()
3245 });
3246 self.update_restoration_data(cx, |data| {
3247 data.folds = inmemory_folds;
3248 });
3249
3250 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3251 return;
3252 };
3253 let background_executor = cx.background_executor().clone();
3254 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3255 let db_folds = self.display_map.update(cx, |display_map, cx| {
3256 display_map
3257 .snapshot(cx)
3258 .folds_in_range(0..snapshot.len())
3259 .map(|fold| {
3260 (
3261 fold.range.start.text_anchor.to_offset(&snapshot),
3262 fold.range.end.text_anchor.to_offset(&snapshot),
3263 )
3264 })
3265 .collect()
3266 });
3267 self.serialize_folds = cx.background_spawn(async move {
3268 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3269 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3270 .await
3271 .with_context(|| {
3272 format!(
3273 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3274 )
3275 })
3276 .log_err();
3277 });
3278 }
3279
3280 pub fn sync_selections(
3281 &mut self,
3282 other: Entity<Editor>,
3283 cx: &mut Context<Self>,
3284 ) -> gpui::Subscription {
3285 let other_selections = other.read(cx).selections.disjoint.to_vec();
3286 self.selections.change_with(cx, |selections| {
3287 selections.select_anchors(other_selections);
3288 });
3289
3290 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3291 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3292 let other_selections = other.read(cx).selections.disjoint.to_vec();
3293 if other_selections.is_empty() {
3294 return;
3295 }
3296 this.selections.change_with(cx, |selections| {
3297 selections.select_anchors(other_selections);
3298 });
3299 }
3300 });
3301
3302 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3303 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3304 let these_selections = this.selections.disjoint.to_vec();
3305 if these_selections.is_empty() {
3306 return;
3307 }
3308 other.update(cx, |other_editor, cx| {
3309 other_editor.selections.change_with(cx, |selections| {
3310 selections.select_anchors(these_selections);
3311 })
3312 });
3313 }
3314 });
3315
3316 Subscription::join(other_subscription, this_subscription)
3317 }
3318
3319 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3320 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3321 /// effects of selection change occur at the end of the transaction.
3322 pub fn change_selections<R>(
3323 &mut self,
3324 effects: SelectionEffects,
3325 window: &mut Window,
3326 cx: &mut Context<Self>,
3327 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3328 ) -> R {
3329 if let Some(state) = &mut self.deferred_selection_effects_state {
3330 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3331 state.effects.completions = effects.completions;
3332 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3333 let (changed, result) = self.selections.change_with(cx, change);
3334 state.changed |= changed;
3335 return result;
3336 }
3337 let mut state = DeferredSelectionEffectsState {
3338 changed: false,
3339 effects,
3340 old_cursor_position: self.selections.newest_anchor().head(),
3341 history_entry: SelectionHistoryEntry {
3342 selections: self.selections.disjoint_anchors(),
3343 select_next_state: self.select_next_state.clone(),
3344 select_prev_state: self.select_prev_state.clone(),
3345 add_selections_state: self.add_selections_state.clone(),
3346 },
3347 };
3348 let (changed, result) = self.selections.change_with(cx, change);
3349 state.changed = state.changed || changed;
3350 if self.defer_selection_effects {
3351 self.deferred_selection_effects_state = Some(state);
3352 } else {
3353 self.apply_selection_effects(state, window, cx);
3354 }
3355 result
3356 }
3357
3358 /// Defers the effects of selection change, so that the effects of multiple calls to
3359 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3360 /// to selection history and the state of popovers based on selection position aren't
3361 /// erroneously updated.
3362 pub fn with_selection_effects_deferred<R>(
3363 &mut self,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3367 ) -> R {
3368 let already_deferred = self.defer_selection_effects;
3369 self.defer_selection_effects = true;
3370 let result = update(self, window, cx);
3371 if !already_deferred {
3372 self.defer_selection_effects = false;
3373 if let Some(state) = self.deferred_selection_effects_state.take() {
3374 self.apply_selection_effects(state, window, cx);
3375 }
3376 }
3377 result
3378 }
3379
3380 fn apply_selection_effects(
3381 &mut self,
3382 state: DeferredSelectionEffectsState,
3383 window: &mut Window,
3384 cx: &mut Context<Self>,
3385 ) {
3386 if state.changed {
3387 self.selection_history.push(state.history_entry);
3388
3389 if let Some(autoscroll) = state.effects.scroll {
3390 self.request_autoscroll(autoscroll, cx);
3391 }
3392
3393 let old_cursor_position = &state.old_cursor_position;
3394
3395 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3396
3397 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3398 self.show_signature_help(&ShowSignatureHelp, window, cx);
3399 }
3400 }
3401 }
3402
3403 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3404 where
3405 I: IntoIterator<Item = (Range<S>, T)>,
3406 S: ToOffset,
3407 T: Into<Arc<str>>,
3408 {
3409 if self.read_only(cx) {
3410 return;
3411 }
3412
3413 self.buffer
3414 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3415 }
3416
3417 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3418 where
3419 I: IntoIterator<Item = (Range<S>, T)>,
3420 S: ToOffset,
3421 T: Into<Arc<str>>,
3422 {
3423 if self.read_only(cx) {
3424 return;
3425 }
3426
3427 self.buffer.update(cx, |buffer, cx| {
3428 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3429 });
3430 }
3431
3432 pub fn edit_with_block_indent<I, S, T>(
3433 &mut self,
3434 edits: I,
3435 original_indent_columns: Vec<Option<u32>>,
3436 cx: &mut Context<Self>,
3437 ) where
3438 I: IntoIterator<Item = (Range<S>, T)>,
3439 S: ToOffset,
3440 T: Into<Arc<str>>,
3441 {
3442 if self.read_only(cx) {
3443 return;
3444 }
3445
3446 self.buffer.update(cx, |buffer, cx| {
3447 buffer.edit(
3448 edits,
3449 Some(AutoindentMode::Block {
3450 original_indent_columns,
3451 }),
3452 cx,
3453 )
3454 });
3455 }
3456
3457 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3458 self.hide_context_menu(window, cx);
3459
3460 match phase {
3461 SelectPhase::Begin {
3462 position,
3463 add,
3464 click_count,
3465 } => self.begin_selection(position, add, click_count, window, cx),
3466 SelectPhase::BeginColumnar {
3467 position,
3468 goal_column,
3469 reset,
3470 mode,
3471 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3472 SelectPhase::Extend {
3473 position,
3474 click_count,
3475 } => self.extend_selection(position, click_count, window, cx),
3476 SelectPhase::Update {
3477 position,
3478 goal_column,
3479 scroll_delta,
3480 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3481 SelectPhase::End => self.end_selection(window, cx),
3482 }
3483 }
3484
3485 fn extend_selection(
3486 &mut self,
3487 position: DisplayPoint,
3488 click_count: usize,
3489 window: &mut Window,
3490 cx: &mut Context<Self>,
3491 ) {
3492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3493 let tail = self.selections.newest::<usize>(cx).tail();
3494 self.begin_selection(position, false, click_count, window, cx);
3495
3496 let position = position.to_offset(&display_map, Bias::Left);
3497 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3498
3499 let mut pending_selection = self
3500 .selections
3501 .pending_anchor()
3502 .expect("extend_selection not called with pending selection");
3503 if position >= tail {
3504 pending_selection.start = tail_anchor;
3505 } else {
3506 pending_selection.end = tail_anchor;
3507 pending_selection.reversed = true;
3508 }
3509
3510 let mut pending_mode = self.selections.pending_mode().unwrap();
3511 match &mut pending_mode {
3512 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3513 _ => {}
3514 }
3515
3516 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3517 SelectionEffects::scroll(Autoscroll::fit())
3518 } else {
3519 SelectionEffects::no_scroll()
3520 };
3521
3522 self.change_selections(effects, window, cx, |s| {
3523 s.set_pending(pending_selection, pending_mode)
3524 });
3525 }
3526
3527 fn begin_selection(
3528 &mut self,
3529 position: DisplayPoint,
3530 add: bool,
3531 click_count: usize,
3532 window: &mut Window,
3533 cx: &mut Context<Self>,
3534 ) {
3535 if !self.focus_handle.is_focused(window) {
3536 self.last_focused_descendant = None;
3537 window.focus(&self.focus_handle);
3538 }
3539
3540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3541 let buffer = &display_map.buffer_snapshot;
3542 let position = display_map.clip_point(position, Bias::Left);
3543
3544 let start;
3545 let end;
3546 let mode;
3547 let mut auto_scroll;
3548 match click_count {
3549 1 => {
3550 start = buffer.anchor_before(position.to_point(&display_map));
3551 end = start;
3552 mode = SelectMode::Character;
3553 auto_scroll = true;
3554 }
3555 2 => {
3556 let position = display_map
3557 .clip_point(position, Bias::Left)
3558 .to_offset(&display_map, Bias::Left);
3559 let (range, _) = buffer.surrounding_word(position, false);
3560 start = buffer.anchor_before(range.start);
3561 end = buffer.anchor_before(range.end);
3562 mode = SelectMode::Word(start..end);
3563 auto_scroll = true;
3564 }
3565 3 => {
3566 let position = display_map
3567 .clip_point(position, Bias::Left)
3568 .to_point(&display_map);
3569 let line_start = display_map.prev_line_boundary(position).0;
3570 let next_line_start = buffer.clip_point(
3571 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3572 Bias::Left,
3573 );
3574 start = buffer.anchor_before(line_start);
3575 end = buffer.anchor_before(next_line_start);
3576 mode = SelectMode::Line(start..end);
3577 auto_scroll = true;
3578 }
3579 _ => {
3580 start = buffer.anchor_before(0);
3581 end = buffer.anchor_before(buffer.len());
3582 mode = SelectMode::All;
3583 auto_scroll = false;
3584 }
3585 }
3586 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3587
3588 let point_to_delete: Option<usize> = {
3589 let selected_points: Vec<Selection<Point>> =
3590 self.selections.disjoint_in_range(start..end, cx);
3591
3592 if !add || click_count > 1 {
3593 None
3594 } else if !selected_points.is_empty() {
3595 Some(selected_points[0].id)
3596 } else {
3597 let clicked_point_already_selected =
3598 self.selections.disjoint.iter().find(|selection| {
3599 selection.start.to_point(buffer) == start.to_point(buffer)
3600 || selection.end.to_point(buffer) == end.to_point(buffer)
3601 });
3602
3603 clicked_point_already_selected.map(|selection| selection.id)
3604 }
3605 };
3606
3607 let selections_count = self.selections.count();
3608 let effects = if auto_scroll {
3609 SelectionEffects::default()
3610 } else {
3611 SelectionEffects::no_scroll()
3612 };
3613
3614 self.change_selections(effects, window, cx, |s| {
3615 if let Some(point_to_delete) = point_to_delete {
3616 s.delete(point_to_delete);
3617
3618 if selections_count == 1 {
3619 s.set_pending_anchor_range(start..end, mode);
3620 }
3621 } else {
3622 if !add {
3623 s.clear_disjoint();
3624 }
3625
3626 s.set_pending_anchor_range(start..end, mode);
3627 }
3628 });
3629 }
3630
3631 fn begin_columnar_selection(
3632 &mut self,
3633 position: DisplayPoint,
3634 goal_column: u32,
3635 reset: bool,
3636 mode: ColumnarMode,
3637 window: &mut Window,
3638 cx: &mut Context<Self>,
3639 ) {
3640 if !self.focus_handle.is_focused(window) {
3641 self.last_focused_descendant = None;
3642 window.focus(&self.focus_handle);
3643 }
3644
3645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3646
3647 if reset {
3648 let pointer_position = display_map
3649 .buffer_snapshot
3650 .anchor_before(position.to_point(&display_map));
3651
3652 self.change_selections(
3653 SelectionEffects::scroll(Autoscroll::newest()),
3654 window,
3655 cx,
3656 |s| {
3657 s.clear_disjoint();
3658 s.set_pending_anchor_range(
3659 pointer_position..pointer_position,
3660 SelectMode::Character,
3661 );
3662 },
3663 );
3664 };
3665
3666 let tail = self.selections.newest::<Point>(cx).tail();
3667 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3668 self.columnar_selection_state = match mode {
3669 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3670 selection_tail: selection_anchor,
3671 display_point: if reset {
3672 if position.column() != goal_column {
3673 Some(DisplayPoint::new(position.row(), goal_column))
3674 } else {
3675 None
3676 }
3677 } else {
3678 None
3679 },
3680 }),
3681 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3682 selection_tail: selection_anchor,
3683 }),
3684 };
3685
3686 if !reset {
3687 self.select_columns(position, goal_column, &display_map, window, cx);
3688 }
3689 }
3690
3691 fn update_selection(
3692 &mut self,
3693 position: DisplayPoint,
3694 goal_column: u32,
3695 scroll_delta: gpui::Point<f32>,
3696 window: &mut Window,
3697 cx: &mut Context<Self>,
3698 ) {
3699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3700
3701 if self.columnar_selection_state.is_some() {
3702 self.select_columns(position, goal_column, &display_map, window, cx);
3703 } else if let Some(mut pending) = self.selections.pending_anchor() {
3704 let buffer = &display_map.buffer_snapshot;
3705 let head;
3706 let tail;
3707 let mode = self.selections.pending_mode().unwrap();
3708 match &mode {
3709 SelectMode::Character => {
3710 head = position.to_point(&display_map);
3711 tail = pending.tail().to_point(buffer);
3712 }
3713 SelectMode::Word(original_range) => {
3714 let offset = display_map
3715 .clip_point(position, Bias::Left)
3716 .to_offset(&display_map, Bias::Left);
3717 let original_range = original_range.to_offset(buffer);
3718
3719 let head_offset = if buffer.is_inside_word(offset, false)
3720 || original_range.contains(&offset)
3721 {
3722 let (word_range, _) = buffer.surrounding_word(offset, false);
3723 if word_range.start < original_range.start {
3724 word_range.start
3725 } else {
3726 word_range.end
3727 }
3728 } else {
3729 offset
3730 };
3731
3732 head = head_offset.to_point(buffer);
3733 if head_offset <= original_range.start {
3734 tail = original_range.end.to_point(buffer);
3735 } else {
3736 tail = original_range.start.to_point(buffer);
3737 }
3738 }
3739 SelectMode::Line(original_range) => {
3740 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3741
3742 let position = display_map
3743 .clip_point(position, Bias::Left)
3744 .to_point(&display_map);
3745 let line_start = display_map.prev_line_boundary(position).0;
3746 let next_line_start = buffer.clip_point(
3747 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3748 Bias::Left,
3749 );
3750
3751 if line_start < original_range.start {
3752 head = line_start
3753 } else {
3754 head = next_line_start
3755 }
3756
3757 if head <= original_range.start {
3758 tail = original_range.end;
3759 } else {
3760 tail = original_range.start;
3761 }
3762 }
3763 SelectMode::All => {
3764 return;
3765 }
3766 };
3767
3768 if head < tail {
3769 pending.start = buffer.anchor_before(head);
3770 pending.end = buffer.anchor_before(tail);
3771 pending.reversed = true;
3772 } else {
3773 pending.start = buffer.anchor_before(tail);
3774 pending.end = buffer.anchor_before(head);
3775 pending.reversed = false;
3776 }
3777
3778 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3779 s.set_pending(pending, mode);
3780 });
3781 } else {
3782 log::error!("update_selection dispatched with no pending selection");
3783 return;
3784 }
3785
3786 self.apply_scroll_delta(scroll_delta, window, cx);
3787 cx.notify();
3788 }
3789
3790 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3791 self.columnar_selection_state.take();
3792 if self.selections.pending_anchor().is_some() {
3793 let selections = self.selections.all::<usize>(cx);
3794 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3795 s.select(selections);
3796 s.clear_pending();
3797 });
3798 }
3799 }
3800
3801 fn select_columns(
3802 &mut self,
3803 head: DisplayPoint,
3804 goal_column: u32,
3805 display_map: &DisplaySnapshot,
3806 window: &mut Window,
3807 cx: &mut Context<Self>,
3808 ) {
3809 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3810 return;
3811 };
3812
3813 let tail = match columnar_state {
3814 ColumnarSelectionState::FromMouse {
3815 selection_tail,
3816 display_point,
3817 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3818 ColumnarSelectionState::FromSelection { selection_tail } => {
3819 selection_tail.to_display_point(display_map)
3820 }
3821 };
3822
3823 let start_row = cmp::min(tail.row(), head.row());
3824 let end_row = cmp::max(tail.row(), head.row());
3825 let start_column = cmp::min(tail.column(), goal_column);
3826 let end_column = cmp::max(tail.column(), goal_column);
3827 let reversed = start_column < tail.column();
3828
3829 let selection_ranges = (start_row.0..=end_row.0)
3830 .map(DisplayRow)
3831 .filter_map(|row| {
3832 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3833 || start_column <= display_map.line_len(row))
3834 && !display_map.is_block_line(row)
3835 {
3836 let start = display_map
3837 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3838 .to_point(display_map);
3839 let end = display_map
3840 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3841 .to_point(display_map);
3842 if reversed {
3843 Some(end..start)
3844 } else {
3845 Some(start..end)
3846 }
3847 } else {
3848 None
3849 }
3850 })
3851 .collect::<Vec<_>>();
3852
3853 let ranges = match columnar_state {
3854 ColumnarSelectionState::FromMouse { .. } => {
3855 let mut non_empty_ranges = selection_ranges
3856 .iter()
3857 .filter(|selection_range| selection_range.start != selection_range.end)
3858 .peekable();
3859 if non_empty_ranges.peek().is_some() {
3860 non_empty_ranges.cloned().collect()
3861 } else {
3862 selection_ranges
3863 }
3864 }
3865 _ => selection_ranges,
3866 };
3867
3868 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3869 s.select_ranges(ranges);
3870 });
3871 cx.notify();
3872 }
3873
3874 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3875 self.selections
3876 .all_adjusted(cx)
3877 .iter()
3878 .any(|selection| !selection.is_empty())
3879 }
3880
3881 pub fn has_pending_nonempty_selection(&self) -> bool {
3882 let pending_nonempty_selection = match self.selections.pending_anchor() {
3883 Some(Selection { start, end, .. }) => start != end,
3884 None => false,
3885 };
3886
3887 pending_nonempty_selection
3888 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3889 }
3890
3891 pub fn has_pending_selection(&self) -> bool {
3892 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3893 }
3894
3895 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3896 self.selection_mark_mode = false;
3897 self.selection_drag_state = SelectionDragState::None;
3898
3899 if self.clear_expanded_diff_hunks(cx) {
3900 cx.notify();
3901 return;
3902 }
3903 if self.dismiss_menus_and_popups(true, window, cx) {
3904 return;
3905 }
3906
3907 if self.mode.is_full()
3908 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3909 {
3910 return;
3911 }
3912
3913 cx.propagate();
3914 }
3915
3916 pub fn dismiss_menus_and_popups(
3917 &mut self,
3918 is_user_requested: bool,
3919 window: &mut Window,
3920 cx: &mut Context<Self>,
3921 ) -> bool {
3922 if self.take_rename(false, window, cx).is_some() {
3923 return true;
3924 }
3925
3926 if hide_hover(self, cx) {
3927 return true;
3928 }
3929
3930 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3931 return true;
3932 }
3933
3934 if self.hide_context_menu(window, cx).is_some() {
3935 return true;
3936 }
3937
3938 if self.mouse_context_menu.take().is_some() {
3939 return true;
3940 }
3941
3942 if is_user_requested && self.discard_edit_prediction(true, cx) {
3943 return true;
3944 }
3945
3946 if self.snippet_stack.pop().is_some() {
3947 return true;
3948 }
3949
3950 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3951 self.dismiss_diagnostics(cx);
3952 return true;
3953 }
3954
3955 false
3956 }
3957
3958 fn linked_editing_ranges_for(
3959 &self,
3960 selection: Range<text::Anchor>,
3961 cx: &App,
3962 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3963 if self.linked_edit_ranges.is_empty() {
3964 return None;
3965 }
3966 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3967 selection.end.buffer_id.and_then(|end_buffer_id| {
3968 if selection.start.buffer_id != Some(end_buffer_id) {
3969 return None;
3970 }
3971 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3972 let snapshot = buffer.read(cx).snapshot();
3973 self.linked_edit_ranges
3974 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3975 .map(|ranges| (ranges, snapshot, buffer))
3976 })?;
3977 use text::ToOffset as TO;
3978 // find offset from the start of current range to current cursor position
3979 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3980
3981 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3982 let start_difference = start_offset - start_byte_offset;
3983 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3984 let end_difference = end_offset - start_byte_offset;
3985 // Current range has associated linked ranges.
3986 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3987 for range in linked_ranges.iter() {
3988 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3989 let end_offset = start_offset + end_difference;
3990 let start_offset = start_offset + start_difference;
3991 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3992 continue;
3993 }
3994 if self.selections.disjoint_anchor_ranges().any(|s| {
3995 if s.start.buffer_id != selection.start.buffer_id
3996 || s.end.buffer_id != selection.end.buffer_id
3997 {
3998 return false;
3999 }
4000 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4001 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4002 }) {
4003 continue;
4004 }
4005 let start = buffer_snapshot.anchor_after(start_offset);
4006 let end = buffer_snapshot.anchor_after(end_offset);
4007 linked_edits
4008 .entry(buffer.clone())
4009 .or_default()
4010 .push(start..end);
4011 }
4012 Some(linked_edits)
4013 }
4014
4015 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4016 let text: Arc<str> = text.into();
4017
4018 if self.read_only(cx) {
4019 return;
4020 }
4021
4022 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4023
4024 let selections = self.selections.all_adjusted(cx);
4025 let mut bracket_inserted = false;
4026 let mut edits = Vec::new();
4027 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4028 let mut new_selections = Vec::with_capacity(selections.len());
4029 let mut new_autoclose_regions = Vec::new();
4030 let snapshot = self.buffer.read(cx).read(cx);
4031 let mut clear_linked_edit_ranges = false;
4032
4033 for (selection, autoclose_region) in
4034 self.selections_with_autoclose_regions(selections, &snapshot)
4035 {
4036 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4037 // Determine if the inserted text matches the opening or closing
4038 // bracket of any of this language's bracket pairs.
4039 let mut bracket_pair = None;
4040 let mut is_bracket_pair_start = false;
4041 let mut is_bracket_pair_end = false;
4042 if !text.is_empty() {
4043 let mut bracket_pair_matching_end = None;
4044 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4045 // and they are removing the character that triggered IME popup.
4046 for (pair, enabled) in scope.brackets() {
4047 if !pair.close && !pair.surround {
4048 continue;
4049 }
4050
4051 if enabled && pair.start.ends_with(text.as_ref()) {
4052 let prefix_len = pair.start.len() - text.len();
4053 let preceding_text_matches_prefix = prefix_len == 0
4054 || (selection.start.column >= (prefix_len as u32)
4055 && snapshot.contains_str_at(
4056 Point::new(
4057 selection.start.row,
4058 selection.start.column - (prefix_len as u32),
4059 ),
4060 &pair.start[..prefix_len],
4061 ));
4062 if preceding_text_matches_prefix {
4063 bracket_pair = Some(pair.clone());
4064 is_bracket_pair_start = true;
4065 break;
4066 }
4067 }
4068 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4069 {
4070 // take first bracket pair matching end, but don't break in case a later bracket
4071 // pair matches start
4072 bracket_pair_matching_end = Some(pair.clone());
4073 }
4074 }
4075 if let Some(end) = bracket_pair_matching_end
4076 && bracket_pair.is_none()
4077 {
4078 bracket_pair = Some(end);
4079 is_bracket_pair_end = true;
4080 }
4081 }
4082
4083 if let Some(bracket_pair) = bracket_pair {
4084 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4085 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4086 let auto_surround =
4087 self.use_auto_surround && snapshot_settings.use_auto_surround;
4088 if selection.is_empty() {
4089 if is_bracket_pair_start {
4090 // If the inserted text is a suffix of an opening bracket and the
4091 // selection is preceded by the rest of the opening bracket, then
4092 // insert the closing bracket.
4093 let following_text_allows_autoclose = snapshot
4094 .chars_at(selection.start)
4095 .next()
4096 .is_none_or(|c| scope.should_autoclose_before(c));
4097
4098 let preceding_text_allows_autoclose = selection.start.column == 0
4099 || snapshot
4100 .reversed_chars_at(selection.start)
4101 .next()
4102 .is_none_or(|c| {
4103 bracket_pair.start != bracket_pair.end
4104 || !snapshot
4105 .char_classifier_at(selection.start)
4106 .is_word(c)
4107 });
4108
4109 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4110 && bracket_pair.start.len() == 1
4111 {
4112 let target = bracket_pair.start.chars().next().unwrap();
4113 let current_line_count = snapshot
4114 .reversed_chars_at(selection.start)
4115 .take_while(|&c| c != '\n')
4116 .filter(|&c| c == target)
4117 .count();
4118 current_line_count % 2 == 1
4119 } else {
4120 false
4121 };
4122
4123 if autoclose
4124 && bracket_pair.close
4125 && following_text_allows_autoclose
4126 && preceding_text_allows_autoclose
4127 && !is_closing_quote
4128 {
4129 let anchor = snapshot.anchor_before(selection.end);
4130 new_selections.push((selection.map(|_| anchor), text.len()));
4131 new_autoclose_regions.push((
4132 anchor,
4133 text.len(),
4134 selection.id,
4135 bracket_pair.clone(),
4136 ));
4137 edits.push((
4138 selection.range(),
4139 format!("{}{}", text, bracket_pair.end).into(),
4140 ));
4141 bracket_inserted = true;
4142 continue;
4143 }
4144 }
4145
4146 if let Some(region) = autoclose_region {
4147 // If the selection is followed by an auto-inserted closing bracket,
4148 // then don't insert that closing bracket again; just move the selection
4149 // past the closing bracket.
4150 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4151 && text.as_ref() == region.pair.end.as_str()
4152 && snapshot.contains_str_at(region.range.end, text.as_ref());
4153 if should_skip {
4154 let anchor = snapshot.anchor_after(selection.end);
4155 new_selections
4156 .push((selection.map(|_| anchor), region.pair.end.len()));
4157 continue;
4158 }
4159 }
4160
4161 let always_treat_brackets_as_autoclosed = snapshot
4162 .language_settings_at(selection.start, cx)
4163 .always_treat_brackets_as_autoclosed;
4164 if always_treat_brackets_as_autoclosed
4165 && is_bracket_pair_end
4166 && snapshot.contains_str_at(selection.end, text.as_ref())
4167 {
4168 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4169 // and the inserted text is a closing bracket and the selection is followed
4170 // by the closing bracket then move the selection past the closing bracket.
4171 let anchor = snapshot.anchor_after(selection.end);
4172 new_selections.push((selection.map(|_| anchor), text.len()));
4173 continue;
4174 }
4175 }
4176 // If an opening bracket is 1 character long and is typed while
4177 // text is selected, then surround that text with the bracket pair.
4178 else if auto_surround
4179 && bracket_pair.surround
4180 && is_bracket_pair_start
4181 && bracket_pair.start.chars().count() == 1
4182 {
4183 edits.push((selection.start..selection.start, text.clone()));
4184 edits.push((
4185 selection.end..selection.end,
4186 bracket_pair.end.as_str().into(),
4187 ));
4188 bracket_inserted = true;
4189 new_selections.push((
4190 Selection {
4191 id: selection.id,
4192 start: snapshot.anchor_after(selection.start),
4193 end: snapshot.anchor_before(selection.end),
4194 reversed: selection.reversed,
4195 goal: selection.goal,
4196 },
4197 0,
4198 ));
4199 continue;
4200 }
4201 }
4202 }
4203
4204 if self.auto_replace_emoji_shortcode
4205 && selection.is_empty()
4206 && text.as_ref().ends_with(':')
4207 && let Some(possible_emoji_short_code) =
4208 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4209 && !possible_emoji_short_code.is_empty()
4210 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4211 {
4212 let emoji_shortcode_start = Point::new(
4213 selection.start.row,
4214 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4215 );
4216
4217 // Remove shortcode from buffer
4218 edits.push((
4219 emoji_shortcode_start..selection.start,
4220 "".to_string().into(),
4221 ));
4222 new_selections.push((
4223 Selection {
4224 id: selection.id,
4225 start: snapshot.anchor_after(emoji_shortcode_start),
4226 end: snapshot.anchor_before(selection.start),
4227 reversed: selection.reversed,
4228 goal: selection.goal,
4229 },
4230 0,
4231 ));
4232
4233 // Insert emoji
4234 let selection_start_anchor = snapshot.anchor_after(selection.start);
4235 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4236 edits.push((selection.start..selection.end, emoji.to_string().into()));
4237
4238 continue;
4239 }
4240
4241 // If not handling any auto-close operation, then just replace the selected
4242 // text with the given input and move the selection to the end of the
4243 // newly inserted text.
4244 let anchor = snapshot.anchor_after(selection.end);
4245 if !self.linked_edit_ranges.is_empty() {
4246 let start_anchor = snapshot.anchor_before(selection.start);
4247
4248 let is_word_char = text.chars().next().is_none_or(|char| {
4249 let classifier = snapshot
4250 .char_classifier_at(start_anchor.to_offset(&snapshot))
4251 .ignore_punctuation(true);
4252 classifier.is_word(char)
4253 });
4254
4255 if is_word_char {
4256 if let Some(ranges) = self
4257 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4258 {
4259 for (buffer, edits) in ranges {
4260 linked_edits
4261 .entry(buffer.clone())
4262 .or_default()
4263 .extend(edits.into_iter().map(|range| (range, text.clone())));
4264 }
4265 }
4266 } else {
4267 clear_linked_edit_ranges = true;
4268 }
4269 }
4270
4271 new_selections.push((selection.map(|_| anchor), 0));
4272 edits.push((selection.start..selection.end, text.clone()));
4273 }
4274
4275 drop(snapshot);
4276
4277 self.transact(window, cx, |this, window, cx| {
4278 if clear_linked_edit_ranges {
4279 this.linked_edit_ranges.clear();
4280 }
4281 let initial_buffer_versions =
4282 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4283
4284 this.buffer.update(cx, |buffer, cx| {
4285 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4286 });
4287 for (buffer, edits) in linked_edits {
4288 buffer.update(cx, |buffer, cx| {
4289 let snapshot = buffer.snapshot();
4290 let edits = edits
4291 .into_iter()
4292 .map(|(range, text)| {
4293 use text::ToPoint as TP;
4294 let end_point = TP::to_point(&range.end, &snapshot);
4295 let start_point = TP::to_point(&range.start, &snapshot);
4296 (start_point..end_point, text)
4297 })
4298 .sorted_by_key(|(range, _)| range.start);
4299 buffer.edit(edits, None, cx);
4300 })
4301 }
4302 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4303 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4304 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4305 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4306 .zip(new_selection_deltas)
4307 .map(|(selection, delta)| Selection {
4308 id: selection.id,
4309 start: selection.start + delta,
4310 end: selection.end + delta,
4311 reversed: selection.reversed,
4312 goal: SelectionGoal::None,
4313 })
4314 .collect::<Vec<_>>();
4315
4316 let mut i = 0;
4317 for (position, delta, selection_id, pair) in new_autoclose_regions {
4318 let position = position.to_offset(&map.buffer_snapshot) + delta;
4319 let start = map.buffer_snapshot.anchor_before(position);
4320 let end = map.buffer_snapshot.anchor_after(position);
4321 while let Some(existing_state) = this.autoclose_regions.get(i) {
4322 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4323 Ordering::Less => i += 1,
4324 Ordering::Greater => break,
4325 Ordering::Equal => {
4326 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4327 Ordering::Less => i += 1,
4328 Ordering::Equal => break,
4329 Ordering::Greater => break,
4330 }
4331 }
4332 }
4333 }
4334 this.autoclose_regions.insert(
4335 i,
4336 AutocloseRegion {
4337 selection_id,
4338 range: start..end,
4339 pair,
4340 },
4341 );
4342 }
4343
4344 let had_active_edit_prediction = this.has_active_edit_prediction();
4345 this.change_selections(
4346 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4347 window,
4348 cx,
4349 |s| s.select(new_selections),
4350 );
4351
4352 if !bracket_inserted
4353 && let Some(on_type_format_task) =
4354 this.trigger_on_type_formatting(text.to_string(), window, cx)
4355 {
4356 on_type_format_task.detach_and_log_err(cx);
4357 }
4358
4359 let editor_settings = EditorSettings::get_global(cx);
4360 if bracket_inserted
4361 && (editor_settings.auto_signature_help
4362 || editor_settings.show_signature_help_after_edits)
4363 {
4364 this.show_signature_help(&ShowSignatureHelp, window, cx);
4365 }
4366
4367 let trigger_in_words =
4368 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4369 if this.hard_wrap.is_some() {
4370 let latest: Range<Point> = this.selections.newest(cx).range();
4371 if latest.is_empty()
4372 && this
4373 .buffer()
4374 .read(cx)
4375 .snapshot(cx)
4376 .line_len(MultiBufferRow(latest.start.row))
4377 == latest.start.column
4378 {
4379 this.rewrap_impl(
4380 RewrapOptions {
4381 override_language_settings: true,
4382 preserve_existing_whitespace: true,
4383 },
4384 cx,
4385 )
4386 }
4387 }
4388 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4389 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4390 this.refresh_edit_prediction(true, false, window, cx);
4391 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4392 });
4393 }
4394
4395 fn find_possible_emoji_shortcode_at_position(
4396 snapshot: &MultiBufferSnapshot,
4397 position: Point,
4398 ) -> Option<String> {
4399 let mut chars = Vec::new();
4400 let mut found_colon = false;
4401 for char in snapshot.reversed_chars_at(position).take(100) {
4402 // Found a possible emoji shortcode in the middle of the buffer
4403 if found_colon {
4404 if char.is_whitespace() {
4405 chars.reverse();
4406 return Some(chars.iter().collect());
4407 }
4408 // If the previous character is not a whitespace, we are in the middle of a word
4409 // and we only want to complete the shortcode if the word is made up of other emojis
4410 let mut containing_word = String::new();
4411 for ch in snapshot
4412 .reversed_chars_at(position)
4413 .skip(chars.len() + 1)
4414 .take(100)
4415 {
4416 if ch.is_whitespace() {
4417 break;
4418 }
4419 containing_word.push(ch);
4420 }
4421 let containing_word = containing_word.chars().rev().collect::<String>();
4422 if util::word_consists_of_emojis(containing_word.as_str()) {
4423 chars.reverse();
4424 return Some(chars.iter().collect());
4425 }
4426 }
4427
4428 if char.is_whitespace() || !char.is_ascii() {
4429 return None;
4430 }
4431 if char == ':' {
4432 found_colon = true;
4433 } else {
4434 chars.push(char);
4435 }
4436 }
4437 // Found a possible emoji shortcode at the beginning of the buffer
4438 chars.reverse();
4439 Some(chars.iter().collect())
4440 }
4441
4442 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4443 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4444 self.transact(window, cx, |this, window, cx| {
4445 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4446 let selections = this.selections.all::<usize>(cx);
4447 let multi_buffer = this.buffer.read(cx);
4448 let buffer = multi_buffer.snapshot(cx);
4449 selections
4450 .iter()
4451 .map(|selection| {
4452 let start_point = selection.start.to_point(&buffer);
4453 let mut existing_indent =
4454 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4455 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4456 let start = selection.start;
4457 let end = selection.end;
4458 let selection_is_empty = start == end;
4459 let language_scope = buffer.language_scope_at(start);
4460 let (
4461 comment_delimiter,
4462 doc_delimiter,
4463 insert_extra_newline,
4464 indent_on_newline,
4465 indent_on_extra_newline,
4466 ) = if let Some(language) = &language_scope {
4467 let mut insert_extra_newline =
4468 insert_extra_newline_brackets(&buffer, start..end, language)
4469 || insert_extra_newline_tree_sitter(&buffer, start..end);
4470
4471 // Comment extension on newline is allowed only for cursor selections
4472 let comment_delimiter = maybe!({
4473 if !selection_is_empty {
4474 return None;
4475 }
4476
4477 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4478 return None;
4479 }
4480
4481 let delimiters = language.line_comment_prefixes();
4482 let max_len_of_delimiter =
4483 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4484 let (snapshot, range) =
4485 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4486
4487 let num_of_whitespaces = snapshot
4488 .chars_for_range(range.clone())
4489 .take_while(|c| c.is_whitespace())
4490 .count();
4491 let comment_candidate = snapshot
4492 .chars_for_range(range.clone())
4493 .skip(num_of_whitespaces)
4494 .take(max_len_of_delimiter)
4495 .collect::<String>();
4496 let (delimiter, trimmed_len) = delimiters
4497 .iter()
4498 .filter_map(|delimiter| {
4499 let prefix = delimiter.trim_end();
4500 if comment_candidate.starts_with(prefix) {
4501 Some((delimiter, prefix.len()))
4502 } else {
4503 None
4504 }
4505 })
4506 .max_by_key(|(_, len)| *len)?;
4507
4508 if let Some(BlockCommentConfig {
4509 start: block_start, ..
4510 }) = language.block_comment()
4511 {
4512 let block_start_trimmed = block_start.trim_end();
4513 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4514 let line_content = snapshot
4515 .chars_for_range(range)
4516 .skip(num_of_whitespaces)
4517 .take(block_start_trimmed.len())
4518 .collect::<String>();
4519
4520 if line_content.starts_with(block_start_trimmed) {
4521 return None;
4522 }
4523 }
4524 }
4525
4526 let cursor_is_placed_after_comment_marker =
4527 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4528 if cursor_is_placed_after_comment_marker {
4529 Some(delimiter.clone())
4530 } else {
4531 None
4532 }
4533 });
4534
4535 let mut indent_on_newline = IndentSize::spaces(0);
4536 let mut indent_on_extra_newline = IndentSize::spaces(0);
4537
4538 let doc_delimiter = maybe!({
4539 if !selection_is_empty {
4540 return None;
4541 }
4542
4543 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4544 return None;
4545 }
4546
4547 let BlockCommentConfig {
4548 start: start_tag,
4549 end: end_tag,
4550 prefix: delimiter,
4551 tab_size: len,
4552 } = language.documentation_comment()?;
4553 let is_within_block_comment = buffer
4554 .language_scope_at(start_point)
4555 .is_some_and(|scope| scope.override_name() == Some("comment"));
4556 if !is_within_block_comment {
4557 return None;
4558 }
4559
4560 let (snapshot, range) =
4561 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4562
4563 let num_of_whitespaces = snapshot
4564 .chars_for_range(range.clone())
4565 .take_while(|c| c.is_whitespace())
4566 .count();
4567
4568 // 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.
4569 let column = start_point.column;
4570 let cursor_is_after_start_tag = {
4571 let start_tag_len = start_tag.len();
4572 let start_tag_line = snapshot
4573 .chars_for_range(range.clone())
4574 .skip(num_of_whitespaces)
4575 .take(start_tag_len)
4576 .collect::<String>();
4577 if start_tag_line.starts_with(start_tag.as_ref()) {
4578 num_of_whitespaces + start_tag_len <= column as usize
4579 } else {
4580 false
4581 }
4582 };
4583
4584 let cursor_is_after_delimiter = {
4585 let delimiter_trim = delimiter.trim_end();
4586 let delimiter_line = snapshot
4587 .chars_for_range(range.clone())
4588 .skip(num_of_whitespaces)
4589 .take(delimiter_trim.len())
4590 .collect::<String>();
4591 if delimiter_line.starts_with(delimiter_trim) {
4592 num_of_whitespaces + delimiter_trim.len() <= column as usize
4593 } else {
4594 false
4595 }
4596 };
4597
4598 let cursor_is_before_end_tag_if_exists = {
4599 let mut char_position = 0u32;
4600 let mut end_tag_offset = None;
4601
4602 'outer: for chunk in snapshot.text_for_range(range) {
4603 if let Some(byte_pos) = chunk.find(&**end_tag) {
4604 let chars_before_match =
4605 chunk[..byte_pos].chars().count() as u32;
4606 end_tag_offset =
4607 Some(char_position + chars_before_match);
4608 break 'outer;
4609 }
4610 char_position += chunk.chars().count() as u32;
4611 }
4612
4613 if let Some(end_tag_offset) = end_tag_offset {
4614 let cursor_is_before_end_tag = column <= end_tag_offset;
4615 if cursor_is_after_start_tag {
4616 if cursor_is_before_end_tag {
4617 insert_extra_newline = true;
4618 }
4619 let cursor_is_at_start_of_end_tag =
4620 column == end_tag_offset;
4621 if cursor_is_at_start_of_end_tag {
4622 indent_on_extra_newline.len = *len;
4623 }
4624 }
4625 cursor_is_before_end_tag
4626 } else {
4627 true
4628 }
4629 };
4630
4631 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4632 && cursor_is_before_end_tag_if_exists
4633 {
4634 if cursor_is_after_start_tag {
4635 indent_on_newline.len = *len;
4636 }
4637 Some(delimiter.clone())
4638 } else {
4639 None
4640 }
4641 });
4642
4643 (
4644 comment_delimiter,
4645 doc_delimiter,
4646 insert_extra_newline,
4647 indent_on_newline,
4648 indent_on_extra_newline,
4649 )
4650 } else {
4651 (
4652 None,
4653 None,
4654 false,
4655 IndentSize::default(),
4656 IndentSize::default(),
4657 )
4658 };
4659
4660 let prevent_auto_indent = doc_delimiter.is_some();
4661 let delimiter = comment_delimiter.or(doc_delimiter);
4662
4663 let capacity_for_delimiter =
4664 delimiter.as_deref().map(str::len).unwrap_or_default();
4665 let mut new_text = String::with_capacity(
4666 1 + capacity_for_delimiter
4667 + existing_indent.len as usize
4668 + indent_on_newline.len as usize
4669 + indent_on_extra_newline.len as usize,
4670 );
4671 new_text.push('\n');
4672 new_text.extend(existing_indent.chars());
4673 new_text.extend(indent_on_newline.chars());
4674
4675 if let Some(delimiter) = &delimiter {
4676 new_text.push_str(delimiter);
4677 }
4678
4679 if insert_extra_newline {
4680 new_text.push('\n');
4681 new_text.extend(existing_indent.chars());
4682 new_text.extend(indent_on_extra_newline.chars());
4683 }
4684
4685 let anchor = buffer.anchor_after(end);
4686 let new_selection = selection.map(|_| anchor);
4687 (
4688 ((start..end, new_text), prevent_auto_indent),
4689 (insert_extra_newline, new_selection),
4690 )
4691 })
4692 .unzip()
4693 };
4694
4695 let mut auto_indent_edits = Vec::new();
4696 let mut edits = Vec::new();
4697 for (edit, prevent_auto_indent) in edits_with_flags {
4698 if prevent_auto_indent {
4699 edits.push(edit);
4700 } else {
4701 auto_indent_edits.push(edit);
4702 }
4703 }
4704 if !edits.is_empty() {
4705 this.edit(edits, cx);
4706 }
4707 if !auto_indent_edits.is_empty() {
4708 this.edit_with_autoindent(auto_indent_edits, cx);
4709 }
4710
4711 let buffer = this.buffer.read(cx).snapshot(cx);
4712 let new_selections = selection_info
4713 .into_iter()
4714 .map(|(extra_newline_inserted, new_selection)| {
4715 let mut cursor = new_selection.end.to_point(&buffer);
4716 if extra_newline_inserted {
4717 cursor.row -= 1;
4718 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4719 }
4720 new_selection.map(|_| cursor)
4721 })
4722 .collect();
4723
4724 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4725 this.refresh_edit_prediction(true, false, window, cx);
4726 });
4727 }
4728
4729 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4731
4732 let buffer = self.buffer.read(cx);
4733 let snapshot = buffer.snapshot(cx);
4734
4735 let mut edits = Vec::new();
4736 let mut rows = Vec::new();
4737
4738 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4739 let cursor = selection.head();
4740 let row = cursor.row;
4741
4742 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4743
4744 let newline = "\n".to_string();
4745 edits.push((start_of_line..start_of_line, newline));
4746
4747 rows.push(row + rows_inserted as u32);
4748 }
4749
4750 self.transact(window, cx, |editor, window, cx| {
4751 editor.edit(edits, cx);
4752
4753 editor.change_selections(Default::default(), window, cx, |s| {
4754 let mut index = 0;
4755 s.move_cursors_with(|map, _, _| {
4756 let row = rows[index];
4757 index += 1;
4758
4759 let point = Point::new(row, 0);
4760 let boundary = map.next_line_boundary(point).1;
4761 let clipped = map.clip_point(boundary, Bias::Left);
4762
4763 (clipped, SelectionGoal::None)
4764 });
4765 });
4766
4767 let mut indent_edits = Vec::new();
4768 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4769 for row in rows {
4770 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4771 for (row, indent) in indents {
4772 if indent.len == 0 {
4773 continue;
4774 }
4775
4776 let text = match indent.kind {
4777 IndentKind::Space => " ".repeat(indent.len as usize),
4778 IndentKind::Tab => "\t".repeat(indent.len as usize),
4779 };
4780 let point = Point::new(row.0, 0);
4781 indent_edits.push((point..point, text));
4782 }
4783 }
4784 editor.edit(indent_edits, cx);
4785 });
4786 }
4787
4788 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4790
4791 let buffer = self.buffer.read(cx);
4792 let snapshot = buffer.snapshot(cx);
4793
4794 let mut edits = Vec::new();
4795 let mut rows = Vec::new();
4796 let mut rows_inserted = 0;
4797
4798 for selection in self.selections.all_adjusted(cx) {
4799 let cursor = selection.head();
4800 let row = cursor.row;
4801
4802 let point = Point::new(row + 1, 0);
4803 let start_of_line = snapshot.clip_point(point, Bias::Left);
4804
4805 let newline = "\n".to_string();
4806 edits.push((start_of_line..start_of_line, newline));
4807
4808 rows_inserted += 1;
4809 rows.push(row + rows_inserted);
4810 }
4811
4812 self.transact(window, cx, |editor, window, cx| {
4813 editor.edit(edits, cx);
4814
4815 editor.change_selections(Default::default(), window, cx, |s| {
4816 let mut index = 0;
4817 s.move_cursors_with(|map, _, _| {
4818 let row = rows[index];
4819 index += 1;
4820
4821 let point = Point::new(row, 0);
4822 let boundary = map.next_line_boundary(point).1;
4823 let clipped = map.clip_point(boundary, Bias::Left);
4824
4825 (clipped, SelectionGoal::None)
4826 });
4827 });
4828
4829 let mut indent_edits = Vec::new();
4830 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4831 for row in rows {
4832 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4833 for (row, indent) in indents {
4834 if indent.len == 0 {
4835 continue;
4836 }
4837
4838 let text = match indent.kind {
4839 IndentKind::Space => " ".repeat(indent.len as usize),
4840 IndentKind::Tab => "\t".repeat(indent.len as usize),
4841 };
4842 let point = Point::new(row.0, 0);
4843 indent_edits.push((point..point, text));
4844 }
4845 }
4846 editor.edit(indent_edits, cx);
4847 });
4848 }
4849
4850 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4851 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4852 original_indent_columns: Vec::new(),
4853 });
4854 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4855 }
4856
4857 fn insert_with_autoindent_mode(
4858 &mut self,
4859 text: &str,
4860 autoindent_mode: Option<AutoindentMode>,
4861 window: &mut Window,
4862 cx: &mut Context<Self>,
4863 ) {
4864 if self.read_only(cx) {
4865 return;
4866 }
4867
4868 let text: Arc<str> = text.into();
4869 self.transact(window, cx, |this, window, cx| {
4870 let old_selections = this.selections.all_adjusted(cx);
4871 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4872 let anchors = {
4873 let snapshot = buffer.read(cx);
4874 old_selections
4875 .iter()
4876 .map(|s| {
4877 let anchor = snapshot.anchor_after(s.head());
4878 s.map(|_| anchor)
4879 })
4880 .collect::<Vec<_>>()
4881 };
4882 buffer.edit(
4883 old_selections
4884 .iter()
4885 .map(|s| (s.start..s.end, text.clone())),
4886 autoindent_mode,
4887 cx,
4888 );
4889 anchors
4890 });
4891
4892 this.change_selections(Default::default(), window, cx, |s| {
4893 s.select_anchors(selection_anchors);
4894 });
4895
4896 cx.notify();
4897 });
4898 }
4899
4900 fn trigger_completion_on_input(
4901 &mut self,
4902 text: &str,
4903 trigger_in_words: bool,
4904 window: &mut Window,
4905 cx: &mut Context<Self>,
4906 ) {
4907 let completions_source = self
4908 .context_menu
4909 .borrow()
4910 .as_ref()
4911 .and_then(|menu| match menu {
4912 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4913 CodeContextMenu::CodeActions(_) => None,
4914 });
4915
4916 match completions_source {
4917 Some(CompletionsMenuSource::Words { .. }) => {
4918 self.open_or_update_completions_menu(
4919 Some(CompletionsMenuSource::Words {
4920 ignore_threshold: false,
4921 }),
4922 None,
4923 window,
4924 cx,
4925 );
4926 }
4927 Some(CompletionsMenuSource::Normal)
4928 | Some(CompletionsMenuSource::SnippetChoices)
4929 | None
4930 if self.is_completion_trigger(
4931 text,
4932 trigger_in_words,
4933 completions_source.is_some(),
4934 cx,
4935 ) =>
4936 {
4937 self.show_completions(
4938 &ShowCompletions {
4939 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4940 },
4941 window,
4942 cx,
4943 )
4944 }
4945 _ => {
4946 self.hide_context_menu(window, cx);
4947 }
4948 }
4949 }
4950
4951 fn is_completion_trigger(
4952 &self,
4953 text: &str,
4954 trigger_in_words: bool,
4955 menu_is_open: bool,
4956 cx: &mut Context<Self>,
4957 ) -> bool {
4958 let position = self.selections.newest_anchor().head();
4959 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4960 return false;
4961 };
4962
4963 if let Some(completion_provider) = &self.completion_provider {
4964 completion_provider.is_completion_trigger(
4965 &buffer,
4966 position.text_anchor,
4967 text,
4968 trigger_in_words,
4969 menu_is_open,
4970 cx,
4971 )
4972 } else {
4973 false
4974 }
4975 }
4976
4977 /// If any empty selections is touching the start of its innermost containing autoclose
4978 /// region, expand it to select the brackets.
4979 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4980 let selections = self.selections.all::<usize>(cx);
4981 let buffer = self.buffer.read(cx).read(cx);
4982 let new_selections = self
4983 .selections_with_autoclose_regions(selections, &buffer)
4984 .map(|(mut selection, region)| {
4985 if !selection.is_empty() {
4986 return selection;
4987 }
4988
4989 if let Some(region) = region {
4990 let mut range = region.range.to_offset(&buffer);
4991 if selection.start == range.start && range.start >= region.pair.start.len() {
4992 range.start -= region.pair.start.len();
4993 if buffer.contains_str_at(range.start, ®ion.pair.start)
4994 && buffer.contains_str_at(range.end, ®ion.pair.end)
4995 {
4996 range.end += region.pair.end.len();
4997 selection.start = range.start;
4998 selection.end = range.end;
4999
5000 return selection;
5001 }
5002 }
5003 }
5004
5005 let always_treat_brackets_as_autoclosed = buffer
5006 .language_settings_at(selection.start, cx)
5007 .always_treat_brackets_as_autoclosed;
5008
5009 if !always_treat_brackets_as_autoclosed {
5010 return selection;
5011 }
5012
5013 if let Some(scope) = buffer.language_scope_at(selection.start) {
5014 for (pair, enabled) in scope.brackets() {
5015 if !enabled || !pair.close {
5016 continue;
5017 }
5018
5019 if buffer.contains_str_at(selection.start, &pair.end) {
5020 let pair_start_len = pair.start.len();
5021 if buffer.contains_str_at(
5022 selection.start.saturating_sub(pair_start_len),
5023 &pair.start,
5024 ) {
5025 selection.start -= pair_start_len;
5026 selection.end += pair.end.len();
5027
5028 return selection;
5029 }
5030 }
5031 }
5032 }
5033
5034 selection
5035 })
5036 .collect();
5037
5038 drop(buffer);
5039 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5040 selections.select(new_selections)
5041 });
5042 }
5043
5044 /// Iterate the given selections, and for each one, find the smallest surrounding
5045 /// autoclose region. This uses the ordering of the selections and the autoclose
5046 /// regions to avoid repeated comparisons.
5047 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5048 &'a self,
5049 selections: impl IntoIterator<Item = Selection<D>>,
5050 buffer: &'a MultiBufferSnapshot,
5051 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5052 let mut i = 0;
5053 let mut regions = self.autoclose_regions.as_slice();
5054 selections.into_iter().map(move |selection| {
5055 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5056
5057 let mut enclosing = None;
5058 while let Some(pair_state) = regions.get(i) {
5059 if pair_state.range.end.to_offset(buffer) < range.start {
5060 regions = ®ions[i + 1..];
5061 i = 0;
5062 } else if pair_state.range.start.to_offset(buffer) > range.end {
5063 break;
5064 } else {
5065 if pair_state.selection_id == selection.id {
5066 enclosing = Some(pair_state);
5067 }
5068 i += 1;
5069 }
5070 }
5071
5072 (selection, enclosing)
5073 })
5074 }
5075
5076 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5077 fn invalidate_autoclose_regions(
5078 &mut self,
5079 mut selections: &[Selection<Anchor>],
5080 buffer: &MultiBufferSnapshot,
5081 ) {
5082 self.autoclose_regions.retain(|state| {
5083 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5084 return false;
5085 }
5086
5087 let mut i = 0;
5088 while let Some(selection) = selections.get(i) {
5089 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5090 selections = &selections[1..];
5091 continue;
5092 }
5093 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5094 break;
5095 }
5096 if selection.id == state.selection_id {
5097 return true;
5098 } else {
5099 i += 1;
5100 }
5101 }
5102 false
5103 });
5104 }
5105
5106 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5107 let offset = position.to_offset(buffer);
5108 let (word_range, kind) = buffer.surrounding_word(offset, true);
5109 if offset > word_range.start && kind == Some(CharKind::Word) {
5110 Some(
5111 buffer
5112 .text_for_range(word_range.start..offset)
5113 .collect::<String>(),
5114 )
5115 } else {
5116 None
5117 }
5118 }
5119
5120 pub fn toggle_inline_values(
5121 &mut self,
5122 _: &ToggleInlineValues,
5123 _: &mut Window,
5124 cx: &mut Context<Self>,
5125 ) {
5126 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5127
5128 self.refresh_inline_values(cx);
5129 }
5130
5131 pub fn toggle_inlay_hints(
5132 &mut self,
5133 _: &ToggleInlayHints,
5134 _: &mut Window,
5135 cx: &mut Context<Self>,
5136 ) {
5137 self.refresh_inlay_hints(
5138 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5139 cx,
5140 );
5141 }
5142
5143 pub fn inlay_hints_enabled(&self) -> bool {
5144 self.inlay_hint_cache.enabled
5145 }
5146
5147 pub fn inline_values_enabled(&self) -> bool {
5148 self.inline_value_cache.enabled
5149 }
5150
5151 #[cfg(any(test, feature = "test-support"))]
5152 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5153 self.display_map
5154 .read(cx)
5155 .current_inlays()
5156 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5157 .cloned()
5158 .collect()
5159 }
5160
5161 #[cfg(any(test, feature = "test-support"))]
5162 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5163 self.display_map
5164 .read(cx)
5165 .current_inlays()
5166 .cloned()
5167 .collect()
5168 }
5169
5170 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5171 if self.semantics_provider.is_none() || !self.mode.is_full() {
5172 return;
5173 }
5174
5175 let reason_description = reason.description();
5176 let ignore_debounce = matches!(
5177 reason,
5178 InlayHintRefreshReason::SettingsChange(_)
5179 | InlayHintRefreshReason::Toggle(_)
5180 | InlayHintRefreshReason::ExcerptsRemoved(_)
5181 | InlayHintRefreshReason::ModifiersChanged(_)
5182 );
5183 let (invalidate_cache, required_languages) = match reason {
5184 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5185 match self.inlay_hint_cache.modifiers_override(enabled) {
5186 Some(enabled) => {
5187 if enabled {
5188 (InvalidationStrategy::RefreshRequested, None)
5189 } else {
5190 self.splice_inlays(
5191 &self
5192 .visible_inlay_hints(cx)
5193 .iter()
5194 .map(|inlay| inlay.id)
5195 .collect::<Vec<InlayId>>(),
5196 Vec::new(),
5197 cx,
5198 );
5199 return;
5200 }
5201 }
5202 None => return,
5203 }
5204 }
5205 InlayHintRefreshReason::Toggle(enabled) => {
5206 if self.inlay_hint_cache.toggle(enabled) {
5207 if enabled {
5208 (InvalidationStrategy::RefreshRequested, None)
5209 } else {
5210 self.splice_inlays(
5211 &self
5212 .visible_inlay_hints(cx)
5213 .iter()
5214 .map(|inlay| inlay.id)
5215 .collect::<Vec<InlayId>>(),
5216 Vec::new(),
5217 cx,
5218 );
5219 return;
5220 }
5221 } else {
5222 return;
5223 }
5224 }
5225 InlayHintRefreshReason::SettingsChange(new_settings) => {
5226 match self.inlay_hint_cache.update_settings(
5227 &self.buffer,
5228 new_settings,
5229 self.visible_inlay_hints(cx),
5230 cx,
5231 ) {
5232 ControlFlow::Break(Some(InlaySplice {
5233 to_remove,
5234 to_insert,
5235 })) => {
5236 self.splice_inlays(&to_remove, to_insert, cx);
5237 return;
5238 }
5239 ControlFlow::Break(None) => return,
5240 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5241 }
5242 }
5243 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5244 if let Some(InlaySplice {
5245 to_remove,
5246 to_insert,
5247 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5248 {
5249 self.splice_inlays(&to_remove, to_insert, cx);
5250 }
5251 self.display_map.update(cx, |display_map, _| {
5252 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5253 });
5254 return;
5255 }
5256 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5257 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5258 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5259 }
5260 InlayHintRefreshReason::RefreshRequested => {
5261 (InvalidationStrategy::RefreshRequested, None)
5262 }
5263 };
5264
5265 if let Some(InlaySplice {
5266 to_remove,
5267 to_insert,
5268 }) = self.inlay_hint_cache.spawn_hint_refresh(
5269 reason_description,
5270 self.visible_excerpts(required_languages.as_ref(), cx),
5271 invalidate_cache,
5272 ignore_debounce,
5273 cx,
5274 ) {
5275 self.splice_inlays(&to_remove, to_insert, cx);
5276 }
5277 }
5278
5279 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5280 self.display_map
5281 .read(cx)
5282 .current_inlays()
5283 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5284 .cloned()
5285 .collect()
5286 }
5287
5288 pub fn visible_excerpts(
5289 &self,
5290 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5291 cx: &mut Context<Editor>,
5292 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5293 let Some(project) = self.project() else {
5294 return HashMap::default();
5295 };
5296 let project = project.read(cx);
5297 let multi_buffer = self.buffer().read(cx);
5298 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5299 let multi_buffer_visible_start = self
5300 .scroll_manager
5301 .anchor()
5302 .anchor
5303 .to_point(&multi_buffer_snapshot);
5304 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5305 multi_buffer_visible_start
5306 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5307 Bias::Left,
5308 );
5309 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5310 multi_buffer_snapshot
5311 .range_to_buffer_ranges(multi_buffer_visible_range)
5312 .into_iter()
5313 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5314 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5315 let buffer_file = project::File::from_dyn(buffer.file())?;
5316 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5317 let worktree_entry = buffer_worktree
5318 .read(cx)
5319 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5320 if worktree_entry.is_ignored {
5321 return None;
5322 }
5323
5324 let language = buffer.language()?;
5325 if let Some(restrict_to_languages) = restrict_to_languages
5326 && !restrict_to_languages.contains(language)
5327 {
5328 return None;
5329 }
5330 Some((
5331 excerpt_id,
5332 (
5333 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5334 buffer.version().clone(),
5335 excerpt_visible_range,
5336 ),
5337 ))
5338 })
5339 .collect()
5340 }
5341
5342 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5343 TextLayoutDetails {
5344 text_system: window.text_system().clone(),
5345 editor_style: self.style.clone().unwrap(),
5346 rem_size: window.rem_size(),
5347 scroll_anchor: self.scroll_manager.anchor(),
5348 visible_rows: self.visible_line_count(),
5349 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5350 }
5351 }
5352
5353 pub fn splice_inlays(
5354 &self,
5355 to_remove: &[InlayId],
5356 to_insert: Vec<Inlay>,
5357 cx: &mut Context<Self>,
5358 ) {
5359 self.display_map.update(cx, |display_map, cx| {
5360 display_map.splice_inlays(to_remove, to_insert, cx)
5361 });
5362 cx.notify();
5363 }
5364
5365 fn trigger_on_type_formatting(
5366 &self,
5367 input: String,
5368 window: &mut Window,
5369 cx: &mut Context<Self>,
5370 ) -> Option<Task<Result<()>>> {
5371 if input.len() != 1 {
5372 return None;
5373 }
5374
5375 let project = self.project()?;
5376 let position = self.selections.newest_anchor().head();
5377 let (buffer, buffer_position) = self
5378 .buffer
5379 .read(cx)
5380 .text_anchor_for_position(position, cx)?;
5381
5382 let settings = language_settings::language_settings(
5383 buffer
5384 .read(cx)
5385 .language_at(buffer_position)
5386 .map(|l| l.name()),
5387 buffer.read(cx).file(),
5388 cx,
5389 );
5390 if !settings.use_on_type_format {
5391 return None;
5392 }
5393
5394 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5395 // hence we do LSP request & edit on host side only — add formats to host's history.
5396 let push_to_lsp_host_history = true;
5397 // If this is not the host, append its history with new edits.
5398 let push_to_client_history = project.read(cx).is_via_collab();
5399
5400 let on_type_formatting = project.update(cx, |project, cx| {
5401 project.on_type_format(
5402 buffer.clone(),
5403 buffer_position,
5404 input,
5405 push_to_lsp_host_history,
5406 cx,
5407 )
5408 });
5409 Some(cx.spawn_in(window, async move |editor, cx| {
5410 if let Some(transaction) = on_type_formatting.await? {
5411 if push_to_client_history {
5412 buffer
5413 .update(cx, |buffer, _| {
5414 buffer.push_transaction(transaction, Instant::now());
5415 buffer.finalize_last_transaction();
5416 })
5417 .ok();
5418 }
5419 editor.update(cx, |editor, cx| {
5420 editor.refresh_document_highlights(cx);
5421 })?;
5422 }
5423 Ok(())
5424 }))
5425 }
5426
5427 pub fn show_word_completions(
5428 &mut self,
5429 _: &ShowWordCompletions,
5430 window: &mut Window,
5431 cx: &mut Context<Self>,
5432 ) {
5433 self.open_or_update_completions_menu(
5434 Some(CompletionsMenuSource::Words {
5435 ignore_threshold: true,
5436 }),
5437 None,
5438 window,
5439 cx,
5440 );
5441 }
5442
5443 pub fn show_completions(
5444 &mut self,
5445 options: &ShowCompletions,
5446 window: &mut Window,
5447 cx: &mut Context<Self>,
5448 ) {
5449 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5450 }
5451
5452 fn open_or_update_completions_menu(
5453 &mut self,
5454 requested_source: Option<CompletionsMenuSource>,
5455 trigger: Option<&str>,
5456 window: &mut Window,
5457 cx: &mut Context<Self>,
5458 ) {
5459 if self.pending_rename.is_some() {
5460 return;
5461 }
5462
5463 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5464
5465 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5466 // inserted and selected. To handle that case, the start of the selection is used so that
5467 // the menu starts with all choices.
5468 let position = self
5469 .selections
5470 .newest_anchor()
5471 .start
5472 .bias_right(&multibuffer_snapshot);
5473 if position.diff_base_anchor.is_some() {
5474 return;
5475 }
5476 let buffer_position = multibuffer_snapshot.anchor_before(position);
5477 let Some(buffer) = buffer_position
5478 .buffer_id
5479 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5480 else {
5481 return;
5482 };
5483 let buffer_snapshot = buffer.read(cx).snapshot();
5484
5485 let query: Option<Arc<String>> =
5486 Self::completion_query(&multibuffer_snapshot, buffer_position)
5487 .map(|query| query.into());
5488
5489 drop(multibuffer_snapshot);
5490
5491 // Hide the current completions menu when query is empty. Without this, cached
5492 // completions from before the trigger char may be reused (#32774).
5493 if query.is_none() {
5494 let menu_is_open = matches!(
5495 self.context_menu.borrow().as_ref(),
5496 Some(CodeContextMenu::Completions(_))
5497 );
5498 if menu_is_open {
5499 self.hide_context_menu(window, cx);
5500 }
5501 }
5502
5503 let mut ignore_word_threshold = false;
5504 let provider = match requested_source {
5505 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5506 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5507 ignore_word_threshold = ignore_threshold;
5508 None
5509 }
5510 Some(CompletionsMenuSource::SnippetChoices) => {
5511 log::error!("bug: SnippetChoices requested_source is not handled");
5512 None
5513 }
5514 };
5515
5516 let sort_completions = provider
5517 .as_ref()
5518 .is_some_and(|provider| provider.sort_completions());
5519
5520 let filter_completions = provider
5521 .as_ref()
5522 .is_none_or(|provider| provider.filter_completions());
5523
5524 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5525 if filter_completions {
5526 menu.filter(query.clone(), provider.clone(), window, cx);
5527 }
5528 // When `is_incomplete` is false, no need to re-query completions when the current query
5529 // is a suffix of the initial query.
5530 if !menu.is_incomplete {
5531 // If the new query is a suffix of the old query (typing more characters) and
5532 // the previous result was complete, the existing completions can be filtered.
5533 //
5534 // Note that this is always true for snippet completions.
5535 let query_matches = match (&menu.initial_query, &query) {
5536 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5537 (None, _) => true,
5538 _ => false,
5539 };
5540 if query_matches {
5541 let position_matches = if menu.initial_position == position {
5542 true
5543 } else {
5544 let snapshot = self.buffer.read(cx).read(cx);
5545 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5546 };
5547 if position_matches {
5548 return;
5549 }
5550 }
5551 }
5552 };
5553
5554 let trigger_kind = match trigger {
5555 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5556 CompletionTriggerKind::TRIGGER_CHARACTER
5557 }
5558 _ => CompletionTriggerKind::INVOKED,
5559 };
5560 let completion_context = CompletionContext {
5561 trigger_character: trigger.and_then(|trigger| {
5562 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5563 Some(String::from(trigger))
5564 } else {
5565 None
5566 }
5567 }),
5568 trigger_kind,
5569 };
5570
5571 let Anchor {
5572 excerpt_id: buffer_excerpt_id,
5573 text_anchor: buffer_position,
5574 ..
5575 } = buffer_position;
5576
5577 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5578 buffer_snapshot.surrounding_word(buffer_position, false)
5579 {
5580 let word_to_exclude = buffer_snapshot
5581 .text_for_range(word_range.clone())
5582 .collect::<String>();
5583 (
5584 buffer_snapshot.anchor_before(word_range.start)
5585 ..buffer_snapshot.anchor_after(buffer_position),
5586 Some(word_to_exclude),
5587 )
5588 } else {
5589 (buffer_position..buffer_position, None)
5590 };
5591
5592 let language = buffer_snapshot
5593 .language_at(buffer_position)
5594 .map(|language| language.name());
5595
5596 let completion_settings =
5597 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5598
5599 let show_completion_documentation = buffer_snapshot
5600 .settings_at(buffer_position, cx)
5601 .show_completion_documentation;
5602
5603 // The document can be large, so stay in reasonable bounds when searching for words,
5604 // otherwise completion pop-up might be slow to appear.
5605 const WORD_LOOKUP_ROWS: u32 = 5_000;
5606 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5607 let min_word_search = buffer_snapshot.clip_point(
5608 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5609 Bias::Left,
5610 );
5611 let max_word_search = buffer_snapshot.clip_point(
5612 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5613 Bias::Right,
5614 );
5615 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5616 ..buffer_snapshot.point_to_offset(max_word_search);
5617
5618 let skip_digits = query
5619 .as_ref()
5620 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5621
5622 let omit_word_completions = !self.word_completions_enabled
5623 || (!ignore_word_threshold
5624 && match &query {
5625 Some(query) => query.chars().count() < completion_settings.words_min_length,
5626 None => completion_settings.words_min_length != 0,
5627 });
5628
5629 let (mut words, provider_responses) = match &provider {
5630 Some(provider) => {
5631 let provider_responses = provider.completions(
5632 buffer_excerpt_id,
5633 &buffer,
5634 buffer_position,
5635 completion_context,
5636 window,
5637 cx,
5638 );
5639
5640 let words = match (omit_word_completions, completion_settings.words) {
5641 (true, _) | (_, WordsCompletionMode::Disabled) => {
5642 Task::ready(BTreeMap::default())
5643 }
5644 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5645 .background_spawn(async move {
5646 buffer_snapshot.words_in_range(WordsQuery {
5647 fuzzy_contents: None,
5648 range: word_search_range,
5649 skip_digits,
5650 })
5651 }),
5652 };
5653
5654 (words, provider_responses)
5655 }
5656 None => {
5657 let words = if omit_word_completions {
5658 Task::ready(BTreeMap::default())
5659 } else {
5660 cx.background_spawn(async move {
5661 buffer_snapshot.words_in_range(WordsQuery {
5662 fuzzy_contents: None,
5663 range: word_search_range,
5664 skip_digits,
5665 })
5666 })
5667 };
5668 (words, Task::ready(Ok(Vec::new())))
5669 }
5670 };
5671
5672 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5673
5674 let id = post_inc(&mut self.next_completion_id);
5675 let task = cx.spawn_in(window, async move |editor, cx| {
5676 let Ok(()) = editor.update(cx, |this, _| {
5677 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5678 }) else {
5679 return;
5680 };
5681
5682 // TODO: Ideally completions from different sources would be selectively re-queried, so
5683 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5684 let mut completions = Vec::new();
5685 let mut is_incomplete = false;
5686 let mut display_options: Option<CompletionDisplayOptions> = None;
5687 if let Some(provider_responses) = provider_responses.await.log_err()
5688 && !provider_responses.is_empty()
5689 {
5690 for response in provider_responses {
5691 completions.extend(response.completions);
5692 is_incomplete = is_incomplete || response.is_incomplete;
5693 match display_options.as_mut() {
5694 None => {
5695 display_options = Some(response.display_options);
5696 }
5697 Some(options) => options.merge(&response.display_options),
5698 }
5699 }
5700 if completion_settings.words == WordsCompletionMode::Fallback {
5701 words = Task::ready(BTreeMap::default());
5702 }
5703 }
5704 let display_options = display_options.unwrap_or_default();
5705
5706 let mut words = words.await;
5707 if let Some(word_to_exclude) = &word_to_exclude {
5708 words.remove(word_to_exclude);
5709 }
5710 for lsp_completion in &completions {
5711 words.remove(&lsp_completion.new_text);
5712 }
5713 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5714 replace_range: word_replace_range.clone(),
5715 new_text: word.clone(),
5716 label: CodeLabel::plain(word, None),
5717 icon_path: None,
5718 documentation: None,
5719 source: CompletionSource::BufferWord {
5720 word_range,
5721 resolved: false,
5722 },
5723 insert_text_mode: Some(InsertTextMode::AS_IS),
5724 confirm: None,
5725 }));
5726
5727 let menu = if completions.is_empty() {
5728 None
5729 } else {
5730 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5731 let languages = editor
5732 .workspace
5733 .as_ref()
5734 .and_then(|(workspace, _)| workspace.upgrade())
5735 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5736 let menu = CompletionsMenu::new(
5737 id,
5738 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5739 sort_completions,
5740 show_completion_documentation,
5741 position,
5742 query.clone(),
5743 is_incomplete,
5744 buffer.clone(),
5745 completions.into(),
5746 display_options,
5747 snippet_sort_order,
5748 languages,
5749 language,
5750 cx,
5751 );
5752
5753 let query = if filter_completions { query } else { None };
5754 let matches_task = if let Some(query) = query {
5755 menu.do_async_filtering(query, cx)
5756 } else {
5757 Task::ready(menu.unfiltered_matches())
5758 };
5759 (menu, matches_task)
5760 }) else {
5761 return;
5762 };
5763
5764 let matches = matches_task.await;
5765
5766 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5767 // Newer menu already set, so exit.
5768 if let Some(CodeContextMenu::Completions(prev_menu)) =
5769 editor.context_menu.borrow().as_ref()
5770 && prev_menu.id > id
5771 {
5772 return;
5773 };
5774
5775 // Only valid to take prev_menu because it the new menu is immediately set
5776 // below, or the menu is hidden.
5777 if let Some(CodeContextMenu::Completions(prev_menu)) =
5778 editor.context_menu.borrow_mut().take()
5779 {
5780 let position_matches =
5781 if prev_menu.initial_position == menu.initial_position {
5782 true
5783 } else {
5784 let snapshot = editor.buffer.read(cx).read(cx);
5785 prev_menu.initial_position.to_offset(&snapshot)
5786 == menu.initial_position.to_offset(&snapshot)
5787 };
5788 if position_matches {
5789 // Preserve markdown cache before `set_filter_results` because it will
5790 // try to populate the documentation cache.
5791 menu.preserve_markdown_cache(prev_menu);
5792 }
5793 };
5794
5795 menu.set_filter_results(matches, provider, window, cx);
5796 }) else {
5797 return;
5798 };
5799
5800 menu.visible().then_some(menu)
5801 };
5802
5803 editor
5804 .update_in(cx, |editor, window, cx| {
5805 if editor.focus_handle.is_focused(window)
5806 && let Some(menu) = menu
5807 {
5808 *editor.context_menu.borrow_mut() =
5809 Some(CodeContextMenu::Completions(menu));
5810
5811 crate::hover_popover::hide_hover(editor, cx);
5812 if editor.show_edit_predictions_in_menu() {
5813 editor.update_visible_edit_prediction(window, cx);
5814 } else {
5815 editor.discard_edit_prediction(false, cx);
5816 }
5817
5818 cx.notify();
5819 return;
5820 }
5821
5822 if editor.completion_tasks.len() <= 1 {
5823 // If there are no more completion tasks and the last menu was empty, we should hide it.
5824 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5825 // If it was already hidden and we don't show edit predictions in the menu,
5826 // we should also show the edit prediction when available.
5827 if was_hidden && editor.show_edit_predictions_in_menu() {
5828 editor.update_visible_edit_prediction(window, cx);
5829 }
5830 }
5831 })
5832 .ok();
5833 });
5834
5835 self.completion_tasks.push((id, task));
5836 }
5837
5838 #[cfg(feature = "test-support")]
5839 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5840 let menu = self.context_menu.borrow();
5841 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5842 let completions = menu.completions.borrow();
5843 Some(completions.to_vec())
5844 } else {
5845 None
5846 }
5847 }
5848
5849 pub fn with_completions_menu_matching_id<R>(
5850 &self,
5851 id: CompletionId,
5852 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5853 ) -> R {
5854 let mut context_menu = self.context_menu.borrow_mut();
5855 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5856 return f(None);
5857 };
5858 if completions_menu.id != id {
5859 return f(None);
5860 }
5861 f(Some(completions_menu))
5862 }
5863
5864 pub fn confirm_completion(
5865 &mut self,
5866 action: &ConfirmCompletion,
5867 window: &mut Window,
5868 cx: &mut Context<Self>,
5869 ) -> Option<Task<Result<()>>> {
5870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5871 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5872 }
5873
5874 pub fn confirm_completion_insert(
5875 &mut self,
5876 _: &ConfirmCompletionInsert,
5877 window: &mut Window,
5878 cx: &mut Context<Self>,
5879 ) -> Option<Task<Result<()>>> {
5880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5881 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5882 }
5883
5884 pub fn confirm_completion_replace(
5885 &mut self,
5886 _: &ConfirmCompletionReplace,
5887 window: &mut Window,
5888 cx: &mut Context<Self>,
5889 ) -> Option<Task<Result<()>>> {
5890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5891 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5892 }
5893
5894 pub fn compose_completion(
5895 &mut self,
5896 action: &ComposeCompletion,
5897 window: &mut Window,
5898 cx: &mut Context<Self>,
5899 ) -> Option<Task<Result<()>>> {
5900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5901 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5902 }
5903
5904 fn do_completion(
5905 &mut self,
5906 item_ix: Option<usize>,
5907 intent: CompletionIntent,
5908 window: &mut Window,
5909 cx: &mut Context<Editor>,
5910 ) -> Option<Task<Result<()>>> {
5911 use language::ToOffset as _;
5912
5913 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5914 else {
5915 return None;
5916 };
5917
5918 let candidate_id = {
5919 let entries = completions_menu.entries.borrow();
5920 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5921 if self.show_edit_predictions_in_menu() {
5922 self.discard_edit_prediction(true, cx);
5923 }
5924 mat.candidate_id
5925 };
5926
5927 let completion = completions_menu
5928 .completions
5929 .borrow()
5930 .get(candidate_id)?
5931 .clone();
5932 cx.stop_propagation();
5933
5934 let buffer_handle = completions_menu.buffer.clone();
5935
5936 let CompletionEdit {
5937 new_text,
5938 snippet,
5939 replace_range,
5940 } = process_completion_for_edit(
5941 &completion,
5942 intent,
5943 &buffer_handle,
5944 &completions_menu.initial_position.text_anchor,
5945 cx,
5946 );
5947
5948 let buffer = buffer_handle.read(cx);
5949 let snapshot = self.buffer.read(cx).snapshot(cx);
5950 let newest_anchor = self.selections.newest_anchor();
5951 let replace_range_multibuffer = {
5952 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5953 let multibuffer_anchor = snapshot
5954 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5955 .unwrap()
5956 ..snapshot
5957 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5958 .unwrap();
5959 multibuffer_anchor.start.to_offset(&snapshot)
5960 ..multibuffer_anchor.end.to_offset(&snapshot)
5961 };
5962 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5963 return None;
5964 }
5965
5966 let old_text = buffer
5967 .text_for_range(replace_range.clone())
5968 .collect::<String>();
5969 let lookbehind = newest_anchor
5970 .start
5971 .text_anchor
5972 .to_offset(buffer)
5973 .saturating_sub(replace_range.start);
5974 let lookahead = replace_range
5975 .end
5976 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5977 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5978 let suffix = &old_text[lookbehind.min(old_text.len())..];
5979
5980 let selections = self.selections.all::<usize>(cx);
5981 let mut ranges = Vec::new();
5982 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5983
5984 for selection in &selections {
5985 let range = if selection.id == newest_anchor.id {
5986 replace_range_multibuffer.clone()
5987 } else {
5988 let mut range = selection.range();
5989
5990 // if prefix is present, don't duplicate it
5991 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5992 range.start = range.start.saturating_sub(lookbehind);
5993
5994 // if suffix is also present, mimic the newest cursor and replace it
5995 if selection.id != newest_anchor.id
5996 && snapshot.contains_str_at(range.end, suffix)
5997 {
5998 range.end += lookahead;
5999 }
6000 }
6001 range
6002 };
6003
6004 ranges.push(range.clone());
6005
6006 if !self.linked_edit_ranges.is_empty() {
6007 let start_anchor = snapshot.anchor_before(range.start);
6008 let end_anchor = snapshot.anchor_after(range.end);
6009 if let Some(ranges) = self
6010 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6011 {
6012 for (buffer, edits) in ranges {
6013 linked_edits
6014 .entry(buffer.clone())
6015 .or_default()
6016 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6017 }
6018 }
6019 }
6020 }
6021
6022 let common_prefix_len = old_text
6023 .chars()
6024 .zip(new_text.chars())
6025 .take_while(|(a, b)| a == b)
6026 .map(|(a, _)| a.len_utf8())
6027 .sum::<usize>();
6028
6029 cx.emit(EditorEvent::InputHandled {
6030 utf16_range_to_replace: None,
6031 text: new_text[common_prefix_len..].into(),
6032 });
6033
6034 self.transact(window, cx, |editor, window, cx| {
6035 if let Some(mut snippet) = snippet {
6036 snippet.text = new_text.to_string();
6037 editor
6038 .insert_snippet(&ranges, snippet, window, cx)
6039 .log_err();
6040 } else {
6041 editor.buffer.update(cx, |multi_buffer, cx| {
6042 let auto_indent = match completion.insert_text_mode {
6043 Some(InsertTextMode::AS_IS) => None,
6044 _ => editor.autoindent_mode.clone(),
6045 };
6046 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6047 multi_buffer.edit(edits, auto_indent, cx);
6048 });
6049 }
6050 for (buffer, edits) in linked_edits {
6051 buffer.update(cx, |buffer, cx| {
6052 let snapshot = buffer.snapshot();
6053 let edits = edits
6054 .into_iter()
6055 .map(|(range, text)| {
6056 use text::ToPoint as TP;
6057 let end_point = TP::to_point(&range.end, &snapshot);
6058 let start_point = TP::to_point(&range.start, &snapshot);
6059 (start_point..end_point, text)
6060 })
6061 .sorted_by_key(|(range, _)| range.start);
6062 buffer.edit(edits, None, cx);
6063 })
6064 }
6065
6066 editor.refresh_edit_prediction(true, false, window, cx);
6067 });
6068 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6069
6070 let show_new_completions_on_confirm = completion
6071 .confirm
6072 .as_ref()
6073 .is_some_and(|confirm| confirm(intent, window, cx));
6074 if show_new_completions_on_confirm {
6075 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6076 }
6077
6078 let provider = self.completion_provider.as_ref()?;
6079 drop(completion);
6080 let apply_edits = provider.apply_additional_edits_for_completion(
6081 buffer_handle,
6082 completions_menu.completions.clone(),
6083 candidate_id,
6084 true,
6085 cx,
6086 );
6087
6088 let editor_settings = EditorSettings::get_global(cx);
6089 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6090 // After the code completion is finished, users often want to know what signatures are needed.
6091 // so we should automatically call signature_help
6092 self.show_signature_help(&ShowSignatureHelp, window, cx);
6093 }
6094
6095 Some(cx.foreground_executor().spawn(async move {
6096 apply_edits.await?;
6097 Ok(())
6098 }))
6099 }
6100
6101 pub fn toggle_code_actions(
6102 &mut self,
6103 action: &ToggleCodeActions,
6104 window: &mut Window,
6105 cx: &mut Context<Self>,
6106 ) {
6107 let quick_launch = action.quick_launch;
6108 let mut context_menu = self.context_menu.borrow_mut();
6109 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6110 if code_actions.deployed_from == action.deployed_from {
6111 // Toggle if we're selecting the same one
6112 *context_menu = None;
6113 cx.notify();
6114 return;
6115 } else {
6116 // Otherwise, clear it and start a new one
6117 *context_menu = None;
6118 cx.notify();
6119 }
6120 }
6121 drop(context_menu);
6122 let snapshot = self.snapshot(window, cx);
6123 let deployed_from = action.deployed_from.clone();
6124 let action = action.clone();
6125 self.completion_tasks.clear();
6126 self.discard_edit_prediction(false, cx);
6127
6128 let multibuffer_point = match &action.deployed_from {
6129 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6130 DisplayPoint::new(*row, 0).to_point(&snapshot)
6131 }
6132 _ => self.selections.newest::<Point>(cx).head(),
6133 };
6134 let Some((buffer, buffer_row)) = snapshot
6135 .buffer_snapshot
6136 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6137 .and_then(|(buffer_snapshot, range)| {
6138 self.buffer()
6139 .read(cx)
6140 .buffer(buffer_snapshot.remote_id())
6141 .map(|buffer| (buffer, range.start.row))
6142 })
6143 else {
6144 return;
6145 };
6146 let buffer_id = buffer.read(cx).remote_id();
6147 let tasks = self
6148 .tasks
6149 .get(&(buffer_id, buffer_row))
6150 .map(|t| Arc::new(t.to_owned()));
6151
6152 if !self.focus_handle.is_focused(window) {
6153 return;
6154 }
6155 let project = self.project.clone();
6156
6157 let code_actions_task = match deployed_from {
6158 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6159 _ => self.code_actions(buffer_row, window, cx),
6160 };
6161
6162 let runnable_task = match deployed_from {
6163 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6164 _ => {
6165 let mut task_context_task = Task::ready(None);
6166 if let Some(tasks) = &tasks
6167 && let Some(project) = project
6168 {
6169 task_context_task =
6170 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6171 }
6172
6173 cx.spawn_in(window, {
6174 let buffer = buffer.clone();
6175 async move |editor, cx| {
6176 let task_context = task_context_task.await;
6177
6178 let resolved_tasks =
6179 tasks
6180 .zip(task_context.clone())
6181 .map(|(tasks, task_context)| ResolvedTasks {
6182 templates: tasks.resolve(&task_context).collect(),
6183 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6184 multibuffer_point.row,
6185 tasks.column,
6186 )),
6187 });
6188 let debug_scenarios = editor
6189 .update(cx, |editor, cx| {
6190 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6191 })?
6192 .await;
6193 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6194 }
6195 })
6196 }
6197 };
6198
6199 cx.spawn_in(window, async move |editor, cx| {
6200 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6201 let code_actions = code_actions_task.await;
6202 let spawn_straight_away = quick_launch
6203 && resolved_tasks
6204 .as_ref()
6205 .is_some_and(|tasks| tasks.templates.len() == 1)
6206 && code_actions
6207 .as_ref()
6208 .is_none_or(|actions| actions.is_empty())
6209 && debug_scenarios.is_empty();
6210
6211 editor.update_in(cx, |editor, window, cx| {
6212 crate::hover_popover::hide_hover(editor, cx);
6213 let actions = CodeActionContents::new(
6214 resolved_tasks,
6215 code_actions,
6216 debug_scenarios,
6217 task_context.unwrap_or_default(),
6218 );
6219
6220 // Don't show the menu if there are no actions available
6221 if actions.is_empty() {
6222 cx.notify();
6223 return Task::ready(Ok(()));
6224 }
6225
6226 *editor.context_menu.borrow_mut() =
6227 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6228 buffer,
6229 actions,
6230 selected_item: Default::default(),
6231 scroll_handle: UniformListScrollHandle::default(),
6232 deployed_from,
6233 }));
6234 cx.notify();
6235 if spawn_straight_away
6236 && let Some(task) = editor.confirm_code_action(
6237 &ConfirmCodeAction { item_ix: Some(0) },
6238 window,
6239 cx,
6240 )
6241 {
6242 return task;
6243 }
6244
6245 Task::ready(Ok(()))
6246 })
6247 })
6248 .detach_and_log_err(cx);
6249 }
6250
6251 fn debug_scenarios(
6252 &mut self,
6253 resolved_tasks: &Option<ResolvedTasks>,
6254 buffer: &Entity<Buffer>,
6255 cx: &mut App,
6256 ) -> Task<Vec<task::DebugScenario>> {
6257 maybe!({
6258 let project = self.project()?;
6259 let dap_store = project.read(cx).dap_store();
6260 let mut scenarios = vec![];
6261 let resolved_tasks = resolved_tasks.as_ref()?;
6262 let buffer = buffer.read(cx);
6263 let language = buffer.language()?;
6264 let file = buffer.file();
6265 let debug_adapter = language_settings(language.name().into(), file, cx)
6266 .debuggers
6267 .first()
6268 .map(SharedString::from)
6269 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6270
6271 dap_store.update(cx, |dap_store, cx| {
6272 for (_, task) in &resolved_tasks.templates {
6273 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6274 task.original_task().clone(),
6275 debug_adapter.clone().into(),
6276 task.display_label().to_owned().into(),
6277 cx,
6278 );
6279 scenarios.push(maybe_scenario);
6280 }
6281 });
6282 Some(cx.background_spawn(async move {
6283 futures::future::join_all(scenarios)
6284 .await
6285 .into_iter()
6286 .flatten()
6287 .collect::<Vec<_>>()
6288 }))
6289 })
6290 .unwrap_or_else(|| Task::ready(vec![]))
6291 }
6292
6293 fn code_actions(
6294 &mut self,
6295 buffer_row: u32,
6296 window: &mut Window,
6297 cx: &mut Context<Self>,
6298 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6299 let mut task = self.code_actions_task.take();
6300 cx.spawn_in(window, async move |editor, cx| {
6301 while let Some(prev_task) = task {
6302 prev_task.await.log_err();
6303 task = editor
6304 .update(cx, |this, _| this.code_actions_task.take())
6305 .ok()?;
6306 }
6307
6308 editor
6309 .update(cx, |editor, cx| {
6310 editor
6311 .available_code_actions
6312 .clone()
6313 .and_then(|(location, code_actions)| {
6314 let snapshot = location.buffer.read(cx).snapshot();
6315 let point_range = location.range.to_point(&snapshot);
6316 let point_range = point_range.start.row..=point_range.end.row;
6317 if point_range.contains(&buffer_row) {
6318 Some(code_actions)
6319 } else {
6320 None
6321 }
6322 })
6323 })
6324 .ok()
6325 .flatten()
6326 })
6327 }
6328
6329 pub fn confirm_code_action(
6330 &mut self,
6331 action: &ConfirmCodeAction,
6332 window: &mut Window,
6333 cx: &mut Context<Self>,
6334 ) -> Option<Task<Result<()>>> {
6335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6336
6337 let actions_menu =
6338 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6339 menu
6340 } else {
6341 return None;
6342 };
6343
6344 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6345 let action = actions_menu.actions.get(action_ix)?;
6346 let title = action.label();
6347 let buffer = actions_menu.buffer;
6348 let workspace = self.workspace()?;
6349
6350 match action {
6351 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6352 workspace.update(cx, |workspace, cx| {
6353 workspace.schedule_resolved_task(
6354 task_source_kind,
6355 resolved_task,
6356 false,
6357 window,
6358 cx,
6359 );
6360
6361 Some(Task::ready(Ok(())))
6362 })
6363 }
6364 CodeActionsItem::CodeAction {
6365 excerpt_id,
6366 action,
6367 provider,
6368 } => {
6369 let apply_code_action =
6370 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6371 let workspace = workspace.downgrade();
6372 Some(cx.spawn_in(window, async move |editor, cx| {
6373 let project_transaction = apply_code_action.await?;
6374 Self::open_project_transaction(
6375 &editor,
6376 workspace,
6377 project_transaction,
6378 title,
6379 cx,
6380 )
6381 .await
6382 }))
6383 }
6384 CodeActionsItem::DebugScenario(scenario) => {
6385 let context = actions_menu.actions.context;
6386
6387 workspace.update(cx, |workspace, cx| {
6388 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6389 workspace.start_debug_session(
6390 scenario,
6391 context,
6392 Some(buffer),
6393 None,
6394 window,
6395 cx,
6396 );
6397 });
6398 Some(Task::ready(Ok(())))
6399 }
6400 }
6401 }
6402
6403 pub async fn open_project_transaction(
6404 editor: &WeakEntity<Editor>,
6405 workspace: WeakEntity<Workspace>,
6406 transaction: ProjectTransaction,
6407 title: String,
6408 cx: &mut AsyncWindowContext,
6409 ) -> Result<()> {
6410 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6411 cx.update(|_, cx| {
6412 entries.sort_unstable_by_key(|(buffer, _)| {
6413 buffer.read(cx).file().map(|f| f.path().clone())
6414 });
6415 })?;
6416
6417 // If the project transaction's edits are all contained within this editor, then
6418 // avoid opening a new editor to display them.
6419
6420 if let Some((buffer, transaction)) = entries.first() {
6421 if entries.len() == 1 {
6422 let excerpt = editor.update(cx, |editor, cx| {
6423 editor
6424 .buffer()
6425 .read(cx)
6426 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6427 })?;
6428 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6429 && excerpted_buffer == *buffer
6430 {
6431 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6432 let excerpt_range = excerpt_range.to_offset(buffer);
6433 buffer
6434 .edited_ranges_for_transaction::<usize>(transaction)
6435 .all(|range| {
6436 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6437 })
6438 })?;
6439
6440 if all_edits_within_excerpt {
6441 return Ok(());
6442 }
6443 }
6444 }
6445 } else {
6446 return Ok(());
6447 }
6448
6449 let mut ranges_to_highlight = Vec::new();
6450 let excerpt_buffer = cx.new(|cx| {
6451 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6452 for (buffer_handle, transaction) in &entries {
6453 let edited_ranges = buffer_handle
6454 .read(cx)
6455 .edited_ranges_for_transaction::<Point>(transaction)
6456 .collect::<Vec<_>>();
6457 let (ranges, _) = multibuffer.set_excerpts_for_path(
6458 PathKey::for_buffer(buffer_handle, cx),
6459 buffer_handle.clone(),
6460 edited_ranges,
6461 multibuffer_context_lines(cx),
6462 cx,
6463 );
6464
6465 ranges_to_highlight.extend(ranges);
6466 }
6467 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6468 multibuffer
6469 })?;
6470
6471 workspace.update_in(cx, |workspace, window, cx| {
6472 let project = workspace.project().clone();
6473 let editor =
6474 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6475 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6476 editor.update(cx, |editor, cx| {
6477 editor.highlight_background::<Self>(
6478 &ranges_to_highlight,
6479 |theme| theme.colors().editor_highlighted_line_background,
6480 cx,
6481 );
6482 });
6483 })?;
6484
6485 Ok(())
6486 }
6487
6488 pub fn clear_code_action_providers(&mut self) {
6489 self.code_action_providers.clear();
6490 self.available_code_actions.take();
6491 }
6492
6493 pub fn add_code_action_provider(
6494 &mut self,
6495 provider: Rc<dyn CodeActionProvider>,
6496 window: &mut Window,
6497 cx: &mut Context<Self>,
6498 ) {
6499 if self
6500 .code_action_providers
6501 .iter()
6502 .any(|existing_provider| existing_provider.id() == provider.id())
6503 {
6504 return;
6505 }
6506
6507 self.code_action_providers.push(provider);
6508 self.refresh_code_actions(window, cx);
6509 }
6510
6511 pub fn remove_code_action_provider(
6512 &mut self,
6513 id: Arc<str>,
6514 window: &mut Window,
6515 cx: &mut Context<Self>,
6516 ) {
6517 self.code_action_providers
6518 .retain(|provider| provider.id() != id);
6519 self.refresh_code_actions(window, cx);
6520 }
6521
6522 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6523 !self.code_action_providers.is_empty()
6524 && EditorSettings::get_global(cx).toolbar.code_actions
6525 }
6526
6527 pub fn has_available_code_actions(&self) -> bool {
6528 self.available_code_actions
6529 .as_ref()
6530 .is_some_and(|(_, actions)| !actions.is_empty())
6531 }
6532
6533 fn render_inline_code_actions(
6534 &self,
6535 icon_size: ui::IconSize,
6536 display_row: DisplayRow,
6537 is_active: bool,
6538 cx: &mut Context<Self>,
6539 ) -> AnyElement {
6540 let show_tooltip = !self.context_menu_visible();
6541 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6542 .icon_size(icon_size)
6543 .shape(ui::IconButtonShape::Square)
6544 .icon_color(ui::Color::Hidden)
6545 .toggle_state(is_active)
6546 .when(show_tooltip, |this| {
6547 this.tooltip({
6548 let focus_handle = self.focus_handle.clone();
6549 move |window, cx| {
6550 Tooltip::for_action_in(
6551 "Toggle Code Actions",
6552 &ToggleCodeActions {
6553 deployed_from: None,
6554 quick_launch: false,
6555 },
6556 &focus_handle,
6557 window,
6558 cx,
6559 )
6560 }
6561 })
6562 })
6563 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6564 window.focus(&editor.focus_handle(cx));
6565 editor.toggle_code_actions(
6566 &crate::actions::ToggleCodeActions {
6567 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6568 display_row,
6569 )),
6570 quick_launch: false,
6571 },
6572 window,
6573 cx,
6574 );
6575 }))
6576 .into_any_element()
6577 }
6578
6579 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6580 &self.context_menu
6581 }
6582
6583 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6584 let newest_selection = self.selections.newest_anchor().clone();
6585 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6586 let buffer = self.buffer.read(cx);
6587 if newest_selection.head().diff_base_anchor.is_some() {
6588 return None;
6589 }
6590 let (start_buffer, start) =
6591 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6592 let (end_buffer, end) =
6593 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6594 if start_buffer != end_buffer {
6595 return None;
6596 }
6597
6598 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6599 cx.background_executor()
6600 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6601 .await;
6602
6603 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6604 let providers = this.code_action_providers.clone();
6605 let tasks = this
6606 .code_action_providers
6607 .iter()
6608 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6609 .collect::<Vec<_>>();
6610 (providers, tasks)
6611 })?;
6612
6613 let mut actions = Vec::new();
6614 for (provider, provider_actions) in
6615 providers.into_iter().zip(future::join_all(tasks).await)
6616 {
6617 if let Some(provider_actions) = provider_actions.log_err() {
6618 actions.extend(provider_actions.into_iter().map(|action| {
6619 AvailableCodeAction {
6620 excerpt_id: newest_selection.start.excerpt_id,
6621 action,
6622 provider: provider.clone(),
6623 }
6624 }));
6625 }
6626 }
6627
6628 this.update(cx, |this, cx| {
6629 this.available_code_actions = if actions.is_empty() {
6630 None
6631 } else {
6632 Some((
6633 Location {
6634 buffer: start_buffer,
6635 range: start..end,
6636 },
6637 actions.into(),
6638 ))
6639 };
6640 cx.notify();
6641 })
6642 }));
6643 None
6644 }
6645
6646 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6647 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6648 self.show_git_blame_inline = false;
6649
6650 self.show_git_blame_inline_delay_task =
6651 Some(cx.spawn_in(window, async move |this, cx| {
6652 cx.background_executor().timer(delay).await;
6653
6654 this.update(cx, |this, cx| {
6655 this.show_git_blame_inline = true;
6656 cx.notify();
6657 })
6658 .log_err();
6659 }));
6660 }
6661 }
6662
6663 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6664 let snapshot = self.snapshot(window, cx);
6665 let cursor = self.selections.newest::<Point>(cx).head();
6666 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6667 else {
6668 return;
6669 };
6670
6671 let Some(blame) = self.blame.as_ref() else {
6672 return;
6673 };
6674
6675 let row_info = RowInfo {
6676 buffer_id: Some(buffer.remote_id()),
6677 buffer_row: Some(point.row),
6678 ..Default::default()
6679 };
6680 let Some((buffer, blame_entry)) = blame
6681 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6682 .flatten()
6683 else {
6684 return;
6685 };
6686
6687 let anchor = self.selections.newest_anchor().head();
6688 let position = self.to_pixel_point(anchor, &snapshot, window);
6689 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6690 self.show_blame_popover(
6691 buffer,
6692 &blame_entry,
6693 position + last_bounds.origin,
6694 true,
6695 cx,
6696 );
6697 };
6698 }
6699
6700 fn show_blame_popover(
6701 &mut self,
6702 buffer: BufferId,
6703 blame_entry: &BlameEntry,
6704 position: gpui::Point<Pixels>,
6705 ignore_timeout: bool,
6706 cx: &mut Context<Self>,
6707 ) {
6708 if let Some(state) = &mut self.inline_blame_popover {
6709 state.hide_task.take();
6710 } else {
6711 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6712 let blame_entry = blame_entry.clone();
6713 let show_task = cx.spawn(async move |editor, cx| {
6714 if !ignore_timeout {
6715 cx.background_executor()
6716 .timer(std::time::Duration::from_millis(blame_popover_delay))
6717 .await;
6718 }
6719 editor
6720 .update(cx, |editor, cx| {
6721 editor.inline_blame_popover_show_task.take();
6722 let Some(blame) = editor.blame.as_ref() else {
6723 return;
6724 };
6725 let blame = blame.read(cx);
6726 let details = blame.details_for_entry(buffer, &blame_entry);
6727 let markdown = cx.new(|cx| {
6728 Markdown::new(
6729 details
6730 .as_ref()
6731 .map(|message| message.message.clone())
6732 .unwrap_or_default(),
6733 None,
6734 None,
6735 cx,
6736 )
6737 });
6738 editor.inline_blame_popover = Some(InlineBlamePopover {
6739 position,
6740 hide_task: None,
6741 popover_bounds: None,
6742 popover_state: InlineBlamePopoverState {
6743 scroll_handle: ScrollHandle::new(),
6744 commit_message: details,
6745 markdown,
6746 },
6747 keyboard_grace: ignore_timeout,
6748 });
6749 cx.notify();
6750 })
6751 .ok();
6752 });
6753 self.inline_blame_popover_show_task = Some(show_task);
6754 }
6755 }
6756
6757 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6758 self.inline_blame_popover_show_task.take();
6759 if let Some(state) = &mut self.inline_blame_popover {
6760 let hide_task = cx.spawn(async move |editor, cx| {
6761 cx.background_executor()
6762 .timer(std::time::Duration::from_millis(100))
6763 .await;
6764 editor
6765 .update(cx, |editor, cx| {
6766 editor.inline_blame_popover.take();
6767 cx.notify();
6768 })
6769 .ok();
6770 });
6771 state.hide_task = Some(hide_task);
6772 }
6773 }
6774
6775 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6776 if self.pending_rename.is_some() {
6777 return None;
6778 }
6779
6780 let provider = self.semantics_provider.clone()?;
6781 let buffer = self.buffer.read(cx);
6782 let newest_selection = self.selections.newest_anchor().clone();
6783 let cursor_position = newest_selection.head();
6784 let (cursor_buffer, cursor_buffer_position) =
6785 buffer.text_anchor_for_position(cursor_position, cx)?;
6786 let (tail_buffer, tail_buffer_position) =
6787 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6788 if cursor_buffer != tail_buffer {
6789 return None;
6790 }
6791
6792 let snapshot = cursor_buffer.read(cx).snapshot();
6793 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6794 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6795 if start_word_range != end_word_range {
6796 self.document_highlights_task.take();
6797 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6798 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6799 return None;
6800 }
6801
6802 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6803 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6804 cx.background_executor()
6805 .timer(Duration::from_millis(debounce))
6806 .await;
6807
6808 let highlights = if let Some(highlights) = cx
6809 .update(|cx| {
6810 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6811 })
6812 .ok()
6813 .flatten()
6814 {
6815 highlights.await.log_err()
6816 } else {
6817 None
6818 };
6819
6820 if let Some(highlights) = highlights {
6821 this.update(cx, |this, cx| {
6822 if this.pending_rename.is_some() {
6823 return;
6824 }
6825
6826 let buffer = this.buffer.read(cx);
6827 if buffer
6828 .text_anchor_for_position(cursor_position, cx)
6829 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6830 {
6831 return;
6832 }
6833
6834 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6835 let mut write_ranges = Vec::new();
6836 let mut read_ranges = Vec::new();
6837 for highlight in highlights {
6838 let buffer_id = cursor_buffer.read(cx).remote_id();
6839 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6840 {
6841 let start = highlight
6842 .range
6843 .start
6844 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6845 let end = highlight
6846 .range
6847 .end
6848 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6849 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6850 continue;
6851 }
6852
6853 let range = Anchor {
6854 buffer_id: Some(buffer_id),
6855 excerpt_id,
6856 text_anchor: start,
6857 diff_base_anchor: None,
6858 }..Anchor {
6859 buffer_id: Some(buffer_id),
6860 excerpt_id,
6861 text_anchor: end,
6862 diff_base_anchor: None,
6863 };
6864 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6865 write_ranges.push(range);
6866 } else {
6867 read_ranges.push(range);
6868 }
6869 }
6870 }
6871
6872 this.highlight_background::<DocumentHighlightRead>(
6873 &read_ranges,
6874 |theme| theme.colors().editor_document_highlight_read_background,
6875 cx,
6876 );
6877 this.highlight_background::<DocumentHighlightWrite>(
6878 &write_ranges,
6879 |theme| theme.colors().editor_document_highlight_write_background,
6880 cx,
6881 );
6882 cx.notify();
6883 })
6884 .log_err();
6885 }
6886 }));
6887 None
6888 }
6889
6890 fn prepare_highlight_query_from_selection(
6891 &mut self,
6892 cx: &mut Context<Editor>,
6893 ) -> Option<(String, Range<Anchor>)> {
6894 if matches!(self.mode, EditorMode::SingleLine) {
6895 return None;
6896 }
6897 if !EditorSettings::get_global(cx).selection_highlight {
6898 return None;
6899 }
6900 if self.selections.count() != 1 || self.selections.line_mode {
6901 return None;
6902 }
6903 let selection = self.selections.newest::<Point>(cx);
6904 if selection.is_empty() || selection.start.row != selection.end.row {
6905 return None;
6906 }
6907 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6908 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6909 let query = multi_buffer_snapshot
6910 .text_for_range(selection_anchor_range.clone())
6911 .collect::<String>();
6912 if query.trim().is_empty() {
6913 return None;
6914 }
6915 Some((query, selection_anchor_range))
6916 }
6917
6918 fn update_selection_occurrence_highlights(
6919 &mut self,
6920 query_text: String,
6921 query_range: Range<Anchor>,
6922 multi_buffer_range_to_query: Range<Point>,
6923 use_debounce: bool,
6924 window: &mut Window,
6925 cx: &mut Context<Editor>,
6926 ) -> Task<()> {
6927 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6928 cx.spawn_in(window, async move |editor, cx| {
6929 if use_debounce {
6930 cx.background_executor()
6931 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6932 .await;
6933 }
6934 let match_task = cx.background_spawn(async move {
6935 let buffer_ranges = multi_buffer_snapshot
6936 .range_to_buffer_ranges(multi_buffer_range_to_query)
6937 .into_iter()
6938 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6939 let mut match_ranges = Vec::new();
6940 let Ok(regex) = project::search::SearchQuery::text(
6941 query_text.clone(),
6942 false,
6943 false,
6944 false,
6945 Default::default(),
6946 Default::default(),
6947 false,
6948 None,
6949 ) else {
6950 return Vec::default();
6951 };
6952 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6953 match_ranges.extend(
6954 regex
6955 .search(buffer_snapshot, Some(search_range.clone()))
6956 .await
6957 .into_iter()
6958 .filter_map(|match_range| {
6959 let match_start = buffer_snapshot
6960 .anchor_after(search_range.start + match_range.start);
6961 let match_end = buffer_snapshot
6962 .anchor_before(search_range.start + match_range.end);
6963 let match_anchor_range = Anchor::range_in_buffer(
6964 excerpt_id,
6965 buffer_snapshot.remote_id(),
6966 match_start..match_end,
6967 );
6968 (match_anchor_range != query_range).then_some(match_anchor_range)
6969 }),
6970 );
6971 }
6972 match_ranges
6973 });
6974 let match_ranges = match_task.await;
6975 editor
6976 .update_in(cx, |editor, _, cx| {
6977 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6978 if !match_ranges.is_empty() {
6979 editor.highlight_background::<SelectedTextHighlight>(
6980 &match_ranges,
6981 |theme| theme.colors().editor_document_highlight_bracket_background,
6982 cx,
6983 )
6984 }
6985 })
6986 .log_err();
6987 })
6988 }
6989
6990 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6991 struct NewlineFold;
6992 let type_id = std::any::TypeId::of::<NewlineFold>();
6993 if !self.mode.is_single_line() {
6994 return;
6995 }
6996 let snapshot = self.snapshot(window, cx);
6997 if snapshot.buffer_snapshot.max_point().row == 0 {
6998 return;
6999 }
7000 let task = cx.background_spawn(async move {
7001 let new_newlines = snapshot
7002 .buffer_chars_at(0)
7003 .filter_map(|(c, i)| {
7004 if c == '\n' {
7005 Some(
7006 snapshot.buffer_snapshot.anchor_after(i)
7007 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7008 )
7009 } else {
7010 None
7011 }
7012 })
7013 .collect::<Vec<_>>();
7014 let existing_newlines = snapshot
7015 .folds_in_range(0..snapshot.buffer_snapshot.len())
7016 .filter_map(|fold| {
7017 if fold.placeholder.type_tag == Some(type_id) {
7018 Some(fold.range.start..fold.range.end)
7019 } else {
7020 None
7021 }
7022 })
7023 .collect::<Vec<_>>();
7024
7025 (new_newlines, existing_newlines)
7026 });
7027 self.folding_newlines = cx.spawn(async move |this, cx| {
7028 let (new_newlines, existing_newlines) = task.await;
7029 if new_newlines == existing_newlines {
7030 return;
7031 }
7032 let placeholder = FoldPlaceholder {
7033 render: Arc::new(move |_, _, cx| {
7034 div()
7035 .bg(cx.theme().status().hint_background)
7036 .border_b_1()
7037 .size_full()
7038 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7039 .border_color(cx.theme().status().hint)
7040 .child("\\n")
7041 .into_any()
7042 }),
7043 constrain_width: false,
7044 merge_adjacent: false,
7045 type_tag: Some(type_id),
7046 };
7047 let creases = new_newlines
7048 .into_iter()
7049 .map(|range| Crease::simple(range, placeholder.clone()))
7050 .collect();
7051 this.update(cx, |this, cx| {
7052 this.display_map.update(cx, |display_map, cx| {
7053 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7054 display_map.fold(creases, cx);
7055 });
7056 })
7057 .ok();
7058 });
7059 }
7060
7061 fn refresh_selected_text_highlights(
7062 &mut self,
7063 on_buffer_edit: bool,
7064 window: &mut Window,
7065 cx: &mut Context<Editor>,
7066 ) {
7067 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7068 else {
7069 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7070 self.quick_selection_highlight_task.take();
7071 self.debounced_selection_highlight_task.take();
7072 return;
7073 };
7074 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7075 if on_buffer_edit
7076 || self
7077 .quick_selection_highlight_task
7078 .as_ref()
7079 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7080 {
7081 let multi_buffer_visible_start = self
7082 .scroll_manager
7083 .anchor()
7084 .anchor
7085 .to_point(&multi_buffer_snapshot);
7086 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7087 multi_buffer_visible_start
7088 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7089 Bias::Left,
7090 );
7091 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7092 self.quick_selection_highlight_task = Some((
7093 query_range.clone(),
7094 self.update_selection_occurrence_highlights(
7095 query_text.clone(),
7096 query_range.clone(),
7097 multi_buffer_visible_range,
7098 false,
7099 window,
7100 cx,
7101 ),
7102 ));
7103 }
7104 if on_buffer_edit
7105 || self
7106 .debounced_selection_highlight_task
7107 .as_ref()
7108 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7109 {
7110 let multi_buffer_start = multi_buffer_snapshot
7111 .anchor_before(0)
7112 .to_point(&multi_buffer_snapshot);
7113 let multi_buffer_end = multi_buffer_snapshot
7114 .anchor_after(multi_buffer_snapshot.len())
7115 .to_point(&multi_buffer_snapshot);
7116 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7117 self.debounced_selection_highlight_task = Some((
7118 query_range.clone(),
7119 self.update_selection_occurrence_highlights(
7120 query_text,
7121 query_range,
7122 multi_buffer_full_range,
7123 true,
7124 window,
7125 cx,
7126 ),
7127 ));
7128 }
7129 }
7130
7131 pub fn refresh_edit_prediction(
7132 &mut self,
7133 debounce: bool,
7134 user_requested: bool,
7135 window: &mut Window,
7136 cx: &mut Context<Self>,
7137 ) -> Option<()> {
7138 if DisableAiSettings::get_global(cx).disable_ai {
7139 return None;
7140 }
7141
7142 let provider = self.edit_prediction_provider()?;
7143 let cursor = self.selections.newest_anchor().head();
7144 let (buffer, cursor_buffer_position) =
7145 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7146
7147 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7148 self.discard_edit_prediction(false, cx);
7149 return None;
7150 }
7151
7152 self.update_visible_edit_prediction(window, cx);
7153
7154 if !user_requested
7155 && (!self.should_show_edit_predictions()
7156 || !self.is_focused(window)
7157 || buffer.read(cx).is_empty())
7158 {
7159 self.discard_edit_prediction(false, cx);
7160 return None;
7161 }
7162
7163 provider.refresh(
7164 self.project.clone(),
7165 buffer,
7166 cursor_buffer_position,
7167 debounce,
7168 cx,
7169 );
7170 Some(())
7171 }
7172
7173 fn show_edit_predictions_in_menu(&self) -> bool {
7174 match self.edit_prediction_settings {
7175 EditPredictionSettings::Disabled => false,
7176 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7177 }
7178 }
7179
7180 pub fn edit_predictions_enabled(&self) -> bool {
7181 match self.edit_prediction_settings {
7182 EditPredictionSettings::Disabled => false,
7183 EditPredictionSettings::Enabled { .. } => true,
7184 }
7185 }
7186
7187 fn edit_prediction_requires_modifier(&self) -> bool {
7188 match self.edit_prediction_settings {
7189 EditPredictionSettings::Disabled => false,
7190 EditPredictionSettings::Enabled {
7191 preview_requires_modifier,
7192 ..
7193 } => preview_requires_modifier,
7194 }
7195 }
7196
7197 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7198 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7199 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7200 self.discard_edit_prediction(false, cx);
7201 } else {
7202 let selection = self.selections.newest_anchor();
7203 let cursor = selection.head();
7204
7205 if let Some((buffer, cursor_buffer_position)) =
7206 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7207 {
7208 self.edit_prediction_settings =
7209 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7210 }
7211 }
7212 }
7213
7214 fn edit_prediction_settings_at_position(
7215 &self,
7216 buffer: &Entity<Buffer>,
7217 buffer_position: language::Anchor,
7218 cx: &App,
7219 ) -> EditPredictionSettings {
7220 if !self.mode.is_full()
7221 || !self.show_edit_predictions_override.unwrap_or(true)
7222 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7223 {
7224 return EditPredictionSettings::Disabled;
7225 }
7226
7227 let buffer = buffer.read(cx);
7228
7229 let file = buffer.file();
7230
7231 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7232 return EditPredictionSettings::Disabled;
7233 };
7234
7235 let by_provider = matches!(
7236 self.menu_edit_predictions_policy,
7237 MenuEditPredictionsPolicy::ByProvider
7238 );
7239
7240 let show_in_menu = by_provider
7241 && self
7242 .edit_prediction_provider
7243 .as_ref()
7244 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7245
7246 let preview_requires_modifier =
7247 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7248
7249 EditPredictionSettings::Enabled {
7250 show_in_menu,
7251 preview_requires_modifier,
7252 }
7253 }
7254
7255 fn should_show_edit_predictions(&self) -> bool {
7256 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7257 }
7258
7259 pub fn edit_prediction_preview_is_active(&self) -> bool {
7260 matches!(
7261 self.edit_prediction_preview,
7262 EditPredictionPreview::Active { .. }
7263 )
7264 }
7265
7266 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7267 let cursor = self.selections.newest_anchor().head();
7268 if let Some((buffer, cursor_position)) =
7269 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7270 {
7271 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7272 } else {
7273 false
7274 }
7275 }
7276
7277 pub fn supports_minimap(&self, cx: &App) -> bool {
7278 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7279 }
7280
7281 fn edit_predictions_enabled_in_buffer(
7282 &self,
7283 buffer: &Entity<Buffer>,
7284 buffer_position: language::Anchor,
7285 cx: &App,
7286 ) -> bool {
7287 maybe!({
7288 if self.read_only(cx) {
7289 return Some(false);
7290 }
7291 let provider = self.edit_prediction_provider()?;
7292 if !provider.is_enabled(buffer, buffer_position, cx) {
7293 return Some(false);
7294 }
7295 let buffer = buffer.read(cx);
7296 let Some(file) = buffer.file() else {
7297 return Some(true);
7298 };
7299 let settings = all_language_settings(Some(file), cx);
7300 Some(settings.edit_predictions_enabled_for_file(file, cx))
7301 })
7302 .unwrap_or(false)
7303 }
7304
7305 fn cycle_edit_prediction(
7306 &mut self,
7307 direction: Direction,
7308 window: &mut Window,
7309 cx: &mut Context<Self>,
7310 ) -> Option<()> {
7311 let provider = self.edit_prediction_provider()?;
7312 let cursor = self.selections.newest_anchor().head();
7313 let (buffer, cursor_buffer_position) =
7314 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7315 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7316 return None;
7317 }
7318
7319 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7320 self.update_visible_edit_prediction(window, cx);
7321
7322 Some(())
7323 }
7324
7325 pub fn show_edit_prediction(
7326 &mut self,
7327 _: &ShowEditPrediction,
7328 window: &mut Window,
7329 cx: &mut Context<Self>,
7330 ) {
7331 if !self.has_active_edit_prediction() {
7332 self.refresh_edit_prediction(false, true, window, cx);
7333 return;
7334 }
7335
7336 self.update_visible_edit_prediction(window, cx);
7337 }
7338
7339 pub fn display_cursor_names(
7340 &mut self,
7341 _: &DisplayCursorNames,
7342 window: &mut Window,
7343 cx: &mut Context<Self>,
7344 ) {
7345 self.show_cursor_names(window, cx);
7346 }
7347
7348 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7349 self.show_cursor_names = true;
7350 cx.notify();
7351 cx.spawn_in(window, async move |this, cx| {
7352 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7353 this.update(cx, |this, cx| {
7354 this.show_cursor_names = false;
7355 cx.notify()
7356 })
7357 .ok()
7358 })
7359 .detach();
7360 }
7361
7362 pub fn next_edit_prediction(
7363 &mut self,
7364 _: &NextEditPrediction,
7365 window: &mut Window,
7366 cx: &mut Context<Self>,
7367 ) {
7368 if self.has_active_edit_prediction() {
7369 self.cycle_edit_prediction(Direction::Next, window, cx);
7370 } else {
7371 let is_copilot_disabled = self
7372 .refresh_edit_prediction(false, true, window, cx)
7373 .is_none();
7374 if is_copilot_disabled {
7375 cx.propagate();
7376 }
7377 }
7378 }
7379
7380 pub fn previous_edit_prediction(
7381 &mut self,
7382 _: &PreviousEditPrediction,
7383 window: &mut Window,
7384 cx: &mut Context<Self>,
7385 ) {
7386 if self.has_active_edit_prediction() {
7387 self.cycle_edit_prediction(Direction::Prev, window, cx);
7388 } else {
7389 let is_copilot_disabled = self
7390 .refresh_edit_prediction(false, true, window, cx)
7391 .is_none();
7392 if is_copilot_disabled {
7393 cx.propagate();
7394 }
7395 }
7396 }
7397
7398 pub fn accept_edit_prediction(
7399 &mut self,
7400 _: &AcceptEditPrediction,
7401 window: &mut Window,
7402 cx: &mut Context<Self>,
7403 ) {
7404 if self.show_edit_predictions_in_menu() {
7405 self.hide_context_menu(window, cx);
7406 }
7407
7408 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7409 return;
7410 };
7411
7412 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7413
7414 match &active_edit_prediction.completion {
7415 EditPrediction::Move { target, .. } => {
7416 let target = *target;
7417
7418 if let Some(position_map) = &self.last_position_map {
7419 if position_map
7420 .visible_row_range
7421 .contains(&target.to_display_point(&position_map.snapshot).row())
7422 || !self.edit_prediction_requires_modifier()
7423 {
7424 self.unfold_ranges(&[target..target], true, false, cx);
7425 // Note that this is also done in vim's handler of the Tab action.
7426 self.change_selections(
7427 SelectionEffects::scroll(Autoscroll::newest()),
7428 window,
7429 cx,
7430 |selections| {
7431 selections.select_anchor_ranges([target..target]);
7432 },
7433 );
7434 self.clear_row_highlights::<EditPredictionPreview>();
7435
7436 self.edit_prediction_preview
7437 .set_previous_scroll_position(None);
7438 } else {
7439 self.edit_prediction_preview
7440 .set_previous_scroll_position(Some(
7441 position_map.snapshot.scroll_anchor,
7442 ));
7443
7444 self.highlight_rows::<EditPredictionPreview>(
7445 target..target,
7446 cx.theme().colors().editor_highlighted_line_background,
7447 RowHighlightOptions {
7448 autoscroll: true,
7449 ..Default::default()
7450 },
7451 cx,
7452 );
7453 self.request_autoscroll(Autoscroll::fit(), cx);
7454 }
7455 }
7456 }
7457 EditPrediction::Edit { edits, .. } => {
7458 if let Some(provider) = self.edit_prediction_provider() {
7459 provider.accept(cx);
7460 }
7461
7462 // Store the transaction ID and selections before applying the edit
7463 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7464
7465 let snapshot = self.buffer.read(cx).snapshot(cx);
7466 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7467
7468 self.buffer.update(cx, |buffer, cx| {
7469 buffer.edit(edits.iter().cloned(), None, cx)
7470 });
7471
7472 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7473 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7474 });
7475
7476 let selections = self.selections.disjoint_anchors();
7477 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7478 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7479 if has_new_transaction {
7480 self.selection_history
7481 .insert_transaction(transaction_id_now, selections);
7482 }
7483 }
7484
7485 self.update_visible_edit_prediction(window, cx);
7486 if self.active_edit_prediction.is_none() {
7487 self.refresh_edit_prediction(true, true, window, cx);
7488 }
7489
7490 cx.notify();
7491 }
7492 }
7493
7494 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7495 }
7496
7497 pub fn accept_partial_edit_prediction(
7498 &mut self,
7499 _: &AcceptPartialEditPrediction,
7500 window: &mut Window,
7501 cx: &mut Context<Self>,
7502 ) {
7503 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7504 return;
7505 };
7506 if self.selections.count() != 1 {
7507 return;
7508 }
7509
7510 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7511
7512 match &active_edit_prediction.completion {
7513 EditPrediction::Move { target, .. } => {
7514 let target = *target;
7515 self.change_selections(
7516 SelectionEffects::scroll(Autoscroll::newest()),
7517 window,
7518 cx,
7519 |selections| {
7520 selections.select_anchor_ranges([target..target]);
7521 },
7522 );
7523 }
7524 EditPrediction::Edit { edits, .. } => {
7525 // Find an insertion that starts at the cursor position.
7526 let snapshot = self.buffer.read(cx).snapshot(cx);
7527 let cursor_offset = self.selections.newest::<usize>(cx).head();
7528 let insertion = edits.iter().find_map(|(range, text)| {
7529 let range = range.to_offset(&snapshot);
7530 if range.is_empty() && range.start == cursor_offset {
7531 Some(text)
7532 } else {
7533 None
7534 }
7535 });
7536
7537 if let Some(text) = insertion {
7538 let mut partial_completion = text
7539 .chars()
7540 .by_ref()
7541 .take_while(|c| c.is_alphabetic())
7542 .collect::<String>();
7543 if partial_completion.is_empty() {
7544 partial_completion = text
7545 .chars()
7546 .by_ref()
7547 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7548 .collect::<String>();
7549 }
7550
7551 cx.emit(EditorEvent::InputHandled {
7552 utf16_range_to_replace: None,
7553 text: partial_completion.clone().into(),
7554 });
7555
7556 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7557
7558 self.refresh_edit_prediction(true, true, window, cx);
7559 cx.notify();
7560 } else {
7561 self.accept_edit_prediction(&Default::default(), window, cx);
7562 }
7563 }
7564 }
7565 }
7566
7567 fn discard_edit_prediction(
7568 &mut self,
7569 should_report_edit_prediction_event: bool,
7570 cx: &mut Context<Self>,
7571 ) -> bool {
7572 if should_report_edit_prediction_event {
7573 let completion_id = self
7574 .active_edit_prediction
7575 .as_ref()
7576 .and_then(|active_completion| active_completion.completion_id.clone());
7577
7578 self.report_edit_prediction_event(completion_id, false, cx);
7579 }
7580
7581 if let Some(provider) = self.edit_prediction_provider() {
7582 provider.discard(cx);
7583 }
7584
7585 self.take_active_edit_prediction(cx)
7586 }
7587
7588 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7589 let Some(provider) = self.edit_prediction_provider() else {
7590 return;
7591 };
7592
7593 let Some((_, buffer, _)) = self
7594 .buffer
7595 .read(cx)
7596 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7597 else {
7598 return;
7599 };
7600
7601 let extension = buffer
7602 .read(cx)
7603 .file()
7604 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7605
7606 let event_type = match accepted {
7607 true => "Edit Prediction Accepted",
7608 false => "Edit Prediction Discarded",
7609 };
7610 telemetry::event!(
7611 event_type,
7612 provider = provider.name(),
7613 prediction_id = id,
7614 suggestion_accepted = accepted,
7615 file_extension = extension,
7616 );
7617 }
7618
7619 pub fn has_active_edit_prediction(&self) -> bool {
7620 self.active_edit_prediction.is_some()
7621 }
7622
7623 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7624 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7625 return false;
7626 };
7627
7628 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7629 self.clear_highlights::<EditPredictionHighlight>(cx);
7630 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7631 true
7632 }
7633
7634 /// Returns true when we're displaying the edit prediction popover below the cursor
7635 /// like we are not previewing and the LSP autocomplete menu is visible
7636 /// or we are in `when_holding_modifier` mode.
7637 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7638 if self.edit_prediction_preview_is_active()
7639 || !self.show_edit_predictions_in_menu()
7640 || !self.edit_predictions_enabled()
7641 {
7642 return false;
7643 }
7644
7645 if self.has_visible_completions_menu() {
7646 return true;
7647 }
7648
7649 has_completion && self.edit_prediction_requires_modifier()
7650 }
7651
7652 fn handle_modifiers_changed(
7653 &mut self,
7654 modifiers: Modifiers,
7655 position_map: &PositionMap,
7656 window: &mut Window,
7657 cx: &mut Context<Self>,
7658 ) {
7659 if self.show_edit_predictions_in_menu() {
7660 self.update_edit_prediction_preview(&modifiers, window, cx);
7661 }
7662
7663 self.update_selection_mode(&modifiers, position_map, window, cx);
7664
7665 let mouse_position = window.mouse_position();
7666 if !position_map.text_hitbox.is_hovered(window) {
7667 return;
7668 }
7669
7670 self.update_hovered_link(
7671 position_map.point_for_position(mouse_position),
7672 &position_map.snapshot,
7673 modifiers,
7674 window,
7675 cx,
7676 )
7677 }
7678
7679 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7680 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7681 if invert {
7682 match multi_cursor_setting {
7683 MultiCursorModifier::Alt => modifiers.alt,
7684 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7685 }
7686 } else {
7687 match multi_cursor_setting {
7688 MultiCursorModifier::Alt => modifiers.secondary(),
7689 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7690 }
7691 }
7692 }
7693
7694 fn columnar_selection_mode(
7695 modifiers: &Modifiers,
7696 cx: &mut Context<Self>,
7697 ) -> Option<ColumnarMode> {
7698 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7699 if Self::multi_cursor_modifier(false, modifiers, cx) {
7700 Some(ColumnarMode::FromMouse)
7701 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7702 Some(ColumnarMode::FromSelection)
7703 } else {
7704 None
7705 }
7706 } else {
7707 None
7708 }
7709 }
7710
7711 fn update_selection_mode(
7712 &mut self,
7713 modifiers: &Modifiers,
7714 position_map: &PositionMap,
7715 window: &mut Window,
7716 cx: &mut Context<Self>,
7717 ) {
7718 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7719 return;
7720 };
7721 if self.selections.pending.is_none() {
7722 return;
7723 }
7724
7725 let mouse_position = window.mouse_position();
7726 let point_for_position = position_map.point_for_position(mouse_position);
7727 let position = point_for_position.previous_valid;
7728
7729 self.select(
7730 SelectPhase::BeginColumnar {
7731 position,
7732 reset: false,
7733 mode,
7734 goal_column: point_for_position.exact_unclipped.column(),
7735 },
7736 window,
7737 cx,
7738 );
7739 }
7740
7741 fn update_edit_prediction_preview(
7742 &mut self,
7743 modifiers: &Modifiers,
7744 window: &mut Window,
7745 cx: &mut Context<Self>,
7746 ) {
7747 let mut modifiers_held = false;
7748 if let Some(accept_keystroke) = self
7749 .accept_edit_prediction_keybind(false, window, cx)
7750 .keystroke()
7751 {
7752 modifiers_held = modifiers_held
7753 || (accept_keystroke.modifiers() == modifiers
7754 && accept_keystroke.modifiers().modified());
7755 };
7756 if let Some(accept_partial_keystroke) = self
7757 .accept_edit_prediction_keybind(true, window, cx)
7758 .keystroke()
7759 {
7760 modifiers_held = modifiers_held
7761 || (accept_partial_keystroke.modifiers() == modifiers
7762 && accept_partial_keystroke.modifiers().modified());
7763 }
7764
7765 if modifiers_held {
7766 if matches!(
7767 self.edit_prediction_preview,
7768 EditPredictionPreview::Inactive { .. }
7769 ) {
7770 self.edit_prediction_preview = EditPredictionPreview::Active {
7771 previous_scroll_position: None,
7772 since: Instant::now(),
7773 };
7774
7775 self.update_visible_edit_prediction(window, cx);
7776 cx.notify();
7777 }
7778 } else if let EditPredictionPreview::Active {
7779 previous_scroll_position,
7780 since,
7781 } = self.edit_prediction_preview
7782 {
7783 if let (Some(previous_scroll_position), Some(position_map)) =
7784 (previous_scroll_position, self.last_position_map.as_ref())
7785 {
7786 self.set_scroll_position(
7787 previous_scroll_position
7788 .scroll_position(&position_map.snapshot.display_snapshot),
7789 window,
7790 cx,
7791 );
7792 }
7793
7794 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7795 released_too_fast: since.elapsed() < Duration::from_millis(200),
7796 };
7797 self.clear_row_highlights::<EditPredictionPreview>();
7798 self.update_visible_edit_prediction(window, cx);
7799 cx.notify();
7800 }
7801 }
7802
7803 fn update_visible_edit_prediction(
7804 &mut self,
7805 _window: &mut Window,
7806 cx: &mut Context<Self>,
7807 ) -> Option<()> {
7808 if DisableAiSettings::get_global(cx).disable_ai {
7809 return None;
7810 }
7811
7812 if self.ime_transaction.is_some() {
7813 self.discard_edit_prediction(false, cx);
7814 return None;
7815 }
7816
7817 let selection = self.selections.newest_anchor();
7818 let cursor = selection.head();
7819 let multibuffer = self.buffer.read(cx).snapshot(cx);
7820 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7821 let excerpt_id = cursor.excerpt_id;
7822
7823 let show_in_menu = self.show_edit_predictions_in_menu();
7824 let completions_menu_has_precedence = !show_in_menu
7825 && (self.context_menu.borrow().is_some()
7826 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7827
7828 if completions_menu_has_precedence
7829 || !offset_selection.is_empty()
7830 || self
7831 .active_edit_prediction
7832 .as_ref()
7833 .is_some_and(|completion| {
7834 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7835 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7836 !invalidation_range.contains(&offset_selection.head())
7837 })
7838 {
7839 self.discard_edit_prediction(false, cx);
7840 return None;
7841 }
7842
7843 self.take_active_edit_prediction(cx);
7844 let Some(provider) = self.edit_prediction_provider() else {
7845 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7846 return None;
7847 };
7848
7849 let (buffer, cursor_buffer_position) =
7850 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7851
7852 self.edit_prediction_settings =
7853 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7854
7855 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7856
7857 if self.edit_prediction_indent_conflict {
7858 let cursor_point = cursor.to_point(&multibuffer);
7859
7860 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7861
7862 if let Some((_, indent)) = indents.iter().next()
7863 && indent.len == cursor_point.column
7864 {
7865 self.edit_prediction_indent_conflict = false;
7866 }
7867 }
7868
7869 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7870 let edits = edit_prediction
7871 .edits
7872 .into_iter()
7873 .flat_map(|(range, new_text)| {
7874 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7875 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7876 Some((start..end, new_text))
7877 })
7878 .collect::<Vec<_>>();
7879 if edits.is_empty() {
7880 return None;
7881 }
7882
7883 let first_edit_start = edits.first().unwrap().0.start;
7884 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7885 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7886
7887 let last_edit_end = edits.last().unwrap().0.end;
7888 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7889 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7890
7891 let cursor_row = cursor.to_point(&multibuffer).row;
7892
7893 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7894
7895 let mut inlay_ids = Vec::new();
7896 let invalidation_row_range;
7897 let move_invalidation_row_range = if cursor_row < edit_start_row {
7898 Some(cursor_row..edit_end_row)
7899 } else if cursor_row > edit_end_row {
7900 Some(edit_start_row..cursor_row)
7901 } else {
7902 None
7903 };
7904 let supports_jump = self
7905 .edit_prediction_provider
7906 .as_ref()
7907 .map(|provider| provider.provider.supports_jump_to_edit())
7908 .unwrap_or(true);
7909
7910 let is_move = supports_jump
7911 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7912 let completion = if is_move {
7913 invalidation_row_range =
7914 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7915 let target = first_edit_start;
7916 EditPrediction::Move { target, snapshot }
7917 } else {
7918 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7919 && !self.edit_predictions_hidden_for_vim_mode;
7920
7921 if show_completions_in_buffer {
7922 if edits
7923 .iter()
7924 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7925 {
7926 let mut inlays = Vec::new();
7927 for (range, new_text) in &edits {
7928 let inlay = Inlay::edit_prediction(
7929 post_inc(&mut self.next_inlay_id),
7930 range.start,
7931 new_text.as_str(),
7932 );
7933 inlay_ids.push(inlay.id);
7934 inlays.push(inlay);
7935 }
7936
7937 self.splice_inlays(&[], inlays, cx);
7938 } else {
7939 let background_color = cx.theme().status().deleted_background;
7940 self.highlight_text::<EditPredictionHighlight>(
7941 edits.iter().map(|(range, _)| range.clone()).collect(),
7942 HighlightStyle {
7943 background_color: Some(background_color),
7944 ..Default::default()
7945 },
7946 cx,
7947 );
7948 }
7949 }
7950
7951 invalidation_row_range = edit_start_row..edit_end_row;
7952
7953 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7954 if provider.show_tab_accept_marker() {
7955 EditDisplayMode::TabAccept
7956 } else {
7957 EditDisplayMode::Inline
7958 }
7959 } else {
7960 EditDisplayMode::DiffPopover
7961 };
7962
7963 EditPrediction::Edit {
7964 edits,
7965 edit_preview: edit_prediction.edit_preview,
7966 display_mode,
7967 snapshot,
7968 }
7969 };
7970
7971 let invalidation_range = multibuffer
7972 .anchor_before(Point::new(invalidation_row_range.start, 0))
7973 ..multibuffer.anchor_after(Point::new(
7974 invalidation_row_range.end,
7975 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7976 ));
7977
7978 self.stale_edit_prediction_in_menu = None;
7979 self.active_edit_prediction = Some(EditPredictionState {
7980 inlay_ids,
7981 completion,
7982 completion_id: edit_prediction.id,
7983 invalidation_range,
7984 });
7985
7986 cx.notify();
7987
7988 Some(())
7989 }
7990
7991 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7992 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7993 }
7994
7995 fn clear_tasks(&mut self) {
7996 self.tasks.clear()
7997 }
7998
7999 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8000 if self.tasks.insert(key, value).is_some() {
8001 // This case should hopefully be rare, but just in case...
8002 log::error!(
8003 "multiple different run targets found on a single line, only the last target will be rendered"
8004 )
8005 }
8006 }
8007
8008 /// Get all display points of breakpoints that will be rendered within editor
8009 ///
8010 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8011 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8012 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8013 fn active_breakpoints(
8014 &self,
8015 range: Range<DisplayRow>,
8016 window: &mut Window,
8017 cx: &mut Context<Self>,
8018 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8019 let mut breakpoint_display_points = HashMap::default();
8020
8021 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8022 return breakpoint_display_points;
8023 };
8024
8025 let snapshot = self.snapshot(window, cx);
8026
8027 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8028 let Some(project) = self.project() else {
8029 return breakpoint_display_points;
8030 };
8031
8032 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8033 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8034
8035 for (buffer_snapshot, range, excerpt_id) in
8036 multi_buffer_snapshot.range_to_buffer_ranges(range)
8037 {
8038 let Some(buffer) = project
8039 .read(cx)
8040 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8041 else {
8042 continue;
8043 };
8044 let breakpoints = breakpoint_store.read(cx).breakpoints(
8045 &buffer,
8046 Some(
8047 buffer_snapshot.anchor_before(range.start)
8048 ..buffer_snapshot.anchor_after(range.end),
8049 ),
8050 buffer_snapshot,
8051 cx,
8052 );
8053 for (breakpoint, state) in breakpoints {
8054 let multi_buffer_anchor =
8055 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8056 let position = multi_buffer_anchor
8057 .to_point(multi_buffer_snapshot)
8058 .to_display_point(&snapshot);
8059
8060 breakpoint_display_points.insert(
8061 position.row(),
8062 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8063 );
8064 }
8065 }
8066
8067 breakpoint_display_points
8068 }
8069
8070 fn breakpoint_context_menu(
8071 &self,
8072 anchor: Anchor,
8073 window: &mut Window,
8074 cx: &mut Context<Self>,
8075 ) -> Entity<ui::ContextMenu> {
8076 let weak_editor = cx.weak_entity();
8077 let focus_handle = self.focus_handle(cx);
8078
8079 let row = self
8080 .buffer
8081 .read(cx)
8082 .snapshot(cx)
8083 .summary_for_anchor::<Point>(&anchor)
8084 .row;
8085
8086 let breakpoint = self
8087 .breakpoint_at_row(row, window, cx)
8088 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8089
8090 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8091 "Edit Log Breakpoint"
8092 } else {
8093 "Set Log Breakpoint"
8094 };
8095
8096 let condition_breakpoint_msg = if breakpoint
8097 .as_ref()
8098 .is_some_and(|bp| bp.1.condition.is_some())
8099 {
8100 "Edit Condition Breakpoint"
8101 } else {
8102 "Set Condition Breakpoint"
8103 };
8104
8105 let hit_condition_breakpoint_msg = if breakpoint
8106 .as_ref()
8107 .is_some_and(|bp| bp.1.hit_condition.is_some())
8108 {
8109 "Edit Hit Condition Breakpoint"
8110 } else {
8111 "Set Hit Condition Breakpoint"
8112 };
8113
8114 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8115 "Unset Breakpoint"
8116 } else {
8117 "Set Breakpoint"
8118 };
8119
8120 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8121
8122 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8123 BreakpointState::Enabled => Some("Disable"),
8124 BreakpointState::Disabled => Some("Enable"),
8125 });
8126
8127 let (anchor, breakpoint) =
8128 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8129
8130 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8131 menu.on_blur_subscription(Subscription::new(|| {}))
8132 .context(focus_handle)
8133 .when(run_to_cursor, |this| {
8134 let weak_editor = weak_editor.clone();
8135 this.entry("Run to cursor", None, move |window, cx| {
8136 weak_editor
8137 .update(cx, |editor, cx| {
8138 editor.change_selections(
8139 SelectionEffects::no_scroll(),
8140 window,
8141 cx,
8142 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8143 );
8144 })
8145 .ok();
8146
8147 window.dispatch_action(Box::new(RunToCursor), cx);
8148 })
8149 .separator()
8150 })
8151 .when_some(toggle_state_msg, |this, msg| {
8152 this.entry(msg, None, {
8153 let weak_editor = weak_editor.clone();
8154 let breakpoint = breakpoint.clone();
8155 move |_window, cx| {
8156 weak_editor
8157 .update(cx, |this, cx| {
8158 this.edit_breakpoint_at_anchor(
8159 anchor,
8160 breakpoint.as_ref().clone(),
8161 BreakpointEditAction::InvertState,
8162 cx,
8163 );
8164 })
8165 .log_err();
8166 }
8167 })
8168 })
8169 .entry(set_breakpoint_msg, None, {
8170 let weak_editor = weak_editor.clone();
8171 let breakpoint = breakpoint.clone();
8172 move |_window, cx| {
8173 weak_editor
8174 .update(cx, |this, cx| {
8175 this.edit_breakpoint_at_anchor(
8176 anchor,
8177 breakpoint.as_ref().clone(),
8178 BreakpointEditAction::Toggle,
8179 cx,
8180 );
8181 })
8182 .log_err();
8183 }
8184 })
8185 .entry(log_breakpoint_msg, None, {
8186 let breakpoint = breakpoint.clone();
8187 let weak_editor = weak_editor.clone();
8188 move |window, cx| {
8189 weak_editor
8190 .update(cx, |this, cx| {
8191 this.add_edit_breakpoint_block(
8192 anchor,
8193 breakpoint.as_ref(),
8194 BreakpointPromptEditAction::Log,
8195 window,
8196 cx,
8197 );
8198 })
8199 .log_err();
8200 }
8201 })
8202 .entry(condition_breakpoint_msg, None, {
8203 let breakpoint = breakpoint.clone();
8204 let weak_editor = weak_editor.clone();
8205 move |window, cx| {
8206 weak_editor
8207 .update(cx, |this, cx| {
8208 this.add_edit_breakpoint_block(
8209 anchor,
8210 breakpoint.as_ref(),
8211 BreakpointPromptEditAction::Condition,
8212 window,
8213 cx,
8214 );
8215 })
8216 .log_err();
8217 }
8218 })
8219 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8220 weak_editor
8221 .update(cx, |this, cx| {
8222 this.add_edit_breakpoint_block(
8223 anchor,
8224 breakpoint.as_ref(),
8225 BreakpointPromptEditAction::HitCondition,
8226 window,
8227 cx,
8228 );
8229 })
8230 .log_err();
8231 })
8232 })
8233 }
8234
8235 fn render_breakpoint(
8236 &self,
8237 position: Anchor,
8238 row: DisplayRow,
8239 breakpoint: &Breakpoint,
8240 state: Option<BreakpointSessionState>,
8241 cx: &mut Context<Self>,
8242 ) -> IconButton {
8243 let is_rejected = state.is_some_and(|s| !s.verified);
8244 // Is it a breakpoint that shows up when hovering over gutter?
8245 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8246 (false, false),
8247 |PhantomBreakpointIndicator {
8248 is_active,
8249 display_row,
8250 collides_with_existing_breakpoint,
8251 }| {
8252 (
8253 is_active && display_row == row,
8254 collides_with_existing_breakpoint,
8255 )
8256 },
8257 );
8258
8259 let (color, icon) = {
8260 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8261 (false, false) => ui::IconName::DebugBreakpoint,
8262 (true, false) => ui::IconName::DebugLogBreakpoint,
8263 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8264 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8265 };
8266
8267 let color = if is_phantom {
8268 Color::Hint
8269 } else if is_rejected {
8270 Color::Disabled
8271 } else {
8272 Color::Debugger
8273 };
8274
8275 (color, icon)
8276 };
8277
8278 let breakpoint = Arc::from(breakpoint.clone());
8279
8280 let alt_as_text = gpui::Keystroke {
8281 modifiers: Modifiers::secondary_key(),
8282 ..Default::default()
8283 };
8284 let primary_action_text = if breakpoint.is_disabled() {
8285 "Enable breakpoint"
8286 } else if is_phantom && !collides_with_existing {
8287 "Set breakpoint"
8288 } else {
8289 "Unset breakpoint"
8290 };
8291 let focus_handle = self.focus_handle.clone();
8292
8293 let meta = if is_rejected {
8294 SharedString::from("No executable code is associated with this line.")
8295 } else if collides_with_existing && !breakpoint.is_disabled() {
8296 SharedString::from(format!(
8297 "{alt_as_text}-click to disable,\nright-click for more options."
8298 ))
8299 } else {
8300 SharedString::from("Right-click for more options.")
8301 };
8302 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8303 .icon_size(IconSize::XSmall)
8304 .size(ui::ButtonSize::None)
8305 .when(is_rejected, |this| {
8306 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8307 })
8308 .icon_color(color)
8309 .style(ButtonStyle::Transparent)
8310 .on_click(cx.listener({
8311 move |editor, event: &ClickEvent, window, cx| {
8312 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8313 BreakpointEditAction::InvertState
8314 } else {
8315 BreakpointEditAction::Toggle
8316 };
8317
8318 window.focus(&editor.focus_handle(cx));
8319 editor.edit_breakpoint_at_anchor(
8320 position,
8321 breakpoint.as_ref().clone(),
8322 edit_action,
8323 cx,
8324 );
8325 }
8326 }))
8327 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8328 editor.set_breakpoint_context_menu(
8329 row,
8330 Some(position),
8331 event.position(),
8332 window,
8333 cx,
8334 );
8335 }))
8336 .tooltip(move |window, cx| {
8337 Tooltip::with_meta_in(
8338 primary_action_text,
8339 Some(&ToggleBreakpoint),
8340 meta.clone(),
8341 &focus_handle,
8342 window,
8343 cx,
8344 )
8345 })
8346 }
8347
8348 fn build_tasks_context(
8349 project: &Entity<Project>,
8350 buffer: &Entity<Buffer>,
8351 buffer_row: u32,
8352 tasks: &Arc<RunnableTasks>,
8353 cx: &mut Context<Self>,
8354 ) -> Task<Option<task::TaskContext>> {
8355 let position = Point::new(buffer_row, tasks.column);
8356 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8357 let location = Location {
8358 buffer: buffer.clone(),
8359 range: range_start..range_start,
8360 };
8361 // Fill in the environmental variables from the tree-sitter captures
8362 let mut captured_task_variables = TaskVariables::default();
8363 for (capture_name, value) in tasks.extra_variables.clone() {
8364 captured_task_variables.insert(
8365 task::VariableName::Custom(capture_name.into()),
8366 value.clone(),
8367 );
8368 }
8369 project.update(cx, |project, cx| {
8370 project.task_store().update(cx, |task_store, cx| {
8371 task_store.task_context_for_location(captured_task_variables, location, cx)
8372 })
8373 })
8374 }
8375
8376 pub fn spawn_nearest_task(
8377 &mut self,
8378 action: &SpawnNearestTask,
8379 window: &mut Window,
8380 cx: &mut Context<Self>,
8381 ) {
8382 let Some((workspace, _)) = self.workspace.clone() else {
8383 return;
8384 };
8385 let Some(project) = self.project.clone() else {
8386 return;
8387 };
8388
8389 // Try to find a closest, enclosing node using tree-sitter that has a task
8390 let Some((buffer, buffer_row, tasks)) = self
8391 .find_enclosing_node_task(cx)
8392 // Or find the task that's closest in row-distance.
8393 .or_else(|| self.find_closest_task(cx))
8394 else {
8395 return;
8396 };
8397
8398 let reveal_strategy = action.reveal;
8399 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8400 cx.spawn_in(window, async move |_, cx| {
8401 let context = task_context.await?;
8402 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8403
8404 let resolved = &mut resolved_task.resolved;
8405 resolved.reveal = reveal_strategy;
8406
8407 workspace
8408 .update_in(cx, |workspace, window, cx| {
8409 workspace.schedule_resolved_task(
8410 task_source_kind,
8411 resolved_task,
8412 false,
8413 window,
8414 cx,
8415 );
8416 })
8417 .ok()
8418 })
8419 .detach();
8420 }
8421
8422 fn find_closest_task(
8423 &mut self,
8424 cx: &mut Context<Self>,
8425 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8426 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8427
8428 let ((buffer_id, row), tasks) = self
8429 .tasks
8430 .iter()
8431 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8432
8433 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8434 let tasks = Arc::new(tasks.to_owned());
8435 Some((buffer, *row, tasks))
8436 }
8437
8438 fn find_enclosing_node_task(
8439 &mut self,
8440 cx: &mut Context<Self>,
8441 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8442 let snapshot = self.buffer.read(cx).snapshot(cx);
8443 let offset = self.selections.newest::<usize>(cx).head();
8444 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8445 let buffer_id = excerpt.buffer().remote_id();
8446
8447 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8448 let mut cursor = layer.node().walk();
8449
8450 while cursor.goto_first_child_for_byte(offset).is_some() {
8451 if cursor.node().end_byte() == offset {
8452 cursor.goto_next_sibling();
8453 }
8454 }
8455
8456 // Ascend to the smallest ancestor that contains the range and has a task.
8457 loop {
8458 let node = cursor.node();
8459 let node_range = node.byte_range();
8460 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8461
8462 // Check if this node contains our offset
8463 if node_range.start <= offset && node_range.end >= offset {
8464 // If it contains offset, check for task
8465 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8466 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8467 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8468 }
8469 }
8470
8471 if !cursor.goto_parent() {
8472 break;
8473 }
8474 }
8475 None
8476 }
8477
8478 fn render_run_indicator(
8479 &self,
8480 _style: &EditorStyle,
8481 is_active: bool,
8482 row: DisplayRow,
8483 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8484 cx: &mut Context<Self>,
8485 ) -> IconButton {
8486 let color = Color::Muted;
8487 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8488
8489 IconButton::new(
8490 ("run_indicator", row.0 as usize),
8491 ui::IconName::PlayOutlined,
8492 )
8493 .shape(ui::IconButtonShape::Square)
8494 .icon_size(IconSize::XSmall)
8495 .icon_color(color)
8496 .toggle_state(is_active)
8497 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8498 let quick_launch = match e {
8499 ClickEvent::Keyboard(_) => true,
8500 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8501 };
8502
8503 window.focus(&editor.focus_handle(cx));
8504 editor.toggle_code_actions(
8505 &ToggleCodeActions {
8506 deployed_from: Some(CodeActionSource::RunMenu(row)),
8507 quick_launch,
8508 },
8509 window,
8510 cx,
8511 );
8512 }))
8513 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8514 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8515 }))
8516 }
8517
8518 pub fn context_menu_visible(&self) -> bool {
8519 !self.edit_prediction_preview_is_active()
8520 && self
8521 .context_menu
8522 .borrow()
8523 .as_ref()
8524 .is_some_and(|menu| menu.visible())
8525 }
8526
8527 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8528 self.context_menu
8529 .borrow()
8530 .as_ref()
8531 .map(|menu| menu.origin())
8532 }
8533
8534 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8535 self.context_menu_options = Some(options);
8536 }
8537
8538 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8539 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8540
8541 fn render_edit_prediction_popover(
8542 &mut self,
8543 text_bounds: &Bounds<Pixels>,
8544 content_origin: gpui::Point<Pixels>,
8545 right_margin: Pixels,
8546 editor_snapshot: &EditorSnapshot,
8547 visible_row_range: Range<DisplayRow>,
8548 scroll_top: f32,
8549 scroll_bottom: f32,
8550 line_layouts: &[LineWithInvisibles],
8551 line_height: Pixels,
8552 scroll_pixel_position: gpui::Point<Pixels>,
8553 newest_selection_head: Option<DisplayPoint>,
8554 editor_width: Pixels,
8555 style: &EditorStyle,
8556 window: &mut Window,
8557 cx: &mut App,
8558 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8559 if self.mode().is_minimap() {
8560 return None;
8561 }
8562 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8563
8564 if self.edit_prediction_visible_in_cursor_popover(true) {
8565 return None;
8566 }
8567
8568 match &active_edit_prediction.completion {
8569 EditPrediction::Move { target, .. } => {
8570 let target_display_point = target.to_display_point(editor_snapshot);
8571
8572 if self.edit_prediction_requires_modifier() {
8573 if !self.edit_prediction_preview_is_active() {
8574 return None;
8575 }
8576
8577 self.render_edit_prediction_modifier_jump_popover(
8578 text_bounds,
8579 content_origin,
8580 visible_row_range,
8581 line_layouts,
8582 line_height,
8583 scroll_pixel_position,
8584 newest_selection_head,
8585 target_display_point,
8586 window,
8587 cx,
8588 )
8589 } else {
8590 self.render_edit_prediction_eager_jump_popover(
8591 text_bounds,
8592 content_origin,
8593 editor_snapshot,
8594 visible_row_range,
8595 scroll_top,
8596 scroll_bottom,
8597 line_height,
8598 scroll_pixel_position,
8599 target_display_point,
8600 editor_width,
8601 window,
8602 cx,
8603 )
8604 }
8605 }
8606 EditPrediction::Edit {
8607 display_mode: EditDisplayMode::Inline,
8608 ..
8609 } => None,
8610 EditPrediction::Edit {
8611 display_mode: EditDisplayMode::TabAccept,
8612 edits,
8613 ..
8614 } => {
8615 let range = &edits.first()?.0;
8616 let target_display_point = range.end.to_display_point(editor_snapshot);
8617
8618 self.render_edit_prediction_end_of_line_popover(
8619 "Accept",
8620 editor_snapshot,
8621 visible_row_range,
8622 target_display_point,
8623 line_height,
8624 scroll_pixel_position,
8625 content_origin,
8626 editor_width,
8627 window,
8628 cx,
8629 )
8630 }
8631 EditPrediction::Edit {
8632 edits,
8633 edit_preview,
8634 display_mode: EditDisplayMode::DiffPopover,
8635 snapshot,
8636 } => self.render_edit_prediction_diff_popover(
8637 text_bounds,
8638 content_origin,
8639 right_margin,
8640 editor_snapshot,
8641 visible_row_range,
8642 line_layouts,
8643 line_height,
8644 scroll_pixel_position,
8645 newest_selection_head,
8646 editor_width,
8647 style,
8648 edits,
8649 edit_preview,
8650 snapshot,
8651 window,
8652 cx,
8653 ),
8654 }
8655 }
8656
8657 fn render_edit_prediction_modifier_jump_popover(
8658 &mut self,
8659 text_bounds: &Bounds<Pixels>,
8660 content_origin: gpui::Point<Pixels>,
8661 visible_row_range: Range<DisplayRow>,
8662 line_layouts: &[LineWithInvisibles],
8663 line_height: Pixels,
8664 scroll_pixel_position: gpui::Point<Pixels>,
8665 newest_selection_head: Option<DisplayPoint>,
8666 target_display_point: DisplayPoint,
8667 window: &mut Window,
8668 cx: &mut App,
8669 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8670 let scrolled_content_origin =
8671 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8672
8673 const SCROLL_PADDING_Y: Pixels = px(12.);
8674
8675 if target_display_point.row() < visible_row_range.start {
8676 return self.render_edit_prediction_scroll_popover(
8677 |_| SCROLL_PADDING_Y,
8678 IconName::ArrowUp,
8679 visible_row_range,
8680 line_layouts,
8681 newest_selection_head,
8682 scrolled_content_origin,
8683 window,
8684 cx,
8685 );
8686 } else if target_display_point.row() >= visible_row_range.end {
8687 return self.render_edit_prediction_scroll_popover(
8688 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8689 IconName::ArrowDown,
8690 visible_row_range,
8691 line_layouts,
8692 newest_selection_head,
8693 scrolled_content_origin,
8694 window,
8695 cx,
8696 );
8697 }
8698
8699 const POLE_WIDTH: Pixels = px(2.);
8700
8701 let line_layout =
8702 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8703 let target_column = target_display_point.column() as usize;
8704
8705 let target_x = line_layout.x_for_index(target_column);
8706 let target_y =
8707 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8708
8709 let flag_on_right = target_x < text_bounds.size.width / 2.;
8710
8711 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8712 border_color.l += 0.001;
8713
8714 let mut element = v_flex()
8715 .items_end()
8716 .when(flag_on_right, |el| el.items_start())
8717 .child(if flag_on_right {
8718 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8719 .rounded_bl(px(0.))
8720 .rounded_tl(px(0.))
8721 .border_l_2()
8722 .border_color(border_color)
8723 } else {
8724 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8725 .rounded_br(px(0.))
8726 .rounded_tr(px(0.))
8727 .border_r_2()
8728 .border_color(border_color)
8729 })
8730 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8731 .into_any();
8732
8733 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8734
8735 let mut origin = scrolled_content_origin + point(target_x, target_y)
8736 - point(
8737 if flag_on_right {
8738 POLE_WIDTH
8739 } else {
8740 size.width - POLE_WIDTH
8741 },
8742 size.height - line_height,
8743 );
8744
8745 origin.x = origin.x.max(content_origin.x);
8746
8747 element.prepaint_at(origin, window, cx);
8748
8749 Some((element, origin))
8750 }
8751
8752 fn render_edit_prediction_scroll_popover(
8753 &mut self,
8754 to_y: impl Fn(Size<Pixels>) -> Pixels,
8755 scroll_icon: IconName,
8756 visible_row_range: Range<DisplayRow>,
8757 line_layouts: &[LineWithInvisibles],
8758 newest_selection_head: Option<DisplayPoint>,
8759 scrolled_content_origin: gpui::Point<Pixels>,
8760 window: &mut Window,
8761 cx: &mut App,
8762 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8763 let mut element = self
8764 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8765 .into_any();
8766
8767 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8768
8769 let cursor = newest_selection_head?;
8770 let cursor_row_layout =
8771 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8772 let cursor_column = cursor.column() as usize;
8773
8774 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8775
8776 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8777
8778 element.prepaint_at(origin, window, cx);
8779 Some((element, origin))
8780 }
8781
8782 fn render_edit_prediction_eager_jump_popover(
8783 &mut self,
8784 text_bounds: &Bounds<Pixels>,
8785 content_origin: gpui::Point<Pixels>,
8786 editor_snapshot: &EditorSnapshot,
8787 visible_row_range: Range<DisplayRow>,
8788 scroll_top: f32,
8789 scroll_bottom: f32,
8790 line_height: Pixels,
8791 scroll_pixel_position: gpui::Point<Pixels>,
8792 target_display_point: DisplayPoint,
8793 editor_width: Pixels,
8794 window: &mut Window,
8795 cx: &mut App,
8796 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8797 if target_display_point.row().as_f32() < scroll_top {
8798 let mut element = self
8799 .render_edit_prediction_line_popover(
8800 "Jump to Edit",
8801 Some(IconName::ArrowUp),
8802 window,
8803 cx,
8804 )?
8805 .into_any();
8806
8807 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8808 let offset = point(
8809 (text_bounds.size.width - size.width) / 2.,
8810 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8811 );
8812
8813 let origin = text_bounds.origin + offset;
8814 element.prepaint_at(origin, window, cx);
8815 Some((element, origin))
8816 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8817 let mut element = self
8818 .render_edit_prediction_line_popover(
8819 "Jump to Edit",
8820 Some(IconName::ArrowDown),
8821 window,
8822 cx,
8823 )?
8824 .into_any();
8825
8826 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8827 let offset = point(
8828 (text_bounds.size.width - size.width) / 2.,
8829 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8830 );
8831
8832 let origin = text_bounds.origin + offset;
8833 element.prepaint_at(origin, window, cx);
8834 Some((element, origin))
8835 } else {
8836 self.render_edit_prediction_end_of_line_popover(
8837 "Jump to Edit",
8838 editor_snapshot,
8839 visible_row_range,
8840 target_display_point,
8841 line_height,
8842 scroll_pixel_position,
8843 content_origin,
8844 editor_width,
8845 window,
8846 cx,
8847 )
8848 }
8849 }
8850
8851 fn render_edit_prediction_end_of_line_popover(
8852 self: &mut Editor,
8853 label: &'static str,
8854 editor_snapshot: &EditorSnapshot,
8855 visible_row_range: Range<DisplayRow>,
8856 target_display_point: DisplayPoint,
8857 line_height: Pixels,
8858 scroll_pixel_position: gpui::Point<Pixels>,
8859 content_origin: gpui::Point<Pixels>,
8860 editor_width: Pixels,
8861 window: &mut Window,
8862 cx: &mut App,
8863 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8864 let target_line_end = DisplayPoint::new(
8865 target_display_point.row(),
8866 editor_snapshot.line_len(target_display_point.row()),
8867 );
8868
8869 let mut element = self
8870 .render_edit_prediction_line_popover(label, None, window, cx)?
8871 .into_any();
8872
8873 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8874
8875 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8876
8877 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8878 let mut origin = start_point
8879 + line_origin
8880 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8881 origin.x = origin.x.max(content_origin.x);
8882
8883 let max_x = content_origin.x + editor_width - size.width;
8884
8885 if origin.x > max_x {
8886 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8887
8888 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8889 origin.y += offset;
8890 IconName::ArrowUp
8891 } else {
8892 origin.y -= offset;
8893 IconName::ArrowDown
8894 };
8895
8896 element = self
8897 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8898 .into_any();
8899
8900 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8901
8902 origin.x = content_origin.x + editor_width - size.width - px(2.);
8903 }
8904
8905 element.prepaint_at(origin, window, cx);
8906 Some((element, origin))
8907 }
8908
8909 fn render_edit_prediction_diff_popover(
8910 self: &Editor,
8911 text_bounds: &Bounds<Pixels>,
8912 content_origin: gpui::Point<Pixels>,
8913 right_margin: Pixels,
8914 editor_snapshot: &EditorSnapshot,
8915 visible_row_range: Range<DisplayRow>,
8916 line_layouts: &[LineWithInvisibles],
8917 line_height: Pixels,
8918 scroll_pixel_position: gpui::Point<Pixels>,
8919 newest_selection_head: Option<DisplayPoint>,
8920 editor_width: Pixels,
8921 style: &EditorStyle,
8922 edits: &Vec<(Range<Anchor>, String)>,
8923 edit_preview: &Option<language::EditPreview>,
8924 snapshot: &language::BufferSnapshot,
8925 window: &mut Window,
8926 cx: &mut App,
8927 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8928 let edit_start = edits
8929 .first()
8930 .unwrap()
8931 .0
8932 .start
8933 .to_display_point(editor_snapshot);
8934 let edit_end = edits
8935 .last()
8936 .unwrap()
8937 .0
8938 .end
8939 .to_display_point(editor_snapshot);
8940
8941 let is_visible = visible_row_range.contains(&edit_start.row())
8942 || visible_row_range.contains(&edit_end.row());
8943 if !is_visible {
8944 return None;
8945 }
8946
8947 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8948 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8949 } else {
8950 // Fallback for providers without edit_preview
8951 crate::edit_prediction_fallback_text(edits, cx)
8952 };
8953
8954 let styled_text = highlighted_edits.to_styled_text(&style.text);
8955 let line_count = highlighted_edits.text.lines().count();
8956
8957 const BORDER_WIDTH: Pixels = px(1.);
8958
8959 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8960 let has_keybind = keybind.is_some();
8961
8962 let mut element = h_flex()
8963 .items_start()
8964 .child(
8965 h_flex()
8966 .bg(cx.theme().colors().editor_background)
8967 .border(BORDER_WIDTH)
8968 .shadow_xs()
8969 .border_color(cx.theme().colors().border)
8970 .rounded_l_lg()
8971 .when(line_count > 1, |el| el.rounded_br_lg())
8972 .pr_1()
8973 .child(styled_text),
8974 )
8975 .child(
8976 h_flex()
8977 .h(line_height + BORDER_WIDTH * 2.)
8978 .px_1p5()
8979 .gap_1()
8980 // Workaround: For some reason, there's a gap if we don't do this
8981 .ml(-BORDER_WIDTH)
8982 .shadow(vec![gpui::BoxShadow {
8983 color: gpui::black().opacity(0.05),
8984 offset: point(px(1.), px(1.)),
8985 blur_radius: px(2.),
8986 spread_radius: px(0.),
8987 }])
8988 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8989 .border(BORDER_WIDTH)
8990 .border_color(cx.theme().colors().border)
8991 .rounded_r_lg()
8992 .id("edit_prediction_diff_popover_keybind")
8993 .when(!has_keybind, |el| {
8994 let status_colors = cx.theme().status();
8995
8996 el.bg(status_colors.error_background)
8997 .border_color(status_colors.error.opacity(0.6))
8998 .child(Icon::new(IconName::Info).color(Color::Error))
8999 .cursor_default()
9000 .hoverable_tooltip(move |_window, cx| {
9001 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9002 })
9003 })
9004 .children(keybind),
9005 )
9006 .into_any();
9007
9008 let longest_row =
9009 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9010 let longest_line_width = if visible_row_range.contains(&longest_row) {
9011 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9012 } else {
9013 layout_line(
9014 longest_row,
9015 editor_snapshot,
9016 style,
9017 editor_width,
9018 |_| false,
9019 window,
9020 cx,
9021 )
9022 .width
9023 };
9024
9025 let viewport_bounds =
9026 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9027 right: -right_margin,
9028 ..Default::default()
9029 });
9030
9031 let x_after_longest =
9032 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9033 - scroll_pixel_position.x;
9034
9035 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9036
9037 // Fully visible if it can be displayed within the window (allow overlapping other
9038 // panes). However, this is only allowed if the popover starts within text_bounds.
9039 let can_position_to_the_right = x_after_longest < text_bounds.right()
9040 && x_after_longest + element_bounds.width < viewport_bounds.right();
9041
9042 let mut origin = if can_position_to_the_right {
9043 point(
9044 x_after_longest,
9045 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9046 - scroll_pixel_position.y,
9047 )
9048 } else {
9049 let cursor_row = newest_selection_head.map(|head| head.row());
9050 let above_edit = edit_start
9051 .row()
9052 .0
9053 .checked_sub(line_count as u32)
9054 .map(DisplayRow);
9055 let below_edit = Some(edit_end.row() + 1);
9056 let above_cursor =
9057 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9058 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9059
9060 // Place the edit popover adjacent to the edit if there is a location
9061 // available that is onscreen and does not obscure the cursor. Otherwise,
9062 // place it adjacent to the cursor.
9063 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9064 .into_iter()
9065 .flatten()
9066 .find(|&start_row| {
9067 let end_row = start_row + line_count as u32;
9068 visible_row_range.contains(&start_row)
9069 && visible_row_range.contains(&end_row)
9070 && cursor_row
9071 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9072 })?;
9073
9074 content_origin
9075 + point(
9076 -scroll_pixel_position.x,
9077 row_target.as_f32() * line_height - scroll_pixel_position.y,
9078 )
9079 };
9080
9081 origin.x -= BORDER_WIDTH;
9082
9083 window.defer_draw(element, origin, 1);
9084
9085 // Do not return an element, since it will already be drawn due to defer_draw.
9086 None
9087 }
9088
9089 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9090 px(30.)
9091 }
9092
9093 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9094 if self.read_only(cx) {
9095 cx.theme().players().read_only()
9096 } else {
9097 self.style.as_ref().unwrap().local_player
9098 }
9099 }
9100
9101 fn render_edit_prediction_accept_keybind(
9102 &self,
9103 window: &mut Window,
9104 cx: &App,
9105 ) -> Option<AnyElement> {
9106 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9107 let accept_keystroke = accept_binding.keystroke()?;
9108
9109 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9110
9111 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9112 Color::Accent
9113 } else {
9114 Color::Muted
9115 };
9116
9117 h_flex()
9118 .px_0p5()
9119 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9120 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9121 .text_size(TextSize::XSmall.rems(cx))
9122 .child(h_flex().children(ui::render_modifiers(
9123 accept_keystroke.modifiers(),
9124 PlatformStyle::platform(),
9125 Some(modifiers_color),
9126 Some(IconSize::XSmall.rems().into()),
9127 true,
9128 )))
9129 .when(is_platform_style_mac, |parent| {
9130 parent.child(accept_keystroke.key().to_string())
9131 })
9132 .when(!is_platform_style_mac, |parent| {
9133 parent.child(
9134 Key::new(
9135 util::capitalize(accept_keystroke.key()),
9136 Some(Color::Default),
9137 )
9138 .size(Some(IconSize::XSmall.rems().into())),
9139 )
9140 })
9141 .into_any()
9142 .into()
9143 }
9144
9145 fn render_edit_prediction_line_popover(
9146 &self,
9147 label: impl Into<SharedString>,
9148 icon: Option<IconName>,
9149 window: &mut Window,
9150 cx: &App,
9151 ) -> Option<Stateful<Div>> {
9152 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9153
9154 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9155 let has_keybind = keybind.is_some();
9156
9157 let result = h_flex()
9158 .id("ep-line-popover")
9159 .py_0p5()
9160 .pl_1()
9161 .pr(padding_right)
9162 .gap_1()
9163 .rounded_md()
9164 .border_1()
9165 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9166 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9167 .shadow_xs()
9168 .when(!has_keybind, |el| {
9169 let status_colors = cx.theme().status();
9170
9171 el.bg(status_colors.error_background)
9172 .border_color(status_colors.error.opacity(0.6))
9173 .pl_2()
9174 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9175 .cursor_default()
9176 .hoverable_tooltip(move |_window, cx| {
9177 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9178 })
9179 })
9180 .children(keybind)
9181 .child(
9182 Label::new(label)
9183 .size(LabelSize::Small)
9184 .when(!has_keybind, |el| {
9185 el.color(cx.theme().status().error.into()).strikethrough()
9186 }),
9187 )
9188 .when(!has_keybind, |el| {
9189 el.child(
9190 h_flex().ml_1().child(
9191 Icon::new(IconName::Info)
9192 .size(IconSize::Small)
9193 .color(cx.theme().status().error.into()),
9194 ),
9195 )
9196 })
9197 .when_some(icon, |element, icon| {
9198 element.child(
9199 div()
9200 .mt(px(1.5))
9201 .child(Icon::new(icon).size(IconSize::Small)),
9202 )
9203 });
9204
9205 Some(result)
9206 }
9207
9208 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9209 let accent_color = cx.theme().colors().text_accent;
9210 let editor_bg_color = cx.theme().colors().editor_background;
9211 editor_bg_color.blend(accent_color.opacity(0.1))
9212 }
9213
9214 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9215 let accent_color = cx.theme().colors().text_accent;
9216 let editor_bg_color = cx.theme().colors().editor_background;
9217 editor_bg_color.blend(accent_color.opacity(0.6))
9218 }
9219 fn get_prediction_provider_icon_name(
9220 provider: &Option<RegisteredEditPredictionProvider>,
9221 ) -> IconName {
9222 match provider {
9223 Some(provider) => match provider.provider.name() {
9224 "copilot" => IconName::Copilot,
9225 "supermaven" => IconName::Supermaven,
9226 _ => IconName::ZedPredict,
9227 },
9228 None => IconName::ZedPredict,
9229 }
9230 }
9231
9232 fn render_edit_prediction_cursor_popover(
9233 &self,
9234 min_width: Pixels,
9235 max_width: Pixels,
9236 cursor_point: Point,
9237 style: &EditorStyle,
9238 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9239 _window: &Window,
9240 cx: &mut Context<Editor>,
9241 ) -> Option<AnyElement> {
9242 let provider = self.edit_prediction_provider.as_ref()?;
9243 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9244
9245 let is_refreshing = provider.provider.is_refreshing(cx);
9246
9247 fn pending_completion_container(icon: IconName) -> Div {
9248 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9249 }
9250
9251 let completion = match &self.active_edit_prediction {
9252 Some(prediction) => {
9253 if !self.has_visible_completions_menu() {
9254 const RADIUS: Pixels = px(6.);
9255 const BORDER_WIDTH: Pixels = px(1.);
9256
9257 return Some(
9258 h_flex()
9259 .elevation_2(cx)
9260 .border(BORDER_WIDTH)
9261 .border_color(cx.theme().colors().border)
9262 .when(accept_keystroke.is_none(), |el| {
9263 el.border_color(cx.theme().status().error)
9264 })
9265 .rounded(RADIUS)
9266 .rounded_tl(px(0.))
9267 .overflow_hidden()
9268 .child(div().px_1p5().child(match &prediction.completion {
9269 EditPrediction::Move { target, snapshot } => {
9270 use text::ToPoint as _;
9271 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9272 {
9273 Icon::new(IconName::ZedPredictDown)
9274 } else {
9275 Icon::new(IconName::ZedPredictUp)
9276 }
9277 }
9278 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9279 }))
9280 .child(
9281 h_flex()
9282 .gap_1()
9283 .py_1()
9284 .px_2()
9285 .rounded_r(RADIUS - BORDER_WIDTH)
9286 .border_l_1()
9287 .border_color(cx.theme().colors().border)
9288 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9289 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9290 el.child(
9291 Label::new("Hold")
9292 .size(LabelSize::Small)
9293 .when(accept_keystroke.is_none(), |el| {
9294 el.strikethrough()
9295 })
9296 .line_height_style(LineHeightStyle::UiLabel),
9297 )
9298 })
9299 .id("edit_prediction_cursor_popover_keybind")
9300 .when(accept_keystroke.is_none(), |el| {
9301 let status_colors = cx.theme().status();
9302
9303 el.bg(status_colors.error_background)
9304 .border_color(status_colors.error.opacity(0.6))
9305 .child(Icon::new(IconName::Info).color(Color::Error))
9306 .cursor_default()
9307 .hoverable_tooltip(move |_window, cx| {
9308 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9309 .into()
9310 })
9311 })
9312 .when_some(
9313 accept_keystroke.as_ref(),
9314 |el, accept_keystroke| {
9315 el.child(h_flex().children(ui::render_modifiers(
9316 accept_keystroke.modifiers(),
9317 PlatformStyle::platform(),
9318 Some(Color::Default),
9319 Some(IconSize::XSmall.rems().into()),
9320 false,
9321 )))
9322 },
9323 ),
9324 )
9325 .into_any(),
9326 );
9327 }
9328
9329 self.render_edit_prediction_cursor_popover_preview(
9330 prediction,
9331 cursor_point,
9332 style,
9333 cx,
9334 )?
9335 }
9336
9337 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9338 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9339 stale_completion,
9340 cursor_point,
9341 style,
9342 cx,
9343 )?,
9344
9345 None => pending_completion_container(provider_icon)
9346 .child(Label::new("...").size(LabelSize::Small)),
9347 },
9348
9349 None => pending_completion_container(provider_icon)
9350 .child(Label::new("...").size(LabelSize::Small)),
9351 };
9352
9353 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9354 completion
9355 .with_animation(
9356 "loading-completion",
9357 Animation::new(Duration::from_secs(2))
9358 .repeat()
9359 .with_easing(pulsating_between(0.4, 0.8)),
9360 |label, delta| label.opacity(delta),
9361 )
9362 .into_any_element()
9363 } else {
9364 completion.into_any_element()
9365 };
9366
9367 let has_completion = self.active_edit_prediction.is_some();
9368
9369 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9370 Some(
9371 h_flex()
9372 .min_w(min_width)
9373 .max_w(max_width)
9374 .flex_1()
9375 .elevation_2(cx)
9376 .border_color(cx.theme().colors().border)
9377 .child(
9378 div()
9379 .flex_1()
9380 .py_1()
9381 .px_2()
9382 .overflow_hidden()
9383 .child(completion),
9384 )
9385 .when_some(accept_keystroke, |el, accept_keystroke| {
9386 if !accept_keystroke.modifiers().modified() {
9387 return el;
9388 }
9389
9390 el.child(
9391 h_flex()
9392 .h_full()
9393 .border_l_1()
9394 .rounded_r_lg()
9395 .border_color(cx.theme().colors().border)
9396 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9397 .gap_1()
9398 .py_1()
9399 .px_2()
9400 .child(
9401 h_flex()
9402 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9403 .when(is_platform_style_mac, |parent| parent.gap_1())
9404 .child(h_flex().children(ui::render_modifiers(
9405 accept_keystroke.modifiers(),
9406 PlatformStyle::platform(),
9407 Some(if !has_completion {
9408 Color::Muted
9409 } else {
9410 Color::Default
9411 }),
9412 None,
9413 false,
9414 ))),
9415 )
9416 .child(Label::new("Preview").into_any_element())
9417 .opacity(if has_completion { 1.0 } else { 0.4 }),
9418 )
9419 })
9420 .into_any(),
9421 )
9422 }
9423
9424 fn render_edit_prediction_cursor_popover_preview(
9425 &self,
9426 completion: &EditPredictionState,
9427 cursor_point: Point,
9428 style: &EditorStyle,
9429 cx: &mut Context<Editor>,
9430 ) -> Option<Div> {
9431 use text::ToPoint as _;
9432
9433 fn render_relative_row_jump(
9434 prefix: impl Into<String>,
9435 current_row: u32,
9436 target_row: u32,
9437 ) -> Div {
9438 let (row_diff, arrow) = if target_row < current_row {
9439 (current_row - target_row, IconName::ArrowUp)
9440 } else {
9441 (target_row - current_row, IconName::ArrowDown)
9442 };
9443
9444 h_flex()
9445 .child(
9446 Label::new(format!("{}{}", prefix.into(), row_diff))
9447 .color(Color::Muted)
9448 .size(LabelSize::Small),
9449 )
9450 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9451 }
9452
9453 let supports_jump = self
9454 .edit_prediction_provider
9455 .as_ref()
9456 .map(|provider| provider.provider.supports_jump_to_edit())
9457 .unwrap_or(true);
9458
9459 match &completion.completion {
9460 EditPrediction::Move {
9461 target, snapshot, ..
9462 } => {
9463 if !supports_jump {
9464 return None;
9465 }
9466
9467 Some(
9468 h_flex()
9469 .px_2()
9470 .gap_2()
9471 .flex_1()
9472 .child(
9473 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9474 Icon::new(IconName::ZedPredictDown)
9475 } else {
9476 Icon::new(IconName::ZedPredictUp)
9477 },
9478 )
9479 .child(Label::new("Jump to Edit")),
9480 )
9481 }
9482
9483 EditPrediction::Edit {
9484 edits,
9485 edit_preview,
9486 snapshot,
9487 display_mode: _,
9488 } => {
9489 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9490
9491 let (highlighted_edits, has_more_lines) =
9492 if let Some(edit_preview) = edit_preview.as_ref() {
9493 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9494 .first_line_preview()
9495 } else {
9496 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9497 };
9498
9499 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9500 .with_default_highlights(&style.text, highlighted_edits.highlights);
9501
9502 let preview = h_flex()
9503 .gap_1()
9504 .min_w_16()
9505 .child(styled_text)
9506 .when(has_more_lines, |parent| parent.child("…"));
9507
9508 let left = if supports_jump && first_edit_row != cursor_point.row {
9509 render_relative_row_jump("", cursor_point.row, first_edit_row)
9510 .into_any_element()
9511 } else {
9512 let icon_name =
9513 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9514 Icon::new(icon_name).into_any_element()
9515 };
9516
9517 Some(
9518 h_flex()
9519 .h_full()
9520 .flex_1()
9521 .gap_2()
9522 .pr_1()
9523 .overflow_x_hidden()
9524 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9525 .child(left)
9526 .child(preview),
9527 )
9528 }
9529 }
9530 }
9531
9532 pub fn render_context_menu(
9533 &self,
9534 style: &EditorStyle,
9535 max_height_in_lines: u32,
9536 window: &mut Window,
9537 cx: &mut Context<Editor>,
9538 ) -> Option<AnyElement> {
9539 let menu = self.context_menu.borrow();
9540 let menu = menu.as_ref()?;
9541 if !menu.visible() {
9542 return None;
9543 };
9544 Some(menu.render(style, max_height_in_lines, window, cx))
9545 }
9546
9547 fn render_context_menu_aside(
9548 &mut self,
9549 max_size: Size<Pixels>,
9550 window: &mut Window,
9551 cx: &mut Context<Editor>,
9552 ) -> Option<AnyElement> {
9553 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9554 if menu.visible() {
9555 menu.render_aside(max_size, window, cx)
9556 } else {
9557 None
9558 }
9559 })
9560 }
9561
9562 fn hide_context_menu(
9563 &mut self,
9564 window: &mut Window,
9565 cx: &mut Context<Self>,
9566 ) -> Option<CodeContextMenu> {
9567 cx.notify();
9568 self.completion_tasks.clear();
9569 let context_menu = self.context_menu.borrow_mut().take();
9570 self.stale_edit_prediction_in_menu.take();
9571 self.update_visible_edit_prediction(window, cx);
9572 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9573 && let Some(completion_provider) = &self.completion_provider
9574 {
9575 completion_provider.selection_changed(None, window, cx);
9576 }
9577 context_menu
9578 }
9579
9580 fn show_snippet_choices(
9581 &mut self,
9582 choices: &Vec<String>,
9583 selection: Range<Anchor>,
9584 cx: &mut Context<Self>,
9585 ) {
9586 let Some((_, buffer, _)) = self
9587 .buffer()
9588 .read(cx)
9589 .excerpt_containing(selection.start, cx)
9590 else {
9591 return;
9592 };
9593 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9594 else {
9595 return;
9596 };
9597 if buffer != end_buffer {
9598 log::error!("expected anchor range to have matching buffer IDs");
9599 return;
9600 }
9601
9602 let id = post_inc(&mut self.next_completion_id);
9603 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9604 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9605 CompletionsMenu::new_snippet_choices(
9606 id,
9607 true,
9608 choices,
9609 selection,
9610 buffer,
9611 snippet_sort_order,
9612 ),
9613 ));
9614 }
9615
9616 pub fn insert_snippet(
9617 &mut self,
9618 insertion_ranges: &[Range<usize>],
9619 snippet: Snippet,
9620 window: &mut Window,
9621 cx: &mut Context<Self>,
9622 ) -> Result<()> {
9623 struct Tabstop<T> {
9624 is_end_tabstop: bool,
9625 ranges: Vec<Range<T>>,
9626 choices: Option<Vec<String>>,
9627 }
9628
9629 let tabstops = self.buffer.update(cx, |buffer, cx| {
9630 let snippet_text: Arc<str> = snippet.text.clone().into();
9631 let edits = insertion_ranges
9632 .iter()
9633 .cloned()
9634 .map(|range| (range, snippet_text.clone()));
9635 let autoindent_mode = AutoindentMode::Block {
9636 original_indent_columns: Vec::new(),
9637 };
9638 buffer.edit(edits, Some(autoindent_mode), cx);
9639
9640 let snapshot = &*buffer.read(cx);
9641 let snippet = &snippet;
9642 snippet
9643 .tabstops
9644 .iter()
9645 .map(|tabstop| {
9646 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9647 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9648 });
9649 let mut tabstop_ranges = tabstop
9650 .ranges
9651 .iter()
9652 .flat_map(|tabstop_range| {
9653 let mut delta = 0_isize;
9654 insertion_ranges.iter().map(move |insertion_range| {
9655 let insertion_start = insertion_range.start as isize + delta;
9656 delta +=
9657 snippet.text.len() as isize - insertion_range.len() as isize;
9658
9659 let start = ((insertion_start + tabstop_range.start) as usize)
9660 .min(snapshot.len());
9661 let end = ((insertion_start + tabstop_range.end) as usize)
9662 .min(snapshot.len());
9663 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9664 })
9665 })
9666 .collect::<Vec<_>>();
9667 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9668
9669 Tabstop {
9670 is_end_tabstop,
9671 ranges: tabstop_ranges,
9672 choices: tabstop.choices.clone(),
9673 }
9674 })
9675 .collect::<Vec<_>>()
9676 });
9677 if let Some(tabstop) = tabstops.first() {
9678 self.change_selections(Default::default(), window, cx, |s| {
9679 // Reverse order so that the first range is the newest created selection.
9680 // Completions will use it and autoscroll will prioritize it.
9681 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9682 });
9683
9684 if let Some(choices) = &tabstop.choices
9685 && let Some(selection) = tabstop.ranges.first()
9686 {
9687 self.show_snippet_choices(choices, selection.clone(), cx)
9688 }
9689
9690 // If we're already at the last tabstop and it's at the end of the snippet,
9691 // we're done, we don't need to keep the state around.
9692 if !tabstop.is_end_tabstop {
9693 let choices = tabstops
9694 .iter()
9695 .map(|tabstop| tabstop.choices.clone())
9696 .collect();
9697
9698 let ranges = tabstops
9699 .into_iter()
9700 .map(|tabstop| tabstop.ranges)
9701 .collect::<Vec<_>>();
9702
9703 self.snippet_stack.push(SnippetState {
9704 active_index: 0,
9705 ranges,
9706 choices,
9707 });
9708 }
9709
9710 // Check whether the just-entered snippet ends with an auto-closable bracket.
9711 if self.autoclose_regions.is_empty() {
9712 let snapshot = self.buffer.read(cx).snapshot(cx);
9713 let mut all_selections = self.selections.all::<Point>(cx);
9714 for selection in &mut all_selections {
9715 let selection_head = selection.head();
9716 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9717 continue;
9718 };
9719
9720 let mut bracket_pair = None;
9721 let max_lookup_length = scope
9722 .brackets()
9723 .map(|(pair, _)| {
9724 pair.start
9725 .as_str()
9726 .chars()
9727 .count()
9728 .max(pair.end.as_str().chars().count())
9729 })
9730 .max();
9731 if let Some(max_lookup_length) = max_lookup_length {
9732 let next_text = snapshot
9733 .chars_at(selection_head)
9734 .take(max_lookup_length)
9735 .collect::<String>();
9736 let prev_text = snapshot
9737 .reversed_chars_at(selection_head)
9738 .take(max_lookup_length)
9739 .collect::<String>();
9740
9741 for (pair, enabled) in scope.brackets() {
9742 if enabled
9743 && pair.close
9744 && prev_text.starts_with(pair.start.as_str())
9745 && next_text.starts_with(pair.end.as_str())
9746 {
9747 bracket_pair = Some(pair.clone());
9748 break;
9749 }
9750 }
9751 }
9752
9753 if let Some(pair) = bracket_pair {
9754 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9755 let autoclose_enabled =
9756 self.use_autoclose && snapshot_settings.use_autoclose;
9757 if autoclose_enabled {
9758 let start = snapshot.anchor_after(selection_head);
9759 let end = snapshot.anchor_after(selection_head);
9760 self.autoclose_regions.push(AutocloseRegion {
9761 selection_id: selection.id,
9762 range: start..end,
9763 pair,
9764 });
9765 }
9766 }
9767 }
9768 }
9769 }
9770 Ok(())
9771 }
9772
9773 pub fn move_to_next_snippet_tabstop(
9774 &mut self,
9775 window: &mut Window,
9776 cx: &mut Context<Self>,
9777 ) -> bool {
9778 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9779 }
9780
9781 pub fn move_to_prev_snippet_tabstop(
9782 &mut self,
9783 window: &mut Window,
9784 cx: &mut Context<Self>,
9785 ) -> bool {
9786 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9787 }
9788
9789 pub fn move_to_snippet_tabstop(
9790 &mut self,
9791 bias: Bias,
9792 window: &mut Window,
9793 cx: &mut Context<Self>,
9794 ) -> bool {
9795 if let Some(mut snippet) = self.snippet_stack.pop() {
9796 match bias {
9797 Bias::Left => {
9798 if snippet.active_index > 0 {
9799 snippet.active_index -= 1;
9800 } else {
9801 self.snippet_stack.push(snippet);
9802 return false;
9803 }
9804 }
9805 Bias::Right => {
9806 if snippet.active_index + 1 < snippet.ranges.len() {
9807 snippet.active_index += 1;
9808 } else {
9809 self.snippet_stack.push(snippet);
9810 return false;
9811 }
9812 }
9813 }
9814 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9815 self.change_selections(Default::default(), window, cx, |s| {
9816 // Reverse order so that the first range is the newest created selection.
9817 // Completions will use it and autoscroll will prioritize it.
9818 s.select_ranges(current_ranges.iter().rev().cloned())
9819 });
9820
9821 if let Some(choices) = &snippet.choices[snippet.active_index]
9822 && let Some(selection) = current_ranges.first()
9823 {
9824 self.show_snippet_choices(choices, selection.clone(), cx);
9825 }
9826
9827 // If snippet state is not at the last tabstop, push it back on the stack
9828 if snippet.active_index + 1 < snippet.ranges.len() {
9829 self.snippet_stack.push(snippet);
9830 }
9831 return true;
9832 }
9833 }
9834
9835 false
9836 }
9837
9838 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9839 self.transact(window, cx, |this, window, cx| {
9840 this.select_all(&SelectAll, window, cx);
9841 this.insert("", window, cx);
9842 });
9843 }
9844
9845 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9846 if self.read_only(cx) {
9847 return;
9848 }
9849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9850 self.transact(window, cx, |this, window, cx| {
9851 this.select_autoclose_pair(window, cx);
9852 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9853 if !this.linked_edit_ranges.is_empty() {
9854 let selections = this.selections.all::<MultiBufferPoint>(cx);
9855 let snapshot = this.buffer.read(cx).snapshot(cx);
9856
9857 for selection in selections.iter() {
9858 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9859 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9860 if selection_start.buffer_id != selection_end.buffer_id {
9861 continue;
9862 }
9863 if let Some(ranges) =
9864 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9865 {
9866 for (buffer, entries) in ranges {
9867 linked_ranges.entry(buffer).or_default().extend(entries);
9868 }
9869 }
9870 }
9871 }
9872
9873 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9874 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9875 for selection in &mut selections {
9876 if selection.is_empty() {
9877 let old_head = selection.head();
9878 let mut new_head =
9879 movement::left(&display_map, old_head.to_display_point(&display_map))
9880 .to_point(&display_map);
9881 if let Some((buffer, line_buffer_range)) = display_map
9882 .buffer_snapshot
9883 .buffer_line_for_row(MultiBufferRow(old_head.row))
9884 {
9885 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9886 let indent_len = match indent_size.kind {
9887 IndentKind::Space => {
9888 buffer.settings_at(line_buffer_range.start, cx).tab_size
9889 }
9890 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9891 };
9892 if old_head.column <= indent_size.len && old_head.column > 0 {
9893 let indent_len = indent_len.get();
9894 new_head = cmp::min(
9895 new_head,
9896 MultiBufferPoint::new(
9897 old_head.row,
9898 ((old_head.column - 1) / indent_len) * indent_len,
9899 ),
9900 );
9901 }
9902 }
9903
9904 selection.set_head(new_head, SelectionGoal::None);
9905 }
9906 }
9907
9908 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9909 this.insert("", window, cx);
9910 let empty_str: Arc<str> = Arc::from("");
9911 for (buffer, edits) in linked_ranges {
9912 let snapshot = buffer.read(cx).snapshot();
9913 use text::ToPoint as TP;
9914
9915 let edits = edits
9916 .into_iter()
9917 .map(|range| {
9918 let end_point = TP::to_point(&range.end, &snapshot);
9919 let mut start_point = TP::to_point(&range.start, &snapshot);
9920
9921 if end_point == start_point {
9922 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9923 .saturating_sub(1);
9924 start_point =
9925 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9926 };
9927
9928 (start_point..end_point, empty_str.clone())
9929 })
9930 .sorted_by_key(|(range, _)| range.start)
9931 .collect::<Vec<_>>();
9932 buffer.update(cx, |this, cx| {
9933 this.edit(edits, None, cx);
9934 })
9935 }
9936 this.refresh_edit_prediction(true, false, window, cx);
9937 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9938 });
9939 }
9940
9941 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9942 if self.read_only(cx) {
9943 return;
9944 }
9945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9946 self.transact(window, cx, |this, window, cx| {
9947 this.change_selections(Default::default(), window, cx, |s| {
9948 s.move_with(|map, selection| {
9949 if selection.is_empty() {
9950 let cursor = movement::right(map, selection.head());
9951 selection.end = cursor;
9952 selection.reversed = true;
9953 selection.goal = SelectionGoal::None;
9954 }
9955 })
9956 });
9957 this.insert("", window, cx);
9958 this.refresh_edit_prediction(true, false, window, cx);
9959 });
9960 }
9961
9962 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9963 if self.mode.is_single_line() {
9964 cx.propagate();
9965 return;
9966 }
9967
9968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9969 if self.move_to_prev_snippet_tabstop(window, cx) {
9970 return;
9971 }
9972 self.outdent(&Outdent, window, cx);
9973 }
9974
9975 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9976 if self.mode.is_single_line() {
9977 cx.propagate();
9978 return;
9979 }
9980
9981 if self.move_to_next_snippet_tabstop(window, cx) {
9982 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9983 return;
9984 }
9985 if self.read_only(cx) {
9986 return;
9987 }
9988 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9989 let mut selections = self.selections.all_adjusted(cx);
9990 let buffer = self.buffer.read(cx);
9991 let snapshot = buffer.snapshot(cx);
9992 let rows_iter = selections.iter().map(|s| s.head().row);
9993 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9994
9995 let has_some_cursor_in_whitespace = selections
9996 .iter()
9997 .filter(|selection| selection.is_empty())
9998 .any(|selection| {
9999 let cursor = selection.head();
10000 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10001 cursor.column < current_indent.len
10002 });
10003
10004 let mut edits = Vec::new();
10005 let mut prev_edited_row = 0;
10006 let mut row_delta = 0;
10007 for selection in &mut selections {
10008 if selection.start.row != prev_edited_row {
10009 row_delta = 0;
10010 }
10011 prev_edited_row = selection.end.row;
10012
10013 // If the selection is non-empty, then increase the indentation of the selected lines.
10014 if !selection.is_empty() {
10015 row_delta =
10016 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10017 continue;
10018 }
10019
10020 let cursor = selection.head();
10021 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10022 if let Some(suggested_indent) =
10023 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10024 {
10025 // Don't do anything if already at suggested indent
10026 // and there is any other cursor which is not
10027 if has_some_cursor_in_whitespace
10028 && cursor.column == current_indent.len
10029 && current_indent.len == suggested_indent.len
10030 {
10031 continue;
10032 }
10033
10034 // Adjust line and move cursor to suggested indent
10035 // if cursor is not at suggested indent
10036 if cursor.column < suggested_indent.len
10037 && cursor.column <= current_indent.len
10038 && current_indent.len <= suggested_indent.len
10039 {
10040 selection.start = Point::new(cursor.row, suggested_indent.len);
10041 selection.end = selection.start;
10042 if row_delta == 0 {
10043 edits.extend(Buffer::edit_for_indent_size_adjustment(
10044 cursor.row,
10045 current_indent,
10046 suggested_indent,
10047 ));
10048 row_delta = suggested_indent.len - current_indent.len;
10049 }
10050 continue;
10051 }
10052
10053 // If current indent is more than suggested indent
10054 // only move cursor to current indent and skip indent
10055 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10056 selection.start = Point::new(cursor.row, current_indent.len);
10057 selection.end = selection.start;
10058 continue;
10059 }
10060 }
10061
10062 // Otherwise, insert a hard or soft tab.
10063 let settings = buffer.language_settings_at(cursor, cx);
10064 let tab_size = if settings.hard_tabs {
10065 IndentSize::tab()
10066 } else {
10067 let tab_size = settings.tab_size.get();
10068 let indent_remainder = snapshot
10069 .text_for_range(Point::new(cursor.row, 0)..cursor)
10070 .flat_map(str::chars)
10071 .fold(row_delta % tab_size, |counter: u32, c| {
10072 if c == '\t' {
10073 0
10074 } else {
10075 (counter + 1) % tab_size
10076 }
10077 });
10078
10079 let chars_to_next_tab_stop = tab_size - indent_remainder;
10080 IndentSize::spaces(chars_to_next_tab_stop)
10081 };
10082 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10083 selection.end = selection.start;
10084 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10085 row_delta += tab_size.len;
10086 }
10087
10088 self.transact(window, cx, |this, window, cx| {
10089 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10090 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10091 this.refresh_edit_prediction(true, false, window, cx);
10092 });
10093 }
10094
10095 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10096 if self.read_only(cx) {
10097 return;
10098 }
10099 if self.mode.is_single_line() {
10100 cx.propagate();
10101 return;
10102 }
10103
10104 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10105 let mut selections = self.selections.all::<Point>(cx);
10106 let mut prev_edited_row = 0;
10107 let mut row_delta = 0;
10108 let mut edits = Vec::new();
10109 let buffer = self.buffer.read(cx);
10110 let snapshot = buffer.snapshot(cx);
10111 for selection in &mut selections {
10112 if selection.start.row != prev_edited_row {
10113 row_delta = 0;
10114 }
10115 prev_edited_row = selection.end.row;
10116
10117 row_delta =
10118 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10119 }
10120
10121 self.transact(window, cx, |this, window, cx| {
10122 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10123 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10124 });
10125 }
10126
10127 fn indent_selection(
10128 buffer: &MultiBuffer,
10129 snapshot: &MultiBufferSnapshot,
10130 selection: &mut Selection<Point>,
10131 edits: &mut Vec<(Range<Point>, String)>,
10132 delta_for_start_row: u32,
10133 cx: &App,
10134 ) -> u32 {
10135 let settings = buffer.language_settings_at(selection.start, cx);
10136 let tab_size = settings.tab_size.get();
10137 let indent_kind = if settings.hard_tabs {
10138 IndentKind::Tab
10139 } else {
10140 IndentKind::Space
10141 };
10142 let mut start_row = selection.start.row;
10143 let mut end_row = selection.end.row + 1;
10144
10145 // If a selection ends at the beginning of a line, don't indent
10146 // that last line.
10147 if selection.end.column == 0 && selection.end.row > selection.start.row {
10148 end_row -= 1;
10149 }
10150
10151 // Avoid re-indenting a row that has already been indented by a
10152 // previous selection, but still update this selection's column
10153 // to reflect that indentation.
10154 if delta_for_start_row > 0 {
10155 start_row += 1;
10156 selection.start.column += delta_for_start_row;
10157 if selection.end.row == selection.start.row {
10158 selection.end.column += delta_for_start_row;
10159 }
10160 }
10161
10162 let mut delta_for_end_row = 0;
10163 let has_multiple_rows = start_row + 1 != end_row;
10164 for row in start_row..end_row {
10165 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10166 let indent_delta = match (current_indent.kind, indent_kind) {
10167 (IndentKind::Space, IndentKind::Space) => {
10168 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10169 IndentSize::spaces(columns_to_next_tab_stop)
10170 }
10171 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10172 (_, IndentKind::Tab) => IndentSize::tab(),
10173 };
10174
10175 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10176 0
10177 } else {
10178 selection.start.column
10179 };
10180 let row_start = Point::new(row, start);
10181 edits.push((
10182 row_start..row_start,
10183 indent_delta.chars().collect::<String>(),
10184 ));
10185
10186 // Update this selection's endpoints to reflect the indentation.
10187 if row == selection.start.row {
10188 selection.start.column += indent_delta.len;
10189 }
10190 if row == selection.end.row {
10191 selection.end.column += indent_delta.len;
10192 delta_for_end_row = indent_delta.len;
10193 }
10194 }
10195
10196 if selection.start.row == selection.end.row {
10197 delta_for_start_row + delta_for_end_row
10198 } else {
10199 delta_for_end_row
10200 }
10201 }
10202
10203 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10204 if self.read_only(cx) {
10205 return;
10206 }
10207 if self.mode.is_single_line() {
10208 cx.propagate();
10209 return;
10210 }
10211
10212 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10213 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10214 let selections = self.selections.all::<Point>(cx);
10215 let mut deletion_ranges = Vec::new();
10216 let mut last_outdent = None;
10217 {
10218 let buffer = self.buffer.read(cx);
10219 let snapshot = buffer.snapshot(cx);
10220 for selection in &selections {
10221 let settings = buffer.language_settings_at(selection.start, cx);
10222 let tab_size = settings.tab_size.get();
10223 let mut rows = selection.spanned_rows(false, &display_map);
10224
10225 // Avoid re-outdenting a row that has already been outdented by a
10226 // previous selection.
10227 if let Some(last_row) = last_outdent
10228 && last_row == rows.start
10229 {
10230 rows.start = rows.start.next_row();
10231 }
10232 let has_multiple_rows = rows.len() > 1;
10233 for row in rows.iter_rows() {
10234 let indent_size = snapshot.indent_size_for_line(row);
10235 if indent_size.len > 0 {
10236 let deletion_len = match indent_size.kind {
10237 IndentKind::Space => {
10238 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10239 if columns_to_prev_tab_stop == 0 {
10240 tab_size
10241 } else {
10242 columns_to_prev_tab_stop
10243 }
10244 }
10245 IndentKind::Tab => 1,
10246 };
10247 let start = if has_multiple_rows
10248 || deletion_len > selection.start.column
10249 || indent_size.len < selection.start.column
10250 {
10251 0
10252 } else {
10253 selection.start.column - deletion_len
10254 };
10255 deletion_ranges.push(
10256 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10257 );
10258 last_outdent = Some(row);
10259 }
10260 }
10261 }
10262 }
10263
10264 self.transact(window, cx, |this, window, cx| {
10265 this.buffer.update(cx, |buffer, cx| {
10266 let empty_str: Arc<str> = Arc::default();
10267 buffer.edit(
10268 deletion_ranges
10269 .into_iter()
10270 .map(|range| (range, empty_str.clone())),
10271 None,
10272 cx,
10273 );
10274 });
10275 let selections = this.selections.all::<usize>(cx);
10276 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10277 });
10278 }
10279
10280 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10281 if self.read_only(cx) {
10282 return;
10283 }
10284 if self.mode.is_single_line() {
10285 cx.propagate();
10286 return;
10287 }
10288
10289 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10290 let selections = self
10291 .selections
10292 .all::<usize>(cx)
10293 .into_iter()
10294 .map(|s| s.range());
10295
10296 self.transact(window, cx, |this, window, cx| {
10297 this.buffer.update(cx, |buffer, cx| {
10298 buffer.autoindent_ranges(selections, cx);
10299 });
10300 let selections = this.selections.all::<usize>(cx);
10301 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10302 });
10303 }
10304
10305 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10306 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10307 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10308 let selections = self.selections.all::<Point>(cx);
10309
10310 let mut new_cursors = Vec::new();
10311 let mut edit_ranges = Vec::new();
10312 let mut selections = selections.iter().peekable();
10313 while let Some(selection) = selections.next() {
10314 let mut rows = selection.spanned_rows(false, &display_map);
10315 let goal_display_column = selection.head().to_display_point(&display_map).column();
10316
10317 // Accumulate contiguous regions of rows that we want to delete.
10318 while let Some(next_selection) = selections.peek() {
10319 let next_rows = next_selection.spanned_rows(false, &display_map);
10320 if next_rows.start <= rows.end {
10321 rows.end = next_rows.end;
10322 selections.next().unwrap();
10323 } else {
10324 break;
10325 }
10326 }
10327
10328 let buffer = &display_map.buffer_snapshot;
10329 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10330 let edit_end;
10331 let cursor_buffer_row;
10332 if buffer.max_point().row >= rows.end.0 {
10333 // If there's a line after the range, delete the \n from the end of the row range
10334 // and position the cursor on the next line.
10335 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10336 cursor_buffer_row = rows.end;
10337 } else {
10338 // If there isn't a line after the range, delete the \n from the line before the
10339 // start of the row range and position the cursor there.
10340 edit_start = edit_start.saturating_sub(1);
10341 edit_end = buffer.len();
10342 cursor_buffer_row = rows.start.previous_row();
10343 }
10344
10345 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10346 *cursor.column_mut() =
10347 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10348
10349 new_cursors.push((
10350 selection.id,
10351 buffer.anchor_after(cursor.to_point(&display_map)),
10352 ));
10353 edit_ranges.push(edit_start..edit_end);
10354 }
10355
10356 self.transact(window, cx, |this, window, cx| {
10357 let buffer = this.buffer.update(cx, |buffer, cx| {
10358 let empty_str: Arc<str> = Arc::default();
10359 buffer.edit(
10360 edit_ranges
10361 .into_iter()
10362 .map(|range| (range, empty_str.clone())),
10363 None,
10364 cx,
10365 );
10366 buffer.snapshot(cx)
10367 });
10368 let new_selections = new_cursors
10369 .into_iter()
10370 .map(|(id, cursor)| {
10371 let cursor = cursor.to_point(&buffer);
10372 Selection {
10373 id,
10374 start: cursor,
10375 end: cursor,
10376 reversed: false,
10377 goal: SelectionGoal::None,
10378 }
10379 })
10380 .collect();
10381
10382 this.change_selections(Default::default(), window, cx, |s| {
10383 s.select(new_selections);
10384 });
10385 });
10386 }
10387
10388 pub fn join_lines_impl(
10389 &mut self,
10390 insert_whitespace: bool,
10391 window: &mut Window,
10392 cx: &mut Context<Self>,
10393 ) {
10394 if self.read_only(cx) {
10395 return;
10396 }
10397 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10398 for selection in self.selections.all::<Point>(cx) {
10399 let start = MultiBufferRow(selection.start.row);
10400 // Treat single line selections as if they include the next line. Otherwise this action
10401 // would do nothing for single line selections individual cursors.
10402 let end = if selection.start.row == selection.end.row {
10403 MultiBufferRow(selection.start.row + 1)
10404 } else {
10405 MultiBufferRow(selection.end.row)
10406 };
10407
10408 if let Some(last_row_range) = row_ranges.last_mut()
10409 && start <= last_row_range.end
10410 {
10411 last_row_range.end = end;
10412 continue;
10413 }
10414 row_ranges.push(start..end);
10415 }
10416
10417 let snapshot = self.buffer.read(cx).snapshot(cx);
10418 let mut cursor_positions = Vec::new();
10419 for row_range in &row_ranges {
10420 let anchor = snapshot.anchor_before(Point::new(
10421 row_range.end.previous_row().0,
10422 snapshot.line_len(row_range.end.previous_row()),
10423 ));
10424 cursor_positions.push(anchor..anchor);
10425 }
10426
10427 self.transact(window, cx, |this, window, cx| {
10428 for row_range in row_ranges.into_iter().rev() {
10429 for row in row_range.iter_rows().rev() {
10430 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10431 let next_line_row = row.next_row();
10432 let indent = snapshot.indent_size_for_line(next_line_row);
10433 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10434
10435 let replace =
10436 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10437 " "
10438 } else {
10439 ""
10440 };
10441
10442 this.buffer.update(cx, |buffer, cx| {
10443 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10444 });
10445 }
10446 }
10447
10448 this.change_selections(Default::default(), window, cx, |s| {
10449 s.select_anchor_ranges(cursor_positions)
10450 });
10451 });
10452 }
10453
10454 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10456 self.join_lines_impl(true, window, cx);
10457 }
10458
10459 pub fn sort_lines_case_sensitive(
10460 &mut self,
10461 _: &SortLinesCaseSensitive,
10462 window: &mut Window,
10463 cx: &mut Context<Self>,
10464 ) {
10465 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10466 }
10467
10468 pub fn sort_lines_by_length(
10469 &mut self,
10470 _: &SortLinesByLength,
10471 window: &mut Window,
10472 cx: &mut Context<Self>,
10473 ) {
10474 self.manipulate_immutable_lines(window, cx, |lines| {
10475 lines.sort_by_key(|&line| line.chars().count())
10476 })
10477 }
10478
10479 pub fn sort_lines_case_insensitive(
10480 &mut self,
10481 _: &SortLinesCaseInsensitive,
10482 window: &mut Window,
10483 cx: &mut Context<Self>,
10484 ) {
10485 self.manipulate_immutable_lines(window, cx, |lines| {
10486 lines.sort_by_key(|line| line.to_lowercase())
10487 })
10488 }
10489
10490 pub fn unique_lines_case_insensitive(
10491 &mut self,
10492 _: &UniqueLinesCaseInsensitive,
10493 window: &mut Window,
10494 cx: &mut Context<Self>,
10495 ) {
10496 self.manipulate_immutable_lines(window, cx, |lines| {
10497 let mut seen = HashSet::default();
10498 lines.retain(|line| seen.insert(line.to_lowercase()));
10499 })
10500 }
10501
10502 pub fn unique_lines_case_sensitive(
10503 &mut self,
10504 _: &UniqueLinesCaseSensitive,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) {
10508 self.manipulate_immutable_lines(window, cx, |lines| {
10509 let mut seen = HashSet::default();
10510 lines.retain(|line| seen.insert(*line));
10511 })
10512 }
10513
10514 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10515 let snapshot = self.buffer.read(cx).snapshot(cx);
10516 for selection in self.selections.disjoint_anchors().iter() {
10517 if snapshot
10518 .language_at(selection.start)
10519 .and_then(|lang| lang.config().wrap_characters.as_ref())
10520 .is_some()
10521 {
10522 return true;
10523 }
10524 }
10525 false
10526 }
10527
10528 fn wrap_selections_in_tag(
10529 &mut self,
10530 _: &WrapSelectionsInTag,
10531 window: &mut Window,
10532 cx: &mut Context<Self>,
10533 ) {
10534 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10535
10536 let snapshot = self.buffer.read(cx).snapshot(cx);
10537
10538 let mut edits = Vec::new();
10539 let mut boundaries = Vec::new();
10540
10541 for selection in self.selections.all::<Point>(cx).iter() {
10542 let Some(wrap_config) = snapshot
10543 .language_at(selection.start)
10544 .and_then(|lang| lang.config().wrap_characters.clone())
10545 else {
10546 continue;
10547 };
10548
10549 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10550 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10551
10552 let start_before = snapshot.anchor_before(selection.start);
10553 let end_after = snapshot.anchor_after(selection.end);
10554
10555 edits.push((start_before..start_before, open_tag));
10556 edits.push((end_after..end_after, close_tag));
10557
10558 boundaries.push((
10559 start_before,
10560 end_after,
10561 wrap_config.start_prefix.len(),
10562 wrap_config.end_suffix.len(),
10563 ));
10564 }
10565
10566 if edits.is_empty() {
10567 return;
10568 }
10569
10570 self.transact(window, cx, |this, window, cx| {
10571 let buffer = this.buffer.update(cx, |buffer, cx| {
10572 buffer.edit(edits, None, cx);
10573 buffer.snapshot(cx)
10574 });
10575
10576 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10577 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10578 boundaries.into_iter()
10579 {
10580 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10581 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10582 new_selections.push(open_offset..open_offset);
10583 new_selections.push(close_offset..close_offset);
10584 }
10585
10586 this.change_selections(Default::default(), window, cx, |s| {
10587 s.select_ranges(new_selections);
10588 });
10589
10590 this.request_autoscroll(Autoscroll::fit(), cx);
10591 });
10592 }
10593
10594 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10595 let Some(project) = self.project.clone() else {
10596 return;
10597 };
10598 self.reload(project, window, cx)
10599 .detach_and_notify_err(window, cx);
10600 }
10601
10602 pub fn restore_file(
10603 &mut self,
10604 _: &::git::RestoreFile,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10609 let mut buffer_ids = HashSet::default();
10610 let snapshot = self.buffer().read(cx).snapshot(cx);
10611 for selection in self.selections.all::<usize>(cx) {
10612 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10613 }
10614
10615 let buffer = self.buffer().read(cx);
10616 let ranges = buffer_ids
10617 .into_iter()
10618 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10619 .collect::<Vec<_>>();
10620
10621 self.restore_hunks_in_ranges(ranges, window, cx);
10622 }
10623
10624 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10625 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10626 let selections = self
10627 .selections
10628 .all(cx)
10629 .into_iter()
10630 .map(|s| s.range())
10631 .collect();
10632 self.restore_hunks_in_ranges(selections, window, cx);
10633 }
10634
10635 pub fn restore_hunks_in_ranges(
10636 &mut self,
10637 ranges: Vec<Range<Point>>,
10638 window: &mut Window,
10639 cx: &mut Context<Editor>,
10640 ) {
10641 let mut revert_changes = HashMap::default();
10642 let chunk_by = self
10643 .snapshot(window, cx)
10644 .hunks_for_ranges(ranges)
10645 .into_iter()
10646 .chunk_by(|hunk| hunk.buffer_id);
10647 for (buffer_id, hunks) in &chunk_by {
10648 let hunks = hunks.collect::<Vec<_>>();
10649 for hunk in &hunks {
10650 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10651 }
10652 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10653 }
10654 drop(chunk_by);
10655 if !revert_changes.is_empty() {
10656 self.transact(window, cx, |editor, window, cx| {
10657 editor.restore(revert_changes, window, cx);
10658 });
10659 }
10660 }
10661
10662 pub fn open_active_item_in_terminal(
10663 &mut self,
10664 _: &OpenInTerminal,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10669 let project_path = buffer.read(cx).project_path(cx)?;
10670 let project = self.project()?.read(cx);
10671 let entry = project.entry_for_path(&project_path, cx)?;
10672 let parent = match &entry.canonical_path {
10673 Some(canonical_path) => canonical_path.to_path_buf(),
10674 None => project.absolute_path(&project_path, cx)?,
10675 }
10676 .parent()?
10677 .to_path_buf();
10678 Some(parent)
10679 }) {
10680 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10681 }
10682 }
10683
10684 fn set_breakpoint_context_menu(
10685 &mut self,
10686 display_row: DisplayRow,
10687 position: Option<Anchor>,
10688 clicked_point: gpui::Point<Pixels>,
10689 window: &mut Window,
10690 cx: &mut Context<Self>,
10691 ) {
10692 let source = self
10693 .buffer
10694 .read(cx)
10695 .snapshot(cx)
10696 .anchor_before(Point::new(display_row.0, 0u32));
10697
10698 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10699
10700 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10701 self,
10702 source,
10703 clicked_point,
10704 context_menu,
10705 window,
10706 cx,
10707 );
10708 }
10709
10710 fn add_edit_breakpoint_block(
10711 &mut self,
10712 anchor: Anchor,
10713 breakpoint: &Breakpoint,
10714 edit_action: BreakpointPromptEditAction,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) {
10718 let weak_editor = cx.weak_entity();
10719 let bp_prompt = cx.new(|cx| {
10720 BreakpointPromptEditor::new(
10721 weak_editor,
10722 anchor,
10723 breakpoint.clone(),
10724 edit_action,
10725 window,
10726 cx,
10727 )
10728 });
10729
10730 let height = bp_prompt.update(cx, |this, cx| {
10731 this.prompt
10732 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10733 });
10734 let cloned_prompt = bp_prompt.clone();
10735 let blocks = vec![BlockProperties {
10736 style: BlockStyle::Sticky,
10737 placement: BlockPlacement::Above(anchor),
10738 height: Some(height),
10739 render: Arc::new(move |cx| {
10740 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10741 cloned_prompt.clone().into_any_element()
10742 }),
10743 priority: 0,
10744 }];
10745
10746 let focus_handle = bp_prompt.focus_handle(cx);
10747 window.focus(&focus_handle);
10748
10749 let block_ids = self.insert_blocks(blocks, None, cx);
10750 bp_prompt.update(cx, |prompt, _| {
10751 prompt.add_block_ids(block_ids);
10752 });
10753 }
10754
10755 pub(crate) fn breakpoint_at_row(
10756 &self,
10757 row: u32,
10758 window: &mut Window,
10759 cx: &mut Context<Self>,
10760 ) -> Option<(Anchor, Breakpoint)> {
10761 let snapshot = self.snapshot(window, cx);
10762 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10763
10764 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10765 }
10766
10767 pub(crate) fn breakpoint_at_anchor(
10768 &self,
10769 breakpoint_position: Anchor,
10770 snapshot: &EditorSnapshot,
10771 cx: &mut Context<Self>,
10772 ) -> Option<(Anchor, Breakpoint)> {
10773 let buffer = self
10774 .buffer
10775 .read(cx)
10776 .buffer_for_anchor(breakpoint_position, cx)?;
10777
10778 let enclosing_excerpt = breakpoint_position.excerpt_id;
10779 let buffer_snapshot = buffer.read(cx).snapshot();
10780
10781 let row = buffer_snapshot
10782 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10783 .row;
10784
10785 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10786 let anchor_end = snapshot
10787 .buffer_snapshot
10788 .anchor_after(Point::new(row, line_len));
10789
10790 self.breakpoint_store
10791 .as_ref()?
10792 .read_with(cx, |breakpoint_store, cx| {
10793 breakpoint_store
10794 .breakpoints(
10795 &buffer,
10796 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10797 &buffer_snapshot,
10798 cx,
10799 )
10800 .next()
10801 .and_then(|(bp, _)| {
10802 let breakpoint_row = buffer_snapshot
10803 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10804 .row;
10805
10806 if breakpoint_row == row {
10807 snapshot
10808 .buffer_snapshot
10809 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10810 .map(|position| (position, bp.bp.clone()))
10811 } else {
10812 None
10813 }
10814 })
10815 })
10816 }
10817
10818 pub fn edit_log_breakpoint(
10819 &mut self,
10820 _: &EditLogBreakpoint,
10821 window: &mut Window,
10822 cx: &mut Context<Self>,
10823 ) {
10824 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10825 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10826 message: None,
10827 state: BreakpointState::Enabled,
10828 condition: None,
10829 hit_condition: None,
10830 });
10831
10832 self.add_edit_breakpoint_block(
10833 anchor,
10834 &breakpoint,
10835 BreakpointPromptEditAction::Log,
10836 window,
10837 cx,
10838 );
10839 }
10840 }
10841
10842 fn breakpoints_at_cursors(
10843 &self,
10844 window: &mut Window,
10845 cx: &mut Context<Self>,
10846 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10847 let snapshot = self.snapshot(window, cx);
10848 let cursors = self
10849 .selections
10850 .disjoint_anchors()
10851 .iter()
10852 .map(|selection| {
10853 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10854
10855 let breakpoint_position = self
10856 .breakpoint_at_row(cursor_position.row, window, cx)
10857 .map(|bp| bp.0)
10858 .unwrap_or_else(|| {
10859 snapshot
10860 .display_snapshot
10861 .buffer_snapshot
10862 .anchor_after(Point::new(cursor_position.row, 0))
10863 });
10864
10865 let breakpoint = self
10866 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10867 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10868
10869 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10870 })
10871 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
10872 .collect::<HashMap<Anchor, _>>();
10873
10874 cursors.into_iter().collect()
10875 }
10876
10877 pub fn enable_breakpoint(
10878 &mut self,
10879 _: &crate::actions::EnableBreakpoint,
10880 window: &mut Window,
10881 cx: &mut Context<Self>,
10882 ) {
10883 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10884 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10885 continue;
10886 };
10887 self.edit_breakpoint_at_anchor(
10888 anchor,
10889 breakpoint,
10890 BreakpointEditAction::InvertState,
10891 cx,
10892 );
10893 }
10894 }
10895
10896 pub fn disable_breakpoint(
10897 &mut self,
10898 _: &crate::actions::DisableBreakpoint,
10899 window: &mut Window,
10900 cx: &mut Context<Self>,
10901 ) {
10902 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10903 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10904 continue;
10905 };
10906 self.edit_breakpoint_at_anchor(
10907 anchor,
10908 breakpoint,
10909 BreakpointEditAction::InvertState,
10910 cx,
10911 );
10912 }
10913 }
10914
10915 pub fn toggle_breakpoint(
10916 &mut self,
10917 _: &crate::actions::ToggleBreakpoint,
10918 window: &mut Window,
10919 cx: &mut Context<Self>,
10920 ) {
10921 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10922 if let Some(breakpoint) = breakpoint {
10923 self.edit_breakpoint_at_anchor(
10924 anchor,
10925 breakpoint,
10926 BreakpointEditAction::Toggle,
10927 cx,
10928 );
10929 } else {
10930 self.edit_breakpoint_at_anchor(
10931 anchor,
10932 Breakpoint::new_standard(),
10933 BreakpointEditAction::Toggle,
10934 cx,
10935 );
10936 }
10937 }
10938 }
10939
10940 pub fn edit_breakpoint_at_anchor(
10941 &mut self,
10942 breakpoint_position: Anchor,
10943 breakpoint: Breakpoint,
10944 edit_action: BreakpointEditAction,
10945 cx: &mut Context<Self>,
10946 ) {
10947 let Some(breakpoint_store) = &self.breakpoint_store else {
10948 return;
10949 };
10950
10951 let Some(buffer) = self
10952 .buffer
10953 .read(cx)
10954 .buffer_for_anchor(breakpoint_position, cx)
10955 else {
10956 return;
10957 };
10958
10959 breakpoint_store.update(cx, |breakpoint_store, cx| {
10960 breakpoint_store.toggle_breakpoint(
10961 buffer,
10962 BreakpointWithPosition {
10963 position: breakpoint_position.text_anchor,
10964 bp: breakpoint,
10965 },
10966 edit_action,
10967 cx,
10968 );
10969 });
10970
10971 cx.notify();
10972 }
10973
10974 #[cfg(any(test, feature = "test-support"))]
10975 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10976 self.breakpoint_store.clone()
10977 }
10978
10979 pub fn prepare_restore_change(
10980 &self,
10981 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10982 hunk: &MultiBufferDiffHunk,
10983 cx: &mut App,
10984 ) -> Option<()> {
10985 if hunk.is_created_file() {
10986 return None;
10987 }
10988 let buffer = self.buffer.read(cx);
10989 let diff = buffer.diff_for(hunk.buffer_id)?;
10990 let buffer = buffer.buffer(hunk.buffer_id)?;
10991 let buffer = buffer.read(cx);
10992 let original_text = diff
10993 .read(cx)
10994 .base_text()
10995 .as_rope()
10996 .slice(hunk.diff_base_byte_range.clone());
10997 let buffer_snapshot = buffer.snapshot();
10998 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10999 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11000 probe
11001 .0
11002 .start
11003 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11004 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11005 }) {
11006 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11007 Some(())
11008 } else {
11009 None
11010 }
11011 }
11012
11013 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11014 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11015 }
11016
11017 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11018 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11019 }
11020
11021 fn manipulate_lines<M>(
11022 &mut self,
11023 window: &mut Window,
11024 cx: &mut Context<Self>,
11025 mut manipulate: M,
11026 ) where
11027 M: FnMut(&str) -> LineManipulationResult,
11028 {
11029 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11030
11031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11032 let buffer = self.buffer.read(cx).snapshot(cx);
11033
11034 let mut edits = Vec::new();
11035
11036 let selections = self.selections.all::<Point>(cx);
11037 let mut selections = selections.iter().peekable();
11038 let mut contiguous_row_selections = Vec::new();
11039 let mut new_selections = Vec::new();
11040 let mut added_lines = 0;
11041 let mut removed_lines = 0;
11042
11043 while let Some(selection) = selections.next() {
11044 let (start_row, end_row) = consume_contiguous_rows(
11045 &mut contiguous_row_selections,
11046 selection,
11047 &display_map,
11048 &mut selections,
11049 );
11050
11051 let start_point = Point::new(start_row.0, 0);
11052 let end_point = Point::new(
11053 end_row.previous_row().0,
11054 buffer.line_len(end_row.previous_row()),
11055 );
11056 let text = buffer
11057 .text_for_range(start_point..end_point)
11058 .collect::<String>();
11059
11060 let LineManipulationResult {
11061 new_text,
11062 line_count_before,
11063 line_count_after,
11064 } = manipulate(&text);
11065
11066 edits.push((start_point..end_point, new_text));
11067
11068 // Selections must change based on added and removed line count
11069 let start_row =
11070 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11071 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11072 new_selections.push(Selection {
11073 id: selection.id,
11074 start: start_row,
11075 end: end_row,
11076 goal: SelectionGoal::None,
11077 reversed: selection.reversed,
11078 });
11079
11080 if line_count_after > line_count_before {
11081 added_lines += line_count_after - line_count_before;
11082 } else if line_count_before > line_count_after {
11083 removed_lines += line_count_before - line_count_after;
11084 }
11085 }
11086
11087 self.transact(window, cx, |this, window, cx| {
11088 let buffer = this.buffer.update(cx, |buffer, cx| {
11089 buffer.edit(edits, None, cx);
11090 buffer.snapshot(cx)
11091 });
11092
11093 // Recalculate offsets on newly edited buffer
11094 let new_selections = new_selections
11095 .iter()
11096 .map(|s| {
11097 let start_point = Point::new(s.start.0, 0);
11098 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11099 Selection {
11100 id: s.id,
11101 start: buffer.point_to_offset(start_point),
11102 end: buffer.point_to_offset(end_point),
11103 goal: s.goal,
11104 reversed: s.reversed,
11105 }
11106 })
11107 .collect();
11108
11109 this.change_selections(Default::default(), window, cx, |s| {
11110 s.select(new_selections);
11111 });
11112
11113 this.request_autoscroll(Autoscroll::fit(), cx);
11114 });
11115 }
11116
11117 fn manipulate_immutable_lines<Fn>(
11118 &mut self,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 mut callback: Fn,
11122 ) where
11123 Fn: FnMut(&mut Vec<&str>),
11124 {
11125 self.manipulate_lines(window, cx, |text| {
11126 let mut lines: Vec<&str> = text.split('\n').collect();
11127 let line_count_before = lines.len();
11128
11129 callback(&mut lines);
11130
11131 LineManipulationResult {
11132 new_text: lines.join("\n"),
11133 line_count_before,
11134 line_count_after: lines.len(),
11135 }
11136 });
11137 }
11138
11139 fn manipulate_mutable_lines<Fn>(
11140 &mut self,
11141 window: &mut Window,
11142 cx: &mut Context<Self>,
11143 mut callback: Fn,
11144 ) where
11145 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11146 {
11147 self.manipulate_lines(window, cx, |text| {
11148 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11149 let line_count_before = lines.len();
11150
11151 callback(&mut lines);
11152
11153 LineManipulationResult {
11154 new_text: lines.join("\n"),
11155 line_count_before,
11156 line_count_after: lines.len(),
11157 }
11158 });
11159 }
11160
11161 pub fn convert_indentation_to_spaces(
11162 &mut self,
11163 _: &ConvertIndentationToSpaces,
11164 window: &mut Window,
11165 cx: &mut Context<Self>,
11166 ) {
11167 let settings = self.buffer.read(cx).language_settings(cx);
11168 let tab_size = settings.tab_size.get() as usize;
11169
11170 self.manipulate_mutable_lines(window, cx, |lines| {
11171 // Allocates a reasonably sized scratch buffer once for the whole loop
11172 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11173 // Avoids recomputing spaces that could be inserted many times
11174 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11175 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11176 .collect();
11177
11178 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11179 let mut chars = line.as_ref().chars();
11180 let mut col = 0;
11181 let mut changed = false;
11182
11183 for ch in chars.by_ref() {
11184 match ch {
11185 ' ' => {
11186 reindented_line.push(' ');
11187 col += 1;
11188 }
11189 '\t' => {
11190 // \t are converted to spaces depending on the current column
11191 let spaces_len = tab_size - (col % tab_size);
11192 reindented_line.extend(&space_cache[spaces_len - 1]);
11193 col += spaces_len;
11194 changed = true;
11195 }
11196 _ => {
11197 // If we dont append before break, the character is consumed
11198 reindented_line.push(ch);
11199 break;
11200 }
11201 }
11202 }
11203
11204 if !changed {
11205 reindented_line.clear();
11206 continue;
11207 }
11208 // Append the rest of the line and replace old reference with new one
11209 reindented_line.extend(chars);
11210 *line = Cow::Owned(reindented_line.clone());
11211 reindented_line.clear();
11212 }
11213 });
11214 }
11215
11216 pub fn convert_indentation_to_tabs(
11217 &mut self,
11218 _: &ConvertIndentationToTabs,
11219 window: &mut Window,
11220 cx: &mut Context<Self>,
11221 ) {
11222 let settings = self.buffer.read(cx).language_settings(cx);
11223 let tab_size = settings.tab_size.get() as usize;
11224
11225 self.manipulate_mutable_lines(window, cx, |lines| {
11226 // Allocates a reasonably sized buffer once for the whole loop
11227 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11228 // Avoids recomputing spaces that could be inserted many times
11229 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11230 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11231 .collect();
11232
11233 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11234 let mut chars = line.chars();
11235 let mut spaces_count = 0;
11236 let mut first_non_indent_char = None;
11237 let mut changed = false;
11238
11239 for ch in chars.by_ref() {
11240 match ch {
11241 ' ' => {
11242 // Keep track of spaces. Append \t when we reach tab_size
11243 spaces_count += 1;
11244 changed = true;
11245 if spaces_count == tab_size {
11246 reindented_line.push('\t');
11247 spaces_count = 0;
11248 }
11249 }
11250 '\t' => {
11251 reindented_line.push('\t');
11252 spaces_count = 0;
11253 }
11254 _ => {
11255 // Dont append it yet, we might have remaining spaces
11256 first_non_indent_char = Some(ch);
11257 break;
11258 }
11259 }
11260 }
11261
11262 if !changed {
11263 reindented_line.clear();
11264 continue;
11265 }
11266 // Remaining spaces that didn't make a full tab stop
11267 if spaces_count > 0 {
11268 reindented_line.extend(&space_cache[spaces_count - 1]);
11269 }
11270 // If we consume an extra character that was not indentation, add it back
11271 if let Some(extra_char) = first_non_indent_char {
11272 reindented_line.push(extra_char);
11273 }
11274 // Append the rest of the line and replace old reference with new one
11275 reindented_line.extend(chars);
11276 *line = Cow::Owned(reindented_line.clone());
11277 reindented_line.clear();
11278 }
11279 });
11280 }
11281
11282 pub fn convert_to_upper_case(
11283 &mut self,
11284 _: &ConvertToUpperCase,
11285 window: &mut Window,
11286 cx: &mut Context<Self>,
11287 ) {
11288 self.manipulate_text(window, cx, |text| text.to_uppercase())
11289 }
11290
11291 pub fn convert_to_lower_case(
11292 &mut self,
11293 _: &ConvertToLowerCase,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 self.manipulate_text(window, cx, |text| text.to_lowercase())
11298 }
11299
11300 pub fn convert_to_title_case(
11301 &mut self,
11302 _: &ConvertToTitleCase,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.manipulate_text(window, cx, |text| {
11307 text.split('\n')
11308 .map(|line| line.to_case(Case::Title))
11309 .join("\n")
11310 })
11311 }
11312
11313 pub fn convert_to_snake_case(
11314 &mut self,
11315 _: &ConvertToSnakeCase,
11316 window: &mut Window,
11317 cx: &mut Context<Self>,
11318 ) {
11319 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11320 }
11321
11322 pub fn convert_to_kebab_case(
11323 &mut self,
11324 _: &ConvertToKebabCase,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11329 }
11330
11331 pub fn convert_to_upper_camel_case(
11332 &mut self,
11333 _: &ConvertToUpperCamelCase,
11334 window: &mut Window,
11335 cx: &mut Context<Self>,
11336 ) {
11337 self.manipulate_text(window, cx, |text| {
11338 text.split('\n')
11339 .map(|line| line.to_case(Case::UpperCamel))
11340 .join("\n")
11341 })
11342 }
11343
11344 pub fn convert_to_lower_camel_case(
11345 &mut self,
11346 _: &ConvertToLowerCamelCase,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11351 }
11352
11353 pub fn convert_to_opposite_case(
11354 &mut self,
11355 _: &ConvertToOppositeCase,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 self.manipulate_text(window, cx, |text| {
11360 text.chars()
11361 .fold(String::with_capacity(text.len()), |mut t, c| {
11362 if c.is_uppercase() {
11363 t.extend(c.to_lowercase());
11364 } else {
11365 t.extend(c.to_uppercase());
11366 }
11367 t
11368 })
11369 })
11370 }
11371
11372 pub fn convert_to_sentence_case(
11373 &mut self,
11374 _: &ConvertToSentenceCase,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11379 }
11380
11381 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11382 self.manipulate_text(window, cx, |text| {
11383 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11384 if has_upper_case_characters {
11385 text.to_lowercase()
11386 } else {
11387 text.to_uppercase()
11388 }
11389 })
11390 }
11391
11392 pub fn convert_to_rot13(
11393 &mut self,
11394 _: &ConvertToRot13,
11395 window: &mut Window,
11396 cx: &mut Context<Self>,
11397 ) {
11398 self.manipulate_text(window, cx, |text| {
11399 text.chars()
11400 .map(|c| match c {
11401 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11402 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11403 _ => c,
11404 })
11405 .collect()
11406 })
11407 }
11408
11409 pub fn convert_to_rot47(
11410 &mut self,
11411 _: &ConvertToRot47,
11412 window: &mut Window,
11413 cx: &mut Context<Self>,
11414 ) {
11415 self.manipulate_text(window, cx, |text| {
11416 text.chars()
11417 .map(|c| {
11418 let code_point = c as u32;
11419 if code_point >= 33 && code_point <= 126 {
11420 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11421 }
11422 c
11423 })
11424 .collect()
11425 })
11426 }
11427
11428 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11429 where
11430 Fn: FnMut(&str) -> String,
11431 {
11432 let buffer = self.buffer.read(cx).snapshot(cx);
11433
11434 let mut new_selections = Vec::new();
11435 let mut edits = Vec::new();
11436 let mut selection_adjustment = 0i32;
11437
11438 for selection in self.selections.all_adjusted(cx) {
11439 let selection_is_empty = selection.is_empty();
11440
11441 let (start, end) = if selection_is_empty {
11442 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11443 (word_range.start, word_range.end)
11444 } else {
11445 (
11446 buffer.point_to_offset(selection.start),
11447 buffer.point_to_offset(selection.end),
11448 )
11449 };
11450
11451 let text = buffer.text_for_range(start..end).collect::<String>();
11452 let old_length = text.len() as i32;
11453 let text = callback(&text);
11454
11455 new_selections.push(Selection {
11456 start: (start as i32 - selection_adjustment) as usize,
11457 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11458 goal: SelectionGoal::None,
11459 id: selection.id,
11460 reversed: selection.reversed,
11461 });
11462
11463 selection_adjustment += old_length - text.len() as i32;
11464
11465 edits.push((start..end, text));
11466 }
11467
11468 self.transact(window, cx, |this, window, cx| {
11469 this.buffer.update(cx, |buffer, cx| {
11470 buffer.edit(edits, None, cx);
11471 });
11472
11473 this.change_selections(Default::default(), window, cx, |s| {
11474 s.select(new_selections);
11475 });
11476
11477 this.request_autoscroll(Autoscroll::fit(), cx);
11478 });
11479 }
11480
11481 pub fn move_selection_on_drop(
11482 &mut self,
11483 selection: &Selection<Anchor>,
11484 target: DisplayPoint,
11485 is_cut: bool,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11490 let buffer = &display_map.buffer_snapshot;
11491 let mut edits = Vec::new();
11492 let insert_point = display_map
11493 .clip_point(target, Bias::Left)
11494 .to_point(&display_map);
11495 let text = buffer
11496 .text_for_range(selection.start..selection.end)
11497 .collect::<String>();
11498 if is_cut {
11499 edits.push(((selection.start..selection.end), String::new()));
11500 }
11501 let insert_anchor = buffer.anchor_before(insert_point);
11502 edits.push(((insert_anchor..insert_anchor), text));
11503 let last_edit_start = insert_anchor.bias_left(buffer);
11504 let last_edit_end = insert_anchor.bias_right(buffer);
11505 self.transact(window, cx, |this, window, cx| {
11506 this.buffer.update(cx, |buffer, cx| {
11507 buffer.edit(edits, None, cx);
11508 });
11509 this.change_selections(Default::default(), window, cx, |s| {
11510 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11511 });
11512 });
11513 }
11514
11515 pub fn clear_selection_drag_state(&mut self) {
11516 self.selection_drag_state = SelectionDragState::None;
11517 }
11518
11519 pub fn duplicate(
11520 &mut self,
11521 upwards: bool,
11522 whole_lines: bool,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11527
11528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11529 let buffer = &display_map.buffer_snapshot;
11530 let selections = self.selections.all::<Point>(cx);
11531
11532 let mut edits = Vec::new();
11533 let mut selections_iter = selections.iter().peekable();
11534 while let Some(selection) = selections_iter.next() {
11535 let mut rows = selection.spanned_rows(false, &display_map);
11536 // duplicate line-wise
11537 if whole_lines || selection.start == selection.end {
11538 // Avoid duplicating the same lines twice.
11539 while let Some(next_selection) = selections_iter.peek() {
11540 let next_rows = next_selection.spanned_rows(false, &display_map);
11541 if next_rows.start < rows.end {
11542 rows.end = next_rows.end;
11543 selections_iter.next().unwrap();
11544 } else {
11545 break;
11546 }
11547 }
11548
11549 // Copy the text from the selected row region and splice it either at the start
11550 // or end of the region.
11551 let start = Point::new(rows.start.0, 0);
11552 let end = Point::new(
11553 rows.end.previous_row().0,
11554 buffer.line_len(rows.end.previous_row()),
11555 );
11556 let text = buffer
11557 .text_for_range(start..end)
11558 .chain(Some("\n"))
11559 .collect::<String>();
11560 let insert_location = if upwards {
11561 Point::new(rows.end.0, 0)
11562 } else {
11563 start
11564 };
11565 edits.push((insert_location..insert_location, text));
11566 } else {
11567 // duplicate character-wise
11568 let start = selection.start;
11569 let end = selection.end;
11570 let text = buffer.text_for_range(start..end).collect::<String>();
11571 edits.push((selection.end..selection.end, text));
11572 }
11573 }
11574
11575 self.transact(window, cx, |this, _, cx| {
11576 this.buffer.update(cx, |buffer, cx| {
11577 buffer.edit(edits, None, cx);
11578 });
11579
11580 this.request_autoscroll(Autoscroll::fit(), cx);
11581 });
11582 }
11583
11584 pub fn duplicate_line_up(
11585 &mut self,
11586 _: &DuplicateLineUp,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 ) {
11590 self.duplicate(true, true, window, cx);
11591 }
11592
11593 pub fn duplicate_line_down(
11594 &mut self,
11595 _: &DuplicateLineDown,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 ) {
11599 self.duplicate(false, true, window, cx);
11600 }
11601
11602 pub fn duplicate_selection(
11603 &mut self,
11604 _: &DuplicateSelection,
11605 window: &mut Window,
11606 cx: &mut Context<Self>,
11607 ) {
11608 self.duplicate(false, false, window, cx);
11609 }
11610
11611 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11612 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11613 if self.mode.is_single_line() {
11614 cx.propagate();
11615 return;
11616 }
11617
11618 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11619 let buffer = self.buffer.read(cx).snapshot(cx);
11620
11621 let mut edits = Vec::new();
11622 let mut unfold_ranges = Vec::new();
11623 let mut refold_creases = Vec::new();
11624
11625 let selections = self.selections.all::<Point>(cx);
11626 let mut selections = selections.iter().peekable();
11627 let mut contiguous_row_selections = Vec::new();
11628 let mut new_selections = Vec::new();
11629
11630 while let Some(selection) = selections.next() {
11631 // Find all the selections that span a contiguous row range
11632 let (start_row, end_row) = consume_contiguous_rows(
11633 &mut contiguous_row_selections,
11634 selection,
11635 &display_map,
11636 &mut selections,
11637 );
11638
11639 // Move the text spanned by the row range to be before the line preceding the row range
11640 if start_row.0 > 0 {
11641 let range_to_move = Point::new(
11642 start_row.previous_row().0,
11643 buffer.line_len(start_row.previous_row()),
11644 )
11645 ..Point::new(
11646 end_row.previous_row().0,
11647 buffer.line_len(end_row.previous_row()),
11648 );
11649 let insertion_point = display_map
11650 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11651 .0;
11652
11653 // Don't move lines across excerpts
11654 if buffer
11655 .excerpt_containing(insertion_point..range_to_move.end)
11656 .is_some()
11657 {
11658 let text = buffer
11659 .text_for_range(range_to_move.clone())
11660 .flat_map(|s| s.chars())
11661 .skip(1)
11662 .chain(['\n'])
11663 .collect::<String>();
11664
11665 edits.push((
11666 buffer.anchor_after(range_to_move.start)
11667 ..buffer.anchor_before(range_to_move.end),
11668 String::new(),
11669 ));
11670 let insertion_anchor = buffer.anchor_after(insertion_point);
11671 edits.push((insertion_anchor..insertion_anchor, text));
11672
11673 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11674
11675 // Move selections up
11676 new_selections.extend(contiguous_row_selections.drain(..).map(
11677 |mut selection| {
11678 selection.start.row -= row_delta;
11679 selection.end.row -= row_delta;
11680 selection
11681 },
11682 ));
11683
11684 // Move folds up
11685 unfold_ranges.push(range_to_move.clone());
11686 for fold in display_map.folds_in_range(
11687 buffer.anchor_before(range_to_move.start)
11688 ..buffer.anchor_after(range_to_move.end),
11689 ) {
11690 let mut start = fold.range.start.to_point(&buffer);
11691 let mut end = fold.range.end.to_point(&buffer);
11692 start.row -= row_delta;
11693 end.row -= row_delta;
11694 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11695 }
11696 }
11697 }
11698
11699 // If we didn't move line(s), preserve the existing selections
11700 new_selections.append(&mut contiguous_row_selections);
11701 }
11702
11703 self.transact(window, cx, |this, window, cx| {
11704 this.unfold_ranges(&unfold_ranges, true, true, cx);
11705 this.buffer.update(cx, |buffer, cx| {
11706 for (range, text) in edits {
11707 buffer.edit([(range, text)], None, cx);
11708 }
11709 });
11710 this.fold_creases(refold_creases, true, window, cx);
11711 this.change_selections(Default::default(), window, cx, |s| {
11712 s.select(new_selections);
11713 })
11714 });
11715 }
11716
11717 pub fn move_line_down(
11718 &mut self,
11719 _: &MoveLineDown,
11720 window: &mut Window,
11721 cx: &mut Context<Self>,
11722 ) {
11723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11724 if self.mode.is_single_line() {
11725 cx.propagate();
11726 return;
11727 }
11728
11729 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11730 let buffer = self.buffer.read(cx).snapshot(cx);
11731
11732 let mut edits = Vec::new();
11733 let mut unfold_ranges = Vec::new();
11734 let mut refold_creases = Vec::new();
11735
11736 let selections = self.selections.all::<Point>(cx);
11737 let mut selections = selections.iter().peekable();
11738 let mut contiguous_row_selections = Vec::new();
11739 let mut new_selections = Vec::new();
11740
11741 while let Some(selection) = selections.next() {
11742 // Find all the selections that span a contiguous row range
11743 let (start_row, end_row) = consume_contiguous_rows(
11744 &mut contiguous_row_selections,
11745 selection,
11746 &display_map,
11747 &mut selections,
11748 );
11749
11750 // Move the text spanned by the row range to be after the last line of the row range
11751 if end_row.0 <= buffer.max_point().row {
11752 let range_to_move =
11753 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11754 let insertion_point = display_map
11755 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11756 .0;
11757
11758 // Don't move lines across excerpt boundaries
11759 if buffer
11760 .excerpt_containing(range_to_move.start..insertion_point)
11761 .is_some()
11762 {
11763 let mut text = String::from("\n");
11764 text.extend(buffer.text_for_range(range_to_move.clone()));
11765 text.pop(); // Drop trailing newline
11766 edits.push((
11767 buffer.anchor_after(range_to_move.start)
11768 ..buffer.anchor_before(range_to_move.end),
11769 String::new(),
11770 ));
11771 let insertion_anchor = buffer.anchor_after(insertion_point);
11772 edits.push((insertion_anchor..insertion_anchor, text));
11773
11774 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11775
11776 // Move selections down
11777 new_selections.extend(contiguous_row_selections.drain(..).map(
11778 |mut selection| {
11779 selection.start.row += row_delta;
11780 selection.end.row += row_delta;
11781 selection
11782 },
11783 ));
11784
11785 // Move folds down
11786 unfold_ranges.push(range_to_move.clone());
11787 for fold in display_map.folds_in_range(
11788 buffer.anchor_before(range_to_move.start)
11789 ..buffer.anchor_after(range_to_move.end),
11790 ) {
11791 let mut start = fold.range.start.to_point(&buffer);
11792 let mut end = fold.range.end.to_point(&buffer);
11793 start.row += row_delta;
11794 end.row += row_delta;
11795 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11796 }
11797 }
11798 }
11799
11800 // If we didn't move line(s), preserve the existing selections
11801 new_selections.append(&mut contiguous_row_selections);
11802 }
11803
11804 self.transact(window, cx, |this, window, cx| {
11805 this.unfold_ranges(&unfold_ranges, true, true, cx);
11806 this.buffer.update(cx, |buffer, cx| {
11807 for (range, text) in edits {
11808 buffer.edit([(range, text)], None, cx);
11809 }
11810 });
11811 this.fold_creases(refold_creases, true, window, cx);
11812 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11813 });
11814 }
11815
11816 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11817 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11818 let text_layout_details = &self.text_layout_details(window);
11819 self.transact(window, cx, |this, window, cx| {
11820 let edits = this.change_selections(Default::default(), window, cx, |s| {
11821 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11822 s.move_with(|display_map, selection| {
11823 if !selection.is_empty() {
11824 return;
11825 }
11826
11827 let mut head = selection.head();
11828 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11829 if head.column() == display_map.line_len(head.row()) {
11830 transpose_offset = display_map
11831 .buffer_snapshot
11832 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11833 }
11834
11835 if transpose_offset == 0 {
11836 return;
11837 }
11838
11839 *head.column_mut() += 1;
11840 head = display_map.clip_point(head, Bias::Right);
11841 let goal = SelectionGoal::HorizontalPosition(
11842 display_map
11843 .x_for_display_point(head, text_layout_details)
11844 .into(),
11845 );
11846 selection.collapse_to(head, goal);
11847
11848 let transpose_start = display_map
11849 .buffer_snapshot
11850 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11851 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11852 let transpose_end = display_map
11853 .buffer_snapshot
11854 .clip_offset(transpose_offset + 1, Bias::Right);
11855 if let Some(ch) =
11856 display_map.buffer_snapshot.chars_at(transpose_start).next()
11857 {
11858 edits.push((transpose_start..transpose_offset, String::new()));
11859 edits.push((transpose_end..transpose_end, ch.to_string()));
11860 }
11861 }
11862 });
11863 edits
11864 });
11865 this.buffer
11866 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11867 let selections = this.selections.all::<usize>(cx);
11868 this.change_selections(Default::default(), window, cx, |s| {
11869 s.select(selections);
11870 });
11871 });
11872 }
11873
11874 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11875 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11876 if self.mode.is_single_line() {
11877 cx.propagate();
11878 return;
11879 }
11880
11881 self.rewrap_impl(RewrapOptions::default(), cx)
11882 }
11883
11884 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11885 let buffer = self.buffer.read(cx).snapshot(cx);
11886 let selections = self.selections.all::<Point>(cx);
11887
11888 #[derive(Clone, Debug, PartialEq)]
11889 enum CommentFormat {
11890 /// single line comment, with prefix for line
11891 Line(String),
11892 /// single line within a block comment, with prefix for line
11893 BlockLine(String),
11894 /// a single line of a block comment that includes the initial delimiter
11895 BlockCommentWithStart(BlockCommentConfig),
11896 /// a single line of a block comment that includes the ending delimiter
11897 BlockCommentWithEnd(BlockCommentConfig),
11898 }
11899
11900 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11901 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11902 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11903 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11904 .peekable();
11905
11906 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11907 row
11908 } else {
11909 return Vec::new();
11910 };
11911
11912 let language_settings = buffer.language_settings_at(selection.head(), cx);
11913 let language_scope = buffer.language_scope_at(selection.head());
11914
11915 let indent_and_prefix_for_row =
11916 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11917 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11918 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11919 &language_scope
11920 {
11921 let indent_end = Point::new(row, indent.len);
11922 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11923 let line_text_after_indent = buffer
11924 .text_for_range(indent_end..line_end)
11925 .collect::<String>();
11926
11927 let is_within_comment_override = buffer
11928 .language_scope_at(indent_end)
11929 .is_some_and(|scope| scope.override_name() == Some("comment"));
11930 let comment_delimiters = if is_within_comment_override {
11931 // we are within a comment syntax node, but we don't
11932 // yet know what kind of comment: block, doc or line
11933 match (
11934 language_scope.documentation_comment(),
11935 language_scope.block_comment(),
11936 ) {
11937 (Some(config), _) | (_, Some(config))
11938 if buffer.contains_str_at(indent_end, &config.start) =>
11939 {
11940 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11941 }
11942 (Some(config), _) | (_, Some(config))
11943 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11944 {
11945 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11946 }
11947 (Some(config), _) | (_, Some(config))
11948 if buffer.contains_str_at(indent_end, &config.prefix) =>
11949 {
11950 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11951 }
11952 (_, _) => language_scope
11953 .line_comment_prefixes()
11954 .iter()
11955 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11956 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11957 }
11958 } else {
11959 // we not in an overridden comment node, but we may
11960 // be within a non-overridden line comment node
11961 language_scope
11962 .line_comment_prefixes()
11963 .iter()
11964 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11965 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11966 };
11967
11968 let rewrap_prefix = language_scope
11969 .rewrap_prefixes()
11970 .iter()
11971 .find_map(|prefix_regex| {
11972 prefix_regex.find(&line_text_after_indent).map(|mat| {
11973 if mat.start() == 0 {
11974 Some(mat.as_str().to_string())
11975 } else {
11976 None
11977 }
11978 })
11979 })
11980 .flatten();
11981 (comment_delimiters, rewrap_prefix)
11982 } else {
11983 (None, None)
11984 };
11985 (indent, comment_prefix, rewrap_prefix)
11986 };
11987
11988 let mut ranges = Vec::new();
11989 let from_empty_selection = selection.is_empty();
11990
11991 let mut current_range_start = first_row;
11992 let mut prev_row = first_row;
11993 let (
11994 mut current_range_indent,
11995 mut current_range_comment_delimiters,
11996 mut current_range_rewrap_prefix,
11997 ) = indent_and_prefix_for_row(first_row);
11998
11999 for row in non_blank_rows_iter.skip(1) {
12000 let has_paragraph_break = row > prev_row + 1;
12001
12002 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12003 indent_and_prefix_for_row(row);
12004
12005 let has_indent_change = row_indent != current_range_indent;
12006 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12007
12008 let has_boundary_change = has_comment_change
12009 || row_rewrap_prefix.is_some()
12010 || (has_indent_change && current_range_comment_delimiters.is_some());
12011
12012 if has_paragraph_break || has_boundary_change {
12013 ranges.push((
12014 language_settings.clone(),
12015 Point::new(current_range_start, 0)
12016 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12017 current_range_indent,
12018 current_range_comment_delimiters.clone(),
12019 current_range_rewrap_prefix.clone(),
12020 from_empty_selection,
12021 ));
12022 current_range_start = row;
12023 current_range_indent = row_indent;
12024 current_range_comment_delimiters = row_comment_delimiters;
12025 current_range_rewrap_prefix = row_rewrap_prefix;
12026 }
12027 prev_row = row;
12028 }
12029
12030 ranges.push((
12031 language_settings.clone(),
12032 Point::new(current_range_start, 0)
12033 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12034 current_range_indent,
12035 current_range_comment_delimiters,
12036 current_range_rewrap_prefix,
12037 from_empty_selection,
12038 ));
12039
12040 ranges
12041 });
12042
12043 let mut edits = Vec::new();
12044 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12045
12046 for (
12047 language_settings,
12048 wrap_range,
12049 mut indent_size,
12050 comment_prefix,
12051 rewrap_prefix,
12052 from_empty_selection,
12053 ) in wrap_ranges
12054 {
12055 let mut start_row = wrap_range.start.row;
12056 let mut end_row = wrap_range.end.row;
12057
12058 // Skip selections that overlap with a range that has already been rewrapped.
12059 let selection_range = start_row..end_row;
12060 if rewrapped_row_ranges
12061 .iter()
12062 .any(|range| range.overlaps(&selection_range))
12063 {
12064 continue;
12065 }
12066
12067 let tab_size = language_settings.tab_size;
12068
12069 let (line_prefix, inside_comment) = match &comment_prefix {
12070 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12071 (Some(prefix.as_str()), true)
12072 }
12073 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12074 (Some(prefix.as_ref()), true)
12075 }
12076 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12077 start: _,
12078 end: _,
12079 prefix,
12080 tab_size,
12081 })) => {
12082 indent_size.len += tab_size;
12083 (Some(prefix.as_ref()), true)
12084 }
12085 None => (None, false),
12086 };
12087 let indent_prefix = indent_size.chars().collect::<String>();
12088 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12089
12090 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12091 RewrapBehavior::InComments => inside_comment,
12092 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12093 RewrapBehavior::Anywhere => true,
12094 };
12095
12096 let should_rewrap = options.override_language_settings
12097 || allow_rewrap_based_on_language
12098 || self.hard_wrap.is_some();
12099 if !should_rewrap {
12100 continue;
12101 }
12102
12103 if from_empty_selection {
12104 'expand_upwards: while start_row > 0 {
12105 let prev_row = start_row - 1;
12106 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12107 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12108 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12109 {
12110 start_row = prev_row;
12111 } else {
12112 break 'expand_upwards;
12113 }
12114 }
12115
12116 'expand_downwards: while end_row < buffer.max_point().row {
12117 let next_row = end_row + 1;
12118 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12119 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12120 && !buffer.is_line_blank(MultiBufferRow(next_row))
12121 {
12122 end_row = next_row;
12123 } else {
12124 break 'expand_downwards;
12125 }
12126 }
12127 }
12128
12129 let start = Point::new(start_row, 0);
12130 let start_offset = start.to_offset(&buffer);
12131 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12132 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12133 let mut first_line_delimiter = None;
12134 let mut last_line_delimiter = None;
12135 let Some(lines_without_prefixes) = selection_text
12136 .lines()
12137 .enumerate()
12138 .map(|(ix, line)| {
12139 let line_trimmed = line.trim_start();
12140 if rewrap_prefix.is_some() && ix > 0 {
12141 Ok(line_trimmed)
12142 } else if let Some(
12143 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12144 start,
12145 prefix,
12146 end,
12147 tab_size,
12148 })
12149 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12150 start,
12151 prefix,
12152 end,
12153 tab_size,
12154 }),
12155 ) = &comment_prefix
12156 {
12157 let line_trimmed = line_trimmed
12158 .strip_prefix(start.as_ref())
12159 .map(|s| {
12160 let mut indent_size = indent_size;
12161 indent_size.len -= tab_size;
12162 let indent_prefix: String = indent_size.chars().collect();
12163 first_line_delimiter = Some((indent_prefix, start));
12164 s.trim_start()
12165 })
12166 .unwrap_or(line_trimmed);
12167 let line_trimmed = line_trimmed
12168 .strip_suffix(end.as_ref())
12169 .map(|s| {
12170 last_line_delimiter = Some(end);
12171 s.trim_end()
12172 })
12173 .unwrap_or(line_trimmed);
12174 let line_trimmed = line_trimmed
12175 .strip_prefix(prefix.as_ref())
12176 .unwrap_or(line_trimmed);
12177 Ok(line_trimmed)
12178 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12179 line_trimmed.strip_prefix(prefix).with_context(|| {
12180 format!("line did not start with prefix {prefix:?}: {line:?}")
12181 })
12182 } else {
12183 line_trimmed
12184 .strip_prefix(&line_prefix.trim_start())
12185 .with_context(|| {
12186 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12187 })
12188 }
12189 })
12190 .collect::<Result<Vec<_>, _>>()
12191 .log_err()
12192 else {
12193 continue;
12194 };
12195
12196 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12197 buffer
12198 .language_settings_at(Point::new(start_row, 0), cx)
12199 .preferred_line_length as usize
12200 });
12201
12202 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12203 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12204 } else {
12205 line_prefix.clone()
12206 };
12207
12208 let wrapped_text = {
12209 let mut wrapped_text = wrap_with_prefix(
12210 line_prefix,
12211 subsequent_lines_prefix,
12212 lines_without_prefixes.join("\n"),
12213 wrap_column,
12214 tab_size,
12215 options.preserve_existing_whitespace,
12216 );
12217
12218 if let Some((indent, delimiter)) = first_line_delimiter {
12219 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12220 }
12221 if let Some(last_line) = last_line_delimiter {
12222 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12223 }
12224
12225 wrapped_text
12226 };
12227
12228 // TODO: should always use char-based diff while still supporting cursor behavior that
12229 // matches vim.
12230 let mut diff_options = DiffOptions::default();
12231 if options.override_language_settings {
12232 diff_options.max_word_diff_len = 0;
12233 diff_options.max_word_diff_line_count = 0;
12234 } else {
12235 diff_options.max_word_diff_len = usize::MAX;
12236 diff_options.max_word_diff_line_count = usize::MAX;
12237 }
12238
12239 for (old_range, new_text) in
12240 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12241 {
12242 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12243 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12244 edits.push((edit_start..edit_end, new_text));
12245 }
12246
12247 rewrapped_row_ranges.push(start_row..=end_row);
12248 }
12249
12250 self.buffer
12251 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12252 }
12253
12254 pub fn cut_common(
12255 &mut self,
12256 cut_no_selection_line: bool,
12257 window: &mut Window,
12258 cx: &mut Context<Self>,
12259 ) -> ClipboardItem {
12260 let mut text = String::new();
12261 let buffer = self.buffer.read(cx).snapshot(cx);
12262 let mut selections = self.selections.all::<Point>(cx);
12263 let mut clipboard_selections = Vec::with_capacity(selections.len());
12264 {
12265 let max_point = buffer.max_point();
12266 let mut is_first = true;
12267 for selection in &mut selections {
12268 let is_entire_line =
12269 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12270 if is_entire_line {
12271 selection.start = Point::new(selection.start.row, 0);
12272 if !selection.is_empty() && selection.end.column == 0 {
12273 selection.end = cmp::min(max_point, selection.end);
12274 } else {
12275 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12276 }
12277 selection.goal = SelectionGoal::None;
12278 }
12279 if is_first {
12280 is_first = false;
12281 } else {
12282 text += "\n";
12283 }
12284 let mut len = 0;
12285 for chunk in buffer.text_for_range(selection.start..selection.end) {
12286 text.push_str(chunk);
12287 len += chunk.len();
12288 }
12289 clipboard_selections.push(ClipboardSelection {
12290 len,
12291 is_entire_line,
12292 first_line_indent: buffer
12293 .indent_size_for_line(MultiBufferRow(selection.start.row))
12294 .len,
12295 });
12296 }
12297 }
12298
12299 self.transact(window, cx, |this, window, cx| {
12300 this.change_selections(Default::default(), window, cx, |s| {
12301 s.select(selections);
12302 });
12303 this.insert("", window, cx);
12304 });
12305 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12306 }
12307
12308 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12310 let item = self.cut_common(true, window, cx);
12311 cx.write_to_clipboard(item);
12312 }
12313
12314 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12316 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12317 s.move_with(|snapshot, sel| {
12318 if sel.is_empty() {
12319 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12320 }
12321 if sel.is_empty() {
12322 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12323 }
12324 });
12325 });
12326 let item = self.cut_common(true, window, cx);
12327 cx.set_global(KillRing(item))
12328 }
12329
12330 pub fn kill_ring_yank(
12331 &mut self,
12332 _: &KillRingYank,
12333 window: &mut Window,
12334 cx: &mut Context<Self>,
12335 ) {
12336 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12337 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12338 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12339 (kill_ring.text().to_string(), kill_ring.metadata_json())
12340 } else {
12341 return;
12342 }
12343 } else {
12344 return;
12345 };
12346 self.do_paste(&text, metadata, false, window, cx);
12347 }
12348
12349 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12350 self.do_copy(true, cx);
12351 }
12352
12353 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12354 self.do_copy(false, cx);
12355 }
12356
12357 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12358 let selections = self.selections.all::<Point>(cx);
12359 let buffer = self.buffer.read(cx).read(cx);
12360 let mut text = String::new();
12361
12362 let mut clipboard_selections = Vec::with_capacity(selections.len());
12363 {
12364 let max_point = buffer.max_point();
12365 let mut is_first = true;
12366 for selection in &selections {
12367 let mut start = selection.start;
12368 let mut end = selection.end;
12369 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12370 if is_entire_line {
12371 start = Point::new(start.row, 0);
12372 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12373 }
12374
12375 let mut trimmed_selections = Vec::new();
12376 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12377 let row = MultiBufferRow(start.row);
12378 let first_indent = buffer.indent_size_for_line(row);
12379 if first_indent.len == 0 || start.column > first_indent.len {
12380 trimmed_selections.push(start..end);
12381 } else {
12382 trimmed_selections.push(
12383 Point::new(row.0, first_indent.len)
12384 ..Point::new(row.0, buffer.line_len(row)),
12385 );
12386 for row in start.row + 1..=end.row {
12387 let mut line_len = buffer.line_len(MultiBufferRow(row));
12388 if row == end.row {
12389 line_len = end.column;
12390 }
12391 if line_len == 0 {
12392 trimmed_selections
12393 .push(Point::new(row, 0)..Point::new(row, line_len));
12394 continue;
12395 }
12396 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12397 if row_indent_size.len >= first_indent.len {
12398 trimmed_selections.push(
12399 Point::new(row, first_indent.len)..Point::new(row, line_len),
12400 );
12401 } else {
12402 trimmed_selections.clear();
12403 trimmed_selections.push(start..end);
12404 break;
12405 }
12406 }
12407 }
12408 } else {
12409 trimmed_selections.push(start..end);
12410 }
12411
12412 for trimmed_range in trimmed_selections {
12413 if is_first {
12414 is_first = false;
12415 } else {
12416 text += "\n";
12417 }
12418 let mut len = 0;
12419 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12420 text.push_str(chunk);
12421 len += chunk.len();
12422 }
12423 clipboard_selections.push(ClipboardSelection {
12424 len,
12425 is_entire_line,
12426 first_line_indent: buffer
12427 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12428 .len,
12429 });
12430 }
12431 }
12432 }
12433
12434 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12435 text,
12436 clipboard_selections,
12437 ));
12438 }
12439
12440 pub fn do_paste(
12441 &mut self,
12442 text: &String,
12443 clipboard_selections: Option<Vec<ClipboardSelection>>,
12444 handle_entire_lines: bool,
12445 window: &mut Window,
12446 cx: &mut Context<Self>,
12447 ) {
12448 if self.read_only(cx) {
12449 return;
12450 }
12451
12452 let clipboard_text = Cow::Borrowed(text);
12453
12454 self.transact(window, cx, |this, window, cx| {
12455 let had_active_edit_prediction = this.has_active_edit_prediction();
12456
12457 if let Some(mut clipboard_selections) = clipboard_selections {
12458 let old_selections = this.selections.all::<usize>(cx);
12459 let all_selections_were_entire_line =
12460 clipboard_selections.iter().all(|s| s.is_entire_line);
12461 let first_selection_indent_column =
12462 clipboard_selections.first().map(|s| s.first_line_indent);
12463 if clipboard_selections.len() != old_selections.len() {
12464 clipboard_selections.drain(..);
12465 }
12466 let cursor_offset = this.selections.last::<usize>(cx).head();
12467 let mut auto_indent_on_paste = true;
12468
12469 this.buffer.update(cx, |buffer, cx| {
12470 let snapshot = buffer.read(cx);
12471 auto_indent_on_paste = snapshot
12472 .language_settings_at(cursor_offset, cx)
12473 .auto_indent_on_paste;
12474
12475 let mut start_offset = 0;
12476 let mut edits = Vec::new();
12477 let mut original_indent_columns = Vec::new();
12478 for (ix, selection) in old_selections.iter().enumerate() {
12479 let to_insert;
12480 let entire_line;
12481 let original_indent_column;
12482 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12483 let end_offset = start_offset + clipboard_selection.len;
12484 to_insert = &clipboard_text[start_offset..end_offset];
12485 entire_line = clipboard_selection.is_entire_line;
12486 start_offset = end_offset + 1;
12487 original_indent_column = Some(clipboard_selection.first_line_indent);
12488 } else {
12489 to_insert = clipboard_text.as_str();
12490 entire_line = all_selections_were_entire_line;
12491 original_indent_column = first_selection_indent_column
12492 }
12493
12494 // If the corresponding selection was empty when this slice of the
12495 // clipboard text was written, then the entire line containing the
12496 // selection was copied. If this selection is also currently empty,
12497 // then paste the line before the current line of the buffer.
12498 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12499 let column = selection.start.to_point(&snapshot).column as usize;
12500 let line_start = selection.start - column;
12501 line_start..line_start
12502 } else {
12503 selection.range()
12504 };
12505
12506 edits.push((range, to_insert));
12507 original_indent_columns.push(original_indent_column);
12508 }
12509 drop(snapshot);
12510
12511 buffer.edit(
12512 edits,
12513 if auto_indent_on_paste {
12514 Some(AutoindentMode::Block {
12515 original_indent_columns,
12516 })
12517 } else {
12518 None
12519 },
12520 cx,
12521 );
12522 });
12523
12524 let selections = this.selections.all::<usize>(cx);
12525 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12526 } else {
12527 this.insert(&clipboard_text, window, cx);
12528 }
12529
12530 let trigger_in_words =
12531 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12532
12533 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12534 });
12535 }
12536
12537 pub fn diff_clipboard_with_selection(
12538 &mut self,
12539 _: &DiffClipboardWithSelection,
12540 window: &mut Window,
12541 cx: &mut Context<Self>,
12542 ) {
12543 let selections = self.selections.all::<usize>(cx);
12544
12545 if selections.is_empty() {
12546 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12547 return;
12548 };
12549
12550 let clipboard_text = match cx.read_from_clipboard() {
12551 Some(item) => match item.entries().first() {
12552 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12553 _ => None,
12554 },
12555 None => None,
12556 };
12557
12558 let Some(clipboard_text) = clipboard_text else {
12559 log::warn!("Clipboard doesn't contain text.");
12560 return;
12561 };
12562
12563 window.dispatch_action(
12564 Box::new(DiffClipboardWithSelectionData {
12565 clipboard_text,
12566 editor: cx.entity(),
12567 }),
12568 cx,
12569 );
12570 }
12571
12572 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12574 if let Some(item) = cx.read_from_clipboard() {
12575 let entries = item.entries();
12576
12577 match entries.first() {
12578 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12579 // of all the pasted entries.
12580 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12581 .do_paste(
12582 clipboard_string.text(),
12583 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12584 true,
12585 window,
12586 cx,
12587 ),
12588 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12589 }
12590 }
12591 }
12592
12593 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12594 if self.read_only(cx) {
12595 return;
12596 }
12597
12598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12599
12600 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12601 if let Some((selections, _)) =
12602 self.selection_history.transaction(transaction_id).cloned()
12603 {
12604 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12605 s.select_anchors(selections.to_vec());
12606 });
12607 } else {
12608 log::error!(
12609 "No entry in selection_history found for undo. \
12610 This may correspond to a bug where undo does not update the selection. \
12611 If this is occurring, please add details to \
12612 https://github.com/zed-industries/zed/issues/22692"
12613 );
12614 }
12615 self.request_autoscroll(Autoscroll::fit(), cx);
12616 self.unmark_text(window, cx);
12617 self.refresh_edit_prediction(true, false, window, cx);
12618 cx.emit(EditorEvent::Edited { transaction_id });
12619 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12620 }
12621 }
12622
12623 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12624 if self.read_only(cx) {
12625 return;
12626 }
12627
12628 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12629
12630 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12631 if let Some((_, Some(selections))) =
12632 self.selection_history.transaction(transaction_id).cloned()
12633 {
12634 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12635 s.select_anchors(selections.to_vec());
12636 });
12637 } else {
12638 log::error!(
12639 "No entry in selection_history found for redo. \
12640 This may correspond to a bug where undo does not update the selection. \
12641 If this is occurring, please add details to \
12642 https://github.com/zed-industries/zed/issues/22692"
12643 );
12644 }
12645 self.request_autoscroll(Autoscroll::fit(), cx);
12646 self.unmark_text(window, cx);
12647 self.refresh_edit_prediction(true, false, window, cx);
12648 cx.emit(EditorEvent::Edited { transaction_id });
12649 }
12650 }
12651
12652 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12653 self.buffer
12654 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12655 }
12656
12657 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12658 self.buffer
12659 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12660 }
12661
12662 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12664 self.change_selections(Default::default(), window, cx, |s| {
12665 s.move_with(|map, selection| {
12666 let cursor = if selection.is_empty() {
12667 movement::left(map, selection.start)
12668 } else {
12669 selection.start
12670 };
12671 selection.collapse_to(cursor, SelectionGoal::None);
12672 });
12673 })
12674 }
12675
12676 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12678 self.change_selections(Default::default(), window, cx, |s| {
12679 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12680 })
12681 }
12682
12683 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12685 self.change_selections(Default::default(), window, cx, |s| {
12686 s.move_with(|map, selection| {
12687 let cursor = if selection.is_empty() {
12688 movement::right(map, selection.end)
12689 } else {
12690 selection.end
12691 };
12692 selection.collapse_to(cursor, SelectionGoal::None)
12693 });
12694 })
12695 }
12696
12697 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12699 self.change_selections(Default::default(), window, cx, |s| {
12700 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12701 })
12702 }
12703
12704 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12705 if self.take_rename(true, window, cx).is_some() {
12706 return;
12707 }
12708
12709 if self.mode.is_single_line() {
12710 cx.propagate();
12711 return;
12712 }
12713
12714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12715
12716 let text_layout_details = &self.text_layout_details(window);
12717 let selection_count = self.selections.count();
12718 let first_selection = self.selections.first_anchor();
12719
12720 self.change_selections(Default::default(), window, cx, |s| {
12721 s.move_with(|map, selection| {
12722 if !selection.is_empty() {
12723 selection.goal = SelectionGoal::None;
12724 }
12725 let (cursor, goal) = movement::up(
12726 map,
12727 selection.start,
12728 selection.goal,
12729 false,
12730 text_layout_details,
12731 );
12732 selection.collapse_to(cursor, goal);
12733 });
12734 });
12735
12736 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12737 {
12738 cx.propagate();
12739 }
12740 }
12741
12742 pub fn move_up_by_lines(
12743 &mut self,
12744 action: &MoveUpByLines,
12745 window: &mut Window,
12746 cx: &mut Context<Self>,
12747 ) {
12748 if self.take_rename(true, window, cx).is_some() {
12749 return;
12750 }
12751
12752 if self.mode.is_single_line() {
12753 cx.propagate();
12754 return;
12755 }
12756
12757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12758
12759 let text_layout_details = &self.text_layout_details(window);
12760
12761 self.change_selections(Default::default(), window, cx, |s| {
12762 s.move_with(|map, selection| {
12763 if !selection.is_empty() {
12764 selection.goal = SelectionGoal::None;
12765 }
12766 let (cursor, goal) = movement::up_by_rows(
12767 map,
12768 selection.start,
12769 action.lines,
12770 selection.goal,
12771 false,
12772 text_layout_details,
12773 );
12774 selection.collapse_to(cursor, goal);
12775 });
12776 })
12777 }
12778
12779 pub fn move_down_by_lines(
12780 &mut self,
12781 action: &MoveDownByLines,
12782 window: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 if self.take_rename(true, window, cx).is_some() {
12786 return;
12787 }
12788
12789 if self.mode.is_single_line() {
12790 cx.propagate();
12791 return;
12792 }
12793
12794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12795
12796 let text_layout_details = &self.text_layout_details(window);
12797
12798 self.change_selections(Default::default(), window, cx, |s| {
12799 s.move_with(|map, selection| {
12800 if !selection.is_empty() {
12801 selection.goal = SelectionGoal::None;
12802 }
12803 let (cursor, goal) = movement::down_by_rows(
12804 map,
12805 selection.start,
12806 action.lines,
12807 selection.goal,
12808 false,
12809 text_layout_details,
12810 );
12811 selection.collapse_to(cursor, goal);
12812 });
12813 })
12814 }
12815
12816 pub fn select_down_by_lines(
12817 &mut self,
12818 action: &SelectDownByLines,
12819 window: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) {
12822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12823 let text_layout_details = &self.text_layout_details(window);
12824 self.change_selections(Default::default(), window, cx, |s| {
12825 s.move_heads_with(|map, head, goal| {
12826 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12827 })
12828 })
12829 }
12830
12831 pub fn select_up_by_lines(
12832 &mut self,
12833 action: &SelectUpByLines,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12838 let text_layout_details = &self.text_layout_details(window);
12839 self.change_selections(Default::default(), window, cx, |s| {
12840 s.move_heads_with(|map, head, goal| {
12841 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12842 })
12843 })
12844 }
12845
12846 pub fn select_page_up(
12847 &mut self,
12848 _: &SelectPageUp,
12849 window: &mut Window,
12850 cx: &mut Context<Self>,
12851 ) {
12852 let Some(row_count) = self.visible_row_count() else {
12853 return;
12854 };
12855
12856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12857
12858 let text_layout_details = &self.text_layout_details(window);
12859
12860 self.change_selections(Default::default(), window, cx, |s| {
12861 s.move_heads_with(|map, head, goal| {
12862 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12863 })
12864 })
12865 }
12866
12867 pub fn move_page_up(
12868 &mut self,
12869 action: &MovePageUp,
12870 window: &mut Window,
12871 cx: &mut Context<Self>,
12872 ) {
12873 if self.take_rename(true, window, cx).is_some() {
12874 return;
12875 }
12876
12877 if self
12878 .context_menu
12879 .borrow_mut()
12880 .as_mut()
12881 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12882 .unwrap_or(false)
12883 {
12884 return;
12885 }
12886
12887 if matches!(self.mode, EditorMode::SingleLine) {
12888 cx.propagate();
12889 return;
12890 }
12891
12892 let Some(row_count) = self.visible_row_count() else {
12893 return;
12894 };
12895
12896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12897
12898 let effects = if action.center_cursor {
12899 SelectionEffects::scroll(Autoscroll::center())
12900 } else {
12901 SelectionEffects::default()
12902 };
12903
12904 let text_layout_details = &self.text_layout_details(window);
12905
12906 self.change_selections(effects, window, cx, |s| {
12907 s.move_with(|map, selection| {
12908 if !selection.is_empty() {
12909 selection.goal = SelectionGoal::None;
12910 }
12911 let (cursor, goal) = movement::up_by_rows(
12912 map,
12913 selection.end,
12914 row_count,
12915 selection.goal,
12916 false,
12917 text_layout_details,
12918 );
12919 selection.collapse_to(cursor, goal);
12920 });
12921 });
12922 }
12923
12924 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12926 let text_layout_details = &self.text_layout_details(window);
12927 self.change_selections(Default::default(), window, cx, |s| {
12928 s.move_heads_with(|map, head, goal| {
12929 movement::up(map, head, goal, false, text_layout_details)
12930 })
12931 })
12932 }
12933
12934 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12935 self.take_rename(true, window, cx);
12936
12937 if self.mode.is_single_line() {
12938 cx.propagate();
12939 return;
12940 }
12941
12942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12943
12944 let text_layout_details = &self.text_layout_details(window);
12945 let selection_count = self.selections.count();
12946 let first_selection = self.selections.first_anchor();
12947
12948 self.change_selections(Default::default(), window, cx, |s| {
12949 s.move_with(|map, selection| {
12950 if !selection.is_empty() {
12951 selection.goal = SelectionGoal::None;
12952 }
12953 let (cursor, goal) = movement::down(
12954 map,
12955 selection.end,
12956 selection.goal,
12957 false,
12958 text_layout_details,
12959 );
12960 selection.collapse_to(cursor, goal);
12961 });
12962 });
12963
12964 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12965 {
12966 cx.propagate();
12967 }
12968 }
12969
12970 pub fn select_page_down(
12971 &mut self,
12972 _: &SelectPageDown,
12973 window: &mut Window,
12974 cx: &mut Context<Self>,
12975 ) {
12976 let Some(row_count) = self.visible_row_count() else {
12977 return;
12978 };
12979
12980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12981
12982 let text_layout_details = &self.text_layout_details(window);
12983
12984 self.change_selections(Default::default(), window, cx, |s| {
12985 s.move_heads_with(|map, head, goal| {
12986 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12987 })
12988 })
12989 }
12990
12991 pub fn move_page_down(
12992 &mut self,
12993 action: &MovePageDown,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) {
12997 if self.take_rename(true, window, cx).is_some() {
12998 return;
12999 }
13000
13001 if self
13002 .context_menu
13003 .borrow_mut()
13004 .as_mut()
13005 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13006 .unwrap_or(false)
13007 {
13008 return;
13009 }
13010
13011 if matches!(self.mode, EditorMode::SingleLine) {
13012 cx.propagate();
13013 return;
13014 }
13015
13016 let Some(row_count) = self.visible_row_count() else {
13017 return;
13018 };
13019
13020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13021
13022 let effects = if action.center_cursor {
13023 SelectionEffects::scroll(Autoscroll::center())
13024 } else {
13025 SelectionEffects::default()
13026 };
13027
13028 let text_layout_details = &self.text_layout_details(window);
13029 self.change_selections(effects, window, cx, |s| {
13030 s.move_with(|map, selection| {
13031 if !selection.is_empty() {
13032 selection.goal = SelectionGoal::None;
13033 }
13034 let (cursor, goal) = movement::down_by_rows(
13035 map,
13036 selection.end,
13037 row_count,
13038 selection.goal,
13039 false,
13040 text_layout_details,
13041 );
13042 selection.collapse_to(cursor, goal);
13043 });
13044 });
13045 }
13046
13047 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13049 let text_layout_details = &self.text_layout_details(window);
13050 self.change_selections(Default::default(), window, cx, |s| {
13051 s.move_heads_with(|map, head, goal| {
13052 movement::down(map, head, goal, false, text_layout_details)
13053 })
13054 });
13055 }
13056
13057 pub fn context_menu_first(
13058 &mut self,
13059 _: &ContextMenuFirst,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13064 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13065 }
13066 }
13067
13068 pub fn context_menu_prev(
13069 &mut self,
13070 _: &ContextMenuPrevious,
13071 window: &mut Window,
13072 cx: &mut Context<Self>,
13073 ) {
13074 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13075 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13076 }
13077 }
13078
13079 pub fn context_menu_next(
13080 &mut self,
13081 _: &ContextMenuNext,
13082 window: &mut Window,
13083 cx: &mut Context<Self>,
13084 ) {
13085 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13086 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13087 }
13088 }
13089
13090 pub fn context_menu_last(
13091 &mut self,
13092 _: &ContextMenuLast,
13093 window: &mut Window,
13094 cx: &mut Context<Self>,
13095 ) {
13096 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13097 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13098 }
13099 }
13100
13101 pub fn signature_help_prev(
13102 &mut self,
13103 _: &SignatureHelpPrevious,
13104 _: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 if let Some(popover) = self.signature_help_state.popover_mut() {
13108 if popover.current_signature == 0 {
13109 popover.current_signature = popover.signatures.len() - 1;
13110 } else {
13111 popover.current_signature -= 1;
13112 }
13113 cx.notify();
13114 }
13115 }
13116
13117 pub fn signature_help_next(
13118 &mut self,
13119 _: &SignatureHelpNext,
13120 _: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) {
13123 if let Some(popover) = self.signature_help_state.popover_mut() {
13124 if popover.current_signature + 1 == popover.signatures.len() {
13125 popover.current_signature = 0;
13126 } else {
13127 popover.current_signature += 1;
13128 }
13129 cx.notify();
13130 }
13131 }
13132
13133 pub fn move_to_previous_word_start(
13134 &mut self,
13135 _: &MoveToPreviousWordStart,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) {
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140 self.change_selections(Default::default(), window, cx, |s| {
13141 s.move_cursors_with(|map, head, _| {
13142 (
13143 movement::previous_word_start(map, head),
13144 SelectionGoal::None,
13145 )
13146 });
13147 })
13148 }
13149
13150 pub fn move_to_previous_subword_start(
13151 &mut self,
13152 _: &MoveToPreviousSubwordStart,
13153 window: &mut Window,
13154 cx: &mut Context<Self>,
13155 ) {
13156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13157 self.change_selections(Default::default(), window, cx, |s| {
13158 s.move_cursors_with(|map, head, _| {
13159 (
13160 movement::previous_subword_start(map, head),
13161 SelectionGoal::None,
13162 )
13163 });
13164 })
13165 }
13166
13167 pub fn select_to_previous_word_start(
13168 &mut self,
13169 _: &SelectToPreviousWordStart,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) {
13173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13174 self.change_selections(Default::default(), window, cx, |s| {
13175 s.move_heads_with(|map, head, _| {
13176 (
13177 movement::previous_word_start(map, head),
13178 SelectionGoal::None,
13179 )
13180 });
13181 })
13182 }
13183
13184 pub fn select_to_previous_subword_start(
13185 &mut self,
13186 _: &SelectToPreviousSubwordStart,
13187 window: &mut Window,
13188 cx: &mut Context<Self>,
13189 ) {
13190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13191 self.change_selections(Default::default(), window, cx, |s| {
13192 s.move_heads_with(|map, head, _| {
13193 (
13194 movement::previous_subword_start(map, head),
13195 SelectionGoal::None,
13196 )
13197 });
13198 })
13199 }
13200
13201 pub fn delete_to_previous_word_start(
13202 &mut self,
13203 action: &DeleteToPreviousWordStart,
13204 window: &mut Window,
13205 cx: &mut Context<Self>,
13206 ) {
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13208 self.transact(window, cx, |this, window, cx| {
13209 this.select_autoclose_pair(window, cx);
13210 this.change_selections(Default::default(), window, cx, |s| {
13211 s.move_with(|map, selection| {
13212 if selection.is_empty() {
13213 let mut cursor = if action.ignore_newlines {
13214 movement::previous_word_start(map, selection.head())
13215 } else {
13216 movement::previous_word_start_or_newline(map, selection.head())
13217 };
13218 cursor = movement::adjust_greedy_deletion(
13219 map,
13220 selection.head(),
13221 cursor,
13222 action.ignore_brackets,
13223 );
13224 selection.set_head(cursor, SelectionGoal::None);
13225 }
13226 });
13227 });
13228 this.insert("", window, cx);
13229 });
13230 }
13231
13232 pub fn delete_to_previous_subword_start(
13233 &mut self,
13234 _: &DeleteToPreviousSubwordStart,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) {
13238 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13239 self.transact(window, cx, |this, window, cx| {
13240 this.select_autoclose_pair(window, cx);
13241 this.change_selections(Default::default(), window, cx, |s| {
13242 s.move_with(|map, selection| {
13243 if selection.is_empty() {
13244 let mut cursor = movement::previous_subword_start(map, selection.head());
13245 cursor =
13246 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13247 selection.set_head(cursor, SelectionGoal::None);
13248 }
13249 });
13250 });
13251 this.insert("", window, cx);
13252 });
13253 }
13254
13255 pub fn move_to_next_word_end(
13256 &mut self,
13257 _: &MoveToNextWordEnd,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13262 self.change_selections(Default::default(), window, cx, |s| {
13263 s.move_cursors_with(|map, head, _| {
13264 (movement::next_word_end(map, head), SelectionGoal::None)
13265 });
13266 })
13267 }
13268
13269 pub fn move_to_next_subword_end(
13270 &mut self,
13271 _: &MoveToNextSubwordEnd,
13272 window: &mut Window,
13273 cx: &mut Context<Self>,
13274 ) {
13275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13276 self.change_selections(Default::default(), window, cx, |s| {
13277 s.move_cursors_with(|map, head, _| {
13278 (movement::next_subword_end(map, head), SelectionGoal::None)
13279 });
13280 })
13281 }
13282
13283 pub fn select_to_next_word_end(
13284 &mut self,
13285 _: &SelectToNextWordEnd,
13286 window: &mut Window,
13287 cx: &mut Context<Self>,
13288 ) {
13289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13290 self.change_selections(Default::default(), window, cx, |s| {
13291 s.move_heads_with(|map, head, _| {
13292 (movement::next_word_end(map, head), SelectionGoal::None)
13293 });
13294 })
13295 }
13296
13297 pub fn select_to_next_subword_end(
13298 &mut self,
13299 _: &SelectToNextSubwordEnd,
13300 window: &mut Window,
13301 cx: &mut Context<Self>,
13302 ) {
13303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13304 self.change_selections(Default::default(), window, cx, |s| {
13305 s.move_heads_with(|map, head, _| {
13306 (movement::next_subword_end(map, head), SelectionGoal::None)
13307 });
13308 })
13309 }
13310
13311 pub fn delete_to_next_word_end(
13312 &mut self,
13313 action: &DeleteToNextWordEnd,
13314 window: &mut Window,
13315 cx: &mut Context<Self>,
13316 ) {
13317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13318 self.transact(window, cx, |this, window, cx| {
13319 this.change_selections(Default::default(), window, cx, |s| {
13320 s.move_with(|map, selection| {
13321 if selection.is_empty() {
13322 let mut cursor = if action.ignore_newlines {
13323 movement::next_word_end(map, selection.head())
13324 } else {
13325 movement::next_word_end_or_newline(map, selection.head())
13326 };
13327 cursor = movement::adjust_greedy_deletion(
13328 map,
13329 selection.head(),
13330 cursor,
13331 action.ignore_brackets,
13332 );
13333 selection.set_head(cursor, SelectionGoal::None);
13334 }
13335 });
13336 });
13337 this.insert("", window, cx);
13338 });
13339 }
13340
13341 pub fn delete_to_next_subword_end(
13342 &mut self,
13343 _: &DeleteToNextSubwordEnd,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13348 self.transact(window, cx, |this, window, cx| {
13349 this.change_selections(Default::default(), window, cx, |s| {
13350 s.move_with(|map, selection| {
13351 if selection.is_empty() {
13352 let mut cursor = movement::next_subword_end(map, selection.head());
13353 cursor =
13354 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13355 selection.set_head(cursor, SelectionGoal::None);
13356 }
13357 });
13358 });
13359 this.insert("", window, cx);
13360 });
13361 }
13362
13363 pub fn move_to_beginning_of_line(
13364 &mut self,
13365 action: &MoveToBeginningOfLine,
13366 window: &mut Window,
13367 cx: &mut Context<Self>,
13368 ) {
13369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13370 self.change_selections(Default::default(), window, cx, |s| {
13371 s.move_cursors_with(|map, head, _| {
13372 (
13373 movement::indented_line_beginning(
13374 map,
13375 head,
13376 action.stop_at_soft_wraps,
13377 action.stop_at_indent,
13378 ),
13379 SelectionGoal::None,
13380 )
13381 });
13382 })
13383 }
13384
13385 pub fn select_to_beginning_of_line(
13386 &mut self,
13387 action: &SelectToBeginningOfLine,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) {
13391 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13392 self.change_selections(Default::default(), window, cx, |s| {
13393 s.move_heads_with(|map, head, _| {
13394 (
13395 movement::indented_line_beginning(
13396 map,
13397 head,
13398 action.stop_at_soft_wraps,
13399 action.stop_at_indent,
13400 ),
13401 SelectionGoal::None,
13402 )
13403 });
13404 });
13405 }
13406
13407 pub fn delete_to_beginning_of_line(
13408 &mut self,
13409 action: &DeleteToBeginningOfLine,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13414 self.transact(window, cx, |this, window, cx| {
13415 this.change_selections(Default::default(), window, cx, |s| {
13416 s.move_with(|_, selection| {
13417 selection.reversed = true;
13418 });
13419 });
13420
13421 this.select_to_beginning_of_line(
13422 &SelectToBeginningOfLine {
13423 stop_at_soft_wraps: false,
13424 stop_at_indent: action.stop_at_indent,
13425 },
13426 window,
13427 cx,
13428 );
13429 this.backspace(&Backspace, window, cx);
13430 });
13431 }
13432
13433 pub fn move_to_end_of_line(
13434 &mut self,
13435 action: &MoveToEndOfLine,
13436 window: &mut Window,
13437 cx: &mut Context<Self>,
13438 ) {
13439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13440 self.change_selections(Default::default(), window, cx, |s| {
13441 s.move_cursors_with(|map, head, _| {
13442 (
13443 movement::line_end(map, head, action.stop_at_soft_wraps),
13444 SelectionGoal::None,
13445 )
13446 });
13447 })
13448 }
13449
13450 pub fn select_to_end_of_line(
13451 &mut self,
13452 action: &SelectToEndOfLine,
13453 window: &mut Window,
13454 cx: &mut Context<Self>,
13455 ) {
13456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13457 self.change_selections(Default::default(), window, cx, |s| {
13458 s.move_heads_with(|map, head, _| {
13459 (
13460 movement::line_end(map, head, action.stop_at_soft_wraps),
13461 SelectionGoal::None,
13462 )
13463 });
13464 })
13465 }
13466
13467 pub fn delete_to_end_of_line(
13468 &mut self,
13469 _: &DeleteToEndOfLine,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) {
13473 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13474 self.transact(window, cx, |this, window, cx| {
13475 this.select_to_end_of_line(
13476 &SelectToEndOfLine {
13477 stop_at_soft_wraps: false,
13478 },
13479 window,
13480 cx,
13481 );
13482 this.delete(&Delete, window, cx);
13483 });
13484 }
13485
13486 pub fn cut_to_end_of_line(
13487 &mut self,
13488 action: &CutToEndOfLine,
13489 window: &mut Window,
13490 cx: &mut Context<Self>,
13491 ) {
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13493 self.transact(window, cx, |this, window, cx| {
13494 this.select_to_end_of_line(
13495 &SelectToEndOfLine {
13496 stop_at_soft_wraps: false,
13497 },
13498 window,
13499 cx,
13500 );
13501 if !action.stop_at_newlines {
13502 this.change_selections(Default::default(), window, cx, |s| {
13503 s.move_with(|_, sel| {
13504 if sel.is_empty() {
13505 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13506 }
13507 });
13508 });
13509 }
13510 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13511 let item = this.cut_common(false, window, cx);
13512 cx.write_to_clipboard(item);
13513 });
13514 }
13515
13516 pub fn move_to_start_of_paragraph(
13517 &mut self,
13518 _: &MoveToStartOfParagraph,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 if matches!(self.mode, EditorMode::SingleLine) {
13523 cx.propagate();
13524 return;
13525 }
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_with(|map, selection| {
13529 selection.collapse_to(
13530 movement::start_of_paragraph(map, selection.head(), 1),
13531 SelectionGoal::None,
13532 )
13533 });
13534 })
13535 }
13536
13537 pub fn move_to_end_of_paragraph(
13538 &mut self,
13539 _: &MoveToEndOfParagraph,
13540 window: &mut Window,
13541 cx: &mut Context<Self>,
13542 ) {
13543 if matches!(self.mode, EditorMode::SingleLine) {
13544 cx.propagate();
13545 return;
13546 }
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.move_with(|map, selection| {
13550 selection.collapse_to(
13551 movement::end_of_paragraph(map, selection.head(), 1),
13552 SelectionGoal::None,
13553 )
13554 });
13555 })
13556 }
13557
13558 pub fn select_to_start_of_paragraph(
13559 &mut self,
13560 _: &SelectToStartOfParagraph,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 if matches!(self.mode, EditorMode::SingleLine) {
13565 cx.propagate();
13566 return;
13567 }
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_heads_with(|map, head, _| {
13571 (
13572 movement::start_of_paragraph(map, head, 1),
13573 SelectionGoal::None,
13574 )
13575 });
13576 })
13577 }
13578
13579 pub fn select_to_end_of_paragraph(
13580 &mut self,
13581 _: &SelectToEndOfParagraph,
13582 window: &mut Window,
13583 cx: &mut Context<Self>,
13584 ) {
13585 if matches!(self.mode, EditorMode::SingleLine) {
13586 cx.propagate();
13587 return;
13588 }
13589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13590 self.change_selections(Default::default(), window, cx, |s| {
13591 s.move_heads_with(|map, head, _| {
13592 (
13593 movement::end_of_paragraph(map, head, 1),
13594 SelectionGoal::None,
13595 )
13596 });
13597 })
13598 }
13599
13600 pub fn move_to_start_of_excerpt(
13601 &mut self,
13602 _: &MoveToStartOfExcerpt,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) {
13606 if matches!(self.mode, EditorMode::SingleLine) {
13607 cx.propagate();
13608 return;
13609 }
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13611 self.change_selections(Default::default(), window, cx, |s| {
13612 s.move_with(|map, selection| {
13613 selection.collapse_to(
13614 movement::start_of_excerpt(
13615 map,
13616 selection.head(),
13617 workspace::searchable::Direction::Prev,
13618 ),
13619 SelectionGoal::None,
13620 )
13621 });
13622 })
13623 }
13624
13625 pub fn move_to_start_of_next_excerpt(
13626 &mut self,
13627 _: &MoveToStartOfNextExcerpt,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 if matches!(self.mode, EditorMode::SingleLine) {
13632 cx.propagate();
13633 return;
13634 }
13635
13636 self.change_selections(Default::default(), window, cx, |s| {
13637 s.move_with(|map, selection| {
13638 selection.collapse_to(
13639 movement::start_of_excerpt(
13640 map,
13641 selection.head(),
13642 workspace::searchable::Direction::Next,
13643 ),
13644 SelectionGoal::None,
13645 )
13646 });
13647 })
13648 }
13649
13650 pub fn move_to_end_of_excerpt(
13651 &mut self,
13652 _: &MoveToEndOfExcerpt,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 if matches!(self.mode, EditorMode::SingleLine) {
13657 cx.propagate();
13658 return;
13659 }
13660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13661 self.change_selections(Default::default(), window, cx, |s| {
13662 s.move_with(|map, selection| {
13663 selection.collapse_to(
13664 movement::end_of_excerpt(
13665 map,
13666 selection.head(),
13667 workspace::searchable::Direction::Next,
13668 ),
13669 SelectionGoal::None,
13670 )
13671 });
13672 })
13673 }
13674
13675 pub fn move_to_end_of_previous_excerpt(
13676 &mut self,
13677 _: &MoveToEndOfPreviousExcerpt,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 if matches!(self.mode, EditorMode::SingleLine) {
13682 cx.propagate();
13683 return;
13684 }
13685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13686 self.change_selections(Default::default(), window, cx, |s| {
13687 s.move_with(|map, selection| {
13688 selection.collapse_to(
13689 movement::end_of_excerpt(
13690 map,
13691 selection.head(),
13692 workspace::searchable::Direction::Prev,
13693 ),
13694 SelectionGoal::None,
13695 )
13696 });
13697 })
13698 }
13699
13700 pub fn select_to_start_of_excerpt(
13701 &mut self,
13702 _: &SelectToStartOfExcerpt,
13703 window: &mut Window,
13704 cx: &mut Context<Self>,
13705 ) {
13706 if matches!(self.mode, EditorMode::SingleLine) {
13707 cx.propagate();
13708 return;
13709 }
13710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13711 self.change_selections(Default::default(), window, cx, |s| {
13712 s.move_heads_with(|map, head, _| {
13713 (
13714 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13715 SelectionGoal::None,
13716 )
13717 });
13718 })
13719 }
13720
13721 pub fn select_to_start_of_next_excerpt(
13722 &mut self,
13723 _: &SelectToStartOfNextExcerpt,
13724 window: &mut Window,
13725 cx: &mut Context<Self>,
13726 ) {
13727 if matches!(self.mode, EditorMode::SingleLine) {
13728 cx.propagate();
13729 return;
13730 }
13731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13732 self.change_selections(Default::default(), window, cx, |s| {
13733 s.move_heads_with(|map, head, _| {
13734 (
13735 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13736 SelectionGoal::None,
13737 )
13738 });
13739 })
13740 }
13741
13742 pub fn select_to_end_of_excerpt(
13743 &mut self,
13744 _: &SelectToEndOfExcerpt,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 if matches!(self.mode, EditorMode::SingleLine) {
13749 cx.propagate();
13750 return;
13751 }
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.move_heads_with(|map, head, _| {
13755 (
13756 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13757 SelectionGoal::None,
13758 )
13759 });
13760 })
13761 }
13762
13763 pub fn select_to_end_of_previous_excerpt(
13764 &mut self,
13765 _: &SelectToEndOfPreviousExcerpt,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 if matches!(self.mode, EditorMode::SingleLine) {
13770 cx.propagate();
13771 return;
13772 }
13773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_heads_with(|map, head, _| {
13776 (
13777 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13778 SelectionGoal::None,
13779 )
13780 });
13781 })
13782 }
13783
13784 pub fn move_to_beginning(
13785 &mut self,
13786 _: &MoveToBeginning,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) {
13790 if matches!(self.mode, EditorMode::SingleLine) {
13791 cx.propagate();
13792 return;
13793 }
13794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13795 self.change_selections(Default::default(), window, cx, |s| {
13796 s.select_ranges(vec![0..0]);
13797 });
13798 }
13799
13800 pub fn select_to_beginning(
13801 &mut self,
13802 _: &SelectToBeginning,
13803 window: &mut Window,
13804 cx: &mut Context<Self>,
13805 ) {
13806 let mut selection = self.selections.last::<Point>(cx);
13807 selection.set_head(Point::zero(), SelectionGoal::None);
13808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13809 self.change_selections(Default::default(), window, cx, |s| {
13810 s.select(vec![selection]);
13811 });
13812 }
13813
13814 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13815 if matches!(self.mode, EditorMode::SingleLine) {
13816 cx.propagate();
13817 return;
13818 }
13819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13820 let cursor = self.buffer.read(cx).read(cx).len();
13821 self.change_selections(Default::default(), window, cx, |s| {
13822 s.select_ranges(vec![cursor..cursor])
13823 });
13824 }
13825
13826 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13827 self.nav_history = nav_history;
13828 }
13829
13830 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13831 self.nav_history.as_ref()
13832 }
13833
13834 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13835 self.push_to_nav_history(
13836 self.selections.newest_anchor().head(),
13837 None,
13838 false,
13839 true,
13840 cx,
13841 );
13842 }
13843
13844 fn push_to_nav_history(
13845 &mut self,
13846 cursor_anchor: Anchor,
13847 new_position: Option<Point>,
13848 is_deactivate: bool,
13849 always: bool,
13850 cx: &mut Context<Self>,
13851 ) {
13852 if let Some(nav_history) = self.nav_history.as_mut() {
13853 let buffer = self.buffer.read(cx).read(cx);
13854 let cursor_position = cursor_anchor.to_point(&buffer);
13855 let scroll_state = self.scroll_manager.anchor();
13856 let scroll_top_row = scroll_state.top_row(&buffer);
13857 drop(buffer);
13858
13859 if let Some(new_position) = new_position {
13860 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13861 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13862 return;
13863 }
13864 }
13865
13866 nav_history.push(
13867 Some(NavigationData {
13868 cursor_anchor,
13869 cursor_position,
13870 scroll_anchor: scroll_state,
13871 scroll_top_row,
13872 }),
13873 cx,
13874 );
13875 cx.emit(EditorEvent::PushedToNavHistory {
13876 anchor: cursor_anchor,
13877 is_deactivate,
13878 })
13879 }
13880 }
13881
13882 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13884 let buffer = self.buffer.read(cx).snapshot(cx);
13885 let mut selection = self.selections.first::<usize>(cx);
13886 selection.set_head(buffer.len(), SelectionGoal::None);
13887 self.change_selections(Default::default(), window, cx, |s| {
13888 s.select(vec![selection]);
13889 });
13890 }
13891
13892 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 let end = self.buffer.read(cx).read(cx).len();
13895 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13896 s.select_ranges(vec![0..end]);
13897 });
13898 }
13899
13900 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13903 let mut selections = self.selections.all::<Point>(cx);
13904 let max_point = display_map.buffer_snapshot.max_point();
13905 for selection in &mut selections {
13906 let rows = selection.spanned_rows(true, &display_map);
13907 selection.start = Point::new(rows.start.0, 0);
13908 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13909 selection.reversed = false;
13910 }
13911 self.change_selections(Default::default(), window, cx, |s| {
13912 s.select(selections);
13913 });
13914 }
13915
13916 pub fn split_selection_into_lines(
13917 &mut self,
13918 action: &SplitSelectionIntoLines,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 let selections = self
13923 .selections
13924 .all::<Point>(cx)
13925 .into_iter()
13926 .map(|selection| selection.start..selection.end)
13927 .collect::<Vec<_>>();
13928 self.unfold_ranges(&selections, true, true, cx);
13929
13930 let mut new_selection_ranges = Vec::new();
13931 {
13932 let buffer = self.buffer.read(cx).read(cx);
13933 for selection in selections {
13934 for row in selection.start.row..selection.end.row {
13935 let line_start = Point::new(row, 0);
13936 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13937
13938 if action.keep_selections {
13939 // Keep the selection range for each line
13940 let selection_start = if row == selection.start.row {
13941 selection.start
13942 } else {
13943 line_start
13944 };
13945 new_selection_ranges.push(selection_start..line_end);
13946 } else {
13947 // Collapse to cursor at end of line
13948 new_selection_ranges.push(line_end..line_end);
13949 }
13950 }
13951
13952 let is_multiline_selection = selection.start.row != selection.end.row;
13953 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13954 // so this action feels more ergonomic when paired with other selection operations
13955 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13956 if !should_skip_last {
13957 if action.keep_selections {
13958 if is_multiline_selection {
13959 let line_start = Point::new(selection.end.row, 0);
13960 new_selection_ranges.push(line_start..selection.end);
13961 } else {
13962 new_selection_ranges.push(selection.start..selection.end);
13963 }
13964 } else {
13965 new_selection_ranges.push(selection.end..selection.end);
13966 }
13967 }
13968 }
13969 }
13970 self.change_selections(Default::default(), window, cx, |s| {
13971 s.select_ranges(new_selection_ranges);
13972 });
13973 }
13974
13975 pub fn add_selection_above(
13976 &mut self,
13977 _: &AddSelectionAbove,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 self.add_selection(true, window, cx);
13982 }
13983
13984 pub fn add_selection_below(
13985 &mut self,
13986 _: &AddSelectionBelow,
13987 window: &mut Window,
13988 cx: &mut Context<Self>,
13989 ) {
13990 self.add_selection(false, window, cx);
13991 }
13992
13993 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13995
13996 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13997 let all_selections = self.selections.all::<Point>(cx);
13998 let text_layout_details = self.text_layout_details(window);
13999
14000 let (mut columnar_selections, new_selections_to_columnarize) = {
14001 if let Some(state) = self.add_selections_state.as_ref() {
14002 let columnar_selection_ids: HashSet<_> = state
14003 .groups
14004 .iter()
14005 .flat_map(|group| group.stack.iter())
14006 .copied()
14007 .collect();
14008
14009 all_selections
14010 .into_iter()
14011 .partition(|s| columnar_selection_ids.contains(&s.id))
14012 } else {
14013 (Vec::new(), all_selections)
14014 }
14015 };
14016
14017 let mut state = self
14018 .add_selections_state
14019 .take()
14020 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14021
14022 for selection in new_selections_to_columnarize {
14023 let range = selection.display_range(&display_map).sorted();
14024 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14025 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14026 let positions = start_x.min(end_x)..start_x.max(end_x);
14027 let mut stack = Vec::new();
14028 for row in range.start.row().0..=range.end.row().0 {
14029 if let Some(selection) = self.selections.build_columnar_selection(
14030 &display_map,
14031 DisplayRow(row),
14032 &positions,
14033 selection.reversed,
14034 &text_layout_details,
14035 ) {
14036 stack.push(selection.id);
14037 columnar_selections.push(selection);
14038 }
14039 }
14040 if !stack.is_empty() {
14041 if above {
14042 stack.reverse();
14043 }
14044 state.groups.push(AddSelectionsGroup { above, stack });
14045 }
14046 }
14047
14048 let mut final_selections = Vec::new();
14049 let end_row = if above {
14050 DisplayRow(0)
14051 } else {
14052 display_map.max_point().row()
14053 };
14054
14055 let mut last_added_item_per_group = HashMap::default();
14056 for group in state.groups.iter_mut() {
14057 if let Some(last_id) = group.stack.last() {
14058 last_added_item_per_group.insert(*last_id, group);
14059 }
14060 }
14061
14062 for selection in columnar_selections {
14063 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14064 if above == group.above {
14065 let range = selection.display_range(&display_map).sorted();
14066 debug_assert_eq!(range.start.row(), range.end.row());
14067 let mut row = range.start.row();
14068 let positions =
14069 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14070 px(start)..px(end)
14071 } else {
14072 let start_x =
14073 display_map.x_for_display_point(range.start, &text_layout_details);
14074 let end_x =
14075 display_map.x_for_display_point(range.end, &text_layout_details);
14076 start_x.min(end_x)..start_x.max(end_x)
14077 };
14078
14079 let mut maybe_new_selection = None;
14080 while row != end_row {
14081 if above {
14082 row.0 -= 1;
14083 } else {
14084 row.0 += 1;
14085 }
14086 if let Some(new_selection) = self.selections.build_columnar_selection(
14087 &display_map,
14088 row,
14089 &positions,
14090 selection.reversed,
14091 &text_layout_details,
14092 ) {
14093 maybe_new_selection = Some(new_selection);
14094 break;
14095 }
14096 }
14097
14098 if let Some(new_selection) = maybe_new_selection {
14099 group.stack.push(new_selection.id);
14100 if above {
14101 final_selections.push(new_selection);
14102 final_selections.push(selection);
14103 } else {
14104 final_selections.push(selection);
14105 final_selections.push(new_selection);
14106 }
14107 } else {
14108 final_selections.push(selection);
14109 }
14110 } else {
14111 group.stack.pop();
14112 }
14113 } else {
14114 final_selections.push(selection);
14115 }
14116 }
14117
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.select(final_selections);
14120 });
14121
14122 let final_selection_ids: HashSet<_> = self
14123 .selections
14124 .all::<Point>(cx)
14125 .iter()
14126 .map(|s| s.id)
14127 .collect();
14128 state.groups.retain_mut(|group| {
14129 // selections might get merged above so we remove invalid items from stacks
14130 group.stack.retain(|id| final_selection_ids.contains(id));
14131
14132 // single selection in stack can be treated as initial state
14133 group.stack.len() > 1
14134 });
14135
14136 if !state.groups.is_empty() {
14137 self.add_selections_state = Some(state);
14138 }
14139 }
14140
14141 fn select_match_ranges(
14142 &mut self,
14143 range: Range<usize>,
14144 reversed: bool,
14145 replace_newest: bool,
14146 auto_scroll: Option<Autoscroll>,
14147 window: &mut Window,
14148 cx: &mut Context<Editor>,
14149 ) {
14150 self.unfold_ranges(
14151 std::slice::from_ref(&range),
14152 false,
14153 auto_scroll.is_some(),
14154 cx,
14155 );
14156 let effects = if let Some(scroll) = auto_scroll {
14157 SelectionEffects::scroll(scroll)
14158 } else {
14159 SelectionEffects::no_scroll()
14160 };
14161 self.change_selections(effects, window, cx, |s| {
14162 if replace_newest {
14163 s.delete(s.newest_anchor().id);
14164 }
14165 if reversed {
14166 s.insert_range(range.end..range.start);
14167 } else {
14168 s.insert_range(range);
14169 }
14170 });
14171 }
14172
14173 pub fn select_next_match_internal(
14174 &mut self,
14175 display_map: &DisplaySnapshot,
14176 replace_newest: bool,
14177 autoscroll: Option<Autoscroll>,
14178 window: &mut Window,
14179 cx: &mut Context<Self>,
14180 ) -> Result<()> {
14181 let buffer = &display_map.buffer_snapshot;
14182 let mut selections = self.selections.all::<usize>(cx);
14183 if let Some(mut select_next_state) = self.select_next_state.take() {
14184 let query = &select_next_state.query;
14185 if !select_next_state.done {
14186 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14187 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14188 let mut next_selected_range = None;
14189
14190 let bytes_after_last_selection =
14191 buffer.bytes_in_range(last_selection.end..buffer.len());
14192 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14193 let query_matches = query
14194 .stream_find_iter(bytes_after_last_selection)
14195 .map(|result| (last_selection.end, result))
14196 .chain(
14197 query
14198 .stream_find_iter(bytes_before_first_selection)
14199 .map(|result| (0, result)),
14200 );
14201
14202 for (start_offset, query_match) in query_matches {
14203 let query_match = query_match.unwrap(); // can only fail due to I/O
14204 let offset_range =
14205 start_offset + query_match.start()..start_offset + query_match.end();
14206
14207 if !select_next_state.wordwise
14208 || (!buffer.is_inside_word(offset_range.start, false)
14209 && !buffer.is_inside_word(offset_range.end, false))
14210 {
14211 // TODO: This is n^2, because we might check all the selections
14212 if !selections
14213 .iter()
14214 .any(|selection| selection.range().overlaps(&offset_range))
14215 {
14216 next_selected_range = Some(offset_range);
14217 break;
14218 }
14219 }
14220 }
14221
14222 if let Some(next_selected_range) = next_selected_range {
14223 self.select_match_ranges(
14224 next_selected_range,
14225 last_selection.reversed,
14226 replace_newest,
14227 autoscroll,
14228 window,
14229 cx,
14230 );
14231 } else {
14232 select_next_state.done = true;
14233 }
14234 }
14235
14236 self.select_next_state = Some(select_next_state);
14237 } else {
14238 let mut only_carets = true;
14239 let mut same_text_selected = true;
14240 let mut selected_text = None;
14241
14242 let mut selections_iter = selections.iter().peekable();
14243 while let Some(selection) = selections_iter.next() {
14244 if selection.start != selection.end {
14245 only_carets = false;
14246 }
14247
14248 if same_text_selected {
14249 if selected_text.is_none() {
14250 selected_text =
14251 Some(buffer.text_for_range(selection.range()).collect::<String>());
14252 }
14253
14254 if let Some(next_selection) = selections_iter.peek() {
14255 if next_selection.range().len() == selection.range().len() {
14256 let next_selected_text = buffer
14257 .text_for_range(next_selection.range())
14258 .collect::<String>();
14259 if Some(next_selected_text) != selected_text {
14260 same_text_selected = false;
14261 selected_text = None;
14262 }
14263 } else {
14264 same_text_selected = false;
14265 selected_text = None;
14266 }
14267 }
14268 }
14269 }
14270
14271 if only_carets {
14272 for selection in &mut selections {
14273 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14274 selection.start = word_range.start;
14275 selection.end = word_range.end;
14276 selection.goal = SelectionGoal::None;
14277 selection.reversed = false;
14278 self.select_match_ranges(
14279 selection.start..selection.end,
14280 selection.reversed,
14281 replace_newest,
14282 autoscroll,
14283 window,
14284 cx,
14285 );
14286 }
14287
14288 if selections.len() == 1 {
14289 let selection = selections
14290 .last()
14291 .expect("ensured that there's only one selection");
14292 let query = buffer
14293 .text_for_range(selection.start..selection.end)
14294 .collect::<String>();
14295 let is_empty = query.is_empty();
14296 let select_state = SelectNextState {
14297 query: AhoCorasick::new(&[query])?,
14298 wordwise: true,
14299 done: is_empty,
14300 };
14301 self.select_next_state = Some(select_state);
14302 } else {
14303 self.select_next_state = None;
14304 }
14305 } else if let Some(selected_text) = selected_text {
14306 self.select_next_state = Some(SelectNextState {
14307 query: AhoCorasick::new(&[selected_text])?,
14308 wordwise: false,
14309 done: false,
14310 });
14311 self.select_next_match_internal(
14312 display_map,
14313 replace_newest,
14314 autoscroll,
14315 window,
14316 cx,
14317 )?;
14318 }
14319 }
14320 Ok(())
14321 }
14322
14323 pub fn select_all_matches(
14324 &mut self,
14325 _action: &SelectAllMatches,
14326 window: &mut Window,
14327 cx: &mut Context<Self>,
14328 ) -> Result<()> {
14329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14330
14331 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14332
14333 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14334 let Some(select_next_state) = self.select_next_state.as_mut() else {
14335 return Ok(());
14336 };
14337 if select_next_state.done {
14338 return Ok(());
14339 }
14340
14341 let mut new_selections = Vec::new();
14342
14343 let reversed = self.selections.oldest::<usize>(cx).reversed;
14344 let buffer = &display_map.buffer_snapshot;
14345 let query_matches = select_next_state
14346 .query
14347 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14348
14349 for query_match in query_matches.into_iter() {
14350 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14351 let offset_range = if reversed {
14352 query_match.end()..query_match.start()
14353 } else {
14354 query_match.start()..query_match.end()
14355 };
14356
14357 if !select_next_state.wordwise
14358 || (!buffer.is_inside_word(offset_range.start, false)
14359 && !buffer.is_inside_word(offset_range.end, false))
14360 {
14361 new_selections.push(offset_range.start..offset_range.end);
14362 }
14363 }
14364
14365 select_next_state.done = true;
14366
14367 if new_selections.is_empty() {
14368 log::error!("bug: new_selections is empty in select_all_matches");
14369 return Ok(());
14370 }
14371
14372 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14373 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14374 selections.select_ranges(new_selections)
14375 });
14376
14377 Ok(())
14378 }
14379
14380 pub fn select_next(
14381 &mut self,
14382 action: &SelectNext,
14383 window: &mut Window,
14384 cx: &mut Context<Self>,
14385 ) -> Result<()> {
14386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14387 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14388 self.select_next_match_internal(
14389 &display_map,
14390 action.replace_newest,
14391 Some(Autoscroll::newest()),
14392 window,
14393 cx,
14394 )?;
14395 Ok(())
14396 }
14397
14398 pub fn select_previous(
14399 &mut self,
14400 action: &SelectPrevious,
14401 window: &mut Window,
14402 cx: &mut Context<Self>,
14403 ) -> Result<()> {
14404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14406 let buffer = &display_map.buffer_snapshot;
14407 let mut selections = self.selections.all::<usize>(cx);
14408 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14409 let query = &select_prev_state.query;
14410 if !select_prev_state.done {
14411 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14412 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14413 let mut next_selected_range = None;
14414 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14415 let bytes_before_last_selection =
14416 buffer.reversed_bytes_in_range(0..last_selection.start);
14417 let bytes_after_first_selection =
14418 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14419 let query_matches = query
14420 .stream_find_iter(bytes_before_last_selection)
14421 .map(|result| (last_selection.start, result))
14422 .chain(
14423 query
14424 .stream_find_iter(bytes_after_first_selection)
14425 .map(|result| (buffer.len(), result)),
14426 );
14427 for (end_offset, query_match) in query_matches {
14428 let query_match = query_match.unwrap(); // can only fail due to I/O
14429 let offset_range =
14430 end_offset - query_match.end()..end_offset - query_match.start();
14431
14432 if !select_prev_state.wordwise
14433 || (!buffer.is_inside_word(offset_range.start, false)
14434 && !buffer.is_inside_word(offset_range.end, false))
14435 {
14436 next_selected_range = Some(offset_range);
14437 break;
14438 }
14439 }
14440
14441 if let Some(next_selected_range) = next_selected_range {
14442 self.select_match_ranges(
14443 next_selected_range,
14444 last_selection.reversed,
14445 action.replace_newest,
14446 Some(Autoscroll::newest()),
14447 window,
14448 cx,
14449 );
14450 } else {
14451 select_prev_state.done = true;
14452 }
14453 }
14454
14455 self.select_prev_state = Some(select_prev_state);
14456 } else {
14457 let mut only_carets = true;
14458 let mut same_text_selected = true;
14459 let mut selected_text = None;
14460
14461 let mut selections_iter = selections.iter().peekable();
14462 while let Some(selection) = selections_iter.next() {
14463 if selection.start != selection.end {
14464 only_carets = false;
14465 }
14466
14467 if same_text_selected {
14468 if selected_text.is_none() {
14469 selected_text =
14470 Some(buffer.text_for_range(selection.range()).collect::<String>());
14471 }
14472
14473 if let Some(next_selection) = selections_iter.peek() {
14474 if next_selection.range().len() == selection.range().len() {
14475 let next_selected_text = buffer
14476 .text_for_range(next_selection.range())
14477 .collect::<String>();
14478 if Some(next_selected_text) != selected_text {
14479 same_text_selected = false;
14480 selected_text = None;
14481 }
14482 } else {
14483 same_text_selected = false;
14484 selected_text = None;
14485 }
14486 }
14487 }
14488 }
14489
14490 if only_carets {
14491 for selection in &mut selections {
14492 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14493 selection.start = word_range.start;
14494 selection.end = word_range.end;
14495 selection.goal = SelectionGoal::None;
14496 selection.reversed = false;
14497 self.select_match_ranges(
14498 selection.start..selection.end,
14499 selection.reversed,
14500 action.replace_newest,
14501 Some(Autoscroll::newest()),
14502 window,
14503 cx,
14504 );
14505 }
14506 if selections.len() == 1 {
14507 let selection = selections
14508 .last()
14509 .expect("ensured that there's only one selection");
14510 let query = buffer
14511 .text_for_range(selection.start..selection.end)
14512 .collect::<String>();
14513 let is_empty = query.is_empty();
14514 let select_state = SelectNextState {
14515 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14516 wordwise: true,
14517 done: is_empty,
14518 };
14519 self.select_prev_state = Some(select_state);
14520 } else {
14521 self.select_prev_state = None;
14522 }
14523 } else if let Some(selected_text) = selected_text {
14524 self.select_prev_state = Some(SelectNextState {
14525 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14526 wordwise: false,
14527 done: false,
14528 });
14529 self.select_previous(action, window, cx)?;
14530 }
14531 }
14532 Ok(())
14533 }
14534
14535 pub fn find_next_match(
14536 &mut self,
14537 _: &FindNextMatch,
14538 window: &mut Window,
14539 cx: &mut Context<Self>,
14540 ) -> Result<()> {
14541 let selections = self.selections.disjoint_anchors();
14542 match selections.first() {
14543 Some(first) if selections.len() >= 2 => {
14544 self.change_selections(Default::default(), window, cx, |s| {
14545 s.select_ranges([first.range()]);
14546 });
14547 }
14548 _ => self.select_next(
14549 &SelectNext {
14550 replace_newest: true,
14551 },
14552 window,
14553 cx,
14554 )?,
14555 }
14556 Ok(())
14557 }
14558
14559 pub fn find_previous_match(
14560 &mut self,
14561 _: &FindPreviousMatch,
14562 window: &mut Window,
14563 cx: &mut Context<Self>,
14564 ) -> Result<()> {
14565 let selections = self.selections.disjoint_anchors();
14566 match selections.last() {
14567 Some(last) if selections.len() >= 2 => {
14568 self.change_selections(Default::default(), window, cx, |s| {
14569 s.select_ranges([last.range()]);
14570 });
14571 }
14572 _ => self.select_previous(
14573 &SelectPrevious {
14574 replace_newest: true,
14575 },
14576 window,
14577 cx,
14578 )?,
14579 }
14580 Ok(())
14581 }
14582
14583 pub fn toggle_comments(
14584 &mut self,
14585 action: &ToggleComments,
14586 window: &mut Window,
14587 cx: &mut Context<Self>,
14588 ) {
14589 if self.read_only(cx) {
14590 return;
14591 }
14592 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14593 let text_layout_details = &self.text_layout_details(window);
14594 self.transact(window, cx, |this, window, cx| {
14595 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14596 let mut edits = Vec::new();
14597 let mut selection_edit_ranges = Vec::new();
14598 let mut last_toggled_row = None;
14599 let snapshot = this.buffer.read(cx).read(cx);
14600 let empty_str: Arc<str> = Arc::default();
14601 let mut suffixes_inserted = Vec::new();
14602 let ignore_indent = action.ignore_indent;
14603
14604 fn comment_prefix_range(
14605 snapshot: &MultiBufferSnapshot,
14606 row: MultiBufferRow,
14607 comment_prefix: &str,
14608 comment_prefix_whitespace: &str,
14609 ignore_indent: bool,
14610 ) -> Range<Point> {
14611 let indent_size = if ignore_indent {
14612 0
14613 } else {
14614 snapshot.indent_size_for_line(row).len
14615 };
14616
14617 let start = Point::new(row.0, indent_size);
14618
14619 let mut line_bytes = snapshot
14620 .bytes_in_range(start..snapshot.max_point())
14621 .flatten()
14622 .copied();
14623
14624 // If this line currently begins with the line comment prefix, then record
14625 // the range containing the prefix.
14626 if line_bytes
14627 .by_ref()
14628 .take(comment_prefix.len())
14629 .eq(comment_prefix.bytes())
14630 {
14631 // Include any whitespace that matches the comment prefix.
14632 let matching_whitespace_len = line_bytes
14633 .zip(comment_prefix_whitespace.bytes())
14634 .take_while(|(a, b)| a == b)
14635 .count() as u32;
14636 let end = Point::new(
14637 start.row,
14638 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14639 );
14640 start..end
14641 } else {
14642 start..start
14643 }
14644 }
14645
14646 fn comment_suffix_range(
14647 snapshot: &MultiBufferSnapshot,
14648 row: MultiBufferRow,
14649 comment_suffix: &str,
14650 comment_suffix_has_leading_space: bool,
14651 ) -> Range<Point> {
14652 let end = Point::new(row.0, snapshot.line_len(row));
14653 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14654
14655 let mut line_end_bytes = snapshot
14656 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14657 .flatten()
14658 .copied();
14659
14660 let leading_space_len = if suffix_start_column > 0
14661 && line_end_bytes.next() == Some(b' ')
14662 && comment_suffix_has_leading_space
14663 {
14664 1
14665 } else {
14666 0
14667 };
14668
14669 // If this line currently begins with the line comment prefix, then record
14670 // the range containing the prefix.
14671 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14672 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14673 start..end
14674 } else {
14675 end..end
14676 }
14677 }
14678
14679 // TODO: Handle selections that cross excerpts
14680 for selection in &mut selections {
14681 let start_column = snapshot
14682 .indent_size_for_line(MultiBufferRow(selection.start.row))
14683 .len;
14684 let language = if let Some(language) =
14685 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14686 {
14687 language
14688 } else {
14689 continue;
14690 };
14691
14692 selection_edit_ranges.clear();
14693
14694 // If multiple selections contain a given row, avoid processing that
14695 // row more than once.
14696 let mut start_row = MultiBufferRow(selection.start.row);
14697 if last_toggled_row == Some(start_row) {
14698 start_row = start_row.next_row();
14699 }
14700 let end_row =
14701 if selection.end.row > selection.start.row && selection.end.column == 0 {
14702 MultiBufferRow(selection.end.row - 1)
14703 } else {
14704 MultiBufferRow(selection.end.row)
14705 };
14706 last_toggled_row = Some(end_row);
14707
14708 if start_row > end_row {
14709 continue;
14710 }
14711
14712 // If the language has line comments, toggle those.
14713 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14714
14715 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14716 if ignore_indent {
14717 full_comment_prefixes = full_comment_prefixes
14718 .into_iter()
14719 .map(|s| Arc::from(s.trim_end()))
14720 .collect();
14721 }
14722
14723 if !full_comment_prefixes.is_empty() {
14724 let first_prefix = full_comment_prefixes
14725 .first()
14726 .expect("prefixes is non-empty");
14727 let prefix_trimmed_lengths = full_comment_prefixes
14728 .iter()
14729 .map(|p| p.trim_end_matches(' ').len())
14730 .collect::<SmallVec<[usize; 4]>>();
14731
14732 let mut all_selection_lines_are_comments = true;
14733
14734 for row in start_row.0..=end_row.0 {
14735 let row = MultiBufferRow(row);
14736 if start_row < end_row && snapshot.is_line_blank(row) {
14737 continue;
14738 }
14739
14740 let prefix_range = full_comment_prefixes
14741 .iter()
14742 .zip(prefix_trimmed_lengths.iter().copied())
14743 .map(|(prefix, trimmed_prefix_len)| {
14744 comment_prefix_range(
14745 snapshot.deref(),
14746 row,
14747 &prefix[..trimmed_prefix_len],
14748 &prefix[trimmed_prefix_len..],
14749 ignore_indent,
14750 )
14751 })
14752 .max_by_key(|range| range.end.column - range.start.column)
14753 .expect("prefixes is non-empty");
14754
14755 if prefix_range.is_empty() {
14756 all_selection_lines_are_comments = false;
14757 }
14758
14759 selection_edit_ranges.push(prefix_range);
14760 }
14761
14762 if all_selection_lines_are_comments {
14763 edits.extend(
14764 selection_edit_ranges
14765 .iter()
14766 .cloned()
14767 .map(|range| (range, empty_str.clone())),
14768 );
14769 } else {
14770 let min_column = selection_edit_ranges
14771 .iter()
14772 .map(|range| range.start.column)
14773 .min()
14774 .unwrap_or(0);
14775 edits.extend(selection_edit_ranges.iter().map(|range| {
14776 let position = Point::new(range.start.row, min_column);
14777 (position..position, first_prefix.clone())
14778 }));
14779 }
14780 } else if let Some(BlockCommentConfig {
14781 start: full_comment_prefix,
14782 end: comment_suffix,
14783 ..
14784 }) = language.block_comment()
14785 {
14786 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14787 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14788 let prefix_range = comment_prefix_range(
14789 snapshot.deref(),
14790 start_row,
14791 comment_prefix,
14792 comment_prefix_whitespace,
14793 ignore_indent,
14794 );
14795 let suffix_range = comment_suffix_range(
14796 snapshot.deref(),
14797 end_row,
14798 comment_suffix.trim_start_matches(' '),
14799 comment_suffix.starts_with(' '),
14800 );
14801
14802 if prefix_range.is_empty() || suffix_range.is_empty() {
14803 edits.push((
14804 prefix_range.start..prefix_range.start,
14805 full_comment_prefix.clone(),
14806 ));
14807 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14808 suffixes_inserted.push((end_row, comment_suffix.len()));
14809 } else {
14810 edits.push((prefix_range, empty_str.clone()));
14811 edits.push((suffix_range, empty_str.clone()));
14812 }
14813 } else {
14814 continue;
14815 }
14816 }
14817
14818 drop(snapshot);
14819 this.buffer.update(cx, |buffer, cx| {
14820 buffer.edit(edits, None, cx);
14821 });
14822
14823 // Adjust selections so that they end before any comment suffixes that
14824 // were inserted.
14825 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14826 let mut selections = this.selections.all::<Point>(cx);
14827 let snapshot = this.buffer.read(cx).read(cx);
14828 for selection in &mut selections {
14829 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14830 match row.cmp(&MultiBufferRow(selection.end.row)) {
14831 Ordering::Less => {
14832 suffixes_inserted.next();
14833 continue;
14834 }
14835 Ordering::Greater => break,
14836 Ordering::Equal => {
14837 if selection.end.column == snapshot.line_len(row) {
14838 if selection.is_empty() {
14839 selection.start.column -= suffix_len as u32;
14840 }
14841 selection.end.column -= suffix_len as u32;
14842 }
14843 break;
14844 }
14845 }
14846 }
14847 }
14848
14849 drop(snapshot);
14850 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14851
14852 let selections = this.selections.all::<Point>(cx);
14853 let selections_on_single_row = selections.windows(2).all(|selections| {
14854 selections[0].start.row == selections[1].start.row
14855 && selections[0].end.row == selections[1].end.row
14856 && selections[0].start.row == selections[0].end.row
14857 });
14858 let selections_selecting = selections
14859 .iter()
14860 .any(|selection| selection.start != selection.end);
14861 let advance_downwards = action.advance_downwards
14862 && selections_on_single_row
14863 && !selections_selecting
14864 && !matches!(this.mode, EditorMode::SingleLine);
14865
14866 if advance_downwards {
14867 let snapshot = this.buffer.read(cx).snapshot(cx);
14868
14869 this.change_selections(Default::default(), window, cx, |s| {
14870 s.move_cursors_with(|display_snapshot, display_point, _| {
14871 let mut point = display_point.to_point(display_snapshot);
14872 point.row += 1;
14873 point = snapshot.clip_point(point, Bias::Left);
14874 let display_point = point.to_display_point(display_snapshot);
14875 let goal = SelectionGoal::HorizontalPosition(
14876 display_snapshot
14877 .x_for_display_point(display_point, text_layout_details)
14878 .into(),
14879 );
14880 (display_point, goal)
14881 })
14882 });
14883 }
14884 });
14885 }
14886
14887 pub fn select_enclosing_symbol(
14888 &mut self,
14889 _: &SelectEnclosingSymbol,
14890 window: &mut Window,
14891 cx: &mut Context<Self>,
14892 ) {
14893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14894
14895 let buffer = self.buffer.read(cx).snapshot(cx);
14896 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14897
14898 fn update_selection(
14899 selection: &Selection<usize>,
14900 buffer_snap: &MultiBufferSnapshot,
14901 ) -> Option<Selection<usize>> {
14902 let cursor = selection.head();
14903 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14904 for symbol in symbols.iter().rev() {
14905 let start = symbol.range.start.to_offset(buffer_snap);
14906 let end = symbol.range.end.to_offset(buffer_snap);
14907 let new_range = start..end;
14908 if start < selection.start || end > selection.end {
14909 return Some(Selection {
14910 id: selection.id,
14911 start: new_range.start,
14912 end: new_range.end,
14913 goal: SelectionGoal::None,
14914 reversed: selection.reversed,
14915 });
14916 }
14917 }
14918 None
14919 }
14920
14921 let mut selected_larger_symbol = false;
14922 let new_selections = old_selections
14923 .iter()
14924 .map(|selection| match update_selection(selection, &buffer) {
14925 Some(new_selection) => {
14926 if new_selection.range() != selection.range() {
14927 selected_larger_symbol = true;
14928 }
14929 new_selection
14930 }
14931 None => selection.clone(),
14932 })
14933 .collect::<Vec<_>>();
14934
14935 if selected_larger_symbol {
14936 self.change_selections(Default::default(), window, cx, |s| {
14937 s.select(new_selections);
14938 });
14939 }
14940 }
14941
14942 pub fn select_larger_syntax_node(
14943 &mut self,
14944 _: &SelectLargerSyntaxNode,
14945 window: &mut Window,
14946 cx: &mut Context<Self>,
14947 ) {
14948 let Some(visible_row_count) = self.visible_row_count() else {
14949 return;
14950 };
14951 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14952 if old_selections.is_empty() {
14953 return;
14954 }
14955
14956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14957
14958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14959 let buffer = self.buffer.read(cx).snapshot(cx);
14960
14961 let mut selected_larger_node = false;
14962 let mut new_selections = old_selections
14963 .iter()
14964 .map(|selection| {
14965 let old_range = selection.start..selection.end;
14966
14967 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14968 // manually select word at selection
14969 if ["string_content", "inline"].contains(&node.kind()) {
14970 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14971 // ignore if word is already selected
14972 if !word_range.is_empty() && old_range != word_range {
14973 let (last_word_range, _) =
14974 buffer.surrounding_word(old_range.end, false);
14975 // only select word if start and end point belongs to same word
14976 if word_range == last_word_range {
14977 selected_larger_node = true;
14978 return Selection {
14979 id: selection.id,
14980 start: word_range.start,
14981 end: word_range.end,
14982 goal: SelectionGoal::None,
14983 reversed: selection.reversed,
14984 };
14985 }
14986 }
14987 }
14988 }
14989
14990 let mut new_range = old_range.clone();
14991 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
14992 new_range = range;
14993 if !node.is_named() {
14994 continue;
14995 }
14996 if !display_map.intersects_fold(new_range.start)
14997 && !display_map.intersects_fold(new_range.end)
14998 {
14999 break;
15000 }
15001 }
15002
15003 selected_larger_node |= new_range != old_range;
15004 Selection {
15005 id: selection.id,
15006 start: new_range.start,
15007 end: new_range.end,
15008 goal: SelectionGoal::None,
15009 reversed: selection.reversed,
15010 }
15011 })
15012 .collect::<Vec<_>>();
15013
15014 if !selected_larger_node {
15015 return; // don't put this call in the history
15016 }
15017
15018 // scroll based on transformation done to the last selection created by the user
15019 let (last_old, last_new) = old_selections
15020 .last()
15021 .zip(new_selections.last().cloned())
15022 .expect("old_selections isn't empty");
15023
15024 // revert selection
15025 let is_selection_reversed = {
15026 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15027 new_selections.last_mut().expect("checked above").reversed =
15028 should_newest_selection_be_reversed;
15029 should_newest_selection_be_reversed
15030 };
15031
15032 if selected_larger_node {
15033 self.select_syntax_node_history.disable_clearing = true;
15034 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15035 s.select(new_selections.clone());
15036 });
15037 self.select_syntax_node_history.disable_clearing = false;
15038 }
15039
15040 let start_row = last_new.start.to_display_point(&display_map).row().0;
15041 let end_row = last_new.end.to_display_point(&display_map).row().0;
15042 let selection_height = end_row - start_row + 1;
15043 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15044
15045 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15046 let scroll_behavior = if fits_on_the_screen {
15047 self.request_autoscroll(Autoscroll::fit(), cx);
15048 SelectSyntaxNodeScrollBehavior::FitSelection
15049 } else if is_selection_reversed {
15050 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15051 SelectSyntaxNodeScrollBehavior::CursorTop
15052 } else {
15053 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15054 SelectSyntaxNodeScrollBehavior::CursorBottom
15055 };
15056
15057 self.select_syntax_node_history.push((
15058 old_selections,
15059 scroll_behavior,
15060 is_selection_reversed,
15061 ));
15062 }
15063
15064 pub fn select_smaller_syntax_node(
15065 &mut self,
15066 _: &SelectSmallerSyntaxNode,
15067 window: &mut Window,
15068 cx: &mut Context<Self>,
15069 ) {
15070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15071
15072 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15073 self.select_syntax_node_history.pop()
15074 {
15075 if let Some(selection) = selections.last_mut() {
15076 selection.reversed = is_selection_reversed;
15077 }
15078
15079 self.select_syntax_node_history.disable_clearing = true;
15080 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15081 s.select(selections.to_vec());
15082 });
15083 self.select_syntax_node_history.disable_clearing = false;
15084
15085 match scroll_behavior {
15086 SelectSyntaxNodeScrollBehavior::CursorTop => {
15087 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15088 }
15089 SelectSyntaxNodeScrollBehavior::FitSelection => {
15090 self.request_autoscroll(Autoscroll::fit(), cx);
15091 }
15092 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15093 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15094 }
15095 }
15096 }
15097 }
15098
15099 pub fn unwrap_syntax_node(
15100 &mut self,
15101 _: &UnwrapSyntaxNode,
15102 window: &mut Window,
15103 cx: &mut Context<Self>,
15104 ) {
15105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15106
15107 let buffer = self.buffer.read(cx).snapshot(cx);
15108 let selections = self
15109 .selections
15110 .all::<usize>(cx)
15111 .into_iter()
15112 // subtracting the offset requires sorting
15113 .sorted_by_key(|i| i.start);
15114
15115 let full_edits = selections
15116 .into_iter()
15117 .filter_map(|selection| {
15118 // Only requires two branches once if-let-chains stabilize (#53667)
15119 let child = if !selection.is_empty() {
15120 selection.range()
15121 } else if let Some((_, ancestor_range)) =
15122 buffer.syntax_ancestor(selection.start..selection.end)
15123 {
15124 ancestor_range
15125 } else {
15126 selection.range()
15127 };
15128
15129 let mut parent = child.clone();
15130 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15131 parent = ancestor_range;
15132 if parent.start < child.start || parent.end > child.end {
15133 break;
15134 }
15135 }
15136
15137 if parent == child {
15138 return None;
15139 }
15140 let text = buffer.text_for_range(child).collect::<String>();
15141 Some((selection.id, parent, text))
15142 })
15143 .collect::<Vec<_>>();
15144
15145 self.transact(window, cx, |this, window, cx| {
15146 this.buffer.update(cx, |buffer, cx| {
15147 buffer.edit(
15148 full_edits
15149 .iter()
15150 .map(|(_, p, t)| (p.clone(), t.clone()))
15151 .collect::<Vec<_>>(),
15152 None,
15153 cx,
15154 );
15155 });
15156 this.change_selections(Default::default(), window, cx, |s| {
15157 let mut offset = 0;
15158 let mut selections = vec![];
15159 for (id, parent, text) in full_edits {
15160 let start = parent.start - offset;
15161 offset += parent.len() - text.len();
15162 selections.push(Selection {
15163 id,
15164 start,
15165 end: start + text.len(),
15166 reversed: false,
15167 goal: Default::default(),
15168 });
15169 }
15170 s.select(selections);
15171 });
15172 });
15173 }
15174
15175 pub fn select_next_syntax_node(
15176 &mut self,
15177 _: &SelectNextSyntaxNode,
15178 window: &mut Window,
15179 cx: &mut Context<Self>,
15180 ) {
15181 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15182 if old_selections.is_empty() {
15183 return;
15184 }
15185
15186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15187
15188 let buffer = self.buffer.read(cx).snapshot(cx);
15189 let mut selected_sibling = false;
15190
15191 let new_selections = old_selections
15192 .iter()
15193 .map(|selection| {
15194 let old_range = selection.start..selection.end;
15195
15196 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15197 let new_range = node.byte_range();
15198 selected_sibling = true;
15199 Selection {
15200 id: selection.id,
15201 start: new_range.start,
15202 end: new_range.end,
15203 goal: SelectionGoal::None,
15204 reversed: selection.reversed,
15205 }
15206 } else {
15207 selection.clone()
15208 }
15209 })
15210 .collect::<Vec<_>>();
15211
15212 if selected_sibling {
15213 self.change_selections(
15214 SelectionEffects::scroll(Autoscroll::fit()),
15215 window,
15216 cx,
15217 |s| {
15218 s.select(new_selections);
15219 },
15220 );
15221 }
15222 }
15223
15224 pub fn select_prev_syntax_node(
15225 &mut self,
15226 _: &SelectPreviousSyntaxNode,
15227 window: &mut Window,
15228 cx: &mut Context<Self>,
15229 ) {
15230 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15231 if old_selections.is_empty() {
15232 return;
15233 }
15234
15235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15236
15237 let buffer = self.buffer.read(cx).snapshot(cx);
15238 let mut selected_sibling = false;
15239
15240 let new_selections = old_selections
15241 .iter()
15242 .map(|selection| {
15243 let old_range = selection.start..selection.end;
15244
15245 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15246 let new_range = node.byte_range();
15247 selected_sibling = true;
15248 Selection {
15249 id: selection.id,
15250 start: new_range.start,
15251 end: new_range.end,
15252 goal: SelectionGoal::None,
15253 reversed: selection.reversed,
15254 }
15255 } else {
15256 selection.clone()
15257 }
15258 })
15259 .collect::<Vec<_>>();
15260
15261 if selected_sibling {
15262 self.change_selections(
15263 SelectionEffects::scroll(Autoscroll::fit()),
15264 window,
15265 cx,
15266 |s| {
15267 s.select(new_selections);
15268 },
15269 );
15270 }
15271 }
15272
15273 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15274 if !EditorSettings::get_global(cx).gutter.runnables {
15275 self.clear_tasks();
15276 return Task::ready(());
15277 }
15278 let project = self.project().map(Entity::downgrade);
15279 let task_sources = self.lsp_task_sources(cx);
15280 let multi_buffer = self.buffer.downgrade();
15281 cx.spawn_in(window, async move |editor, cx| {
15282 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15283 let Some(project) = project.and_then(|p| p.upgrade()) else {
15284 return;
15285 };
15286 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15287 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15288 }) else {
15289 return;
15290 };
15291
15292 let hide_runnables = project
15293 .update(cx, |project, _| project.is_via_collab())
15294 .unwrap_or(true);
15295 if hide_runnables {
15296 return;
15297 }
15298 let new_rows =
15299 cx.background_spawn({
15300 let snapshot = display_snapshot.clone();
15301 async move {
15302 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15303 }
15304 })
15305 .await;
15306 let Ok(lsp_tasks) =
15307 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15308 else {
15309 return;
15310 };
15311 let lsp_tasks = lsp_tasks.await;
15312
15313 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15314 lsp_tasks
15315 .into_iter()
15316 .flat_map(|(kind, tasks)| {
15317 tasks.into_iter().filter_map(move |(location, task)| {
15318 Some((kind.clone(), location?, task))
15319 })
15320 })
15321 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15322 let buffer = location.target.buffer;
15323 let buffer_snapshot = buffer.read(cx).snapshot();
15324 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15325 |(excerpt_id, snapshot, _)| {
15326 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15327 display_snapshot
15328 .buffer_snapshot
15329 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15330 } else {
15331 None
15332 }
15333 },
15334 );
15335 if let Some(offset) = offset {
15336 let task_buffer_range =
15337 location.target.range.to_point(&buffer_snapshot);
15338 let context_buffer_range =
15339 task_buffer_range.to_offset(&buffer_snapshot);
15340 let context_range = BufferOffset(context_buffer_range.start)
15341 ..BufferOffset(context_buffer_range.end);
15342
15343 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15344 .or_insert_with(|| RunnableTasks {
15345 templates: Vec::new(),
15346 offset,
15347 column: task_buffer_range.start.column,
15348 extra_variables: HashMap::default(),
15349 context_range,
15350 })
15351 .templates
15352 .push((kind, task.original_task().clone()));
15353 }
15354
15355 acc
15356 })
15357 }) else {
15358 return;
15359 };
15360
15361 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15362 buffer.language_settings(cx).tasks.prefer_lsp
15363 }) else {
15364 return;
15365 };
15366
15367 let rows = Self::runnable_rows(
15368 project,
15369 display_snapshot,
15370 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15371 new_rows,
15372 cx.clone(),
15373 )
15374 .await;
15375 editor
15376 .update(cx, |editor, _| {
15377 editor.clear_tasks();
15378 for (key, mut value) in rows {
15379 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15380 value.templates.extend(lsp_tasks.templates);
15381 }
15382
15383 editor.insert_tasks(key, value);
15384 }
15385 for (key, value) in lsp_tasks_by_rows {
15386 editor.insert_tasks(key, value);
15387 }
15388 })
15389 .ok();
15390 })
15391 }
15392 fn fetch_runnable_ranges(
15393 snapshot: &DisplaySnapshot,
15394 range: Range<Anchor>,
15395 ) -> Vec<language::RunnableRange> {
15396 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15397 }
15398
15399 fn runnable_rows(
15400 project: Entity<Project>,
15401 snapshot: DisplaySnapshot,
15402 prefer_lsp: bool,
15403 runnable_ranges: Vec<RunnableRange>,
15404 cx: AsyncWindowContext,
15405 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15406 cx.spawn(async move |cx| {
15407 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15408 for mut runnable in runnable_ranges {
15409 let Some(tasks) = cx
15410 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15411 .ok()
15412 else {
15413 continue;
15414 };
15415 let mut tasks = tasks.await;
15416
15417 if prefer_lsp {
15418 tasks.retain(|(task_kind, _)| {
15419 !matches!(task_kind, TaskSourceKind::Language { .. })
15420 });
15421 }
15422 if tasks.is_empty() {
15423 continue;
15424 }
15425
15426 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15427 let Some(row) = snapshot
15428 .buffer_snapshot
15429 .buffer_line_for_row(MultiBufferRow(point.row))
15430 .map(|(_, range)| range.start.row)
15431 else {
15432 continue;
15433 };
15434
15435 let context_range =
15436 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15437 runnable_rows.push((
15438 (runnable.buffer_id, row),
15439 RunnableTasks {
15440 templates: tasks,
15441 offset: snapshot
15442 .buffer_snapshot
15443 .anchor_before(runnable.run_range.start),
15444 context_range,
15445 column: point.column,
15446 extra_variables: runnable.extra_captures,
15447 },
15448 ));
15449 }
15450 runnable_rows
15451 })
15452 }
15453
15454 fn templates_with_tags(
15455 project: &Entity<Project>,
15456 runnable: &mut Runnable,
15457 cx: &mut App,
15458 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15459 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15460 let (worktree_id, file) = project
15461 .buffer_for_id(runnable.buffer, cx)
15462 .and_then(|buffer| buffer.read(cx).file())
15463 .map(|file| (file.worktree_id(cx), file.clone()))
15464 .unzip();
15465
15466 (
15467 project.task_store().read(cx).task_inventory().cloned(),
15468 worktree_id,
15469 file,
15470 )
15471 });
15472
15473 let tags = mem::take(&mut runnable.tags);
15474 let language = runnable.language.clone();
15475 cx.spawn(async move |cx| {
15476 let mut templates_with_tags = Vec::new();
15477 if let Some(inventory) = inventory {
15478 for RunnableTag(tag) in tags {
15479 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15480 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15481 }) else {
15482 return templates_with_tags;
15483 };
15484 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15485 move |(_, template)| {
15486 template.tags.iter().any(|source_tag| source_tag == &tag)
15487 },
15488 ));
15489 }
15490 }
15491 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15492
15493 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15494 // Strongest source wins; if we have worktree tag binding, prefer that to
15495 // global and language bindings;
15496 // if we have a global binding, prefer that to language binding.
15497 let first_mismatch = templates_with_tags
15498 .iter()
15499 .position(|(tag_source, _)| tag_source != leading_tag_source);
15500 if let Some(index) = first_mismatch {
15501 templates_with_tags.truncate(index);
15502 }
15503 }
15504
15505 templates_with_tags
15506 })
15507 }
15508
15509 pub fn move_to_enclosing_bracket(
15510 &mut self,
15511 _: &MoveToEnclosingBracket,
15512 window: &mut Window,
15513 cx: &mut Context<Self>,
15514 ) {
15515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15516 self.change_selections(Default::default(), window, cx, |s| {
15517 s.move_offsets_with(|snapshot, selection| {
15518 let Some(enclosing_bracket_ranges) =
15519 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15520 else {
15521 return;
15522 };
15523
15524 let mut best_length = usize::MAX;
15525 let mut best_inside = false;
15526 let mut best_in_bracket_range = false;
15527 let mut best_destination = None;
15528 for (open, close) in enclosing_bracket_ranges {
15529 let close = close.to_inclusive();
15530 let length = close.end() - open.start;
15531 let inside = selection.start >= open.end && selection.end <= *close.start();
15532 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15533 || close.contains(&selection.head());
15534
15535 // If best is next to a bracket and current isn't, skip
15536 if !in_bracket_range && best_in_bracket_range {
15537 continue;
15538 }
15539
15540 // Prefer smaller lengths unless best is inside and current isn't
15541 if length > best_length && (best_inside || !inside) {
15542 continue;
15543 }
15544
15545 best_length = length;
15546 best_inside = inside;
15547 best_in_bracket_range = in_bracket_range;
15548 best_destination = Some(
15549 if close.contains(&selection.start) && close.contains(&selection.end) {
15550 if inside { open.end } else { open.start }
15551 } else if inside {
15552 *close.start()
15553 } else {
15554 *close.end()
15555 },
15556 );
15557 }
15558
15559 if let Some(destination) = best_destination {
15560 selection.collapse_to(destination, SelectionGoal::None);
15561 }
15562 })
15563 });
15564 }
15565
15566 pub fn undo_selection(
15567 &mut self,
15568 _: &UndoSelection,
15569 window: &mut Window,
15570 cx: &mut Context<Self>,
15571 ) {
15572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15573 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15574 self.selection_history.mode = SelectionHistoryMode::Undoing;
15575 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15576 this.end_selection(window, cx);
15577 this.change_selections(
15578 SelectionEffects::scroll(Autoscroll::newest()),
15579 window,
15580 cx,
15581 |s| s.select_anchors(entry.selections.to_vec()),
15582 );
15583 });
15584 self.selection_history.mode = SelectionHistoryMode::Normal;
15585
15586 self.select_next_state = entry.select_next_state;
15587 self.select_prev_state = entry.select_prev_state;
15588 self.add_selections_state = entry.add_selections_state;
15589 }
15590 }
15591
15592 pub fn redo_selection(
15593 &mut self,
15594 _: &RedoSelection,
15595 window: &mut Window,
15596 cx: &mut Context<Self>,
15597 ) {
15598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15599 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15600 self.selection_history.mode = SelectionHistoryMode::Redoing;
15601 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15602 this.end_selection(window, cx);
15603 this.change_selections(
15604 SelectionEffects::scroll(Autoscroll::newest()),
15605 window,
15606 cx,
15607 |s| s.select_anchors(entry.selections.to_vec()),
15608 );
15609 });
15610 self.selection_history.mode = SelectionHistoryMode::Normal;
15611
15612 self.select_next_state = entry.select_next_state;
15613 self.select_prev_state = entry.select_prev_state;
15614 self.add_selections_state = entry.add_selections_state;
15615 }
15616 }
15617
15618 pub fn expand_excerpts(
15619 &mut self,
15620 action: &ExpandExcerpts,
15621 _: &mut Window,
15622 cx: &mut Context<Self>,
15623 ) {
15624 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15625 }
15626
15627 pub fn expand_excerpts_down(
15628 &mut self,
15629 action: &ExpandExcerptsDown,
15630 _: &mut Window,
15631 cx: &mut Context<Self>,
15632 ) {
15633 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15634 }
15635
15636 pub fn expand_excerpts_up(
15637 &mut self,
15638 action: &ExpandExcerptsUp,
15639 _: &mut Window,
15640 cx: &mut Context<Self>,
15641 ) {
15642 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15643 }
15644
15645 pub fn expand_excerpts_for_direction(
15646 &mut self,
15647 lines: u32,
15648 direction: ExpandExcerptDirection,
15649
15650 cx: &mut Context<Self>,
15651 ) {
15652 let selections = self.selections.disjoint_anchors();
15653
15654 let lines = if lines == 0 {
15655 EditorSettings::get_global(cx).expand_excerpt_lines
15656 } else {
15657 lines
15658 };
15659
15660 self.buffer.update(cx, |buffer, cx| {
15661 let snapshot = buffer.snapshot(cx);
15662 let mut excerpt_ids = selections
15663 .iter()
15664 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15665 .collect::<Vec<_>>();
15666 excerpt_ids.sort();
15667 excerpt_ids.dedup();
15668 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15669 })
15670 }
15671
15672 pub fn expand_excerpt(
15673 &mut self,
15674 excerpt: ExcerptId,
15675 direction: ExpandExcerptDirection,
15676 window: &mut Window,
15677 cx: &mut Context<Self>,
15678 ) {
15679 let current_scroll_position = self.scroll_position(cx);
15680 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15681 let mut should_scroll_up = false;
15682
15683 if direction == ExpandExcerptDirection::Down {
15684 let multi_buffer = self.buffer.read(cx);
15685 let snapshot = multi_buffer.snapshot(cx);
15686 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15687 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15688 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15689 {
15690 let buffer_snapshot = buffer.read(cx).snapshot();
15691 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15692 let last_row = buffer_snapshot.max_point().row;
15693 let lines_below = last_row.saturating_sub(excerpt_end_row);
15694 should_scroll_up = lines_below >= lines_to_expand;
15695 }
15696 }
15697
15698 self.buffer.update(cx, |buffer, cx| {
15699 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15700 });
15701
15702 if should_scroll_up {
15703 let new_scroll_position =
15704 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15705 self.set_scroll_position(new_scroll_position, window, cx);
15706 }
15707 }
15708
15709 pub fn go_to_singleton_buffer_point(
15710 &mut self,
15711 point: Point,
15712 window: &mut Window,
15713 cx: &mut Context<Self>,
15714 ) {
15715 self.go_to_singleton_buffer_range(point..point, window, cx);
15716 }
15717
15718 pub fn go_to_singleton_buffer_range(
15719 &mut self,
15720 range: Range<Point>,
15721 window: &mut Window,
15722 cx: &mut Context<Self>,
15723 ) {
15724 let multibuffer = self.buffer().read(cx);
15725 let Some(buffer) = multibuffer.as_singleton() else {
15726 return;
15727 };
15728 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15729 return;
15730 };
15731 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15732 return;
15733 };
15734 self.change_selections(
15735 SelectionEffects::default().nav_history(true),
15736 window,
15737 cx,
15738 |s| s.select_anchor_ranges([start..end]),
15739 );
15740 }
15741
15742 pub fn go_to_diagnostic(
15743 &mut self,
15744 action: &GoToDiagnostic,
15745 window: &mut Window,
15746 cx: &mut Context<Self>,
15747 ) {
15748 if !self.diagnostics_enabled() {
15749 return;
15750 }
15751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15752 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15753 }
15754
15755 pub fn go_to_prev_diagnostic(
15756 &mut self,
15757 action: &GoToPreviousDiagnostic,
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::Prev, action.severity, window, cx)
15766 }
15767
15768 pub fn go_to_diagnostic_impl(
15769 &mut self,
15770 direction: Direction,
15771 severity: GoToDiagnosticSeverityFilter,
15772 window: &mut Window,
15773 cx: &mut Context<Self>,
15774 ) {
15775 let buffer = self.buffer.read(cx).snapshot(cx);
15776 let selection = self.selections.newest::<usize>(cx);
15777
15778 let mut active_group_id = None;
15779 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15780 && active_group.active_range.start.to_offset(&buffer) == selection.start
15781 {
15782 active_group_id = Some(active_group.group_id);
15783 }
15784
15785 fn filtered(
15786 snapshot: EditorSnapshot,
15787 severity: GoToDiagnosticSeverityFilter,
15788 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15789 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15790 diagnostics
15791 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15792 .filter(|entry| entry.range.start != entry.range.end)
15793 .filter(|entry| !entry.diagnostic.is_unnecessary)
15794 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15795 }
15796
15797 let snapshot = self.snapshot(window, cx);
15798 let before = filtered(
15799 snapshot.clone(),
15800 severity,
15801 buffer
15802 .diagnostics_in_range(0..selection.start)
15803 .filter(|entry| entry.range.start <= selection.start),
15804 );
15805 let after = filtered(
15806 snapshot,
15807 severity,
15808 buffer
15809 .diagnostics_in_range(selection.start..buffer.len())
15810 .filter(|entry| entry.range.start >= selection.start),
15811 );
15812
15813 let mut found: Option<DiagnosticEntry<usize>> = None;
15814 if direction == Direction::Prev {
15815 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15816 {
15817 for diagnostic in prev_diagnostics.into_iter().rev() {
15818 if diagnostic.range.start != selection.start
15819 || active_group_id
15820 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15821 {
15822 found = Some(diagnostic);
15823 break 'outer;
15824 }
15825 }
15826 }
15827 } else {
15828 for diagnostic in after.chain(before) {
15829 if diagnostic.range.start != selection.start
15830 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15831 {
15832 found = Some(diagnostic);
15833 break;
15834 }
15835 }
15836 }
15837 let Some(next_diagnostic) = found else {
15838 return;
15839 };
15840
15841 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15842 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15843 return;
15844 };
15845 self.change_selections(Default::default(), window, cx, |s| {
15846 s.select_ranges(vec![
15847 next_diagnostic.range.start..next_diagnostic.range.start,
15848 ])
15849 });
15850 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15851 self.refresh_edit_prediction(false, true, window, cx);
15852 }
15853
15854 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15856 let snapshot = self.snapshot(window, cx);
15857 let selection = self.selections.newest::<Point>(cx);
15858 self.go_to_hunk_before_or_after_position(
15859 &snapshot,
15860 selection.head(),
15861 Direction::Next,
15862 window,
15863 cx,
15864 );
15865 }
15866
15867 pub fn go_to_hunk_before_or_after_position(
15868 &mut self,
15869 snapshot: &EditorSnapshot,
15870 position: Point,
15871 direction: Direction,
15872 window: &mut Window,
15873 cx: &mut Context<Editor>,
15874 ) {
15875 let row = if direction == Direction::Next {
15876 self.hunk_after_position(snapshot, position)
15877 .map(|hunk| hunk.row_range.start)
15878 } else {
15879 self.hunk_before_position(snapshot, position)
15880 };
15881
15882 if let Some(row) = row {
15883 let destination = Point::new(row.0, 0);
15884 let autoscroll = Autoscroll::center();
15885
15886 self.unfold_ranges(&[destination..destination], false, false, cx);
15887 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15888 s.select_ranges([destination..destination]);
15889 });
15890 }
15891 }
15892
15893 fn hunk_after_position(
15894 &mut self,
15895 snapshot: &EditorSnapshot,
15896 position: Point,
15897 ) -> Option<MultiBufferDiffHunk> {
15898 snapshot
15899 .buffer_snapshot
15900 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15901 .find(|hunk| hunk.row_range.start.0 > position.row)
15902 .or_else(|| {
15903 snapshot
15904 .buffer_snapshot
15905 .diff_hunks_in_range(Point::zero()..position)
15906 .find(|hunk| hunk.row_range.end.0 < position.row)
15907 })
15908 }
15909
15910 fn go_to_prev_hunk(
15911 &mut self,
15912 _: &GoToPreviousHunk,
15913 window: &mut Window,
15914 cx: &mut Context<Self>,
15915 ) {
15916 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15917 let snapshot = self.snapshot(window, cx);
15918 let selection = self.selections.newest::<Point>(cx);
15919 self.go_to_hunk_before_or_after_position(
15920 &snapshot,
15921 selection.head(),
15922 Direction::Prev,
15923 window,
15924 cx,
15925 );
15926 }
15927
15928 fn hunk_before_position(
15929 &mut self,
15930 snapshot: &EditorSnapshot,
15931 position: Point,
15932 ) -> Option<MultiBufferRow> {
15933 snapshot
15934 .buffer_snapshot
15935 .diff_hunk_before(position)
15936 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15937 }
15938
15939 fn go_to_next_change(
15940 &mut self,
15941 _: &GoToNextChange,
15942 window: &mut Window,
15943 cx: &mut Context<Self>,
15944 ) {
15945 if let Some(selections) = self
15946 .change_list
15947 .next_change(1, Direction::Next)
15948 .map(|s| s.to_vec())
15949 {
15950 self.change_selections(Default::default(), window, cx, |s| {
15951 let map = s.display_map();
15952 s.select_display_ranges(selections.iter().map(|a| {
15953 let point = a.to_display_point(&map);
15954 point..point
15955 }))
15956 })
15957 }
15958 }
15959
15960 fn go_to_previous_change(
15961 &mut self,
15962 _: &GoToPreviousChange,
15963 window: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) {
15966 if let Some(selections) = self
15967 .change_list
15968 .next_change(1, Direction::Prev)
15969 .map(|s| s.to_vec())
15970 {
15971 self.change_selections(Default::default(), window, cx, |s| {
15972 let map = s.display_map();
15973 s.select_display_ranges(selections.iter().map(|a| {
15974 let point = a.to_display_point(&map);
15975 point..point
15976 }))
15977 })
15978 }
15979 }
15980
15981 pub fn go_to_next_document_highlight(
15982 &mut self,
15983 _: &GoToNextDocumentHighlight,
15984 window: &mut Window,
15985 cx: &mut Context<Self>,
15986 ) {
15987 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
15988 }
15989
15990 pub fn go_to_prev_document_highlight(
15991 &mut self,
15992 _: &GoToPreviousDocumentHighlight,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
15997 }
15998
15999 pub fn go_to_document_highlight_before_or_after_position(
16000 &mut self,
16001 direction: Direction,
16002 window: &mut Window,
16003 cx: &mut Context<Editor>,
16004 ) {
16005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16006 let snapshot = self.snapshot(window, cx);
16007 let buffer = &snapshot.buffer_snapshot;
16008 let position = self.selections.newest::<Point>(cx).head();
16009 let anchor_position = buffer.anchor_after(position);
16010
16011 // Get all document highlights (both read and write)
16012 let mut all_highlights = Vec::new();
16013
16014 if let Some((_, read_highlights)) = self
16015 .background_highlights
16016 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16017 {
16018 all_highlights.extend(read_highlights.iter());
16019 }
16020
16021 if let Some((_, write_highlights)) = self
16022 .background_highlights
16023 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16024 {
16025 all_highlights.extend(write_highlights.iter());
16026 }
16027
16028 if all_highlights.is_empty() {
16029 return;
16030 }
16031
16032 // Sort highlights by position
16033 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16034
16035 let target_highlight = match direction {
16036 Direction::Next => {
16037 // Find the first highlight after the current position
16038 all_highlights
16039 .iter()
16040 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16041 }
16042 Direction::Prev => {
16043 // Find the last highlight before the current position
16044 all_highlights
16045 .iter()
16046 .rev()
16047 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16048 }
16049 };
16050
16051 if let Some(highlight) = target_highlight {
16052 let destination = highlight.start.to_point(buffer);
16053 let autoscroll = Autoscroll::center();
16054
16055 self.unfold_ranges(&[destination..destination], false, false, cx);
16056 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16057 s.select_ranges([destination..destination]);
16058 });
16059 }
16060 }
16061
16062 fn go_to_line<T: 'static>(
16063 &mut self,
16064 position: Anchor,
16065 highlight_color: Option<Hsla>,
16066 window: &mut Window,
16067 cx: &mut Context<Self>,
16068 ) {
16069 let snapshot = self.snapshot(window, cx).display_snapshot;
16070 let position = position.to_point(&snapshot.buffer_snapshot);
16071 let start = snapshot
16072 .buffer_snapshot
16073 .clip_point(Point::new(position.row, 0), Bias::Left);
16074 let end = start + Point::new(1, 0);
16075 let start = snapshot.buffer_snapshot.anchor_before(start);
16076 let end = snapshot.buffer_snapshot.anchor_before(end);
16077
16078 self.highlight_rows::<T>(
16079 start..end,
16080 highlight_color
16081 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16082 Default::default(),
16083 cx,
16084 );
16085
16086 if self.buffer.read(cx).is_singleton() {
16087 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16088 }
16089 }
16090
16091 pub fn go_to_definition(
16092 &mut self,
16093 _: &GoToDefinition,
16094 window: &mut Window,
16095 cx: &mut Context<Self>,
16096 ) -> Task<Result<Navigated>> {
16097 let definition =
16098 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16099 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16100 cx.spawn_in(window, async move |editor, cx| {
16101 if definition.await? == Navigated::Yes {
16102 return Ok(Navigated::Yes);
16103 }
16104 match fallback_strategy {
16105 GoToDefinitionFallback::None => Ok(Navigated::No),
16106 GoToDefinitionFallback::FindAllReferences => {
16107 match editor.update_in(cx, |editor, window, cx| {
16108 editor.find_all_references(&FindAllReferences, window, cx)
16109 })? {
16110 Some(references) => references.await,
16111 None => Ok(Navigated::No),
16112 }
16113 }
16114 }
16115 })
16116 }
16117
16118 pub fn go_to_declaration(
16119 &mut self,
16120 _: &GoToDeclaration,
16121 window: &mut Window,
16122 cx: &mut Context<Self>,
16123 ) -> Task<Result<Navigated>> {
16124 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16125 }
16126
16127 pub fn go_to_declaration_split(
16128 &mut self,
16129 _: &GoToDeclaration,
16130 window: &mut Window,
16131 cx: &mut Context<Self>,
16132 ) -> Task<Result<Navigated>> {
16133 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16134 }
16135
16136 pub fn go_to_implementation(
16137 &mut self,
16138 _: &GoToImplementation,
16139 window: &mut Window,
16140 cx: &mut Context<Self>,
16141 ) -> Task<Result<Navigated>> {
16142 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16143 }
16144
16145 pub fn go_to_implementation_split(
16146 &mut self,
16147 _: &GoToImplementationSplit,
16148 window: &mut Window,
16149 cx: &mut Context<Self>,
16150 ) -> Task<Result<Navigated>> {
16151 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16152 }
16153
16154 pub fn go_to_type_definition(
16155 &mut self,
16156 _: &GoToTypeDefinition,
16157 window: &mut Window,
16158 cx: &mut Context<Self>,
16159 ) -> Task<Result<Navigated>> {
16160 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16161 }
16162
16163 pub fn go_to_definition_split(
16164 &mut self,
16165 _: &GoToDefinitionSplit,
16166 window: &mut Window,
16167 cx: &mut Context<Self>,
16168 ) -> Task<Result<Navigated>> {
16169 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16170 }
16171
16172 pub fn go_to_type_definition_split(
16173 &mut self,
16174 _: &GoToTypeDefinitionSplit,
16175 window: &mut Window,
16176 cx: &mut Context<Self>,
16177 ) -> Task<Result<Navigated>> {
16178 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16179 }
16180
16181 fn go_to_definition_of_kind(
16182 &mut self,
16183 kind: GotoDefinitionKind,
16184 split: bool,
16185 window: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) -> Task<Result<Navigated>> {
16188 let Some(provider) = self.semantics_provider.clone() else {
16189 return Task::ready(Ok(Navigated::No));
16190 };
16191 let head = self.selections.newest::<usize>(cx).head();
16192 let buffer = self.buffer.read(cx);
16193 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16194 return Task::ready(Ok(Navigated::No));
16195 };
16196 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16197 return Task::ready(Ok(Navigated::No));
16198 };
16199
16200 cx.spawn_in(window, async move |editor, cx| {
16201 let Some(definitions) = definitions.await? else {
16202 return Ok(Navigated::No);
16203 };
16204 let navigated = editor
16205 .update_in(cx, |editor, window, cx| {
16206 editor.navigate_to_hover_links(
16207 Some(kind),
16208 definitions
16209 .into_iter()
16210 .filter(|location| {
16211 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16212 })
16213 .map(HoverLink::Text)
16214 .collect::<Vec<_>>(),
16215 split,
16216 window,
16217 cx,
16218 )
16219 })?
16220 .await?;
16221 anyhow::Ok(navigated)
16222 })
16223 }
16224
16225 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16226 let selection = self.selections.newest_anchor();
16227 let head = selection.head();
16228 let tail = selection.tail();
16229
16230 let Some((buffer, start_position)) =
16231 self.buffer.read(cx).text_anchor_for_position(head, cx)
16232 else {
16233 return;
16234 };
16235
16236 let end_position = if head != tail {
16237 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16238 return;
16239 };
16240 Some(pos)
16241 } else {
16242 None
16243 };
16244
16245 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16246 let url = if let Some(end_pos) = end_position {
16247 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16248 } else {
16249 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16250 };
16251
16252 if let Some(url) = url {
16253 editor.update(cx, |_, cx| {
16254 cx.open_url(&url);
16255 })
16256 } else {
16257 Ok(())
16258 }
16259 });
16260
16261 url_finder.detach();
16262 }
16263
16264 pub fn open_selected_filename(
16265 &mut self,
16266 _: &OpenSelectedFilename,
16267 window: &mut Window,
16268 cx: &mut Context<Self>,
16269 ) {
16270 let Some(workspace) = self.workspace() else {
16271 return;
16272 };
16273
16274 let position = self.selections.newest_anchor().head();
16275
16276 let Some((buffer, buffer_position)) =
16277 self.buffer.read(cx).text_anchor_for_position(position, cx)
16278 else {
16279 return;
16280 };
16281
16282 let project = self.project.clone();
16283
16284 cx.spawn_in(window, async move |_, cx| {
16285 let result = find_file(&buffer, project, buffer_position, cx).await;
16286
16287 if let Some((_, path)) = result {
16288 workspace
16289 .update_in(cx, |workspace, window, cx| {
16290 workspace.open_resolved_path(path, window, cx)
16291 })?
16292 .await?;
16293 }
16294 anyhow::Ok(())
16295 })
16296 .detach();
16297 }
16298
16299 pub(crate) fn navigate_to_hover_links(
16300 &mut self,
16301 kind: Option<GotoDefinitionKind>,
16302 definitions: Vec<HoverLink>,
16303 split: bool,
16304 window: &mut Window,
16305 cx: &mut Context<Editor>,
16306 ) -> Task<Result<Navigated>> {
16307 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16308 let mut first_url_or_file = None;
16309 let definitions: Vec<_> = definitions
16310 .into_iter()
16311 .filter_map(|def| match def {
16312 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16313 HoverLink::InlayHint(lsp_location, server_id) => {
16314 let computation =
16315 self.compute_target_location(lsp_location, server_id, window, cx);
16316 Some(cx.background_spawn(computation))
16317 }
16318 HoverLink::Url(url) => {
16319 first_url_or_file = Some(Either::Left(url));
16320 None
16321 }
16322 HoverLink::File(path) => {
16323 first_url_or_file = Some(Either::Right(path));
16324 None
16325 }
16326 })
16327 .collect();
16328
16329 let workspace = self.workspace();
16330
16331 cx.spawn_in(window, async move |editor, acx| {
16332 let mut locations: Vec<Location> = future::join_all(definitions)
16333 .await
16334 .into_iter()
16335 .filter_map(|location| location.transpose())
16336 .collect::<Result<_>>()
16337 .context("location tasks")?;
16338
16339 if locations.len() > 1 {
16340 let Some(workspace) = workspace else {
16341 return Ok(Navigated::No);
16342 };
16343
16344 let tab_kind = match kind {
16345 Some(GotoDefinitionKind::Implementation) => "Implementations",
16346 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16347 Some(GotoDefinitionKind::Declaration) => "Declarations",
16348 Some(GotoDefinitionKind::Type) => "Types",
16349 };
16350 let title = editor
16351 .update_in(acx, |_, _, cx| {
16352 let target = locations
16353 .iter()
16354 .map(|location| {
16355 location
16356 .buffer
16357 .read(cx)
16358 .text_for_range(location.range.clone())
16359 .collect::<String>()
16360 })
16361 .filter(|text| !text.contains('\n'))
16362 .unique()
16363 .take(3)
16364 .join(", ");
16365 if target.is_empty() {
16366 tab_kind.to_owned()
16367 } else {
16368 format!("{tab_kind} for {target}")
16369 }
16370 })
16371 .context("buffer title")?;
16372
16373 let opened = workspace
16374 .update_in(acx, |workspace, window, cx| {
16375 Self::open_locations_in_multibuffer(
16376 workspace,
16377 locations,
16378 title,
16379 split,
16380 MultibufferSelectionMode::First,
16381 window,
16382 cx,
16383 )
16384 })
16385 .is_ok();
16386
16387 anyhow::Ok(Navigated::from_bool(opened))
16388 } else if locations.is_empty() {
16389 // If there is one url or file, open it directly
16390 match first_url_or_file {
16391 Some(Either::Left(url)) => {
16392 acx.update(|_, cx| cx.open_url(&url))?;
16393 Ok(Navigated::Yes)
16394 }
16395 Some(Either::Right(path)) => {
16396 let Some(workspace) = workspace else {
16397 return Ok(Navigated::No);
16398 };
16399
16400 workspace
16401 .update_in(acx, |workspace, window, cx| {
16402 workspace.open_resolved_path(path, window, cx)
16403 })?
16404 .await?;
16405 Ok(Navigated::Yes)
16406 }
16407 None => Ok(Navigated::No),
16408 }
16409 } else {
16410 let Some(workspace) = workspace else {
16411 return Ok(Navigated::No);
16412 };
16413
16414 let target = locations.pop().unwrap();
16415 editor.update_in(acx, |editor, window, cx| {
16416 let range = target.range.to_point(target.buffer.read(cx));
16417 let range = editor.range_for_match(&range);
16418 let range = collapse_multiline_range(range);
16419
16420 if !split
16421 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16422 {
16423 editor.go_to_singleton_buffer_range(range, window, cx);
16424 } else {
16425 let pane = workspace.read(cx).active_pane().clone();
16426 window.defer(cx, move |window, cx| {
16427 let target_editor: Entity<Self> =
16428 workspace.update(cx, |workspace, cx| {
16429 let pane = if split {
16430 workspace.adjacent_pane(window, cx)
16431 } else {
16432 workspace.active_pane().clone()
16433 };
16434
16435 workspace.open_project_item(
16436 pane,
16437 target.buffer.clone(),
16438 true,
16439 true,
16440 window,
16441 cx,
16442 )
16443 });
16444 target_editor.update(cx, |target_editor, cx| {
16445 // When selecting a definition in a different buffer, disable the nav history
16446 // to avoid creating a history entry at the previous cursor location.
16447 pane.update(cx, |pane, _| pane.disable_history());
16448 target_editor.go_to_singleton_buffer_range(range, window, cx);
16449 pane.update(cx, |pane, _| pane.enable_history());
16450 });
16451 });
16452 }
16453 Navigated::Yes
16454 })
16455 }
16456 })
16457 }
16458
16459 fn compute_target_location(
16460 &self,
16461 lsp_location: lsp::Location,
16462 server_id: LanguageServerId,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) -> Task<anyhow::Result<Option<Location>>> {
16466 let Some(project) = self.project.clone() else {
16467 return Task::ready(Ok(None));
16468 };
16469
16470 cx.spawn_in(window, async move |editor, cx| {
16471 let location_task = editor.update(cx, |_, cx| {
16472 project.update(cx, |project, cx| {
16473 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16474 })
16475 })?;
16476 let location = Some({
16477 let target_buffer_handle = location_task.await.context("open local buffer")?;
16478 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16479 let target_start = target_buffer
16480 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16481 let target_end = target_buffer
16482 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16483 target_buffer.anchor_after(target_start)
16484 ..target_buffer.anchor_before(target_end)
16485 })?;
16486 Location {
16487 buffer: target_buffer_handle,
16488 range,
16489 }
16490 });
16491 Ok(location)
16492 })
16493 }
16494
16495 pub fn find_all_references(
16496 &mut self,
16497 _: &FindAllReferences,
16498 window: &mut Window,
16499 cx: &mut Context<Self>,
16500 ) -> Option<Task<Result<Navigated>>> {
16501 let selection = self.selections.newest::<usize>(cx);
16502 let multi_buffer = self.buffer.read(cx);
16503 let head = selection.head();
16504
16505 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16506 let head_anchor = multi_buffer_snapshot.anchor_at(
16507 head,
16508 if head < selection.tail() {
16509 Bias::Right
16510 } else {
16511 Bias::Left
16512 },
16513 );
16514
16515 match self
16516 .find_all_references_task_sources
16517 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16518 {
16519 Ok(_) => {
16520 log::info!(
16521 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16522 );
16523 return None;
16524 }
16525 Err(i) => {
16526 self.find_all_references_task_sources.insert(i, head_anchor);
16527 }
16528 }
16529
16530 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16531 let workspace = self.workspace()?;
16532 let project = workspace.read(cx).project().clone();
16533 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16534 Some(cx.spawn_in(window, async move |editor, cx| {
16535 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16536 if let Ok(i) = editor
16537 .find_all_references_task_sources
16538 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16539 {
16540 editor.find_all_references_task_sources.remove(i);
16541 }
16542 });
16543
16544 let Some(locations) = references.await? else {
16545 return anyhow::Ok(Navigated::No);
16546 };
16547 if locations.is_empty() {
16548 return anyhow::Ok(Navigated::No);
16549 }
16550
16551 workspace.update_in(cx, |workspace, window, cx| {
16552 let target = locations
16553 .iter()
16554 .map(|location| {
16555 location
16556 .buffer
16557 .read(cx)
16558 .text_for_range(location.range.clone())
16559 .collect::<String>()
16560 })
16561 .filter(|text| !text.contains('\n'))
16562 .unique()
16563 .take(3)
16564 .join(", ");
16565 let title = if target.is_empty() {
16566 "References".to_owned()
16567 } else {
16568 format!("References to {target}")
16569 };
16570 Self::open_locations_in_multibuffer(
16571 workspace,
16572 locations,
16573 title,
16574 false,
16575 MultibufferSelectionMode::First,
16576 window,
16577 cx,
16578 );
16579 Navigated::Yes
16580 })
16581 }))
16582 }
16583
16584 /// Opens a multibuffer with the given project locations in it
16585 pub fn open_locations_in_multibuffer(
16586 workspace: &mut Workspace,
16587 mut locations: Vec<Location>,
16588 title: String,
16589 split: bool,
16590 multibuffer_selection_mode: MultibufferSelectionMode,
16591 window: &mut Window,
16592 cx: &mut Context<Workspace>,
16593 ) {
16594 if locations.is_empty() {
16595 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16596 return;
16597 }
16598
16599 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16600
16601 let mut locations = locations.into_iter().peekable();
16602 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16603 let capability = workspace.project().read(cx).capability();
16604
16605 // a key to find existing multibuffer editors with the same set of locations
16606 // to prevent us from opening more and more multibuffer tabs for searches and the like
16607 let mut key = (title.clone(), vec![]);
16608 let excerpt_buffer = cx.new(|cx| {
16609 let key = &mut key.1;
16610 let mut multibuffer = MultiBuffer::new(capability);
16611 while let Some(location) = locations.next() {
16612 let buffer = location.buffer.read(cx);
16613 let mut ranges_for_buffer = Vec::new();
16614 let range = location.range.to_point(buffer);
16615 ranges_for_buffer.push(range.clone());
16616
16617 while let Some(next_location) =
16618 locations.next_if(|next_location| next_location.buffer == location.buffer)
16619 {
16620 ranges_for_buffer.push(next_location.range.to_point(buffer));
16621 }
16622
16623 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16624 key.push((
16625 location.buffer.read(cx).remote_id(),
16626 ranges_for_buffer.clone(),
16627 ));
16628 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16629 PathKey::for_buffer(&location.buffer, cx),
16630 location.buffer.clone(),
16631 ranges_for_buffer,
16632 multibuffer_context_lines(cx),
16633 cx,
16634 );
16635 ranges.extend(new_ranges)
16636 }
16637
16638 multibuffer.with_title(title)
16639 });
16640 let existing = workspace.active_pane().update(cx, |pane, cx| {
16641 pane.items()
16642 .filter_map(|item| item.downcast::<Editor>())
16643 .find(|editor| {
16644 editor
16645 .read(cx)
16646 .lookup_key
16647 .as_ref()
16648 .and_then(|it| {
16649 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16650 })
16651 .is_some_and(|it| *it == key)
16652 })
16653 });
16654 let editor = existing.unwrap_or_else(|| {
16655 cx.new(|cx| {
16656 let mut editor = Editor::for_multibuffer(
16657 excerpt_buffer,
16658 Some(workspace.project().clone()),
16659 window,
16660 cx,
16661 );
16662 editor.lookup_key = Some(Box::new(key));
16663 editor
16664 })
16665 });
16666 editor.update(cx, |editor, cx| {
16667 match multibuffer_selection_mode {
16668 MultibufferSelectionMode::First => {
16669 if let Some(first_range) = ranges.first() {
16670 editor.change_selections(
16671 SelectionEffects::no_scroll(),
16672 window,
16673 cx,
16674 |selections| {
16675 selections.clear_disjoint();
16676 selections
16677 .select_anchor_ranges(std::iter::once(first_range.clone()));
16678 },
16679 );
16680 }
16681 editor.highlight_background::<Self>(
16682 &ranges,
16683 |theme| theme.colors().editor_highlighted_line_background,
16684 cx,
16685 );
16686 }
16687 MultibufferSelectionMode::All => {
16688 editor.change_selections(
16689 SelectionEffects::no_scroll(),
16690 window,
16691 cx,
16692 |selections| {
16693 selections.clear_disjoint();
16694 selections.select_anchor_ranges(ranges);
16695 },
16696 );
16697 }
16698 }
16699 editor.register_buffers_with_language_servers(cx);
16700 });
16701
16702 let item = Box::new(editor);
16703 let item_id = item.item_id();
16704
16705 if split {
16706 workspace.split_item(SplitDirection::Right, item, window, cx);
16707 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16708 let (preview_item_id, preview_item_idx) =
16709 workspace.active_pane().read_with(cx, |pane, _| {
16710 (pane.preview_item_id(), pane.preview_item_idx())
16711 });
16712
16713 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16714
16715 if let Some(preview_item_id) = preview_item_id {
16716 workspace.active_pane().update(cx, |pane, cx| {
16717 pane.remove_item(preview_item_id, false, false, window, cx);
16718 });
16719 }
16720 } else {
16721 workspace.add_item_to_active_pane(item, None, true, window, cx);
16722 }
16723 workspace.active_pane().update(cx, |pane, cx| {
16724 pane.set_preview_item_id(Some(item_id), cx);
16725 });
16726 }
16727
16728 pub fn rename(
16729 &mut self,
16730 _: &Rename,
16731 window: &mut Window,
16732 cx: &mut Context<Self>,
16733 ) -> Option<Task<Result<()>>> {
16734 use language::ToOffset as _;
16735
16736 let provider = self.semantics_provider.clone()?;
16737 let selection = self.selections.newest_anchor().clone();
16738 let (cursor_buffer, cursor_buffer_position) = self
16739 .buffer
16740 .read(cx)
16741 .text_anchor_for_position(selection.head(), cx)?;
16742 let (tail_buffer, cursor_buffer_position_end) = self
16743 .buffer
16744 .read(cx)
16745 .text_anchor_for_position(selection.tail(), cx)?;
16746 if tail_buffer != cursor_buffer {
16747 return None;
16748 }
16749
16750 let snapshot = cursor_buffer.read(cx).snapshot();
16751 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16752 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16753 let prepare_rename = provider
16754 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16755 .unwrap_or_else(|| Task::ready(Ok(None)));
16756 drop(snapshot);
16757
16758 Some(cx.spawn_in(window, async move |this, cx| {
16759 let rename_range = if let Some(range) = prepare_rename.await? {
16760 Some(range)
16761 } else {
16762 this.update(cx, |this, cx| {
16763 let buffer = this.buffer.read(cx).snapshot(cx);
16764 let mut buffer_highlights = this
16765 .document_highlights_for_position(selection.head(), &buffer)
16766 .filter(|highlight| {
16767 highlight.start.excerpt_id == selection.head().excerpt_id
16768 && highlight.end.excerpt_id == selection.head().excerpt_id
16769 });
16770 buffer_highlights
16771 .next()
16772 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16773 })?
16774 };
16775 if let Some(rename_range) = rename_range {
16776 this.update_in(cx, |this, window, cx| {
16777 let snapshot = cursor_buffer.read(cx).snapshot();
16778 let rename_buffer_range = rename_range.to_offset(&snapshot);
16779 let cursor_offset_in_rename_range =
16780 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16781 let cursor_offset_in_rename_range_end =
16782 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16783
16784 this.take_rename(false, window, cx);
16785 let buffer = this.buffer.read(cx).read(cx);
16786 let cursor_offset = selection.head().to_offset(&buffer);
16787 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16788 let rename_end = rename_start + rename_buffer_range.len();
16789 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16790 let mut old_highlight_id = None;
16791 let old_name: Arc<str> = buffer
16792 .chunks(rename_start..rename_end, true)
16793 .map(|chunk| {
16794 if old_highlight_id.is_none() {
16795 old_highlight_id = chunk.syntax_highlight_id;
16796 }
16797 chunk.text
16798 })
16799 .collect::<String>()
16800 .into();
16801
16802 drop(buffer);
16803
16804 // Position the selection in the rename editor so that it matches the current selection.
16805 this.show_local_selections = false;
16806 let rename_editor = cx.new(|cx| {
16807 let mut editor = Editor::single_line(window, cx);
16808 editor.buffer.update(cx, |buffer, cx| {
16809 buffer.edit([(0..0, old_name.clone())], None, cx)
16810 });
16811 let rename_selection_range = match cursor_offset_in_rename_range
16812 .cmp(&cursor_offset_in_rename_range_end)
16813 {
16814 Ordering::Equal => {
16815 editor.select_all(&SelectAll, window, cx);
16816 return editor;
16817 }
16818 Ordering::Less => {
16819 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16820 }
16821 Ordering::Greater => {
16822 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16823 }
16824 };
16825 if rename_selection_range.end > old_name.len() {
16826 editor.select_all(&SelectAll, window, cx);
16827 } else {
16828 editor.change_selections(Default::default(), window, cx, |s| {
16829 s.select_ranges([rename_selection_range]);
16830 });
16831 }
16832 editor
16833 });
16834 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16835 if e == &EditorEvent::Focused {
16836 cx.emit(EditorEvent::FocusedIn)
16837 }
16838 })
16839 .detach();
16840
16841 let write_highlights =
16842 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16843 let read_highlights =
16844 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16845 let ranges = write_highlights
16846 .iter()
16847 .flat_map(|(_, ranges)| ranges.iter())
16848 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16849 .cloned()
16850 .collect();
16851
16852 this.highlight_text::<Rename>(
16853 ranges,
16854 HighlightStyle {
16855 fade_out: Some(0.6),
16856 ..Default::default()
16857 },
16858 cx,
16859 );
16860 let rename_focus_handle = rename_editor.focus_handle(cx);
16861 window.focus(&rename_focus_handle);
16862 let block_id = this.insert_blocks(
16863 [BlockProperties {
16864 style: BlockStyle::Flex,
16865 placement: BlockPlacement::Below(range.start),
16866 height: Some(1),
16867 render: Arc::new({
16868 let rename_editor = rename_editor.clone();
16869 move |cx: &mut BlockContext| {
16870 let mut text_style = cx.editor_style.text.clone();
16871 if let Some(highlight_style) = old_highlight_id
16872 .and_then(|h| h.style(&cx.editor_style.syntax))
16873 {
16874 text_style = text_style.highlight(highlight_style);
16875 }
16876 div()
16877 .block_mouse_except_scroll()
16878 .pl(cx.anchor_x)
16879 .child(EditorElement::new(
16880 &rename_editor,
16881 EditorStyle {
16882 background: cx.theme().system().transparent,
16883 local_player: cx.editor_style.local_player,
16884 text: text_style,
16885 scrollbar_width: cx.editor_style.scrollbar_width,
16886 syntax: cx.editor_style.syntax.clone(),
16887 status: cx.editor_style.status.clone(),
16888 inlay_hints_style: HighlightStyle {
16889 font_weight: Some(FontWeight::BOLD),
16890 ..make_inlay_hints_style(cx.app)
16891 },
16892 edit_prediction_styles: make_suggestion_styles(
16893 cx.app,
16894 ),
16895 ..EditorStyle::default()
16896 },
16897 ))
16898 .into_any_element()
16899 }
16900 }),
16901 priority: 0,
16902 }],
16903 Some(Autoscroll::fit()),
16904 cx,
16905 )[0];
16906 this.pending_rename = Some(RenameState {
16907 range,
16908 old_name,
16909 editor: rename_editor,
16910 block_id,
16911 });
16912 })?;
16913 }
16914
16915 Ok(())
16916 }))
16917 }
16918
16919 pub fn confirm_rename(
16920 &mut self,
16921 _: &ConfirmRename,
16922 window: &mut Window,
16923 cx: &mut Context<Self>,
16924 ) -> Option<Task<Result<()>>> {
16925 let rename = self.take_rename(false, window, cx)?;
16926 let workspace = self.workspace()?.downgrade();
16927 let (buffer, start) = self
16928 .buffer
16929 .read(cx)
16930 .text_anchor_for_position(rename.range.start, cx)?;
16931 let (end_buffer, _) = self
16932 .buffer
16933 .read(cx)
16934 .text_anchor_for_position(rename.range.end, cx)?;
16935 if buffer != end_buffer {
16936 return None;
16937 }
16938
16939 let old_name = rename.old_name;
16940 let new_name = rename.editor.read(cx).text(cx);
16941
16942 let rename = self.semantics_provider.as_ref()?.perform_rename(
16943 &buffer,
16944 start,
16945 new_name.clone(),
16946 cx,
16947 )?;
16948
16949 Some(cx.spawn_in(window, async move |editor, cx| {
16950 let project_transaction = rename.await?;
16951 Self::open_project_transaction(
16952 &editor,
16953 workspace,
16954 project_transaction,
16955 format!("Rename: {} → {}", old_name, new_name),
16956 cx,
16957 )
16958 .await?;
16959
16960 editor.update(cx, |editor, cx| {
16961 editor.refresh_document_highlights(cx);
16962 })?;
16963 Ok(())
16964 }))
16965 }
16966
16967 fn take_rename(
16968 &mut self,
16969 moving_cursor: bool,
16970 window: &mut Window,
16971 cx: &mut Context<Self>,
16972 ) -> Option<RenameState> {
16973 let rename = self.pending_rename.take()?;
16974 if rename.editor.focus_handle(cx).is_focused(window) {
16975 window.focus(&self.focus_handle);
16976 }
16977
16978 self.remove_blocks(
16979 [rename.block_id].into_iter().collect(),
16980 Some(Autoscroll::fit()),
16981 cx,
16982 );
16983 self.clear_highlights::<Rename>(cx);
16984 self.show_local_selections = true;
16985
16986 if moving_cursor {
16987 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16988 editor.selections.newest::<usize>(cx).head()
16989 });
16990
16991 // Update the selection to match the position of the selection inside
16992 // the rename editor.
16993 let snapshot = self.buffer.read(cx).read(cx);
16994 let rename_range = rename.range.to_offset(&snapshot);
16995 let cursor_in_editor = snapshot
16996 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16997 .min(rename_range.end);
16998 drop(snapshot);
16999
17000 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17001 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17002 });
17003 } else {
17004 self.refresh_document_highlights(cx);
17005 }
17006
17007 Some(rename)
17008 }
17009
17010 pub fn pending_rename(&self) -> Option<&RenameState> {
17011 self.pending_rename.as_ref()
17012 }
17013
17014 fn format(
17015 &mut self,
17016 _: &Format,
17017 window: &mut Window,
17018 cx: &mut Context<Self>,
17019 ) -> Option<Task<Result<()>>> {
17020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17021
17022 let project = match &self.project {
17023 Some(project) => project.clone(),
17024 None => return None,
17025 };
17026
17027 Some(self.perform_format(
17028 project,
17029 FormatTrigger::Manual,
17030 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17031 window,
17032 cx,
17033 ))
17034 }
17035
17036 fn format_selections(
17037 &mut self,
17038 _: &FormatSelections,
17039 window: &mut Window,
17040 cx: &mut Context<Self>,
17041 ) -> Option<Task<Result<()>>> {
17042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17043
17044 let project = match &self.project {
17045 Some(project) => project.clone(),
17046 None => return None,
17047 };
17048
17049 let ranges = self
17050 .selections
17051 .all_adjusted(cx)
17052 .into_iter()
17053 .map(|selection| selection.range())
17054 .collect_vec();
17055
17056 Some(self.perform_format(
17057 project,
17058 FormatTrigger::Manual,
17059 FormatTarget::Ranges(ranges),
17060 window,
17061 cx,
17062 ))
17063 }
17064
17065 fn perform_format(
17066 &mut self,
17067 project: Entity<Project>,
17068 trigger: FormatTrigger,
17069 target: FormatTarget,
17070 window: &mut Window,
17071 cx: &mut Context<Self>,
17072 ) -> Task<Result<()>> {
17073 let buffer = self.buffer.clone();
17074 let (buffers, target) = match target {
17075 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17076 FormatTarget::Ranges(selection_ranges) => {
17077 let multi_buffer = buffer.read(cx);
17078 let snapshot = multi_buffer.read(cx);
17079 let mut buffers = HashSet::default();
17080 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17081 BTreeMap::new();
17082 for selection_range in selection_ranges {
17083 for (buffer, buffer_range, _) in
17084 snapshot.range_to_buffer_ranges(selection_range)
17085 {
17086 let buffer_id = buffer.remote_id();
17087 let start = buffer.anchor_before(buffer_range.start);
17088 let end = buffer.anchor_after(buffer_range.end);
17089 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17090 buffer_id_to_ranges
17091 .entry(buffer_id)
17092 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17093 .or_insert_with(|| vec![start..end]);
17094 }
17095 }
17096 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17097 }
17098 };
17099
17100 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17101 let selections_prev = transaction_id_prev
17102 .and_then(|transaction_id_prev| {
17103 // default to selections as they were after the last edit, if we have them,
17104 // instead of how they are now.
17105 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17106 // will take you back to where you made the last edit, instead of staying where you scrolled
17107 self.selection_history
17108 .transaction(transaction_id_prev)
17109 .map(|t| t.0.clone())
17110 })
17111 .unwrap_or_else(|| self.selections.disjoint_anchors());
17112
17113 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17114 let format = project.update(cx, |project, cx| {
17115 project.format(buffers, target, true, trigger, cx)
17116 });
17117
17118 cx.spawn_in(window, async move |editor, cx| {
17119 let transaction = futures::select_biased! {
17120 transaction = format.log_err().fuse() => transaction,
17121 () = timeout => {
17122 log::warn!("timed out waiting for formatting");
17123 None
17124 }
17125 };
17126
17127 buffer
17128 .update(cx, |buffer, cx| {
17129 if let Some(transaction) = transaction
17130 && !buffer.is_singleton()
17131 {
17132 buffer.push_transaction(&transaction.0, cx);
17133 }
17134 cx.notify();
17135 })
17136 .ok();
17137
17138 if let Some(transaction_id_now) =
17139 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17140 {
17141 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17142 if has_new_transaction {
17143 _ = editor.update(cx, |editor, _| {
17144 editor
17145 .selection_history
17146 .insert_transaction(transaction_id_now, selections_prev);
17147 });
17148 }
17149 }
17150
17151 Ok(())
17152 })
17153 }
17154
17155 fn organize_imports(
17156 &mut self,
17157 _: &OrganizeImports,
17158 window: &mut Window,
17159 cx: &mut Context<Self>,
17160 ) -> Option<Task<Result<()>>> {
17161 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17162 let project = match &self.project {
17163 Some(project) => project.clone(),
17164 None => return None,
17165 };
17166 Some(self.perform_code_action_kind(
17167 project,
17168 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17169 window,
17170 cx,
17171 ))
17172 }
17173
17174 fn perform_code_action_kind(
17175 &mut self,
17176 project: Entity<Project>,
17177 kind: CodeActionKind,
17178 window: &mut Window,
17179 cx: &mut Context<Self>,
17180 ) -> Task<Result<()>> {
17181 let buffer = self.buffer.clone();
17182 let buffers = buffer.read(cx).all_buffers();
17183 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17184 let apply_action = project.update(cx, |project, cx| {
17185 project.apply_code_action_kind(buffers, kind, true, cx)
17186 });
17187 cx.spawn_in(window, async move |_, cx| {
17188 let transaction = futures::select_biased! {
17189 () = timeout => {
17190 log::warn!("timed out waiting for executing code action");
17191 None
17192 }
17193 transaction = apply_action.log_err().fuse() => transaction,
17194 };
17195 buffer
17196 .update(cx, |buffer, cx| {
17197 // check if we need this
17198 if let Some(transaction) = transaction
17199 && !buffer.is_singleton()
17200 {
17201 buffer.push_transaction(&transaction.0, cx);
17202 }
17203 cx.notify();
17204 })
17205 .ok();
17206 Ok(())
17207 })
17208 }
17209
17210 pub fn restart_language_server(
17211 &mut self,
17212 _: &RestartLanguageServer,
17213 _: &mut Window,
17214 cx: &mut Context<Self>,
17215 ) {
17216 if let Some(project) = self.project.clone() {
17217 self.buffer.update(cx, |multi_buffer, cx| {
17218 project.update(cx, |project, cx| {
17219 project.restart_language_servers_for_buffers(
17220 multi_buffer.all_buffers().into_iter().collect(),
17221 HashSet::default(),
17222 cx,
17223 );
17224 });
17225 })
17226 }
17227 }
17228
17229 pub fn stop_language_server(
17230 &mut self,
17231 _: &StopLanguageServer,
17232 _: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) {
17235 if let Some(project) = self.project.clone() {
17236 self.buffer.update(cx, |multi_buffer, cx| {
17237 project.update(cx, |project, cx| {
17238 project.stop_language_servers_for_buffers(
17239 multi_buffer.all_buffers().into_iter().collect(),
17240 HashSet::default(),
17241 cx,
17242 );
17243 cx.emit(project::Event::RefreshInlayHints);
17244 });
17245 });
17246 }
17247 }
17248
17249 fn cancel_language_server_work(
17250 workspace: &mut Workspace,
17251 _: &actions::CancelLanguageServerWork,
17252 _: &mut Window,
17253 cx: &mut Context<Workspace>,
17254 ) {
17255 let project = workspace.project();
17256 let buffers = workspace
17257 .active_item(cx)
17258 .and_then(|item| item.act_as::<Editor>(cx))
17259 .map_or(HashSet::default(), |editor| {
17260 editor.read(cx).buffer.read(cx).all_buffers()
17261 });
17262 project.update(cx, |project, cx| {
17263 project.cancel_language_server_work_for_buffers(buffers, cx);
17264 });
17265 }
17266
17267 fn show_character_palette(
17268 &mut self,
17269 _: &ShowCharacterPalette,
17270 window: &mut Window,
17271 _: &mut Context<Self>,
17272 ) {
17273 window.show_character_palette();
17274 }
17275
17276 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17277 if !self.diagnostics_enabled() {
17278 return;
17279 }
17280
17281 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17282 let buffer = self.buffer.read(cx).snapshot(cx);
17283 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17284 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17285 let is_valid = buffer
17286 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17287 .any(|entry| {
17288 entry.diagnostic.is_primary
17289 && !entry.range.is_empty()
17290 && entry.range.start == primary_range_start
17291 && entry.diagnostic.message == active_diagnostics.active_message
17292 });
17293
17294 if !is_valid {
17295 self.dismiss_diagnostics(cx);
17296 }
17297 }
17298 }
17299
17300 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17301 match &self.active_diagnostics {
17302 ActiveDiagnostic::Group(group) => Some(group),
17303 _ => None,
17304 }
17305 }
17306
17307 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17308 if !self.diagnostics_enabled() {
17309 return;
17310 }
17311 self.dismiss_diagnostics(cx);
17312 self.active_diagnostics = ActiveDiagnostic::All;
17313 }
17314
17315 fn activate_diagnostics(
17316 &mut self,
17317 buffer_id: BufferId,
17318 diagnostic: DiagnosticEntry<usize>,
17319 window: &mut Window,
17320 cx: &mut Context<Self>,
17321 ) {
17322 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17323 return;
17324 }
17325 self.dismiss_diagnostics(cx);
17326 let snapshot = self.snapshot(window, cx);
17327 let buffer = self.buffer.read(cx).snapshot(cx);
17328 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17329 return;
17330 };
17331
17332 let diagnostic_group = buffer
17333 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17334 .collect::<Vec<_>>();
17335
17336 let blocks =
17337 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17338
17339 let blocks = self.display_map.update(cx, |display_map, cx| {
17340 display_map.insert_blocks(blocks, cx).into_iter().collect()
17341 });
17342 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17343 active_range: buffer.anchor_before(diagnostic.range.start)
17344 ..buffer.anchor_after(diagnostic.range.end),
17345 active_message: diagnostic.diagnostic.message.clone(),
17346 group_id: diagnostic.diagnostic.group_id,
17347 blocks,
17348 });
17349 cx.notify();
17350 }
17351
17352 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17353 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17354 return;
17355 };
17356
17357 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17358 if let ActiveDiagnostic::Group(group) = prev {
17359 self.display_map.update(cx, |display_map, cx| {
17360 display_map.remove_blocks(group.blocks, cx);
17361 });
17362 cx.notify();
17363 }
17364 }
17365
17366 /// Disable inline diagnostics rendering for this editor.
17367 pub fn disable_inline_diagnostics(&mut self) {
17368 self.inline_diagnostics_enabled = false;
17369 self.inline_diagnostics_update = Task::ready(());
17370 self.inline_diagnostics.clear();
17371 }
17372
17373 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17374 self.diagnostics_enabled = false;
17375 self.dismiss_diagnostics(cx);
17376 self.inline_diagnostics_update = Task::ready(());
17377 self.inline_diagnostics.clear();
17378 }
17379
17380 pub fn disable_word_completions(&mut self) {
17381 self.word_completions_enabled = false;
17382 }
17383
17384 pub fn diagnostics_enabled(&self) -> bool {
17385 self.diagnostics_enabled && self.mode.is_full()
17386 }
17387
17388 pub fn inline_diagnostics_enabled(&self) -> bool {
17389 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17390 }
17391
17392 pub fn show_inline_diagnostics(&self) -> bool {
17393 self.show_inline_diagnostics
17394 }
17395
17396 pub fn toggle_inline_diagnostics(
17397 &mut self,
17398 _: &ToggleInlineDiagnostics,
17399 window: &mut Window,
17400 cx: &mut Context<Editor>,
17401 ) {
17402 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17403 self.refresh_inline_diagnostics(false, window, cx);
17404 }
17405
17406 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17407 self.diagnostics_max_severity = severity;
17408 self.display_map.update(cx, |display_map, _| {
17409 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17410 });
17411 }
17412
17413 pub fn toggle_diagnostics(
17414 &mut self,
17415 _: &ToggleDiagnostics,
17416 window: &mut Window,
17417 cx: &mut Context<Editor>,
17418 ) {
17419 if !self.diagnostics_enabled() {
17420 return;
17421 }
17422
17423 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17424 EditorSettings::get_global(cx)
17425 .diagnostics_max_severity
17426 .filter(|severity| severity != &DiagnosticSeverity::Off)
17427 .unwrap_or(DiagnosticSeverity::Hint)
17428 } else {
17429 DiagnosticSeverity::Off
17430 };
17431 self.set_max_diagnostics_severity(new_severity, cx);
17432 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17433 self.active_diagnostics = ActiveDiagnostic::None;
17434 self.inline_diagnostics_update = Task::ready(());
17435 self.inline_diagnostics.clear();
17436 } else {
17437 self.refresh_inline_diagnostics(false, window, cx);
17438 }
17439
17440 cx.notify();
17441 }
17442
17443 pub fn toggle_minimap(
17444 &mut self,
17445 _: &ToggleMinimap,
17446 window: &mut Window,
17447 cx: &mut Context<Editor>,
17448 ) {
17449 if self.supports_minimap(cx) {
17450 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17451 }
17452 }
17453
17454 fn refresh_inline_diagnostics(
17455 &mut self,
17456 debounce: bool,
17457 window: &mut Window,
17458 cx: &mut Context<Self>,
17459 ) {
17460 let max_severity = ProjectSettings::get_global(cx)
17461 .diagnostics
17462 .inline
17463 .max_severity
17464 .unwrap_or(self.diagnostics_max_severity);
17465
17466 if !self.inline_diagnostics_enabled()
17467 || !self.show_inline_diagnostics
17468 || max_severity == DiagnosticSeverity::Off
17469 {
17470 self.inline_diagnostics_update = Task::ready(());
17471 self.inline_diagnostics.clear();
17472 return;
17473 }
17474
17475 let debounce_ms = ProjectSettings::get_global(cx)
17476 .diagnostics
17477 .inline
17478 .update_debounce_ms;
17479 let debounce = if debounce && debounce_ms > 0 {
17480 Some(Duration::from_millis(debounce_ms))
17481 } else {
17482 None
17483 };
17484 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17485 if let Some(debounce) = debounce {
17486 cx.background_executor().timer(debounce).await;
17487 }
17488 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17489 editor
17490 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17491 .ok()
17492 }) else {
17493 return;
17494 };
17495
17496 let new_inline_diagnostics = cx
17497 .background_spawn(async move {
17498 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17499 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17500 let message = diagnostic_entry
17501 .diagnostic
17502 .message
17503 .split_once('\n')
17504 .map(|(line, _)| line)
17505 .map(SharedString::new)
17506 .unwrap_or_else(|| {
17507 SharedString::from(diagnostic_entry.diagnostic.message)
17508 });
17509 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17510 let (Ok(i) | Err(i)) = inline_diagnostics
17511 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17512 inline_diagnostics.insert(
17513 i,
17514 (
17515 start_anchor,
17516 InlineDiagnostic {
17517 message,
17518 group_id: diagnostic_entry.diagnostic.group_id,
17519 start: diagnostic_entry.range.start.to_point(&snapshot),
17520 is_primary: diagnostic_entry.diagnostic.is_primary,
17521 severity: diagnostic_entry.diagnostic.severity,
17522 },
17523 ),
17524 );
17525 }
17526 inline_diagnostics
17527 })
17528 .await;
17529
17530 editor
17531 .update(cx, |editor, cx| {
17532 editor.inline_diagnostics = new_inline_diagnostics;
17533 cx.notify();
17534 })
17535 .ok();
17536 });
17537 }
17538
17539 fn pull_diagnostics(
17540 &mut self,
17541 buffer_id: Option<BufferId>,
17542 window: &Window,
17543 cx: &mut Context<Self>,
17544 ) -> Option<()> {
17545 if !self.mode().is_full() {
17546 return None;
17547 }
17548 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17549 .diagnostics
17550 .lsp_pull_diagnostics;
17551 if !pull_diagnostics_settings.enabled {
17552 return None;
17553 }
17554 let project = self.project()?.downgrade();
17555 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17556 let mut buffers = self.buffer.read(cx).all_buffers();
17557 if let Some(buffer_id) = buffer_id {
17558 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17559 }
17560
17561 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17562 cx.background_executor().timer(debounce).await;
17563
17564 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17565 buffers
17566 .into_iter()
17567 .filter_map(|buffer| {
17568 project
17569 .update(cx, |project, cx| {
17570 project.lsp_store().update(cx, |lsp_store, cx| {
17571 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17572 })
17573 })
17574 .ok()
17575 })
17576 .collect::<FuturesUnordered<_>>()
17577 }) else {
17578 return;
17579 };
17580
17581 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17582 match pull_task {
17583 Ok(()) => {
17584 if editor
17585 .update_in(cx, |editor, window, cx| {
17586 editor.update_diagnostics_state(window, cx);
17587 })
17588 .is_err()
17589 {
17590 return;
17591 }
17592 }
17593 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17594 }
17595 }
17596 });
17597
17598 Some(())
17599 }
17600
17601 pub fn set_selections_from_remote(
17602 &mut self,
17603 selections: Vec<Selection<Anchor>>,
17604 pending_selection: Option<Selection<Anchor>>,
17605 window: &mut Window,
17606 cx: &mut Context<Self>,
17607 ) {
17608 let old_cursor_position = self.selections.newest_anchor().head();
17609 self.selections.change_with(cx, |s| {
17610 s.select_anchors(selections);
17611 if let Some(pending_selection) = pending_selection {
17612 s.set_pending(pending_selection, SelectMode::Character);
17613 } else {
17614 s.clear_pending();
17615 }
17616 });
17617 self.selections_did_change(
17618 false,
17619 &old_cursor_position,
17620 SelectionEffects::default(),
17621 window,
17622 cx,
17623 );
17624 }
17625
17626 pub fn transact(
17627 &mut self,
17628 window: &mut Window,
17629 cx: &mut Context<Self>,
17630 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17631 ) -> Option<TransactionId> {
17632 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17633 this.start_transaction_at(Instant::now(), window, cx);
17634 update(this, window, cx);
17635 this.end_transaction_at(Instant::now(), cx)
17636 })
17637 }
17638
17639 pub fn start_transaction_at(
17640 &mut self,
17641 now: Instant,
17642 window: &mut Window,
17643 cx: &mut Context<Self>,
17644 ) -> Option<TransactionId> {
17645 self.end_selection(window, cx);
17646 if let Some(tx_id) = self
17647 .buffer
17648 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17649 {
17650 self.selection_history
17651 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17652 cx.emit(EditorEvent::TransactionBegun {
17653 transaction_id: tx_id,
17654 });
17655 Some(tx_id)
17656 } else {
17657 None
17658 }
17659 }
17660
17661 pub fn end_transaction_at(
17662 &mut self,
17663 now: Instant,
17664 cx: &mut Context<Self>,
17665 ) -> Option<TransactionId> {
17666 if let Some(transaction_id) = self
17667 .buffer
17668 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17669 {
17670 if let Some((_, end_selections)) =
17671 self.selection_history.transaction_mut(transaction_id)
17672 {
17673 *end_selections = Some(self.selections.disjoint_anchors());
17674 } else {
17675 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17676 }
17677
17678 cx.emit(EditorEvent::Edited { transaction_id });
17679 Some(transaction_id)
17680 } else {
17681 None
17682 }
17683 }
17684
17685 pub fn modify_transaction_selection_history(
17686 &mut self,
17687 transaction_id: TransactionId,
17688 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17689 ) -> bool {
17690 self.selection_history
17691 .transaction_mut(transaction_id)
17692 .map(modify)
17693 .is_some()
17694 }
17695
17696 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17697 if self.selection_mark_mode {
17698 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17699 s.move_with(|_, sel| {
17700 sel.collapse_to(sel.head(), SelectionGoal::None);
17701 });
17702 })
17703 }
17704 self.selection_mark_mode = true;
17705 cx.notify();
17706 }
17707
17708 pub fn swap_selection_ends(
17709 &mut self,
17710 _: &actions::SwapSelectionEnds,
17711 window: &mut Window,
17712 cx: &mut Context<Self>,
17713 ) {
17714 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17715 s.move_with(|_, sel| {
17716 if sel.start != sel.end {
17717 sel.reversed = !sel.reversed
17718 }
17719 });
17720 });
17721 self.request_autoscroll(Autoscroll::newest(), cx);
17722 cx.notify();
17723 }
17724
17725 pub fn toggle_focus(
17726 workspace: &mut Workspace,
17727 _: &actions::ToggleFocus,
17728 window: &mut Window,
17729 cx: &mut Context<Workspace>,
17730 ) {
17731 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17732 return;
17733 };
17734 workspace.activate_item(&item, true, true, window, cx);
17735 }
17736
17737 pub fn toggle_fold(
17738 &mut self,
17739 _: &actions::ToggleFold,
17740 window: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) {
17743 if self.is_singleton(cx) {
17744 let selection = self.selections.newest::<Point>(cx);
17745
17746 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17747 let range = if selection.is_empty() {
17748 let point = selection.head().to_display_point(&display_map);
17749 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17750 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17751 .to_point(&display_map);
17752 start..end
17753 } else {
17754 selection.range()
17755 };
17756 if display_map.folds_in_range(range).next().is_some() {
17757 self.unfold_lines(&Default::default(), window, cx)
17758 } else {
17759 self.fold(&Default::default(), window, cx)
17760 }
17761 } else {
17762 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17763 let buffer_ids: HashSet<_> = self
17764 .selections
17765 .disjoint_anchor_ranges()
17766 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17767 .collect();
17768
17769 let should_unfold = buffer_ids
17770 .iter()
17771 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17772
17773 for buffer_id in buffer_ids {
17774 if should_unfold {
17775 self.unfold_buffer(buffer_id, cx);
17776 } else {
17777 self.fold_buffer(buffer_id, cx);
17778 }
17779 }
17780 }
17781 }
17782
17783 pub fn toggle_fold_recursive(
17784 &mut self,
17785 _: &actions::ToggleFoldRecursive,
17786 window: &mut Window,
17787 cx: &mut Context<Self>,
17788 ) {
17789 let selection = self.selections.newest::<Point>(cx);
17790
17791 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17792 let range = if selection.is_empty() {
17793 let point = selection.head().to_display_point(&display_map);
17794 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17795 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17796 .to_point(&display_map);
17797 start..end
17798 } else {
17799 selection.range()
17800 };
17801 if display_map.folds_in_range(range).next().is_some() {
17802 self.unfold_recursive(&Default::default(), window, cx)
17803 } else {
17804 self.fold_recursive(&Default::default(), window, cx)
17805 }
17806 }
17807
17808 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17809 if self.is_singleton(cx) {
17810 let mut to_fold = Vec::new();
17811 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17812 let selections = self.selections.all_adjusted(cx);
17813
17814 for selection in selections {
17815 let range = selection.range().sorted();
17816 let buffer_start_row = range.start.row;
17817
17818 if range.start.row != range.end.row {
17819 let mut found = false;
17820 let mut row = range.start.row;
17821 while row <= range.end.row {
17822 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17823 {
17824 found = true;
17825 row = crease.range().end.row + 1;
17826 to_fold.push(crease);
17827 } else {
17828 row += 1
17829 }
17830 }
17831 if found {
17832 continue;
17833 }
17834 }
17835
17836 for row in (0..=range.start.row).rev() {
17837 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17838 && crease.range().end.row >= buffer_start_row
17839 {
17840 to_fold.push(crease);
17841 if row <= range.start.row {
17842 break;
17843 }
17844 }
17845 }
17846 }
17847
17848 self.fold_creases(to_fold, true, window, cx);
17849 } else {
17850 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17851 let buffer_ids = self
17852 .selections
17853 .disjoint_anchor_ranges()
17854 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17855 .collect::<HashSet<_>>();
17856 for buffer_id in buffer_ids {
17857 self.fold_buffer(buffer_id, cx);
17858 }
17859 }
17860 }
17861
17862 pub fn toggle_fold_all(
17863 &mut self,
17864 _: &actions::ToggleFoldAll,
17865 window: &mut Window,
17866 cx: &mut Context<Self>,
17867 ) {
17868 if self.buffer.read(cx).is_singleton() {
17869 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17870 let has_folds = display_map
17871 .folds_in_range(0..display_map.buffer_snapshot.len())
17872 .next()
17873 .is_some();
17874
17875 if has_folds {
17876 self.unfold_all(&actions::UnfoldAll, window, cx);
17877 } else {
17878 self.fold_all(&actions::FoldAll, window, cx);
17879 }
17880 } else {
17881 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17882 let should_unfold = buffer_ids
17883 .iter()
17884 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17885
17886 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17887 editor
17888 .update_in(cx, |editor, _, cx| {
17889 for buffer_id in buffer_ids {
17890 if should_unfold {
17891 editor.unfold_buffer(buffer_id, cx);
17892 } else {
17893 editor.fold_buffer(buffer_id, cx);
17894 }
17895 }
17896 })
17897 .ok();
17898 });
17899 }
17900 }
17901
17902 fn fold_at_level(
17903 &mut self,
17904 fold_at: &FoldAtLevel,
17905 window: &mut Window,
17906 cx: &mut Context<Self>,
17907 ) {
17908 if !self.buffer.read(cx).is_singleton() {
17909 return;
17910 }
17911
17912 let fold_at_level = fold_at.0;
17913 let snapshot = self.buffer.read(cx).snapshot(cx);
17914 let mut to_fold = Vec::new();
17915 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17916
17917 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17918 while start_row < end_row {
17919 match self
17920 .snapshot(window, cx)
17921 .crease_for_buffer_row(MultiBufferRow(start_row))
17922 {
17923 Some(crease) => {
17924 let nested_start_row = crease.range().start.row + 1;
17925 let nested_end_row = crease.range().end.row;
17926
17927 if current_level < fold_at_level {
17928 stack.push((nested_start_row, nested_end_row, current_level + 1));
17929 } else if current_level == fold_at_level {
17930 to_fold.push(crease);
17931 }
17932
17933 start_row = nested_end_row + 1;
17934 }
17935 None => start_row += 1,
17936 }
17937 }
17938 }
17939
17940 self.fold_creases(to_fold, true, window, cx);
17941 }
17942
17943 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17944 if self.buffer.read(cx).is_singleton() {
17945 let mut fold_ranges = Vec::new();
17946 let snapshot = self.buffer.read(cx).snapshot(cx);
17947
17948 for row in 0..snapshot.max_row().0 {
17949 if let Some(foldable_range) = self
17950 .snapshot(window, cx)
17951 .crease_for_buffer_row(MultiBufferRow(row))
17952 {
17953 fold_ranges.push(foldable_range);
17954 }
17955 }
17956
17957 self.fold_creases(fold_ranges, true, window, cx);
17958 } else {
17959 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17960 editor
17961 .update_in(cx, |editor, _, cx| {
17962 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17963 editor.fold_buffer(buffer_id, cx);
17964 }
17965 })
17966 .ok();
17967 });
17968 }
17969 }
17970
17971 pub fn fold_function_bodies(
17972 &mut self,
17973 _: &actions::FoldFunctionBodies,
17974 window: &mut Window,
17975 cx: &mut Context<Self>,
17976 ) {
17977 let snapshot = self.buffer.read(cx).snapshot(cx);
17978
17979 let ranges = snapshot
17980 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17981 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17982 .collect::<Vec<_>>();
17983
17984 let creases = ranges
17985 .into_iter()
17986 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17987 .collect();
17988
17989 self.fold_creases(creases, true, window, cx);
17990 }
17991
17992 pub fn fold_recursive(
17993 &mut self,
17994 _: &actions::FoldRecursive,
17995 window: &mut Window,
17996 cx: &mut Context<Self>,
17997 ) {
17998 let mut to_fold = Vec::new();
17999 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18000 let selections = self.selections.all_adjusted(cx);
18001
18002 for selection in selections {
18003 let range = selection.range().sorted();
18004 let buffer_start_row = range.start.row;
18005
18006 if range.start.row != range.end.row {
18007 let mut found = false;
18008 for row in range.start.row..=range.end.row {
18009 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18010 found = true;
18011 to_fold.push(crease);
18012 }
18013 }
18014 if found {
18015 continue;
18016 }
18017 }
18018
18019 for row in (0..=range.start.row).rev() {
18020 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18021 if crease.range().end.row >= buffer_start_row {
18022 to_fold.push(crease);
18023 } else {
18024 break;
18025 }
18026 }
18027 }
18028 }
18029
18030 self.fold_creases(to_fold, true, window, cx);
18031 }
18032
18033 pub fn fold_at(
18034 &mut self,
18035 buffer_row: MultiBufferRow,
18036 window: &mut Window,
18037 cx: &mut Context<Self>,
18038 ) {
18039 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18040
18041 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18042 let autoscroll = self
18043 .selections
18044 .all::<Point>(cx)
18045 .iter()
18046 .any(|selection| crease.range().overlaps(&selection.range()));
18047
18048 self.fold_creases(vec![crease], autoscroll, window, cx);
18049 }
18050 }
18051
18052 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18053 if self.is_singleton(cx) {
18054 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18055 let buffer = &display_map.buffer_snapshot;
18056 let selections = self.selections.all::<Point>(cx);
18057 let ranges = selections
18058 .iter()
18059 .map(|s| {
18060 let range = s.display_range(&display_map).sorted();
18061 let mut start = range.start.to_point(&display_map);
18062 let mut end = range.end.to_point(&display_map);
18063 start.column = 0;
18064 end.column = buffer.line_len(MultiBufferRow(end.row));
18065 start..end
18066 })
18067 .collect::<Vec<_>>();
18068
18069 self.unfold_ranges(&ranges, true, true, cx);
18070 } else {
18071 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18072 let buffer_ids = self
18073 .selections
18074 .disjoint_anchor_ranges()
18075 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18076 .collect::<HashSet<_>>();
18077 for buffer_id in buffer_ids {
18078 self.unfold_buffer(buffer_id, cx);
18079 }
18080 }
18081 }
18082
18083 pub fn unfold_recursive(
18084 &mut self,
18085 _: &UnfoldRecursive,
18086 _window: &mut Window,
18087 cx: &mut Context<Self>,
18088 ) {
18089 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18090 let selections = self.selections.all::<Point>(cx);
18091 let ranges = selections
18092 .iter()
18093 .map(|s| {
18094 let mut range = s.display_range(&display_map).sorted();
18095 *range.start.column_mut() = 0;
18096 *range.end.column_mut() = display_map.line_len(range.end.row());
18097 let start = range.start.to_point(&display_map);
18098 let end = range.end.to_point(&display_map);
18099 start..end
18100 })
18101 .collect::<Vec<_>>();
18102
18103 self.unfold_ranges(&ranges, true, true, cx);
18104 }
18105
18106 pub fn unfold_at(
18107 &mut self,
18108 buffer_row: MultiBufferRow,
18109 _window: &mut Window,
18110 cx: &mut Context<Self>,
18111 ) {
18112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18113
18114 let intersection_range = Point::new(buffer_row.0, 0)
18115 ..Point::new(
18116 buffer_row.0,
18117 display_map.buffer_snapshot.line_len(buffer_row),
18118 );
18119
18120 let autoscroll = self
18121 .selections
18122 .all::<Point>(cx)
18123 .iter()
18124 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18125
18126 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18127 }
18128
18129 pub fn unfold_all(
18130 &mut self,
18131 _: &actions::UnfoldAll,
18132 _window: &mut Window,
18133 cx: &mut Context<Self>,
18134 ) {
18135 if self.buffer.read(cx).is_singleton() {
18136 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18137 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18138 } else {
18139 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18140 editor
18141 .update(cx, |editor, cx| {
18142 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18143 editor.unfold_buffer(buffer_id, cx);
18144 }
18145 })
18146 .ok();
18147 });
18148 }
18149 }
18150
18151 pub fn fold_selected_ranges(
18152 &mut self,
18153 _: &FoldSelectedRanges,
18154 window: &mut Window,
18155 cx: &mut Context<Self>,
18156 ) {
18157 let selections = self.selections.all_adjusted(cx);
18158 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18159 let ranges = selections
18160 .into_iter()
18161 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18162 .collect::<Vec<_>>();
18163 self.fold_creases(ranges, true, window, cx);
18164 }
18165
18166 pub fn fold_ranges<T: ToOffset + Clone>(
18167 &mut self,
18168 ranges: Vec<Range<T>>,
18169 auto_scroll: bool,
18170 window: &mut Window,
18171 cx: &mut Context<Self>,
18172 ) {
18173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18174 let ranges = ranges
18175 .into_iter()
18176 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18177 .collect::<Vec<_>>();
18178 self.fold_creases(ranges, auto_scroll, window, cx);
18179 }
18180
18181 pub fn fold_creases<T: ToOffset + Clone>(
18182 &mut self,
18183 creases: Vec<Crease<T>>,
18184 auto_scroll: bool,
18185 _window: &mut Window,
18186 cx: &mut Context<Self>,
18187 ) {
18188 if creases.is_empty() {
18189 return;
18190 }
18191
18192 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18193
18194 if auto_scroll {
18195 self.request_autoscroll(Autoscroll::fit(), cx);
18196 }
18197
18198 cx.notify();
18199
18200 self.scrollbar_marker_state.dirty = true;
18201 self.folds_did_change(cx);
18202 }
18203
18204 /// Removes any folds whose ranges intersect any of the given ranges.
18205 pub fn unfold_ranges<T: ToOffset + Clone>(
18206 &mut self,
18207 ranges: &[Range<T>],
18208 inclusive: bool,
18209 auto_scroll: bool,
18210 cx: &mut Context<Self>,
18211 ) {
18212 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18213 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18214 });
18215 self.folds_did_change(cx);
18216 }
18217
18218 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18219 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18220 return;
18221 }
18222 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18223 self.display_map.update(cx, |display_map, cx| {
18224 display_map.fold_buffers([buffer_id], cx)
18225 });
18226 cx.emit(EditorEvent::BufferFoldToggled {
18227 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18228 folded: true,
18229 });
18230 cx.notify();
18231 }
18232
18233 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18234 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18235 return;
18236 }
18237 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18238 self.display_map.update(cx, |display_map, cx| {
18239 display_map.unfold_buffers([buffer_id], cx);
18240 });
18241 cx.emit(EditorEvent::BufferFoldToggled {
18242 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18243 folded: false,
18244 });
18245 cx.notify();
18246 }
18247
18248 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18249 self.display_map.read(cx).is_buffer_folded(buffer)
18250 }
18251
18252 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18253 self.display_map.read(cx).folded_buffers()
18254 }
18255
18256 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18257 self.display_map.update(cx, |display_map, cx| {
18258 display_map.disable_header_for_buffer(buffer_id, cx);
18259 });
18260 cx.notify();
18261 }
18262
18263 /// Removes any folds with the given ranges.
18264 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18265 &mut self,
18266 ranges: &[Range<T>],
18267 type_id: TypeId,
18268 auto_scroll: bool,
18269 cx: &mut Context<Self>,
18270 ) {
18271 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18272 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18273 });
18274 self.folds_did_change(cx);
18275 }
18276
18277 fn remove_folds_with<T: ToOffset + Clone>(
18278 &mut self,
18279 ranges: &[Range<T>],
18280 auto_scroll: bool,
18281 cx: &mut Context<Self>,
18282 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18283 ) {
18284 if ranges.is_empty() {
18285 return;
18286 }
18287
18288 let mut buffers_affected = HashSet::default();
18289 let multi_buffer = self.buffer().read(cx);
18290 for range in ranges {
18291 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18292 buffers_affected.insert(buffer.read(cx).remote_id());
18293 };
18294 }
18295
18296 self.display_map.update(cx, update);
18297
18298 if auto_scroll {
18299 self.request_autoscroll(Autoscroll::fit(), cx);
18300 }
18301
18302 cx.notify();
18303 self.scrollbar_marker_state.dirty = true;
18304 self.active_indent_guides_state.dirty = true;
18305 }
18306
18307 pub fn update_renderer_widths(
18308 &mut self,
18309 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18310 cx: &mut Context<Self>,
18311 ) -> bool {
18312 self.display_map
18313 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18314 }
18315
18316 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18317 self.display_map.read(cx).fold_placeholder.clone()
18318 }
18319
18320 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18321 self.buffer.update(cx, |buffer, cx| {
18322 buffer.set_all_diff_hunks_expanded(cx);
18323 });
18324 }
18325
18326 pub fn expand_all_diff_hunks(
18327 &mut self,
18328 _: &ExpandAllDiffHunks,
18329 _window: &mut Window,
18330 cx: &mut Context<Self>,
18331 ) {
18332 self.buffer.update(cx, |buffer, cx| {
18333 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18334 });
18335 }
18336
18337 pub fn toggle_selected_diff_hunks(
18338 &mut self,
18339 _: &ToggleSelectedDiffHunks,
18340 _window: &mut Window,
18341 cx: &mut Context<Self>,
18342 ) {
18343 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18344 self.toggle_diff_hunks_in_ranges(ranges, cx);
18345 }
18346
18347 pub fn diff_hunks_in_ranges<'a>(
18348 &'a self,
18349 ranges: &'a [Range<Anchor>],
18350 buffer: &'a MultiBufferSnapshot,
18351 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18352 ranges.iter().flat_map(move |range| {
18353 let end_excerpt_id = range.end.excerpt_id;
18354 let range = range.to_point(buffer);
18355 let mut peek_end = range.end;
18356 if range.end.row < buffer.max_row().0 {
18357 peek_end = Point::new(range.end.row + 1, 0);
18358 }
18359 buffer
18360 .diff_hunks_in_range(range.start..peek_end)
18361 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18362 })
18363 }
18364
18365 pub fn has_stageable_diff_hunks_in_ranges(
18366 &self,
18367 ranges: &[Range<Anchor>],
18368 snapshot: &MultiBufferSnapshot,
18369 ) -> bool {
18370 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18371 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18372 }
18373
18374 pub fn toggle_staged_selected_diff_hunks(
18375 &mut self,
18376 _: &::git::ToggleStaged,
18377 _: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 let snapshot = self.buffer.read(cx).snapshot(cx);
18381 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18382 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18383 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18384 }
18385
18386 pub fn set_render_diff_hunk_controls(
18387 &mut self,
18388 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18389 cx: &mut Context<Self>,
18390 ) {
18391 self.render_diff_hunk_controls = render_diff_hunk_controls;
18392 cx.notify();
18393 }
18394
18395 pub fn stage_and_next(
18396 &mut self,
18397 _: &::git::StageAndNext,
18398 window: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) {
18401 self.do_stage_or_unstage_and_next(true, window, cx);
18402 }
18403
18404 pub fn unstage_and_next(
18405 &mut self,
18406 _: &::git::UnstageAndNext,
18407 window: &mut Window,
18408 cx: &mut Context<Self>,
18409 ) {
18410 self.do_stage_or_unstage_and_next(false, window, cx);
18411 }
18412
18413 pub fn stage_or_unstage_diff_hunks(
18414 &mut self,
18415 stage: bool,
18416 ranges: Vec<Range<Anchor>>,
18417 cx: &mut Context<Self>,
18418 ) {
18419 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18420 cx.spawn(async move |this, cx| {
18421 task.await?;
18422 this.update(cx, |this, cx| {
18423 let snapshot = this.buffer.read(cx).snapshot(cx);
18424 let chunk_by = this
18425 .diff_hunks_in_ranges(&ranges, &snapshot)
18426 .chunk_by(|hunk| hunk.buffer_id);
18427 for (buffer_id, hunks) in &chunk_by {
18428 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18429 }
18430 })
18431 })
18432 .detach_and_log_err(cx);
18433 }
18434
18435 fn save_buffers_for_ranges_if_needed(
18436 &mut self,
18437 ranges: &[Range<Anchor>],
18438 cx: &mut Context<Editor>,
18439 ) -> Task<Result<()>> {
18440 let multibuffer = self.buffer.read(cx);
18441 let snapshot = multibuffer.read(cx);
18442 let buffer_ids: HashSet<_> = ranges
18443 .iter()
18444 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18445 .collect();
18446 drop(snapshot);
18447
18448 let mut buffers = HashSet::default();
18449 for buffer_id in buffer_ids {
18450 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18451 let buffer = buffer_entity.read(cx);
18452 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18453 {
18454 buffers.insert(buffer_entity);
18455 }
18456 }
18457 }
18458
18459 if let Some(project) = &self.project {
18460 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18461 } else {
18462 Task::ready(Ok(()))
18463 }
18464 }
18465
18466 fn do_stage_or_unstage_and_next(
18467 &mut self,
18468 stage: bool,
18469 window: &mut Window,
18470 cx: &mut Context<Self>,
18471 ) {
18472 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18473
18474 if ranges.iter().any(|range| range.start != range.end) {
18475 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18476 return;
18477 }
18478
18479 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18480 let snapshot = self.snapshot(window, cx);
18481 let position = self.selections.newest::<Point>(cx).head();
18482 let mut row = snapshot
18483 .buffer_snapshot
18484 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18485 .find(|hunk| hunk.row_range.start.0 > position.row)
18486 .map(|hunk| hunk.row_range.start);
18487
18488 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18489 // Outside of the project diff editor, wrap around to the beginning.
18490 if !all_diff_hunks_expanded {
18491 row = row.or_else(|| {
18492 snapshot
18493 .buffer_snapshot
18494 .diff_hunks_in_range(Point::zero()..position)
18495 .find(|hunk| hunk.row_range.end.0 < position.row)
18496 .map(|hunk| hunk.row_range.start)
18497 });
18498 }
18499
18500 if let Some(row) = row {
18501 let destination = Point::new(row.0, 0);
18502 let autoscroll = Autoscroll::center();
18503
18504 self.unfold_ranges(&[destination..destination], false, false, cx);
18505 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18506 s.select_ranges([destination..destination]);
18507 });
18508 }
18509 }
18510
18511 fn do_stage_or_unstage(
18512 &self,
18513 stage: bool,
18514 buffer_id: BufferId,
18515 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18516 cx: &mut App,
18517 ) -> Option<()> {
18518 let project = self.project()?;
18519 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18520 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18521 let buffer_snapshot = buffer.read(cx).snapshot();
18522 let file_exists = buffer_snapshot
18523 .file()
18524 .is_some_and(|file| file.disk_state().exists());
18525 diff.update(cx, |diff, cx| {
18526 diff.stage_or_unstage_hunks(
18527 stage,
18528 &hunks
18529 .map(|hunk| buffer_diff::DiffHunk {
18530 buffer_range: hunk.buffer_range,
18531 diff_base_byte_range: hunk.diff_base_byte_range,
18532 secondary_status: hunk.secondary_status,
18533 range: Point::zero()..Point::zero(), // unused
18534 })
18535 .collect::<Vec<_>>(),
18536 &buffer_snapshot,
18537 file_exists,
18538 cx,
18539 )
18540 });
18541 None
18542 }
18543
18544 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18545 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18546 self.buffer
18547 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18548 }
18549
18550 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18551 self.buffer.update(cx, |buffer, cx| {
18552 let ranges = vec![Anchor::min()..Anchor::max()];
18553 if !buffer.all_diff_hunks_expanded()
18554 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18555 {
18556 buffer.collapse_diff_hunks(ranges, cx);
18557 true
18558 } else {
18559 false
18560 }
18561 })
18562 }
18563
18564 fn toggle_diff_hunks_in_ranges(
18565 &mut self,
18566 ranges: Vec<Range<Anchor>>,
18567 cx: &mut Context<Editor>,
18568 ) {
18569 self.buffer.update(cx, |buffer, cx| {
18570 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18571 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18572 })
18573 }
18574
18575 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18576 self.buffer.update(cx, |buffer, cx| {
18577 let snapshot = buffer.snapshot(cx);
18578 let excerpt_id = range.end.excerpt_id;
18579 let point_range = range.to_point(&snapshot);
18580 let expand = !buffer.single_hunk_is_expanded(range, cx);
18581 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18582 })
18583 }
18584
18585 pub(crate) fn apply_all_diff_hunks(
18586 &mut self,
18587 _: &ApplyAllDiffHunks,
18588 window: &mut Window,
18589 cx: &mut Context<Self>,
18590 ) {
18591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18592
18593 let buffers = self.buffer.read(cx).all_buffers();
18594 for branch_buffer in buffers {
18595 branch_buffer.update(cx, |branch_buffer, cx| {
18596 branch_buffer.merge_into_base(Vec::new(), cx);
18597 });
18598 }
18599
18600 if let Some(project) = self.project.clone() {
18601 self.save(
18602 SaveOptions {
18603 format: true,
18604 autosave: false,
18605 },
18606 project,
18607 window,
18608 cx,
18609 )
18610 .detach_and_log_err(cx);
18611 }
18612 }
18613
18614 pub(crate) fn apply_selected_diff_hunks(
18615 &mut self,
18616 _: &ApplyDiffHunk,
18617 window: &mut Window,
18618 cx: &mut Context<Self>,
18619 ) {
18620 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18621 let snapshot = self.snapshot(window, cx);
18622 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18623 let mut ranges_by_buffer = HashMap::default();
18624 self.transact(window, cx, |editor, _window, cx| {
18625 for hunk in hunks {
18626 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18627 ranges_by_buffer
18628 .entry(buffer.clone())
18629 .or_insert_with(Vec::new)
18630 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18631 }
18632 }
18633
18634 for (buffer, ranges) in ranges_by_buffer {
18635 buffer.update(cx, |buffer, cx| {
18636 buffer.merge_into_base(ranges, cx);
18637 });
18638 }
18639 });
18640
18641 if let Some(project) = self.project.clone() {
18642 self.save(
18643 SaveOptions {
18644 format: true,
18645 autosave: false,
18646 },
18647 project,
18648 window,
18649 cx,
18650 )
18651 .detach_and_log_err(cx);
18652 }
18653 }
18654
18655 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18656 if hovered != self.gutter_hovered {
18657 self.gutter_hovered = hovered;
18658 cx.notify();
18659 }
18660 }
18661
18662 pub fn insert_blocks(
18663 &mut self,
18664 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18665 autoscroll: Option<Autoscroll>,
18666 cx: &mut Context<Self>,
18667 ) -> Vec<CustomBlockId> {
18668 let blocks = self
18669 .display_map
18670 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18671 if let Some(autoscroll) = autoscroll {
18672 self.request_autoscroll(autoscroll, cx);
18673 }
18674 cx.notify();
18675 blocks
18676 }
18677
18678 pub fn resize_blocks(
18679 &mut self,
18680 heights: HashMap<CustomBlockId, u32>,
18681 autoscroll: Option<Autoscroll>,
18682 cx: &mut Context<Self>,
18683 ) {
18684 self.display_map
18685 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18686 if let Some(autoscroll) = autoscroll {
18687 self.request_autoscroll(autoscroll, cx);
18688 }
18689 cx.notify();
18690 }
18691
18692 pub fn replace_blocks(
18693 &mut self,
18694 renderers: HashMap<CustomBlockId, RenderBlock>,
18695 autoscroll: Option<Autoscroll>,
18696 cx: &mut Context<Self>,
18697 ) {
18698 self.display_map
18699 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18700 if let Some(autoscroll) = autoscroll {
18701 self.request_autoscroll(autoscroll, cx);
18702 }
18703 cx.notify();
18704 }
18705
18706 pub fn remove_blocks(
18707 &mut self,
18708 block_ids: HashSet<CustomBlockId>,
18709 autoscroll: Option<Autoscroll>,
18710 cx: &mut Context<Self>,
18711 ) {
18712 self.display_map.update(cx, |display_map, cx| {
18713 display_map.remove_blocks(block_ids, cx)
18714 });
18715 if let Some(autoscroll) = autoscroll {
18716 self.request_autoscroll(autoscroll, cx);
18717 }
18718 cx.notify();
18719 }
18720
18721 pub fn row_for_block(
18722 &self,
18723 block_id: CustomBlockId,
18724 cx: &mut Context<Self>,
18725 ) -> Option<DisplayRow> {
18726 self.display_map
18727 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18728 }
18729
18730 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18731 self.focused_block = Some(focused_block);
18732 }
18733
18734 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18735 self.focused_block.take()
18736 }
18737
18738 pub fn insert_creases(
18739 &mut self,
18740 creases: impl IntoIterator<Item = Crease<Anchor>>,
18741 cx: &mut Context<Self>,
18742 ) -> Vec<CreaseId> {
18743 self.display_map
18744 .update(cx, |map, cx| map.insert_creases(creases, cx))
18745 }
18746
18747 pub fn remove_creases(
18748 &mut self,
18749 ids: impl IntoIterator<Item = CreaseId>,
18750 cx: &mut Context<Self>,
18751 ) -> Vec<(CreaseId, Range<Anchor>)> {
18752 self.display_map
18753 .update(cx, |map, cx| map.remove_creases(ids, cx))
18754 }
18755
18756 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18757 self.display_map
18758 .update(cx, |map, cx| map.snapshot(cx))
18759 .longest_row()
18760 }
18761
18762 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18763 self.display_map
18764 .update(cx, |map, cx| map.snapshot(cx))
18765 .max_point()
18766 }
18767
18768 pub fn text(&self, cx: &App) -> String {
18769 self.buffer.read(cx).read(cx).text()
18770 }
18771
18772 pub fn is_empty(&self, cx: &App) -> bool {
18773 self.buffer.read(cx).read(cx).is_empty()
18774 }
18775
18776 pub fn text_option(&self, cx: &App) -> Option<String> {
18777 let text = self.text(cx);
18778 let text = text.trim();
18779
18780 if text.is_empty() {
18781 return None;
18782 }
18783
18784 Some(text.to_string())
18785 }
18786
18787 pub fn set_text(
18788 &mut self,
18789 text: impl Into<Arc<str>>,
18790 window: &mut Window,
18791 cx: &mut Context<Self>,
18792 ) {
18793 self.transact(window, cx, |this, _, cx| {
18794 this.buffer
18795 .read(cx)
18796 .as_singleton()
18797 .expect("you can only call set_text on editors for singleton buffers")
18798 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18799 });
18800 }
18801
18802 pub fn display_text(&self, cx: &mut App) -> String {
18803 self.display_map
18804 .update(cx, |map, cx| map.snapshot(cx))
18805 .text()
18806 }
18807
18808 fn create_minimap(
18809 &self,
18810 minimap_settings: MinimapSettings,
18811 window: &mut Window,
18812 cx: &mut Context<Self>,
18813 ) -> Option<Entity<Self>> {
18814 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18815 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18816 }
18817
18818 fn initialize_new_minimap(
18819 &self,
18820 minimap_settings: MinimapSettings,
18821 window: &mut Window,
18822 cx: &mut Context<Self>,
18823 ) -> Entity<Self> {
18824 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18825
18826 let mut minimap = Editor::new_internal(
18827 EditorMode::Minimap {
18828 parent: cx.weak_entity(),
18829 },
18830 self.buffer.clone(),
18831 None,
18832 Some(self.display_map.clone()),
18833 window,
18834 cx,
18835 );
18836 minimap.scroll_manager.clone_state(&self.scroll_manager);
18837 minimap.set_text_style_refinement(TextStyleRefinement {
18838 font_size: Some(MINIMAP_FONT_SIZE),
18839 font_weight: Some(MINIMAP_FONT_WEIGHT),
18840 ..Default::default()
18841 });
18842 minimap.update_minimap_configuration(minimap_settings, cx);
18843 cx.new(|_| minimap)
18844 }
18845
18846 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18847 let current_line_highlight = minimap_settings
18848 .current_line_highlight
18849 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18850 self.set_current_line_highlight(Some(current_line_highlight));
18851 }
18852
18853 pub fn minimap(&self) -> Option<&Entity<Self>> {
18854 self.minimap
18855 .as_ref()
18856 .filter(|_| self.minimap_visibility.visible())
18857 }
18858
18859 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18860 let mut wrap_guides = smallvec![];
18861
18862 if self.show_wrap_guides == Some(false) {
18863 return wrap_guides;
18864 }
18865
18866 let settings = self.buffer.read(cx).language_settings(cx);
18867 if settings.show_wrap_guides {
18868 match self.soft_wrap_mode(cx) {
18869 SoftWrap::Column(soft_wrap) => {
18870 wrap_guides.push((soft_wrap as usize, true));
18871 }
18872 SoftWrap::Bounded(soft_wrap) => {
18873 wrap_guides.push((soft_wrap as usize, true));
18874 }
18875 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18876 }
18877 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18878 }
18879
18880 wrap_guides
18881 }
18882
18883 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18884 let settings = self.buffer.read(cx).language_settings(cx);
18885 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18886 match mode {
18887 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18888 SoftWrap::None
18889 }
18890 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18891 language_settings::SoftWrap::PreferredLineLength => {
18892 SoftWrap::Column(settings.preferred_line_length)
18893 }
18894 language_settings::SoftWrap::Bounded => {
18895 SoftWrap::Bounded(settings.preferred_line_length)
18896 }
18897 }
18898 }
18899
18900 pub fn set_soft_wrap_mode(
18901 &mut self,
18902 mode: language_settings::SoftWrap,
18903
18904 cx: &mut Context<Self>,
18905 ) {
18906 self.soft_wrap_mode_override = Some(mode);
18907 cx.notify();
18908 }
18909
18910 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18911 self.hard_wrap = hard_wrap;
18912 cx.notify();
18913 }
18914
18915 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18916 self.text_style_refinement = Some(style);
18917 }
18918
18919 /// called by the Element so we know what style we were most recently rendered with.
18920 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
18921 // We intentionally do not inform the display map about the minimap style
18922 // so that wrapping is not recalculated and stays consistent for the editor
18923 // and its linked minimap.
18924 if !self.mode.is_minimap() {
18925 let font = style.text.font();
18926 let font_size = style.text.font_size.to_pixels(window.rem_size());
18927 let display_map = self
18928 .placeholder_display_map
18929 .as_ref()
18930 .filter(|_| self.is_empty(cx))
18931 .unwrap_or(&self.display_map);
18932
18933 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
18934 }
18935 self.style = Some(style);
18936 }
18937
18938 pub fn style(&self) -> Option<&EditorStyle> {
18939 self.style.as_ref()
18940 }
18941
18942 // Called by the element. This method is not designed to be called outside of the editor
18943 // element's layout code because it does not notify when rewrapping is computed synchronously.
18944 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18945 if self.is_empty(cx) {
18946 self.placeholder_display_map
18947 .as_ref()
18948 .map_or(false, |display_map| {
18949 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
18950 })
18951 } else {
18952 self.display_map
18953 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18954 }
18955 }
18956
18957 pub fn set_soft_wrap(&mut self) {
18958 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18959 }
18960
18961 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18962 if self.soft_wrap_mode_override.is_some() {
18963 self.soft_wrap_mode_override.take();
18964 } else {
18965 let soft_wrap = match self.soft_wrap_mode(cx) {
18966 SoftWrap::GitDiff => return,
18967 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18968 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18969 language_settings::SoftWrap::None
18970 }
18971 };
18972 self.soft_wrap_mode_override = Some(soft_wrap);
18973 }
18974 cx.notify();
18975 }
18976
18977 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18978 let Some(workspace) = self.workspace() else {
18979 return;
18980 };
18981 let fs = workspace.read(cx).app_state().fs.clone();
18982 let current_show = TabBarSettings::get_global(cx).show;
18983 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18984 setting.show = Some(!current_show);
18985 });
18986 }
18987
18988 pub fn toggle_indent_guides(
18989 &mut self,
18990 _: &ToggleIndentGuides,
18991 _: &mut Window,
18992 cx: &mut Context<Self>,
18993 ) {
18994 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18995 self.buffer
18996 .read(cx)
18997 .language_settings(cx)
18998 .indent_guides
18999 .enabled
19000 });
19001 self.show_indent_guides = Some(!currently_enabled);
19002 cx.notify();
19003 }
19004
19005 fn should_show_indent_guides(&self) -> Option<bool> {
19006 self.show_indent_guides
19007 }
19008
19009 pub fn toggle_line_numbers(
19010 &mut self,
19011 _: &ToggleLineNumbers,
19012 _: &mut Window,
19013 cx: &mut Context<Self>,
19014 ) {
19015 let mut editor_settings = EditorSettings::get_global(cx).clone();
19016 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19017 EditorSettings::override_global(editor_settings, cx);
19018 }
19019
19020 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19021 if let Some(show_line_numbers) = self.show_line_numbers {
19022 return show_line_numbers;
19023 }
19024 EditorSettings::get_global(cx).gutter.line_numbers
19025 }
19026
19027 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19028 self.use_relative_line_numbers
19029 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19030 }
19031
19032 pub fn toggle_relative_line_numbers(
19033 &mut self,
19034 _: &ToggleRelativeLineNumbers,
19035 _: &mut Window,
19036 cx: &mut Context<Self>,
19037 ) {
19038 let is_relative = self.should_use_relative_line_numbers(cx);
19039 self.set_relative_line_number(Some(!is_relative), cx)
19040 }
19041
19042 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19043 self.use_relative_line_numbers = is_relative;
19044 cx.notify();
19045 }
19046
19047 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19048 self.show_gutter = show_gutter;
19049 cx.notify();
19050 }
19051
19052 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19053 self.show_scrollbars = ScrollbarAxes {
19054 horizontal: show,
19055 vertical: show,
19056 };
19057 cx.notify();
19058 }
19059
19060 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19061 self.show_scrollbars.vertical = show;
19062 cx.notify();
19063 }
19064
19065 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19066 self.show_scrollbars.horizontal = show;
19067 cx.notify();
19068 }
19069
19070 pub fn set_minimap_visibility(
19071 &mut self,
19072 minimap_visibility: MinimapVisibility,
19073 window: &mut Window,
19074 cx: &mut Context<Self>,
19075 ) {
19076 if self.minimap_visibility != minimap_visibility {
19077 if minimap_visibility.visible() && self.minimap.is_none() {
19078 let minimap_settings = EditorSettings::get_global(cx).minimap;
19079 self.minimap =
19080 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19081 }
19082 self.minimap_visibility = minimap_visibility;
19083 cx.notify();
19084 }
19085 }
19086
19087 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19088 self.set_show_scrollbars(false, cx);
19089 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19090 }
19091
19092 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19093 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19094 }
19095
19096 /// Normally the text in full mode and auto height editors is padded on the
19097 /// left side by roughly half a character width for improved hit testing.
19098 ///
19099 /// Use this method to disable this for cases where this is not wanted (e.g.
19100 /// if you want to align the editor text with some other text above or below)
19101 /// or if you want to add this padding to single-line editors.
19102 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19103 self.offset_content = offset_content;
19104 cx.notify();
19105 }
19106
19107 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19108 self.show_line_numbers = Some(show_line_numbers);
19109 cx.notify();
19110 }
19111
19112 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19113 self.disable_expand_excerpt_buttons = true;
19114 cx.notify();
19115 }
19116
19117 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19118 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19119 cx.notify();
19120 }
19121
19122 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19123 self.show_code_actions = Some(show_code_actions);
19124 cx.notify();
19125 }
19126
19127 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19128 self.show_runnables = Some(show_runnables);
19129 cx.notify();
19130 }
19131
19132 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19133 self.show_breakpoints = Some(show_breakpoints);
19134 cx.notify();
19135 }
19136
19137 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19138 if self.display_map.read(cx).masked != masked {
19139 self.display_map.update(cx, |map, _| map.masked = masked);
19140 }
19141 cx.notify()
19142 }
19143
19144 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19145 self.show_wrap_guides = Some(show_wrap_guides);
19146 cx.notify();
19147 }
19148
19149 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19150 self.show_indent_guides = Some(show_indent_guides);
19151 cx.notify();
19152 }
19153
19154 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19155 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19156 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19157 && let Some(dir) = file.abs_path(cx).parent()
19158 {
19159 return Some(dir.to_owned());
19160 }
19161
19162 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19163 return Some(project_path.path.to_path_buf());
19164 }
19165 }
19166
19167 None
19168 }
19169
19170 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19171 self.active_excerpt(cx)?
19172 .1
19173 .read(cx)
19174 .file()
19175 .and_then(|f| f.as_local())
19176 }
19177
19178 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19179 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19180 let buffer = buffer.read(cx);
19181 if let Some(project_path) = buffer.project_path(cx) {
19182 let project = self.project()?.read(cx);
19183 project.absolute_path(&project_path, cx)
19184 } else {
19185 buffer
19186 .file()
19187 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19188 }
19189 })
19190 }
19191
19192 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19193 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19194 let project_path = buffer.read(cx).project_path(cx)?;
19195 let project = self.project()?.read(cx);
19196 let entry = project.entry_for_path(&project_path, cx)?;
19197 let path = entry.path.to_path_buf();
19198 Some(path)
19199 })
19200 }
19201
19202 pub fn reveal_in_finder(
19203 &mut self,
19204 _: &RevealInFileManager,
19205 _window: &mut Window,
19206 cx: &mut Context<Self>,
19207 ) {
19208 if let Some(target) = self.target_file(cx) {
19209 cx.reveal_path(&target.abs_path(cx));
19210 }
19211 }
19212
19213 pub fn copy_path(
19214 &mut self,
19215 _: &zed_actions::workspace::CopyPath,
19216 _window: &mut Window,
19217 cx: &mut Context<Self>,
19218 ) {
19219 if let Some(path) = self.target_file_abs_path(cx)
19220 && let Some(path) = path.to_str()
19221 {
19222 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19223 }
19224 }
19225
19226 pub fn copy_relative_path(
19227 &mut self,
19228 _: &zed_actions::workspace::CopyRelativePath,
19229 _window: &mut Window,
19230 cx: &mut Context<Self>,
19231 ) {
19232 if let Some(path) = self.target_file_path(cx)
19233 && let Some(path) = path.to_str()
19234 {
19235 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19236 }
19237 }
19238
19239 /// Returns the project path for the editor's buffer, if any buffer is
19240 /// opened in the editor.
19241 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19242 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19243 buffer.read(cx).project_path(cx)
19244 } else {
19245 None
19246 }
19247 }
19248
19249 // Returns true if the editor handled a go-to-line request
19250 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19251 maybe!({
19252 let breakpoint_store = self.breakpoint_store.as_ref()?;
19253
19254 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19255 else {
19256 self.clear_row_highlights::<ActiveDebugLine>();
19257 return None;
19258 };
19259
19260 let position = active_stack_frame.position;
19261 let buffer_id = position.buffer_id?;
19262 let snapshot = self
19263 .project
19264 .as_ref()?
19265 .read(cx)
19266 .buffer_for_id(buffer_id, cx)?
19267 .read(cx)
19268 .snapshot();
19269
19270 let mut handled = false;
19271 for (id, ExcerptRange { context, .. }) in
19272 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19273 {
19274 if context.start.cmp(&position, &snapshot).is_ge()
19275 || context.end.cmp(&position, &snapshot).is_lt()
19276 {
19277 continue;
19278 }
19279 let snapshot = self.buffer.read(cx).snapshot(cx);
19280 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19281
19282 handled = true;
19283 self.clear_row_highlights::<ActiveDebugLine>();
19284
19285 self.go_to_line::<ActiveDebugLine>(
19286 multibuffer_anchor,
19287 Some(cx.theme().colors().editor_debugger_active_line_background),
19288 window,
19289 cx,
19290 );
19291
19292 cx.notify();
19293 }
19294
19295 handled.then_some(())
19296 })
19297 .is_some()
19298 }
19299
19300 pub fn copy_file_name_without_extension(
19301 &mut self,
19302 _: &CopyFileNameWithoutExtension,
19303 _: &mut Window,
19304 cx: &mut Context<Self>,
19305 ) {
19306 if let Some(file) = self.target_file(cx)
19307 && let Some(file_stem) = file.path().file_stem()
19308 && let Some(name) = file_stem.to_str()
19309 {
19310 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19311 }
19312 }
19313
19314 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19315 if let Some(file) = self.target_file(cx)
19316 && let Some(file_name) = file.path().file_name()
19317 && let Some(name) = file_name.to_str()
19318 {
19319 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19320 }
19321 }
19322
19323 pub fn toggle_git_blame(
19324 &mut self,
19325 _: &::git::Blame,
19326 window: &mut Window,
19327 cx: &mut Context<Self>,
19328 ) {
19329 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19330
19331 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19332 self.start_git_blame(true, window, cx);
19333 }
19334
19335 cx.notify();
19336 }
19337
19338 pub fn toggle_git_blame_inline(
19339 &mut self,
19340 _: &ToggleGitBlameInline,
19341 window: &mut Window,
19342 cx: &mut Context<Self>,
19343 ) {
19344 self.toggle_git_blame_inline_internal(true, window, cx);
19345 cx.notify();
19346 }
19347
19348 pub fn open_git_blame_commit(
19349 &mut self,
19350 _: &OpenGitBlameCommit,
19351 window: &mut Window,
19352 cx: &mut Context<Self>,
19353 ) {
19354 self.open_git_blame_commit_internal(window, cx);
19355 }
19356
19357 fn open_git_blame_commit_internal(
19358 &mut self,
19359 window: &mut Window,
19360 cx: &mut Context<Self>,
19361 ) -> Option<()> {
19362 let blame = self.blame.as_ref()?;
19363 let snapshot = self.snapshot(window, cx);
19364 let cursor = self.selections.newest::<Point>(cx).head();
19365 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19366 let (_, blame_entry) = blame
19367 .update(cx, |blame, cx| {
19368 blame
19369 .blame_for_rows(
19370 &[RowInfo {
19371 buffer_id: Some(buffer.remote_id()),
19372 buffer_row: Some(point.row),
19373 ..Default::default()
19374 }],
19375 cx,
19376 )
19377 .next()
19378 })
19379 .flatten()?;
19380 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19381 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19382 let workspace = self.workspace()?.downgrade();
19383 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19384 None
19385 }
19386
19387 pub fn git_blame_inline_enabled(&self) -> bool {
19388 self.git_blame_inline_enabled
19389 }
19390
19391 pub fn toggle_selection_menu(
19392 &mut self,
19393 _: &ToggleSelectionMenu,
19394 _: &mut Window,
19395 cx: &mut Context<Self>,
19396 ) {
19397 self.show_selection_menu = self
19398 .show_selection_menu
19399 .map(|show_selections_menu| !show_selections_menu)
19400 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19401
19402 cx.notify();
19403 }
19404
19405 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19406 self.show_selection_menu
19407 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19408 }
19409
19410 fn start_git_blame(
19411 &mut self,
19412 user_triggered: bool,
19413 window: &mut Window,
19414 cx: &mut Context<Self>,
19415 ) {
19416 if let Some(project) = self.project() {
19417 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19418 && buffer.read(cx).file().is_none()
19419 {
19420 return;
19421 }
19422
19423 let focused = self.focus_handle(cx).contains_focused(window, cx);
19424
19425 let project = project.clone();
19426 let blame = cx
19427 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19428 self.blame_subscription =
19429 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19430 self.blame = Some(blame);
19431 }
19432 }
19433
19434 fn toggle_git_blame_inline_internal(
19435 &mut self,
19436 user_triggered: bool,
19437 window: &mut Window,
19438 cx: &mut Context<Self>,
19439 ) {
19440 if self.git_blame_inline_enabled {
19441 self.git_blame_inline_enabled = false;
19442 self.show_git_blame_inline = false;
19443 self.show_git_blame_inline_delay_task.take();
19444 } else {
19445 self.git_blame_inline_enabled = true;
19446 self.start_git_blame_inline(user_triggered, window, cx);
19447 }
19448
19449 cx.notify();
19450 }
19451
19452 fn start_git_blame_inline(
19453 &mut self,
19454 user_triggered: bool,
19455 window: &mut Window,
19456 cx: &mut Context<Self>,
19457 ) {
19458 self.start_git_blame(user_triggered, window, cx);
19459
19460 if ProjectSettings::get_global(cx)
19461 .git
19462 .inline_blame_delay()
19463 .is_some()
19464 {
19465 self.start_inline_blame_timer(window, cx);
19466 } else {
19467 self.show_git_blame_inline = true
19468 }
19469 }
19470
19471 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19472 self.blame.as_ref()
19473 }
19474
19475 pub fn show_git_blame_gutter(&self) -> bool {
19476 self.show_git_blame_gutter
19477 }
19478
19479 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19480 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19481 }
19482
19483 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19484 self.show_git_blame_inline
19485 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19486 && !self.newest_selection_head_on_empty_line(cx)
19487 && self.has_blame_entries(cx)
19488 }
19489
19490 fn has_blame_entries(&self, cx: &App) -> bool {
19491 self.blame()
19492 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19493 }
19494
19495 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19496 let cursor_anchor = self.selections.newest_anchor().head();
19497
19498 let snapshot = self.buffer.read(cx).snapshot(cx);
19499 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19500
19501 snapshot.line_len(buffer_row) == 0
19502 }
19503
19504 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19505 let buffer_and_selection = maybe!({
19506 let selection = self.selections.newest::<Point>(cx);
19507 let selection_range = selection.range();
19508
19509 let multi_buffer = self.buffer().read(cx);
19510 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19511 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19512
19513 let (buffer, range, _) = if selection.reversed {
19514 buffer_ranges.first()
19515 } else {
19516 buffer_ranges.last()
19517 }?;
19518
19519 let selection = text::ToPoint::to_point(&range.start, buffer).row
19520 ..text::ToPoint::to_point(&range.end, buffer).row;
19521 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19522 });
19523
19524 let Some((buffer, selection)) = buffer_and_selection else {
19525 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19526 };
19527
19528 let Some(project) = self.project() else {
19529 return Task::ready(Err(anyhow!("editor does not have project")));
19530 };
19531
19532 project.update(cx, |project, cx| {
19533 project.get_permalink_to_line(&buffer, selection, cx)
19534 })
19535 }
19536
19537 pub fn copy_permalink_to_line(
19538 &mut self,
19539 _: &CopyPermalinkToLine,
19540 window: &mut Window,
19541 cx: &mut Context<Self>,
19542 ) {
19543 let permalink_task = self.get_permalink_to_line(cx);
19544 let workspace = self.workspace();
19545
19546 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19547 Ok(permalink) => {
19548 cx.update(|_, cx| {
19549 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19550 })
19551 .ok();
19552 }
19553 Err(err) => {
19554 let message = format!("Failed to copy permalink: {err}");
19555
19556 anyhow::Result::<()>::Err(err).log_err();
19557
19558 if let Some(workspace) = workspace {
19559 workspace
19560 .update_in(cx, |workspace, _, cx| {
19561 struct CopyPermalinkToLine;
19562
19563 workspace.show_toast(
19564 Toast::new(
19565 NotificationId::unique::<CopyPermalinkToLine>(),
19566 message,
19567 ),
19568 cx,
19569 )
19570 })
19571 .ok();
19572 }
19573 }
19574 })
19575 .detach();
19576 }
19577
19578 pub fn copy_file_location(
19579 &mut self,
19580 _: &CopyFileLocation,
19581 _: &mut Window,
19582 cx: &mut Context<Self>,
19583 ) {
19584 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19585 if let Some(file) = self.target_file(cx)
19586 && let Some(path) = file.path().to_str()
19587 {
19588 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19589 }
19590 }
19591
19592 pub fn open_permalink_to_line(
19593 &mut self,
19594 _: &OpenPermalinkToLine,
19595 window: &mut Window,
19596 cx: &mut Context<Self>,
19597 ) {
19598 let permalink_task = self.get_permalink_to_line(cx);
19599 let workspace = self.workspace();
19600
19601 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19602 Ok(permalink) => {
19603 cx.update(|_, cx| {
19604 cx.open_url(permalink.as_ref());
19605 })
19606 .ok();
19607 }
19608 Err(err) => {
19609 let message = format!("Failed to open permalink: {err}");
19610
19611 anyhow::Result::<()>::Err(err).log_err();
19612
19613 if let Some(workspace) = workspace {
19614 workspace
19615 .update(cx, |workspace, cx| {
19616 struct OpenPermalinkToLine;
19617
19618 workspace.show_toast(
19619 Toast::new(
19620 NotificationId::unique::<OpenPermalinkToLine>(),
19621 message,
19622 ),
19623 cx,
19624 )
19625 })
19626 .ok();
19627 }
19628 }
19629 })
19630 .detach();
19631 }
19632
19633 pub fn insert_uuid_v4(
19634 &mut self,
19635 _: &InsertUuidV4,
19636 window: &mut Window,
19637 cx: &mut Context<Self>,
19638 ) {
19639 self.insert_uuid(UuidVersion::V4, window, cx);
19640 }
19641
19642 pub fn insert_uuid_v7(
19643 &mut self,
19644 _: &InsertUuidV7,
19645 window: &mut Window,
19646 cx: &mut Context<Self>,
19647 ) {
19648 self.insert_uuid(UuidVersion::V7, window, cx);
19649 }
19650
19651 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19652 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19653 self.transact(window, cx, |this, window, cx| {
19654 let edits = this
19655 .selections
19656 .all::<Point>(cx)
19657 .into_iter()
19658 .map(|selection| {
19659 let uuid = match version {
19660 UuidVersion::V4 => uuid::Uuid::new_v4(),
19661 UuidVersion::V7 => uuid::Uuid::now_v7(),
19662 };
19663
19664 (selection.range(), uuid.to_string())
19665 });
19666 this.edit(edits, cx);
19667 this.refresh_edit_prediction(true, false, window, cx);
19668 });
19669 }
19670
19671 pub fn open_selections_in_multibuffer(
19672 &mut self,
19673 _: &OpenSelectionsInMultibuffer,
19674 window: &mut Window,
19675 cx: &mut Context<Self>,
19676 ) {
19677 let multibuffer = self.buffer.read(cx);
19678
19679 let Some(buffer) = multibuffer.as_singleton() else {
19680 return;
19681 };
19682
19683 let Some(workspace) = self.workspace() else {
19684 return;
19685 };
19686
19687 let title = multibuffer.title(cx).to_string();
19688
19689 let locations = self
19690 .selections
19691 .all_anchors(cx)
19692 .iter()
19693 .map(|selection| Location {
19694 buffer: buffer.clone(),
19695 range: selection.start.text_anchor..selection.end.text_anchor,
19696 })
19697 .collect::<Vec<_>>();
19698
19699 cx.spawn_in(window, async move |_, cx| {
19700 workspace.update_in(cx, |workspace, window, cx| {
19701 Self::open_locations_in_multibuffer(
19702 workspace,
19703 locations,
19704 format!("Selections for '{title}'"),
19705 false,
19706 MultibufferSelectionMode::All,
19707 window,
19708 cx,
19709 );
19710 })
19711 })
19712 .detach();
19713 }
19714
19715 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19716 /// last highlight added will be used.
19717 ///
19718 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19719 pub fn highlight_rows<T: 'static>(
19720 &mut self,
19721 range: Range<Anchor>,
19722 color: Hsla,
19723 options: RowHighlightOptions,
19724 cx: &mut Context<Self>,
19725 ) {
19726 let snapshot = self.buffer().read(cx).snapshot(cx);
19727 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19728 let ix = row_highlights.binary_search_by(|highlight| {
19729 Ordering::Equal
19730 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19731 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19732 });
19733
19734 if let Err(mut ix) = ix {
19735 let index = post_inc(&mut self.highlight_order);
19736
19737 // If this range intersects with the preceding highlight, then merge it with
19738 // the preceding highlight. Otherwise insert a new highlight.
19739 let mut merged = false;
19740 if ix > 0 {
19741 let prev_highlight = &mut row_highlights[ix - 1];
19742 if prev_highlight
19743 .range
19744 .end
19745 .cmp(&range.start, &snapshot)
19746 .is_ge()
19747 {
19748 ix -= 1;
19749 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19750 prev_highlight.range.end = range.end;
19751 }
19752 merged = true;
19753 prev_highlight.index = index;
19754 prev_highlight.color = color;
19755 prev_highlight.options = options;
19756 }
19757 }
19758
19759 if !merged {
19760 row_highlights.insert(
19761 ix,
19762 RowHighlight {
19763 range,
19764 index,
19765 color,
19766 options,
19767 type_id: TypeId::of::<T>(),
19768 },
19769 );
19770 }
19771
19772 // If any of the following highlights intersect with this one, merge them.
19773 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19774 let highlight = &row_highlights[ix];
19775 if next_highlight
19776 .range
19777 .start
19778 .cmp(&highlight.range.end, &snapshot)
19779 .is_le()
19780 {
19781 if next_highlight
19782 .range
19783 .end
19784 .cmp(&highlight.range.end, &snapshot)
19785 .is_gt()
19786 {
19787 row_highlights[ix].range.end = next_highlight.range.end;
19788 }
19789 row_highlights.remove(ix + 1);
19790 } else {
19791 break;
19792 }
19793 }
19794 }
19795 }
19796
19797 /// Remove any highlighted row ranges of the given type that intersect the
19798 /// given ranges.
19799 pub fn remove_highlighted_rows<T: 'static>(
19800 &mut self,
19801 ranges_to_remove: Vec<Range<Anchor>>,
19802 cx: &mut Context<Self>,
19803 ) {
19804 let snapshot = self.buffer().read(cx).snapshot(cx);
19805 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19806 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19807 row_highlights.retain(|highlight| {
19808 while let Some(range_to_remove) = ranges_to_remove.peek() {
19809 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19810 Ordering::Less | Ordering::Equal => {
19811 ranges_to_remove.next();
19812 }
19813 Ordering::Greater => {
19814 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19815 Ordering::Less | Ordering::Equal => {
19816 return false;
19817 }
19818 Ordering::Greater => break,
19819 }
19820 }
19821 }
19822 }
19823
19824 true
19825 })
19826 }
19827
19828 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19829 pub fn clear_row_highlights<T: 'static>(&mut self) {
19830 self.highlighted_rows.remove(&TypeId::of::<T>());
19831 }
19832
19833 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19834 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19835 self.highlighted_rows
19836 .get(&TypeId::of::<T>())
19837 .map_or(&[] as &[_], |vec| vec.as_slice())
19838 .iter()
19839 .map(|highlight| (highlight.range.clone(), highlight.color))
19840 }
19841
19842 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19843 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19844 /// Allows to ignore certain kinds of highlights.
19845 pub fn highlighted_display_rows(
19846 &self,
19847 window: &mut Window,
19848 cx: &mut App,
19849 ) -> BTreeMap<DisplayRow, LineHighlight> {
19850 let snapshot = self.snapshot(window, cx);
19851 let mut used_highlight_orders = HashMap::default();
19852 self.highlighted_rows
19853 .iter()
19854 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19855 .fold(
19856 BTreeMap::<DisplayRow, LineHighlight>::new(),
19857 |mut unique_rows, highlight| {
19858 let start = highlight.range.start.to_display_point(&snapshot);
19859 let end = highlight.range.end.to_display_point(&snapshot);
19860 let start_row = start.row().0;
19861 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19862 && end.column() == 0
19863 {
19864 end.row().0.saturating_sub(1)
19865 } else {
19866 end.row().0
19867 };
19868 for row in start_row..=end_row {
19869 let used_index =
19870 used_highlight_orders.entry(row).or_insert(highlight.index);
19871 if highlight.index >= *used_index {
19872 *used_index = highlight.index;
19873 unique_rows.insert(
19874 DisplayRow(row),
19875 LineHighlight {
19876 include_gutter: highlight.options.include_gutter,
19877 border: None,
19878 background: highlight.color.into(),
19879 type_id: Some(highlight.type_id),
19880 },
19881 );
19882 }
19883 }
19884 unique_rows
19885 },
19886 )
19887 }
19888
19889 pub fn highlighted_display_row_for_autoscroll(
19890 &self,
19891 snapshot: &DisplaySnapshot,
19892 ) -> Option<DisplayRow> {
19893 self.highlighted_rows
19894 .values()
19895 .flat_map(|highlighted_rows| highlighted_rows.iter())
19896 .filter_map(|highlight| {
19897 if highlight.options.autoscroll {
19898 Some(highlight.range.start.to_display_point(snapshot).row())
19899 } else {
19900 None
19901 }
19902 })
19903 .min()
19904 }
19905
19906 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19907 self.highlight_background::<SearchWithinRange>(
19908 ranges,
19909 |colors| colors.colors().editor_document_highlight_read_background,
19910 cx,
19911 )
19912 }
19913
19914 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19915 self.breadcrumb_header = Some(new_header);
19916 }
19917
19918 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19919 self.clear_background_highlights::<SearchWithinRange>(cx);
19920 }
19921
19922 pub fn highlight_background<T: 'static>(
19923 &mut self,
19924 ranges: &[Range<Anchor>],
19925 color_fetcher: fn(&Theme) -> Hsla,
19926 cx: &mut Context<Self>,
19927 ) {
19928 self.background_highlights.insert(
19929 HighlightKey::Type(TypeId::of::<T>()),
19930 (color_fetcher, Arc::from(ranges)),
19931 );
19932 self.scrollbar_marker_state.dirty = true;
19933 cx.notify();
19934 }
19935
19936 pub fn highlight_background_key<T: 'static>(
19937 &mut self,
19938 key: usize,
19939 ranges: &[Range<Anchor>],
19940 color_fetcher: fn(&Theme) -> Hsla,
19941 cx: &mut Context<Self>,
19942 ) {
19943 self.background_highlights.insert(
19944 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19945 (color_fetcher, Arc::from(ranges)),
19946 );
19947 self.scrollbar_marker_state.dirty = true;
19948 cx.notify();
19949 }
19950
19951 pub fn clear_background_highlights<T: 'static>(
19952 &mut self,
19953 cx: &mut Context<Self>,
19954 ) -> Option<BackgroundHighlight> {
19955 let text_highlights = self
19956 .background_highlights
19957 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19958 if !text_highlights.1.is_empty() {
19959 self.scrollbar_marker_state.dirty = true;
19960 cx.notify();
19961 }
19962 Some(text_highlights)
19963 }
19964
19965 pub fn highlight_gutter<T: 'static>(
19966 &mut self,
19967 ranges: impl Into<Vec<Range<Anchor>>>,
19968 color_fetcher: fn(&App) -> Hsla,
19969 cx: &mut Context<Self>,
19970 ) {
19971 self.gutter_highlights
19972 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19973 cx.notify();
19974 }
19975
19976 pub fn clear_gutter_highlights<T: 'static>(
19977 &mut self,
19978 cx: &mut Context<Self>,
19979 ) -> Option<GutterHighlight> {
19980 cx.notify();
19981 self.gutter_highlights.remove(&TypeId::of::<T>())
19982 }
19983
19984 pub fn insert_gutter_highlight<T: 'static>(
19985 &mut self,
19986 range: Range<Anchor>,
19987 color_fetcher: fn(&App) -> Hsla,
19988 cx: &mut Context<Self>,
19989 ) {
19990 let snapshot = self.buffer().read(cx).snapshot(cx);
19991 let mut highlights = self
19992 .gutter_highlights
19993 .remove(&TypeId::of::<T>())
19994 .map(|(_, highlights)| highlights)
19995 .unwrap_or_default();
19996 let ix = highlights.binary_search_by(|highlight| {
19997 Ordering::Equal
19998 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19999 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20000 });
20001 if let Err(ix) = ix {
20002 highlights.insert(ix, range);
20003 }
20004 self.gutter_highlights
20005 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20006 }
20007
20008 pub fn remove_gutter_highlights<T: 'static>(
20009 &mut self,
20010 ranges_to_remove: Vec<Range<Anchor>>,
20011 cx: &mut Context<Self>,
20012 ) {
20013 let snapshot = self.buffer().read(cx).snapshot(cx);
20014 let Some((color_fetcher, mut gutter_highlights)) =
20015 self.gutter_highlights.remove(&TypeId::of::<T>())
20016 else {
20017 return;
20018 };
20019 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20020 gutter_highlights.retain(|highlight| {
20021 while let Some(range_to_remove) = ranges_to_remove.peek() {
20022 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20023 Ordering::Less | Ordering::Equal => {
20024 ranges_to_remove.next();
20025 }
20026 Ordering::Greater => {
20027 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20028 Ordering::Less | Ordering::Equal => {
20029 return false;
20030 }
20031 Ordering::Greater => break,
20032 }
20033 }
20034 }
20035 }
20036
20037 true
20038 });
20039 self.gutter_highlights
20040 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20041 }
20042
20043 #[cfg(feature = "test-support")]
20044 pub fn all_text_highlights(
20045 &self,
20046 window: &mut Window,
20047 cx: &mut Context<Self>,
20048 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20049 let snapshot = self.snapshot(window, cx);
20050 self.display_map.update(cx, |display_map, _| {
20051 display_map
20052 .all_text_highlights()
20053 .map(|highlight| {
20054 let (style, ranges) = highlight.as_ref();
20055 (
20056 *style,
20057 ranges
20058 .iter()
20059 .map(|range| range.clone().to_display_points(&snapshot))
20060 .collect(),
20061 )
20062 })
20063 .collect()
20064 })
20065 }
20066
20067 #[cfg(feature = "test-support")]
20068 pub fn all_text_background_highlights(
20069 &self,
20070 window: &mut Window,
20071 cx: &mut Context<Self>,
20072 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20073 let snapshot = self.snapshot(window, cx);
20074 let buffer = &snapshot.buffer_snapshot;
20075 let start = buffer.anchor_before(0);
20076 let end = buffer.anchor_after(buffer.len());
20077 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20078 }
20079
20080 #[cfg(any(test, feature = "test-support"))]
20081 pub fn sorted_background_highlights_in_range(
20082 &self,
20083 search_range: Range<Anchor>,
20084 display_snapshot: &DisplaySnapshot,
20085 theme: &Theme,
20086 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20087 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20088 res.sort_by(|a, b| {
20089 a.0.start
20090 .cmp(&b.0.start)
20091 .then_with(|| a.0.end.cmp(&b.0.end))
20092 .then_with(|| a.1.cmp(&b.1))
20093 });
20094 res
20095 }
20096
20097 #[cfg(feature = "test-support")]
20098 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20099 let snapshot = self.buffer().read(cx).snapshot(cx);
20100
20101 let highlights = self
20102 .background_highlights
20103 .get(&HighlightKey::Type(TypeId::of::<
20104 items::BufferSearchHighlights,
20105 >()));
20106
20107 if let Some((_color, ranges)) = highlights {
20108 ranges
20109 .iter()
20110 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20111 .collect_vec()
20112 } else {
20113 vec![]
20114 }
20115 }
20116
20117 fn document_highlights_for_position<'a>(
20118 &'a self,
20119 position: Anchor,
20120 buffer: &'a MultiBufferSnapshot,
20121 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20122 let read_highlights = self
20123 .background_highlights
20124 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20125 .map(|h| &h.1);
20126 let write_highlights = self
20127 .background_highlights
20128 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20129 .map(|h| &h.1);
20130 let left_position = position.bias_left(buffer);
20131 let right_position = position.bias_right(buffer);
20132 read_highlights
20133 .into_iter()
20134 .chain(write_highlights)
20135 .flat_map(move |ranges| {
20136 let start_ix = match ranges.binary_search_by(|probe| {
20137 let cmp = probe.end.cmp(&left_position, buffer);
20138 if cmp.is_ge() {
20139 Ordering::Greater
20140 } else {
20141 Ordering::Less
20142 }
20143 }) {
20144 Ok(i) | Err(i) => i,
20145 };
20146
20147 ranges[start_ix..]
20148 .iter()
20149 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20150 })
20151 }
20152
20153 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20154 self.background_highlights
20155 .get(&HighlightKey::Type(TypeId::of::<T>()))
20156 .is_some_and(|(_, highlights)| !highlights.is_empty())
20157 }
20158
20159 /// Returns all background highlights for a given range.
20160 ///
20161 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20162 pub fn background_highlights_in_range(
20163 &self,
20164 search_range: Range<Anchor>,
20165 display_snapshot: &DisplaySnapshot,
20166 theme: &Theme,
20167 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20168 let mut results = Vec::new();
20169 for (color_fetcher, ranges) in self.background_highlights.values() {
20170 let color = color_fetcher(theme);
20171 let start_ix = match ranges.binary_search_by(|probe| {
20172 let cmp = probe
20173 .end
20174 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20175 if cmp.is_gt() {
20176 Ordering::Greater
20177 } else {
20178 Ordering::Less
20179 }
20180 }) {
20181 Ok(i) | Err(i) => i,
20182 };
20183 for range in &ranges[start_ix..] {
20184 if range
20185 .start
20186 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20187 .is_ge()
20188 {
20189 break;
20190 }
20191
20192 let start = range.start.to_display_point(display_snapshot);
20193 let end = range.end.to_display_point(display_snapshot);
20194 results.push((start..end, color))
20195 }
20196 }
20197 results
20198 }
20199
20200 pub fn gutter_highlights_in_range(
20201 &self,
20202 search_range: Range<Anchor>,
20203 display_snapshot: &DisplaySnapshot,
20204 cx: &App,
20205 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20206 let mut results = Vec::new();
20207 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20208 let color = color_fetcher(cx);
20209 let start_ix = match ranges.binary_search_by(|probe| {
20210 let cmp = probe
20211 .end
20212 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20213 if cmp.is_gt() {
20214 Ordering::Greater
20215 } else {
20216 Ordering::Less
20217 }
20218 }) {
20219 Ok(i) | Err(i) => i,
20220 };
20221 for range in &ranges[start_ix..] {
20222 if range
20223 .start
20224 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20225 .is_ge()
20226 {
20227 break;
20228 }
20229
20230 let start = range.start.to_display_point(display_snapshot);
20231 let end = range.end.to_display_point(display_snapshot);
20232 results.push((start..end, color))
20233 }
20234 }
20235 results
20236 }
20237
20238 /// Get the text ranges corresponding to the redaction query
20239 pub fn redacted_ranges(
20240 &self,
20241 search_range: Range<Anchor>,
20242 display_snapshot: &DisplaySnapshot,
20243 cx: &App,
20244 ) -> Vec<Range<DisplayPoint>> {
20245 display_snapshot
20246 .buffer_snapshot
20247 .redacted_ranges(search_range, |file| {
20248 if let Some(file) = file {
20249 file.is_private()
20250 && EditorSettings::get(
20251 Some(SettingsLocation {
20252 worktree_id: file.worktree_id(cx),
20253 path: file.path().as_ref(),
20254 }),
20255 cx,
20256 )
20257 .redact_private_values
20258 } else {
20259 false
20260 }
20261 })
20262 .map(|range| {
20263 range.start.to_display_point(display_snapshot)
20264 ..range.end.to_display_point(display_snapshot)
20265 })
20266 .collect()
20267 }
20268
20269 pub fn highlight_text_key<T: 'static>(
20270 &mut self,
20271 key: usize,
20272 ranges: Vec<Range<Anchor>>,
20273 style: HighlightStyle,
20274 cx: &mut Context<Self>,
20275 ) {
20276 self.display_map.update(cx, |map, _| {
20277 map.highlight_text(
20278 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20279 ranges,
20280 style,
20281 );
20282 });
20283 cx.notify();
20284 }
20285
20286 pub fn highlight_text<T: 'static>(
20287 &mut self,
20288 ranges: Vec<Range<Anchor>>,
20289 style: HighlightStyle,
20290 cx: &mut Context<Self>,
20291 ) {
20292 self.display_map.update(cx, |map, _| {
20293 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20294 });
20295 cx.notify();
20296 }
20297
20298 pub(crate) fn highlight_inlays<T: 'static>(
20299 &mut self,
20300 highlights: Vec<InlayHighlight>,
20301 style: HighlightStyle,
20302 cx: &mut Context<Self>,
20303 ) {
20304 self.display_map.update(cx, |map, _| {
20305 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20306 });
20307 cx.notify();
20308 }
20309
20310 pub fn text_highlights<'a, T: 'static>(
20311 &'a self,
20312 cx: &'a App,
20313 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20314 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20315 }
20316
20317 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20318 let cleared = self
20319 .display_map
20320 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20321 if cleared {
20322 cx.notify();
20323 }
20324 }
20325
20326 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20327 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20328 && self.focus_handle.is_focused(window)
20329 }
20330
20331 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20332 self.show_cursor_when_unfocused = is_enabled;
20333 cx.notify();
20334 }
20335
20336 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20337 cx.notify();
20338 }
20339
20340 fn on_debug_session_event(
20341 &mut self,
20342 _session: Entity<Session>,
20343 event: &SessionEvent,
20344 cx: &mut Context<Self>,
20345 ) {
20346 if let SessionEvent::InvalidateInlineValue = event {
20347 self.refresh_inline_values(cx);
20348 }
20349 }
20350
20351 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20352 let Some(project) = self.project.clone() else {
20353 return;
20354 };
20355
20356 if !self.inline_value_cache.enabled {
20357 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20358 self.splice_inlays(&inlays, Vec::new(), cx);
20359 return;
20360 }
20361
20362 let current_execution_position = self
20363 .highlighted_rows
20364 .get(&TypeId::of::<ActiveDebugLine>())
20365 .and_then(|lines| lines.last().map(|line| line.range.end));
20366
20367 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20368 let inline_values = editor
20369 .update(cx, |editor, cx| {
20370 let Some(current_execution_position) = current_execution_position else {
20371 return Some(Task::ready(Ok(Vec::new())));
20372 };
20373
20374 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20375 let snapshot = buffer.snapshot(cx);
20376
20377 let excerpt = snapshot.excerpt_containing(
20378 current_execution_position..current_execution_position,
20379 )?;
20380
20381 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20382 })?;
20383
20384 let range =
20385 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20386
20387 project.inline_values(buffer, range, cx)
20388 })
20389 .ok()
20390 .flatten()?
20391 .await
20392 .context("refreshing debugger inlays")
20393 .log_err()?;
20394
20395 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20396
20397 for (buffer_id, inline_value) in inline_values
20398 .into_iter()
20399 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20400 {
20401 buffer_inline_values
20402 .entry(buffer_id)
20403 .or_default()
20404 .push(inline_value);
20405 }
20406
20407 editor
20408 .update(cx, |editor, cx| {
20409 let snapshot = editor.buffer.read(cx).snapshot(cx);
20410 let mut new_inlays = Vec::default();
20411
20412 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20413 let buffer_id = buffer_snapshot.remote_id();
20414 buffer_inline_values
20415 .get(&buffer_id)
20416 .into_iter()
20417 .flatten()
20418 .for_each(|hint| {
20419 let inlay = Inlay::debugger(
20420 post_inc(&mut editor.next_inlay_id),
20421 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20422 hint.text(),
20423 );
20424 if !inlay.text.chars().contains(&'\n') {
20425 new_inlays.push(inlay);
20426 }
20427 });
20428 }
20429
20430 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20431 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20432
20433 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20434 })
20435 .ok()?;
20436 Some(())
20437 });
20438 }
20439
20440 fn on_buffer_event(
20441 &mut self,
20442 multibuffer: &Entity<MultiBuffer>,
20443 event: &multi_buffer::Event,
20444 window: &mut Window,
20445 cx: &mut Context<Self>,
20446 ) {
20447 match event {
20448 multi_buffer::Event::Edited {
20449 singleton_buffer_edited,
20450 edited_buffer,
20451 } => {
20452 self.scrollbar_marker_state.dirty = true;
20453 self.active_indent_guides_state.dirty = true;
20454 self.refresh_active_diagnostics(cx);
20455 self.refresh_code_actions(window, cx);
20456 self.refresh_selected_text_highlights(true, window, cx);
20457 self.refresh_single_line_folds(window, cx);
20458 refresh_matching_bracket_highlights(self, window, cx);
20459 if self.has_active_edit_prediction() {
20460 self.update_visible_edit_prediction(window, cx);
20461 }
20462 if let Some(project) = self.project.as_ref()
20463 && let Some(edited_buffer) = edited_buffer
20464 {
20465 project.update(cx, |project, cx| {
20466 self.registered_buffers
20467 .entry(edited_buffer.read(cx).remote_id())
20468 .or_insert_with(|| {
20469 project.register_buffer_with_language_servers(edited_buffer, cx)
20470 });
20471 });
20472 }
20473 cx.emit(EditorEvent::BufferEdited);
20474 cx.emit(SearchEvent::MatchesInvalidated);
20475
20476 if let Some(buffer) = edited_buffer {
20477 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20478 }
20479
20480 if *singleton_buffer_edited {
20481 if let Some(buffer) = edited_buffer
20482 && buffer.read(cx).file().is_none()
20483 {
20484 cx.emit(EditorEvent::TitleChanged);
20485 }
20486 if let Some(project) = &self.project {
20487 #[allow(clippy::mutable_key_type)]
20488 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20489 multibuffer
20490 .all_buffers()
20491 .into_iter()
20492 .filter_map(|buffer| {
20493 buffer.update(cx, |buffer, cx| {
20494 let language = buffer.language()?;
20495 let should_discard = project.update(cx, |project, cx| {
20496 project.is_local()
20497 && !project.has_language_servers_for(buffer, cx)
20498 });
20499 should_discard.not().then_some(language.clone())
20500 })
20501 })
20502 .collect::<HashSet<_>>()
20503 });
20504 if !languages_affected.is_empty() {
20505 self.refresh_inlay_hints(
20506 InlayHintRefreshReason::BufferEdited(languages_affected),
20507 cx,
20508 );
20509 }
20510 }
20511 }
20512
20513 let Some(project) = &self.project else { return };
20514 let (telemetry, is_via_ssh) = {
20515 let project = project.read(cx);
20516 let telemetry = project.client().telemetry().clone();
20517 let is_via_ssh = project.is_via_remote_server();
20518 (telemetry, is_via_ssh)
20519 };
20520 refresh_linked_ranges(self, window, cx);
20521 telemetry.log_edit_event("editor", is_via_ssh);
20522 }
20523 multi_buffer::Event::ExcerptsAdded {
20524 buffer,
20525 predecessor,
20526 excerpts,
20527 } => {
20528 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20529 let buffer_id = buffer.read(cx).remote_id();
20530 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20531 && let Some(project) = &self.project
20532 {
20533 update_uncommitted_diff_for_buffer(
20534 cx.entity(),
20535 project,
20536 [buffer.clone()],
20537 self.buffer.clone(),
20538 cx,
20539 )
20540 .detach();
20541 }
20542 if self.active_diagnostics != ActiveDiagnostic::All {
20543 self.update_lsp_data(false, Some(buffer_id), window, cx);
20544 }
20545 cx.emit(EditorEvent::ExcerptsAdded {
20546 buffer: buffer.clone(),
20547 predecessor: *predecessor,
20548 excerpts: excerpts.clone(),
20549 });
20550 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20551 }
20552 multi_buffer::Event::ExcerptsRemoved {
20553 ids,
20554 removed_buffer_ids,
20555 } => {
20556 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20557 let buffer = self.buffer.read(cx);
20558 self.registered_buffers
20559 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20560 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20561 cx.emit(EditorEvent::ExcerptsRemoved {
20562 ids: ids.clone(),
20563 removed_buffer_ids: removed_buffer_ids.clone(),
20564 });
20565 }
20566 multi_buffer::Event::ExcerptsEdited {
20567 excerpt_ids,
20568 buffer_ids,
20569 } => {
20570 self.display_map.update(cx, |map, cx| {
20571 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20572 });
20573 cx.emit(EditorEvent::ExcerptsEdited {
20574 ids: excerpt_ids.clone(),
20575 });
20576 }
20577 multi_buffer::Event::ExcerptsExpanded { ids } => {
20578 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20579 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20580 }
20581 multi_buffer::Event::Reparsed(buffer_id) => {
20582 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20583 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20584
20585 cx.emit(EditorEvent::Reparsed(*buffer_id));
20586 }
20587 multi_buffer::Event::DiffHunksToggled => {
20588 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20589 }
20590 multi_buffer::Event::LanguageChanged(buffer_id) => {
20591 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20592 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20593 cx.emit(EditorEvent::Reparsed(*buffer_id));
20594 cx.notify();
20595 }
20596 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20597 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20598 multi_buffer::Event::FileHandleChanged
20599 | multi_buffer::Event::Reloaded
20600 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20601 multi_buffer::Event::DiagnosticsUpdated => {
20602 self.update_diagnostics_state(window, cx);
20603 }
20604 _ => {}
20605 };
20606 }
20607
20608 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20609 if !self.diagnostics_enabled() {
20610 return;
20611 }
20612 self.refresh_active_diagnostics(cx);
20613 self.refresh_inline_diagnostics(true, window, cx);
20614 self.scrollbar_marker_state.dirty = true;
20615 cx.notify();
20616 }
20617
20618 pub fn start_temporary_diff_override(&mut self) {
20619 self.load_diff_task.take();
20620 self.temporary_diff_override = true;
20621 }
20622
20623 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20624 self.temporary_diff_override = false;
20625 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20626 self.buffer.update(cx, |buffer, cx| {
20627 buffer.set_all_diff_hunks_collapsed(cx);
20628 });
20629
20630 if let Some(project) = self.project.clone() {
20631 self.load_diff_task = Some(
20632 update_uncommitted_diff_for_buffer(
20633 cx.entity(),
20634 &project,
20635 self.buffer.read(cx).all_buffers(),
20636 self.buffer.clone(),
20637 cx,
20638 )
20639 .shared(),
20640 );
20641 }
20642 }
20643
20644 fn on_display_map_changed(
20645 &mut self,
20646 _: Entity<DisplayMap>,
20647 _: &mut Window,
20648 cx: &mut Context<Self>,
20649 ) {
20650 cx.notify();
20651 }
20652
20653 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20654 if self.diagnostics_enabled() {
20655 let new_severity = EditorSettings::get_global(cx)
20656 .diagnostics_max_severity
20657 .unwrap_or(DiagnosticSeverity::Hint);
20658 self.set_max_diagnostics_severity(new_severity, cx);
20659 }
20660 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20661 self.update_edit_prediction_settings(cx);
20662 self.refresh_edit_prediction(true, false, window, cx);
20663 self.refresh_inline_values(cx);
20664 self.refresh_inlay_hints(
20665 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20666 self.selections.newest_anchor().head(),
20667 &self.buffer.read(cx).snapshot(cx),
20668 cx,
20669 )),
20670 cx,
20671 );
20672
20673 let old_cursor_shape = self.cursor_shape;
20674 let old_show_breadcrumbs = self.show_breadcrumbs;
20675
20676 {
20677 let editor_settings = EditorSettings::get_global(cx);
20678 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20679 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20680 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20681 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20682 }
20683
20684 if old_cursor_shape != self.cursor_shape {
20685 cx.emit(EditorEvent::CursorShapeChanged);
20686 }
20687
20688 if old_show_breadcrumbs != self.show_breadcrumbs {
20689 cx.emit(EditorEvent::BreadcrumbsChanged);
20690 }
20691
20692 let project_settings = ProjectSettings::get_global(cx);
20693 self.serialize_dirty_buffers =
20694 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20695
20696 if self.mode.is_full() {
20697 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20698 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20699 if self.show_inline_diagnostics != show_inline_diagnostics {
20700 self.show_inline_diagnostics = show_inline_diagnostics;
20701 self.refresh_inline_diagnostics(false, window, cx);
20702 }
20703
20704 if self.git_blame_inline_enabled != inline_blame_enabled {
20705 self.toggle_git_blame_inline_internal(false, window, cx);
20706 }
20707
20708 let minimap_settings = EditorSettings::get_global(cx).minimap;
20709 if self.minimap_visibility != MinimapVisibility::Disabled {
20710 if self.minimap_visibility.settings_visibility()
20711 != minimap_settings.minimap_enabled()
20712 {
20713 self.set_minimap_visibility(
20714 MinimapVisibility::for_mode(self.mode(), cx),
20715 window,
20716 cx,
20717 );
20718 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20719 minimap_entity.update(cx, |minimap_editor, cx| {
20720 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20721 })
20722 }
20723 }
20724 }
20725
20726 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20727 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20728 }) {
20729 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20730 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20731 }
20732 self.refresh_colors(false, None, window, cx);
20733 }
20734
20735 cx.notify();
20736 }
20737
20738 pub fn set_searchable(&mut self, searchable: bool) {
20739 self.searchable = searchable;
20740 }
20741
20742 pub fn searchable(&self) -> bool {
20743 self.searchable
20744 }
20745
20746 fn open_proposed_changes_editor(
20747 &mut self,
20748 _: &OpenProposedChangesEditor,
20749 window: &mut Window,
20750 cx: &mut Context<Self>,
20751 ) {
20752 let Some(workspace) = self.workspace() else {
20753 cx.propagate();
20754 return;
20755 };
20756
20757 let selections = self.selections.all::<usize>(cx);
20758 let multi_buffer = self.buffer.read(cx);
20759 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20760 let mut new_selections_by_buffer = HashMap::default();
20761 for selection in selections {
20762 for (buffer, range, _) in
20763 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20764 {
20765 let mut range = range.to_point(buffer);
20766 range.start.column = 0;
20767 range.end.column = buffer.line_len(range.end.row);
20768 new_selections_by_buffer
20769 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20770 .or_insert(Vec::new())
20771 .push(range)
20772 }
20773 }
20774
20775 let proposed_changes_buffers = new_selections_by_buffer
20776 .into_iter()
20777 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20778 .collect::<Vec<_>>();
20779 let proposed_changes_editor = cx.new(|cx| {
20780 ProposedChangesEditor::new(
20781 "Proposed changes",
20782 proposed_changes_buffers,
20783 self.project.clone(),
20784 window,
20785 cx,
20786 )
20787 });
20788
20789 window.defer(cx, move |window, cx| {
20790 workspace.update(cx, |workspace, cx| {
20791 workspace.active_pane().update(cx, |pane, cx| {
20792 pane.add_item(
20793 Box::new(proposed_changes_editor),
20794 true,
20795 true,
20796 None,
20797 window,
20798 cx,
20799 );
20800 });
20801 });
20802 });
20803 }
20804
20805 pub fn open_excerpts_in_split(
20806 &mut self,
20807 _: &OpenExcerptsSplit,
20808 window: &mut Window,
20809 cx: &mut Context<Self>,
20810 ) {
20811 self.open_excerpts_common(None, true, window, cx)
20812 }
20813
20814 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20815 self.open_excerpts_common(None, false, window, cx)
20816 }
20817
20818 fn open_excerpts_common(
20819 &mut self,
20820 jump_data: Option<JumpData>,
20821 split: bool,
20822 window: &mut Window,
20823 cx: &mut Context<Self>,
20824 ) {
20825 let Some(workspace) = self.workspace() else {
20826 cx.propagate();
20827 return;
20828 };
20829
20830 if self.buffer.read(cx).is_singleton() {
20831 cx.propagate();
20832 return;
20833 }
20834
20835 let mut new_selections_by_buffer = HashMap::default();
20836 match &jump_data {
20837 Some(JumpData::MultiBufferPoint {
20838 excerpt_id,
20839 position,
20840 anchor,
20841 line_offset_from_top,
20842 }) => {
20843 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20844 if let Some(buffer) = multi_buffer_snapshot
20845 .buffer_id_for_excerpt(*excerpt_id)
20846 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20847 {
20848 let buffer_snapshot = buffer.read(cx).snapshot();
20849 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20850 language::ToPoint::to_point(anchor, &buffer_snapshot)
20851 } else {
20852 buffer_snapshot.clip_point(*position, Bias::Left)
20853 };
20854 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20855 new_selections_by_buffer.insert(
20856 buffer,
20857 (
20858 vec![jump_to_offset..jump_to_offset],
20859 Some(*line_offset_from_top),
20860 ),
20861 );
20862 }
20863 }
20864 Some(JumpData::MultiBufferRow {
20865 row,
20866 line_offset_from_top,
20867 }) => {
20868 let point = MultiBufferPoint::new(row.0, 0);
20869 if let Some((buffer, buffer_point, _)) =
20870 self.buffer.read(cx).point_to_buffer_point(point, cx)
20871 {
20872 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20873 new_selections_by_buffer
20874 .entry(buffer)
20875 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20876 .0
20877 .push(buffer_offset..buffer_offset)
20878 }
20879 }
20880 None => {
20881 let selections = self.selections.all::<usize>(cx);
20882 let multi_buffer = self.buffer.read(cx);
20883 for selection in selections {
20884 for (snapshot, range, _, anchor) in multi_buffer
20885 .snapshot(cx)
20886 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20887 {
20888 if let Some(anchor) = anchor {
20889 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20890 else {
20891 continue;
20892 };
20893 let offset = text::ToOffset::to_offset(
20894 &anchor.text_anchor,
20895 &buffer_handle.read(cx).snapshot(),
20896 );
20897 let range = offset..offset;
20898 new_selections_by_buffer
20899 .entry(buffer_handle)
20900 .or_insert((Vec::new(), None))
20901 .0
20902 .push(range)
20903 } else {
20904 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20905 else {
20906 continue;
20907 };
20908 new_selections_by_buffer
20909 .entry(buffer_handle)
20910 .or_insert((Vec::new(), None))
20911 .0
20912 .push(range)
20913 }
20914 }
20915 }
20916 }
20917 }
20918
20919 new_selections_by_buffer
20920 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20921
20922 if new_selections_by_buffer.is_empty() {
20923 return;
20924 }
20925
20926 // We defer the pane interaction because we ourselves are a workspace item
20927 // and activating a new item causes the pane to call a method on us reentrantly,
20928 // which panics if we're on the stack.
20929 window.defer(cx, move |window, cx| {
20930 workspace.update(cx, |workspace, cx| {
20931 let pane = if split {
20932 workspace.adjacent_pane(window, cx)
20933 } else {
20934 workspace.active_pane().clone()
20935 };
20936
20937 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20938 let editor = buffer
20939 .read(cx)
20940 .file()
20941 .is_none()
20942 .then(|| {
20943 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20944 // so `workspace.open_project_item` will never find them, always opening a new editor.
20945 // Instead, we try to activate the existing editor in the pane first.
20946 let (editor, pane_item_index) =
20947 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20948 let editor = item.downcast::<Editor>()?;
20949 let singleton_buffer =
20950 editor.read(cx).buffer().read(cx).as_singleton()?;
20951 if singleton_buffer == buffer {
20952 Some((editor, i))
20953 } else {
20954 None
20955 }
20956 })?;
20957 pane.update(cx, |pane, cx| {
20958 pane.activate_item(pane_item_index, true, true, window, cx)
20959 });
20960 Some(editor)
20961 })
20962 .flatten()
20963 .unwrap_or_else(|| {
20964 workspace.open_project_item::<Self>(
20965 pane.clone(),
20966 buffer,
20967 true,
20968 true,
20969 window,
20970 cx,
20971 )
20972 });
20973
20974 editor.update(cx, |editor, cx| {
20975 let autoscroll = match scroll_offset {
20976 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20977 None => Autoscroll::newest(),
20978 };
20979 let nav_history = editor.nav_history.take();
20980 editor.change_selections(
20981 SelectionEffects::scroll(autoscroll),
20982 window,
20983 cx,
20984 |s| {
20985 s.select_ranges(ranges);
20986 },
20987 );
20988 editor.nav_history = nav_history;
20989 });
20990 }
20991 })
20992 });
20993 }
20994
20995 // For now, don't allow opening excerpts in buffers that aren't backed by
20996 // regular project files.
20997 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20998 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20999 }
21000
21001 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21002 let snapshot = self.buffer.read(cx).read(cx);
21003 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21004 Some(
21005 ranges
21006 .iter()
21007 .map(move |range| {
21008 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21009 })
21010 .collect(),
21011 )
21012 }
21013
21014 fn selection_replacement_ranges(
21015 &self,
21016 range: Range<OffsetUtf16>,
21017 cx: &mut App,
21018 ) -> Vec<Range<OffsetUtf16>> {
21019 let selections = self.selections.all::<OffsetUtf16>(cx);
21020 let newest_selection = selections
21021 .iter()
21022 .max_by_key(|selection| selection.id)
21023 .unwrap();
21024 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21025 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21026 let snapshot = self.buffer.read(cx).read(cx);
21027 selections
21028 .into_iter()
21029 .map(|mut selection| {
21030 selection.start.0 =
21031 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21032 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21033 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21034 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21035 })
21036 .collect()
21037 }
21038
21039 fn report_editor_event(
21040 &self,
21041 reported_event: ReportEditorEvent,
21042 file_extension: Option<String>,
21043 cx: &App,
21044 ) {
21045 if cfg!(any(test, feature = "test-support")) {
21046 return;
21047 }
21048
21049 let Some(project) = &self.project else { return };
21050
21051 // If None, we are in a file without an extension
21052 let file = self
21053 .buffer
21054 .read(cx)
21055 .as_singleton()
21056 .and_then(|b| b.read(cx).file());
21057 let file_extension = file_extension.or(file
21058 .as_ref()
21059 .and_then(|file| Path::new(file.file_name(cx)).extension())
21060 .and_then(|e| e.to_str())
21061 .map(|a| a.to_string()));
21062
21063 let vim_mode = vim_enabled(cx);
21064
21065 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21066 let copilot_enabled = edit_predictions_provider
21067 == language::language_settings::EditPredictionProvider::Copilot;
21068 let copilot_enabled_for_language = self
21069 .buffer
21070 .read(cx)
21071 .language_settings(cx)
21072 .show_edit_predictions;
21073
21074 let project = project.read(cx);
21075 let event_type = reported_event.event_type();
21076
21077 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21078 telemetry::event!(
21079 event_type,
21080 type = if auto_saved {"autosave"} else {"manual"},
21081 file_extension,
21082 vim_mode,
21083 copilot_enabled,
21084 copilot_enabled_for_language,
21085 edit_predictions_provider,
21086 is_via_ssh = project.is_via_remote_server(),
21087 );
21088 } else {
21089 telemetry::event!(
21090 event_type,
21091 file_extension,
21092 vim_mode,
21093 copilot_enabled,
21094 copilot_enabled_for_language,
21095 edit_predictions_provider,
21096 is_via_ssh = project.is_via_remote_server(),
21097 );
21098 };
21099 }
21100
21101 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21102 /// with each line being an array of {text, highlight} objects.
21103 fn copy_highlight_json(
21104 &mut self,
21105 _: &CopyHighlightJson,
21106 window: &mut Window,
21107 cx: &mut Context<Self>,
21108 ) {
21109 #[derive(Serialize)]
21110 struct Chunk<'a> {
21111 text: String,
21112 highlight: Option<&'a str>,
21113 }
21114
21115 let snapshot = self.buffer.read(cx).snapshot(cx);
21116 let range = self
21117 .selected_text_range(false, window, cx)
21118 .and_then(|selection| {
21119 if selection.range.is_empty() {
21120 None
21121 } else {
21122 Some(selection.range)
21123 }
21124 })
21125 .unwrap_or_else(|| 0..snapshot.len());
21126
21127 let chunks = snapshot.chunks(range, true);
21128 let mut lines = Vec::new();
21129 let mut line: VecDeque<Chunk> = VecDeque::new();
21130
21131 let Some(style) = self.style.as_ref() else {
21132 return;
21133 };
21134
21135 for chunk in chunks {
21136 let highlight = chunk
21137 .syntax_highlight_id
21138 .and_then(|id| id.name(&style.syntax));
21139 let mut chunk_lines = chunk.text.split('\n').peekable();
21140 while let Some(text) = chunk_lines.next() {
21141 let mut merged_with_last_token = false;
21142 if let Some(last_token) = line.back_mut()
21143 && last_token.highlight == highlight
21144 {
21145 last_token.text.push_str(text);
21146 merged_with_last_token = true;
21147 }
21148
21149 if !merged_with_last_token {
21150 line.push_back(Chunk {
21151 text: text.into(),
21152 highlight,
21153 });
21154 }
21155
21156 if chunk_lines.peek().is_some() {
21157 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21158 line.pop_front();
21159 }
21160 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21161 line.pop_back();
21162 }
21163
21164 lines.push(mem::take(&mut line));
21165 }
21166 }
21167 }
21168
21169 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21170 return;
21171 };
21172 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21173 }
21174
21175 pub fn open_context_menu(
21176 &mut self,
21177 _: &OpenContextMenu,
21178 window: &mut Window,
21179 cx: &mut Context<Self>,
21180 ) {
21181 self.request_autoscroll(Autoscroll::newest(), cx);
21182 let position = self.selections.newest_display(cx).start;
21183 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21184 }
21185
21186 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21187 &self.inlay_hint_cache
21188 }
21189
21190 pub fn replay_insert_event(
21191 &mut self,
21192 text: &str,
21193 relative_utf16_range: Option<Range<isize>>,
21194 window: &mut Window,
21195 cx: &mut Context<Self>,
21196 ) {
21197 if !self.input_enabled {
21198 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21199 return;
21200 }
21201 if let Some(relative_utf16_range) = relative_utf16_range {
21202 let selections = self.selections.all::<OffsetUtf16>(cx);
21203 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21204 let new_ranges = selections.into_iter().map(|range| {
21205 let start = OffsetUtf16(
21206 range
21207 .head()
21208 .0
21209 .saturating_add_signed(relative_utf16_range.start),
21210 );
21211 let end = OffsetUtf16(
21212 range
21213 .head()
21214 .0
21215 .saturating_add_signed(relative_utf16_range.end),
21216 );
21217 start..end
21218 });
21219 s.select_ranges(new_ranges);
21220 });
21221 }
21222
21223 self.handle_input(text, window, cx);
21224 }
21225
21226 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21227 let Some(provider) = self.semantics_provider.as_ref() else {
21228 return false;
21229 };
21230
21231 let mut supports = false;
21232 self.buffer().update(cx, |this, cx| {
21233 this.for_each_buffer(|buffer| {
21234 supports |= provider.supports_inlay_hints(buffer, cx);
21235 });
21236 });
21237
21238 supports
21239 }
21240
21241 pub fn is_focused(&self, window: &Window) -> bool {
21242 self.focus_handle.is_focused(window)
21243 }
21244
21245 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21246 cx.emit(EditorEvent::Focused);
21247
21248 if let Some(descendant) = self
21249 .last_focused_descendant
21250 .take()
21251 .and_then(|descendant| descendant.upgrade())
21252 {
21253 window.focus(&descendant);
21254 } else {
21255 if let Some(blame) = self.blame.as_ref() {
21256 blame.update(cx, GitBlame::focus)
21257 }
21258
21259 self.blink_manager.update(cx, BlinkManager::enable);
21260 self.show_cursor_names(window, cx);
21261 self.buffer.update(cx, |buffer, cx| {
21262 buffer.finalize_last_transaction(cx);
21263 if self.leader_id.is_none() {
21264 buffer.set_active_selections(
21265 &self.selections.disjoint_anchors(),
21266 self.selections.line_mode,
21267 self.cursor_shape,
21268 cx,
21269 );
21270 }
21271 });
21272 }
21273 }
21274
21275 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21276 cx.emit(EditorEvent::FocusedIn)
21277 }
21278
21279 fn handle_focus_out(
21280 &mut self,
21281 event: FocusOutEvent,
21282 _window: &mut Window,
21283 cx: &mut Context<Self>,
21284 ) {
21285 if event.blurred != self.focus_handle {
21286 self.last_focused_descendant = Some(event.blurred);
21287 }
21288 self.selection_drag_state = SelectionDragState::None;
21289 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21290 }
21291
21292 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21293 self.blink_manager.update(cx, BlinkManager::disable);
21294 self.buffer
21295 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21296
21297 if let Some(blame) = self.blame.as_ref() {
21298 blame.update(cx, GitBlame::blur)
21299 }
21300 if !self.hover_state.focused(window, cx) {
21301 hide_hover(self, cx);
21302 }
21303 if !self
21304 .context_menu
21305 .borrow()
21306 .as_ref()
21307 .is_some_and(|context_menu| context_menu.focused(window, cx))
21308 {
21309 self.hide_context_menu(window, cx);
21310 }
21311 self.discard_edit_prediction(false, cx);
21312 cx.emit(EditorEvent::Blurred);
21313 cx.notify();
21314 }
21315
21316 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21317 let mut pending: String = window
21318 .pending_input_keystrokes()
21319 .into_iter()
21320 .flatten()
21321 .filter_map(|keystroke| {
21322 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21323 keystroke.key_char.clone()
21324 } else {
21325 None
21326 }
21327 })
21328 .collect();
21329
21330 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21331 pending = "".to_string();
21332 }
21333
21334 let existing_pending = self
21335 .text_highlights::<PendingInput>(cx)
21336 .map(|(_, ranges)| ranges.to_vec());
21337 if existing_pending.is_none() && pending.is_empty() {
21338 return;
21339 }
21340 let transaction =
21341 self.transact(window, cx, |this, window, cx| {
21342 let selections = this.selections.all::<usize>(cx);
21343 let edits = selections
21344 .iter()
21345 .map(|selection| (selection.end..selection.end, pending.clone()));
21346 this.edit(edits, cx);
21347 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21348 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21349 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21350 }));
21351 });
21352 if let Some(existing_ranges) = existing_pending {
21353 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21354 this.edit(edits, cx);
21355 }
21356 });
21357
21358 let snapshot = self.snapshot(window, cx);
21359 let ranges = self
21360 .selections
21361 .all::<usize>(cx)
21362 .into_iter()
21363 .map(|selection| {
21364 snapshot.buffer_snapshot.anchor_after(selection.end)
21365 ..snapshot
21366 .buffer_snapshot
21367 .anchor_before(selection.end + pending.len())
21368 })
21369 .collect();
21370
21371 if pending.is_empty() {
21372 self.clear_highlights::<PendingInput>(cx);
21373 } else {
21374 self.highlight_text::<PendingInput>(
21375 ranges,
21376 HighlightStyle {
21377 underline: Some(UnderlineStyle {
21378 thickness: px(1.),
21379 color: None,
21380 wavy: false,
21381 }),
21382 ..Default::default()
21383 },
21384 cx,
21385 );
21386 }
21387
21388 self.ime_transaction = self.ime_transaction.or(transaction);
21389 if let Some(transaction) = self.ime_transaction {
21390 self.buffer.update(cx, |buffer, cx| {
21391 buffer.group_until_transaction(transaction, cx);
21392 });
21393 }
21394
21395 if self.text_highlights::<PendingInput>(cx).is_none() {
21396 self.ime_transaction.take();
21397 }
21398 }
21399
21400 pub fn register_action_renderer(
21401 &mut self,
21402 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21403 ) -> Subscription {
21404 let id = self.next_editor_action_id.post_inc();
21405 self.editor_actions
21406 .borrow_mut()
21407 .insert(id, Box::new(listener));
21408
21409 let editor_actions = self.editor_actions.clone();
21410 Subscription::new(move || {
21411 editor_actions.borrow_mut().remove(&id);
21412 })
21413 }
21414
21415 pub fn register_action<A: Action>(
21416 &mut self,
21417 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21418 ) -> Subscription {
21419 let id = self.next_editor_action_id.post_inc();
21420 let listener = Arc::new(listener);
21421 self.editor_actions.borrow_mut().insert(
21422 id,
21423 Box::new(move |_, window, _| {
21424 let listener = listener.clone();
21425 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21426 let action = action.downcast_ref().unwrap();
21427 if phase == DispatchPhase::Bubble {
21428 listener(action, window, cx)
21429 }
21430 })
21431 }),
21432 );
21433
21434 let editor_actions = self.editor_actions.clone();
21435 Subscription::new(move || {
21436 editor_actions.borrow_mut().remove(&id);
21437 })
21438 }
21439
21440 pub fn file_header_size(&self) -> u32 {
21441 FILE_HEADER_HEIGHT
21442 }
21443
21444 pub fn restore(
21445 &mut self,
21446 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21447 window: &mut Window,
21448 cx: &mut Context<Self>,
21449 ) {
21450 let workspace = self.workspace();
21451 let project = self.project();
21452 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21453 let mut tasks = Vec::new();
21454 for (buffer_id, changes) in revert_changes {
21455 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21456 buffer.update(cx, |buffer, cx| {
21457 buffer.edit(
21458 changes
21459 .into_iter()
21460 .map(|(range, text)| (range, text.to_string())),
21461 None,
21462 cx,
21463 );
21464 });
21465
21466 if let Some(project) =
21467 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21468 {
21469 project.update(cx, |project, cx| {
21470 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21471 })
21472 }
21473 }
21474 }
21475 tasks
21476 });
21477 cx.spawn_in(window, async move |_, cx| {
21478 for (buffer, task) in save_tasks {
21479 let result = task.await;
21480 if result.is_err() {
21481 let Some(path) = buffer
21482 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21483 .ok()
21484 else {
21485 continue;
21486 };
21487 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21488 let Some(task) = cx
21489 .update_window_entity(workspace, |workspace, window, cx| {
21490 workspace
21491 .open_path_preview(path, None, false, false, false, window, cx)
21492 })
21493 .ok()
21494 else {
21495 continue;
21496 };
21497 task.await.log_err();
21498 }
21499 }
21500 }
21501 })
21502 .detach();
21503 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21504 selections.refresh()
21505 });
21506 }
21507
21508 pub fn to_pixel_point(
21509 &self,
21510 source: multi_buffer::Anchor,
21511 editor_snapshot: &EditorSnapshot,
21512 window: &mut Window,
21513 ) -> Option<gpui::Point<Pixels>> {
21514 let source_point = source.to_display_point(editor_snapshot);
21515 self.display_to_pixel_point(source_point, editor_snapshot, window)
21516 }
21517
21518 pub fn display_to_pixel_point(
21519 &self,
21520 source: DisplayPoint,
21521 editor_snapshot: &EditorSnapshot,
21522 window: &mut Window,
21523 ) -> Option<gpui::Point<Pixels>> {
21524 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21525 let text_layout_details = self.text_layout_details(window);
21526 let scroll_top = text_layout_details
21527 .scroll_anchor
21528 .scroll_position(editor_snapshot)
21529 .y;
21530
21531 if source.row().as_f32() < scroll_top.floor() {
21532 return None;
21533 }
21534 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21535 let source_y = line_height * (source.row().as_f32() - scroll_top);
21536 Some(gpui::Point::new(source_x, source_y))
21537 }
21538
21539 pub fn has_visible_completions_menu(&self) -> bool {
21540 !self.edit_prediction_preview_is_active()
21541 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21542 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21543 })
21544 }
21545
21546 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21547 if self.mode.is_minimap() {
21548 return;
21549 }
21550 self.addons
21551 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21552 }
21553
21554 pub fn unregister_addon<T: Addon>(&mut self) {
21555 self.addons.remove(&std::any::TypeId::of::<T>());
21556 }
21557
21558 pub fn addon<T: Addon>(&self) -> Option<&T> {
21559 let type_id = std::any::TypeId::of::<T>();
21560 self.addons
21561 .get(&type_id)
21562 .and_then(|item| item.to_any().downcast_ref::<T>())
21563 }
21564
21565 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21566 let type_id = std::any::TypeId::of::<T>();
21567 self.addons
21568 .get_mut(&type_id)
21569 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21570 }
21571
21572 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21573 let text_layout_details = self.text_layout_details(window);
21574 let style = &text_layout_details.editor_style;
21575 let font_id = window.text_system().resolve_font(&style.text.font());
21576 let font_size = style.text.font_size.to_pixels(window.rem_size());
21577 let line_height = style.text.line_height_in_pixels(window.rem_size());
21578 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21579 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21580
21581 CharacterDimensions {
21582 em_width,
21583 em_advance,
21584 line_height,
21585 }
21586 }
21587
21588 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21589 self.load_diff_task.clone()
21590 }
21591
21592 fn read_metadata_from_db(
21593 &mut self,
21594 item_id: u64,
21595 workspace_id: WorkspaceId,
21596 window: &mut Window,
21597 cx: &mut Context<Editor>,
21598 ) {
21599 if self.is_singleton(cx)
21600 && !self.mode.is_minimap()
21601 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21602 {
21603 let buffer_snapshot = OnceCell::new();
21604
21605 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21606 && !folds.is_empty()
21607 {
21608 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21609 self.fold_ranges(
21610 folds
21611 .into_iter()
21612 .map(|(start, end)| {
21613 snapshot.clip_offset(start, Bias::Left)
21614 ..snapshot.clip_offset(end, Bias::Right)
21615 })
21616 .collect(),
21617 false,
21618 window,
21619 cx,
21620 );
21621 }
21622
21623 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21624 && !selections.is_empty()
21625 {
21626 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21627 // skip adding the initial selection to selection history
21628 self.selection_history.mode = SelectionHistoryMode::Skipping;
21629 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21630 s.select_ranges(selections.into_iter().map(|(start, end)| {
21631 snapshot.clip_offset(start, Bias::Left)
21632 ..snapshot.clip_offset(end, Bias::Right)
21633 }));
21634 });
21635 self.selection_history.mode = SelectionHistoryMode::Normal;
21636 };
21637 }
21638
21639 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21640 }
21641
21642 fn update_lsp_data(
21643 &mut self,
21644 ignore_cache: bool,
21645 for_buffer: Option<BufferId>,
21646 window: &mut Window,
21647 cx: &mut Context<'_, Self>,
21648 ) {
21649 self.pull_diagnostics(for_buffer, window, cx);
21650 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21651 }
21652}
21653
21654fn vim_enabled(cx: &App) -> bool {
21655 cx.global::<SettingsStore>()
21656 .raw_user_settings()
21657 .get("vim_mode")
21658 == Some(&serde_json::Value::Bool(true))
21659}
21660
21661fn process_completion_for_edit(
21662 completion: &Completion,
21663 intent: CompletionIntent,
21664 buffer: &Entity<Buffer>,
21665 cursor_position: &text::Anchor,
21666 cx: &mut Context<Editor>,
21667) -> CompletionEdit {
21668 let buffer = buffer.read(cx);
21669 let buffer_snapshot = buffer.snapshot();
21670 let (snippet, new_text) = if completion.is_snippet() {
21671 // Workaround for typescript language server issues so that methods don't expand within
21672 // strings and functions with type expressions. The previous point is used because the query
21673 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21674 let mut snippet_source = completion.new_text.clone();
21675 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21676 previous_point.column = previous_point.column.saturating_sub(1);
21677 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21678 && scope.prefers_label_for_snippet_in_completion()
21679 && let Some(label) = completion.label()
21680 && matches!(
21681 completion.kind(),
21682 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21683 )
21684 {
21685 snippet_source = label;
21686 }
21687 match Snippet::parse(&snippet_source).log_err() {
21688 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21689 None => (None, completion.new_text.clone()),
21690 }
21691 } else {
21692 (None, completion.new_text.clone())
21693 };
21694
21695 let mut range_to_replace = {
21696 let replace_range = &completion.replace_range;
21697 if let CompletionSource::Lsp {
21698 insert_range: Some(insert_range),
21699 ..
21700 } = &completion.source
21701 {
21702 debug_assert_eq!(
21703 insert_range.start, replace_range.start,
21704 "insert_range and replace_range should start at the same position"
21705 );
21706 debug_assert!(
21707 insert_range
21708 .start
21709 .cmp(cursor_position, &buffer_snapshot)
21710 .is_le(),
21711 "insert_range should start before or at cursor position"
21712 );
21713 debug_assert!(
21714 replace_range
21715 .start
21716 .cmp(cursor_position, &buffer_snapshot)
21717 .is_le(),
21718 "replace_range should start before or at cursor position"
21719 );
21720
21721 let should_replace = match intent {
21722 CompletionIntent::CompleteWithInsert => false,
21723 CompletionIntent::CompleteWithReplace => true,
21724 CompletionIntent::Complete | CompletionIntent::Compose => {
21725 let insert_mode =
21726 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21727 .completions
21728 .lsp_insert_mode;
21729 match insert_mode {
21730 LspInsertMode::Insert => false,
21731 LspInsertMode::Replace => true,
21732 LspInsertMode::ReplaceSubsequence => {
21733 let mut text_to_replace = buffer.chars_for_range(
21734 buffer.anchor_before(replace_range.start)
21735 ..buffer.anchor_after(replace_range.end),
21736 );
21737 let mut current_needle = text_to_replace.next();
21738 for haystack_ch in completion.label.text.chars() {
21739 if let Some(needle_ch) = current_needle
21740 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21741 {
21742 current_needle = text_to_replace.next();
21743 }
21744 }
21745 current_needle.is_none()
21746 }
21747 LspInsertMode::ReplaceSuffix => {
21748 if replace_range
21749 .end
21750 .cmp(cursor_position, &buffer_snapshot)
21751 .is_gt()
21752 {
21753 let range_after_cursor = *cursor_position..replace_range.end;
21754 let text_after_cursor = buffer
21755 .text_for_range(
21756 buffer.anchor_before(range_after_cursor.start)
21757 ..buffer.anchor_after(range_after_cursor.end),
21758 )
21759 .collect::<String>()
21760 .to_ascii_lowercase();
21761 completion
21762 .label
21763 .text
21764 .to_ascii_lowercase()
21765 .ends_with(&text_after_cursor)
21766 } else {
21767 true
21768 }
21769 }
21770 }
21771 }
21772 };
21773
21774 if should_replace {
21775 replace_range.clone()
21776 } else {
21777 insert_range.clone()
21778 }
21779 } else {
21780 replace_range.clone()
21781 }
21782 };
21783
21784 if range_to_replace
21785 .end
21786 .cmp(cursor_position, &buffer_snapshot)
21787 .is_lt()
21788 {
21789 range_to_replace.end = *cursor_position;
21790 }
21791
21792 CompletionEdit {
21793 new_text,
21794 replace_range: range_to_replace.to_offset(buffer),
21795 snippet,
21796 }
21797}
21798
21799struct CompletionEdit {
21800 new_text: String,
21801 replace_range: Range<usize>,
21802 snippet: Option<Snippet>,
21803}
21804
21805fn insert_extra_newline_brackets(
21806 buffer: &MultiBufferSnapshot,
21807 range: Range<usize>,
21808 language: &language::LanguageScope,
21809) -> bool {
21810 let leading_whitespace_len = buffer
21811 .reversed_chars_at(range.start)
21812 .take_while(|c| c.is_whitespace() && *c != '\n')
21813 .map(|c| c.len_utf8())
21814 .sum::<usize>();
21815 let trailing_whitespace_len = buffer
21816 .chars_at(range.end)
21817 .take_while(|c| c.is_whitespace() && *c != '\n')
21818 .map(|c| c.len_utf8())
21819 .sum::<usize>();
21820 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21821
21822 language.brackets().any(|(pair, enabled)| {
21823 let pair_start = pair.start.trim_end();
21824 let pair_end = pair.end.trim_start();
21825
21826 enabled
21827 && pair.newline
21828 && buffer.contains_str_at(range.end, pair_end)
21829 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21830 })
21831}
21832
21833fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21834 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21835 [(buffer, range, _)] => (*buffer, range.clone()),
21836 _ => return false,
21837 };
21838 let pair = {
21839 let mut result: Option<BracketMatch> = None;
21840
21841 for pair in buffer
21842 .all_bracket_ranges(range.clone())
21843 .filter(move |pair| {
21844 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21845 })
21846 {
21847 let len = pair.close_range.end - pair.open_range.start;
21848
21849 if let Some(existing) = &result {
21850 let existing_len = existing.close_range.end - existing.open_range.start;
21851 if len > existing_len {
21852 continue;
21853 }
21854 }
21855
21856 result = Some(pair);
21857 }
21858
21859 result
21860 };
21861 let Some(pair) = pair else {
21862 return false;
21863 };
21864 pair.newline_only
21865 && buffer
21866 .chars_for_range(pair.open_range.end..range.start)
21867 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21868 .all(|c| c.is_whitespace() && c != '\n')
21869}
21870
21871fn update_uncommitted_diff_for_buffer(
21872 editor: Entity<Editor>,
21873 project: &Entity<Project>,
21874 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21875 buffer: Entity<MultiBuffer>,
21876 cx: &mut App,
21877) -> Task<()> {
21878 let mut tasks = Vec::new();
21879 project.update(cx, |project, cx| {
21880 for buffer in buffers {
21881 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21882 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21883 }
21884 }
21885 });
21886 cx.spawn(async move |cx| {
21887 let diffs = future::join_all(tasks).await;
21888 if editor
21889 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21890 .unwrap_or(false)
21891 {
21892 return;
21893 }
21894
21895 buffer
21896 .update(cx, |buffer, cx| {
21897 for diff in diffs.into_iter().flatten() {
21898 buffer.add_diff(diff, cx);
21899 }
21900 })
21901 .ok();
21902 })
21903}
21904
21905fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21906 let tab_size = tab_size.get() as usize;
21907 let mut width = offset;
21908
21909 for ch in text.chars() {
21910 width += if ch == '\t' {
21911 tab_size - (width % tab_size)
21912 } else {
21913 1
21914 };
21915 }
21916
21917 width - offset
21918}
21919
21920#[cfg(test)]
21921mod tests {
21922 use super::*;
21923
21924 #[test]
21925 fn test_string_size_with_expanded_tabs() {
21926 let nz = |val| NonZeroU32::new(val).unwrap();
21927 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21928 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21929 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21930 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21931 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21932 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21933 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21934 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21935 }
21936}
21937
21938/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21939struct WordBreakingTokenizer<'a> {
21940 input: &'a str,
21941}
21942
21943impl<'a> WordBreakingTokenizer<'a> {
21944 fn new(input: &'a str) -> Self {
21945 Self { input }
21946 }
21947}
21948
21949fn is_char_ideographic(ch: char) -> bool {
21950 use unicode_script::Script::*;
21951 use unicode_script::UnicodeScript;
21952 matches!(ch.script(), Han | Tangut | Yi)
21953}
21954
21955fn is_grapheme_ideographic(text: &str) -> bool {
21956 text.chars().any(is_char_ideographic)
21957}
21958
21959fn is_grapheme_whitespace(text: &str) -> bool {
21960 text.chars().any(|x| x.is_whitespace())
21961}
21962
21963fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21964 text.chars()
21965 .next()
21966 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21967}
21968
21969#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21970enum WordBreakToken<'a> {
21971 Word { token: &'a str, grapheme_len: usize },
21972 InlineWhitespace { token: &'a str, grapheme_len: usize },
21973 Newline,
21974}
21975
21976impl<'a> Iterator for WordBreakingTokenizer<'a> {
21977 /// Yields a span, the count of graphemes in the token, and whether it was
21978 /// whitespace. Note that it also breaks at word boundaries.
21979 type Item = WordBreakToken<'a>;
21980
21981 fn next(&mut self) -> Option<Self::Item> {
21982 use unicode_segmentation::UnicodeSegmentation;
21983 if self.input.is_empty() {
21984 return None;
21985 }
21986
21987 let mut iter = self.input.graphemes(true).peekable();
21988 let mut offset = 0;
21989 let mut grapheme_len = 0;
21990 if let Some(first_grapheme) = iter.next() {
21991 let is_newline = first_grapheme == "\n";
21992 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21993 offset += first_grapheme.len();
21994 grapheme_len += 1;
21995 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21996 if let Some(grapheme) = iter.peek().copied()
21997 && should_stay_with_preceding_ideograph(grapheme)
21998 {
21999 offset += grapheme.len();
22000 grapheme_len += 1;
22001 }
22002 } else {
22003 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22004 let mut next_word_bound = words.peek().copied();
22005 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22006 next_word_bound = words.next();
22007 }
22008 while let Some(grapheme) = iter.peek().copied() {
22009 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22010 break;
22011 };
22012 if is_grapheme_whitespace(grapheme) != is_whitespace
22013 || (grapheme == "\n") != is_newline
22014 {
22015 break;
22016 };
22017 offset += grapheme.len();
22018 grapheme_len += 1;
22019 iter.next();
22020 }
22021 }
22022 let token = &self.input[..offset];
22023 self.input = &self.input[offset..];
22024 if token == "\n" {
22025 Some(WordBreakToken::Newline)
22026 } else if is_whitespace {
22027 Some(WordBreakToken::InlineWhitespace {
22028 token,
22029 grapheme_len,
22030 })
22031 } else {
22032 Some(WordBreakToken::Word {
22033 token,
22034 grapheme_len,
22035 })
22036 }
22037 } else {
22038 None
22039 }
22040 }
22041}
22042
22043#[test]
22044fn test_word_breaking_tokenizer() {
22045 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22046 ("", &[]),
22047 (" ", &[whitespace(" ", 2)]),
22048 ("Ʒ", &[word("Ʒ", 1)]),
22049 ("Ǽ", &[word("Ǽ", 1)]),
22050 ("⋑", &[word("⋑", 1)]),
22051 ("⋑⋑", &[word("⋑⋑", 2)]),
22052 (
22053 "原理,进而",
22054 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22055 ),
22056 (
22057 "hello world",
22058 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22059 ),
22060 (
22061 "hello, world",
22062 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22063 ),
22064 (
22065 " hello world",
22066 &[
22067 whitespace(" ", 2),
22068 word("hello", 5),
22069 whitespace(" ", 1),
22070 word("world", 5),
22071 ],
22072 ),
22073 (
22074 "这是什么 \n 钢笔",
22075 &[
22076 word("这", 1),
22077 word("是", 1),
22078 word("什", 1),
22079 word("么", 1),
22080 whitespace(" ", 1),
22081 newline(),
22082 whitespace(" ", 1),
22083 word("钢", 1),
22084 word("笔", 1),
22085 ],
22086 ),
22087 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22088 ];
22089
22090 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22091 WordBreakToken::Word {
22092 token,
22093 grapheme_len,
22094 }
22095 }
22096
22097 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22098 WordBreakToken::InlineWhitespace {
22099 token,
22100 grapheme_len,
22101 }
22102 }
22103
22104 fn newline() -> WordBreakToken<'static> {
22105 WordBreakToken::Newline
22106 }
22107
22108 for (input, result) in tests {
22109 assert_eq!(
22110 WordBreakingTokenizer::new(input)
22111 .collect::<Vec<_>>()
22112 .as_slice(),
22113 *result,
22114 );
22115 }
22116}
22117
22118fn wrap_with_prefix(
22119 first_line_prefix: String,
22120 subsequent_lines_prefix: String,
22121 unwrapped_text: String,
22122 wrap_column: usize,
22123 tab_size: NonZeroU32,
22124 preserve_existing_whitespace: bool,
22125) -> String {
22126 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22127 let subsequent_lines_prefix_len =
22128 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22129 let mut wrapped_text = String::new();
22130 let mut current_line = first_line_prefix;
22131 let mut is_first_line = true;
22132
22133 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22134 let mut current_line_len = first_line_prefix_len;
22135 let mut in_whitespace = false;
22136 for token in tokenizer {
22137 let have_preceding_whitespace = in_whitespace;
22138 match token {
22139 WordBreakToken::Word {
22140 token,
22141 grapheme_len,
22142 } => {
22143 in_whitespace = false;
22144 let current_prefix_len = if is_first_line {
22145 first_line_prefix_len
22146 } else {
22147 subsequent_lines_prefix_len
22148 };
22149 if current_line_len + grapheme_len > wrap_column
22150 && current_line_len != current_prefix_len
22151 {
22152 wrapped_text.push_str(current_line.trim_end());
22153 wrapped_text.push('\n');
22154 is_first_line = false;
22155 current_line = subsequent_lines_prefix.clone();
22156 current_line_len = subsequent_lines_prefix_len;
22157 }
22158 current_line.push_str(token);
22159 current_line_len += grapheme_len;
22160 }
22161 WordBreakToken::InlineWhitespace {
22162 mut token,
22163 mut grapheme_len,
22164 } => {
22165 in_whitespace = true;
22166 if have_preceding_whitespace && !preserve_existing_whitespace {
22167 continue;
22168 }
22169 if !preserve_existing_whitespace {
22170 token = " ";
22171 grapheme_len = 1;
22172 }
22173 let current_prefix_len = if is_first_line {
22174 first_line_prefix_len
22175 } else {
22176 subsequent_lines_prefix_len
22177 };
22178 if current_line_len + grapheme_len > wrap_column {
22179 wrapped_text.push_str(current_line.trim_end());
22180 wrapped_text.push('\n');
22181 is_first_line = false;
22182 current_line = subsequent_lines_prefix.clone();
22183 current_line_len = subsequent_lines_prefix_len;
22184 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22185 current_line.push_str(token);
22186 current_line_len += grapheme_len;
22187 }
22188 }
22189 WordBreakToken::Newline => {
22190 in_whitespace = true;
22191 let current_prefix_len = if is_first_line {
22192 first_line_prefix_len
22193 } else {
22194 subsequent_lines_prefix_len
22195 };
22196 if preserve_existing_whitespace {
22197 wrapped_text.push_str(current_line.trim_end());
22198 wrapped_text.push('\n');
22199 is_first_line = false;
22200 current_line = subsequent_lines_prefix.clone();
22201 current_line_len = subsequent_lines_prefix_len;
22202 } else if have_preceding_whitespace {
22203 continue;
22204 } else if current_line_len + 1 > wrap_column
22205 && current_line_len != current_prefix_len
22206 {
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 {
22213 current_line.push(' ');
22214 current_line_len += 1;
22215 }
22216 }
22217 }
22218 }
22219
22220 if !current_line.is_empty() {
22221 wrapped_text.push_str(¤t_line);
22222 }
22223 wrapped_text
22224}
22225
22226#[test]
22227fn test_wrap_with_prefix() {
22228 assert_eq!(
22229 wrap_with_prefix(
22230 "# ".to_string(),
22231 "# ".to_string(),
22232 "abcdefg".to_string(),
22233 4,
22234 NonZeroU32::new(4).unwrap(),
22235 false,
22236 ),
22237 "# abcdefg"
22238 );
22239 assert_eq!(
22240 wrap_with_prefix(
22241 "".to_string(),
22242 "".to_string(),
22243 "\thello world".to_string(),
22244 8,
22245 NonZeroU32::new(4).unwrap(),
22246 false,
22247 ),
22248 "hello\nworld"
22249 );
22250 assert_eq!(
22251 wrap_with_prefix(
22252 "// ".to_string(),
22253 "// ".to_string(),
22254 "xx \nyy zz aa bb cc".to_string(),
22255 12,
22256 NonZeroU32::new(4).unwrap(),
22257 false,
22258 ),
22259 "// xx yy zz\n// aa bb cc"
22260 );
22261 assert_eq!(
22262 wrap_with_prefix(
22263 String::new(),
22264 String::new(),
22265 "这是什么 \n 钢笔".to_string(),
22266 3,
22267 NonZeroU32::new(4).unwrap(),
22268 false,
22269 ),
22270 "这是什\n么 钢\n笔"
22271 );
22272}
22273
22274pub trait CollaborationHub {
22275 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22276 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22277 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22278}
22279
22280impl CollaborationHub for Entity<Project> {
22281 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22282 self.read(cx).collaborators()
22283 }
22284
22285 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22286 self.read(cx).user_store().read(cx).participant_indices()
22287 }
22288
22289 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22290 let this = self.read(cx);
22291 let user_ids = this.collaborators().values().map(|c| c.user_id);
22292 this.user_store().read(cx).participant_names(user_ids, cx)
22293 }
22294}
22295
22296pub trait SemanticsProvider {
22297 fn hover(
22298 &self,
22299 buffer: &Entity<Buffer>,
22300 position: text::Anchor,
22301 cx: &mut App,
22302 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22303
22304 fn inline_values(
22305 &self,
22306 buffer_handle: Entity<Buffer>,
22307 range: Range<text::Anchor>,
22308 cx: &mut App,
22309 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22310
22311 fn inlay_hints(
22312 &self,
22313 buffer_handle: Entity<Buffer>,
22314 range: Range<text::Anchor>,
22315 cx: &mut App,
22316 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22317
22318 fn resolve_inlay_hint(
22319 &self,
22320 hint: InlayHint,
22321 buffer_handle: Entity<Buffer>,
22322 server_id: LanguageServerId,
22323 cx: &mut App,
22324 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22325
22326 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22327
22328 fn document_highlights(
22329 &self,
22330 buffer: &Entity<Buffer>,
22331 position: text::Anchor,
22332 cx: &mut App,
22333 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22334
22335 fn definitions(
22336 &self,
22337 buffer: &Entity<Buffer>,
22338 position: text::Anchor,
22339 kind: GotoDefinitionKind,
22340 cx: &mut App,
22341 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22342
22343 fn range_for_rename(
22344 &self,
22345 buffer: &Entity<Buffer>,
22346 position: text::Anchor,
22347 cx: &mut App,
22348 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22349
22350 fn perform_rename(
22351 &self,
22352 buffer: &Entity<Buffer>,
22353 position: text::Anchor,
22354 new_name: String,
22355 cx: &mut App,
22356 ) -> Option<Task<Result<ProjectTransaction>>>;
22357}
22358
22359pub trait CompletionProvider {
22360 fn completions(
22361 &self,
22362 excerpt_id: ExcerptId,
22363 buffer: &Entity<Buffer>,
22364 buffer_position: text::Anchor,
22365 trigger: CompletionContext,
22366 window: &mut Window,
22367 cx: &mut Context<Editor>,
22368 ) -> Task<Result<Vec<CompletionResponse>>>;
22369
22370 fn resolve_completions(
22371 &self,
22372 _buffer: Entity<Buffer>,
22373 _completion_indices: Vec<usize>,
22374 _completions: Rc<RefCell<Box<[Completion]>>>,
22375 _cx: &mut Context<Editor>,
22376 ) -> Task<Result<bool>> {
22377 Task::ready(Ok(false))
22378 }
22379
22380 fn apply_additional_edits_for_completion(
22381 &self,
22382 _buffer: Entity<Buffer>,
22383 _completions: Rc<RefCell<Box<[Completion]>>>,
22384 _completion_index: usize,
22385 _push_to_history: bool,
22386 _cx: &mut Context<Editor>,
22387 ) -> Task<Result<Option<language::Transaction>>> {
22388 Task::ready(Ok(None))
22389 }
22390
22391 fn is_completion_trigger(
22392 &self,
22393 buffer: &Entity<Buffer>,
22394 position: language::Anchor,
22395 text: &str,
22396 trigger_in_words: bool,
22397 menu_is_open: bool,
22398 cx: &mut Context<Editor>,
22399 ) -> bool;
22400
22401 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22402
22403 fn sort_completions(&self) -> bool {
22404 true
22405 }
22406
22407 fn filter_completions(&self) -> bool {
22408 true
22409 }
22410}
22411
22412pub trait CodeActionProvider {
22413 fn id(&self) -> Arc<str>;
22414
22415 fn code_actions(
22416 &self,
22417 buffer: &Entity<Buffer>,
22418 range: Range<text::Anchor>,
22419 window: &mut Window,
22420 cx: &mut App,
22421 ) -> Task<Result<Vec<CodeAction>>>;
22422
22423 fn apply_code_action(
22424 &self,
22425 buffer_handle: Entity<Buffer>,
22426 action: CodeAction,
22427 excerpt_id: ExcerptId,
22428 push_to_history: bool,
22429 window: &mut Window,
22430 cx: &mut App,
22431 ) -> Task<Result<ProjectTransaction>>;
22432}
22433
22434impl CodeActionProvider for Entity<Project> {
22435 fn id(&self) -> Arc<str> {
22436 "project".into()
22437 }
22438
22439 fn code_actions(
22440 &self,
22441 buffer: &Entity<Buffer>,
22442 range: Range<text::Anchor>,
22443 _window: &mut Window,
22444 cx: &mut App,
22445 ) -> Task<Result<Vec<CodeAction>>> {
22446 self.update(cx, |project, cx| {
22447 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22448 let code_actions = project.code_actions(buffer, range, None, cx);
22449 cx.background_spawn(async move {
22450 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22451 Ok(code_lens_actions
22452 .context("code lens fetch")?
22453 .into_iter()
22454 .flatten()
22455 .chain(
22456 code_actions
22457 .context("code action fetch")?
22458 .into_iter()
22459 .flatten(),
22460 )
22461 .collect())
22462 })
22463 })
22464 }
22465
22466 fn apply_code_action(
22467 &self,
22468 buffer_handle: Entity<Buffer>,
22469 action: CodeAction,
22470 _excerpt_id: ExcerptId,
22471 push_to_history: bool,
22472 _window: &mut Window,
22473 cx: &mut App,
22474 ) -> Task<Result<ProjectTransaction>> {
22475 self.update(cx, |project, cx| {
22476 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22477 })
22478 }
22479}
22480
22481fn snippet_completions(
22482 project: &Project,
22483 buffer: &Entity<Buffer>,
22484 buffer_position: text::Anchor,
22485 cx: &mut App,
22486) -> Task<Result<CompletionResponse>> {
22487 let languages = buffer.read(cx).languages_at(buffer_position);
22488 let snippet_store = project.snippets().read(cx);
22489
22490 let scopes: Vec<_> = languages
22491 .iter()
22492 .filter_map(|language| {
22493 let language_name = language.lsp_id();
22494 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22495
22496 if snippets.is_empty() {
22497 None
22498 } else {
22499 Some((language.default_scope(), snippets))
22500 }
22501 })
22502 .collect();
22503
22504 if scopes.is_empty() {
22505 return Task::ready(Ok(CompletionResponse {
22506 completions: vec![],
22507 display_options: CompletionDisplayOptions::default(),
22508 is_incomplete: false,
22509 }));
22510 }
22511
22512 let snapshot = buffer.read(cx).text_snapshot();
22513 let chars: String = snapshot
22514 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22515 .collect();
22516 let executor = cx.background_executor().clone();
22517
22518 cx.background_spawn(async move {
22519 let mut is_incomplete = false;
22520 let mut completions: Vec<Completion> = Vec::new();
22521 for (scope, snippets) in scopes.into_iter() {
22522 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22523 let mut last_word = chars
22524 .chars()
22525 .take_while(|c| classifier.is_word(*c))
22526 .collect::<String>();
22527 last_word = last_word.chars().rev().collect();
22528
22529 if last_word.is_empty() {
22530 return Ok(CompletionResponse {
22531 completions: vec![],
22532 display_options: CompletionDisplayOptions::default(),
22533 is_incomplete: true,
22534 });
22535 }
22536
22537 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22538 let to_lsp = |point: &text::Anchor| {
22539 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22540 point_to_lsp(end)
22541 };
22542 let lsp_end = to_lsp(&buffer_position);
22543
22544 let candidates = snippets
22545 .iter()
22546 .enumerate()
22547 .flat_map(|(ix, snippet)| {
22548 snippet
22549 .prefix
22550 .iter()
22551 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22552 })
22553 .collect::<Vec<StringMatchCandidate>>();
22554
22555 const MAX_RESULTS: usize = 100;
22556 let mut matches = fuzzy::match_strings(
22557 &candidates,
22558 &last_word,
22559 last_word.chars().any(|c| c.is_uppercase()),
22560 true,
22561 MAX_RESULTS,
22562 &Default::default(),
22563 executor.clone(),
22564 )
22565 .await;
22566
22567 if matches.len() >= MAX_RESULTS {
22568 is_incomplete = true;
22569 }
22570
22571 // Remove all candidates where the query's start does not match the start of any word in the candidate
22572 if let Some(query_start) = last_word.chars().next() {
22573 matches.retain(|string_match| {
22574 split_words(&string_match.string).any(|word| {
22575 // Check that the first codepoint of the word as lowercase matches the first
22576 // codepoint of the query as lowercase
22577 word.chars()
22578 .flat_map(|codepoint| codepoint.to_lowercase())
22579 .zip(query_start.to_lowercase())
22580 .all(|(word_cp, query_cp)| word_cp == query_cp)
22581 })
22582 });
22583 }
22584
22585 let matched_strings = matches
22586 .into_iter()
22587 .map(|m| m.string)
22588 .collect::<HashSet<_>>();
22589
22590 completions.extend(snippets.iter().filter_map(|snippet| {
22591 let matching_prefix = snippet
22592 .prefix
22593 .iter()
22594 .find(|prefix| matched_strings.contains(*prefix))?;
22595 let start = as_offset - last_word.len();
22596 let start = snapshot.anchor_before(start);
22597 let range = start..buffer_position;
22598 let lsp_start = to_lsp(&start);
22599 let lsp_range = lsp::Range {
22600 start: lsp_start,
22601 end: lsp_end,
22602 };
22603 Some(Completion {
22604 replace_range: range,
22605 new_text: snippet.body.clone(),
22606 source: CompletionSource::Lsp {
22607 insert_range: None,
22608 server_id: LanguageServerId(usize::MAX),
22609 resolved: true,
22610 lsp_completion: Box::new(lsp::CompletionItem {
22611 label: snippet.prefix.first().unwrap().clone(),
22612 kind: Some(CompletionItemKind::SNIPPET),
22613 label_details: snippet.description.as_ref().map(|description| {
22614 lsp::CompletionItemLabelDetails {
22615 detail: Some(description.clone()),
22616 description: None,
22617 }
22618 }),
22619 insert_text_format: Some(InsertTextFormat::SNIPPET),
22620 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22621 lsp::InsertReplaceEdit {
22622 new_text: snippet.body.clone(),
22623 insert: lsp_range,
22624 replace: lsp_range,
22625 },
22626 )),
22627 filter_text: Some(snippet.body.clone()),
22628 sort_text: Some(char::MAX.to_string()),
22629 ..lsp::CompletionItem::default()
22630 }),
22631 lsp_defaults: None,
22632 },
22633 label: CodeLabel {
22634 text: matching_prefix.clone(),
22635 runs: Vec::new(),
22636 filter_range: 0..matching_prefix.len(),
22637 },
22638 icon_path: None,
22639 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22640 single_line: snippet.name.clone().into(),
22641 plain_text: snippet
22642 .description
22643 .clone()
22644 .map(|description| description.into()),
22645 }),
22646 insert_text_mode: None,
22647 confirm: None,
22648 })
22649 }))
22650 }
22651
22652 Ok(CompletionResponse {
22653 completions,
22654 display_options: CompletionDisplayOptions::default(),
22655 is_incomplete,
22656 })
22657 })
22658}
22659
22660impl CompletionProvider for Entity<Project> {
22661 fn completions(
22662 &self,
22663 _excerpt_id: ExcerptId,
22664 buffer: &Entity<Buffer>,
22665 buffer_position: text::Anchor,
22666 options: CompletionContext,
22667 _window: &mut Window,
22668 cx: &mut Context<Editor>,
22669 ) -> Task<Result<Vec<CompletionResponse>>> {
22670 self.update(cx, |project, cx| {
22671 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22672 let project_completions = project.completions(buffer, buffer_position, options, cx);
22673 cx.background_spawn(async move {
22674 let mut responses = project_completions.await?;
22675 let snippets = snippets.await?;
22676 if !snippets.completions.is_empty() {
22677 responses.push(snippets);
22678 }
22679 Ok(responses)
22680 })
22681 })
22682 }
22683
22684 fn resolve_completions(
22685 &self,
22686 buffer: Entity<Buffer>,
22687 completion_indices: Vec<usize>,
22688 completions: Rc<RefCell<Box<[Completion]>>>,
22689 cx: &mut Context<Editor>,
22690 ) -> Task<Result<bool>> {
22691 self.update(cx, |project, cx| {
22692 project.lsp_store().update(cx, |lsp_store, cx| {
22693 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22694 })
22695 })
22696 }
22697
22698 fn apply_additional_edits_for_completion(
22699 &self,
22700 buffer: Entity<Buffer>,
22701 completions: Rc<RefCell<Box<[Completion]>>>,
22702 completion_index: usize,
22703 push_to_history: bool,
22704 cx: &mut Context<Editor>,
22705 ) -> Task<Result<Option<language::Transaction>>> {
22706 self.update(cx, |project, cx| {
22707 project.lsp_store().update(cx, |lsp_store, cx| {
22708 lsp_store.apply_additional_edits_for_completion(
22709 buffer,
22710 completions,
22711 completion_index,
22712 push_to_history,
22713 cx,
22714 )
22715 })
22716 })
22717 }
22718
22719 fn is_completion_trigger(
22720 &self,
22721 buffer: &Entity<Buffer>,
22722 position: language::Anchor,
22723 text: &str,
22724 trigger_in_words: bool,
22725 menu_is_open: bool,
22726 cx: &mut Context<Editor>,
22727 ) -> bool {
22728 let mut chars = text.chars();
22729 let char = if let Some(char) = chars.next() {
22730 char
22731 } else {
22732 return false;
22733 };
22734 if chars.next().is_some() {
22735 return false;
22736 }
22737
22738 let buffer = buffer.read(cx);
22739 let snapshot = buffer.snapshot();
22740 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22741 return false;
22742 }
22743 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22744 if trigger_in_words && classifier.is_word(char) {
22745 return true;
22746 }
22747
22748 buffer.completion_triggers().contains(text)
22749 }
22750}
22751
22752impl SemanticsProvider for Entity<Project> {
22753 fn hover(
22754 &self,
22755 buffer: &Entity<Buffer>,
22756 position: text::Anchor,
22757 cx: &mut App,
22758 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22759 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22760 }
22761
22762 fn document_highlights(
22763 &self,
22764 buffer: &Entity<Buffer>,
22765 position: text::Anchor,
22766 cx: &mut App,
22767 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22768 Some(self.update(cx, |project, cx| {
22769 project.document_highlights(buffer, position, cx)
22770 }))
22771 }
22772
22773 fn definitions(
22774 &self,
22775 buffer: &Entity<Buffer>,
22776 position: text::Anchor,
22777 kind: GotoDefinitionKind,
22778 cx: &mut App,
22779 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22780 Some(self.update(cx, |project, cx| match kind {
22781 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22782 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22783 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22784 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22785 }))
22786 }
22787
22788 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22789 self.update(cx, |project, cx| {
22790 if project
22791 .active_debug_session(cx)
22792 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22793 {
22794 return true;
22795 }
22796
22797 buffer.update(cx, |buffer, cx| {
22798 project.any_language_server_supports_inlay_hints(buffer, cx)
22799 })
22800 })
22801 }
22802
22803 fn inline_values(
22804 &self,
22805 buffer_handle: Entity<Buffer>,
22806 range: Range<text::Anchor>,
22807 cx: &mut App,
22808 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22809 self.update(cx, |project, cx| {
22810 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22811
22812 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22813 })
22814 }
22815
22816 fn inlay_hints(
22817 &self,
22818 buffer_handle: Entity<Buffer>,
22819 range: Range<text::Anchor>,
22820 cx: &mut App,
22821 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22822 Some(self.update(cx, |project, cx| {
22823 project.inlay_hints(buffer_handle, range, cx)
22824 }))
22825 }
22826
22827 fn resolve_inlay_hint(
22828 &self,
22829 hint: InlayHint,
22830 buffer_handle: Entity<Buffer>,
22831 server_id: LanguageServerId,
22832 cx: &mut App,
22833 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22834 Some(self.update(cx, |project, cx| {
22835 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22836 }))
22837 }
22838
22839 fn range_for_rename(
22840 &self,
22841 buffer: &Entity<Buffer>,
22842 position: text::Anchor,
22843 cx: &mut App,
22844 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22845 Some(self.update(cx, |project, cx| {
22846 let buffer = buffer.clone();
22847 let task = project.prepare_rename(buffer.clone(), position, cx);
22848 cx.spawn(async move |_, cx| {
22849 Ok(match task.await? {
22850 PrepareRenameResponse::Success(range) => Some(range),
22851 PrepareRenameResponse::InvalidPosition => None,
22852 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22853 // Fallback on using TreeSitter info to determine identifier range
22854 buffer.read_with(cx, |buffer, _| {
22855 let snapshot = buffer.snapshot();
22856 let (range, kind) = snapshot.surrounding_word(position, false);
22857 if kind != Some(CharKind::Word) {
22858 return None;
22859 }
22860 Some(
22861 snapshot.anchor_before(range.start)
22862 ..snapshot.anchor_after(range.end),
22863 )
22864 })?
22865 }
22866 })
22867 })
22868 }))
22869 }
22870
22871 fn perform_rename(
22872 &self,
22873 buffer: &Entity<Buffer>,
22874 position: text::Anchor,
22875 new_name: String,
22876 cx: &mut App,
22877 ) -> Option<Task<Result<ProjectTransaction>>> {
22878 Some(self.update(cx, |project, cx| {
22879 project.perform_rename(buffer.clone(), position, new_name, cx)
22880 }))
22881 }
22882}
22883
22884fn inlay_hint_settings(
22885 location: Anchor,
22886 snapshot: &MultiBufferSnapshot,
22887 cx: &mut Context<Editor>,
22888) -> InlayHintSettings {
22889 let file = snapshot.file_at(location);
22890 let language = snapshot.language_at(location).map(|l| l.name());
22891 language_settings(language, file, cx).inlay_hints
22892}
22893
22894fn consume_contiguous_rows(
22895 contiguous_row_selections: &mut Vec<Selection<Point>>,
22896 selection: &Selection<Point>,
22897 display_map: &DisplaySnapshot,
22898 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22899) -> (MultiBufferRow, MultiBufferRow) {
22900 contiguous_row_selections.push(selection.clone());
22901 let start_row = starting_row(selection, display_map);
22902 let mut end_row = ending_row(selection, display_map);
22903
22904 while let Some(next_selection) = selections.peek() {
22905 if next_selection.start.row <= end_row.0 {
22906 end_row = ending_row(next_selection, display_map);
22907 contiguous_row_selections.push(selections.next().unwrap().clone());
22908 } else {
22909 break;
22910 }
22911 }
22912 (start_row, end_row)
22913}
22914
22915fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22916 if selection.start.column > 0 {
22917 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22918 } else {
22919 MultiBufferRow(selection.start.row)
22920 }
22921}
22922
22923fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22924 if next_selection.end.column > 0 || next_selection.is_empty() {
22925 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22926 } else {
22927 MultiBufferRow(next_selection.end.row)
22928 }
22929}
22930
22931impl EditorSnapshot {
22932 pub fn remote_selections_in_range<'a>(
22933 &'a self,
22934 range: &'a Range<Anchor>,
22935 collaboration_hub: &dyn CollaborationHub,
22936 cx: &'a App,
22937 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22938 let participant_names = collaboration_hub.user_names(cx);
22939 let participant_indices = collaboration_hub.user_participant_indices(cx);
22940 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22941 let collaborators_by_replica_id = collaborators_by_peer_id
22942 .values()
22943 .map(|collaborator| (collaborator.replica_id, collaborator))
22944 .collect::<HashMap<_, _>>();
22945 self.buffer_snapshot
22946 .selections_in_range(range, false)
22947 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22948 if replica_id == AGENT_REPLICA_ID {
22949 Some(RemoteSelection {
22950 replica_id,
22951 selection,
22952 cursor_shape,
22953 line_mode,
22954 collaborator_id: CollaboratorId::Agent,
22955 user_name: Some("Agent".into()),
22956 color: cx.theme().players().agent(),
22957 })
22958 } else {
22959 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22960 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22961 let user_name = participant_names.get(&collaborator.user_id).cloned();
22962 Some(RemoteSelection {
22963 replica_id,
22964 selection,
22965 cursor_shape,
22966 line_mode,
22967 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22968 user_name,
22969 color: if let Some(index) = participant_index {
22970 cx.theme().players().color_for_participant(index.0)
22971 } else {
22972 cx.theme().players().absent()
22973 },
22974 })
22975 }
22976 })
22977 }
22978
22979 pub fn hunks_for_ranges(
22980 &self,
22981 ranges: impl IntoIterator<Item = Range<Point>>,
22982 ) -> Vec<MultiBufferDiffHunk> {
22983 let mut hunks = Vec::new();
22984 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22985 HashMap::default();
22986 for query_range in ranges {
22987 let query_rows =
22988 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22989 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22990 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22991 ) {
22992 // Include deleted hunks that are adjacent to the query range, because
22993 // otherwise they would be missed.
22994 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22995 if hunk.status().is_deleted() {
22996 intersects_range |= hunk.row_range.start == query_rows.end;
22997 intersects_range |= hunk.row_range.end == query_rows.start;
22998 }
22999 if intersects_range {
23000 if !processed_buffer_rows
23001 .entry(hunk.buffer_id)
23002 .or_default()
23003 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23004 {
23005 continue;
23006 }
23007 hunks.push(hunk);
23008 }
23009 }
23010 }
23011
23012 hunks
23013 }
23014
23015 fn display_diff_hunks_for_rows<'a>(
23016 &'a self,
23017 display_rows: Range<DisplayRow>,
23018 folded_buffers: &'a HashSet<BufferId>,
23019 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23020 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23021 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23022
23023 self.buffer_snapshot
23024 .diff_hunks_in_range(buffer_start..buffer_end)
23025 .filter_map(|hunk| {
23026 if folded_buffers.contains(&hunk.buffer_id) {
23027 return None;
23028 }
23029
23030 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23031 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23032
23033 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23034 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23035
23036 let display_hunk = if hunk_display_start.column() != 0 {
23037 DisplayDiffHunk::Folded {
23038 display_row: hunk_display_start.row(),
23039 }
23040 } else {
23041 let mut end_row = hunk_display_end.row();
23042 if hunk_display_end.column() > 0 {
23043 end_row.0 += 1;
23044 }
23045 let is_created_file = hunk.is_created_file();
23046 DisplayDiffHunk::Unfolded {
23047 status: hunk.status(),
23048 diff_base_byte_range: hunk.diff_base_byte_range,
23049 display_row_range: hunk_display_start.row()..end_row,
23050 multi_buffer_range: Anchor::range_in_buffer(
23051 hunk.excerpt_id,
23052 hunk.buffer_id,
23053 hunk.buffer_range,
23054 ),
23055 is_created_file,
23056 }
23057 };
23058
23059 Some(display_hunk)
23060 })
23061 }
23062
23063 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23064 self.display_snapshot.buffer_snapshot.language_at(position)
23065 }
23066
23067 pub fn is_focused(&self) -> bool {
23068 self.is_focused
23069 }
23070
23071 pub fn placeholder_text(&self) -> Option<String> {
23072 self.placeholder_display_snapshot
23073 .as_ref()
23074 .map(|display_map| display_map.text())
23075 }
23076
23077 pub fn scroll_position(&self) -> gpui::Point<f32> {
23078 self.scroll_anchor.scroll_position(&self.display_snapshot)
23079 }
23080
23081 fn gutter_dimensions(
23082 &self,
23083 font_id: FontId,
23084 font_size: Pixels,
23085 max_line_number_width: Pixels,
23086 cx: &App,
23087 ) -> Option<GutterDimensions> {
23088 if !self.show_gutter {
23089 return None;
23090 }
23091
23092 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23093 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23094
23095 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23096 matches!(
23097 ProjectSettings::get_global(cx).git.git_gutter,
23098 Some(GitGutterSetting::TrackedFiles)
23099 )
23100 });
23101 let gutter_settings = EditorSettings::get_global(cx).gutter;
23102 let show_line_numbers = self
23103 .show_line_numbers
23104 .unwrap_or(gutter_settings.line_numbers);
23105 let line_gutter_width = if show_line_numbers {
23106 // Avoid flicker-like gutter resizes when the line number gains another digit by
23107 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23108 let min_width_for_number_on_gutter =
23109 ch_advance * gutter_settings.min_line_number_digits as f32;
23110 max_line_number_width.max(min_width_for_number_on_gutter)
23111 } else {
23112 0.0.into()
23113 };
23114
23115 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23116 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23117
23118 let git_blame_entries_width =
23119 self.git_blame_gutter_max_author_length
23120 .map(|max_author_length| {
23121 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23122 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23123
23124 /// The number of characters to dedicate to gaps and margins.
23125 const SPACING_WIDTH: usize = 4;
23126
23127 let max_char_count = max_author_length.min(renderer.max_author_length())
23128 + ::git::SHORT_SHA_LENGTH
23129 + MAX_RELATIVE_TIMESTAMP.len()
23130 + SPACING_WIDTH;
23131
23132 ch_advance * max_char_count
23133 });
23134
23135 let is_singleton = self.buffer_snapshot.is_singleton();
23136
23137 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23138 left_padding += if !is_singleton {
23139 ch_width * 4.0
23140 } else if show_runnables || show_breakpoints {
23141 ch_width * 3.0
23142 } else if show_git_gutter && show_line_numbers {
23143 ch_width * 2.0
23144 } else if show_git_gutter || show_line_numbers {
23145 ch_width
23146 } else {
23147 px(0.)
23148 };
23149
23150 let shows_folds = is_singleton && gutter_settings.folds;
23151
23152 let right_padding = if shows_folds && show_line_numbers {
23153 ch_width * 4.0
23154 } else if shows_folds || (!is_singleton && show_line_numbers) {
23155 ch_width * 3.0
23156 } else if show_line_numbers {
23157 ch_width
23158 } else {
23159 px(0.)
23160 };
23161
23162 Some(GutterDimensions {
23163 left_padding,
23164 right_padding,
23165 width: line_gutter_width + left_padding + right_padding,
23166 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23167 git_blame_entries_width,
23168 })
23169 }
23170
23171 pub fn render_crease_toggle(
23172 &self,
23173 buffer_row: MultiBufferRow,
23174 row_contains_cursor: bool,
23175 editor: Entity<Editor>,
23176 window: &mut Window,
23177 cx: &mut App,
23178 ) -> Option<AnyElement> {
23179 let folded = self.is_line_folded(buffer_row);
23180 let mut is_foldable = false;
23181
23182 if let Some(crease) = self
23183 .crease_snapshot
23184 .query_row(buffer_row, &self.buffer_snapshot)
23185 {
23186 is_foldable = true;
23187 match crease {
23188 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23189 if let Some(render_toggle) = render_toggle {
23190 let toggle_callback =
23191 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23192 if folded {
23193 editor.update(cx, |editor, cx| {
23194 editor.fold_at(buffer_row, window, cx)
23195 });
23196 } else {
23197 editor.update(cx, |editor, cx| {
23198 editor.unfold_at(buffer_row, window, cx)
23199 });
23200 }
23201 });
23202 return Some((render_toggle)(
23203 buffer_row,
23204 folded,
23205 toggle_callback,
23206 window,
23207 cx,
23208 ));
23209 }
23210 }
23211 }
23212 }
23213
23214 is_foldable |= self.starts_indent(buffer_row);
23215
23216 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23217 Some(
23218 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23219 .toggle_state(folded)
23220 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23221 if folded {
23222 this.unfold_at(buffer_row, window, cx);
23223 } else {
23224 this.fold_at(buffer_row, window, cx);
23225 }
23226 }))
23227 .into_any_element(),
23228 )
23229 } else {
23230 None
23231 }
23232 }
23233
23234 pub fn render_crease_trailer(
23235 &self,
23236 buffer_row: MultiBufferRow,
23237 window: &mut Window,
23238 cx: &mut App,
23239 ) -> Option<AnyElement> {
23240 let folded = self.is_line_folded(buffer_row);
23241 if let Crease::Inline { render_trailer, .. } = self
23242 .crease_snapshot
23243 .query_row(buffer_row, &self.buffer_snapshot)?
23244 {
23245 let render_trailer = render_trailer.as_ref()?;
23246 Some(render_trailer(buffer_row, folded, window, cx))
23247 } else {
23248 None
23249 }
23250 }
23251}
23252
23253impl Deref for EditorSnapshot {
23254 type Target = DisplaySnapshot;
23255
23256 fn deref(&self) -> &Self::Target {
23257 &self.display_snapshot
23258 }
23259}
23260
23261#[derive(Clone, Debug, PartialEq, Eq)]
23262pub enum EditorEvent {
23263 InputIgnored {
23264 text: Arc<str>,
23265 },
23266 InputHandled {
23267 utf16_range_to_replace: Option<Range<isize>>,
23268 text: Arc<str>,
23269 },
23270 ExcerptsAdded {
23271 buffer: Entity<Buffer>,
23272 predecessor: ExcerptId,
23273 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23274 },
23275 ExcerptsRemoved {
23276 ids: Vec<ExcerptId>,
23277 removed_buffer_ids: Vec<BufferId>,
23278 },
23279 BufferFoldToggled {
23280 ids: Vec<ExcerptId>,
23281 folded: bool,
23282 },
23283 ExcerptsEdited {
23284 ids: Vec<ExcerptId>,
23285 },
23286 ExcerptsExpanded {
23287 ids: Vec<ExcerptId>,
23288 },
23289 BufferEdited,
23290 Edited {
23291 transaction_id: clock::Lamport,
23292 },
23293 Reparsed(BufferId),
23294 Focused,
23295 FocusedIn,
23296 Blurred,
23297 DirtyChanged,
23298 Saved,
23299 TitleChanged,
23300 SelectionsChanged {
23301 local: bool,
23302 },
23303 ScrollPositionChanged {
23304 local: bool,
23305 autoscroll: bool,
23306 },
23307 TransactionUndone {
23308 transaction_id: clock::Lamport,
23309 },
23310 TransactionBegun {
23311 transaction_id: clock::Lamport,
23312 },
23313 CursorShapeChanged,
23314 BreadcrumbsChanged,
23315 PushedToNavHistory {
23316 anchor: Anchor,
23317 is_deactivate: bool,
23318 },
23319}
23320
23321impl EventEmitter<EditorEvent> for Editor {}
23322
23323impl Focusable for Editor {
23324 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23325 self.focus_handle.clone()
23326 }
23327}
23328
23329impl Render for Editor {
23330 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23331 let settings = ThemeSettings::get_global(cx);
23332
23333 let mut text_style = match self.mode {
23334 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23335 color: cx.theme().colors().editor_foreground,
23336 font_family: settings.ui_font.family.clone(),
23337 font_features: settings.ui_font.features.clone(),
23338 font_fallbacks: settings.ui_font.fallbacks.clone(),
23339 font_size: rems(0.875).into(),
23340 font_weight: settings.ui_font.weight,
23341 line_height: relative(settings.buffer_line_height.value()),
23342 ..Default::default()
23343 },
23344 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23345 color: cx.theme().colors().editor_foreground,
23346 font_family: settings.buffer_font.family.clone(),
23347 font_features: settings.buffer_font.features.clone(),
23348 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23349 font_size: settings.buffer_font_size(cx).into(),
23350 font_weight: settings.buffer_font.weight,
23351 line_height: relative(settings.buffer_line_height.value()),
23352 ..Default::default()
23353 },
23354 };
23355 if let Some(text_style_refinement) = &self.text_style_refinement {
23356 text_style.refine(text_style_refinement)
23357 }
23358
23359 let background = match self.mode {
23360 EditorMode::SingleLine => cx.theme().system().transparent,
23361 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23362 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23363 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23364 };
23365
23366 EditorElement::new(
23367 &cx.entity(),
23368 EditorStyle {
23369 background,
23370 border: cx.theme().colors().border,
23371 local_player: cx.theme().players().local(),
23372 text: text_style,
23373 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23374 syntax: cx.theme().syntax().clone(),
23375 status: cx.theme().status().clone(),
23376 inlay_hints_style: make_inlay_hints_style(cx),
23377 edit_prediction_styles: make_suggestion_styles(cx),
23378 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23379 show_underlines: self.diagnostics_enabled(),
23380 },
23381 )
23382 }
23383}
23384
23385impl EntityInputHandler for Editor {
23386 fn text_for_range(
23387 &mut self,
23388 range_utf16: Range<usize>,
23389 adjusted_range: &mut Option<Range<usize>>,
23390 _: &mut Window,
23391 cx: &mut Context<Self>,
23392 ) -> Option<String> {
23393 let snapshot = self.buffer.read(cx).read(cx);
23394 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23395 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23396 if (start.0..end.0) != range_utf16 {
23397 adjusted_range.replace(start.0..end.0);
23398 }
23399 Some(snapshot.text_for_range(start..end).collect())
23400 }
23401
23402 fn selected_text_range(
23403 &mut self,
23404 ignore_disabled_input: bool,
23405 _: &mut Window,
23406 cx: &mut Context<Self>,
23407 ) -> Option<UTF16Selection> {
23408 // Prevent the IME menu from appearing when holding down an alphabetic key
23409 // while input is disabled.
23410 if !ignore_disabled_input && !self.input_enabled {
23411 return None;
23412 }
23413
23414 let selection = self.selections.newest::<OffsetUtf16>(cx);
23415 let range = selection.range();
23416
23417 Some(UTF16Selection {
23418 range: range.start.0..range.end.0,
23419 reversed: selection.reversed,
23420 })
23421 }
23422
23423 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23424 let snapshot = self.buffer.read(cx).read(cx);
23425 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23426 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23427 }
23428
23429 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23430 self.clear_highlights::<InputComposition>(cx);
23431 self.ime_transaction.take();
23432 }
23433
23434 fn replace_text_in_range(
23435 &mut self,
23436 range_utf16: Option<Range<usize>>,
23437 text: &str,
23438 window: &mut Window,
23439 cx: &mut Context<Self>,
23440 ) {
23441 if !self.input_enabled {
23442 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23443 return;
23444 }
23445
23446 self.transact(window, cx, |this, window, cx| {
23447 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23448 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23449 Some(this.selection_replacement_ranges(range_utf16, cx))
23450 } else {
23451 this.marked_text_ranges(cx)
23452 };
23453
23454 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23455 let newest_selection_id = this.selections.newest_anchor().id;
23456 this.selections
23457 .all::<OffsetUtf16>(cx)
23458 .iter()
23459 .zip(ranges_to_replace.iter())
23460 .find_map(|(selection, range)| {
23461 if selection.id == newest_selection_id {
23462 Some(
23463 (range.start.0 as isize - selection.head().0 as isize)
23464 ..(range.end.0 as isize - selection.head().0 as isize),
23465 )
23466 } else {
23467 None
23468 }
23469 })
23470 });
23471
23472 cx.emit(EditorEvent::InputHandled {
23473 utf16_range_to_replace: range_to_replace,
23474 text: text.into(),
23475 });
23476
23477 if let Some(new_selected_ranges) = new_selected_ranges {
23478 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23479 selections.select_ranges(new_selected_ranges)
23480 });
23481 this.backspace(&Default::default(), window, cx);
23482 }
23483
23484 this.handle_input(text, window, cx);
23485 });
23486
23487 if let Some(transaction) = self.ime_transaction {
23488 self.buffer.update(cx, |buffer, cx| {
23489 buffer.group_until_transaction(transaction, cx);
23490 });
23491 }
23492
23493 self.unmark_text(window, cx);
23494 }
23495
23496 fn replace_and_mark_text_in_range(
23497 &mut self,
23498 range_utf16: Option<Range<usize>>,
23499 text: &str,
23500 new_selected_range_utf16: Option<Range<usize>>,
23501 window: &mut Window,
23502 cx: &mut Context<Self>,
23503 ) {
23504 if !self.input_enabled {
23505 return;
23506 }
23507
23508 let transaction = self.transact(window, cx, |this, window, cx| {
23509 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23510 let snapshot = this.buffer.read(cx).read(cx);
23511 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23512 for marked_range in &mut marked_ranges {
23513 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23514 marked_range.start.0 += relative_range_utf16.start;
23515 marked_range.start =
23516 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23517 marked_range.end =
23518 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23519 }
23520 }
23521 Some(marked_ranges)
23522 } else if let Some(range_utf16) = range_utf16 {
23523 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23524 Some(this.selection_replacement_ranges(range_utf16, cx))
23525 } else {
23526 None
23527 };
23528
23529 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23530 let newest_selection_id = this.selections.newest_anchor().id;
23531 this.selections
23532 .all::<OffsetUtf16>(cx)
23533 .iter()
23534 .zip(ranges_to_replace.iter())
23535 .find_map(|(selection, range)| {
23536 if selection.id == newest_selection_id {
23537 Some(
23538 (range.start.0 as isize - selection.head().0 as isize)
23539 ..(range.end.0 as isize - selection.head().0 as isize),
23540 )
23541 } else {
23542 None
23543 }
23544 })
23545 });
23546
23547 cx.emit(EditorEvent::InputHandled {
23548 utf16_range_to_replace: range_to_replace,
23549 text: text.into(),
23550 });
23551
23552 if let Some(ranges) = ranges_to_replace {
23553 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23554 s.select_ranges(ranges)
23555 });
23556 }
23557
23558 let marked_ranges = {
23559 let snapshot = this.buffer.read(cx).read(cx);
23560 this.selections
23561 .disjoint_anchors()
23562 .iter()
23563 .map(|selection| {
23564 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23565 })
23566 .collect::<Vec<_>>()
23567 };
23568
23569 if text.is_empty() {
23570 this.unmark_text(window, cx);
23571 } else {
23572 this.highlight_text::<InputComposition>(
23573 marked_ranges.clone(),
23574 HighlightStyle {
23575 underline: Some(UnderlineStyle {
23576 thickness: px(1.),
23577 color: None,
23578 wavy: false,
23579 }),
23580 ..Default::default()
23581 },
23582 cx,
23583 );
23584 }
23585
23586 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23587 let use_autoclose = this.use_autoclose;
23588 let use_auto_surround = this.use_auto_surround;
23589 this.set_use_autoclose(false);
23590 this.set_use_auto_surround(false);
23591 this.handle_input(text, window, cx);
23592 this.set_use_autoclose(use_autoclose);
23593 this.set_use_auto_surround(use_auto_surround);
23594
23595 if let Some(new_selected_range) = new_selected_range_utf16 {
23596 let snapshot = this.buffer.read(cx).read(cx);
23597 let new_selected_ranges = marked_ranges
23598 .into_iter()
23599 .map(|marked_range| {
23600 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23601 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23602 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23603 snapshot.clip_offset_utf16(new_start, Bias::Left)
23604 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23605 })
23606 .collect::<Vec<_>>();
23607
23608 drop(snapshot);
23609 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23610 selections.select_ranges(new_selected_ranges)
23611 });
23612 }
23613 });
23614
23615 self.ime_transaction = self.ime_transaction.or(transaction);
23616 if let Some(transaction) = self.ime_transaction {
23617 self.buffer.update(cx, |buffer, cx| {
23618 buffer.group_until_transaction(transaction, cx);
23619 });
23620 }
23621
23622 if self.text_highlights::<InputComposition>(cx).is_none() {
23623 self.ime_transaction.take();
23624 }
23625 }
23626
23627 fn bounds_for_range(
23628 &mut self,
23629 range_utf16: Range<usize>,
23630 element_bounds: gpui::Bounds<Pixels>,
23631 window: &mut Window,
23632 cx: &mut Context<Self>,
23633 ) -> Option<gpui::Bounds<Pixels>> {
23634 let text_layout_details = self.text_layout_details(window);
23635 let CharacterDimensions {
23636 em_width,
23637 em_advance,
23638 line_height,
23639 } = self.character_dimensions(window);
23640
23641 let snapshot = self.snapshot(window, cx);
23642 let scroll_position = snapshot.scroll_position();
23643 let scroll_left = scroll_position.x * em_advance;
23644
23645 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23646 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23647 + self.gutter_dimensions.full_width();
23648 let y = line_height * (start.row().as_f32() - scroll_position.y);
23649
23650 Some(Bounds {
23651 origin: element_bounds.origin + point(x, y),
23652 size: size(em_width, line_height),
23653 })
23654 }
23655
23656 fn character_index_for_point(
23657 &mut self,
23658 point: gpui::Point<Pixels>,
23659 _window: &mut Window,
23660 _cx: &mut Context<Self>,
23661 ) -> Option<usize> {
23662 let position_map = self.last_position_map.as_ref()?;
23663 if !position_map.text_hitbox.contains(&point) {
23664 return None;
23665 }
23666 let display_point = position_map.point_for_position(point).previous_valid;
23667 let anchor = position_map
23668 .snapshot
23669 .display_point_to_anchor(display_point, Bias::Left);
23670 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23671 Some(utf16_offset.0)
23672 }
23673}
23674
23675trait SelectionExt {
23676 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23677 fn spanned_rows(
23678 &self,
23679 include_end_if_at_line_start: bool,
23680 map: &DisplaySnapshot,
23681 ) -> Range<MultiBufferRow>;
23682}
23683
23684impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23685 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23686 let start = self
23687 .start
23688 .to_point(&map.buffer_snapshot)
23689 .to_display_point(map);
23690 let end = self
23691 .end
23692 .to_point(&map.buffer_snapshot)
23693 .to_display_point(map);
23694 if self.reversed {
23695 end..start
23696 } else {
23697 start..end
23698 }
23699 }
23700
23701 fn spanned_rows(
23702 &self,
23703 include_end_if_at_line_start: bool,
23704 map: &DisplaySnapshot,
23705 ) -> Range<MultiBufferRow> {
23706 let start = self.start.to_point(&map.buffer_snapshot);
23707 let mut end = self.end.to_point(&map.buffer_snapshot);
23708 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23709 end.row -= 1;
23710 }
23711
23712 let buffer_start = map.prev_line_boundary(start).0;
23713 let buffer_end = map.next_line_boundary(end).0;
23714 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23715 }
23716}
23717
23718impl<T: InvalidationRegion> InvalidationStack<T> {
23719 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23720 where
23721 S: Clone + ToOffset,
23722 {
23723 while let Some(region) = self.last() {
23724 let all_selections_inside_invalidation_ranges =
23725 if selections.len() == region.ranges().len() {
23726 selections
23727 .iter()
23728 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23729 .all(|(selection, invalidation_range)| {
23730 let head = selection.head().to_offset(buffer);
23731 invalidation_range.start <= head && invalidation_range.end >= head
23732 })
23733 } else {
23734 false
23735 };
23736
23737 if all_selections_inside_invalidation_ranges {
23738 break;
23739 } else {
23740 self.pop();
23741 }
23742 }
23743 }
23744}
23745
23746impl<T> Default for InvalidationStack<T> {
23747 fn default() -> Self {
23748 Self(Default::default())
23749 }
23750}
23751
23752impl<T> Deref for InvalidationStack<T> {
23753 type Target = Vec<T>;
23754
23755 fn deref(&self) -> &Self::Target {
23756 &self.0
23757 }
23758}
23759
23760impl<T> DerefMut for InvalidationStack<T> {
23761 fn deref_mut(&mut self) -> &mut Self::Target {
23762 &mut self.0
23763 }
23764}
23765
23766impl InvalidationRegion for SnippetState {
23767 fn ranges(&self) -> &[Range<Anchor>] {
23768 &self.ranges[self.active_index]
23769 }
23770}
23771
23772fn edit_prediction_edit_text(
23773 current_snapshot: &BufferSnapshot,
23774 edits: &[(Range<Anchor>, String)],
23775 edit_preview: &EditPreview,
23776 include_deletions: bool,
23777 cx: &App,
23778) -> HighlightedText {
23779 let edits = edits
23780 .iter()
23781 .map(|(anchor, text)| {
23782 (
23783 anchor.start.text_anchor..anchor.end.text_anchor,
23784 text.clone(),
23785 )
23786 })
23787 .collect::<Vec<_>>();
23788
23789 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23790}
23791
23792fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23793 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23794 // Just show the raw edit text with basic styling
23795 let mut text = String::new();
23796 let mut highlights = Vec::new();
23797
23798 let insertion_highlight_style = HighlightStyle {
23799 color: Some(cx.theme().colors().text),
23800 ..Default::default()
23801 };
23802
23803 for (_, edit_text) in edits {
23804 let start_offset = text.len();
23805 text.push_str(edit_text);
23806 let end_offset = text.len();
23807
23808 if start_offset < end_offset {
23809 highlights.push((start_offset..end_offset, insertion_highlight_style));
23810 }
23811 }
23812
23813 HighlightedText {
23814 text: text.into(),
23815 highlights,
23816 }
23817}
23818
23819pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23820 match severity {
23821 lsp::DiagnosticSeverity::ERROR => colors.error,
23822 lsp::DiagnosticSeverity::WARNING => colors.warning,
23823 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23824 lsp::DiagnosticSeverity::HINT => colors.info,
23825 _ => colors.ignored,
23826 }
23827}
23828
23829pub fn styled_runs_for_code_label<'a>(
23830 label: &'a CodeLabel,
23831 syntax_theme: &'a theme::SyntaxTheme,
23832) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23833 let fade_out = HighlightStyle {
23834 fade_out: Some(0.35),
23835 ..Default::default()
23836 };
23837
23838 let mut prev_end = label.filter_range.end;
23839 label
23840 .runs
23841 .iter()
23842 .enumerate()
23843 .flat_map(move |(ix, (range, highlight_id))| {
23844 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23845 style
23846 } else {
23847 return Default::default();
23848 };
23849 let muted_style = style.highlight(fade_out);
23850
23851 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23852 if range.start >= label.filter_range.end {
23853 if range.start > prev_end {
23854 runs.push((prev_end..range.start, fade_out));
23855 }
23856 runs.push((range.clone(), muted_style));
23857 } else if range.end <= label.filter_range.end {
23858 runs.push((range.clone(), style));
23859 } else {
23860 runs.push((range.start..label.filter_range.end, style));
23861 runs.push((label.filter_range.end..range.end, muted_style));
23862 }
23863 prev_end = cmp::max(prev_end, range.end);
23864
23865 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23866 runs.push((prev_end..label.text.len(), fade_out));
23867 }
23868
23869 runs
23870 })
23871}
23872
23873pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23874 let mut prev_index = 0;
23875 let mut prev_codepoint: Option<char> = None;
23876 text.char_indices()
23877 .chain([(text.len(), '\0')])
23878 .filter_map(move |(index, codepoint)| {
23879 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23880 let is_boundary = index == text.len()
23881 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23882 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23883 if is_boundary {
23884 let chunk = &text[prev_index..index];
23885 prev_index = index;
23886 Some(chunk)
23887 } else {
23888 None
23889 }
23890 })
23891}
23892
23893pub trait RangeToAnchorExt: Sized {
23894 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23895
23896 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23897 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23898 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23899 }
23900}
23901
23902impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23903 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23904 let start_offset = self.start.to_offset(snapshot);
23905 let end_offset = self.end.to_offset(snapshot);
23906 if start_offset == end_offset {
23907 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23908 } else {
23909 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23910 }
23911 }
23912}
23913
23914pub trait RowExt {
23915 fn as_f32(&self) -> f32;
23916
23917 fn next_row(&self) -> Self;
23918
23919 fn previous_row(&self) -> Self;
23920
23921 fn minus(&self, other: Self) -> u32;
23922}
23923
23924impl RowExt for DisplayRow {
23925 fn as_f32(&self) -> f32 {
23926 self.0 as f32
23927 }
23928
23929 fn next_row(&self) -> Self {
23930 Self(self.0 + 1)
23931 }
23932
23933 fn previous_row(&self) -> Self {
23934 Self(self.0.saturating_sub(1))
23935 }
23936
23937 fn minus(&self, other: Self) -> u32 {
23938 self.0 - other.0
23939 }
23940}
23941
23942impl RowExt for MultiBufferRow {
23943 fn as_f32(&self) -> f32 {
23944 self.0 as f32
23945 }
23946
23947 fn next_row(&self) -> Self {
23948 Self(self.0 + 1)
23949 }
23950
23951 fn previous_row(&self) -> Self {
23952 Self(self.0.saturating_sub(1))
23953 }
23954
23955 fn minus(&self, other: Self) -> u32 {
23956 self.0 - other.0
23957 }
23958}
23959
23960trait RowRangeExt {
23961 type Row;
23962
23963 fn len(&self) -> usize;
23964
23965 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23966}
23967
23968impl RowRangeExt for Range<MultiBufferRow> {
23969 type Row = MultiBufferRow;
23970
23971 fn len(&self) -> usize {
23972 (self.end.0 - self.start.0) as usize
23973 }
23974
23975 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23976 (self.start.0..self.end.0).map(MultiBufferRow)
23977 }
23978}
23979
23980impl RowRangeExt for Range<DisplayRow> {
23981 type Row = DisplayRow;
23982
23983 fn len(&self) -> usize {
23984 (self.end.0 - self.start.0) as usize
23985 }
23986
23987 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23988 (self.start.0..self.end.0).map(DisplayRow)
23989 }
23990}
23991
23992/// If select range has more than one line, we
23993/// just point the cursor to range.start.
23994fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23995 if range.start.row == range.end.row {
23996 range
23997 } else {
23998 range.start..range.start
23999 }
24000}
24001pub struct KillRing(ClipboardItem);
24002impl Global for KillRing {}
24003
24004const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24005
24006enum BreakpointPromptEditAction {
24007 Log,
24008 Condition,
24009 HitCondition,
24010}
24011
24012struct BreakpointPromptEditor {
24013 pub(crate) prompt: Entity<Editor>,
24014 editor: WeakEntity<Editor>,
24015 breakpoint_anchor: Anchor,
24016 breakpoint: Breakpoint,
24017 edit_action: BreakpointPromptEditAction,
24018 block_ids: HashSet<CustomBlockId>,
24019 editor_margins: Arc<Mutex<EditorMargins>>,
24020 _subscriptions: Vec<Subscription>,
24021}
24022
24023impl BreakpointPromptEditor {
24024 const MAX_LINES: u8 = 4;
24025
24026 fn new(
24027 editor: WeakEntity<Editor>,
24028 breakpoint_anchor: Anchor,
24029 breakpoint: Breakpoint,
24030 edit_action: BreakpointPromptEditAction,
24031 window: &mut Window,
24032 cx: &mut Context<Self>,
24033 ) -> Self {
24034 let base_text = match edit_action {
24035 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24036 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24037 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24038 }
24039 .map(|msg| msg.to_string())
24040 .unwrap_or_default();
24041
24042 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24043 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24044
24045 let prompt = cx.new(|cx| {
24046 let mut prompt = Editor::new(
24047 EditorMode::AutoHeight {
24048 min_lines: 1,
24049 max_lines: Some(Self::MAX_LINES as usize),
24050 },
24051 buffer,
24052 None,
24053 window,
24054 cx,
24055 );
24056 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24057 prompt.set_show_cursor_when_unfocused(false, cx);
24058 prompt.set_placeholder_text(
24059 match edit_action {
24060 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24061 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24062 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24063 },
24064 window,
24065 cx,
24066 );
24067
24068 prompt
24069 });
24070
24071 Self {
24072 prompt,
24073 editor,
24074 breakpoint_anchor,
24075 breakpoint,
24076 edit_action,
24077 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24078 block_ids: Default::default(),
24079 _subscriptions: vec![],
24080 }
24081 }
24082
24083 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24084 self.block_ids.extend(block_ids)
24085 }
24086
24087 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24088 if let Some(editor) = self.editor.upgrade() {
24089 let message = self
24090 .prompt
24091 .read(cx)
24092 .buffer
24093 .read(cx)
24094 .as_singleton()
24095 .expect("A multi buffer in breakpoint prompt isn't possible")
24096 .read(cx)
24097 .as_rope()
24098 .to_string();
24099
24100 editor.update(cx, |editor, cx| {
24101 editor.edit_breakpoint_at_anchor(
24102 self.breakpoint_anchor,
24103 self.breakpoint.clone(),
24104 match self.edit_action {
24105 BreakpointPromptEditAction::Log => {
24106 BreakpointEditAction::EditLogMessage(message.into())
24107 }
24108 BreakpointPromptEditAction::Condition => {
24109 BreakpointEditAction::EditCondition(message.into())
24110 }
24111 BreakpointPromptEditAction::HitCondition => {
24112 BreakpointEditAction::EditHitCondition(message.into())
24113 }
24114 },
24115 cx,
24116 );
24117
24118 editor.remove_blocks(self.block_ids.clone(), None, cx);
24119 cx.focus_self(window);
24120 });
24121 }
24122 }
24123
24124 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24125 self.editor
24126 .update(cx, |editor, cx| {
24127 editor.remove_blocks(self.block_ids.clone(), None, cx);
24128 window.focus(&editor.focus_handle);
24129 })
24130 .log_err();
24131 }
24132
24133 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24134 let settings = ThemeSettings::get_global(cx);
24135 let text_style = TextStyle {
24136 color: if self.prompt.read(cx).read_only(cx) {
24137 cx.theme().colors().text_disabled
24138 } else {
24139 cx.theme().colors().text
24140 },
24141 font_family: settings.buffer_font.family.clone(),
24142 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24143 font_size: settings.buffer_font_size(cx).into(),
24144 font_weight: settings.buffer_font.weight,
24145 line_height: relative(settings.buffer_line_height.value()),
24146 ..Default::default()
24147 };
24148 EditorElement::new(
24149 &self.prompt,
24150 EditorStyle {
24151 background: cx.theme().colors().editor_background,
24152 local_player: cx.theme().players().local(),
24153 text: text_style,
24154 ..Default::default()
24155 },
24156 )
24157 }
24158}
24159
24160impl Render for BreakpointPromptEditor {
24161 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24162 let editor_margins = *self.editor_margins.lock();
24163 let gutter_dimensions = editor_margins.gutter;
24164 h_flex()
24165 .key_context("Editor")
24166 .bg(cx.theme().colors().editor_background)
24167 .border_y_1()
24168 .border_color(cx.theme().status().info_border)
24169 .size_full()
24170 .py(window.line_height() / 2.5)
24171 .on_action(cx.listener(Self::confirm))
24172 .on_action(cx.listener(Self::cancel))
24173 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24174 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24175 }
24176}
24177
24178impl Focusable for BreakpointPromptEditor {
24179 fn focus_handle(&self, cx: &App) -> FocusHandle {
24180 self.prompt.focus_handle(cx)
24181 }
24182}
24183
24184fn all_edits_insertions_or_deletions(
24185 edits: &Vec<(Range<Anchor>, String)>,
24186 snapshot: &MultiBufferSnapshot,
24187) -> bool {
24188 let mut all_insertions = true;
24189 let mut all_deletions = true;
24190
24191 for (range, new_text) in edits.iter() {
24192 let range_is_empty = range.to_offset(snapshot).is_empty();
24193 let text_is_empty = new_text.is_empty();
24194
24195 if range_is_empty != text_is_empty {
24196 if range_is_empty {
24197 all_deletions = false;
24198 } else {
24199 all_insertions = false;
24200 }
24201 } else {
24202 return false;
24203 }
24204
24205 if !all_insertions && !all_deletions {
24206 return false;
24207 }
24208 }
24209 all_insertions || all_deletions
24210}
24211
24212struct MissingEditPredictionKeybindingTooltip;
24213
24214impl Render for MissingEditPredictionKeybindingTooltip {
24215 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24216 ui::tooltip_container(window, cx, |container, _, cx| {
24217 container
24218 .flex_shrink_0()
24219 .max_w_80()
24220 .min_h(rems_from_px(124.))
24221 .justify_between()
24222 .child(
24223 v_flex()
24224 .flex_1()
24225 .text_ui_sm(cx)
24226 .child(Label::new("Conflict with Accept Keybinding"))
24227 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24228 )
24229 .child(
24230 h_flex()
24231 .pb_1()
24232 .gap_1()
24233 .items_end()
24234 .w_full()
24235 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24236 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24237 }))
24238 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24239 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24240 })),
24241 )
24242 })
24243 }
24244}
24245
24246#[derive(Debug, Clone, Copy, PartialEq)]
24247pub struct LineHighlight {
24248 pub background: Background,
24249 pub border: Option<gpui::Hsla>,
24250 pub include_gutter: bool,
24251 pub type_id: Option<TypeId>,
24252}
24253
24254struct LineManipulationResult {
24255 pub new_text: String,
24256 pub line_count_before: usize,
24257 pub line_count_after: usize,
24258}
24259
24260fn render_diff_hunk_controls(
24261 row: u32,
24262 status: &DiffHunkStatus,
24263 hunk_range: Range<Anchor>,
24264 is_created_file: bool,
24265 line_height: Pixels,
24266 editor: &Entity<Editor>,
24267 _window: &mut Window,
24268 cx: &mut App,
24269) -> AnyElement {
24270 h_flex()
24271 .h(line_height)
24272 .mr_1()
24273 .gap_1()
24274 .px_0p5()
24275 .pb_1()
24276 .border_x_1()
24277 .border_b_1()
24278 .border_color(cx.theme().colors().border_variant)
24279 .rounded_b_lg()
24280 .bg(cx.theme().colors().editor_background)
24281 .gap_1()
24282 .block_mouse_except_scroll()
24283 .shadow_md()
24284 .child(if status.has_secondary_hunk() {
24285 Button::new(("stage", row as u64), "Stage")
24286 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24287 .tooltip({
24288 let focus_handle = editor.focus_handle(cx);
24289 move |window, cx| {
24290 Tooltip::for_action_in(
24291 "Stage Hunk",
24292 &::git::ToggleStaged,
24293 &focus_handle,
24294 window,
24295 cx,
24296 )
24297 }
24298 })
24299 .on_click({
24300 let editor = editor.clone();
24301 move |_event, _window, cx| {
24302 editor.update(cx, |editor, cx| {
24303 editor.stage_or_unstage_diff_hunks(
24304 true,
24305 vec![hunk_range.start..hunk_range.start],
24306 cx,
24307 );
24308 });
24309 }
24310 })
24311 } else {
24312 Button::new(("unstage", row as u64), "Unstage")
24313 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24314 .tooltip({
24315 let focus_handle = editor.focus_handle(cx);
24316 move |window, cx| {
24317 Tooltip::for_action_in(
24318 "Unstage Hunk",
24319 &::git::ToggleStaged,
24320 &focus_handle,
24321 window,
24322 cx,
24323 )
24324 }
24325 })
24326 .on_click({
24327 let editor = editor.clone();
24328 move |_event, _window, cx| {
24329 editor.update(cx, |editor, cx| {
24330 editor.stage_or_unstage_diff_hunks(
24331 false,
24332 vec![hunk_range.start..hunk_range.start],
24333 cx,
24334 );
24335 });
24336 }
24337 })
24338 })
24339 .child(
24340 Button::new(("restore", row as u64), "Restore")
24341 .tooltip({
24342 let focus_handle = editor.focus_handle(cx);
24343 move |window, cx| {
24344 Tooltip::for_action_in(
24345 "Restore Hunk",
24346 &::git::Restore,
24347 &focus_handle,
24348 window,
24349 cx,
24350 )
24351 }
24352 })
24353 .on_click({
24354 let editor = editor.clone();
24355 move |_event, window, cx| {
24356 editor.update(cx, |editor, cx| {
24357 let snapshot = editor.snapshot(window, cx);
24358 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24359 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24360 });
24361 }
24362 })
24363 .disabled(is_created_file),
24364 )
24365 .when(
24366 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24367 |el| {
24368 el.child(
24369 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24370 .shape(IconButtonShape::Square)
24371 .icon_size(IconSize::Small)
24372 // .disabled(!has_multiple_hunks)
24373 .tooltip({
24374 let focus_handle = editor.focus_handle(cx);
24375 move |window, cx| {
24376 Tooltip::for_action_in(
24377 "Next Hunk",
24378 &GoToHunk,
24379 &focus_handle,
24380 window,
24381 cx,
24382 )
24383 }
24384 })
24385 .on_click({
24386 let editor = editor.clone();
24387 move |_event, window, cx| {
24388 editor.update(cx, |editor, cx| {
24389 let snapshot = editor.snapshot(window, cx);
24390 let position =
24391 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24392 editor.go_to_hunk_before_or_after_position(
24393 &snapshot,
24394 position,
24395 Direction::Next,
24396 window,
24397 cx,
24398 );
24399 editor.expand_selected_diff_hunks(cx);
24400 });
24401 }
24402 }),
24403 )
24404 .child(
24405 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24406 .shape(IconButtonShape::Square)
24407 .icon_size(IconSize::Small)
24408 // .disabled(!has_multiple_hunks)
24409 .tooltip({
24410 let focus_handle = editor.focus_handle(cx);
24411 move |window, cx| {
24412 Tooltip::for_action_in(
24413 "Previous Hunk",
24414 &GoToPreviousHunk,
24415 &focus_handle,
24416 window,
24417 cx,
24418 )
24419 }
24420 })
24421 .on_click({
24422 let editor = editor.clone();
24423 move |_event, window, cx| {
24424 editor.update(cx, |editor, cx| {
24425 let snapshot = editor.snapshot(window, cx);
24426 let point =
24427 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24428 editor.go_to_hunk_before_or_after_position(
24429 &snapshot,
24430 point,
24431 Direction::Prev,
24432 window,
24433 cx,
24434 );
24435 editor.expand_selected_diff_hunks(cx);
24436 });
24437 }
24438 }),
24439 )
24440 },
24441 )
24442 .into_any_element()
24443}
24444
24445pub fn multibuffer_context_lines(cx: &App) -> u32 {
24446 EditorSettings::try_get(cx)
24447 .map(|settings| settings.excerpt_context_lines)
24448 .unwrap_or(2)
24449 .min(32)
24450}