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//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod proposed_changes_editor;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39pub mod tasks;
40
41#[cfg(test)]
42mod code_completion_tests;
43#[cfg(test)]
44mod edit_prediction_tests;
45#[cfg(test)]
46mod editor_tests;
47mod signature_help;
48#[cfg(any(test, feature = "test-support"))]
49pub mod test;
50
51pub(crate) use actions::*;
52pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
53pub use edit_prediction::Direction;
54pub use editor_settings::{
55 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
56 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
57};
58pub use element::{
59 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
60};
61pub use git::blame::BlameRenderer;
62pub use hover_popover::hover_markdown_style;
63pub use inlays::Inlay;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::ReplicaId;
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
128 language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
150 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
151 ProjectPath, ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{
161 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
162 OpenLspBufferHandle,
163 },
164 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
165};
166use rand::seq::SliceRandom;
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::{Any, TypeId},
178 borrow::Cow,
179 cell::{OnceCell, RefCell},
180 cmp::{self, Ordering, Reverse},
181 iter::{self, Peekable},
182 mem,
183 num::NonZeroU32,
184 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
185 path::{Path, PathBuf},
186 rc::Rc,
187 sync::Arc,
188 time::{Duration, Instant},
189};
190use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
191use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
192use theme::{
193 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
194 observe_buffer_font_size_adjustment,
195};
196use ui::{
197 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
198 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
199};
200use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
201use workspace::{
202 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
203 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
204 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
205 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
206 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
207 searchable::SearchEvent,
208};
209
210use crate::{
211 code_context_menus::CompletionsMenuSource,
212 editor_settings::MultiCursorModifier,
213 hover_links::{find_url, find_url_from_range},
214 inlays::{
215 InlineValueCache,
216 inlay_hints::{LspInlayHintData, inlay_hint_settings},
217 },
218 scroll::{ScrollOffset, ScrollPixelOffset},
219 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
220};
221
222pub const FILE_HEADER_HEIGHT: u32 = 2;
223pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
224const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
225const MAX_LINE_LEN: usize = 1024;
226const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
227const MAX_SELECTION_HISTORY_LEN: usize = 1024;
228pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
229#[doc(hidden)]
230pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
231pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
232
233pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
235pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
236pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
237
238pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
239pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
240pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
241
242pub type RenderDiffHunkControlsFn = Arc<
243 dyn Fn(
244 u32,
245 &DiffHunkStatus,
246 Range<Anchor>,
247 bool,
248 Pixels,
249 &Entity<Editor>,
250 &mut Window,
251 &mut App,
252 ) -> AnyElement,
253>;
254
255enum ReportEditorEvent {
256 Saved { auto_saved: bool },
257 EditorOpened,
258 Closed,
259}
260
261impl ReportEditorEvent {
262 pub fn event_type(&self) -> &'static str {
263 match self {
264 Self::Saved { .. } => "Editor Saved",
265 Self::EditorOpened => "Editor Opened",
266 Self::Closed => "Editor Closed",
267 }
268 }
269}
270
271pub enum ActiveDebugLine {}
272pub enum DebugStackFrameLine {}
273enum DocumentHighlightRead {}
274enum DocumentHighlightWrite {}
275enum InputComposition {}
276pub enum PendingInput {}
277enum SelectedTextHighlight {}
278
279pub enum ConflictsOuter {}
280pub enum ConflictsOurs {}
281pub enum ConflictsTheirs {}
282pub enum ConflictsOursMarker {}
283pub enum ConflictsTheirsMarker {}
284
285#[derive(Debug, Copy, Clone, PartialEq, Eq)]
286pub enum Navigated {
287 Yes,
288 No,
289}
290
291impl Navigated {
292 pub fn from_bool(yes: bool) -> Navigated {
293 if yes { Navigated::Yes } else { Navigated::No }
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
298enum DisplayDiffHunk {
299 Folded {
300 display_row: DisplayRow,
301 },
302 Unfolded {
303 is_created_file: bool,
304 diff_base_byte_range: Range<usize>,
305 display_row_range: Range<DisplayRow>,
306 multi_buffer_range: Range<Anchor>,
307 status: DiffHunkStatus,
308 },
309}
310
311pub enum HideMouseCursorOrigin {
312 TypingAction,
313 MovementAction,
314}
315
316pub fn init_settings(cx: &mut App) {
317 EditorSettings::register(cx);
318}
319
320pub fn init(cx: &mut App) {
321 init_settings(cx);
322
323 cx.set_global(GlobalBlameRenderer(Arc::new(())));
324
325 workspace::register_project_item::<Editor>(cx);
326 workspace::FollowableViewRegistry::register::<Editor>(cx);
327 workspace::register_serializable_item::<Editor>(cx);
328
329 cx.observe_new(
330 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
331 workspace.register_action(Editor::new_file);
332 workspace.register_action(Editor::new_file_split);
333 workspace.register_action(Editor::new_file_vertical);
334 workspace.register_action(Editor::new_file_horizontal);
335 workspace.register_action(Editor::cancel_language_server_work);
336 workspace.register_action(Editor::toggle_focus);
337 },
338 )
339 .detach();
340
341 cx.on_action(move |_: &workspace::NewFile, cx| {
342 let app_state = workspace::AppState::global(cx);
343 if let Some(app_state) = app_state.upgrade() {
344 workspace::open_new(
345 Default::default(),
346 app_state,
347 cx,
348 |workspace, window, cx| {
349 Editor::new_file(workspace, &Default::default(), window, cx)
350 },
351 )
352 .detach();
353 }
354 });
355 cx.on_action(move |_: &workspace::NewWindow, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 cx.activate(true);
364 Editor::new_file(workspace, &Default::default(), window, cx)
365 },
366 )
367 .detach();
368 }
369 });
370}
371
372pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
374}
375
376pub trait DiagnosticRenderer {
377 fn render_group(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 buffer_id: BufferId,
381 snapshot: EditorSnapshot,
382 editor: WeakEntity<Editor>,
383 cx: &mut App,
384 ) -> Vec<BlockProperties<Anchor>>;
385
386 fn render_hover(
387 &self,
388 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
389 range: Range<Point>,
390 buffer_id: BufferId,
391 cx: &mut App,
392 ) -> Option<Entity<markdown::Markdown>>;
393
394 fn open_link(
395 &self,
396 editor: &mut Editor,
397 link: SharedString,
398 window: &mut Window,
399 cx: &mut Context<Editor>,
400 );
401}
402
403pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
404
405impl GlobalDiagnosticRenderer {
406 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
407 cx.try_global::<Self>().map(|g| g.0.clone())
408 }
409}
410
411impl gpui::Global for GlobalDiagnosticRenderer {}
412pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
413 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
414}
415
416pub struct SearchWithinRange;
417
418trait InvalidationRegion {
419 fn ranges(&self) -> &[Range<Anchor>];
420}
421
422#[derive(Clone, Debug, PartialEq)]
423pub enum SelectPhase {
424 Begin {
425 position: DisplayPoint,
426 add: bool,
427 click_count: usize,
428 },
429 BeginColumnar {
430 position: DisplayPoint,
431 reset: bool,
432 mode: ColumnarMode,
433 goal_column: u32,
434 },
435 Extend {
436 position: DisplayPoint,
437 click_count: usize,
438 },
439 Update {
440 position: DisplayPoint,
441 goal_column: u32,
442 scroll_delta: gpui::Point<f32>,
443 },
444 End,
445}
446
447#[derive(Clone, Debug, PartialEq)]
448pub enum ColumnarMode {
449 FromMouse,
450 FromSelection,
451}
452
453#[derive(Clone, Debug)]
454pub enum SelectMode {
455 Character,
456 Word(Range<Anchor>),
457 Line(Range<Anchor>),
458 All,
459}
460
461#[derive(Clone, PartialEq, Eq, Debug)]
462pub enum EditorMode {
463 SingleLine,
464 AutoHeight {
465 min_lines: usize,
466 max_lines: Option<usize>,
467 },
468 Full {
469 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
470 scale_ui_elements_with_buffer_font_size: bool,
471 /// When set to `true`, the editor will render a background for the active line.
472 show_active_line_background: bool,
473 /// When set to `true`, the editor's height will be determined by its content.
474 sized_by_content: bool,
475 },
476 Minimap {
477 parent: WeakEntity<Editor>,
478 },
479}
480
481impl EditorMode {
482 pub fn full() -> Self {
483 Self::Full {
484 scale_ui_elements_with_buffer_font_size: true,
485 show_active_line_background: true,
486 sized_by_content: false,
487 }
488 }
489
490 #[inline]
491 pub fn is_full(&self) -> bool {
492 matches!(self, Self::Full { .. })
493 }
494
495 #[inline]
496 pub fn is_single_line(&self) -> bool {
497 matches!(self, Self::SingleLine { .. })
498 }
499
500 #[inline]
501 fn is_minimap(&self) -> bool {
502 matches!(self, Self::Minimap { .. })
503 }
504}
505
506#[derive(Copy, Clone, Debug)]
507pub enum SoftWrap {
508 /// Prefer not to wrap at all.
509 ///
510 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
511 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
512 GitDiff,
513 /// Prefer a single line generally, unless an overly long line is encountered.
514 None,
515 /// Soft wrap lines that exceed the editor width.
516 EditorWidth,
517 /// Soft wrap lines at the preferred line length.
518 Column(u32),
519 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
520 Bounded(u32),
521}
522
523#[derive(Clone)]
524pub struct EditorStyle {
525 pub background: Hsla,
526 pub border: Hsla,
527 pub local_player: PlayerColor,
528 pub text: TextStyle,
529 pub scrollbar_width: Pixels,
530 pub syntax: Arc<SyntaxTheme>,
531 pub status: StatusColors,
532 pub inlay_hints_style: HighlightStyle,
533 pub edit_prediction_styles: EditPredictionStyles,
534 pub unnecessary_code_fade: f32,
535 pub show_underlines: bool,
536}
537
538impl Default for EditorStyle {
539 fn default() -> Self {
540 Self {
541 background: Hsla::default(),
542 border: Hsla::default(),
543 local_player: PlayerColor::default(),
544 text: TextStyle::default(),
545 scrollbar_width: Pixels::default(),
546 syntax: Default::default(),
547 // HACK: Status colors don't have a real default.
548 // We should look into removing the status colors from the editor
549 // style and retrieve them directly from the theme.
550 status: StatusColors::dark(),
551 inlay_hints_style: HighlightStyle::default(),
552 edit_prediction_styles: EditPredictionStyles {
553 insertion: HighlightStyle::default(),
554 whitespace: HighlightStyle::default(),
555 },
556 unnecessary_code_fade: Default::default(),
557 show_underlines: true,
558 }
559 }
560}
561
562pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
563 let show_background = language_settings::language_settings(None, None, cx)
564 .inlay_hints
565 .show_background;
566
567 let mut style = cx.theme().syntax().get("hint");
568
569 if style.color.is_none() {
570 style.color = Some(cx.theme().status().hint);
571 }
572
573 if !show_background {
574 style.background_color = None;
575 return style;
576 }
577
578 if style.background_color.is_none() {
579 style.background_color = Some(cx.theme().status().hint_background);
580 }
581
582 style
583}
584
585pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
586 EditPredictionStyles {
587 insertion: HighlightStyle {
588 color: Some(cx.theme().status().predictive),
589 ..HighlightStyle::default()
590 },
591 whitespace: HighlightStyle {
592 background_color: Some(cx.theme().status().created_background),
593 ..HighlightStyle::default()
594 },
595 }
596}
597
598type CompletionId = usize;
599
600pub(crate) enum EditDisplayMode {
601 TabAccept,
602 DiffPopover,
603 Inline,
604}
605
606enum EditPrediction {
607 Edit {
608 edits: Vec<(Range<Anchor>, String)>,
609 edit_preview: Option<EditPreview>,
610 display_mode: EditDisplayMode,
611 snapshot: BufferSnapshot,
612 },
613 /// Move to a specific location in the active editor
614 MoveWithin {
615 target: Anchor,
616 snapshot: BufferSnapshot,
617 },
618 /// Move to a specific location in a different editor (not the active one)
619 MoveOutside {
620 target: language::Anchor,
621 snapshot: BufferSnapshot,
622 },
623}
624
625struct EditPredictionState {
626 inlay_ids: Vec<InlayId>,
627 completion: EditPrediction,
628 completion_id: Option<SharedString>,
629 invalidation_range: Option<Range<Anchor>>,
630}
631
632enum EditPredictionSettings {
633 Disabled,
634 Enabled {
635 show_in_menu: bool,
636 preview_requires_modifier: bool,
637 },
638}
639
640enum EditPredictionHighlight {}
641
642#[derive(Debug, Clone)]
643struct InlineDiagnostic {
644 message: SharedString,
645 group_id: usize,
646 is_primary: bool,
647 start: Point,
648 severity: lsp::DiagnosticSeverity,
649}
650
651pub enum MenuEditPredictionsPolicy {
652 Never,
653 ByProvider,
654}
655
656pub enum EditPredictionPreview {
657 /// Modifier is not pressed
658 Inactive { released_too_fast: bool },
659 /// Modifier pressed
660 Active {
661 since: Instant,
662 previous_scroll_position: Option<ScrollAnchor>,
663 },
664}
665
666impl EditPredictionPreview {
667 pub fn released_too_fast(&self) -> bool {
668 match self {
669 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
670 EditPredictionPreview::Active { .. } => false,
671 }
672 }
673
674 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
675 if let EditPredictionPreview::Active {
676 previous_scroll_position,
677 ..
678 } = self
679 {
680 *previous_scroll_position = scroll_position;
681 }
682 }
683}
684
685pub struct ContextMenuOptions {
686 pub min_entries_visible: usize,
687 pub max_entries_visible: usize,
688 pub placement: Option<ContextMenuPlacement>,
689}
690
691#[derive(Debug, Clone, PartialEq, Eq)]
692pub enum ContextMenuPlacement {
693 Above,
694 Below,
695}
696
697#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
698struct EditorActionId(usize);
699
700impl EditorActionId {
701 pub fn post_inc(&mut self) -> Self {
702 let answer = self.0;
703
704 *self = Self(answer + 1);
705
706 Self(answer)
707 }
708}
709
710// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
711// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
712
713type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
714type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
715
716#[derive(Default)]
717struct ScrollbarMarkerState {
718 scrollbar_size: Size<Pixels>,
719 dirty: bool,
720 markers: Arc<[PaintQuad]>,
721 pending_refresh: Option<Task<Result<()>>>,
722}
723
724impl ScrollbarMarkerState {
725 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
726 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
727 }
728}
729
730#[derive(Clone, Copy, PartialEq, Eq)]
731pub enum MinimapVisibility {
732 Disabled,
733 Enabled {
734 /// The configuration currently present in the users settings.
735 setting_configuration: bool,
736 /// Whether to override the currently set visibility from the users setting.
737 toggle_override: bool,
738 },
739}
740
741impl MinimapVisibility {
742 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
743 if mode.is_full() {
744 Self::Enabled {
745 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
746 toggle_override: false,
747 }
748 } else {
749 Self::Disabled
750 }
751 }
752
753 fn hidden(&self) -> Self {
754 match *self {
755 Self::Enabled {
756 setting_configuration,
757 ..
758 } => Self::Enabled {
759 setting_configuration,
760 toggle_override: setting_configuration,
761 },
762 Self::Disabled => Self::Disabled,
763 }
764 }
765
766 fn disabled(&self) -> bool {
767 matches!(*self, Self::Disabled)
768 }
769
770 fn settings_visibility(&self) -> bool {
771 match *self {
772 Self::Enabled {
773 setting_configuration,
774 ..
775 } => setting_configuration,
776 _ => false,
777 }
778 }
779
780 fn visible(&self) -> bool {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 toggle_override,
785 } => setting_configuration ^ toggle_override,
786 _ => false,
787 }
788 }
789
790 fn toggle_visibility(&self) -> Self {
791 match *self {
792 Self::Enabled {
793 toggle_override,
794 setting_configuration,
795 } => Self::Enabled {
796 setting_configuration,
797 toggle_override: !toggle_override,
798 },
799 Self::Disabled => Self::Disabled,
800 }
801 }
802}
803
804#[derive(Clone, Debug)]
805struct RunnableTasks {
806 templates: Vec<(TaskSourceKind, TaskTemplate)>,
807 offset: multi_buffer::Anchor,
808 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
809 column: u32,
810 // Values of all named captures, including those starting with '_'
811 extra_variables: HashMap<String, String>,
812 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
813 context_range: Range<BufferOffset>,
814}
815
816impl RunnableTasks {
817 fn resolve<'a>(
818 &'a self,
819 cx: &'a task::TaskContext,
820 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
821 self.templates.iter().filter_map(|(kind, template)| {
822 template
823 .resolve_task(&kind.to_id_base(), cx)
824 .map(|task| (kind.clone(), task))
825 })
826 }
827}
828
829#[derive(Clone)]
830pub struct ResolvedTasks {
831 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
832 position: Anchor,
833}
834
835#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
836struct BufferOffset(usize);
837
838/// Addons allow storing per-editor state in other crates (e.g. Vim)
839pub trait Addon: 'static {
840 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
841
842 fn render_buffer_header_controls(
843 &self,
844 _: &ExcerptInfo,
845 _: &Window,
846 _: &App,
847 ) -> Option<AnyElement> {
848 None
849 }
850
851 fn to_any(&self) -> &dyn std::any::Any;
852
853 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
854 None
855 }
856}
857
858struct ChangeLocation {
859 current: Option<Vec<Anchor>>,
860 original: Vec<Anchor>,
861}
862impl ChangeLocation {
863 fn locations(&self) -> &[Anchor] {
864 self.current.as_ref().unwrap_or(&self.original)
865 }
866}
867
868/// A set of caret positions, registered when the editor was edited.
869pub struct ChangeList {
870 changes: Vec<ChangeLocation>,
871 /// Currently "selected" change.
872 position: Option<usize>,
873}
874
875impl ChangeList {
876 pub fn new() -> Self {
877 Self {
878 changes: Vec::new(),
879 position: None,
880 }
881 }
882
883 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
884 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
885 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
886 if self.changes.is_empty() {
887 return None;
888 }
889
890 let prev = self.position.unwrap_or(self.changes.len());
891 let next = if direction == Direction::Prev {
892 prev.saturating_sub(count)
893 } else {
894 (prev + count).min(self.changes.len() - 1)
895 };
896 self.position = Some(next);
897 self.changes.get(next).map(|change| change.locations())
898 }
899
900 /// Adds a new change to the list, resetting the change list position.
901 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
902 self.position.take();
903 if let Some(last) = self.changes.last_mut()
904 && group
905 {
906 last.current = Some(new_positions)
907 } else {
908 self.changes.push(ChangeLocation {
909 original: new_positions,
910 current: None,
911 });
912 }
913 }
914
915 pub fn last(&self) -> Option<&[Anchor]> {
916 self.changes.last().map(|change| change.locations())
917 }
918
919 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
920 self.changes.last().map(|change| change.original.as_slice())
921 }
922
923 pub fn invert_last_group(&mut self) {
924 if let Some(last) = self.changes.last_mut()
925 && let Some(current) = last.current.as_mut()
926 {
927 mem::swap(&mut last.original, current);
928 }
929 }
930}
931
932#[derive(Clone)]
933struct InlineBlamePopoverState {
934 scroll_handle: ScrollHandle,
935 commit_message: Option<ParsedCommitMessage>,
936 markdown: Entity<Markdown>,
937}
938
939struct InlineBlamePopover {
940 position: gpui::Point<Pixels>,
941 hide_task: Option<Task<()>>,
942 popover_bounds: Option<Bounds<Pixels>>,
943 popover_state: InlineBlamePopoverState,
944 keyboard_grace: bool,
945}
946
947enum SelectionDragState {
948 /// State when no drag related activity is detected.
949 None,
950 /// State when the mouse is down on a selection that is about to be dragged.
951 ReadyToDrag {
952 selection: Selection<Anchor>,
953 click_position: gpui::Point<Pixels>,
954 mouse_down_time: Instant,
955 },
956 /// State when the mouse is dragging the selection in the editor.
957 Dragging {
958 selection: Selection<Anchor>,
959 drop_cursor: Selection<Anchor>,
960 hide_drop_cursor: bool,
961 },
962}
963
964enum ColumnarSelectionState {
965 FromMouse {
966 selection_tail: Anchor,
967 display_point: Option<DisplayPoint>,
968 },
969 FromSelection {
970 selection_tail: Anchor,
971 },
972}
973
974/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
975/// a breakpoint on them.
976#[derive(Clone, Copy, Debug, PartialEq, Eq)]
977struct PhantomBreakpointIndicator {
978 display_row: DisplayRow,
979 /// There's a small debounce between hovering over the line and showing the indicator.
980 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
981 is_active: bool,
982 collides_with_existing_breakpoint: bool,
983}
984
985/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
986///
987/// See the [module level documentation](self) for more information.
988pub struct Editor {
989 focus_handle: FocusHandle,
990 last_focused_descendant: Option<WeakFocusHandle>,
991 /// The text buffer being edited
992 buffer: Entity<MultiBuffer>,
993 /// Map of how text in the buffer should be displayed.
994 /// Handles soft wraps, folds, fake inlay text insertions, etc.
995 pub display_map: Entity<DisplayMap>,
996 placeholder_display_map: Option<Entity<DisplayMap>>,
997 pub selections: SelectionsCollection,
998 pub scroll_manager: ScrollManager,
999 /// When inline assist editors are linked, they all render cursors because
1000 /// typing enters text into each of them, even the ones that aren't focused.
1001 pub(crate) show_cursor_when_unfocused: bool,
1002 columnar_selection_state: Option<ColumnarSelectionState>,
1003 add_selections_state: Option<AddSelectionsState>,
1004 select_next_state: Option<SelectNextState>,
1005 select_prev_state: Option<SelectNextState>,
1006 selection_history: SelectionHistory,
1007 defer_selection_effects: bool,
1008 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1009 autoclose_regions: Vec<AutocloseRegion>,
1010 snippet_stack: InvalidationStack<SnippetState>,
1011 select_syntax_node_history: SelectSyntaxNodeHistory,
1012 ime_transaction: Option<TransactionId>,
1013 pub diagnostics_max_severity: DiagnosticSeverity,
1014 active_diagnostics: ActiveDiagnostic,
1015 show_inline_diagnostics: bool,
1016 inline_diagnostics_update: Task<()>,
1017 inline_diagnostics_enabled: bool,
1018 diagnostics_enabled: bool,
1019 word_completions_enabled: bool,
1020 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1021 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1022 hard_wrap: Option<usize>,
1023 project: Option<Entity<Project>>,
1024 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1025 completion_provider: Option<Rc<dyn CompletionProvider>>,
1026 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1027 blink_manager: Entity<BlinkManager>,
1028 show_cursor_names: bool,
1029 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1030 pub show_local_selections: bool,
1031 mode: EditorMode,
1032 show_breadcrumbs: bool,
1033 show_gutter: bool,
1034 show_scrollbars: ScrollbarAxes,
1035 minimap_visibility: MinimapVisibility,
1036 offset_content: bool,
1037 disable_expand_excerpt_buttons: bool,
1038 show_line_numbers: Option<bool>,
1039 use_relative_line_numbers: Option<bool>,
1040 show_git_diff_gutter: Option<bool>,
1041 show_code_actions: Option<bool>,
1042 show_runnables: Option<bool>,
1043 show_breakpoints: Option<bool>,
1044 show_wrap_guides: Option<bool>,
1045 show_indent_guides: Option<bool>,
1046 highlight_order: usize,
1047 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1048 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1049 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1050 scrollbar_marker_state: ScrollbarMarkerState,
1051 active_indent_guides_state: ActiveIndentGuidesState,
1052 nav_history: Option<ItemNavHistory>,
1053 context_menu: RefCell<Option<CodeContextMenu>>,
1054 context_menu_options: Option<ContextMenuOptions>,
1055 mouse_context_menu: Option<MouseContextMenu>,
1056 completion_tasks: Vec<(CompletionId, Task<()>)>,
1057 inline_blame_popover: Option<InlineBlamePopover>,
1058 inline_blame_popover_show_task: Option<Task<()>>,
1059 signature_help_state: SignatureHelpState,
1060 auto_signature_help: Option<bool>,
1061 find_all_references_task_sources: Vec<Anchor>,
1062 next_completion_id: CompletionId,
1063 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1064 code_actions_task: Option<Task<Result<()>>>,
1065 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1066 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1067 document_highlights_task: Option<Task<()>>,
1068 linked_editing_range_task: Option<Task<Option<()>>>,
1069 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1070 pending_rename: Option<RenameState>,
1071 searchable: bool,
1072 cursor_shape: CursorShape,
1073 current_line_highlight: Option<CurrentLineHighlight>,
1074 collapse_matches: bool,
1075 autoindent_mode: Option<AutoindentMode>,
1076 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1077 input_enabled: bool,
1078 use_modal_editing: bool,
1079 read_only: bool,
1080 leader_id: Option<CollaboratorId>,
1081 remote_id: Option<ViewId>,
1082 pub hover_state: HoverState,
1083 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1084 gutter_hovered: bool,
1085 hovered_link_state: Option<HoveredLinkState>,
1086 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1087 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1088 active_edit_prediction: Option<EditPredictionState>,
1089 /// Used to prevent flickering as the user types while the menu is open
1090 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1091 edit_prediction_settings: EditPredictionSettings,
1092 edit_predictions_hidden_for_vim_mode: bool,
1093 show_edit_predictions_override: Option<bool>,
1094 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1095 edit_prediction_preview: EditPredictionPreview,
1096 edit_prediction_indent_conflict: bool,
1097 edit_prediction_requires_modifier_in_indent_conflict: bool,
1098 next_inlay_id: usize,
1099 next_color_inlay_id: usize,
1100 _subscriptions: Vec<Subscription>,
1101 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1102 gutter_dimensions: GutterDimensions,
1103 style: Option<EditorStyle>,
1104 text_style_refinement: Option<TextStyleRefinement>,
1105 next_editor_action_id: EditorActionId,
1106 editor_actions: Rc<
1107 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1108 >,
1109 use_autoclose: bool,
1110 use_auto_surround: bool,
1111 auto_replace_emoji_shortcode: bool,
1112 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1113 show_git_blame_gutter: bool,
1114 show_git_blame_inline: bool,
1115 show_git_blame_inline_delay_task: Option<Task<()>>,
1116 git_blame_inline_enabled: bool,
1117 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1118 serialize_dirty_buffers: bool,
1119 show_selection_menu: Option<bool>,
1120 blame: Option<Entity<GitBlame>>,
1121 blame_subscription: Option<Subscription>,
1122 custom_context_menu: Option<
1123 Box<
1124 dyn 'static
1125 + Fn(
1126 &mut Self,
1127 DisplayPoint,
1128 &mut Window,
1129 &mut Context<Self>,
1130 ) -> Option<Entity<ui::ContextMenu>>,
1131 >,
1132 >,
1133 last_bounds: Option<Bounds<Pixels>>,
1134 last_position_map: Option<Rc<PositionMap>>,
1135 expect_bounds_change: Option<Bounds<Pixels>>,
1136 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1137 tasks_update_task: Option<Task<()>>,
1138 breakpoint_store: Option<Entity<BreakpointStore>>,
1139 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1140 hovered_diff_hunk_row: Option<DisplayRow>,
1141 pull_diagnostics_task: Task<()>,
1142 in_project_search: bool,
1143 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1144 breadcrumb_header: Option<String>,
1145 focused_block: Option<FocusedBlock>,
1146 next_scroll_position: NextScrollCursorCenterTopBottom,
1147 addons: HashMap<TypeId, Box<dyn Addon>>,
1148 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1149 load_diff_task: Option<Shared<Task<()>>>,
1150 /// Whether we are temporarily displaying a diff other than git's
1151 temporary_diff_override: bool,
1152 selection_mark_mode: bool,
1153 toggle_fold_multiple_buffers: Task<()>,
1154 _scroll_cursor_center_top_bottom_task: Task<()>,
1155 serialize_selections: Task<()>,
1156 serialize_folds: Task<()>,
1157 mouse_cursor_hidden: bool,
1158 minimap: Option<Entity<Self>>,
1159 hide_mouse_mode: HideMouseMode,
1160 pub change_list: ChangeList,
1161 inline_value_cache: InlineValueCache,
1162 selection_drag_state: SelectionDragState,
1163 colors: Option<LspColorData>,
1164 post_scroll_update: Task<()>,
1165 refresh_colors_task: Task<()>,
1166 inlay_hints: Option<LspInlayHintData>,
1167 folding_newlines: Task<()>,
1168 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1169}
1170
1171fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1172 if debounce_ms > 0 {
1173 Some(Duration::from_millis(debounce_ms))
1174 } else {
1175 None
1176 }
1177}
1178
1179#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1180enum NextScrollCursorCenterTopBottom {
1181 #[default]
1182 Center,
1183 Top,
1184 Bottom,
1185}
1186
1187impl NextScrollCursorCenterTopBottom {
1188 fn next(&self) -> Self {
1189 match self {
1190 Self::Center => Self::Top,
1191 Self::Top => Self::Bottom,
1192 Self::Bottom => Self::Center,
1193 }
1194 }
1195}
1196
1197#[derive(Clone)]
1198pub struct EditorSnapshot {
1199 pub mode: EditorMode,
1200 show_gutter: bool,
1201 show_line_numbers: Option<bool>,
1202 show_git_diff_gutter: Option<bool>,
1203 show_code_actions: Option<bool>,
1204 show_runnables: Option<bool>,
1205 show_breakpoints: Option<bool>,
1206 git_blame_gutter_max_author_length: Option<usize>,
1207 pub display_snapshot: DisplaySnapshot,
1208 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1209 is_focused: bool,
1210 scroll_anchor: ScrollAnchor,
1211 ongoing_scroll: OngoingScroll,
1212 current_line_highlight: CurrentLineHighlight,
1213 gutter_hovered: bool,
1214}
1215
1216#[derive(Default, Debug, Clone, Copy)]
1217pub struct GutterDimensions {
1218 pub left_padding: Pixels,
1219 pub right_padding: Pixels,
1220 pub width: Pixels,
1221 pub margin: Pixels,
1222 pub git_blame_entries_width: Option<Pixels>,
1223}
1224
1225impl GutterDimensions {
1226 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1227 Self {
1228 margin: Self::default_gutter_margin(font_id, font_size, cx),
1229 ..Default::default()
1230 }
1231 }
1232
1233 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1234 -cx.text_system().descent(font_id, font_size)
1235 }
1236 /// The full width of the space taken up by the gutter.
1237 pub fn full_width(&self) -> Pixels {
1238 self.margin + self.width
1239 }
1240
1241 /// The width of the space reserved for the fold indicators,
1242 /// use alongside 'justify_end' and `gutter_width` to
1243 /// right align content with the line numbers
1244 pub fn fold_area_width(&self) -> Pixels {
1245 self.margin + self.right_padding
1246 }
1247}
1248
1249struct CharacterDimensions {
1250 em_width: Pixels,
1251 em_advance: Pixels,
1252 line_height: Pixels,
1253}
1254
1255#[derive(Debug)]
1256pub struct RemoteSelection {
1257 pub replica_id: ReplicaId,
1258 pub selection: Selection<Anchor>,
1259 pub cursor_shape: CursorShape,
1260 pub collaborator_id: CollaboratorId,
1261 pub line_mode: bool,
1262 pub user_name: Option<SharedString>,
1263 pub color: PlayerColor,
1264}
1265
1266#[derive(Clone, Debug)]
1267struct SelectionHistoryEntry {
1268 selections: Arc<[Selection<Anchor>]>,
1269 select_next_state: Option<SelectNextState>,
1270 select_prev_state: Option<SelectNextState>,
1271 add_selections_state: Option<AddSelectionsState>,
1272}
1273
1274#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1275enum SelectionHistoryMode {
1276 Normal,
1277 Undoing,
1278 Redoing,
1279 Skipping,
1280}
1281
1282#[derive(Clone, PartialEq, Eq, Hash)]
1283struct HoveredCursor {
1284 replica_id: ReplicaId,
1285 selection_id: usize,
1286}
1287
1288impl Default for SelectionHistoryMode {
1289 fn default() -> Self {
1290 Self::Normal
1291 }
1292}
1293
1294#[derive(Debug)]
1295/// SelectionEffects controls the side-effects of updating the selection.
1296///
1297/// The default behaviour does "what you mostly want":
1298/// - it pushes to the nav history if the cursor moved by >10 lines
1299/// - it re-triggers completion requests
1300/// - it scrolls to fit
1301///
1302/// You might want to modify these behaviours. For example when doing a "jump"
1303/// like go to definition, we always want to add to nav history; but when scrolling
1304/// in vim mode we never do.
1305///
1306/// Similarly, you might want to disable scrolling if you don't want the viewport to
1307/// move.
1308#[derive(Clone)]
1309pub struct SelectionEffects {
1310 nav_history: Option<bool>,
1311 completions: bool,
1312 scroll: Option<Autoscroll>,
1313}
1314
1315impl Default for SelectionEffects {
1316 fn default() -> Self {
1317 Self {
1318 nav_history: None,
1319 completions: true,
1320 scroll: Some(Autoscroll::fit()),
1321 }
1322 }
1323}
1324impl SelectionEffects {
1325 pub fn scroll(scroll: Autoscroll) -> Self {
1326 Self {
1327 scroll: Some(scroll),
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn no_scroll() -> Self {
1333 Self {
1334 scroll: None,
1335 ..Default::default()
1336 }
1337 }
1338
1339 pub fn completions(self, completions: bool) -> Self {
1340 Self {
1341 completions,
1342 ..self
1343 }
1344 }
1345
1346 pub fn nav_history(self, nav_history: bool) -> Self {
1347 Self {
1348 nav_history: Some(nav_history),
1349 ..self
1350 }
1351 }
1352}
1353
1354struct DeferredSelectionEffectsState {
1355 changed: bool,
1356 effects: SelectionEffects,
1357 old_cursor_position: Anchor,
1358 history_entry: SelectionHistoryEntry,
1359}
1360
1361#[derive(Default)]
1362struct SelectionHistory {
1363 #[allow(clippy::type_complexity)]
1364 selections_by_transaction:
1365 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1366 mode: SelectionHistoryMode,
1367 undo_stack: VecDeque<SelectionHistoryEntry>,
1368 redo_stack: VecDeque<SelectionHistoryEntry>,
1369}
1370
1371impl SelectionHistory {
1372 #[track_caller]
1373 fn insert_transaction(
1374 &mut self,
1375 transaction_id: TransactionId,
1376 selections: Arc<[Selection<Anchor>]>,
1377 ) {
1378 if selections.is_empty() {
1379 log::error!(
1380 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1381 std::panic::Location::caller()
1382 );
1383 return;
1384 }
1385 self.selections_by_transaction
1386 .insert(transaction_id, (selections, None));
1387 }
1388
1389 #[allow(clippy::type_complexity)]
1390 fn transaction(
1391 &self,
1392 transaction_id: TransactionId,
1393 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1394 self.selections_by_transaction.get(&transaction_id)
1395 }
1396
1397 #[allow(clippy::type_complexity)]
1398 fn transaction_mut(
1399 &mut self,
1400 transaction_id: TransactionId,
1401 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1402 self.selections_by_transaction.get_mut(&transaction_id)
1403 }
1404
1405 fn push(&mut self, entry: SelectionHistoryEntry) {
1406 if !entry.selections.is_empty() {
1407 match self.mode {
1408 SelectionHistoryMode::Normal => {
1409 self.push_undo(entry);
1410 self.redo_stack.clear();
1411 }
1412 SelectionHistoryMode::Undoing => self.push_redo(entry),
1413 SelectionHistoryMode::Redoing => self.push_undo(entry),
1414 SelectionHistoryMode::Skipping => {}
1415 }
1416 }
1417 }
1418
1419 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1420 if self
1421 .undo_stack
1422 .back()
1423 .is_none_or(|e| e.selections != entry.selections)
1424 {
1425 self.undo_stack.push_back(entry);
1426 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1427 self.undo_stack.pop_front();
1428 }
1429 }
1430 }
1431
1432 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1433 if self
1434 .redo_stack
1435 .back()
1436 .is_none_or(|e| e.selections != entry.selections)
1437 {
1438 self.redo_stack.push_back(entry);
1439 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1440 self.redo_stack.pop_front();
1441 }
1442 }
1443 }
1444}
1445
1446#[derive(Clone, Copy)]
1447pub struct RowHighlightOptions {
1448 pub autoscroll: bool,
1449 pub include_gutter: bool,
1450}
1451
1452impl Default for RowHighlightOptions {
1453 fn default() -> Self {
1454 Self {
1455 autoscroll: Default::default(),
1456 include_gutter: true,
1457 }
1458 }
1459}
1460
1461struct RowHighlight {
1462 index: usize,
1463 range: Range<Anchor>,
1464 color: Hsla,
1465 options: RowHighlightOptions,
1466 type_id: TypeId,
1467}
1468
1469#[derive(Clone, Debug)]
1470struct AddSelectionsState {
1471 groups: Vec<AddSelectionsGroup>,
1472}
1473
1474#[derive(Clone, Debug)]
1475struct AddSelectionsGroup {
1476 above: bool,
1477 stack: Vec<usize>,
1478}
1479
1480#[derive(Clone)]
1481struct SelectNextState {
1482 query: AhoCorasick,
1483 wordwise: bool,
1484 done: bool,
1485}
1486
1487impl std::fmt::Debug for SelectNextState {
1488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1489 f.debug_struct(std::any::type_name::<Self>())
1490 .field("wordwise", &self.wordwise)
1491 .field("done", &self.done)
1492 .finish()
1493 }
1494}
1495
1496#[derive(Debug)]
1497struct AutocloseRegion {
1498 selection_id: usize,
1499 range: Range<Anchor>,
1500 pair: BracketPair,
1501}
1502
1503#[derive(Debug)]
1504struct SnippetState {
1505 ranges: Vec<Vec<Range<Anchor>>>,
1506 active_index: usize,
1507 choices: Vec<Option<Vec<String>>>,
1508}
1509
1510#[doc(hidden)]
1511pub struct RenameState {
1512 pub range: Range<Anchor>,
1513 pub old_name: Arc<str>,
1514 pub editor: Entity<Editor>,
1515 block_id: CustomBlockId,
1516}
1517
1518struct InvalidationStack<T>(Vec<T>);
1519
1520struct RegisteredEditPredictionProvider {
1521 provider: Arc<dyn EditPredictionProviderHandle>,
1522 _subscription: Subscription,
1523}
1524
1525#[derive(Debug, PartialEq, Eq)]
1526pub struct ActiveDiagnosticGroup {
1527 pub active_range: Range<Anchor>,
1528 pub active_message: String,
1529 pub group_id: usize,
1530 pub blocks: HashSet<CustomBlockId>,
1531}
1532
1533#[derive(Debug, PartialEq, Eq)]
1534
1535pub(crate) enum ActiveDiagnostic {
1536 None,
1537 All,
1538 Group(ActiveDiagnosticGroup),
1539}
1540
1541#[derive(Serialize, Deserialize, Clone, Debug)]
1542pub struct ClipboardSelection {
1543 /// The number of bytes in this selection.
1544 pub len: usize,
1545 /// Whether this was a full-line selection.
1546 pub is_entire_line: bool,
1547 /// The indentation of the first line when this content was originally copied.
1548 pub first_line_indent: u32,
1549}
1550
1551// selections, scroll behavior, was newest selection reversed
1552type SelectSyntaxNodeHistoryState = (
1553 Box<[Selection<usize>]>,
1554 SelectSyntaxNodeScrollBehavior,
1555 bool,
1556);
1557
1558#[derive(Default)]
1559struct SelectSyntaxNodeHistory {
1560 stack: Vec<SelectSyntaxNodeHistoryState>,
1561 // disable temporarily to allow changing selections without losing the stack
1562 pub disable_clearing: bool,
1563}
1564
1565impl SelectSyntaxNodeHistory {
1566 pub fn try_clear(&mut self) {
1567 if !self.disable_clearing {
1568 self.stack.clear();
1569 }
1570 }
1571
1572 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1573 self.stack.push(selection);
1574 }
1575
1576 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1577 self.stack.pop()
1578 }
1579}
1580
1581enum SelectSyntaxNodeScrollBehavior {
1582 CursorTop,
1583 FitSelection,
1584 CursorBottom,
1585}
1586
1587#[derive(Debug)]
1588pub(crate) struct NavigationData {
1589 cursor_anchor: Anchor,
1590 cursor_position: Point,
1591 scroll_anchor: ScrollAnchor,
1592 scroll_top_row: u32,
1593}
1594
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1596pub enum GotoDefinitionKind {
1597 Symbol,
1598 Declaration,
1599 Type,
1600 Implementation,
1601}
1602
1603pub enum FormatTarget {
1604 Buffers(HashSet<Entity<Buffer>>),
1605 Ranges(Vec<Range<MultiBufferPoint>>),
1606}
1607
1608pub(crate) struct FocusedBlock {
1609 id: BlockId,
1610 focus_handle: WeakFocusHandle,
1611}
1612
1613#[derive(Clone)]
1614enum JumpData {
1615 MultiBufferRow {
1616 row: MultiBufferRow,
1617 line_offset_from_top: u32,
1618 },
1619 MultiBufferPoint {
1620 excerpt_id: ExcerptId,
1621 position: Point,
1622 anchor: text::Anchor,
1623 line_offset_from_top: u32,
1624 },
1625}
1626
1627pub enum MultibufferSelectionMode {
1628 First,
1629 All,
1630}
1631
1632#[derive(Clone, Copy, Debug, Default)]
1633pub struct RewrapOptions {
1634 pub override_language_settings: bool,
1635 pub preserve_existing_whitespace: bool,
1636}
1637
1638impl Editor {
1639 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1640 let buffer = cx.new(|cx| Buffer::local("", cx));
1641 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1642 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1643 }
1644
1645 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1646 let buffer = cx.new(|cx| Buffer::local("", cx));
1647 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1648 Self::new(EditorMode::full(), buffer, None, window, cx)
1649 }
1650
1651 pub fn auto_height(
1652 min_lines: usize,
1653 max_lines: usize,
1654 window: &mut Window,
1655 cx: &mut Context<Self>,
1656 ) -> Self {
1657 let buffer = cx.new(|cx| Buffer::local("", cx));
1658 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1659 Self::new(
1660 EditorMode::AutoHeight {
1661 min_lines,
1662 max_lines: Some(max_lines),
1663 },
1664 buffer,
1665 None,
1666 window,
1667 cx,
1668 )
1669 }
1670
1671 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1672 /// The editor grows as tall as needed to fit its content.
1673 pub fn auto_height_unbounded(
1674 min_lines: usize,
1675 window: &mut Window,
1676 cx: &mut Context<Self>,
1677 ) -> Self {
1678 let buffer = cx.new(|cx| Buffer::local("", cx));
1679 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1680 Self::new(
1681 EditorMode::AutoHeight {
1682 min_lines,
1683 max_lines: None,
1684 },
1685 buffer,
1686 None,
1687 window,
1688 cx,
1689 )
1690 }
1691
1692 pub fn for_buffer(
1693 buffer: Entity<Buffer>,
1694 project: Option<Entity<Project>>,
1695 window: &mut Window,
1696 cx: &mut Context<Self>,
1697 ) -> Self {
1698 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1699 Self::new(EditorMode::full(), buffer, project, window, cx)
1700 }
1701
1702 pub fn for_multibuffer(
1703 buffer: Entity<MultiBuffer>,
1704 project: Option<Entity<Project>>,
1705 window: &mut Window,
1706 cx: &mut Context<Self>,
1707 ) -> Self {
1708 Self::new(EditorMode::full(), buffer, project, window, cx)
1709 }
1710
1711 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1712 let mut clone = Self::new(
1713 self.mode.clone(),
1714 self.buffer.clone(),
1715 self.project.clone(),
1716 window,
1717 cx,
1718 );
1719 self.display_map.update(cx, |display_map, cx| {
1720 let snapshot = display_map.snapshot(cx);
1721 clone.display_map.update(cx, |display_map, cx| {
1722 display_map.set_state(&snapshot, cx);
1723 });
1724 });
1725 clone.folds_did_change(cx);
1726 clone.selections.clone_state(&self.selections);
1727 clone.scroll_manager.clone_state(&self.scroll_manager);
1728 clone.searchable = self.searchable;
1729 clone.read_only = self.read_only;
1730 clone
1731 }
1732
1733 pub fn new(
1734 mode: EditorMode,
1735 buffer: Entity<MultiBuffer>,
1736 project: Option<Entity<Project>>,
1737 window: &mut Window,
1738 cx: &mut Context<Self>,
1739 ) -> Self {
1740 Editor::new_internal(mode, buffer, project, None, window, cx)
1741 }
1742
1743 fn new_internal(
1744 mode: EditorMode,
1745 multi_buffer: Entity<MultiBuffer>,
1746 project: Option<Entity<Project>>,
1747 display_map: Option<Entity<DisplayMap>>,
1748 window: &mut Window,
1749 cx: &mut Context<Self>,
1750 ) -> Self {
1751 debug_assert!(
1752 display_map.is_none() || mode.is_minimap(),
1753 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1754 );
1755
1756 let full_mode = mode.is_full();
1757 let is_minimap = mode.is_minimap();
1758 let diagnostics_max_severity = if full_mode {
1759 EditorSettings::get_global(cx)
1760 .diagnostics_max_severity
1761 .unwrap_or(DiagnosticSeverity::Hint)
1762 } else {
1763 DiagnosticSeverity::Off
1764 };
1765 let style = window.text_style();
1766 let font_size = style.font_size.to_pixels(window.rem_size());
1767 let editor = cx.entity().downgrade();
1768 let fold_placeholder = FoldPlaceholder {
1769 constrain_width: false,
1770 render: Arc::new(move |fold_id, fold_range, cx| {
1771 let editor = editor.clone();
1772 div()
1773 .id(fold_id)
1774 .bg(cx.theme().colors().ghost_element_background)
1775 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1776 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1777 .rounded_xs()
1778 .size_full()
1779 .cursor_pointer()
1780 .child("⋯")
1781 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1782 .on_click(move |_, _window, cx| {
1783 editor
1784 .update(cx, |editor, cx| {
1785 editor.unfold_ranges(
1786 &[fold_range.start..fold_range.end],
1787 true,
1788 false,
1789 cx,
1790 );
1791 cx.stop_propagation();
1792 })
1793 .ok();
1794 })
1795 .into_any()
1796 }),
1797 merge_adjacent: true,
1798 ..FoldPlaceholder::default()
1799 };
1800 let display_map = display_map.unwrap_or_else(|| {
1801 cx.new(|cx| {
1802 DisplayMap::new(
1803 multi_buffer.clone(),
1804 style.font(),
1805 font_size,
1806 None,
1807 FILE_HEADER_HEIGHT,
1808 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1809 fold_placeholder,
1810 diagnostics_max_severity,
1811 cx,
1812 )
1813 })
1814 });
1815
1816 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1817
1818 let blink_manager = cx.new(|cx| {
1819 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1820 if is_minimap {
1821 blink_manager.disable(cx);
1822 }
1823 blink_manager
1824 });
1825
1826 let soft_wrap_mode_override =
1827 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1828
1829 let mut project_subscriptions = Vec::new();
1830 if full_mode && let Some(project) = project.as_ref() {
1831 project_subscriptions.push(cx.subscribe_in(
1832 project,
1833 window,
1834 |editor, _, event, window, cx| match event {
1835 project::Event::RefreshCodeLens => {
1836 // we always query lens with actions, without storing them, always refreshing them
1837 }
1838 project::Event::RefreshInlayHints(server_id) => {
1839 editor.refresh_inlay_hints(
1840 InlayHintRefreshReason::RefreshRequested(*server_id),
1841 cx,
1842 );
1843 }
1844 project::Event::LanguageServerRemoved(..) => {
1845 if editor.tasks_update_task.is_none() {
1846 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1847 }
1848 editor.registered_buffers.clear();
1849 editor.register_visible_buffers(cx);
1850 }
1851 project::Event::LanguageServerAdded(..) => {
1852 if editor.tasks_update_task.is_none() {
1853 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1854 }
1855 }
1856 project::Event::SnippetEdit(id, snippet_edits) => {
1857 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1858 let focus_handle = editor.focus_handle(cx);
1859 if focus_handle.is_focused(window) {
1860 let snapshot = buffer.read(cx).snapshot();
1861 for (range, snippet) in snippet_edits {
1862 let editor_range =
1863 language::range_from_lsp(*range).to_offset(&snapshot);
1864 editor
1865 .insert_snippet(
1866 &[editor_range],
1867 snippet.clone(),
1868 window,
1869 cx,
1870 )
1871 .ok();
1872 }
1873 }
1874 }
1875 }
1876 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1877 let buffer_id = *buffer_id;
1878 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1879 editor.register_buffer(buffer_id, cx);
1880 editor.update_lsp_data(Some(buffer_id), window, cx);
1881 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1882 refresh_linked_ranges(editor, window, cx);
1883 editor.refresh_code_actions(window, cx);
1884 editor.refresh_document_highlights(cx);
1885 }
1886 }
1887
1888 project::Event::EntryRenamed(transaction) => {
1889 let Some(workspace) = editor.workspace() else {
1890 return;
1891 };
1892 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1893 else {
1894 return;
1895 };
1896 if active_editor.entity_id() == cx.entity_id() {
1897 let edited_buffers_already_open = {
1898 let other_editors: Vec<Entity<Editor>> = workspace
1899 .read(cx)
1900 .panes()
1901 .iter()
1902 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1903 .filter(|editor| editor.entity_id() != cx.entity_id())
1904 .collect();
1905
1906 transaction.0.keys().all(|buffer| {
1907 other_editors.iter().any(|editor| {
1908 let multi_buffer = editor.read(cx).buffer();
1909 multi_buffer.read(cx).is_singleton()
1910 && multi_buffer.read(cx).as_singleton().map_or(
1911 false,
1912 |singleton| {
1913 singleton.entity_id() == buffer.entity_id()
1914 },
1915 )
1916 })
1917 })
1918 };
1919
1920 if !edited_buffers_already_open {
1921 let workspace = workspace.downgrade();
1922 let transaction = transaction.clone();
1923 cx.defer_in(window, move |_, window, cx| {
1924 cx.spawn_in(window, async move |editor, cx| {
1925 Self::open_project_transaction(
1926 &editor,
1927 workspace,
1928 transaction,
1929 "Rename".to_string(),
1930 cx,
1931 )
1932 .await
1933 .ok()
1934 })
1935 .detach();
1936 });
1937 }
1938 }
1939 }
1940
1941 _ => {}
1942 },
1943 ));
1944 if let Some(task_inventory) = project
1945 .read(cx)
1946 .task_store()
1947 .read(cx)
1948 .task_inventory()
1949 .cloned()
1950 {
1951 project_subscriptions.push(cx.observe_in(
1952 &task_inventory,
1953 window,
1954 |editor, _, window, cx| {
1955 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1956 },
1957 ));
1958 };
1959
1960 project_subscriptions.push(cx.subscribe_in(
1961 &project.read(cx).breakpoint_store(),
1962 window,
1963 |editor, _, event, window, cx| match event {
1964 BreakpointStoreEvent::ClearDebugLines => {
1965 editor.clear_row_highlights::<ActiveDebugLine>();
1966 editor.refresh_inline_values(cx);
1967 }
1968 BreakpointStoreEvent::SetDebugLine => {
1969 if editor.go_to_active_debug_line(window, cx) {
1970 cx.stop_propagation();
1971 }
1972
1973 editor.refresh_inline_values(cx);
1974 }
1975 _ => {}
1976 },
1977 ));
1978 let git_store = project.read(cx).git_store().clone();
1979 let project = project.clone();
1980 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1981 if let GitStoreEvent::RepositoryUpdated(
1982 _,
1983 RepositoryEvent::Updated {
1984 new_instance: true, ..
1985 },
1986 _,
1987 ) = event
1988 {
1989 this.load_diff_task = Some(
1990 update_uncommitted_diff_for_buffer(
1991 cx.entity(),
1992 &project,
1993 this.buffer.read(cx).all_buffers(),
1994 this.buffer.clone(),
1995 cx,
1996 )
1997 .shared(),
1998 );
1999 }
2000 }));
2001 }
2002
2003 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2004
2005 let inlay_hint_settings =
2006 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2007 let focus_handle = cx.focus_handle();
2008 if !is_minimap {
2009 cx.on_focus(&focus_handle, window, Self::handle_focus)
2010 .detach();
2011 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2012 .detach();
2013 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2014 .detach();
2015 cx.on_blur(&focus_handle, window, Self::handle_blur)
2016 .detach();
2017 cx.observe_pending_input(window, Self::observe_pending_input)
2018 .detach();
2019 }
2020
2021 let show_indent_guides =
2022 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2023 Some(false)
2024 } else {
2025 None
2026 };
2027
2028 let breakpoint_store = match (&mode, project.as_ref()) {
2029 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2030 _ => None,
2031 };
2032
2033 let mut code_action_providers = Vec::new();
2034 let mut load_uncommitted_diff = None;
2035 if let Some(project) = project.clone() {
2036 load_uncommitted_diff = Some(
2037 update_uncommitted_diff_for_buffer(
2038 cx.entity(),
2039 &project,
2040 multi_buffer.read(cx).all_buffers(),
2041 multi_buffer.clone(),
2042 cx,
2043 )
2044 .shared(),
2045 );
2046 code_action_providers.push(Rc::new(project) as Rc<_>);
2047 }
2048
2049 let mut editor = Self {
2050 focus_handle,
2051 show_cursor_when_unfocused: false,
2052 last_focused_descendant: None,
2053 buffer: multi_buffer.clone(),
2054 display_map: display_map.clone(),
2055 placeholder_display_map: None,
2056 selections,
2057 scroll_manager: ScrollManager::new(cx),
2058 columnar_selection_state: None,
2059 add_selections_state: None,
2060 select_next_state: None,
2061 select_prev_state: None,
2062 selection_history: SelectionHistory::default(),
2063 defer_selection_effects: false,
2064 deferred_selection_effects_state: None,
2065 autoclose_regions: Vec::new(),
2066 snippet_stack: InvalidationStack::default(),
2067 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2068 ime_transaction: None,
2069 active_diagnostics: ActiveDiagnostic::None,
2070 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2071 inline_diagnostics_update: Task::ready(()),
2072 inline_diagnostics: Vec::new(),
2073 soft_wrap_mode_override,
2074 diagnostics_max_severity,
2075 hard_wrap: None,
2076 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2077 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2078 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2079 project,
2080 blink_manager: blink_manager.clone(),
2081 show_local_selections: true,
2082 show_scrollbars: ScrollbarAxes {
2083 horizontal: full_mode,
2084 vertical: full_mode,
2085 },
2086 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2087 offset_content: !matches!(mode, EditorMode::SingleLine),
2088 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2089 show_gutter: full_mode,
2090 show_line_numbers: (!full_mode).then_some(false),
2091 use_relative_line_numbers: None,
2092 disable_expand_excerpt_buttons: !full_mode,
2093 show_git_diff_gutter: None,
2094 show_code_actions: None,
2095 show_runnables: None,
2096 show_breakpoints: None,
2097 show_wrap_guides: None,
2098 show_indent_guides,
2099 highlight_order: 0,
2100 highlighted_rows: HashMap::default(),
2101 background_highlights: HashMap::default(),
2102 gutter_highlights: HashMap::default(),
2103 scrollbar_marker_state: ScrollbarMarkerState::default(),
2104 active_indent_guides_state: ActiveIndentGuidesState::default(),
2105 nav_history: None,
2106 context_menu: RefCell::new(None),
2107 context_menu_options: None,
2108 mouse_context_menu: None,
2109 completion_tasks: Vec::new(),
2110 inline_blame_popover: None,
2111 inline_blame_popover_show_task: None,
2112 signature_help_state: SignatureHelpState::default(),
2113 auto_signature_help: None,
2114 find_all_references_task_sources: Vec::new(),
2115 next_completion_id: 0,
2116 next_inlay_id: 0,
2117 code_action_providers,
2118 available_code_actions: None,
2119 code_actions_task: None,
2120 quick_selection_highlight_task: None,
2121 debounced_selection_highlight_task: None,
2122 document_highlights_task: None,
2123 linked_editing_range_task: None,
2124 pending_rename: None,
2125 searchable: !is_minimap,
2126 cursor_shape: EditorSettings::get_global(cx)
2127 .cursor_shape
2128 .unwrap_or_default(),
2129 current_line_highlight: None,
2130 autoindent_mode: Some(AutoindentMode::EachLine),
2131 collapse_matches: false,
2132 workspace: None,
2133 input_enabled: !is_minimap,
2134 use_modal_editing: full_mode,
2135 read_only: is_minimap,
2136 use_autoclose: true,
2137 use_auto_surround: true,
2138 auto_replace_emoji_shortcode: false,
2139 jsx_tag_auto_close_enabled_in_any_buffer: false,
2140 leader_id: None,
2141 remote_id: None,
2142 hover_state: HoverState::default(),
2143 pending_mouse_down: None,
2144 hovered_link_state: None,
2145 edit_prediction_provider: None,
2146 active_edit_prediction: None,
2147 stale_edit_prediction_in_menu: None,
2148 edit_prediction_preview: EditPredictionPreview::Inactive {
2149 released_too_fast: false,
2150 },
2151 inline_diagnostics_enabled: full_mode,
2152 diagnostics_enabled: full_mode,
2153 word_completions_enabled: full_mode,
2154 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2155 gutter_hovered: false,
2156 pixel_position_of_newest_cursor: None,
2157 last_bounds: None,
2158 last_position_map: None,
2159 expect_bounds_change: None,
2160 gutter_dimensions: GutterDimensions::default(),
2161 style: None,
2162 show_cursor_names: false,
2163 hovered_cursors: HashMap::default(),
2164 next_editor_action_id: EditorActionId::default(),
2165 editor_actions: Rc::default(),
2166 edit_predictions_hidden_for_vim_mode: false,
2167 show_edit_predictions_override: None,
2168 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2169 edit_prediction_settings: EditPredictionSettings::Disabled,
2170 edit_prediction_indent_conflict: false,
2171 edit_prediction_requires_modifier_in_indent_conflict: true,
2172 custom_context_menu: None,
2173 show_git_blame_gutter: false,
2174 show_git_blame_inline: false,
2175 show_selection_menu: None,
2176 show_git_blame_inline_delay_task: None,
2177 git_blame_inline_enabled: full_mode
2178 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2179 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2180 serialize_dirty_buffers: !is_minimap
2181 && ProjectSettings::get_global(cx)
2182 .session
2183 .restore_unsaved_buffers,
2184 blame: None,
2185 blame_subscription: None,
2186 tasks: BTreeMap::default(),
2187
2188 breakpoint_store,
2189 gutter_breakpoint_indicator: (None, None),
2190 hovered_diff_hunk_row: None,
2191 _subscriptions: (!is_minimap)
2192 .then(|| {
2193 vec![
2194 cx.observe(&multi_buffer, Self::on_buffer_changed),
2195 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2196 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2197 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2198 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2199 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2200 cx.observe_window_activation(window, |editor, window, cx| {
2201 let active = window.is_window_active();
2202 editor.blink_manager.update(cx, |blink_manager, cx| {
2203 if active {
2204 blink_manager.enable(cx);
2205 } else {
2206 blink_manager.disable(cx);
2207 }
2208 });
2209 if active {
2210 editor.show_mouse_cursor(cx);
2211 }
2212 }),
2213 ]
2214 })
2215 .unwrap_or_default(),
2216 tasks_update_task: None,
2217 pull_diagnostics_task: Task::ready(()),
2218 colors: None,
2219 refresh_colors_task: Task::ready(()),
2220 inlay_hints: None,
2221 next_color_inlay_id: 0,
2222 post_scroll_update: Task::ready(()),
2223 linked_edit_ranges: Default::default(),
2224 in_project_search: false,
2225 previous_search_ranges: None,
2226 breadcrumb_header: None,
2227 focused_block: None,
2228 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2229 addons: HashMap::default(),
2230 registered_buffers: HashMap::default(),
2231 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2232 selection_mark_mode: false,
2233 toggle_fold_multiple_buffers: Task::ready(()),
2234 serialize_selections: Task::ready(()),
2235 serialize_folds: Task::ready(()),
2236 text_style_refinement: None,
2237 load_diff_task: load_uncommitted_diff,
2238 temporary_diff_override: false,
2239 mouse_cursor_hidden: false,
2240 minimap: None,
2241 hide_mouse_mode: EditorSettings::get_global(cx)
2242 .hide_mouse
2243 .unwrap_or_default(),
2244 change_list: ChangeList::new(),
2245 mode,
2246 selection_drag_state: SelectionDragState::None,
2247 folding_newlines: Task::ready(()),
2248 lookup_key: None,
2249 };
2250
2251 if is_minimap {
2252 return editor;
2253 }
2254
2255 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2256 editor
2257 ._subscriptions
2258 .push(cx.observe(breakpoints, |_, _, cx| {
2259 cx.notify();
2260 }));
2261 }
2262 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2263 editor._subscriptions.extend(project_subscriptions);
2264
2265 editor._subscriptions.push(cx.subscribe_in(
2266 &cx.entity(),
2267 window,
2268 |editor, _, e: &EditorEvent, window, cx| match e {
2269 EditorEvent::ScrollPositionChanged { local, .. } => {
2270 if *local {
2271 let new_anchor = editor.scroll_manager.anchor();
2272 let snapshot = editor.snapshot(window, cx);
2273 editor.update_restoration_data(cx, move |data| {
2274 data.scroll_position = (
2275 new_anchor.top_row(snapshot.buffer_snapshot()),
2276 new_anchor.offset,
2277 );
2278 });
2279 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2280 editor.inline_blame_popover.take();
2281 }
2282 }
2283 EditorEvent::Edited { .. } => {
2284 if !vim_enabled(cx) {
2285 let display_map = editor.display_snapshot(cx);
2286 let selections = editor.selections.all_adjusted_display(&display_map);
2287 let pop_state = editor
2288 .change_list
2289 .last()
2290 .map(|previous| {
2291 previous.len() == selections.len()
2292 && previous.iter().enumerate().all(|(ix, p)| {
2293 p.to_display_point(&display_map).row()
2294 == selections[ix].head().row()
2295 })
2296 })
2297 .unwrap_or(false);
2298 let new_positions = selections
2299 .into_iter()
2300 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2301 .collect();
2302 editor
2303 .change_list
2304 .push_to_change_list(pop_state, new_positions);
2305 }
2306 }
2307 _ => (),
2308 },
2309 ));
2310
2311 if let Some(dap_store) = editor
2312 .project
2313 .as_ref()
2314 .map(|project| project.read(cx).dap_store())
2315 {
2316 let weak_editor = cx.weak_entity();
2317
2318 editor
2319 ._subscriptions
2320 .push(
2321 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2322 let session_entity = cx.entity();
2323 weak_editor
2324 .update(cx, |editor, cx| {
2325 editor._subscriptions.push(
2326 cx.subscribe(&session_entity, Self::on_debug_session_event),
2327 );
2328 })
2329 .ok();
2330 }),
2331 );
2332
2333 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2334 editor
2335 ._subscriptions
2336 .push(cx.subscribe(&session, Self::on_debug_session_event));
2337 }
2338 }
2339
2340 // skip adding the initial selection to selection history
2341 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2342 editor.end_selection(window, cx);
2343 editor.selection_history.mode = SelectionHistoryMode::Normal;
2344
2345 editor.scroll_manager.show_scrollbars(window, cx);
2346 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2347
2348 if full_mode {
2349 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2350 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2351
2352 if editor.git_blame_inline_enabled {
2353 editor.start_git_blame_inline(false, window, cx);
2354 }
2355
2356 editor.go_to_active_debug_line(window, cx);
2357
2358 editor.minimap =
2359 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2360 editor.colors = Some(LspColorData::new(cx));
2361 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2362
2363 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2364 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2365 }
2366 editor.update_lsp_data(None, window, cx);
2367 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2368 }
2369
2370 editor
2371 }
2372
2373 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2374 self.selections.display_map(cx)
2375 }
2376
2377 pub fn deploy_mouse_context_menu(
2378 &mut self,
2379 position: gpui::Point<Pixels>,
2380 context_menu: Entity<ContextMenu>,
2381 window: &mut Window,
2382 cx: &mut Context<Self>,
2383 ) {
2384 self.mouse_context_menu = Some(MouseContextMenu::new(
2385 self,
2386 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2387 context_menu,
2388 window,
2389 cx,
2390 ));
2391 }
2392
2393 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2394 self.mouse_context_menu
2395 .as_ref()
2396 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2397 }
2398
2399 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2400 if self
2401 .selections
2402 .pending_anchor()
2403 .is_some_and(|pending_selection| {
2404 let snapshot = self.buffer().read(cx).snapshot(cx);
2405 pending_selection.range().includes(range, &snapshot)
2406 })
2407 {
2408 return true;
2409 }
2410
2411 self.selections
2412 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2413 .into_iter()
2414 .any(|selection| {
2415 // This is needed to cover a corner case, if we just check for an existing
2416 // selection in the fold range, having a cursor at the start of the fold
2417 // marks it as selected. Non-empty selections don't cause this.
2418 let length = selection.end - selection.start;
2419 length > 0
2420 })
2421 }
2422
2423 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2424 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2425 }
2426
2427 fn key_context_internal(
2428 &self,
2429 has_active_edit_prediction: bool,
2430 window: &mut Window,
2431 cx: &mut App,
2432 ) -> KeyContext {
2433 let mut key_context = KeyContext::new_with_defaults();
2434 key_context.add("Editor");
2435 let mode = match self.mode {
2436 EditorMode::SingleLine => "single_line",
2437 EditorMode::AutoHeight { .. } => "auto_height",
2438 EditorMode::Minimap { .. } => "minimap",
2439 EditorMode::Full { .. } => "full",
2440 };
2441
2442 if EditorSettings::jupyter_enabled(cx) {
2443 key_context.add("jupyter");
2444 }
2445
2446 key_context.set("mode", mode);
2447 if self.pending_rename.is_some() {
2448 key_context.add("renaming");
2449 }
2450
2451 match self.context_menu.borrow().as_ref() {
2452 Some(CodeContextMenu::Completions(menu)) => {
2453 if menu.visible() {
2454 key_context.add("menu");
2455 key_context.add("showing_completions");
2456 }
2457 }
2458 Some(CodeContextMenu::CodeActions(menu)) => {
2459 if menu.visible() {
2460 key_context.add("menu");
2461 key_context.add("showing_code_actions")
2462 }
2463 }
2464 None => {}
2465 }
2466
2467 if self.signature_help_state.has_multiple_signatures() {
2468 key_context.add("showing_signature_help");
2469 }
2470
2471 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2472 if !self.focus_handle(cx).contains_focused(window, cx)
2473 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2474 {
2475 for addon in self.addons.values() {
2476 addon.extend_key_context(&mut key_context, cx)
2477 }
2478 }
2479
2480 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2481 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2482 Some(
2483 file.full_path(cx)
2484 .extension()?
2485 .to_string_lossy()
2486 .into_owned(),
2487 )
2488 }) {
2489 key_context.set("extension", extension);
2490 }
2491 } else {
2492 key_context.add("multibuffer");
2493 }
2494
2495 if has_active_edit_prediction {
2496 if self.edit_prediction_in_conflict() {
2497 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2498 } else {
2499 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2500 key_context.add("copilot_suggestion");
2501 }
2502 }
2503
2504 if self.selection_mark_mode {
2505 key_context.add("selection_mode");
2506 }
2507
2508 let disjoint = self.selections.disjoint_anchors();
2509 let snapshot = self.snapshot(window, cx);
2510 let snapshot = snapshot.buffer_snapshot();
2511 if self.mode == EditorMode::SingleLine
2512 && let [selection] = disjoint
2513 && selection.start == selection.end
2514 && selection.end.to_offset(snapshot) == snapshot.len()
2515 {
2516 key_context.add("end_of_input");
2517 }
2518
2519 key_context
2520 }
2521
2522 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2523 self.last_bounds.as_ref()
2524 }
2525
2526 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2527 if self.mouse_cursor_hidden {
2528 self.mouse_cursor_hidden = false;
2529 cx.notify();
2530 }
2531 }
2532
2533 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2534 let hide_mouse_cursor = match origin {
2535 HideMouseCursorOrigin::TypingAction => {
2536 matches!(
2537 self.hide_mouse_mode,
2538 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2539 )
2540 }
2541 HideMouseCursorOrigin::MovementAction => {
2542 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2543 }
2544 };
2545 if self.mouse_cursor_hidden != hide_mouse_cursor {
2546 self.mouse_cursor_hidden = hide_mouse_cursor;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn edit_prediction_in_conflict(&self) -> bool {
2552 if !self.show_edit_predictions_in_menu() {
2553 return false;
2554 }
2555
2556 let showing_completions = self
2557 .context_menu
2558 .borrow()
2559 .as_ref()
2560 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2561
2562 showing_completions
2563 || self.edit_prediction_requires_modifier()
2564 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2565 // bindings to insert tab characters.
2566 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2567 }
2568
2569 pub fn accept_edit_prediction_keybind(
2570 &self,
2571 accept_partial: bool,
2572 window: &mut Window,
2573 cx: &mut App,
2574 ) -> AcceptEditPredictionBinding {
2575 let key_context = self.key_context_internal(true, window, cx);
2576 let in_conflict = self.edit_prediction_in_conflict();
2577
2578 let bindings = if accept_partial {
2579 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2580 } else {
2581 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2582 };
2583
2584 // TODO: if the binding contains multiple keystrokes, display all of them, not
2585 // just the first one.
2586 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2587 !in_conflict
2588 || binding
2589 .keystrokes()
2590 .first()
2591 .is_some_and(|keystroke| keystroke.modifiers().modified())
2592 }))
2593 }
2594
2595 pub fn new_file(
2596 workspace: &mut Workspace,
2597 _: &workspace::NewFile,
2598 window: &mut Window,
2599 cx: &mut Context<Workspace>,
2600 ) {
2601 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2602 "Failed to create buffer",
2603 window,
2604 cx,
2605 |e, _, _| match e.error_code() {
2606 ErrorCode::RemoteUpgradeRequired => Some(format!(
2607 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2608 e.error_tag("required").unwrap_or("the latest version")
2609 )),
2610 _ => None,
2611 },
2612 );
2613 }
2614
2615 pub fn new_in_workspace(
2616 workspace: &mut Workspace,
2617 window: &mut Window,
2618 cx: &mut Context<Workspace>,
2619 ) -> Task<Result<Entity<Editor>>> {
2620 let project = workspace.project().clone();
2621 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2622
2623 cx.spawn_in(window, async move |workspace, cx| {
2624 let buffer = create.await?;
2625 workspace.update_in(cx, |workspace, window, cx| {
2626 let editor =
2627 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2628 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2629 editor
2630 })
2631 })
2632 }
2633
2634 fn new_file_vertical(
2635 workspace: &mut Workspace,
2636 _: &workspace::NewFileSplitVertical,
2637 window: &mut Window,
2638 cx: &mut Context<Workspace>,
2639 ) {
2640 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2641 }
2642
2643 fn new_file_horizontal(
2644 workspace: &mut Workspace,
2645 _: &workspace::NewFileSplitHorizontal,
2646 window: &mut Window,
2647 cx: &mut Context<Workspace>,
2648 ) {
2649 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2650 }
2651
2652 fn new_file_split(
2653 workspace: &mut Workspace,
2654 action: &workspace::NewFileSplit,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 Self::new_file_in_direction(workspace, action.0, window, cx)
2659 }
2660
2661 fn new_file_in_direction(
2662 workspace: &mut Workspace,
2663 direction: SplitDirection,
2664 window: &mut Window,
2665 cx: &mut Context<Workspace>,
2666 ) {
2667 let project = workspace.project().clone();
2668 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2669
2670 cx.spawn_in(window, async move |workspace, cx| {
2671 let buffer = create.await?;
2672 workspace.update_in(cx, move |workspace, window, cx| {
2673 workspace.split_item(
2674 direction,
2675 Box::new(
2676 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2677 ),
2678 window,
2679 cx,
2680 )
2681 })?;
2682 anyhow::Ok(())
2683 })
2684 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2685 match e.error_code() {
2686 ErrorCode::RemoteUpgradeRequired => Some(format!(
2687 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2688 e.error_tag("required").unwrap_or("the latest version")
2689 )),
2690 _ => None,
2691 }
2692 });
2693 }
2694
2695 pub fn leader_id(&self) -> Option<CollaboratorId> {
2696 self.leader_id
2697 }
2698
2699 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2700 &self.buffer
2701 }
2702
2703 pub fn project(&self) -> Option<&Entity<Project>> {
2704 self.project.as_ref()
2705 }
2706
2707 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2708 self.workspace.as_ref()?.0.upgrade()
2709 }
2710
2711 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2712 self.buffer().read(cx).title(cx)
2713 }
2714
2715 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2716 let git_blame_gutter_max_author_length = self
2717 .render_git_blame_gutter(cx)
2718 .then(|| {
2719 if let Some(blame) = self.blame.as_ref() {
2720 let max_author_length =
2721 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2722 Some(max_author_length)
2723 } else {
2724 None
2725 }
2726 })
2727 .flatten();
2728
2729 EditorSnapshot {
2730 mode: self.mode.clone(),
2731 show_gutter: self.show_gutter,
2732 show_line_numbers: self.show_line_numbers,
2733 show_git_diff_gutter: self.show_git_diff_gutter,
2734 show_code_actions: self.show_code_actions,
2735 show_runnables: self.show_runnables,
2736 show_breakpoints: self.show_breakpoints,
2737 git_blame_gutter_max_author_length,
2738 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2739 placeholder_display_snapshot: self
2740 .placeholder_display_map
2741 .as_ref()
2742 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2743 scroll_anchor: self.scroll_manager.anchor(),
2744 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2745 is_focused: self.focus_handle.is_focused(window),
2746 current_line_highlight: self
2747 .current_line_highlight
2748 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2749 gutter_hovered: self.gutter_hovered,
2750 }
2751 }
2752
2753 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2754 self.buffer.read(cx).language_at(point, cx)
2755 }
2756
2757 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2758 self.buffer.read(cx).read(cx).file_at(point).cloned()
2759 }
2760
2761 pub fn active_excerpt(
2762 &self,
2763 cx: &App,
2764 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2765 self.buffer
2766 .read(cx)
2767 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2768 }
2769
2770 pub fn mode(&self) -> &EditorMode {
2771 &self.mode
2772 }
2773
2774 pub fn set_mode(&mut self, mode: EditorMode) {
2775 self.mode = mode;
2776 }
2777
2778 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2779 self.collaboration_hub.as_deref()
2780 }
2781
2782 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2783 self.collaboration_hub = Some(hub);
2784 }
2785
2786 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2787 self.in_project_search = in_project_search;
2788 }
2789
2790 pub fn set_custom_context_menu(
2791 &mut self,
2792 f: impl 'static
2793 + Fn(
2794 &mut Self,
2795 DisplayPoint,
2796 &mut Window,
2797 &mut Context<Self>,
2798 ) -> Option<Entity<ui::ContextMenu>>,
2799 ) {
2800 self.custom_context_menu = Some(Box::new(f))
2801 }
2802
2803 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2804 self.completion_provider = provider;
2805 }
2806
2807 #[cfg(any(test, feature = "test-support"))]
2808 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2809 self.completion_provider.clone()
2810 }
2811
2812 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2813 self.semantics_provider.clone()
2814 }
2815
2816 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2817 self.semantics_provider = provider;
2818 }
2819
2820 pub fn set_edit_prediction_provider<T>(
2821 &mut self,
2822 provider: Option<Entity<T>>,
2823 window: &mut Window,
2824 cx: &mut Context<Self>,
2825 ) where
2826 T: EditPredictionProvider,
2827 {
2828 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2829 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2830 if this.focus_handle.is_focused(window) {
2831 this.update_visible_edit_prediction(window, cx);
2832 }
2833 }),
2834 provider: Arc::new(provider),
2835 });
2836 self.update_edit_prediction_settings(cx);
2837 self.refresh_edit_prediction(false, false, window, cx);
2838 }
2839
2840 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2841 self.placeholder_display_map
2842 .as_ref()
2843 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2844 }
2845
2846 pub fn set_placeholder_text(
2847 &mut self,
2848 placeholder_text: &str,
2849 window: &mut Window,
2850 cx: &mut Context<Self>,
2851 ) {
2852 let multibuffer = cx
2853 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2854
2855 let style = window.text_style();
2856
2857 self.placeholder_display_map = Some(cx.new(|cx| {
2858 DisplayMap::new(
2859 multibuffer,
2860 style.font(),
2861 style.font_size.to_pixels(window.rem_size()),
2862 None,
2863 FILE_HEADER_HEIGHT,
2864 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2865 Default::default(),
2866 DiagnosticSeverity::Off,
2867 cx,
2868 )
2869 }));
2870 cx.notify();
2871 }
2872
2873 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2874 self.cursor_shape = cursor_shape;
2875
2876 // Disrupt blink for immediate user feedback that the cursor shape has changed
2877 self.blink_manager.update(cx, BlinkManager::show_cursor);
2878
2879 cx.notify();
2880 }
2881
2882 pub fn set_current_line_highlight(
2883 &mut self,
2884 current_line_highlight: Option<CurrentLineHighlight>,
2885 ) {
2886 self.current_line_highlight = current_line_highlight;
2887 }
2888
2889 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2890 self.collapse_matches = collapse_matches;
2891 }
2892
2893 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2894 if self.collapse_matches {
2895 return range.start..range.start;
2896 }
2897 range.clone()
2898 }
2899
2900 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2901 if self.display_map.read(cx).clip_at_line_ends != clip {
2902 self.display_map
2903 .update(cx, |map, _| map.clip_at_line_ends = clip);
2904 }
2905 }
2906
2907 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2908 self.input_enabled = input_enabled;
2909 }
2910
2911 pub fn set_edit_predictions_hidden_for_vim_mode(
2912 &mut self,
2913 hidden: bool,
2914 window: &mut Window,
2915 cx: &mut Context<Self>,
2916 ) {
2917 if hidden != self.edit_predictions_hidden_for_vim_mode {
2918 self.edit_predictions_hidden_for_vim_mode = hidden;
2919 if hidden {
2920 self.update_visible_edit_prediction(window, cx);
2921 } else {
2922 self.refresh_edit_prediction(true, false, window, cx);
2923 }
2924 }
2925 }
2926
2927 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2928 self.menu_edit_predictions_policy = value;
2929 }
2930
2931 pub fn set_autoindent(&mut self, autoindent: bool) {
2932 if autoindent {
2933 self.autoindent_mode = Some(AutoindentMode::EachLine);
2934 } else {
2935 self.autoindent_mode = None;
2936 }
2937 }
2938
2939 pub fn read_only(&self, cx: &App) -> bool {
2940 self.read_only || self.buffer.read(cx).read_only()
2941 }
2942
2943 pub fn set_read_only(&mut self, read_only: bool) {
2944 self.read_only = read_only;
2945 }
2946
2947 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2948 self.use_autoclose = autoclose;
2949 }
2950
2951 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2952 self.use_auto_surround = auto_surround;
2953 }
2954
2955 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2956 self.auto_replace_emoji_shortcode = auto_replace;
2957 }
2958
2959 pub fn toggle_edit_predictions(
2960 &mut self,
2961 _: &ToggleEditPrediction,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 if self.show_edit_predictions_override.is_some() {
2966 self.set_show_edit_predictions(None, window, cx);
2967 } else {
2968 let show_edit_predictions = !self.edit_predictions_enabled();
2969 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2970 }
2971 }
2972
2973 pub fn set_show_edit_predictions(
2974 &mut self,
2975 show_edit_predictions: Option<bool>,
2976 window: &mut Window,
2977 cx: &mut Context<Self>,
2978 ) {
2979 self.show_edit_predictions_override = show_edit_predictions;
2980 self.update_edit_prediction_settings(cx);
2981
2982 if let Some(false) = show_edit_predictions {
2983 self.discard_edit_prediction(false, cx);
2984 } else {
2985 self.refresh_edit_prediction(false, true, window, cx);
2986 }
2987 }
2988
2989 fn edit_predictions_disabled_in_scope(
2990 &self,
2991 buffer: &Entity<Buffer>,
2992 buffer_position: language::Anchor,
2993 cx: &App,
2994 ) -> bool {
2995 let snapshot = buffer.read(cx).snapshot();
2996 let settings = snapshot.settings_at(buffer_position, cx);
2997
2998 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2999 return false;
3000 };
3001
3002 scope.override_name().is_some_and(|scope_name| {
3003 settings
3004 .edit_predictions_disabled_in
3005 .iter()
3006 .any(|s| s == scope_name)
3007 })
3008 }
3009
3010 pub fn set_use_modal_editing(&mut self, to: bool) {
3011 self.use_modal_editing = to;
3012 }
3013
3014 pub fn use_modal_editing(&self) -> bool {
3015 self.use_modal_editing
3016 }
3017
3018 fn selections_did_change(
3019 &mut self,
3020 local: bool,
3021 old_cursor_position: &Anchor,
3022 effects: SelectionEffects,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 ) {
3026 window.invalidate_character_coordinates();
3027
3028 // Copy selections to primary selection buffer
3029 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3030 if local {
3031 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3032 let buffer_handle = self.buffer.read(cx).read(cx);
3033
3034 let mut text = String::new();
3035 for (index, selection) in selections.iter().enumerate() {
3036 let text_for_selection = buffer_handle
3037 .text_for_range(selection.start..selection.end)
3038 .collect::<String>();
3039
3040 text.push_str(&text_for_selection);
3041 if index != selections.len() - 1 {
3042 text.push('\n');
3043 }
3044 }
3045
3046 if !text.is_empty() {
3047 cx.write_to_primary(ClipboardItem::new_string(text));
3048 }
3049 }
3050
3051 let selection_anchors = self.selections.disjoint_anchors_arc();
3052
3053 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3054 self.buffer.update(cx, |buffer, cx| {
3055 buffer.set_active_selections(
3056 &selection_anchors,
3057 self.selections.line_mode(),
3058 self.cursor_shape,
3059 cx,
3060 )
3061 });
3062 }
3063 let display_map = self
3064 .display_map
3065 .update(cx, |display_map, cx| display_map.snapshot(cx));
3066 let buffer = display_map.buffer_snapshot();
3067 if self.selections.count() == 1 {
3068 self.add_selections_state = None;
3069 }
3070 self.select_next_state = None;
3071 self.select_prev_state = None;
3072 self.select_syntax_node_history.try_clear();
3073 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3074 self.snippet_stack.invalidate(&selection_anchors, buffer);
3075 self.take_rename(false, window, cx);
3076
3077 let newest_selection = self.selections.newest_anchor();
3078 let new_cursor_position = newest_selection.head();
3079 let selection_start = newest_selection.start;
3080
3081 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3082 self.push_to_nav_history(
3083 *old_cursor_position,
3084 Some(new_cursor_position.to_point(buffer)),
3085 false,
3086 effects.nav_history == Some(true),
3087 cx,
3088 );
3089 }
3090
3091 if local {
3092 if let Some(buffer_id) = new_cursor_position.buffer_id {
3093 self.register_buffer(buffer_id, cx);
3094 }
3095
3096 let mut context_menu = self.context_menu.borrow_mut();
3097 let completion_menu = match context_menu.as_ref() {
3098 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3099 Some(CodeContextMenu::CodeActions(_)) => {
3100 *context_menu = None;
3101 None
3102 }
3103 None => None,
3104 };
3105 let completion_position = completion_menu.map(|menu| menu.initial_position);
3106 drop(context_menu);
3107
3108 if effects.completions
3109 && let Some(completion_position) = completion_position
3110 {
3111 let start_offset = selection_start.to_offset(buffer);
3112 let position_matches = start_offset == completion_position.to_offset(buffer);
3113 let continue_showing = if position_matches {
3114 if self.snippet_stack.is_empty() {
3115 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3116 == Some(CharKind::Word)
3117 } else {
3118 // Snippet choices can be shown even when the cursor is in whitespace.
3119 // Dismissing the menu with actions like backspace is handled by
3120 // invalidation regions.
3121 true
3122 }
3123 } else {
3124 false
3125 };
3126
3127 if continue_showing {
3128 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3129 } else {
3130 self.hide_context_menu(window, cx);
3131 }
3132 }
3133
3134 hide_hover(self, cx);
3135
3136 if old_cursor_position.to_display_point(&display_map).row()
3137 != new_cursor_position.to_display_point(&display_map).row()
3138 {
3139 self.available_code_actions.take();
3140 }
3141 self.refresh_code_actions(window, cx);
3142 self.refresh_document_highlights(cx);
3143 refresh_linked_ranges(self, window, cx);
3144
3145 self.refresh_selected_text_highlights(false, window, cx);
3146 refresh_matching_bracket_highlights(self, cx);
3147 self.update_visible_edit_prediction(window, cx);
3148 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3149 self.inline_blame_popover.take();
3150 if self.git_blame_inline_enabled {
3151 self.start_inline_blame_timer(window, cx);
3152 }
3153 }
3154
3155 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3156 cx.emit(EditorEvent::SelectionsChanged { local });
3157
3158 let selections = &self.selections.disjoint_anchors_arc();
3159 if selections.len() == 1 {
3160 cx.emit(SearchEvent::ActiveMatchChanged)
3161 }
3162 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3163 let inmemory_selections = selections
3164 .iter()
3165 .map(|s| {
3166 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3167 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3168 })
3169 .collect();
3170 self.update_restoration_data(cx, |data| {
3171 data.selections = inmemory_selections;
3172 });
3173
3174 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3175 && let Some(workspace_id) =
3176 self.workspace.as_ref().and_then(|workspace| workspace.1)
3177 {
3178 let snapshot = self.buffer().read(cx).snapshot(cx);
3179 let selections = selections.clone();
3180 let background_executor = cx.background_executor().clone();
3181 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3182 self.serialize_selections = cx.background_spawn(async move {
3183 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3184 let db_selections = selections
3185 .iter()
3186 .map(|selection| {
3187 (
3188 selection.start.to_offset(&snapshot),
3189 selection.end.to_offset(&snapshot),
3190 )
3191 })
3192 .collect();
3193
3194 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3195 .await
3196 .with_context(|| {
3197 format!(
3198 "persisting editor selections for editor {editor_id}, \
3199 workspace {workspace_id:?}"
3200 )
3201 })
3202 .log_err();
3203 });
3204 }
3205 }
3206
3207 cx.notify();
3208 }
3209
3210 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3211 use text::ToOffset as _;
3212 use text::ToPoint as _;
3213
3214 if self.mode.is_minimap()
3215 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3216 {
3217 return;
3218 }
3219
3220 if !self.buffer().read(cx).is_singleton() {
3221 return;
3222 }
3223
3224 let display_snapshot = self
3225 .display_map
3226 .update(cx, |display_map, cx| display_map.snapshot(cx));
3227 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3228 return;
3229 };
3230 let inmemory_folds = display_snapshot
3231 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3232 .map(|fold| {
3233 fold.range.start.text_anchor.to_point(&snapshot)
3234 ..fold.range.end.text_anchor.to_point(&snapshot)
3235 })
3236 .collect();
3237 self.update_restoration_data(cx, |data| {
3238 data.folds = inmemory_folds;
3239 });
3240
3241 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3242 return;
3243 };
3244 let background_executor = cx.background_executor().clone();
3245 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3246 let db_folds = display_snapshot
3247 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3248 .map(|fold| {
3249 (
3250 fold.range.start.text_anchor.to_offset(&snapshot),
3251 fold.range.end.text_anchor.to_offset(&snapshot),
3252 )
3253 })
3254 .collect();
3255 self.serialize_folds = cx.background_spawn(async move {
3256 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3257 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3258 .await
3259 .with_context(|| {
3260 format!(
3261 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3262 )
3263 })
3264 .log_err();
3265 });
3266 }
3267
3268 pub fn sync_selections(
3269 &mut self,
3270 other: Entity<Editor>,
3271 cx: &mut Context<Self>,
3272 ) -> gpui::Subscription {
3273 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3274 if !other_selections.is_empty() {
3275 self.selections.change_with(cx, |selections| {
3276 selections.select_anchors(other_selections);
3277 });
3278 }
3279
3280 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3281 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3282 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3283 if other_selections.is_empty() {
3284 return;
3285 }
3286 this.selections.change_with(cx, |selections| {
3287 selections.select_anchors(other_selections);
3288 });
3289 }
3290 });
3291
3292 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3293 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3294 let these_selections = this.selections.disjoint_anchors().to_vec();
3295 if these_selections.is_empty() {
3296 return;
3297 }
3298 other.update(cx, |other_editor, cx| {
3299 other_editor.selections.change_with(cx, |selections| {
3300 selections.select_anchors(these_selections);
3301 })
3302 });
3303 }
3304 });
3305
3306 Subscription::join(other_subscription, this_subscription)
3307 }
3308
3309 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3310 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3311 /// effects of selection change occur at the end of the transaction.
3312 pub fn change_selections<R>(
3313 &mut self,
3314 effects: SelectionEffects,
3315 window: &mut Window,
3316 cx: &mut Context<Self>,
3317 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3318 ) -> R {
3319 if let Some(state) = &mut self.deferred_selection_effects_state {
3320 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3321 state.effects.completions = effects.completions;
3322 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3323 let (changed, result) = self.selections.change_with(cx, change);
3324 state.changed |= changed;
3325 return result;
3326 }
3327 let mut state = DeferredSelectionEffectsState {
3328 changed: false,
3329 effects,
3330 old_cursor_position: self.selections.newest_anchor().head(),
3331 history_entry: SelectionHistoryEntry {
3332 selections: self.selections.disjoint_anchors_arc(),
3333 select_next_state: self.select_next_state.clone(),
3334 select_prev_state: self.select_prev_state.clone(),
3335 add_selections_state: self.add_selections_state.clone(),
3336 },
3337 };
3338 let (changed, result) = self.selections.change_with(cx, change);
3339 state.changed = state.changed || changed;
3340 if self.defer_selection_effects {
3341 self.deferred_selection_effects_state = Some(state);
3342 } else {
3343 self.apply_selection_effects(state, window, cx);
3344 }
3345 result
3346 }
3347
3348 /// Defers the effects of selection change, so that the effects of multiple calls to
3349 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3350 /// to selection history and the state of popovers based on selection position aren't
3351 /// erroneously updated.
3352 pub fn with_selection_effects_deferred<R>(
3353 &mut self,
3354 window: &mut Window,
3355 cx: &mut Context<Self>,
3356 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3357 ) -> R {
3358 let already_deferred = self.defer_selection_effects;
3359 self.defer_selection_effects = true;
3360 let result = update(self, window, cx);
3361 if !already_deferred {
3362 self.defer_selection_effects = false;
3363 if let Some(state) = self.deferred_selection_effects_state.take() {
3364 self.apply_selection_effects(state, window, cx);
3365 }
3366 }
3367 result
3368 }
3369
3370 fn apply_selection_effects(
3371 &mut self,
3372 state: DeferredSelectionEffectsState,
3373 window: &mut Window,
3374 cx: &mut Context<Self>,
3375 ) {
3376 if state.changed {
3377 self.selection_history.push(state.history_entry);
3378
3379 if let Some(autoscroll) = state.effects.scroll {
3380 self.request_autoscroll(autoscroll, cx);
3381 }
3382
3383 let old_cursor_position = &state.old_cursor_position;
3384
3385 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3386
3387 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3388 self.show_signature_help(&ShowSignatureHelp, window, cx);
3389 }
3390 }
3391 }
3392
3393 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3394 where
3395 I: IntoIterator<Item = (Range<S>, T)>,
3396 S: ToOffset,
3397 T: Into<Arc<str>>,
3398 {
3399 if self.read_only(cx) {
3400 return;
3401 }
3402
3403 self.buffer
3404 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3405 }
3406
3407 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3408 where
3409 I: IntoIterator<Item = (Range<S>, T)>,
3410 S: ToOffset,
3411 T: Into<Arc<str>>,
3412 {
3413 if self.read_only(cx) {
3414 return;
3415 }
3416
3417 self.buffer.update(cx, |buffer, cx| {
3418 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3419 });
3420 }
3421
3422 pub fn edit_with_block_indent<I, S, T>(
3423 &mut self,
3424 edits: I,
3425 original_indent_columns: Vec<Option<u32>>,
3426 cx: &mut Context<Self>,
3427 ) where
3428 I: IntoIterator<Item = (Range<S>, T)>,
3429 S: ToOffset,
3430 T: Into<Arc<str>>,
3431 {
3432 if self.read_only(cx) {
3433 return;
3434 }
3435
3436 self.buffer.update(cx, |buffer, cx| {
3437 buffer.edit(
3438 edits,
3439 Some(AutoindentMode::Block {
3440 original_indent_columns,
3441 }),
3442 cx,
3443 )
3444 });
3445 }
3446
3447 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3448 self.hide_context_menu(window, cx);
3449
3450 match phase {
3451 SelectPhase::Begin {
3452 position,
3453 add,
3454 click_count,
3455 } => self.begin_selection(position, add, click_count, window, cx),
3456 SelectPhase::BeginColumnar {
3457 position,
3458 goal_column,
3459 reset,
3460 mode,
3461 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3462 SelectPhase::Extend {
3463 position,
3464 click_count,
3465 } => self.extend_selection(position, click_count, window, cx),
3466 SelectPhase::Update {
3467 position,
3468 goal_column,
3469 scroll_delta,
3470 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3471 SelectPhase::End => self.end_selection(window, cx),
3472 }
3473 }
3474
3475 fn extend_selection(
3476 &mut self,
3477 position: DisplayPoint,
3478 click_count: usize,
3479 window: &mut Window,
3480 cx: &mut Context<Self>,
3481 ) {
3482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3483 let tail = self.selections.newest::<usize>(&display_map).tail();
3484 let click_count = click_count.max(match self.selections.select_mode() {
3485 SelectMode::Character => 1,
3486 SelectMode::Word(_) => 2,
3487 SelectMode::Line(_) => 3,
3488 SelectMode::All => 4,
3489 });
3490 self.begin_selection(position, false, click_count, window, cx);
3491
3492 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3493
3494 let current_selection = match self.selections.select_mode() {
3495 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3496 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3497 };
3498
3499 let mut pending_selection = self
3500 .selections
3501 .pending_anchor()
3502 .cloned()
3503 .expect("extend_selection not called with pending selection");
3504
3505 if pending_selection
3506 .start
3507 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3508 == Ordering::Greater
3509 {
3510 pending_selection.start = current_selection.start;
3511 }
3512 if pending_selection
3513 .end
3514 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3515 == Ordering::Less
3516 {
3517 pending_selection.end = current_selection.end;
3518 pending_selection.reversed = true;
3519 }
3520
3521 let mut pending_mode = self.selections.pending_mode().unwrap();
3522 match &mut pending_mode {
3523 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3524 _ => {}
3525 }
3526
3527 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3528 SelectionEffects::scroll(Autoscroll::fit())
3529 } else {
3530 SelectionEffects::no_scroll()
3531 };
3532
3533 self.change_selections(effects, window, cx, |s| {
3534 s.set_pending(pending_selection.clone(), pending_mode);
3535 s.set_is_extending(true);
3536 });
3537 }
3538
3539 fn begin_selection(
3540 &mut self,
3541 position: DisplayPoint,
3542 add: bool,
3543 click_count: usize,
3544 window: &mut Window,
3545 cx: &mut Context<Self>,
3546 ) {
3547 if !self.focus_handle.is_focused(window) {
3548 self.last_focused_descendant = None;
3549 window.focus(&self.focus_handle);
3550 }
3551
3552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3553 let buffer = display_map.buffer_snapshot();
3554 let position = display_map.clip_point(position, Bias::Left);
3555
3556 let start;
3557 let end;
3558 let mode;
3559 let mut auto_scroll;
3560 match click_count {
3561 1 => {
3562 start = buffer.anchor_before(position.to_point(&display_map));
3563 end = start;
3564 mode = SelectMode::Character;
3565 auto_scroll = true;
3566 }
3567 2 => {
3568 let position = display_map
3569 .clip_point(position, Bias::Left)
3570 .to_offset(&display_map, Bias::Left);
3571 let (range, _) = buffer.surrounding_word(position, None);
3572 start = buffer.anchor_before(range.start);
3573 end = buffer.anchor_before(range.end);
3574 mode = SelectMode::Word(start..end);
3575 auto_scroll = true;
3576 }
3577 3 => {
3578 let position = display_map
3579 .clip_point(position, Bias::Left)
3580 .to_point(&display_map);
3581 let line_start = display_map.prev_line_boundary(position).0;
3582 let next_line_start = buffer.clip_point(
3583 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3584 Bias::Left,
3585 );
3586 start = buffer.anchor_before(line_start);
3587 end = buffer.anchor_before(next_line_start);
3588 mode = SelectMode::Line(start..end);
3589 auto_scroll = true;
3590 }
3591 _ => {
3592 start = buffer.anchor_before(0);
3593 end = buffer.anchor_before(buffer.len());
3594 mode = SelectMode::All;
3595 auto_scroll = false;
3596 }
3597 }
3598 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3599
3600 let point_to_delete: Option<usize> = {
3601 let selected_points: Vec<Selection<Point>> =
3602 self.selections.disjoint_in_range(start..end, &display_map);
3603
3604 if !add || click_count > 1 {
3605 None
3606 } else if !selected_points.is_empty() {
3607 Some(selected_points[0].id)
3608 } else {
3609 let clicked_point_already_selected =
3610 self.selections.disjoint_anchors().iter().find(|selection| {
3611 selection.start.to_point(buffer) == start.to_point(buffer)
3612 || selection.end.to_point(buffer) == end.to_point(buffer)
3613 });
3614
3615 clicked_point_already_selected.map(|selection| selection.id)
3616 }
3617 };
3618
3619 let selections_count = self.selections.count();
3620 let effects = if auto_scroll {
3621 SelectionEffects::default()
3622 } else {
3623 SelectionEffects::no_scroll()
3624 };
3625
3626 self.change_selections(effects, window, cx, |s| {
3627 if let Some(point_to_delete) = point_to_delete {
3628 s.delete(point_to_delete);
3629
3630 if selections_count == 1 {
3631 s.set_pending_anchor_range(start..end, mode);
3632 }
3633 } else {
3634 if !add {
3635 s.clear_disjoint();
3636 }
3637
3638 s.set_pending_anchor_range(start..end, mode);
3639 }
3640 });
3641 }
3642
3643 fn begin_columnar_selection(
3644 &mut self,
3645 position: DisplayPoint,
3646 goal_column: u32,
3647 reset: bool,
3648 mode: ColumnarMode,
3649 window: &mut Window,
3650 cx: &mut Context<Self>,
3651 ) {
3652 if !self.focus_handle.is_focused(window) {
3653 self.last_focused_descendant = None;
3654 window.focus(&self.focus_handle);
3655 }
3656
3657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3658
3659 if reset {
3660 let pointer_position = display_map
3661 .buffer_snapshot()
3662 .anchor_before(position.to_point(&display_map));
3663
3664 self.change_selections(
3665 SelectionEffects::scroll(Autoscroll::newest()),
3666 window,
3667 cx,
3668 |s| {
3669 s.clear_disjoint();
3670 s.set_pending_anchor_range(
3671 pointer_position..pointer_position,
3672 SelectMode::Character,
3673 );
3674 },
3675 );
3676 };
3677
3678 let tail = self.selections.newest::<Point>(&display_map).tail();
3679 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3680 self.columnar_selection_state = match mode {
3681 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3682 selection_tail: selection_anchor,
3683 display_point: if reset {
3684 if position.column() != goal_column {
3685 Some(DisplayPoint::new(position.row(), goal_column))
3686 } else {
3687 None
3688 }
3689 } else {
3690 None
3691 },
3692 }),
3693 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3694 selection_tail: selection_anchor,
3695 }),
3696 };
3697
3698 if !reset {
3699 self.select_columns(position, goal_column, &display_map, window, cx);
3700 }
3701 }
3702
3703 fn update_selection(
3704 &mut self,
3705 position: DisplayPoint,
3706 goal_column: u32,
3707 scroll_delta: gpui::Point<f32>,
3708 window: &mut Window,
3709 cx: &mut Context<Self>,
3710 ) {
3711 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3712
3713 if self.columnar_selection_state.is_some() {
3714 self.select_columns(position, goal_column, &display_map, window, cx);
3715 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3716 let buffer = display_map.buffer_snapshot();
3717 let head;
3718 let tail;
3719 let mode = self.selections.pending_mode().unwrap();
3720 match &mode {
3721 SelectMode::Character => {
3722 head = position.to_point(&display_map);
3723 tail = pending.tail().to_point(buffer);
3724 }
3725 SelectMode::Word(original_range) => {
3726 let offset = display_map
3727 .clip_point(position, Bias::Left)
3728 .to_offset(&display_map, Bias::Left);
3729 let original_range = original_range.to_offset(buffer);
3730
3731 let head_offset = if buffer.is_inside_word(offset, None)
3732 || original_range.contains(&offset)
3733 {
3734 let (word_range, _) = buffer.surrounding_word(offset, None);
3735 if word_range.start < original_range.start {
3736 word_range.start
3737 } else {
3738 word_range.end
3739 }
3740 } else {
3741 offset
3742 };
3743
3744 head = head_offset.to_point(buffer);
3745 if head_offset <= original_range.start {
3746 tail = original_range.end.to_point(buffer);
3747 } else {
3748 tail = original_range.start.to_point(buffer);
3749 }
3750 }
3751 SelectMode::Line(original_range) => {
3752 let original_range = original_range.to_point(display_map.buffer_snapshot());
3753
3754 let position = display_map
3755 .clip_point(position, Bias::Left)
3756 .to_point(&display_map);
3757 let line_start = display_map.prev_line_boundary(position).0;
3758 let next_line_start = buffer.clip_point(
3759 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3760 Bias::Left,
3761 );
3762
3763 if line_start < original_range.start {
3764 head = line_start
3765 } else {
3766 head = next_line_start
3767 }
3768
3769 if head <= original_range.start {
3770 tail = original_range.end;
3771 } else {
3772 tail = original_range.start;
3773 }
3774 }
3775 SelectMode::All => {
3776 return;
3777 }
3778 };
3779
3780 if head < tail {
3781 pending.start = buffer.anchor_before(head);
3782 pending.end = buffer.anchor_before(tail);
3783 pending.reversed = true;
3784 } else {
3785 pending.start = buffer.anchor_before(tail);
3786 pending.end = buffer.anchor_before(head);
3787 pending.reversed = false;
3788 }
3789
3790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3791 s.set_pending(pending.clone(), mode);
3792 });
3793 } else {
3794 log::error!("update_selection dispatched with no pending selection");
3795 return;
3796 }
3797
3798 self.apply_scroll_delta(scroll_delta, window, cx);
3799 cx.notify();
3800 }
3801
3802 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3803 self.columnar_selection_state.take();
3804 if let Some(pending_mode) = self.selections.pending_mode() {
3805 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3806 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3807 s.select(selections);
3808 s.clear_pending();
3809 if s.is_extending() {
3810 s.set_is_extending(false);
3811 } else {
3812 s.set_select_mode(pending_mode);
3813 }
3814 });
3815 }
3816 }
3817
3818 fn select_columns(
3819 &mut self,
3820 head: DisplayPoint,
3821 goal_column: u32,
3822 display_map: &DisplaySnapshot,
3823 window: &mut Window,
3824 cx: &mut Context<Self>,
3825 ) {
3826 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3827 return;
3828 };
3829
3830 let tail = match columnar_state {
3831 ColumnarSelectionState::FromMouse {
3832 selection_tail,
3833 display_point,
3834 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3835 ColumnarSelectionState::FromSelection { selection_tail } => {
3836 selection_tail.to_display_point(display_map)
3837 }
3838 };
3839
3840 let start_row = cmp::min(tail.row(), head.row());
3841 let end_row = cmp::max(tail.row(), head.row());
3842 let start_column = cmp::min(tail.column(), goal_column);
3843 let end_column = cmp::max(tail.column(), goal_column);
3844 let reversed = start_column < tail.column();
3845
3846 let selection_ranges = (start_row.0..=end_row.0)
3847 .map(DisplayRow)
3848 .filter_map(|row| {
3849 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3850 || start_column <= display_map.line_len(row))
3851 && !display_map.is_block_line(row)
3852 {
3853 let start = display_map
3854 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3855 .to_point(display_map);
3856 let end = display_map
3857 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3858 .to_point(display_map);
3859 if reversed {
3860 Some(end..start)
3861 } else {
3862 Some(start..end)
3863 }
3864 } else {
3865 None
3866 }
3867 })
3868 .collect::<Vec<_>>();
3869 if selection_ranges.is_empty() {
3870 return;
3871 }
3872
3873 let ranges = match columnar_state {
3874 ColumnarSelectionState::FromMouse { .. } => {
3875 let mut non_empty_ranges = selection_ranges
3876 .iter()
3877 .filter(|selection_range| selection_range.start != selection_range.end)
3878 .peekable();
3879 if non_empty_ranges.peek().is_some() {
3880 non_empty_ranges.cloned().collect()
3881 } else {
3882 selection_ranges
3883 }
3884 }
3885 _ => selection_ranges,
3886 };
3887
3888 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3889 s.select_ranges(ranges);
3890 });
3891 cx.notify();
3892 }
3893
3894 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3895 self.selections
3896 .all_adjusted(snapshot)
3897 .iter()
3898 .any(|selection| !selection.is_empty())
3899 }
3900
3901 pub fn has_pending_nonempty_selection(&self) -> bool {
3902 let pending_nonempty_selection = match self.selections.pending_anchor() {
3903 Some(Selection { start, end, .. }) => start != end,
3904 None => false,
3905 };
3906
3907 pending_nonempty_selection
3908 || (self.columnar_selection_state.is_some()
3909 && self.selections.disjoint_anchors().len() > 1)
3910 }
3911
3912 pub fn has_pending_selection(&self) -> bool {
3913 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3914 }
3915
3916 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3917 self.selection_mark_mode = false;
3918 self.selection_drag_state = SelectionDragState::None;
3919
3920 if self.clear_expanded_diff_hunks(cx) {
3921 cx.notify();
3922 return;
3923 }
3924 if self.dismiss_menus_and_popups(true, window, cx) {
3925 return;
3926 }
3927
3928 if self.mode.is_full()
3929 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3930 {
3931 return;
3932 }
3933
3934 cx.propagate();
3935 }
3936
3937 pub fn dismiss_menus_and_popups(
3938 &mut self,
3939 is_user_requested: bool,
3940 window: &mut Window,
3941 cx: &mut Context<Self>,
3942 ) -> bool {
3943 if self.take_rename(false, window, cx).is_some() {
3944 return true;
3945 }
3946
3947 if self.hide_blame_popover(true, cx) {
3948 return true;
3949 }
3950
3951 if hide_hover(self, cx) {
3952 return true;
3953 }
3954
3955 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3956 return true;
3957 }
3958
3959 if self.hide_context_menu(window, cx).is_some() {
3960 return true;
3961 }
3962
3963 if self.mouse_context_menu.take().is_some() {
3964 return true;
3965 }
3966
3967 if is_user_requested && self.discard_edit_prediction(true, cx) {
3968 return true;
3969 }
3970
3971 if self.snippet_stack.pop().is_some() {
3972 return true;
3973 }
3974
3975 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3976 self.dismiss_diagnostics(cx);
3977 return true;
3978 }
3979
3980 false
3981 }
3982
3983 fn linked_editing_ranges_for(
3984 &self,
3985 selection: Range<text::Anchor>,
3986 cx: &App,
3987 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3988 if self.linked_edit_ranges.is_empty() {
3989 return None;
3990 }
3991 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3992 selection.end.buffer_id.and_then(|end_buffer_id| {
3993 if selection.start.buffer_id != Some(end_buffer_id) {
3994 return None;
3995 }
3996 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3997 let snapshot = buffer.read(cx).snapshot();
3998 self.linked_edit_ranges
3999 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4000 .map(|ranges| (ranges, snapshot, buffer))
4001 })?;
4002 use text::ToOffset as TO;
4003 // find offset from the start of current range to current cursor position
4004 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4005
4006 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4007 let start_difference = start_offset - start_byte_offset;
4008 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4009 let end_difference = end_offset - start_byte_offset;
4010 // Current range has associated linked ranges.
4011 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4012 for range in linked_ranges.iter() {
4013 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4014 let end_offset = start_offset + end_difference;
4015 let start_offset = start_offset + start_difference;
4016 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4017 continue;
4018 }
4019 if self.selections.disjoint_anchor_ranges().any(|s| {
4020 if s.start.buffer_id != selection.start.buffer_id
4021 || s.end.buffer_id != selection.end.buffer_id
4022 {
4023 return false;
4024 }
4025 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4026 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4027 }) {
4028 continue;
4029 }
4030 let start = buffer_snapshot.anchor_after(start_offset);
4031 let end = buffer_snapshot.anchor_after(end_offset);
4032 linked_edits
4033 .entry(buffer.clone())
4034 .or_default()
4035 .push(start..end);
4036 }
4037 Some(linked_edits)
4038 }
4039
4040 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4041 let text: Arc<str> = text.into();
4042
4043 if self.read_only(cx) {
4044 return;
4045 }
4046
4047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4048
4049 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4050 let mut bracket_inserted = false;
4051 let mut edits = Vec::new();
4052 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4053 let mut new_selections = Vec::with_capacity(selections.len());
4054 let mut new_autoclose_regions = Vec::new();
4055 let snapshot = self.buffer.read(cx).read(cx);
4056 let mut clear_linked_edit_ranges = false;
4057
4058 for (selection, autoclose_region) in
4059 self.selections_with_autoclose_regions(selections, &snapshot)
4060 {
4061 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4062 // Determine if the inserted text matches the opening or closing
4063 // bracket of any of this language's bracket pairs.
4064 let mut bracket_pair = None;
4065 let mut is_bracket_pair_start = false;
4066 let mut is_bracket_pair_end = false;
4067 if !text.is_empty() {
4068 let mut bracket_pair_matching_end = None;
4069 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4070 // and they are removing the character that triggered IME popup.
4071 for (pair, enabled) in scope.brackets() {
4072 if !pair.close && !pair.surround {
4073 continue;
4074 }
4075
4076 if enabled && pair.start.ends_with(text.as_ref()) {
4077 let prefix_len = pair.start.len() - text.len();
4078 let preceding_text_matches_prefix = prefix_len == 0
4079 || (selection.start.column >= (prefix_len as u32)
4080 && snapshot.contains_str_at(
4081 Point::new(
4082 selection.start.row,
4083 selection.start.column - (prefix_len as u32),
4084 ),
4085 &pair.start[..prefix_len],
4086 ));
4087 if preceding_text_matches_prefix {
4088 bracket_pair = Some(pair.clone());
4089 is_bracket_pair_start = true;
4090 break;
4091 }
4092 }
4093 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4094 {
4095 // take first bracket pair matching end, but don't break in case a later bracket
4096 // pair matches start
4097 bracket_pair_matching_end = Some(pair.clone());
4098 }
4099 }
4100 if let Some(end) = bracket_pair_matching_end
4101 && bracket_pair.is_none()
4102 {
4103 bracket_pair = Some(end);
4104 is_bracket_pair_end = true;
4105 }
4106 }
4107
4108 if let Some(bracket_pair) = bracket_pair {
4109 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4110 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4111 let auto_surround =
4112 self.use_auto_surround && snapshot_settings.use_auto_surround;
4113 if selection.is_empty() {
4114 if is_bracket_pair_start {
4115 // If the inserted text is a suffix of an opening bracket and the
4116 // selection is preceded by the rest of the opening bracket, then
4117 // insert the closing bracket.
4118 let following_text_allows_autoclose = snapshot
4119 .chars_at(selection.start)
4120 .next()
4121 .is_none_or(|c| scope.should_autoclose_before(c));
4122
4123 let preceding_text_allows_autoclose = selection.start.column == 0
4124 || snapshot
4125 .reversed_chars_at(selection.start)
4126 .next()
4127 .is_none_or(|c| {
4128 bracket_pair.start != bracket_pair.end
4129 || !snapshot
4130 .char_classifier_at(selection.start)
4131 .is_word(c)
4132 });
4133
4134 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4135 && bracket_pair.start.len() == 1
4136 {
4137 let target = bracket_pair.start.chars().next().unwrap();
4138 let current_line_count = snapshot
4139 .reversed_chars_at(selection.start)
4140 .take_while(|&c| c != '\n')
4141 .filter(|&c| c == target)
4142 .count();
4143 current_line_count % 2 == 1
4144 } else {
4145 false
4146 };
4147
4148 if autoclose
4149 && bracket_pair.close
4150 && following_text_allows_autoclose
4151 && preceding_text_allows_autoclose
4152 && !is_closing_quote
4153 {
4154 let anchor = snapshot.anchor_before(selection.end);
4155 new_selections.push((selection.map(|_| anchor), text.len()));
4156 new_autoclose_regions.push((
4157 anchor,
4158 text.len(),
4159 selection.id,
4160 bracket_pair.clone(),
4161 ));
4162 edits.push((
4163 selection.range(),
4164 format!("{}{}", text, bracket_pair.end).into(),
4165 ));
4166 bracket_inserted = true;
4167 continue;
4168 }
4169 }
4170
4171 if let Some(region) = autoclose_region {
4172 // If the selection is followed by an auto-inserted closing bracket,
4173 // then don't insert that closing bracket again; just move the selection
4174 // past the closing bracket.
4175 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4176 && text.as_ref() == region.pair.end.as_str()
4177 && snapshot.contains_str_at(region.range.end, text.as_ref());
4178 if should_skip {
4179 let anchor = snapshot.anchor_after(selection.end);
4180 new_selections
4181 .push((selection.map(|_| anchor), region.pair.end.len()));
4182 continue;
4183 }
4184 }
4185
4186 let always_treat_brackets_as_autoclosed = snapshot
4187 .language_settings_at(selection.start, cx)
4188 .always_treat_brackets_as_autoclosed;
4189 if always_treat_brackets_as_autoclosed
4190 && is_bracket_pair_end
4191 && snapshot.contains_str_at(selection.end, text.as_ref())
4192 {
4193 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4194 // and the inserted text is a closing bracket and the selection is followed
4195 // by the closing bracket then move the selection past the closing bracket.
4196 let anchor = snapshot.anchor_after(selection.end);
4197 new_selections.push((selection.map(|_| anchor), text.len()));
4198 continue;
4199 }
4200 }
4201 // If an opening bracket is 1 character long and is typed while
4202 // text is selected, then surround that text with the bracket pair.
4203 else if auto_surround
4204 && bracket_pair.surround
4205 && is_bracket_pair_start
4206 && bracket_pair.start.chars().count() == 1
4207 {
4208 edits.push((selection.start..selection.start, text.clone()));
4209 edits.push((
4210 selection.end..selection.end,
4211 bracket_pair.end.as_str().into(),
4212 ));
4213 bracket_inserted = true;
4214 new_selections.push((
4215 Selection {
4216 id: selection.id,
4217 start: snapshot.anchor_after(selection.start),
4218 end: snapshot.anchor_before(selection.end),
4219 reversed: selection.reversed,
4220 goal: selection.goal,
4221 },
4222 0,
4223 ));
4224 continue;
4225 }
4226 }
4227 }
4228
4229 if self.auto_replace_emoji_shortcode
4230 && selection.is_empty()
4231 && text.as_ref().ends_with(':')
4232 && let Some(possible_emoji_short_code) =
4233 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4234 && !possible_emoji_short_code.is_empty()
4235 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4236 {
4237 let emoji_shortcode_start = Point::new(
4238 selection.start.row,
4239 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4240 );
4241
4242 // Remove shortcode from buffer
4243 edits.push((
4244 emoji_shortcode_start..selection.start,
4245 "".to_string().into(),
4246 ));
4247 new_selections.push((
4248 Selection {
4249 id: selection.id,
4250 start: snapshot.anchor_after(emoji_shortcode_start),
4251 end: snapshot.anchor_before(selection.start),
4252 reversed: selection.reversed,
4253 goal: selection.goal,
4254 },
4255 0,
4256 ));
4257
4258 // Insert emoji
4259 let selection_start_anchor = snapshot.anchor_after(selection.start);
4260 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4261 edits.push((selection.start..selection.end, emoji.to_string().into()));
4262
4263 continue;
4264 }
4265
4266 // If not handling any auto-close operation, then just replace the selected
4267 // text with the given input and move the selection to the end of the
4268 // newly inserted text.
4269 let anchor = snapshot.anchor_after(selection.end);
4270 if !self.linked_edit_ranges.is_empty() {
4271 let start_anchor = snapshot.anchor_before(selection.start);
4272
4273 let is_word_char = text.chars().next().is_none_or(|char| {
4274 let classifier = snapshot
4275 .char_classifier_at(start_anchor.to_offset(&snapshot))
4276 .scope_context(Some(CharScopeContext::LinkedEdit));
4277 classifier.is_word(char)
4278 });
4279
4280 if is_word_char {
4281 if let Some(ranges) = self
4282 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4283 {
4284 for (buffer, edits) in ranges {
4285 linked_edits
4286 .entry(buffer.clone())
4287 .or_default()
4288 .extend(edits.into_iter().map(|range| (range, text.clone())));
4289 }
4290 }
4291 } else {
4292 clear_linked_edit_ranges = true;
4293 }
4294 }
4295
4296 new_selections.push((selection.map(|_| anchor), 0));
4297 edits.push((selection.start..selection.end, text.clone()));
4298 }
4299
4300 drop(snapshot);
4301
4302 self.transact(window, cx, |this, window, cx| {
4303 if clear_linked_edit_ranges {
4304 this.linked_edit_ranges.clear();
4305 }
4306 let initial_buffer_versions =
4307 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4308
4309 this.buffer.update(cx, |buffer, cx| {
4310 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4311 });
4312 for (buffer, edits) in linked_edits {
4313 buffer.update(cx, |buffer, cx| {
4314 let snapshot = buffer.snapshot();
4315 let edits = edits
4316 .into_iter()
4317 .map(|(range, text)| {
4318 use text::ToPoint as TP;
4319 let end_point = TP::to_point(&range.end, &snapshot);
4320 let start_point = TP::to_point(&range.start, &snapshot);
4321 (start_point..end_point, text)
4322 })
4323 .sorted_by_key(|(range, _)| range.start);
4324 buffer.edit(edits, None, cx);
4325 })
4326 }
4327 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4328 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4329 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4330 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4331 .zip(new_selection_deltas)
4332 .map(|(selection, delta)| Selection {
4333 id: selection.id,
4334 start: selection.start + delta,
4335 end: selection.end + delta,
4336 reversed: selection.reversed,
4337 goal: SelectionGoal::None,
4338 })
4339 .collect::<Vec<_>>();
4340
4341 let mut i = 0;
4342 for (position, delta, selection_id, pair) in new_autoclose_regions {
4343 let position = position.to_offset(map.buffer_snapshot()) + delta;
4344 let start = map.buffer_snapshot().anchor_before(position);
4345 let end = map.buffer_snapshot().anchor_after(position);
4346 while let Some(existing_state) = this.autoclose_regions.get(i) {
4347 match existing_state
4348 .range
4349 .start
4350 .cmp(&start, map.buffer_snapshot())
4351 {
4352 Ordering::Less => i += 1,
4353 Ordering::Greater => break,
4354 Ordering::Equal => {
4355 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4356 Ordering::Less => i += 1,
4357 Ordering::Equal => break,
4358 Ordering::Greater => break,
4359 }
4360 }
4361 }
4362 }
4363 this.autoclose_regions.insert(
4364 i,
4365 AutocloseRegion {
4366 selection_id,
4367 range: start..end,
4368 pair,
4369 },
4370 );
4371 }
4372
4373 let had_active_edit_prediction = this.has_active_edit_prediction();
4374 this.change_selections(
4375 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4376 window,
4377 cx,
4378 |s| s.select(new_selections),
4379 );
4380
4381 if !bracket_inserted
4382 && let Some(on_type_format_task) =
4383 this.trigger_on_type_formatting(text.to_string(), window, cx)
4384 {
4385 on_type_format_task.detach_and_log_err(cx);
4386 }
4387
4388 let editor_settings = EditorSettings::get_global(cx);
4389 if bracket_inserted
4390 && (editor_settings.auto_signature_help
4391 || editor_settings.show_signature_help_after_edits)
4392 {
4393 this.show_signature_help(&ShowSignatureHelp, window, cx);
4394 }
4395
4396 let trigger_in_words =
4397 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4398 if this.hard_wrap.is_some() {
4399 let latest: Range<Point> = this.selections.newest(&map).range();
4400 if latest.is_empty()
4401 && this
4402 .buffer()
4403 .read(cx)
4404 .snapshot(cx)
4405 .line_len(MultiBufferRow(latest.start.row))
4406 == latest.start.column
4407 {
4408 this.rewrap_impl(
4409 RewrapOptions {
4410 override_language_settings: true,
4411 preserve_existing_whitespace: true,
4412 },
4413 cx,
4414 )
4415 }
4416 }
4417 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4418 refresh_linked_ranges(this, window, cx);
4419 this.refresh_edit_prediction(true, false, window, cx);
4420 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4421 });
4422 }
4423
4424 fn find_possible_emoji_shortcode_at_position(
4425 snapshot: &MultiBufferSnapshot,
4426 position: Point,
4427 ) -> Option<String> {
4428 let mut chars = Vec::new();
4429 let mut found_colon = false;
4430 for char in snapshot.reversed_chars_at(position).take(100) {
4431 // Found a possible emoji shortcode in the middle of the buffer
4432 if found_colon {
4433 if char.is_whitespace() {
4434 chars.reverse();
4435 return Some(chars.iter().collect());
4436 }
4437 // If the previous character is not a whitespace, we are in the middle of a word
4438 // and we only want to complete the shortcode if the word is made up of other emojis
4439 let mut containing_word = String::new();
4440 for ch in snapshot
4441 .reversed_chars_at(position)
4442 .skip(chars.len() + 1)
4443 .take(100)
4444 {
4445 if ch.is_whitespace() {
4446 break;
4447 }
4448 containing_word.push(ch);
4449 }
4450 let containing_word = containing_word.chars().rev().collect::<String>();
4451 if util::word_consists_of_emojis(containing_word.as_str()) {
4452 chars.reverse();
4453 return Some(chars.iter().collect());
4454 }
4455 }
4456
4457 if char.is_whitespace() || !char.is_ascii() {
4458 return None;
4459 }
4460 if char == ':' {
4461 found_colon = true;
4462 } else {
4463 chars.push(char);
4464 }
4465 }
4466 // Found a possible emoji shortcode at the beginning of the buffer
4467 chars.reverse();
4468 Some(chars.iter().collect())
4469 }
4470
4471 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4473 self.transact(window, cx, |this, window, cx| {
4474 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4475 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4476 let multi_buffer = this.buffer.read(cx);
4477 let buffer = multi_buffer.snapshot(cx);
4478 selections
4479 .iter()
4480 .map(|selection| {
4481 let start_point = selection.start.to_point(&buffer);
4482 let mut existing_indent =
4483 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4484 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4485 let start = selection.start;
4486 let end = selection.end;
4487 let selection_is_empty = start == end;
4488 let language_scope = buffer.language_scope_at(start);
4489 let (
4490 comment_delimiter,
4491 doc_delimiter,
4492 insert_extra_newline,
4493 indent_on_newline,
4494 indent_on_extra_newline,
4495 ) = if let Some(language) = &language_scope {
4496 let mut insert_extra_newline =
4497 insert_extra_newline_brackets(&buffer, start..end, language)
4498 || insert_extra_newline_tree_sitter(&buffer, start..end);
4499
4500 // Comment extension on newline is allowed only for cursor selections
4501 let comment_delimiter = maybe!({
4502 if !selection_is_empty {
4503 return None;
4504 }
4505
4506 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4507 return None;
4508 }
4509
4510 let delimiters = language.line_comment_prefixes();
4511 let max_len_of_delimiter =
4512 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4513 let (snapshot, range) =
4514 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4515
4516 let num_of_whitespaces = snapshot
4517 .chars_for_range(range.clone())
4518 .take_while(|c| c.is_whitespace())
4519 .count();
4520 let comment_candidate = snapshot
4521 .chars_for_range(range.clone())
4522 .skip(num_of_whitespaces)
4523 .take(max_len_of_delimiter)
4524 .collect::<String>();
4525 let (delimiter, trimmed_len) = delimiters
4526 .iter()
4527 .filter_map(|delimiter| {
4528 let prefix = delimiter.trim_end();
4529 if comment_candidate.starts_with(prefix) {
4530 Some((delimiter, prefix.len()))
4531 } else {
4532 None
4533 }
4534 })
4535 .max_by_key(|(_, len)| *len)?;
4536
4537 if let Some(BlockCommentConfig {
4538 start: block_start, ..
4539 }) = language.block_comment()
4540 {
4541 let block_start_trimmed = block_start.trim_end();
4542 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4543 let line_content = snapshot
4544 .chars_for_range(range)
4545 .skip(num_of_whitespaces)
4546 .take(block_start_trimmed.len())
4547 .collect::<String>();
4548
4549 if line_content.starts_with(block_start_trimmed) {
4550 return None;
4551 }
4552 }
4553 }
4554
4555 let cursor_is_placed_after_comment_marker =
4556 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4557 if cursor_is_placed_after_comment_marker {
4558 Some(delimiter.clone())
4559 } else {
4560 None
4561 }
4562 });
4563
4564 let mut indent_on_newline = IndentSize::spaces(0);
4565 let mut indent_on_extra_newline = IndentSize::spaces(0);
4566
4567 let doc_delimiter = maybe!({
4568 if !selection_is_empty {
4569 return None;
4570 }
4571
4572 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4573 return None;
4574 }
4575
4576 let BlockCommentConfig {
4577 start: start_tag,
4578 end: end_tag,
4579 prefix: delimiter,
4580 tab_size: len,
4581 } = language.documentation_comment()?;
4582 let is_within_block_comment = buffer
4583 .language_scope_at(start_point)
4584 .is_some_and(|scope| scope.override_name() == Some("comment"));
4585 if !is_within_block_comment {
4586 return None;
4587 }
4588
4589 let (snapshot, range) =
4590 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4591
4592 let num_of_whitespaces = snapshot
4593 .chars_for_range(range.clone())
4594 .take_while(|c| c.is_whitespace())
4595 .count();
4596
4597 // 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.
4598 let column = start_point.column;
4599 let cursor_is_after_start_tag = {
4600 let start_tag_len = start_tag.len();
4601 let start_tag_line = snapshot
4602 .chars_for_range(range.clone())
4603 .skip(num_of_whitespaces)
4604 .take(start_tag_len)
4605 .collect::<String>();
4606 if start_tag_line.starts_with(start_tag.as_ref()) {
4607 num_of_whitespaces + start_tag_len <= column as usize
4608 } else {
4609 false
4610 }
4611 };
4612
4613 let cursor_is_after_delimiter = {
4614 let delimiter_trim = delimiter.trim_end();
4615 let delimiter_line = snapshot
4616 .chars_for_range(range.clone())
4617 .skip(num_of_whitespaces)
4618 .take(delimiter_trim.len())
4619 .collect::<String>();
4620 if delimiter_line.starts_with(delimiter_trim) {
4621 num_of_whitespaces + delimiter_trim.len() <= column as usize
4622 } else {
4623 false
4624 }
4625 };
4626
4627 let cursor_is_before_end_tag_if_exists = {
4628 let mut char_position = 0u32;
4629 let mut end_tag_offset = None;
4630
4631 'outer: for chunk in snapshot.text_for_range(range) {
4632 if let Some(byte_pos) = chunk.find(&**end_tag) {
4633 let chars_before_match =
4634 chunk[..byte_pos].chars().count() as u32;
4635 end_tag_offset =
4636 Some(char_position + chars_before_match);
4637 break 'outer;
4638 }
4639 char_position += chunk.chars().count() as u32;
4640 }
4641
4642 if let Some(end_tag_offset) = end_tag_offset {
4643 let cursor_is_before_end_tag = column <= end_tag_offset;
4644 if cursor_is_after_start_tag {
4645 if cursor_is_before_end_tag {
4646 insert_extra_newline = true;
4647 }
4648 let cursor_is_at_start_of_end_tag =
4649 column == end_tag_offset;
4650 if cursor_is_at_start_of_end_tag {
4651 indent_on_extra_newline.len = *len;
4652 }
4653 }
4654 cursor_is_before_end_tag
4655 } else {
4656 true
4657 }
4658 };
4659
4660 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4661 && cursor_is_before_end_tag_if_exists
4662 {
4663 if cursor_is_after_start_tag {
4664 indent_on_newline.len = *len;
4665 }
4666 Some(delimiter.clone())
4667 } else {
4668 None
4669 }
4670 });
4671
4672 (
4673 comment_delimiter,
4674 doc_delimiter,
4675 insert_extra_newline,
4676 indent_on_newline,
4677 indent_on_extra_newline,
4678 )
4679 } else {
4680 (
4681 None,
4682 None,
4683 false,
4684 IndentSize::default(),
4685 IndentSize::default(),
4686 )
4687 };
4688
4689 let prevent_auto_indent = doc_delimiter.is_some();
4690 let delimiter = comment_delimiter.or(doc_delimiter);
4691
4692 let capacity_for_delimiter =
4693 delimiter.as_deref().map(str::len).unwrap_or_default();
4694 let mut new_text = String::with_capacity(
4695 1 + capacity_for_delimiter
4696 + existing_indent.len as usize
4697 + indent_on_newline.len as usize
4698 + indent_on_extra_newline.len as usize,
4699 );
4700 new_text.push('\n');
4701 new_text.extend(existing_indent.chars());
4702 new_text.extend(indent_on_newline.chars());
4703
4704 if let Some(delimiter) = &delimiter {
4705 new_text.push_str(delimiter);
4706 }
4707
4708 if insert_extra_newline {
4709 new_text.push('\n');
4710 new_text.extend(existing_indent.chars());
4711 new_text.extend(indent_on_extra_newline.chars());
4712 }
4713
4714 let anchor = buffer.anchor_after(end);
4715 let new_selection = selection.map(|_| anchor);
4716 (
4717 ((start..end, new_text), prevent_auto_indent),
4718 (insert_extra_newline, new_selection),
4719 )
4720 })
4721 .unzip()
4722 };
4723
4724 let mut auto_indent_edits = Vec::new();
4725 let mut edits = Vec::new();
4726 for (edit, prevent_auto_indent) in edits_with_flags {
4727 if prevent_auto_indent {
4728 edits.push(edit);
4729 } else {
4730 auto_indent_edits.push(edit);
4731 }
4732 }
4733 if !edits.is_empty() {
4734 this.edit(edits, cx);
4735 }
4736 if !auto_indent_edits.is_empty() {
4737 this.edit_with_autoindent(auto_indent_edits, cx);
4738 }
4739
4740 let buffer = this.buffer.read(cx).snapshot(cx);
4741 let new_selections = selection_info
4742 .into_iter()
4743 .map(|(extra_newline_inserted, new_selection)| {
4744 let mut cursor = new_selection.end.to_point(&buffer);
4745 if extra_newline_inserted {
4746 cursor.row -= 1;
4747 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4748 }
4749 new_selection.map(|_| cursor)
4750 })
4751 .collect();
4752
4753 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4754 this.refresh_edit_prediction(true, false, window, cx);
4755 });
4756 }
4757
4758 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4760
4761 let buffer = self.buffer.read(cx);
4762 let snapshot = buffer.snapshot(cx);
4763
4764 let mut edits = Vec::new();
4765 let mut rows = Vec::new();
4766
4767 for (rows_inserted, selection) in self
4768 .selections
4769 .all_adjusted(&self.display_snapshot(cx))
4770 .into_iter()
4771 .enumerate()
4772 {
4773 let cursor = selection.head();
4774 let row = cursor.row;
4775
4776 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4777
4778 let newline = "\n".to_string();
4779 edits.push((start_of_line..start_of_line, newline));
4780
4781 rows.push(row + rows_inserted as u32);
4782 }
4783
4784 self.transact(window, cx, |editor, window, cx| {
4785 editor.edit(edits, cx);
4786
4787 editor.change_selections(Default::default(), window, cx, |s| {
4788 let mut index = 0;
4789 s.move_cursors_with(|map, _, _| {
4790 let row = rows[index];
4791 index += 1;
4792
4793 let point = Point::new(row, 0);
4794 let boundary = map.next_line_boundary(point).1;
4795 let clipped = map.clip_point(boundary, Bias::Left);
4796
4797 (clipped, SelectionGoal::None)
4798 });
4799 });
4800
4801 let mut indent_edits = Vec::new();
4802 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4803 for row in rows {
4804 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4805 for (row, indent) in indents {
4806 if indent.len == 0 {
4807 continue;
4808 }
4809
4810 let text = match indent.kind {
4811 IndentKind::Space => " ".repeat(indent.len as usize),
4812 IndentKind::Tab => "\t".repeat(indent.len as usize),
4813 };
4814 let point = Point::new(row.0, 0);
4815 indent_edits.push((point..point, text));
4816 }
4817 }
4818 editor.edit(indent_edits, cx);
4819 });
4820 }
4821
4822 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4824
4825 let buffer = self.buffer.read(cx);
4826 let snapshot = buffer.snapshot(cx);
4827
4828 let mut edits = Vec::new();
4829 let mut rows = Vec::new();
4830 let mut rows_inserted = 0;
4831
4832 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4833 let cursor = selection.head();
4834 let row = cursor.row;
4835
4836 let point = Point::new(row + 1, 0);
4837 let start_of_line = snapshot.clip_point(point, Bias::Left);
4838
4839 let newline = "\n".to_string();
4840 edits.push((start_of_line..start_of_line, newline));
4841
4842 rows_inserted += 1;
4843 rows.push(row + rows_inserted);
4844 }
4845
4846 self.transact(window, cx, |editor, window, cx| {
4847 editor.edit(edits, cx);
4848
4849 editor.change_selections(Default::default(), window, cx, |s| {
4850 let mut index = 0;
4851 s.move_cursors_with(|map, _, _| {
4852 let row = rows[index];
4853 index += 1;
4854
4855 let point = Point::new(row, 0);
4856 let boundary = map.next_line_boundary(point).1;
4857 let clipped = map.clip_point(boundary, Bias::Left);
4858
4859 (clipped, SelectionGoal::None)
4860 });
4861 });
4862
4863 let mut indent_edits = Vec::new();
4864 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4865 for row in rows {
4866 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4867 for (row, indent) in indents {
4868 if indent.len == 0 {
4869 continue;
4870 }
4871
4872 let text = match indent.kind {
4873 IndentKind::Space => " ".repeat(indent.len as usize),
4874 IndentKind::Tab => "\t".repeat(indent.len as usize),
4875 };
4876 let point = Point::new(row.0, 0);
4877 indent_edits.push((point..point, text));
4878 }
4879 }
4880 editor.edit(indent_edits, cx);
4881 });
4882 }
4883
4884 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4885 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4886 original_indent_columns: Vec::new(),
4887 });
4888 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4889 }
4890
4891 fn insert_with_autoindent_mode(
4892 &mut self,
4893 text: &str,
4894 autoindent_mode: Option<AutoindentMode>,
4895 window: &mut Window,
4896 cx: &mut Context<Self>,
4897 ) {
4898 if self.read_only(cx) {
4899 return;
4900 }
4901
4902 let text: Arc<str> = text.into();
4903 self.transact(window, cx, |this, window, cx| {
4904 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4905 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4906 let anchors = {
4907 let snapshot = buffer.read(cx);
4908 old_selections
4909 .iter()
4910 .map(|s| {
4911 let anchor = snapshot.anchor_after(s.head());
4912 s.map(|_| anchor)
4913 })
4914 .collect::<Vec<_>>()
4915 };
4916 buffer.edit(
4917 old_selections
4918 .iter()
4919 .map(|s| (s.start..s.end, text.clone())),
4920 autoindent_mode,
4921 cx,
4922 );
4923 anchors
4924 });
4925
4926 this.change_selections(Default::default(), window, cx, |s| {
4927 s.select_anchors(selection_anchors);
4928 });
4929
4930 cx.notify();
4931 });
4932 }
4933
4934 fn trigger_completion_on_input(
4935 &mut self,
4936 text: &str,
4937 trigger_in_words: bool,
4938 window: &mut Window,
4939 cx: &mut Context<Self>,
4940 ) {
4941 let completions_source = self
4942 .context_menu
4943 .borrow()
4944 .as_ref()
4945 .and_then(|menu| match menu {
4946 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4947 CodeContextMenu::CodeActions(_) => None,
4948 });
4949
4950 match completions_source {
4951 Some(CompletionsMenuSource::Words { .. }) => {
4952 self.open_or_update_completions_menu(
4953 Some(CompletionsMenuSource::Words {
4954 ignore_threshold: false,
4955 }),
4956 None,
4957 window,
4958 cx,
4959 );
4960 }
4961 Some(CompletionsMenuSource::Normal)
4962 | Some(CompletionsMenuSource::SnippetChoices)
4963 | None
4964 if self.is_completion_trigger(
4965 text,
4966 trigger_in_words,
4967 completions_source.is_some(),
4968 cx,
4969 ) =>
4970 {
4971 self.show_completions(
4972 &ShowCompletions {
4973 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4974 },
4975 window,
4976 cx,
4977 )
4978 }
4979 _ => {
4980 self.hide_context_menu(window, cx);
4981 }
4982 }
4983 }
4984
4985 fn is_completion_trigger(
4986 &self,
4987 text: &str,
4988 trigger_in_words: bool,
4989 menu_is_open: bool,
4990 cx: &mut Context<Self>,
4991 ) -> bool {
4992 let position = self.selections.newest_anchor().head();
4993 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4994 return false;
4995 };
4996
4997 if let Some(completion_provider) = &self.completion_provider {
4998 completion_provider.is_completion_trigger(
4999 &buffer,
5000 position.text_anchor,
5001 text,
5002 trigger_in_words,
5003 menu_is_open,
5004 cx,
5005 )
5006 } else {
5007 false
5008 }
5009 }
5010
5011 /// If any empty selections is touching the start of its innermost containing autoclose
5012 /// region, expand it to select the brackets.
5013 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5014 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5015 let buffer = self.buffer.read(cx).read(cx);
5016 let new_selections = self
5017 .selections_with_autoclose_regions(selections, &buffer)
5018 .map(|(mut selection, region)| {
5019 if !selection.is_empty() {
5020 return selection;
5021 }
5022
5023 if let Some(region) = region {
5024 let mut range = region.range.to_offset(&buffer);
5025 if selection.start == range.start && range.start >= region.pair.start.len() {
5026 range.start -= region.pair.start.len();
5027 if buffer.contains_str_at(range.start, ®ion.pair.start)
5028 && buffer.contains_str_at(range.end, ®ion.pair.end)
5029 {
5030 range.end += region.pair.end.len();
5031 selection.start = range.start;
5032 selection.end = range.end;
5033
5034 return selection;
5035 }
5036 }
5037 }
5038
5039 let always_treat_brackets_as_autoclosed = buffer
5040 .language_settings_at(selection.start, cx)
5041 .always_treat_brackets_as_autoclosed;
5042
5043 if !always_treat_brackets_as_autoclosed {
5044 return selection;
5045 }
5046
5047 if let Some(scope) = buffer.language_scope_at(selection.start) {
5048 for (pair, enabled) in scope.brackets() {
5049 if !enabled || !pair.close {
5050 continue;
5051 }
5052
5053 if buffer.contains_str_at(selection.start, &pair.end) {
5054 let pair_start_len = pair.start.len();
5055 if buffer.contains_str_at(
5056 selection.start.saturating_sub(pair_start_len),
5057 &pair.start,
5058 ) {
5059 selection.start -= pair_start_len;
5060 selection.end += pair.end.len();
5061
5062 return selection;
5063 }
5064 }
5065 }
5066 }
5067
5068 selection
5069 })
5070 .collect();
5071
5072 drop(buffer);
5073 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5074 selections.select(new_selections)
5075 });
5076 }
5077
5078 /// Iterate the given selections, and for each one, find the smallest surrounding
5079 /// autoclose region. This uses the ordering of the selections and the autoclose
5080 /// regions to avoid repeated comparisons.
5081 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5082 &'a self,
5083 selections: impl IntoIterator<Item = Selection<D>>,
5084 buffer: &'a MultiBufferSnapshot,
5085 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5086 let mut i = 0;
5087 let mut regions = self.autoclose_regions.as_slice();
5088 selections.into_iter().map(move |selection| {
5089 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5090
5091 let mut enclosing = None;
5092 while let Some(pair_state) = regions.get(i) {
5093 if pair_state.range.end.to_offset(buffer) < range.start {
5094 regions = ®ions[i + 1..];
5095 i = 0;
5096 } else if pair_state.range.start.to_offset(buffer) > range.end {
5097 break;
5098 } else {
5099 if pair_state.selection_id == selection.id {
5100 enclosing = Some(pair_state);
5101 }
5102 i += 1;
5103 }
5104 }
5105
5106 (selection, enclosing)
5107 })
5108 }
5109
5110 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5111 fn invalidate_autoclose_regions(
5112 &mut self,
5113 mut selections: &[Selection<Anchor>],
5114 buffer: &MultiBufferSnapshot,
5115 ) {
5116 self.autoclose_regions.retain(|state| {
5117 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5118 return false;
5119 }
5120
5121 let mut i = 0;
5122 while let Some(selection) = selections.get(i) {
5123 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5124 selections = &selections[1..];
5125 continue;
5126 }
5127 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5128 break;
5129 }
5130 if selection.id == state.selection_id {
5131 return true;
5132 } else {
5133 i += 1;
5134 }
5135 }
5136 false
5137 });
5138 }
5139
5140 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5141 let offset = position.to_offset(buffer);
5142 let (word_range, kind) =
5143 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5144 if offset > word_range.start && kind == Some(CharKind::Word) {
5145 Some(
5146 buffer
5147 .text_for_range(word_range.start..offset)
5148 .collect::<String>(),
5149 )
5150 } else {
5151 None
5152 }
5153 }
5154
5155 pub fn visible_excerpts(
5156 &self,
5157 cx: &mut Context<Editor>,
5158 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5159 let Some(project) = self.project() else {
5160 return HashMap::default();
5161 };
5162 let project = project.read(cx);
5163 let multi_buffer = self.buffer().read(cx);
5164 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5165 let multi_buffer_visible_start = self
5166 .scroll_manager
5167 .anchor()
5168 .anchor
5169 .to_point(&multi_buffer_snapshot);
5170 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5171 multi_buffer_visible_start
5172 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5173 Bias::Left,
5174 );
5175 multi_buffer_snapshot
5176 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5177 .into_iter()
5178 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5179 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5180 let buffer_file = project::File::from_dyn(buffer.file())?;
5181 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5182 let worktree_entry = buffer_worktree
5183 .read(cx)
5184 .entry_for_id(buffer_file.project_entry_id()?)?;
5185 if worktree_entry.is_ignored {
5186 None
5187 } else {
5188 Some((
5189 excerpt_id,
5190 (
5191 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5192 buffer.version().clone(),
5193 excerpt_visible_range,
5194 ),
5195 ))
5196 }
5197 })
5198 .collect()
5199 }
5200
5201 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5202 TextLayoutDetails {
5203 text_system: window.text_system().clone(),
5204 editor_style: self.style.clone().unwrap(),
5205 rem_size: window.rem_size(),
5206 scroll_anchor: self.scroll_manager.anchor(),
5207 visible_rows: self.visible_line_count(),
5208 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5209 }
5210 }
5211
5212 fn trigger_on_type_formatting(
5213 &self,
5214 input: String,
5215 window: &mut Window,
5216 cx: &mut Context<Self>,
5217 ) -> Option<Task<Result<()>>> {
5218 if input.len() != 1 {
5219 return None;
5220 }
5221
5222 let project = self.project()?;
5223 let position = self.selections.newest_anchor().head();
5224 let (buffer, buffer_position) = self
5225 .buffer
5226 .read(cx)
5227 .text_anchor_for_position(position, cx)?;
5228
5229 let settings = language_settings::language_settings(
5230 buffer
5231 .read(cx)
5232 .language_at(buffer_position)
5233 .map(|l| l.name()),
5234 buffer.read(cx).file(),
5235 cx,
5236 );
5237 if !settings.use_on_type_format {
5238 return None;
5239 }
5240
5241 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5242 // hence we do LSP request & edit on host side only — add formats to host's history.
5243 let push_to_lsp_host_history = true;
5244 // If this is not the host, append its history with new edits.
5245 let push_to_client_history = project.read(cx).is_via_collab();
5246
5247 let on_type_formatting = project.update(cx, |project, cx| {
5248 project.on_type_format(
5249 buffer.clone(),
5250 buffer_position,
5251 input,
5252 push_to_lsp_host_history,
5253 cx,
5254 )
5255 });
5256 Some(cx.spawn_in(window, async move |editor, cx| {
5257 if let Some(transaction) = on_type_formatting.await? {
5258 if push_to_client_history {
5259 buffer
5260 .update(cx, |buffer, _| {
5261 buffer.push_transaction(transaction, Instant::now());
5262 buffer.finalize_last_transaction();
5263 })
5264 .ok();
5265 }
5266 editor.update(cx, |editor, cx| {
5267 editor.refresh_document_highlights(cx);
5268 })?;
5269 }
5270 Ok(())
5271 }))
5272 }
5273
5274 pub fn show_word_completions(
5275 &mut self,
5276 _: &ShowWordCompletions,
5277 window: &mut Window,
5278 cx: &mut Context<Self>,
5279 ) {
5280 self.open_or_update_completions_menu(
5281 Some(CompletionsMenuSource::Words {
5282 ignore_threshold: true,
5283 }),
5284 None,
5285 window,
5286 cx,
5287 );
5288 }
5289
5290 pub fn show_completions(
5291 &mut self,
5292 options: &ShowCompletions,
5293 window: &mut Window,
5294 cx: &mut Context<Self>,
5295 ) {
5296 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5297 }
5298
5299 fn open_or_update_completions_menu(
5300 &mut self,
5301 requested_source: Option<CompletionsMenuSource>,
5302 trigger: Option<&str>,
5303 window: &mut Window,
5304 cx: &mut Context<Self>,
5305 ) {
5306 if self.pending_rename.is_some() {
5307 return;
5308 }
5309
5310 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5311
5312 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5313 // inserted and selected. To handle that case, the start of the selection is used so that
5314 // the menu starts with all choices.
5315 let position = self
5316 .selections
5317 .newest_anchor()
5318 .start
5319 .bias_right(&multibuffer_snapshot);
5320 if position.diff_base_anchor.is_some() {
5321 return;
5322 }
5323 let buffer_position = multibuffer_snapshot.anchor_before(position);
5324 let Some(buffer) = buffer_position
5325 .buffer_id
5326 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5327 else {
5328 return;
5329 };
5330 let buffer_snapshot = buffer.read(cx).snapshot();
5331
5332 let query: Option<Arc<String>> =
5333 Self::completion_query(&multibuffer_snapshot, buffer_position)
5334 .map(|query| query.into());
5335
5336 drop(multibuffer_snapshot);
5337
5338 // Hide the current completions menu when query is empty. Without this, cached
5339 // completions from before the trigger char may be reused (#32774).
5340 if query.is_none() {
5341 let menu_is_open = matches!(
5342 self.context_menu.borrow().as_ref(),
5343 Some(CodeContextMenu::Completions(_))
5344 );
5345 if menu_is_open {
5346 self.hide_context_menu(window, cx);
5347 }
5348 }
5349
5350 let mut ignore_word_threshold = false;
5351 let provider = match requested_source {
5352 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5353 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5354 ignore_word_threshold = ignore_threshold;
5355 None
5356 }
5357 Some(CompletionsMenuSource::SnippetChoices) => {
5358 log::error!("bug: SnippetChoices requested_source is not handled");
5359 None
5360 }
5361 };
5362
5363 let sort_completions = provider
5364 .as_ref()
5365 .is_some_and(|provider| provider.sort_completions());
5366
5367 let filter_completions = provider
5368 .as_ref()
5369 .is_none_or(|provider| provider.filter_completions());
5370
5371 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5372 if filter_completions {
5373 menu.filter(query.clone(), provider.clone(), window, cx);
5374 }
5375 // When `is_incomplete` is false, no need to re-query completions when the current query
5376 // is a suffix of the initial query.
5377 if !menu.is_incomplete {
5378 // If the new query is a suffix of the old query (typing more characters) and
5379 // the previous result was complete, the existing completions can be filtered.
5380 //
5381 // Note that this is always true for snippet completions.
5382 let query_matches = match (&menu.initial_query, &query) {
5383 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5384 (None, _) => true,
5385 _ => false,
5386 };
5387 if query_matches {
5388 let position_matches = if menu.initial_position == position {
5389 true
5390 } else {
5391 let snapshot = self.buffer.read(cx).read(cx);
5392 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5393 };
5394 if position_matches {
5395 return;
5396 }
5397 }
5398 }
5399 };
5400
5401 let trigger_kind = match trigger {
5402 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5403 CompletionTriggerKind::TRIGGER_CHARACTER
5404 }
5405 _ => CompletionTriggerKind::INVOKED,
5406 };
5407 let completion_context = CompletionContext {
5408 trigger_character: trigger.and_then(|trigger| {
5409 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5410 Some(String::from(trigger))
5411 } else {
5412 None
5413 }
5414 }),
5415 trigger_kind,
5416 };
5417
5418 let Anchor {
5419 excerpt_id: buffer_excerpt_id,
5420 text_anchor: buffer_position,
5421 ..
5422 } = buffer_position;
5423
5424 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5425 buffer_snapshot.surrounding_word(buffer_position, None)
5426 {
5427 let word_to_exclude = buffer_snapshot
5428 .text_for_range(word_range.clone())
5429 .collect::<String>();
5430 (
5431 buffer_snapshot.anchor_before(word_range.start)
5432 ..buffer_snapshot.anchor_after(buffer_position),
5433 Some(word_to_exclude),
5434 )
5435 } else {
5436 (buffer_position..buffer_position, None)
5437 };
5438
5439 let language = buffer_snapshot
5440 .language_at(buffer_position)
5441 .map(|language| language.name());
5442
5443 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5444 .completions
5445 .clone();
5446
5447 let show_completion_documentation = buffer_snapshot
5448 .settings_at(buffer_position, cx)
5449 .show_completion_documentation;
5450
5451 // The document can be large, so stay in reasonable bounds when searching for words,
5452 // otherwise completion pop-up might be slow to appear.
5453 const WORD_LOOKUP_ROWS: u32 = 5_000;
5454 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5455 let min_word_search = buffer_snapshot.clip_point(
5456 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5457 Bias::Left,
5458 );
5459 let max_word_search = buffer_snapshot.clip_point(
5460 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5461 Bias::Right,
5462 );
5463 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5464 ..buffer_snapshot.point_to_offset(max_word_search);
5465
5466 let skip_digits = query
5467 .as_ref()
5468 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5469
5470 let omit_word_completions = !self.word_completions_enabled
5471 || (!ignore_word_threshold
5472 && match &query {
5473 Some(query) => query.chars().count() < completion_settings.words_min_length,
5474 None => completion_settings.words_min_length != 0,
5475 });
5476
5477 let (mut words, provider_responses) = match &provider {
5478 Some(provider) => {
5479 let provider_responses = provider.completions(
5480 buffer_excerpt_id,
5481 &buffer,
5482 buffer_position,
5483 completion_context,
5484 window,
5485 cx,
5486 );
5487
5488 let words = match (omit_word_completions, completion_settings.words) {
5489 (true, _) | (_, WordsCompletionMode::Disabled) => {
5490 Task::ready(BTreeMap::default())
5491 }
5492 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5493 .background_spawn(async move {
5494 buffer_snapshot.words_in_range(WordsQuery {
5495 fuzzy_contents: None,
5496 range: word_search_range,
5497 skip_digits,
5498 })
5499 }),
5500 };
5501
5502 (words, provider_responses)
5503 }
5504 None => {
5505 let words = if omit_word_completions {
5506 Task::ready(BTreeMap::default())
5507 } else {
5508 cx.background_spawn(async move {
5509 buffer_snapshot.words_in_range(WordsQuery {
5510 fuzzy_contents: None,
5511 range: word_search_range,
5512 skip_digits,
5513 })
5514 })
5515 };
5516 (words, Task::ready(Ok(Vec::new())))
5517 }
5518 };
5519
5520 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5521
5522 let id = post_inc(&mut self.next_completion_id);
5523 let task = cx.spawn_in(window, async move |editor, cx| {
5524 let Ok(()) = editor.update(cx, |this, _| {
5525 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5526 }) else {
5527 return;
5528 };
5529
5530 // TODO: Ideally completions from different sources would be selectively re-queried, so
5531 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5532 let mut completions = Vec::new();
5533 let mut is_incomplete = false;
5534 let mut display_options: Option<CompletionDisplayOptions> = None;
5535 if let Some(provider_responses) = provider_responses.await.log_err()
5536 && !provider_responses.is_empty()
5537 {
5538 for response in provider_responses {
5539 completions.extend(response.completions);
5540 is_incomplete = is_incomplete || response.is_incomplete;
5541 match display_options.as_mut() {
5542 None => {
5543 display_options = Some(response.display_options);
5544 }
5545 Some(options) => options.merge(&response.display_options),
5546 }
5547 }
5548 if completion_settings.words == WordsCompletionMode::Fallback {
5549 words = Task::ready(BTreeMap::default());
5550 }
5551 }
5552 let display_options = display_options.unwrap_or_default();
5553
5554 let mut words = words.await;
5555 if let Some(word_to_exclude) = &word_to_exclude {
5556 words.remove(word_to_exclude);
5557 }
5558 for lsp_completion in &completions {
5559 words.remove(&lsp_completion.new_text);
5560 }
5561 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5562 replace_range: word_replace_range.clone(),
5563 new_text: word.clone(),
5564 label: CodeLabel::plain(word, None),
5565 icon_path: None,
5566 documentation: None,
5567 source: CompletionSource::BufferWord {
5568 word_range,
5569 resolved: false,
5570 },
5571 insert_text_mode: Some(InsertTextMode::AS_IS),
5572 confirm: None,
5573 }));
5574
5575 let menu = if completions.is_empty() {
5576 None
5577 } else {
5578 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5579 let languages = editor
5580 .workspace
5581 .as_ref()
5582 .and_then(|(workspace, _)| workspace.upgrade())
5583 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5584 let menu = CompletionsMenu::new(
5585 id,
5586 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5587 sort_completions,
5588 show_completion_documentation,
5589 position,
5590 query.clone(),
5591 is_incomplete,
5592 buffer.clone(),
5593 completions.into(),
5594 display_options,
5595 snippet_sort_order,
5596 languages,
5597 language,
5598 cx,
5599 );
5600
5601 let query = if filter_completions { query } else { None };
5602 let matches_task = if let Some(query) = query {
5603 menu.do_async_filtering(query, cx)
5604 } else {
5605 Task::ready(menu.unfiltered_matches())
5606 };
5607 (menu, matches_task)
5608 }) else {
5609 return;
5610 };
5611
5612 let matches = matches_task.await;
5613
5614 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5615 // Newer menu already set, so exit.
5616 if let Some(CodeContextMenu::Completions(prev_menu)) =
5617 editor.context_menu.borrow().as_ref()
5618 && prev_menu.id > id
5619 {
5620 return;
5621 };
5622
5623 // Only valid to take prev_menu because it the new menu is immediately set
5624 // below, or the menu is hidden.
5625 if let Some(CodeContextMenu::Completions(prev_menu)) =
5626 editor.context_menu.borrow_mut().take()
5627 {
5628 let position_matches =
5629 if prev_menu.initial_position == menu.initial_position {
5630 true
5631 } else {
5632 let snapshot = editor.buffer.read(cx).read(cx);
5633 prev_menu.initial_position.to_offset(&snapshot)
5634 == menu.initial_position.to_offset(&snapshot)
5635 };
5636 if position_matches {
5637 // Preserve markdown cache before `set_filter_results` because it will
5638 // try to populate the documentation cache.
5639 menu.preserve_markdown_cache(prev_menu);
5640 }
5641 };
5642
5643 menu.set_filter_results(matches, provider, window, cx);
5644 }) else {
5645 return;
5646 };
5647
5648 menu.visible().then_some(menu)
5649 };
5650
5651 editor
5652 .update_in(cx, |editor, window, cx| {
5653 if editor.focus_handle.is_focused(window)
5654 && let Some(menu) = menu
5655 {
5656 *editor.context_menu.borrow_mut() =
5657 Some(CodeContextMenu::Completions(menu));
5658
5659 crate::hover_popover::hide_hover(editor, cx);
5660 if editor.show_edit_predictions_in_menu() {
5661 editor.update_visible_edit_prediction(window, cx);
5662 } else {
5663 editor.discard_edit_prediction(false, cx);
5664 }
5665
5666 cx.notify();
5667 return;
5668 }
5669
5670 if editor.completion_tasks.len() <= 1 {
5671 // If there are no more completion tasks and the last menu was empty, we should hide it.
5672 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5673 // If it was already hidden and we don't show edit predictions in the menu,
5674 // we should also show the edit prediction when available.
5675 if was_hidden && editor.show_edit_predictions_in_menu() {
5676 editor.update_visible_edit_prediction(window, cx);
5677 }
5678 }
5679 })
5680 .ok();
5681 });
5682
5683 self.completion_tasks.push((id, task));
5684 }
5685
5686 #[cfg(feature = "test-support")]
5687 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5688 let menu = self.context_menu.borrow();
5689 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5690 let completions = menu.completions.borrow();
5691 Some(completions.to_vec())
5692 } else {
5693 None
5694 }
5695 }
5696
5697 pub fn with_completions_menu_matching_id<R>(
5698 &self,
5699 id: CompletionId,
5700 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5701 ) -> R {
5702 let mut context_menu = self.context_menu.borrow_mut();
5703 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5704 return f(None);
5705 };
5706 if completions_menu.id != id {
5707 return f(None);
5708 }
5709 f(Some(completions_menu))
5710 }
5711
5712 pub fn confirm_completion(
5713 &mut self,
5714 action: &ConfirmCompletion,
5715 window: &mut Window,
5716 cx: &mut Context<Self>,
5717 ) -> Option<Task<Result<()>>> {
5718 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5719 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5720 }
5721
5722 pub fn confirm_completion_insert(
5723 &mut self,
5724 _: &ConfirmCompletionInsert,
5725 window: &mut Window,
5726 cx: &mut Context<Self>,
5727 ) -> Option<Task<Result<()>>> {
5728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5729 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5730 }
5731
5732 pub fn confirm_completion_replace(
5733 &mut self,
5734 _: &ConfirmCompletionReplace,
5735 window: &mut Window,
5736 cx: &mut Context<Self>,
5737 ) -> Option<Task<Result<()>>> {
5738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5739 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5740 }
5741
5742 pub fn compose_completion(
5743 &mut self,
5744 action: &ComposeCompletion,
5745 window: &mut Window,
5746 cx: &mut Context<Self>,
5747 ) -> Option<Task<Result<()>>> {
5748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5749 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5750 }
5751
5752 fn do_completion(
5753 &mut self,
5754 item_ix: Option<usize>,
5755 intent: CompletionIntent,
5756 window: &mut Window,
5757 cx: &mut Context<Editor>,
5758 ) -> Option<Task<Result<()>>> {
5759 use language::ToOffset as _;
5760
5761 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5762 else {
5763 return None;
5764 };
5765
5766 let candidate_id = {
5767 let entries = completions_menu.entries.borrow();
5768 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5769 if self.show_edit_predictions_in_menu() {
5770 self.discard_edit_prediction(true, cx);
5771 }
5772 mat.candidate_id
5773 };
5774
5775 let completion = completions_menu
5776 .completions
5777 .borrow()
5778 .get(candidate_id)?
5779 .clone();
5780 cx.stop_propagation();
5781
5782 let buffer_handle = completions_menu.buffer.clone();
5783
5784 let CompletionEdit {
5785 new_text,
5786 snippet,
5787 replace_range,
5788 } = process_completion_for_edit(
5789 &completion,
5790 intent,
5791 &buffer_handle,
5792 &completions_menu.initial_position.text_anchor,
5793 cx,
5794 );
5795
5796 let buffer = buffer_handle.read(cx);
5797 let snapshot = self.buffer.read(cx).snapshot(cx);
5798 let newest_anchor = self.selections.newest_anchor();
5799 let replace_range_multibuffer = {
5800 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5801 excerpt.map_range_from_buffer(replace_range.clone())
5802 };
5803 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5804 return None;
5805 }
5806
5807 let old_text = buffer
5808 .text_for_range(replace_range.clone())
5809 .collect::<String>();
5810 let lookbehind = newest_anchor
5811 .start
5812 .text_anchor
5813 .to_offset(buffer)
5814 .saturating_sub(replace_range.start);
5815 let lookahead = replace_range
5816 .end
5817 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5818 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5819 let suffix = &old_text[lookbehind.min(old_text.len())..];
5820
5821 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5822 let mut ranges = Vec::new();
5823 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5824
5825 for selection in &selections {
5826 let range = if selection.id == newest_anchor.id {
5827 replace_range_multibuffer.clone()
5828 } else {
5829 let mut range = selection.range();
5830
5831 // if prefix is present, don't duplicate it
5832 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5833 range.start = range.start.saturating_sub(lookbehind);
5834
5835 // if suffix is also present, mimic the newest cursor and replace it
5836 if selection.id != newest_anchor.id
5837 && snapshot.contains_str_at(range.end, suffix)
5838 {
5839 range.end += lookahead;
5840 }
5841 }
5842 range
5843 };
5844
5845 ranges.push(range.clone());
5846
5847 if !self.linked_edit_ranges.is_empty() {
5848 let start_anchor = snapshot.anchor_before(range.start);
5849 let end_anchor = snapshot.anchor_after(range.end);
5850 if let Some(ranges) = self
5851 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5852 {
5853 for (buffer, edits) in ranges {
5854 linked_edits
5855 .entry(buffer.clone())
5856 .or_default()
5857 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5858 }
5859 }
5860 }
5861 }
5862
5863 let common_prefix_len = old_text
5864 .chars()
5865 .zip(new_text.chars())
5866 .take_while(|(a, b)| a == b)
5867 .map(|(a, _)| a.len_utf8())
5868 .sum::<usize>();
5869
5870 cx.emit(EditorEvent::InputHandled {
5871 utf16_range_to_replace: None,
5872 text: new_text[common_prefix_len..].into(),
5873 });
5874
5875 self.transact(window, cx, |editor, window, cx| {
5876 if let Some(mut snippet) = snippet {
5877 snippet.text = new_text.to_string();
5878 editor
5879 .insert_snippet(&ranges, snippet, window, cx)
5880 .log_err();
5881 } else {
5882 editor.buffer.update(cx, |multi_buffer, cx| {
5883 let auto_indent = match completion.insert_text_mode {
5884 Some(InsertTextMode::AS_IS) => None,
5885 _ => editor.autoindent_mode.clone(),
5886 };
5887 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5888 multi_buffer.edit(edits, auto_indent, cx);
5889 });
5890 }
5891 for (buffer, edits) in linked_edits {
5892 buffer.update(cx, |buffer, cx| {
5893 let snapshot = buffer.snapshot();
5894 let edits = edits
5895 .into_iter()
5896 .map(|(range, text)| {
5897 use text::ToPoint as TP;
5898 let end_point = TP::to_point(&range.end, &snapshot);
5899 let start_point = TP::to_point(&range.start, &snapshot);
5900 (start_point..end_point, text)
5901 })
5902 .sorted_by_key(|(range, _)| range.start);
5903 buffer.edit(edits, None, cx);
5904 })
5905 }
5906
5907 editor.refresh_edit_prediction(true, false, window, cx);
5908 });
5909 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5910
5911 let show_new_completions_on_confirm = completion
5912 .confirm
5913 .as_ref()
5914 .is_some_and(|confirm| confirm(intent, window, cx));
5915 if show_new_completions_on_confirm {
5916 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5917 }
5918
5919 let provider = self.completion_provider.as_ref()?;
5920 drop(completion);
5921 let apply_edits = provider.apply_additional_edits_for_completion(
5922 buffer_handle,
5923 completions_menu.completions.clone(),
5924 candidate_id,
5925 true,
5926 cx,
5927 );
5928
5929 let editor_settings = EditorSettings::get_global(cx);
5930 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5931 // After the code completion is finished, users often want to know what signatures are needed.
5932 // so we should automatically call signature_help
5933 self.show_signature_help(&ShowSignatureHelp, window, cx);
5934 }
5935
5936 Some(cx.foreground_executor().spawn(async move {
5937 apply_edits.await?;
5938 Ok(())
5939 }))
5940 }
5941
5942 pub fn toggle_code_actions(
5943 &mut self,
5944 action: &ToggleCodeActions,
5945 window: &mut Window,
5946 cx: &mut Context<Self>,
5947 ) {
5948 let quick_launch = action.quick_launch;
5949 let mut context_menu = self.context_menu.borrow_mut();
5950 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5951 if code_actions.deployed_from == action.deployed_from {
5952 // Toggle if we're selecting the same one
5953 *context_menu = None;
5954 cx.notify();
5955 return;
5956 } else {
5957 // Otherwise, clear it and start a new one
5958 *context_menu = None;
5959 cx.notify();
5960 }
5961 }
5962 drop(context_menu);
5963 let snapshot = self.snapshot(window, cx);
5964 let deployed_from = action.deployed_from.clone();
5965 let action = action.clone();
5966 self.completion_tasks.clear();
5967 self.discard_edit_prediction(false, cx);
5968
5969 let multibuffer_point = match &action.deployed_from {
5970 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5971 DisplayPoint::new(*row, 0).to_point(&snapshot)
5972 }
5973 _ => self
5974 .selections
5975 .newest::<Point>(&snapshot.display_snapshot)
5976 .head(),
5977 };
5978 let Some((buffer, buffer_row)) = snapshot
5979 .buffer_snapshot()
5980 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5981 .and_then(|(buffer_snapshot, range)| {
5982 self.buffer()
5983 .read(cx)
5984 .buffer(buffer_snapshot.remote_id())
5985 .map(|buffer| (buffer, range.start.row))
5986 })
5987 else {
5988 return;
5989 };
5990 let buffer_id = buffer.read(cx).remote_id();
5991 let tasks = self
5992 .tasks
5993 .get(&(buffer_id, buffer_row))
5994 .map(|t| Arc::new(t.to_owned()));
5995
5996 if !self.focus_handle.is_focused(window) {
5997 return;
5998 }
5999 let project = self.project.clone();
6000
6001 let code_actions_task = match deployed_from {
6002 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6003 _ => self.code_actions(buffer_row, window, cx),
6004 };
6005
6006 let runnable_task = match deployed_from {
6007 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6008 _ => {
6009 let mut task_context_task = Task::ready(None);
6010 if let Some(tasks) = &tasks
6011 && let Some(project) = project
6012 {
6013 task_context_task =
6014 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6015 }
6016
6017 cx.spawn_in(window, {
6018 let buffer = buffer.clone();
6019 async move |editor, cx| {
6020 let task_context = task_context_task.await;
6021
6022 let resolved_tasks =
6023 tasks
6024 .zip(task_context.clone())
6025 .map(|(tasks, task_context)| ResolvedTasks {
6026 templates: tasks.resolve(&task_context).collect(),
6027 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6028 multibuffer_point.row,
6029 tasks.column,
6030 )),
6031 });
6032 let debug_scenarios = editor
6033 .update(cx, |editor, cx| {
6034 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6035 })?
6036 .await;
6037 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6038 }
6039 })
6040 }
6041 };
6042
6043 cx.spawn_in(window, async move |editor, cx| {
6044 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6045 let code_actions = code_actions_task.await;
6046 let spawn_straight_away = quick_launch
6047 && resolved_tasks
6048 .as_ref()
6049 .is_some_and(|tasks| tasks.templates.len() == 1)
6050 && code_actions
6051 .as_ref()
6052 .is_none_or(|actions| actions.is_empty())
6053 && debug_scenarios.is_empty();
6054
6055 editor.update_in(cx, |editor, window, cx| {
6056 crate::hover_popover::hide_hover(editor, cx);
6057 let actions = CodeActionContents::new(
6058 resolved_tasks,
6059 code_actions,
6060 debug_scenarios,
6061 task_context.unwrap_or_default(),
6062 );
6063
6064 // Don't show the menu if there are no actions available
6065 if actions.is_empty() {
6066 cx.notify();
6067 return Task::ready(Ok(()));
6068 }
6069
6070 *editor.context_menu.borrow_mut() =
6071 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6072 buffer,
6073 actions,
6074 selected_item: Default::default(),
6075 scroll_handle: UniformListScrollHandle::default(),
6076 deployed_from,
6077 }));
6078 cx.notify();
6079 if spawn_straight_away
6080 && let Some(task) = editor.confirm_code_action(
6081 &ConfirmCodeAction { item_ix: Some(0) },
6082 window,
6083 cx,
6084 )
6085 {
6086 return task;
6087 }
6088
6089 Task::ready(Ok(()))
6090 })
6091 })
6092 .detach_and_log_err(cx);
6093 }
6094
6095 fn debug_scenarios(
6096 &mut self,
6097 resolved_tasks: &Option<ResolvedTasks>,
6098 buffer: &Entity<Buffer>,
6099 cx: &mut App,
6100 ) -> Task<Vec<task::DebugScenario>> {
6101 maybe!({
6102 let project = self.project()?;
6103 let dap_store = project.read(cx).dap_store();
6104 let mut scenarios = vec![];
6105 let resolved_tasks = resolved_tasks.as_ref()?;
6106 let buffer = buffer.read(cx);
6107 let language = buffer.language()?;
6108 let file = buffer.file();
6109 let debug_adapter = language_settings(language.name().into(), file, cx)
6110 .debuggers
6111 .first()
6112 .map(SharedString::from)
6113 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6114
6115 dap_store.update(cx, |dap_store, cx| {
6116 for (_, task) in &resolved_tasks.templates {
6117 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6118 task.original_task().clone(),
6119 debug_adapter.clone().into(),
6120 task.display_label().to_owned().into(),
6121 cx,
6122 );
6123 scenarios.push(maybe_scenario);
6124 }
6125 });
6126 Some(cx.background_spawn(async move {
6127 futures::future::join_all(scenarios)
6128 .await
6129 .into_iter()
6130 .flatten()
6131 .collect::<Vec<_>>()
6132 }))
6133 })
6134 .unwrap_or_else(|| Task::ready(vec![]))
6135 }
6136
6137 fn code_actions(
6138 &mut self,
6139 buffer_row: u32,
6140 window: &mut Window,
6141 cx: &mut Context<Self>,
6142 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6143 let mut task = self.code_actions_task.take();
6144 cx.spawn_in(window, async move |editor, cx| {
6145 while let Some(prev_task) = task {
6146 prev_task.await.log_err();
6147 task = editor
6148 .update(cx, |this, _| this.code_actions_task.take())
6149 .ok()?;
6150 }
6151
6152 editor
6153 .update(cx, |editor, cx| {
6154 editor
6155 .available_code_actions
6156 .clone()
6157 .and_then(|(location, code_actions)| {
6158 let snapshot = location.buffer.read(cx).snapshot();
6159 let point_range = location.range.to_point(&snapshot);
6160 let point_range = point_range.start.row..=point_range.end.row;
6161 if point_range.contains(&buffer_row) {
6162 Some(code_actions)
6163 } else {
6164 None
6165 }
6166 })
6167 })
6168 .ok()
6169 .flatten()
6170 })
6171 }
6172
6173 pub fn confirm_code_action(
6174 &mut self,
6175 action: &ConfirmCodeAction,
6176 window: &mut Window,
6177 cx: &mut Context<Self>,
6178 ) -> Option<Task<Result<()>>> {
6179 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6180
6181 let actions_menu =
6182 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6183 menu
6184 } else {
6185 return None;
6186 };
6187
6188 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6189 let action = actions_menu.actions.get(action_ix)?;
6190 let title = action.label();
6191 let buffer = actions_menu.buffer;
6192 let workspace = self.workspace()?;
6193
6194 match action {
6195 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6196 workspace.update(cx, |workspace, cx| {
6197 workspace.schedule_resolved_task(
6198 task_source_kind,
6199 resolved_task,
6200 false,
6201 window,
6202 cx,
6203 );
6204
6205 Some(Task::ready(Ok(())))
6206 })
6207 }
6208 CodeActionsItem::CodeAction {
6209 excerpt_id,
6210 action,
6211 provider,
6212 } => {
6213 let apply_code_action =
6214 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6215 let workspace = workspace.downgrade();
6216 Some(cx.spawn_in(window, async move |editor, cx| {
6217 let project_transaction = apply_code_action.await?;
6218 Self::open_project_transaction(
6219 &editor,
6220 workspace,
6221 project_transaction,
6222 title,
6223 cx,
6224 )
6225 .await
6226 }))
6227 }
6228 CodeActionsItem::DebugScenario(scenario) => {
6229 let context = actions_menu.actions.context;
6230
6231 workspace.update(cx, |workspace, cx| {
6232 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6233 workspace.start_debug_session(
6234 scenario,
6235 context,
6236 Some(buffer),
6237 None,
6238 window,
6239 cx,
6240 );
6241 });
6242 Some(Task::ready(Ok(())))
6243 }
6244 }
6245 }
6246
6247 pub async fn open_project_transaction(
6248 editor: &WeakEntity<Editor>,
6249 workspace: WeakEntity<Workspace>,
6250 transaction: ProjectTransaction,
6251 title: String,
6252 cx: &mut AsyncWindowContext,
6253 ) -> Result<()> {
6254 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6255 cx.update(|_, cx| {
6256 entries.sort_unstable_by_key(|(buffer, _)| {
6257 buffer.read(cx).file().map(|f| f.path().clone())
6258 });
6259 })?;
6260 if entries.is_empty() {
6261 return Ok(());
6262 }
6263
6264 // If the project transaction's edits are all contained within this editor, then
6265 // avoid opening a new editor to display them.
6266
6267 if let [(buffer, transaction)] = &*entries {
6268 let excerpt = editor.update(cx, |editor, cx| {
6269 editor
6270 .buffer()
6271 .read(cx)
6272 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6273 })?;
6274 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6275 && excerpted_buffer == *buffer
6276 {
6277 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6278 let excerpt_range = excerpt_range.to_offset(buffer);
6279 buffer
6280 .edited_ranges_for_transaction::<usize>(transaction)
6281 .all(|range| {
6282 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6283 })
6284 })?;
6285
6286 if all_edits_within_excerpt {
6287 return Ok(());
6288 }
6289 }
6290 }
6291
6292 let mut ranges_to_highlight = Vec::new();
6293 let excerpt_buffer = cx.new(|cx| {
6294 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6295 for (buffer_handle, transaction) in &entries {
6296 let edited_ranges = buffer_handle
6297 .read(cx)
6298 .edited_ranges_for_transaction::<Point>(transaction)
6299 .collect::<Vec<_>>();
6300 let (ranges, _) = multibuffer.set_excerpts_for_path(
6301 PathKey::for_buffer(buffer_handle, cx),
6302 buffer_handle.clone(),
6303 edited_ranges,
6304 multibuffer_context_lines(cx),
6305 cx,
6306 );
6307
6308 ranges_to_highlight.extend(ranges);
6309 }
6310 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6311 multibuffer
6312 })?;
6313
6314 workspace.update_in(cx, |workspace, window, cx| {
6315 let project = workspace.project().clone();
6316 let editor =
6317 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6318 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6319 editor.update(cx, |editor, cx| {
6320 editor.highlight_background::<Self>(
6321 &ranges_to_highlight,
6322 |theme| theme.colors().editor_highlighted_line_background,
6323 cx,
6324 );
6325 });
6326 })?;
6327
6328 Ok(())
6329 }
6330
6331 pub fn clear_code_action_providers(&mut self) {
6332 self.code_action_providers.clear();
6333 self.available_code_actions.take();
6334 }
6335
6336 pub fn add_code_action_provider(
6337 &mut self,
6338 provider: Rc<dyn CodeActionProvider>,
6339 window: &mut Window,
6340 cx: &mut Context<Self>,
6341 ) {
6342 if self
6343 .code_action_providers
6344 .iter()
6345 .any(|existing_provider| existing_provider.id() == provider.id())
6346 {
6347 return;
6348 }
6349
6350 self.code_action_providers.push(provider);
6351 self.refresh_code_actions(window, cx);
6352 }
6353
6354 pub fn remove_code_action_provider(
6355 &mut self,
6356 id: Arc<str>,
6357 window: &mut Window,
6358 cx: &mut Context<Self>,
6359 ) {
6360 self.code_action_providers
6361 .retain(|provider| provider.id() != id);
6362 self.refresh_code_actions(window, cx);
6363 }
6364
6365 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6366 !self.code_action_providers.is_empty()
6367 && EditorSettings::get_global(cx).toolbar.code_actions
6368 }
6369
6370 pub fn has_available_code_actions(&self) -> bool {
6371 self.available_code_actions
6372 .as_ref()
6373 .is_some_and(|(_, actions)| !actions.is_empty())
6374 }
6375
6376 fn render_inline_code_actions(
6377 &self,
6378 icon_size: ui::IconSize,
6379 display_row: DisplayRow,
6380 is_active: bool,
6381 cx: &mut Context<Self>,
6382 ) -> AnyElement {
6383 let show_tooltip = !self.context_menu_visible();
6384 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6385 .icon_size(icon_size)
6386 .shape(ui::IconButtonShape::Square)
6387 .icon_color(ui::Color::Hidden)
6388 .toggle_state(is_active)
6389 .when(show_tooltip, |this| {
6390 this.tooltip({
6391 let focus_handle = self.focus_handle.clone();
6392 move |_window, cx| {
6393 Tooltip::for_action_in(
6394 "Toggle Code Actions",
6395 &ToggleCodeActions {
6396 deployed_from: None,
6397 quick_launch: false,
6398 },
6399 &focus_handle,
6400 cx,
6401 )
6402 }
6403 })
6404 })
6405 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6406 window.focus(&editor.focus_handle(cx));
6407 editor.toggle_code_actions(
6408 &crate::actions::ToggleCodeActions {
6409 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6410 display_row,
6411 )),
6412 quick_launch: false,
6413 },
6414 window,
6415 cx,
6416 );
6417 }))
6418 .into_any_element()
6419 }
6420
6421 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6422 &self.context_menu
6423 }
6424
6425 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6426 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6427 cx.background_executor()
6428 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6429 .await;
6430
6431 let (start_buffer, start, _, end, newest_selection) = this
6432 .update(cx, |this, cx| {
6433 let newest_selection = this.selections.newest_anchor().clone();
6434 if newest_selection.head().diff_base_anchor.is_some() {
6435 return None;
6436 }
6437 let display_snapshot = this.display_snapshot(cx);
6438 let newest_selection_adjusted =
6439 this.selections.newest_adjusted(&display_snapshot);
6440 let buffer = this.buffer.read(cx);
6441
6442 let (start_buffer, start) =
6443 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6444 let (end_buffer, end) =
6445 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6446
6447 Some((start_buffer, start, end_buffer, end, newest_selection))
6448 })?
6449 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6450 .context(
6451 "Expected selection to lie in a single buffer when refreshing code actions",
6452 )?;
6453 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6454 let providers = this.code_action_providers.clone();
6455 let tasks = this
6456 .code_action_providers
6457 .iter()
6458 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6459 .collect::<Vec<_>>();
6460 (providers, tasks)
6461 })?;
6462
6463 let mut actions = Vec::new();
6464 for (provider, provider_actions) in
6465 providers.into_iter().zip(future::join_all(tasks).await)
6466 {
6467 if let Some(provider_actions) = provider_actions.log_err() {
6468 actions.extend(provider_actions.into_iter().map(|action| {
6469 AvailableCodeAction {
6470 excerpt_id: newest_selection.start.excerpt_id,
6471 action,
6472 provider: provider.clone(),
6473 }
6474 }));
6475 }
6476 }
6477
6478 this.update(cx, |this, cx| {
6479 this.available_code_actions = if actions.is_empty() {
6480 None
6481 } else {
6482 Some((
6483 Location {
6484 buffer: start_buffer,
6485 range: start..end,
6486 },
6487 actions.into(),
6488 ))
6489 };
6490 cx.notify();
6491 })
6492 }));
6493 }
6494
6495 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6496 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6497 self.show_git_blame_inline = false;
6498
6499 self.show_git_blame_inline_delay_task =
6500 Some(cx.spawn_in(window, async move |this, cx| {
6501 cx.background_executor().timer(delay).await;
6502
6503 this.update(cx, |this, cx| {
6504 this.show_git_blame_inline = true;
6505 cx.notify();
6506 })
6507 .log_err();
6508 }));
6509 }
6510 }
6511
6512 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6513 let snapshot = self.snapshot(window, cx);
6514 let cursor = self
6515 .selections
6516 .newest::<Point>(&snapshot.display_snapshot)
6517 .head();
6518 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6519 else {
6520 return;
6521 };
6522
6523 let Some(blame) = self.blame.as_ref() else {
6524 return;
6525 };
6526
6527 let row_info = RowInfo {
6528 buffer_id: Some(buffer.remote_id()),
6529 buffer_row: Some(point.row),
6530 ..Default::default()
6531 };
6532 let Some((buffer, blame_entry)) = blame
6533 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6534 .flatten()
6535 else {
6536 return;
6537 };
6538
6539 let anchor = self.selections.newest_anchor().head();
6540 let position = self.to_pixel_point(anchor, &snapshot, window);
6541 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6542 self.show_blame_popover(
6543 buffer,
6544 &blame_entry,
6545 position + last_bounds.origin,
6546 true,
6547 cx,
6548 );
6549 };
6550 }
6551
6552 fn show_blame_popover(
6553 &mut self,
6554 buffer: BufferId,
6555 blame_entry: &BlameEntry,
6556 position: gpui::Point<Pixels>,
6557 ignore_timeout: bool,
6558 cx: &mut Context<Self>,
6559 ) {
6560 if let Some(state) = &mut self.inline_blame_popover {
6561 state.hide_task.take();
6562 } else {
6563 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6564 let blame_entry = blame_entry.clone();
6565 let show_task = cx.spawn(async move |editor, cx| {
6566 if !ignore_timeout {
6567 cx.background_executor()
6568 .timer(std::time::Duration::from_millis(blame_popover_delay))
6569 .await;
6570 }
6571 editor
6572 .update(cx, |editor, cx| {
6573 editor.inline_blame_popover_show_task.take();
6574 let Some(blame) = editor.blame.as_ref() else {
6575 return;
6576 };
6577 let blame = blame.read(cx);
6578 let details = blame.details_for_entry(buffer, &blame_entry);
6579 let markdown = cx.new(|cx| {
6580 Markdown::new(
6581 details
6582 .as_ref()
6583 .map(|message| message.message.clone())
6584 .unwrap_or_default(),
6585 None,
6586 None,
6587 cx,
6588 )
6589 });
6590 editor.inline_blame_popover = Some(InlineBlamePopover {
6591 position,
6592 hide_task: None,
6593 popover_bounds: None,
6594 popover_state: InlineBlamePopoverState {
6595 scroll_handle: ScrollHandle::new(),
6596 commit_message: details,
6597 markdown,
6598 },
6599 keyboard_grace: ignore_timeout,
6600 });
6601 cx.notify();
6602 })
6603 .ok();
6604 });
6605 self.inline_blame_popover_show_task = Some(show_task);
6606 }
6607 }
6608
6609 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6610 self.inline_blame_popover_show_task.take();
6611 if let Some(state) = &mut self.inline_blame_popover {
6612 let hide_task = cx.spawn(async move |editor, cx| {
6613 if !ignore_timeout {
6614 cx.background_executor()
6615 .timer(std::time::Duration::from_millis(100))
6616 .await;
6617 }
6618 editor
6619 .update(cx, |editor, cx| {
6620 editor.inline_blame_popover.take();
6621 cx.notify();
6622 })
6623 .ok();
6624 });
6625 state.hide_task = Some(hide_task);
6626 true
6627 } else {
6628 false
6629 }
6630 }
6631
6632 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6633 if self.pending_rename.is_some() {
6634 return None;
6635 }
6636
6637 let provider = self.semantics_provider.clone()?;
6638 let buffer = self.buffer.read(cx);
6639 let newest_selection = self.selections.newest_anchor().clone();
6640 let cursor_position = newest_selection.head();
6641 let (cursor_buffer, cursor_buffer_position) =
6642 buffer.text_anchor_for_position(cursor_position, cx)?;
6643 let (tail_buffer, tail_buffer_position) =
6644 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6645 if cursor_buffer != tail_buffer {
6646 return None;
6647 }
6648
6649 let snapshot = cursor_buffer.read(cx).snapshot();
6650 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6651 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6652 if start_word_range != end_word_range {
6653 self.document_highlights_task.take();
6654 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6655 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6656 return None;
6657 }
6658
6659 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6660 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6661 cx.background_executor()
6662 .timer(Duration::from_millis(debounce))
6663 .await;
6664
6665 let highlights = if let Some(highlights) = cx
6666 .update(|cx| {
6667 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6668 })
6669 .ok()
6670 .flatten()
6671 {
6672 highlights.await.log_err()
6673 } else {
6674 None
6675 };
6676
6677 if let Some(highlights) = highlights {
6678 this.update(cx, |this, cx| {
6679 if this.pending_rename.is_some() {
6680 return;
6681 }
6682
6683 let buffer = this.buffer.read(cx);
6684 if buffer
6685 .text_anchor_for_position(cursor_position, cx)
6686 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6687 {
6688 return;
6689 }
6690
6691 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6692 let mut write_ranges = Vec::new();
6693 let mut read_ranges = Vec::new();
6694 for highlight in highlights {
6695 let buffer_id = cursor_buffer.read(cx).remote_id();
6696 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6697 {
6698 let start = highlight
6699 .range
6700 .start
6701 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6702 let end = highlight
6703 .range
6704 .end
6705 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6706 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6707 continue;
6708 }
6709
6710 let range =
6711 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6712 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6713 write_ranges.push(range);
6714 } else {
6715 read_ranges.push(range);
6716 }
6717 }
6718 }
6719
6720 this.highlight_background::<DocumentHighlightRead>(
6721 &read_ranges,
6722 |theme| theme.colors().editor_document_highlight_read_background,
6723 cx,
6724 );
6725 this.highlight_background::<DocumentHighlightWrite>(
6726 &write_ranges,
6727 |theme| theme.colors().editor_document_highlight_write_background,
6728 cx,
6729 );
6730 cx.notify();
6731 })
6732 .log_err();
6733 }
6734 }));
6735 None
6736 }
6737
6738 fn prepare_highlight_query_from_selection(
6739 &mut self,
6740 cx: &mut Context<Editor>,
6741 ) -> Option<(String, Range<Anchor>)> {
6742 if matches!(self.mode, EditorMode::SingleLine) {
6743 return None;
6744 }
6745 if !EditorSettings::get_global(cx).selection_highlight {
6746 return None;
6747 }
6748 if self.selections.count() != 1 || self.selections.line_mode() {
6749 return None;
6750 }
6751 let selection = self.selections.newest_anchor();
6752 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6753 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6754 ..selection.end.to_point(&multi_buffer_snapshot);
6755 // If the selection spans multiple rows OR it is empty
6756 if selection_point_range.start.row != selection_point_range.end.row
6757 || selection_point_range.start.column == selection_point_range.end.column
6758 {
6759 return None;
6760 }
6761
6762 let query = multi_buffer_snapshot
6763 .text_for_range(selection.range())
6764 .collect::<String>();
6765 if query.trim().is_empty() {
6766 return None;
6767 }
6768 Some((query, selection.range()))
6769 }
6770
6771 fn update_selection_occurrence_highlights(
6772 &mut self,
6773 query_text: String,
6774 query_range: Range<Anchor>,
6775 multi_buffer_range_to_query: Range<Point>,
6776 use_debounce: bool,
6777 window: &mut Window,
6778 cx: &mut Context<Editor>,
6779 ) -> Task<()> {
6780 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6781 cx.spawn_in(window, async move |editor, cx| {
6782 if use_debounce {
6783 cx.background_executor()
6784 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6785 .await;
6786 }
6787 let match_task = cx.background_spawn(async move {
6788 let buffer_ranges = multi_buffer_snapshot
6789 .range_to_buffer_ranges(multi_buffer_range_to_query)
6790 .into_iter()
6791 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6792 let mut match_ranges = Vec::new();
6793 let Ok(regex) = project::search::SearchQuery::text(
6794 query_text.clone(),
6795 false,
6796 false,
6797 false,
6798 Default::default(),
6799 Default::default(),
6800 false,
6801 None,
6802 ) else {
6803 return Vec::default();
6804 };
6805 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6806 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6807 match_ranges.extend(
6808 regex
6809 .search(buffer_snapshot, Some(search_range.clone()))
6810 .await
6811 .into_iter()
6812 .filter_map(|match_range| {
6813 let match_start = buffer_snapshot
6814 .anchor_after(search_range.start + match_range.start);
6815 let match_end = buffer_snapshot
6816 .anchor_before(search_range.start + match_range.end);
6817 let match_anchor_range = Anchor::range_in_buffer(
6818 excerpt_id,
6819 buffer_snapshot.remote_id(),
6820 match_start..match_end,
6821 );
6822 (match_anchor_range != query_range).then_some(match_anchor_range)
6823 }),
6824 );
6825 }
6826 match_ranges
6827 });
6828 let match_ranges = match_task.await;
6829 editor
6830 .update_in(cx, |editor, _, cx| {
6831 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6832 if !match_ranges.is_empty() {
6833 editor.highlight_background::<SelectedTextHighlight>(
6834 &match_ranges,
6835 |theme| theme.colors().editor_document_highlight_bracket_background,
6836 cx,
6837 )
6838 }
6839 })
6840 .log_err();
6841 })
6842 }
6843
6844 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6845 struct NewlineFold;
6846 let type_id = std::any::TypeId::of::<NewlineFold>();
6847 if !self.mode.is_single_line() {
6848 return;
6849 }
6850 let snapshot = self.snapshot(window, cx);
6851 if snapshot.buffer_snapshot().max_point().row == 0 {
6852 return;
6853 }
6854 let task = cx.background_spawn(async move {
6855 let new_newlines = snapshot
6856 .buffer_chars_at(0)
6857 .filter_map(|(c, i)| {
6858 if c == '\n' {
6859 Some(
6860 snapshot.buffer_snapshot().anchor_after(i)
6861 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6862 )
6863 } else {
6864 None
6865 }
6866 })
6867 .collect::<Vec<_>>();
6868 let existing_newlines = snapshot
6869 .folds_in_range(0..snapshot.buffer_snapshot().len())
6870 .filter_map(|fold| {
6871 if fold.placeholder.type_tag == Some(type_id) {
6872 Some(fold.range.start..fold.range.end)
6873 } else {
6874 None
6875 }
6876 })
6877 .collect::<Vec<_>>();
6878
6879 (new_newlines, existing_newlines)
6880 });
6881 self.folding_newlines = cx.spawn(async move |this, cx| {
6882 let (new_newlines, existing_newlines) = task.await;
6883 if new_newlines == existing_newlines {
6884 return;
6885 }
6886 let placeholder = FoldPlaceholder {
6887 render: Arc::new(move |_, _, cx| {
6888 div()
6889 .bg(cx.theme().status().hint_background)
6890 .border_b_1()
6891 .size_full()
6892 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6893 .border_color(cx.theme().status().hint)
6894 .child("\\n")
6895 .into_any()
6896 }),
6897 constrain_width: false,
6898 merge_adjacent: false,
6899 type_tag: Some(type_id),
6900 };
6901 let creases = new_newlines
6902 .into_iter()
6903 .map(|range| Crease::simple(range, placeholder.clone()))
6904 .collect();
6905 this.update(cx, |this, cx| {
6906 this.display_map.update(cx, |display_map, cx| {
6907 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6908 display_map.fold(creases, cx);
6909 });
6910 })
6911 .ok();
6912 });
6913 }
6914
6915 fn refresh_selected_text_highlights(
6916 &mut self,
6917 on_buffer_edit: bool,
6918 window: &mut Window,
6919 cx: &mut Context<Editor>,
6920 ) {
6921 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6922 else {
6923 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6924 self.quick_selection_highlight_task.take();
6925 self.debounced_selection_highlight_task.take();
6926 return;
6927 };
6928 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6929 if on_buffer_edit
6930 || self
6931 .quick_selection_highlight_task
6932 .as_ref()
6933 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6934 {
6935 let multi_buffer_visible_start = self
6936 .scroll_manager
6937 .anchor()
6938 .anchor
6939 .to_point(&multi_buffer_snapshot);
6940 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6941 multi_buffer_visible_start
6942 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6943 Bias::Left,
6944 );
6945 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6946 self.quick_selection_highlight_task = Some((
6947 query_range.clone(),
6948 self.update_selection_occurrence_highlights(
6949 query_text.clone(),
6950 query_range.clone(),
6951 multi_buffer_visible_range,
6952 false,
6953 window,
6954 cx,
6955 ),
6956 ));
6957 }
6958 if on_buffer_edit
6959 || self
6960 .debounced_selection_highlight_task
6961 .as_ref()
6962 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6963 {
6964 let multi_buffer_start = multi_buffer_snapshot
6965 .anchor_before(0)
6966 .to_point(&multi_buffer_snapshot);
6967 let multi_buffer_end = multi_buffer_snapshot
6968 .anchor_after(multi_buffer_snapshot.len())
6969 .to_point(&multi_buffer_snapshot);
6970 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6971 self.debounced_selection_highlight_task = Some((
6972 query_range.clone(),
6973 self.update_selection_occurrence_highlights(
6974 query_text,
6975 query_range,
6976 multi_buffer_full_range,
6977 true,
6978 window,
6979 cx,
6980 ),
6981 ));
6982 }
6983 }
6984
6985 pub fn refresh_edit_prediction(
6986 &mut self,
6987 debounce: bool,
6988 user_requested: bool,
6989 window: &mut Window,
6990 cx: &mut Context<Self>,
6991 ) -> Option<()> {
6992 if DisableAiSettings::get_global(cx).disable_ai {
6993 return None;
6994 }
6995
6996 let provider = self.edit_prediction_provider()?;
6997 let cursor = self.selections.newest_anchor().head();
6998 let (buffer, cursor_buffer_position) =
6999 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7000
7001 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7002 self.discard_edit_prediction(false, cx);
7003 return None;
7004 }
7005
7006 self.update_visible_edit_prediction(window, cx);
7007
7008 if !user_requested
7009 && (!self.should_show_edit_predictions()
7010 || !self.is_focused(window)
7011 || buffer.read(cx).is_empty())
7012 {
7013 self.discard_edit_prediction(false, cx);
7014 return None;
7015 }
7016
7017 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7018 Some(())
7019 }
7020
7021 fn show_edit_predictions_in_menu(&self) -> bool {
7022 match self.edit_prediction_settings {
7023 EditPredictionSettings::Disabled => false,
7024 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7025 }
7026 }
7027
7028 pub fn edit_predictions_enabled(&self) -> bool {
7029 match self.edit_prediction_settings {
7030 EditPredictionSettings::Disabled => false,
7031 EditPredictionSettings::Enabled { .. } => true,
7032 }
7033 }
7034
7035 fn edit_prediction_requires_modifier(&self) -> bool {
7036 match self.edit_prediction_settings {
7037 EditPredictionSettings::Disabled => false,
7038 EditPredictionSettings::Enabled {
7039 preview_requires_modifier,
7040 ..
7041 } => preview_requires_modifier,
7042 }
7043 }
7044
7045 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7046 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7047 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7048 self.discard_edit_prediction(false, cx);
7049 } else {
7050 let selection = self.selections.newest_anchor();
7051 let cursor = selection.head();
7052
7053 if let Some((buffer, cursor_buffer_position)) =
7054 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7055 {
7056 self.edit_prediction_settings =
7057 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7058 }
7059 }
7060 }
7061
7062 fn edit_prediction_settings_at_position(
7063 &self,
7064 buffer: &Entity<Buffer>,
7065 buffer_position: language::Anchor,
7066 cx: &App,
7067 ) -> EditPredictionSettings {
7068 if !self.mode.is_full()
7069 || !self.show_edit_predictions_override.unwrap_or(true)
7070 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7071 {
7072 return EditPredictionSettings::Disabled;
7073 }
7074
7075 let buffer = buffer.read(cx);
7076
7077 let file = buffer.file();
7078
7079 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7080 return EditPredictionSettings::Disabled;
7081 };
7082
7083 let by_provider = matches!(
7084 self.menu_edit_predictions_policy,
7085 MenuEditPredictionsPolicy::ByProvider
7086 );
7087
7088 let show_in_menu = by_provider
7089 && self
7090 .edit_prediction_provider
7091 .as_ref()
7092 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7093
7094 let preview_requires_modifier =
7095 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7096
7097 EditPredictionSettings::Enabled {
7098 show_in_menu,
7099 preview_requires_modifier,
7100 }
7101 }
7102
7103 fn should_show_edit_predictions(&self) -> bool {
7104 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7105 }
7106
7107 pub fn edit_prediction_preview_is_active(&self) -> bool {
7108 matches!(
7109 self.edit_prediction_preview,
7110 EditPredictionPreview::Active { .. }
7111 )
7112 }
7113
7114 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7115 let cursor = self.selections.newest_anchor().head();
7116 if let Some((buffer, cursor_position)) =
7117 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7118 {
7119 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7120 } else {
7121 false
7122 }
7123 }
7124
7125 pub fn supports_minimap(&self, cx: &App) -> bool {
7126 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7127 }
7128
7129 fn edit_predictions_enabled_in_buffer(
7130 &self,
7131 buffer: &Entity<Buffer>,
7132 buffer_position: language::Anchor,
7133 cx: &App,
7134 ) -> bool {
7135 maybe!({
7136 if self.read_only(cx) {
7137 return Some(false);
7138 }
7139 let provider = self.edit_prediction_provider()?;
7140 if !provider.is_enabled(buffer, buffer_position, cx) {
7141 return Some(false);
7142 }
7143 let buffer = buffer.read(cx);
7144 let Some(file) = buffer.file() else {
7145 return Some(true);
7146 };
7147 let settings = all_language_settings(Some(file), cx);
7148 Some(settings.edit_predictions_enabled_for_file(file, cx))
7149 })
7150 .unwrap_or(false)
7151 }
7152
7153 fn cycle_edit_prediction(
7154 &mut self,
7155 direction: Direction,
7156 window: &mut Window,
7157 cx: &mut Context<Self>,
7158 ) -> Option<()> {
7159 let provider = self.edit_prediction_provider()?;
7160 let cursor = self.selections.newest_anchor().head();
7161 let (buffer, cursor_buffer_position) =
7162 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7163 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7164 return None;
7165 }
7166
7167 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7168 self.update_visible_edit_prediction(window, cx);
7169
7170 Some(())
7171 }
7172
7173 pub fn show_edit_prediction(
7174 &mut self,
7175 _: &ShowEditPrediction,
7176 window: &mut Window,
7177 cx: &mut Context<Self>,
7178 ) {
7179 if !self.has_active_edit_prediction() {
7180 self.refresh_edit_prediction(false, true, window, cx);
7181 return;
7182 }
7183
7184 self.update_visible_edit_prediction(window, cx);
7185 }
7186
7187 pub fn display_cursor_names(
7188 &mut self,
7189 _: &DisplayCursorNames,
7190 window: &mut Window,
7191 cx: &mut Context<Self>,
7192 ) {
7193 self.show_cursor_names(window, cx);
7194 }
7195
7196 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7197 self.show_cursor_names = true;
7198 cx.notify();
7199 cx.spawn_in(window, async move |this, cx| {
7200 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7201 this.update(cx, |this, cx| {
7202 this.show_cursor_names = false;
7203 cx.notify()
7204 })
7205 .ok()
7206 })
7207 .detach();
7208 }
7209
7210 pub fn next_edit_prediction(
7211 &mut self,
7212 _: &NextEditPrediction,
7213 window: &mut Window,
7214 cx: &mut Context<Self>,
7215 ) {
7216 if self.has_active_edit_prediction() {
7217 self.cycle_edit_prediction(Direction::Next, window, cx);
7218 } else {
7219 let is_copilot_disabled = self
7220 .refresh_edit_prediction(false, true, window, cx)
7221 .is_none();
7222 if is_copilot_disabled {
7223 cx.propagate();
7224 }
7225 }
7226 }
7227
7228 pub fn previous_edit_prediction(
7229 &mut self,
7230 _: &PreviousEditPrediction,
7231 window: &mut Window,
7232 cx: &mut Context<Self>,
7233 ) {
7234 if self.has_active_edit_prediction() {
7235 self.cycle_edit_prediction(Direction::Prev, window, cx);
7236 } else {
7237 let is_copilot_disabled = self
7238 .refresh_edit_prediction(false, true, window, cx)
7239 .is_none();
7240 if is_copilot_disabled {
7241 cx.propagate();
7242 }
7243 }
7244 }
7245
7246 pub fn accept_edit_prediction(
7247 &mut self,
7248 _: &AcceptEditPrediction,
7249 window: &mut Window,
7250 cx: &mut Context<Self>,
7251 ) {
7252 if self.show_edit_predictions_in_menu() {
7253 self.hide_context_menu(window, cx);
7254 }
7255
7256 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7257 return;
7258 };
7259
7260 match &active_edit_prediction.completion {
7261 EditPrediction::MoveWithin { target, .. } => {
7262 let target = *target;
7263
7264 if let Some(position_map) = &self.last_position_map {
7265 if position_map
7266 .visible_row_range
7267 .contains(&target.to_display_point(&position_map.snapshot).row())
7268 || !self.edit_prediction_requires_modifier()
7269 {
7270 self.unfold_ranges(&[target..target], true, false, cx);
7271 // Note that this is also done in vim's handler of the Tab action.
7272 self.change_selections(
7273 SelectionEffects::scroll(Autoscroll::newest()),
7274 window,
7275 cx,
7276 |selections| {
7277 selections.select_anchor_ranges([target..target]);
7278 },
7279 );
7280 self.clear_row_highlights::<EditPredictionPreview>();
7281
7282 self.edit_prediction_preview
7283 .set_previous_scroll_position(None);
7284 } else {
7285 self.edit_prediction_preview
7286 .set_previous_scroll_position(Some(
7287 position_map.snapshot.scroll_anchor,
7288 ));
7289
7290 self.highlight_rows::<EditPredictionPreview>(
7291 target..target,
7292 cx.theme().colors().editor_highlighted_line_background,
7293 RowHighlightOptions {
7294 autoscroll: true,
7295 ..Default::default()
7296 },
7297 cx,
7298 );
7299 self.request_autoscroll(Autoscroll::fit(), cx);
7300 }
7301 }
7302 }
7303 EditPrediction::MoveOutside { snapshot, target } => {
7304 if let Some(workspace) = self.workspace() {
7305 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7306 .detach_and_log_err(cx);
7307 }
7308 }
7309 EditPrediction::Edit { edits, .. } => {
7310 self.report_edit_prediction_event(
7311 active_edit_prediction.completion_id.clone(),
7312 true,
7313 cx,
7314 );
7315
7316 if let Some(provider) = self.edit_prediction_provider() {
7317 provider.accept(cx);
7318 }
7319
7320 // Store the transaction ID and selections before applying the edit
7321 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7322
7323 let snapshot = self.buffer.read(cx).snapshot(cx);
7324 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7325
7326 self.buffer.update(cx, |buffer, cx| {
7327 buffer.edit(edits.iter().cloned(), None, cx)
7328 });
7329
7330 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7331 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7332 });
7333
7334 let selections = self.selections.disjoint_anchors_arc();
7335 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7336 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7337 if has_new_transaction {
7338 self.selection_history
7339 .insert_transaction(transaction_id_now, selections);
7340 }
7341 }
7342
7343 self.update_visible_edit_prediction(window, cx);
7344 if self.active_edit_prediction.is_none() {
7345 self.refresh_edit_prediction(true, true, window, cx);
7346 }
7347
7348 cx.notify();
7349 }
7350 }
7351
7352 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7353 }
7354
7355 pub fn accept_partial_edit_prediction(
7356 &mut self,
7357 _: &AcceptPartialEditPrediction,
7358 window: &mut Window,
7359 cx: &mut Context<Self>,
7360 ) {
7361 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7362 return;
7363 };
7364 if self.selections.count() != 1 {
7365 return;
7366 }
7367
7368 match &active_edit_prediction.completion {
7369 EditPrediction::MoveWithin { target, .. } => {
7370 let target = *target;
7371 self.change_selections(
7372 SelectionEffects::scroll(Autoscroll::newest()),
7373 window,
7374 cx,
7375 |selections| {
7376 selections.select_anchor_ranges([target..target]);
7377 },
7378 );
7379 }
7380 EditPrediction::MoveOutside { snapshot, target } => {
7381 if let Some(workspace) = self.workspace() {
7382 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7383 .detach_and_log_err(cx);
7384 }
7385 }
7386 EditPrediction::Edit { edits, .. } => {
7387 self.report_edit_prediction_event(
7388 active_edit_prediction.completion_id.clone(),
7389 true,
7390 cx,
7391 );
7392
7393 // Find an insertion that starts at the cursor position.
7394 let snapshot = self.buffer.read(cx).snapshot(cx);
7395 let cursor_offset = self
7396 .selections
7397 .newest::<usize>(&self.display_snapshot(cx))
7398 .head();
7399 let insertion = edits.iter().find_map(|(range, text)| {
7400 let range = range.to_offset(&snapshot);
7401 if range.is_empty() && range.start == cursor_offset {
7402 Some(text)
7403 } else {
7404 None
7405 }
7406 });
7407
7408 if let Some(text) = insertion {
7409 let mut partial_completion = text
7410 .chars()
7411 .by_ref()
7412 .take_while(|c| c.is_alphabetic())
7413 .collect::<String>();
7414 if partial_completion.is_empty() {
7415 partial_completion = text
7416 .chars()
7417 .by_ref()
7418 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7419 .collect::<String>();
7420 }
7421
7422 cx.emit(EditorEvent::InputHandled {
7423 utf16_range_to_replace: None,
7424 text: partial_completion.clone().into(),
7425 });
7426
7427 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7428
7429 self.refresh_edit_prediction(true, true, window, cx);
7430 cx.notify();
7431 } else {
7432 self.accept_edit_prediction(&Default::default(), window, cx);
7433 }
7434 }
7435 }
7436 }
7437
7438 fn discard_edit_prediction(
7439 &mut self,
7440 should_report_edit_prediction_event: bool,
7441 cx: &mut Context<Self>,
7442 ) -> bool {
7443 if should_report_edit_prediction_event {
7444 let completion_id = self
7445 .active_edit_prediction
7446 .as_ref()
7447 .and_then(|active_completion| active_completion.completion_id.clone());
7448
7449 self.report_edit_prediction_event(completion_id, false, cx);
7450 }
7451
7452 if let Some(provider) = self.edit_prediction_provider() {
7453 provider.discard(cx);
7454 }
7455
7456 self.take_active_edit_prediction(cx)
7457 }
7458
7459 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7460 let Some(provider) = self.edit_prediction_provider() else {
7461 return;
7462 };
7463
7464 let Some((_, buffer, _)) = self
7465 .buffer
7466 .read(cx)
7467 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7468 else {
7469 return;
7470 };
7471
7472 let extension = buffer
7473 .read(cx)
7474 .file()
7475 .and_then(|file| Some(file.path().extension()?.to_string()));
7476
7477 let event_type = match accepted {
7478 true => "Edit Prediction Accepted",
7479 false => "Edit Prediction Discarded",
7480 };
7481 telemetry::event!(
7482 event_type,
7483 provider = provider.name(),
7484 prediction_id = id,
7485 suggestion_accepted = accepted,
7486 file_extension = extension,
7487 );
7488 }
7489
7490 fn open_editor_at_anchor(
7491 snapshot: &language::BufferSnapshot,
7492 target: language::Anchor,
7493 workspace: &Entity<Workspace>,
7494 window: &mut Window,
7495 cx: &mut App,
7496 ) -> Task<Result<()>> {
7497 workspace.update(cx, |workspace, cx| {
7498 let path = snapshot.file().map(|file| file.full_path(cx));
7499 let Some(path) =
7500 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7501 else {
7502 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7503 };
7504 let target = text::ToPoint::to_point(&target, snapshot);
7505 let item = workspace.open_path(path, None, true, window, cx);
7506 window.spawn(cx, async move |cx| {
7507 let Some(editor) = item.await?.downcast::<Editor>() else {
7508 return Ok(());
7509 };
7510 editor
7511 .update_in(cx, |editor, window, cx| {
7512 editor.go_to_singleton_buffer_point(target, window, cx);
7513 })
7514 .ok();
7515 anyhow::Ok(())
7516 })
7517 })
7518 }
7519
7520 pub fn has_active_edit_prediction(&self) -> bool {
7521 self.active_edit_prediction.is_some()
7522 }
7523
7524 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7525 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7526 return false;
7527 };
7528
7529 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7530 self.clear_highlights::<EditPredictionHighlight>(cx);
7531 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7532 true
7533 }
7534
7535 /// Returns true when we're displaying the edit prediction popover below the cursor
7536 /// like we are not previewing and the LSP autocomplete menu is visible
7537 /// or we are in `when_holding_modifier` mode.
7538 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7539 if self.edit_prediction_preview_is_active()
7540 || !self.show_edit_predictions_in_menu()
7541 || !self.edit_predictions_enabled()
7542 {
7543 return false;
7544 }
7545
7546 if self.has_visible_completions_menu() {
7547 return true;
7548 }
7549
7550 has_completion && self.edit_prediction_requires_modifier()
7551 }
7552
7553 fn handle_modifiers_changed(
7554 &mut self,
7555 modifiers: Modifiers,
7556 position_map: &PositionMap,
7557 window: &mut Window,
7558 cx: &mut Context<Self>,
7559 ) {
7560 if self.show_edit_predictions_in_menu() {
7561 self.update_edit_prediction_preview(&modifiers, window, cx);
7562 }
7563
7564 self.update_selection_mode(&modifiers, position_map, window, cx);
7565
7566 let mouse_position = window.mouse_position();
7567 if !position_map.text_hitbox.is_hovered(window) {
7568 return;
7569 }
7570
7571 self.update_hovered_link(
7572 position_map.point_for_position(mouse_position),
7573 &position_map.snapshot,
7574 modifiers,
7575 window,
7576 cx,
7577 )
7578 }
7579
7580 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7581 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7582 if invert {
7583 match multi_cursor_setting {
7584 MultiCursorModifier::Alt => modifiers.alt,
7585 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7586 }
7587 } else {
7588 match multi_cursor_setting {
7589 MultiCursorModifier::Alt => modifiers.secondary(),
7590 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7591 }
7592 }
7593 }
7594
7595 fn columnar_selection_mode(
7596 modifiers: &Modifiers,
7597 cx: &mut Context<Self>,
7598 ) -> Option<ColumnarMode> {
7599 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7600 if Self::multi_cursor_modifier(false, modifiers, cx) {
7601 Some(ColumnarMode::FromMouse)
7602 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7603 Some(ColumnarMode::FromSelection)
7604 } else {
7605 None
7606 }
7607 } else {
7608 None
7609 }
7610 }
7611
7612 fn update_selection_mode(
7613 &mut self,
7614 modifiers: &Modifiers,
7615 position_map: &PositionMap,
7616 window: &mut Window,
7617 cx: &mut Context<Self>,
7618 ) {
7619 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7620 return;
7621 };
7622 if self.selections.pending_anchor().is_none() {
7623 return;
7624 }
7625
7626 let mouse_position = window.mouse_position();
7627 let point_for_position = position_map.point_for_position(mouse_position);
7628 let position = point_for_position.previous_valid;
7629
7630 self.select(
7631 SelectPhase::BeginColumnar {
7632 position,
7633 reset: false,
7634 mode,
7635 goal_column: point_for_position.exact_unclipped.column(),
7636 },
7637 window,
7638 cx,
7639 );
7640 }
7641
7642 fn update_edit_prediction_preview(
7643 &mut self,
7644 modifiers: &Modifiers,
7645 window: &mut Window,
7646 cx: &mut Context<Self>,
7647 ) {
7648 let mut modifiers_held = false;
7649 if let Some(accept_keystroke) = self
7650 .accept_edit_prediction_keybind(false, window, cx)
7651 .keystroke()
7652 {
7653 modifiers_held = modifiers_held
7654 || (accept_keystroke.modifiers() == modifiers
7655 && accept_keystroke.modifiers().modified());
7656 };
7657 if let Some(accept_partial_keystroke) = self
7658 .accept_edit_prediction_keybind(true, window, cx)
7659 .keystroke()
7660 {
7661 modifiers_held = modifiers_held
7662 || (accept_partial_keystroke.modifiers() == modifiers
7663 && accept_partial_keystroke.modifiers().modified());
7664 }
7665
7666 if modifiers_held {
7667 if matches!(
7668 self.edit_prediction_preview,
7669 EditPredictionPreview::Inactive { .. }
7670 ) {
7671 self.edit_prediction_preview = EditPredictionPreview::Active {
7672 previous_scroll_position: None,
7673 since: Instant::now(),
7674 };
7675
7676 self.update_visible_edit_prediction(window, cx);
7677 cx.notify();
7678 }
7679 } else if let EditPredictionPreview::Active {
7680 previous_scroll_position,
7681 since,
7682 } = self.edit_prediction_preview
7683 {
7684 if let (Some(previous_scroll_position), Some(position_map)) =
7685 (previous_scroll_position, self.last_position_map.as_ref())
7686 {
7687 self.set_scroll_position(
7688 previous_scroll_position
7689 .scroll_position(&position_map.snapshot.display_snapshot),
7690 window,
7691 cx,
7692 );
7693 }
7694
7695 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7696 released_too_fast: since.elapsed() < Duration::from_millis(200),
7697 };
7698 self.clear_row_highlights::<EditPredictionPreview>();
7699 self.update_visible_edit_prediction(window, cx);
7700 cx.notify();
7701 }
7702 }
7703
7704 fn update_visible_edit_prediction(
7705 &mut self,
7706 _window: &mut Window,
7707 cx: &mut Context<Self>,
7708 ) -> Option<()> {
7709 if DisableAiSettings::get_global(cx).disable_ai {
7710 return None;
7711 }
7712
7713 if self.ime_transaction.is_some() {
7714 self.discard_edit_prediction(false, cx);
7715 return None;
7716 }
7717
7718 let selection = self.selections.newest_anchor();
7719 let cursor = selection.head();
7720 let multibuffer = self.buffer.read(cx).snapshot(cx);
7721 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7722 let excerpt_id = cursor.excerpt_id;
7723
7724 let show_in_menu = self.show_edit_predictions_in_menu();
7725 let completions_menu_has_precedence = !show_in_menu
7726 && (self.context_menu.borrow().is_some()
7727 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7728
7729 if completions_menu_has_precedence
7730 || !offset_selection.is_empty()
7731 || self
7732 .active_edit_prediction
7733 .as_ref()
7734 .is_some_and(|completion| {
7735 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7736 return false;
7737 };
7738 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7739 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7740 !invalidation_range.contains(&offset_selection.head())
7741 })
7742 {
7743 self.discard_edit_prediction(false, cx);
7744 return None;
7745 }
7746
7747 self.take_active_edit_prediction(cx);
7748 let Some(provider) = self.edit_prediction_provider() else {
7749 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7750 return None;
7751 };
7752
7753 let (buffer, cursor_buffer_position) =
7754 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7755
7756 self.edit_prediction_settings =
7757 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7758
7759 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7760
7761 if self.edit_prediction_indent_conflict {
7762 let cursor_point = cursor.to_point(&multibuffer);
7763
7764 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7765
7766 if let Some((_, indent)) = indents.iter().next()
7767 && indent.len == cursor_point.column
7768 {
7769 self.edit_prediction_indent_conflict = false;
7770 }
7771 }
7772
7773 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7774
7775 let (completion_id, edits, edit_preview) = match edit_prediction {
7776 edit_prediction::EditPrediction::Local {
7777 id,
7778 edits,
7779 edit_preview,
7780 } => (id, edits, edit_preview),
7781 edit_prediction::EditPrediction::Jump {
7782 id,
7783 snapshot,
7784 target,
7785 } => {
7786 self.stale_edit_prediction_in_menu = None;
7787 self.active_edit_prediction = Some(EditPredictionState {
7788 inlay_ids: vec![],
7789 completion: EditPrediction::MoveOutside { snapshot, target },
7790 completion_id: id,
7791 invalidation_range: None,
7792 });
7793 cx.notify();
7794 return Some(());
7795 }
7796 };
7797
7798 let edits = edits
7799 .into_iter()
7800 .flat_map(|(range, new_text)| {
7801 Some((
7802 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7803 new_text,
7804 ))
7805 })
7806 .collect::<Vec<_>>();
7807 if edits.is_empty() {
7808 return None;
7809 }
7810
7811 let first_edit_start = edits.first().unwrap().0.start;
7812 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7813 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7814
7815 let last_edit_end = edits.last().unwrap().0.end;
7816 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7817 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7818
7819 let cursor_row = cursor.to_point(&multibuffer).row;
7820
7821 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7822
7823 let mut inlay_ids = Vec::new();
7824 let invalidation_row_range;
7825 let move_invalidation_row_range = if cursor_row < edit_start_row {
7826 Some(cursor_row..edit_end_row)
7827 } else if cursor_row > edit_end_row {
7828 Some(edit_start_row..cursor_row)
7829 } else {
7830 None
7831 };
7832 let supports_jump = self
7833 .edit_prediction_provider
7834 .as_ref()
7835 .map(|provider| provider.provider.supports_jump_to_edit())
7836 .unwrap_or(true);
7837
7838 let is_move = supports_jump
7839 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7840 let completion = if is_move {
7841 invalidation_row_range =
7842 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7843 let target = first_edit_start;
7844 EditPrediction::MoveWithin { target, snapshot }
7845 } else {
7846 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7847 && !self.edit_predictions_hidden_for_vim_mode;
7848
7849 if show_completions_in_buffer {
7850 if edits
7851 .iter()
7852 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7853 {
7854 let mut inlays = Vec::new();
7855 for (range, new_text) in &edits {
7856 let inlay = Inlay::edit_prediction(
7857 post_inc(&mut self.next_inlay_id),
7858 range.start,
7859 new_text.as_str(),
7860 );
7861 inlay_ids.push(inlay.id);
7862 inlays.push(inlay);
7863 }
7864
7865 self.splice_inlays(&[], inlays, cx);
7866 } else {
7867 let background_color = cx.theme().status().deleted_background;
7868 self.highlight_text::<EditPredictionHighlight>(
7869 edits.iter().map(|(range, _)| range.clone()).collect(),
7870 HighlightStyle {
7871 background_color: Some(background_color),
7872 ..Default::default()
7873 },
7874 cx,
7875 );
7876 }
7877 }
7878
7879 invalidation_row_range = edit_start_row..edit_end_row;
7880
7881 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7882 if provider.show_tab_accept_marker() {
7883 EditDisplayMode::TabAccept
7884 } else {
7885 EditDisplayMode::Inline
7886 }
7887 } else {
7888 EditDisplayMode::DiffPopover
7889 };
7890
7891 EditPrediction::Edit {
7892 edits,
7893 edit_preview,
7894 display_mode,
7895 snapshot,
7896 }
7897 };
7898
7899 let invalidation_range = multibuffer
7900 .anchor_before(Point::new(invalidation_row_range.start, 0))
7901 ..multibuffer.anchor_after(Point::new(
7902 invalidation_row_range.end,
7903 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7904 ));
7905
7906 self.stale_edit_prediction_in_menu = None;
7907 self.active_edit_prediction = Some(EditPredictionState {
7908 inlay_ids,
7909 completion,
7910 completion_id,
7911 invalidation_range: Some(invalidation_range),
7912 });
7913
7914 cx.notify();
7915
7916 Some(())
7917 }
7918
7919 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7920 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7921 }
7922
7923 fn clear_tasks(&mut self) {
7924 self.tasks.clear()
7925 }
7926
7927 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7928 if self.tasks.insert(key, value).is_some() {
7929 // This case should hopefully be rare, but just in case...
7930 log::error!(
7931 "multiple different run targets found on a single line, only the last target will be rendered"
7932 )
7933 }
7934 }
7935
7936 /// Get all display points of breakpoints that will be rendered within editor
7937 ///
7938 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7939 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7940 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7941 fn active_breakpoints(
7942 &self,
7943 range: Range<DisplayRow>,
7944 window: &mut Window,
7945 cx: &mut Context<Self>,
7946 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7947 let mut breakpoint_display_points = HashMap::default();
7948
7949 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7950 return breakpoint_display_points;
7951 };
7952
7953 let snapshot = self.snapshot(window, cx);
7954
7955 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
7956 let Some(project) = self.project() else {
7957 return breakpoint_display_points;
7958 };
7959
7960 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7961 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7962
7963 for (buffer_snapshot, range, excerpt_id) in
7964 multi_buffer_snapshot.range_to_buffer_ranges(range)
7965 {
7966 let Some(buffer) = project
7967 .read(cx)
7968 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7969 else {
7970 continue;
7971 };
7972 let breakpoints = breakpoint_store.read(cx).breakpoints(
7973 &buffer,
7974 Some(
7975 buffer_snapshot.anchor_before(range.start)
7976 ..buffer_snapshot.anchor_after(range.end),
7977 ),
7978 buffer_snapshot,
7979 cx,
7980 );
7981 for (breakpoint, state) in breakpoints {
7982 let multi_buffer_anchor =
7983 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7984 let position = multi_buffer_anchor
7985 .to_point(multi_buffer_snapshot)
7986 .to_display_point(&snapshot);
7987
7988 breakpoint_display_points.insert(
7989 position.row(),
7990 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7991 );
7992 }
7993 }
7994
7995 breakpoint_display_points
7996 }
7997
7998 fn breakpoint_context_menu(
7999 &self,
8000 anchor: Anchor,
8001 window: &mut Window,
8002 cx: &mut Context<Self>,
8003 ) -> Entity<ui::ContextMenu> {
8004 let weak_editor = cx.weak_entity();
8005 let focus_handle = self.focus_handle(cx);
8006
8007 let row = self
8008 .buffer
8009 .read(cx)
8010 .snapshot(cx)
8011 .summary_for_anchor::<Point>(&anchor)
8012 .row;
8013
8014 let breakpoint = self
8015 .breakpoint_at_row(row, window, cx)
8016 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8017
8018 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8019 "Edit Log Breakpoint"
8020 } else {
8021 "Set Log Breakpoint"
8022 };
8023
8024 let condition_breakpoint_msg = if breakpoint
8025 .as_ref()
8026 .is_some_and(|bp| bp.1.condition.is_some())
8027 {
8028 "Edit Condition Breakpoint"
8029 } else {
8030 "Set Condition Breakpoint"
8031 };
8032
8033 let hit_condition_breakpoint_msg = if breakpoint
8034 .as_ref()
8035 .is_some_and(|bp| bp.1.hit_condition.is_some())
8036 {
8037 "Edit Hit Condition Breakpoint"
8038 } else {
8039 "Set Hit Condition Breakpoint"
8040 };
8041
8042 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8043 "Unset Breakpoint"
8044 } else {
8045 "Set Breakpoint"
8046 };
8047
8048 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8049
8050 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8051 BreakpointState::Enabled => Some("Disable"),
8052 BreakpointState::Disabled => Some("Enable"),
8053 });
8054
8055 let (anchor, breakpoint) =
8056 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8057
8058 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8059 menu.on_blur_subscription(Subscription::new(|| {}))
8060 .context(focus_handle)
8061 .when(run_to_cursor, |this| {
8062 let weak_editor = weak_editor.clone();
8063 this.entry("Run to cursor", None, move |window, cx| {
8064 weak_editor
8065 .update(cx, |editor, cx| {
8066 editor.change_selections(
8067 SelectionEffects::no_scroll(),
8068 window,
8069 cx,
8070 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8071 );
8072 })
8073 .ok();
8074
8075 window.dispatch_action(Box::new(RunToCursor), cx);
8076 })
8077 .separator()
8078 })
8079 .when_some(toggle_state_msg, |this, msg| {
8080 this.entry(msg, None, {
8081 let weak_editor = weak_editor.clone();
8082 let breakpoint = breakpoint.clone();
8083 move |_window, cx| {
8084 weak_editor
8085 .update(cx, |this, cx| {
8086 this.edit_breakpoint_at_anchor(
8087 anchor,
8088 breakpoint.as_ref().clone(),
8089 BreakpointEditAction::InvertState,
8090 cx,
8091 );
8092 })
8093 .log_err();
8094 }
8095 })
8096 })
8097 .entry(set_breakpoint_msg, None, {
8098 let weak_editor = weak_editor.clone();
8099 let breakpoint = breakpoint.clone();
8100 move |_window, cx| {
8101 weak_editor
8102 .update(cx, |this, cx| {
8103 this.edit_breakpoint_at_anchor(
8104 anchor,
8105 breakpoint.as_ref().clone(),
8106 BreakpointEditAction::Toggle,
8107 cx,
8108 );
8109 })
8110 .log_err();
8111 }
8112 })
8113 .entry(log_breakpoint_msg, None, {
8114 let breakpoint = breakpoint.clone();
8115 let weak_editor = weak_editor.clone();
8116 move |window, cx| {
8117 weak_editor
8118 .update(cx, |this, cx| {
8119 this.add_edit_breakpoint_block(
8120 anchor,
8121 breakpoint.as_ref(),
8122 BreakpointPromptEditAction::Log,
8123 window,
8124 cx,
8125 );
8126 })
8127 .log_err();
8128 }
8129 })
8130 .entry(condition_breakpoint_msg, None, {
8131 let breakpoint = breakpoint.clone();
8132 let weak_editor = weak_editor.clone();
8133 move |window, cx| {
8134 weak_editor
8135 .update(cx, |this, cx| {
8136 this.add_edit_breakpoint_block(
8137 anchor,
8138 breakpoint.as_ref(),
8139 BreakpointPromptEditAction::Condition,
8140 window,
8141 cx,
8142 );
8143 })
8144 .log_err();
8145 }
8146 })
8147 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8148 weak_editor
8149 .update(cx, |this, cx| {
8150 this.add_edit_breakpoint_block(
8151 anchor,
8152 breakpoint.as_ref(),
8153 BreakpointPromptEditAction::HitCondition,
8154 window,
8155 cx,
8156 );
8157 })
8158 .log_err();
8159 })
8160 })
8161 }
8162
8163 fn render_breakpoint(
8164 &self,
8165 position: Anchor,
8166 row: DisplayRow,
8167 breakpoint: &Breakpoint,
8168 state: Option<BreakpointSessionState>,
8169 cx: &mut Context<Self>,
8170 ) -> IconButton {
8171 let is_rejected = state.is_some_and(|s| !s.verified);
8172 // Is it a breakpoint that shows up when hovering over gutter?
8173 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8174 (false, false),
8175 |PhantomBreakpointIndicator {
8176 is_active,
8177 display_row,
8178 collides_with_existing_breakpoint,
8179 }| {
8180 (
8181 is_active && display_row == row,
8182 collides_with_existing_breakpoint,
8183 )
8184 },
8185 );
8186
8187 let (color, icon) = {
8188 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8189 (false, false) => ui::IconName::DebugBreakpoint,
8190 (true, false) => ui::IconName::DebugLogBreakpoint,
8191 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8192 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8193 };
8194
8195 let color = if is_phantom {
8196 Color::Hint
8197 } else if is_rejected {
8198 Color::Disabled
8199 } else {
8200 Color::Debugger
8201 };
8202
8203 (color, icon)
8204 };
8205
8206 let breakpoint = Arc::from(breakpoint.clone());
8207
8208 let alt_as_text = gpui::Keystroke {
8209 modifiers: Modifiers::secondary_key(),
8210 ..Default::default()
8211 };
8212 let primary_action_text = if breakpoint.is_disabled() {
8213 "Enable breakpoint"
8214 } else if is_phantom && !collides_with_existing {
8215 "Set breakpoint"
8216 } else {
8217 "Unset breakpoint"
8218 };
8219 let focus_handle = self.focus_handle.clone();
8220
8221 let meta = if is_rejected {
8222 SharedString::from("No executable code is associated with this line.")
8223 } else if collides_with_existing && !breakpoint.is_disabled() {
8224 SharedString::from(format!(
8225 "{alt_as_text}-click to disable,\nright-click for more options."
8226 ))
8227 } else {
8228 SharedString::from("Right-click for more options.")
8229 };
8230 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8231 .icon_size(IconSize::XSmall)
8232 .size(ui::ButtonSize::None)
8233 .when(is_rejected, |this| {
8234 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8235 })
8236 .icon_color(color)
8237 .style(ButtonStyle::Transparent)
8238 .on_click(cx.listener({
8239 move |editor, event: &ClickEvent, window, cx| {
8240 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8241 BreakpointEditAction::InvertState
8242 } else {
8243 BreakpointEditAction::Toggle
8244 };
8245
8246 window.focus(&editor.focus_handle(cx));
8247 editor.edit_breakpoint_at_anchor(
8248 position,
8249 breakpoint.as_ref().clone(),
8250 edit_action,
8251 cx,
8252 );
8253 }
8254 }))
8255 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8256 editor.set_breakpoint_context_menu(
8257 row,
8258 Some(position),
8259 event.position(),
8260 window,
8261 cx,
8262 );
8263 }))
8264 .tooltip(move |_window, cx| {
8265 Tooltip::with_meta_in(
8266 primary_action_text,
8267 Some(&ToggleBreakpoint),
8268 meta.clone(),
8269 &focus_handle,
8270 cx,
8271 )
8272 })
8273 }
8274
8275 fn build_tasks_context(
8276 project: &Entity<Project>,
8277 buffer: &Entity<Buffer>,
8278 buffer_row: u32,
8279 tasks: &Arc<RunnableTasks>,
8280 cx: &mut Context<Self>,
8281 ) -> Task<Option<task::TaskContext>> {
8282 let position = Point::new(buffer_row, tasks.column);
8283 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8284 let location = Location {
8285 buffer: buffer.clone(),
8286 range: range_start..range_start,
8287 };
8288 // Fill in the environmental variables from the tree-sitter captures
8289 let mut captured_task_variables = TaskVariables::default();
8290 for (capture_name, value) in tasks.extra_variables.clone() {
8291 captured_task_variables.insert(
8292 task::VariableName::Custom(capture_name.into()),
8293 value.clone(),
8294 );
8295 }
8296 project.update(cx, |project, cx| {
8297 project.task_store().update(cx, |task_store, cx| {
8298 task_store.task_context_for_location(captured_task_variables, location, cx)
8299 })
8300 })
8301 }
8302
8303 pub fn spawn_nearest_task(
8304 &mut self,
8305 action: &SpawnNearestTask,
8306 window: &mut Window,
8307 cx: &mut Context<Self>,
8308 ) {
8309 let Some((workspace, _)) = self.workspace.clone() else {
8310 return;
8311 };
8312 let Some(project) = self.project.clone() else {
8313 return;
8314 };
8315
8316 // Try to find a closest, enclosing node using tree-sitter that has a task
8317 let Some((buffer, buffer_row, tasks)) = self
8318 .find_enclosing_node_task(cx)
8319 // Or find the task that's closest in row-distance.
8320 .or_else(|| self.find_closest_task(cx))
8321 else {
8322 return;
8323 };
8324
8325 let reveal_strategy = action.reveal;
8326 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8327 cx.spawn_in(window, async move |_, cx| {
8328 let context = task_context.await?;
8329 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8330
8331 let resolved = &mut resolved_task.resolved;
8332 resolved.reveal = reveal_strategy;
8333
8334 workspace
8335 .update_in(cx, |workspace, window, cx| {
8336 workspace.schedule_resolved_task(
8337 task_source_kind,
8338 resolved_task,
8339 false,
8340 window,
8341 cx,
8342 );
8343 })
8344 .ok()
8345 })
8346 .detach();
8347 }
8348
8349 fn find_closest_task(
8350 &mut self,
8351 cx: &mut Context<Self>,
8352 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8353 let cursor_row = self
8354 .selections
8355 .newest_adjusted(&self.display_snapshot(cx))
8356 .head()
8357 .row;
8358
8359 let ((buffer_id, row), tasks) = self
8360 .tasks
8361 .iter()
8362 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8363
8364 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8365 let tasks = Arc::new(tasks.to_owned());
8366 Some((buffer, *row, tasks))
8367 }
8368
8369 fn find_enclosing_node_task(
8370 &mut self,
8371 cx: &mut Context<Self>,
8372 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8373 let snapshot = self.buffer.read(cx).snapshot(cx);
8374 let offset = self
8375 .selections
8376 .newest::<usize>(&self.display_snapshot(cx))
8377 .head();
8378 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8379 let buffer_id = excerpt.buffer().remote_id();
8380
8381 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8382 let mut cursor = layer.node().walk();
8383
8384 while cursor.goto_first_child_for_byte(offset).is_some() {
8385 if cursor.node().end_byte() == offset {
8386 cursor.goto_next_sibling();
8387 }
8388 }
8389
8390 // Ascend to the smallest ancestor that contains the range and has a task.
8391 loop {
8392 let node = cursor.node();
8393 let node_range = node.byte_range();
8394 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8395
8396 // Check if this node contains our offset
8397 if node_range.start <= offset && node_range.end >= offset {
8398 // If it contains offset, check for task
8399 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8400 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8401 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8402 }
8403 }
8404
8405 if !cursor.goto_parent() {
8406 break;
8407 }
8408 }
8409 None
8410 }
8411
8412 fn render_run_indicator(
8413 &self,
8414 _style: &EditorStyle,
8415 is_active: bool,
8416 row: DisplayRow,
8417 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8418 cx: &mut Context<Self>,
8419 ) -> IconButton {
8420 let color = Color::Muted;
8421 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8422
8423 IconButton::new(
8424 ("run_indicator", row.0 as usize),
8425 ui::IconName::PlayOutlined,
8426 )
8427 .shape(ui::IconButtonShape::Square)
8428 .icon_size(IconSize::XSmall)
8429 .icon_color(color)
8430 .toggle_state(is_active)
8431 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8432 let quick_launch = match e {
8433 ClickEvent::Keyboard(_) => true,
8434 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8435 };
8436
8437 window.focus(&editor.focus_handle(cx));
8438 editor.toggle_code_actions(
8439 &ToggleCodeActions {
8440 deployed_from: Some(CodeActionSource::RunMenu(row)),
8441 quick_launch,
8442 },
8443 window,
8444 cx,
8445 );
8446 }))
8447 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8448 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8449 }))
8450 }
8451
8452 pub fn context_menu_visible(&self) -> bool {
8453 !self.edit_prediction_preview_is_active()
8454 && self
8455 .context_menu
8456 .borrow()
8457 .as_ref()
8458 .is_some_and(|menu| menu.visible())
8459 }
8460
8461 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8462 self.context_menu
8463 .borrow()
8464 .as_ref()
8465 .map(|menu| menu.origin())
8466 }
8467
8468 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8469 self.context_menu_options = Some(options);
8470 }
8471
8472 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8473 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8474
8475 fn render_edit_prediction_popover(
8476 &mut self,
8477 text_bounds: &Bounds<Pixels>,
8478 content_origin: gpui::Point<Pixels>,
8479 right_margin: Pixels,
8480 editor_snapshot: &EditorSnapshot,
8481 visible_row_range: Range<DisplayRow>,
8482 scroll_top: ScrollOffset,
8483 scroll_bottom: ScrollOffset,
8484 line_layouts: &[LineWithInvisibles],
8485 line_height: Pixels,
8486 scroll_position: gpui::Point<ScrollOffset>,
8487 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8488 newest_selection_head: Option<DisplayPoint>,
8489 editor_width: Pixels,
8490 style: &EditorStyle,
8491 window: &mut Window,
8492 cx: &mut App,
8493 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8494 if self.mode().is_minimap() {
8495 return None;
8496 }
8497 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8498
8499 if self.edit_prediction_visible_in_cursor_popover(true) {
8500 return None;
8501 }
8502
8503 match &active_edit_prediction.completion {
8504 EditPrediction::MoveWithin { target, .. } => {
8505 let target_display_point = target.to_display_point(editor_snapshot);
8506
8507 if self.edit_prediction_requires_modifier() {
8508 if !self.edit_prediction_preview_is_active() {
8509 return None;
8510 }
8511
8512 self.render_edit_prediction_modifier_jump_popover(
8513 text_bounds,
8514 content_origin,
8515 visible_row_range,
8516 line_layouts,
8517 line_height,
8518 scroll_pixel_position,
8519 newest_selection_head,
8520 target_display_point,
8521 window,
8522 cx,
8523 )
8524 } else {
8525 self.render_edit_prediction_eager_jump_popover(
8526 text_bounds,
8527 content_origin,
8528 editor_snapshot,
8529 visible_row_range,
8530 scroll_top,
8531 scroll_bottom,
8532 line_height,
8533 scroll_pixel_position,
8534 target_display_point,
8535 editor_width,
8536 window,
8537 cx,
8538 )
8539 }
8540 }
8541 EditPrediction::Edit {
8542 display_mode: EditDisplayMode::Inline,
8543 ..
8544 } => None,
8545 EditPrediction::Edit {
8546 display_mode: EditDisplayMode::TabAccept,
8547 edits,
8548 ..
8549 } => {
8550 let range = &edits.first()?.0;
8551 let target_display_point = range.end.to_display_point(editor_snapshot);
8552
8553 self.render_edit_prediction_end_of_line_popover(
8554 "Accept",
8555 editor_snapshot,
8556 visible_row_range,
8557 target_display_point,
8558 line_height,
8559 scroll_pixel_position,
8560 content_origin,
8561 editor_width,
8562 window,
8563 cx,
8564 )
8565 }
8566 EditPrediction::Edit {
8567 edits,
8568 edit_preview,
8569 display_mode: EditDisplayMode::DiffPopover,
8570 snapshot,
8571 } => self.render_edit_prediction_diff_popover(
8572 text_bounds,
8573 content_origin,
8574 right_margin,
8575 editor_snapshot,
8576 visible_row_range,
8577 line_layouts,
8578 line_height,
8579 scroll_position,
8580 scroll_pixel_position,
8581 newest_selection_head,
8582 editor_width,
8583 style,
8584 edits,
8585 edit_preview,
8586 snapshot,
8587 window,
8588 cx,
8589 ),
8590 EditPrediction::MoveOutside { snapshot, .. } => {
8591 let file_name = snapshot
8592 .file()
8593 .map(|file| file.file_name(cx))
8594 .unwrap_or("untitled");
8595 let mut element = self
8596 .render_edit_prediction_line_popover(
8597 format!("Jump to {file_name}"),
8598 Some(IconName::ZedPredict),
8599 window,
8600 cx,
8601 )
8602 .into_any();
8603
8604 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8605 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8606 let origin_y = text_bounds.size.height - size.height - px(30.);
8607 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8608 element.prepaint_at(origin, window, cx);
8609
8610 Some((element, origin))
8611 }
8612 }
8613 }
8614
8615 fn render_edit_prediction_modifier_jump_popover(
8616 &mut self,
8617 text_bounds: &Bounds<Pixels>,
8618 content_origin: gpui::Point<Pixels>,
8619 visible_row_range: Range<DisplayRow>,
8620 line_layouts: &[LineWithInvisibles],
8621 line_height: Pixels,
8622 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8623 newest_selection_head: Option<DisplayPoint>,
8624 target_display_point: DisplayPoint,
8625 window: &mut Window,
8626 cx: &mut App,
8627 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8628 let scrolled_content_origin =
8629 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8630
8631 const SCROLL_PADDING_Y: Pixels = px(12.);
8632
8633 if target_display_point.row() < visible_row_range.start {
8634 return self.render_edit_prediction_scroll_popover(
8635 |_| SCROLL_PADDING_Y,
8636 IconName::ArrowUp,
8637 visible_row_range,
8638 line_layouts,
8639 newest_selection_head,
8640 scrolled_content_origin,
8641 window,
8642 cx,
8643 );
8644 } else if target_display_point.row() >= visible_row_range.end {
8645 return self.render_edit_prediction_scroll_popover(
8646 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8647 IconName::ArrowDown,
8648 visible_row_range,
8649 line_layouts,
8650 newest_selection_head,
8651 scrolled_content_origin,
8652 window,
8653 cx,
8654 );
8655 }
8656
8657 const POLE_WIDTH: Pixels = px(2.);
8658
8659 let line_layout =
8660 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8661 let target_column = target_display_point.column() as usize;
8662
8663 let target_x = line_layout.x_for_index(target_column);
8664 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8665 - scroll_pixel_position.y;
8666
8667 let flag_on_right = target_x < text_bounds.size.width / 2.;
8668
8669 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8670 border_color.l += 0.001;
8671
8672 let mut element = v_flex()
8673 .items_end()
8674 .when(flag_on_right, |el| el.items_start())
8675 .child(if flag_on_right {
8676 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8677 .rounded_bl(px(0.))
8678 .rounded_tl(px(0.))
8679 .border_l_2()
8680 .border_color(border_color)
8681 } else {
8682 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8683 .rounded_br(px(0.))
8684 .rounded_tr(px(0.))
8685 .border_r_2()
8686 .border_color(border_color)
8687 })
8688 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8689 .into_any();
8690
8691 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8692
8693 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8694 - point(
8695 if flag_on_right {
8696 POLE_WIDTH
8697 } else {
8698 size.width - POLE_WIDTH
8699 },
8700 size.height - line_height,
8701 );
8702
8703 origin.x = origin.x.max(content_origin.x);
8704
8705 element.prepaint_at(origin, window, cx);
8706
8707 Some((element, origin))
8708 }
8709
8710 fn render_edit_prediction_scroll_popover(
8711 &mut self,
8712 to_y: impl Fn(Size<Pixels>) -> Pixels,
8713 scroll_icon: IconName,
8714 visible_row_range: Range<DisplayRow>,
8715 line_layouts: &[LineWithInvisibles],
8716 newest_selection_head: Option<DisplayPoint>,
8717 scrolled_content_origin: gpui::Point<Pixels>,
8718 window: &mut Window,
8719 cx: &mut App,
8720 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8721 let mut element = self
8722 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8723 .into_any();
8724
8725 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8726
8727 let cursor = newest_selection_head?;
8728 let cursor_row_layout =
8729 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8730 let cursor_column = cursor.column() as usize;
8731
8732 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8733
8734 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8735
8736 element.prepaint_at(origin, window, cx);
8737 Some((element, origin))
8738 }
8739
8740 fn render_edit_prediction_eager_jump_popover(
8741 &mut self,
8742 text_bounds: &Bounds<Pixels>,
8743 content_origin: gpui::Point<Pixels>,
8744 editor_snapshot: &EditorSnapshot,
8745 visible_row_range: Range<DisplayRow>,
8746 scroll_top: ScrollOffset,
8747 scroll_bottom: ScrollOffset,
8748 line_height: Pixels,
8749 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8750 target_display_point: DisplayPoint,
8751 editor_width: Pixels,
8752 window: &mut Window,
8753 cx: &mut App,
8754 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8755 if target_display_point.row().as_f64() < scroll_top {
8756 let mut element = self
8757 .render_edit_prediction_line_popover(
8758 "Jump to Edit",
8759 Some(IconName::ArrowUp),
8760 window,
8761 cx,
8762 )
8763 .into_any();
8764
8765 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8766 let offset = point(
8767 (text_bounds.size.width - size.width) / 2.,
8768 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8769 );
8770
8771 let origin = text_bounds.origin + offset;
8772 element.prepaint_at(origin, window, cx);
8773 Some((element, origin))
8774 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8775 let mut element = self
8776 .render_edit_prediction_line_popover(
8777 "Jump to Edit",
8778 Some(IconName::ArrowDown),
8779 window,
8780 cx,
8781 )
8782 .into_any();
8783
8784 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8785 let offset = point(
8786 (text_bounds.size.width - size.width) / 2.,
8787 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8788 );
8789
8790 let origin = text_bounds.origin + offset;
8791 element.prepaint_at(origin, window, cx);
8792 Some((element, origin))
8793 } else {
8794 self.render_edit_prediction_end_of_line_popover(
8795 "Jump to Edit",
8796 editor_snapshot,
8797 visible_row_range,
8798 target_display_point,
8799 line_height,
8800 scroll_pixel_position,
8801 content_origin,
8802 editor_width,
8803 window,
8804 cx,
8805 )
8806 }
8807 }
8808
8809 fn render_edit_prediction_end_of_line_popover(
8810 self: &mut Editor,
8811 label: &'static str,
8812 editor_snapshot: &EditorSnapshot,
8813 visible_row_range: Range<DisplayRow>,
8814 target_display_point: DisplayPoint,
8815 line_height: Pixels,
8816 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8817 content_origin: gpui::Point<Pixels>,
8818 editor_width: Pixels,
8819 window: &mut Window,
8820 cx: &mut App,
8821 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8822 let target_line_end = DisplayPoint::new(
8823 target_display_point.row(),
8824 editor_snapshot.line_len(target_display_point.row()),
8825 );
8826
8827 let mut element = self
8828 .render_edit_prediction_line_popover(label, None, window, cx)
8829 .into_any();
8830
8831 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8832
8833 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8834
8835 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8836 let mut origin = start_point
8837 + line_origin
8838 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8839 origin.x = origin.x.max(content_origin.x);
8840
8841 let max_x = content_origin.x + editor_width - size.width;
8842
8843 if origin.x > max_x {
8844 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8845
8846 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8847 origin.y += offset;
8848 IconName::ArrowUp
8849 } else {
8850 origin.y -= offset;
8851 IconName::ArrowDown
8852 };
8853
8854 element = self
8855 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8856 .into_any();
8857
8858 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8859
8860 origin.x = content_origin.x + editor_width - size.width - px(2.);
8861 }
8862
8863 element.prepaint_at(origin, window, cx);
8864 Some((element, origin))
8865 }
8866
8867 fn render_edit_prediction_diff_popover(
8868 self: &Editor,
8869 text_bounds: &Bounds<Pixels>,
8870 content_origin: gpui::Point<Pixels>,
8871 right_margin: Pixels,
8872 editor_snapshot: &EditorSnapshot,
8873 visible_row_range: Range<DisplayRow>,
8874 line_layouts: &[LineWithInvisibles],
8875 line_height: Pixels,
8876 scroll_position: gpui::Point<ScrollOffset>,
8877 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8878 newest_selection_head: Option<DisplayPoint>,
8879 editor_width: Pixels,
8880 style: &EditorStyle,
8881 edits: &Vec<(Range<Anchor>, String)>,
8882 edit_preview: &Option<language::EditPreview>,
8883 snapshot: &language::BufferSnapshot,
8884 window: &mut Window,
8885 cx: &mut App,
8886 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8887 let edit_start = edits
8888 .first()
8889 .unwrap()
8890 .0
8891 .start
8892 .to_display_point(editor_snapshot);
8893 let edit_end = edits
8894 .last()
8895 .unwrap()
8896 .0
8897 .end
8898 .to_display_point(editor_snapshot);
8899
8900 let is_visible = visible_row_range.contains(&edit_start.row())
8901 || visible_row_range.contains(&edit_end.row());
8902 if !is_visible {
8903 return None;
8904 }
8905
8906 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8907 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8908 } else {
8909 // Fallback for providers without edit_preview
8910 crate::edit_prediction_fallback_text(edits, cx)
8911 };
8912
8913 let styled_text = highlighted_edits.to_styled_text(&style.text);
8914 let line_count = highlighted_edits.text.lines().count();
8915
8916 const BORDER_WIDTH: Pixels = px(1.);
8917
8918 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8919 let has_keybind = keybind.is_some();
8920
8921 let mut element = h_flex()
8922 .items_start()
8923 .child(
8924 h_flex()
8925 .bg(cx.theme().colors().editor_background)
8926 .border(BORDER_WIDTH)
8927 .shadow_xs()
8928 .border_color(cx.theme().colors().border)
8929 .rounded_l_lg()
8930 .when(line_count > 1, |el| el.rounded_br_lg())
8931 .pr_1()
8932 .child(styled_text),
8933 )
8934 .child(
8935 h_flex()
8936 .h(line_height + BORDER_WIDTH * 2.)
8937 .px_1p5()
8938 .gap_1()
8939 // Workaround: For some reason, there's a gap if we don't do this
8940 .ml(-BORDER_WIDTH)
8941 .shadow(vec![gpui::BoxShadow {
8942 color: gpui::black().opacity(0.05),
8943 offset: point(px(1.), px(1.)),
8944 blur_radius: px(2.),
8945 spread_radius: px(0.),
8946 }])
8947 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8948 .border(BORDER_WIDTH)
8949 .border_color(cx.theme().colors().border)
8950 .rounded_r_lg()
8951 .id("edit_prediction_diff_popover_keybind")
8952 .when(!has_keybind, |el| {
8953 let status_colors = cx.theme().status();
8954
8955 el.bg(status_colors.error_background)
8956 .border_color(status_colors.error.opacity(0.6))
8957 .child(Icon::new(IconName::Info).color(Color::Error))
8958 .cursor_default()
8959 .hoverable_tooltip(move |_window, cx| {
8960 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8961 })
8962 })
8963 .children(keybind),
8964 )
8965 .into_any();
8966
8967 let longest_row =
8968 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8969 let longest_line_width = if visible_row_range.contains(&longest_row) {
8970 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8971 } else {
8972 layout_line(
8973 longest_row,
8974 editor_snapshot,
8975 style,
8976 editor_width,
8977 |_| false,
8978 window,
8979 cx,
8980 )
8981 .width
8982 };
8983
8984 let viewport_bounds =
8985 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8986 right: -right_margin,
8987 ..Default::default()
8988 });
8989
8990 let x_after_longest = Pixels::from(
8991 ScrollPixelOffset::from(
8992 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8993 ) - scroll_pixel_position.x,
8994 );
8995
8996 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8997
8998 // Fully visible if it can be displayed within the window (allow overlapping other
8999 // panes). However, this is only allowed if the popover starts within text_bounds.
9000 let can_position_to_the_right = x_after_longest < text_bounds.right()
9001 && x_after_longest + element_bounds.width < viewport_bounds.right();
9002
9003 let mut origin = if can_position_to_the_right {
9004 point(
9005 x_after_longest,
9006 text_bounds.origin.y
9007 + Pixels::from(
9008 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9009 - scroll_pixel_position.y,
9010 ),
9011 )
9012 } else {
9013 let cursor_row = newest_selection_head.map(|head| head.row());
9014 let above_edit = edit_start
9015 .row()
9016 .0
9017 .checked_sub(line_count as u32)
9018 .map(DisplayRow);
9019 let below_edit = Some(edit_end.row() + 1);
9020 let above_cursor =
9021 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9022 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9023
9024 // Place the edit popover adjacent to the edit if there is a location
9025 // available that is onscreen and does not obscure the cursor. Otherwise,
9026 // place it adjacent to the cursor.
9027 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9028 .into_iter()
9029 .flatten()
9030 .find(|&start_row| {
9031 let end_row = start_row + line_count as u32;
9032 visible_row_range.contains(&start_row)
9033 && visible_row_range.contains(&end_row)
9034 && cursor_row
9035 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9036 })?;
9037
9038 content_origin
9039 + point(
9040 Pixels::from(-scroll_pixel_position.x),
9041 Pixels::from(
9042 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9043 ),
9044 )
9045 };
9046
9047 origin.x -= BORDER_WIDTH;
9048
9049 window.defer_draw(element, origin, 1);
9050
9051 // Do not return an element, since it will already be drawn due to defer_draw.
9052 None
9053 }
9054
9055 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9056 px(30.)
9057 }
9058
9059 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9060 if self.read_only(cx) {
9061 cx.theme().players().read_only()
9062 } else {
9063 self.style.as_ref().unwrap().local_player
9064 }
9065 }
9066
9067 fn render_edit_prediction_accept_keybind(
9068 &self,
9069 window: &mut Window,
9070 cx: &mut App,
9071 ) -> Option<AnyElement> {
9072 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9073 let accept_keystroke = accept_binding.keystroke()?;
9074
9075 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9076
9077 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9078 Color::Accent
9079 } else {
9080 Color::Muted
9081 };
9082
9083 h_flex()
9084 .px_0p5()
9085 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9086 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9087 .text_size(TextSize::XSmall.rems(cx))
9088 .child(h_flex().children(ui::render_modifiers(
9089 accept_keystroke.modifiers(),
9090 PlatformStyle::platform(),
9091 Some(modifiers_color),
9092 Some(IconSize::XSmall.rems().into()),
9093 true,
9094 )))
9095 .when(is_platform_style_mac, |parent| {
9096 parent.child(accept_keystroke.key().to_string())
9097 })
9098 .when(!is_platform_style_mac, |parent| {
9099 parent.child(
9100 Key::new(
9101 util::capitalize(accept_keystroke.key()),
9102 Some(Color::Default),
9103 )
9104 .size(Some(IconSize::XSmall.rems().into())),
9105 )
9106 })
9107 .into_any()
9108 .into()
9109 }
9110
9111 fn render_edit_prediction_line_popover(
9112 &self,
9113 label: impl Into<SharedString>,
9114 icon: Option<IconName>,
9115 window: &mut Window,
9116 cx: &mut App,
9117 ) -> Stateful<Div> {
9118 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9119
9120 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9121 let has_keybind = keybind.is_some();
9122
9123 h_flex()
9124 .id("ep-line-popover")
9125 .py_0p5()
9126 .pl_1()
9127 .pr(padding_right)
9128 .gap_1()
9129 .rounded_md()
9130 .border_1()
9131 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9132 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9133 .shadow_xs()
9134 .when(!has_keybind, |el| {
9135 let status_colors = cx.theme().status();
9136
9137 el.bg(status_colors.error_background)
9138 .border_color(status_colors.error.opacity(0.6))
9139 .pl_2()
9140 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9141 .cursor_default()
9142 .hoverable_tooltip(move |_window, cx| {
9143 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9144 })
9145 })
9146 .children(keybind)
9147 .child(
9148 Label::new(label)
9149 .size(LabelSize::Small)
9150 .when(!has_keybind, |el| {
9151 el.color(cx.theme().status().error.into()).strikethrough()
9152 }),
9153 )
9154 .when(!has_keybind, |el| {
9155 el.child(
9156 h_flex().ml_1().child(
9157 Icon::new(IconName::Info)
9158 .size(IconSize::Small)
9159 .color(cx.theme().status().error.into()),
9160 ),
9161 )
9162 })
9163 .when_some(icon, |element, icon| {
9164 element.child(
9165 div()
9166 .mt(px(1.5))
9167 .child(Icon::new(icon).size(IconSize::Small)),
9168 )
9169 })
9170 }
9171
9172 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9173 let accent_color = cx.theme().colors().text_accent;
9174 let editor_bg_color = cx.theme().colors().editor_background;
9175 editor_bg_color.blend(accent_color.opacity(0.1))
9176 }
9177
9178 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9179 let accent_color = cx.theme().colors().text_accent;
9180 let editor_bg_color = cx.theme().colors().editor_background;
9181 editor_bg_color.blend(accent_color.opacity(0.6))
9182 }
9183 fn get_prediction_provider_icon_name(
9184 provider: &Option<RegisteredEditPredictionProvider>,
9185 ) -> IconName {
9186 match provider {
9187 Some(provider) => match provider.provider.name() {
9188 "copilot" => IconName::Copilot,
9189 "supermaven" => IconName::Supermaven,
9190 _ => IconName::ZedPredict,
9191 },
9192 None => IconName::ZedPredict,
9193 }
9194 }
9195
9196 fn render_edit_prediction_cursor_popover(
9197 &self,
9198 min_width: Pixels,
9199 max_width: Pixels,
9200 cursor_point: Point,
9201 style: &EditorStyle,
9202 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9203 _window: &Window,
9204 cx: &mut Context<Editor>,
9205 ) -> Option<AnyElement> {
9206 let provider = self.edit_prediction_provider.as_ref()?;
9207 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9208
9209 let is_refreshing = provider.provider.is_refreshing(cx);
9210
9211 fn pending_completion_container(icon: IconName) -> Div {
9212 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9213 }
9214
9215 let completion = match &self.active_edit_prediction {
9216 Some(prediction) => {
9217 if !self.has_visible_completions_menu() {
9218 const RADIUS: Pixels = px(6.);
9219 const BORDER_WIDTH: Pixels = px(1.);
9220
9221 return Some(
9222 h_flex()
9223 .elevation_2(cx)
9224 .border(BORDER_WIDTH)
9225 .border_color(cx.theme().colors().border)
9226 .when(accept_keystroke.is_none(), |el| {
9227 el.border_color(cx.theme().status().error)
9228 })
9229 .rounded(RADIUS)
9230 .rounded_tl(px(0.))
9231 .overflow_hidden()
9232 .child(div().px_1p5().child(match &prediction.completion {
9233 EditPrediction::MoveWithin { target, snapshot } => {
9234 use text::ToPoint as _;
9235 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9236 {
9237 Icon::new(IconName::ZedPredictDown)
9238 } else {
9239 Icon::new(IconName::ZedPredictUp)
9240 }
9241 }
9242 EditPrediction::MoveOutside { .. } => {
9243 // TODO [zeta2] custom icon for external jump?
9244 Icon::new(provider_icon)
9245 }
9246 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9247 }))
9248 .child(
9249 h_flex()
9250 .gap_1()
9251 .py_1()
9252 .px_2()
9253 .rounded_r(RADIUS - BORDER_WIDTH)
9254 .border_l_1()
9255 .border_color(cx.theme().colors().border)
9256 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9257 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9258 el.child(
9259 Label::new("Hold")
9260 .size(LabelSize::Small)
9261 .when(accept_keystroke.is_none(), |el| {
9262 el.strikethrough()
9263 })
9264 .line_height_style(LineHeightStyle::UiLabel),
9265 )
9266 })
9267 .id("edit_prediction_cursor_popover_keybind")
9268 .when(accept_keystroke.is_none(), |el| {
9269 let status_colors = cx.theme().status();
9270
9271 el.bg(status_colors.error_background)
9272 .border_color(status_colors.error.opacity(0.6))
9273 .child(Icon::new(IconName::Info).color(Color::Error))
9274 .cursor_default()
9275 .hoverable_tooltip(move |_window, cx| {
9276 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9277 .into()
9278 })
9279 })
9280 .when_some(
9281 accept_keystroke.as_ref(),
9282 |el, accept_keystroke| {
9283 el.child(h_flex().children(ui::render_modifiers(
9284 accept_keystroke.modifiers(),
9285 PlatformStyle::platform(),
9286 Some(Color::Default),
9287 Some(IconSize::XSmall.rems().into()),
9288 false,
9289 )))
9290 },
9291 ),
9292 )
9293 .into_any(),
9294 );
9295 }
9296
9297 self.render_edit_prediction_cursor_popover_preview(
9298 prediction,
9299 cursor_point,
9300 style,
9301 cx,
9302 )?
9303 }
9304
9305 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9306 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9307 stale_completion,
9308 cursor_point,
9309 style,
9310 cx,
9311 )?,
9312
9313 None => pending_completion_container(provider_icon)
9314 .child(Label::new("...").size(LabelSize::Small)),
9315 },
9316
9317 None => pending_completion_container(provider_icon)
9318 .child(Label::new("...").size(LabelSize::Small)),
9319 };
9320
9321 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9322 completion
9323 .with_animation(
9324 "loading-completion",
9325 Animation::new(Duration::from_secs(2))
9326 .repeat()
9327 .with_easing(pulsating_between(0.4, 0.8)),
9328 |label, delta| label.opacity(delta),
9329 )
9330 .into_any_element()
9331 } else {
9332 completion.into_any_element()
9333 };
9334
9335 let has_completion = self.active_edit_prediction.is_some();
9336
9337 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9338 Some(
9339 h_flex()
9340 .min_w(min_width)
9341 .max_w(max_width)
9342 .flex_1()
9343 .elevation_2(cx)
9344 .border_color(cx.theme().colors().border)
9345 .child(
9346 div()
9347 .flex_1()
9348 .py_1()
9349 .px_2()
9350 .overflow_hidden()
9351 .child(completion),
9352 )
9353 .when_some(accept_keystroke, |el, accept_keystroke| {
9354 if !accept_keystroke.modifiers().modified() {
9355 return el;
9356 }
9357
9358 el.child(
9359 h_flex()
9360 .h_full()
9361 .border_l_1()
9362 .rounded_r_lg()
9363 .border_color(cx.theme().colors().border)
9364 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9365 .gap_1()
9366 .py_1()
9367 .px_2()
9368 .child(
9369 h_flex()
9370 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9371 .when(is_platform_style_mac, |parent| parent.gap_1())
9372 .child(h_flex().children(ui::render_modifiers(
9373 accept_keystroke.modifiers(),
9374 PlatformStyle::platform(),
9375 Some(if !has_completion {
9376 Color::Muted
9377 } else {
9378 Color::Default
9379 }),
9380 None,
9381 false,
9382 ))),
9383 )
9384 .child(Label::new("Preview").into_any_element())
9385 .opacity(if has_completion { 1.0 } else { 0.4 }),
9386 )
9387 })
9388 .into_any(),
9389 )
9390 }
9391
9392 fn render_edit_prediction_cursor_popover_preview(
9393 &self,
9394 completion: &EditPredictionState,
9395 cursor_point: Point,
9396 style: &EditorStyle,
9397 cx: &mut Context<Editor>,
9398 ) -> Option<Div> {
9399 use text::ToPoint as _;
9400
9401 fn render_relative_row_jump(
9402 prefix: impl Into<String>,
9403 current_row: u32,
9404 target_row: u32,
9405 ) -> Div {
9406 let (row_diff, arrow) = if target_row < current_row {
9407 (current_row - target_row, IconName::ArrowUp)
9408 } else {
9409 (target_row - current_row, IconName::ArrowDown)
9410 };
9411
9412 h_flex()
9413 .child(
9414 Label::new(format!("{}{}", prefix.into(), row_diff))
9415 .color(Color::Muted)
9416 .size(LabelSize::Small),
9417 )
9418 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9419 }
9420
9421 let supports_jump = self
9422 .edit_prediction_provider
9423 .as_ref()
9424 .map(|provider| provider.provider.supports_jump_to_edit())
9425 .unwrap_or(true);
9426
9427 match &completion.completion {
9428 EditPrediction::MoveWithin {
9429 target, snapshot, ..
9430 } => {
9431 if !supports_jump {
9432 return None;
9433 }
9434
9435 Some(
9436 h_flex()
9437 .px_2()
9438 .gap_2()
9439 .flex_1()
9440 .child(
9441 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9442 Icon::new(IconName::ZedPredictDown)
9443 } else {
9444 Icon::new(IconName::ZedPredictUp)
9445 },
9446 )
9447 .child(Label::new("Jump to Edit")),
9448 )
9449 }
9450 EditPrediction::MoveOutside { snapshot, .. } => {
9451 let file_name = snapshot
9452 .file()
9453 .map(|file| file.file_name(cx))
9454 .unwrap_or("untitled");
9455 Some(
9456 h_flex()
9457 .px_2()
9458 .gap_2()
9459 .flex_1()
9460 .child(Icon::new(IconName::ZedPredict))
9461 .child(Label::new(format!("Jump to {file_name}"))),
9462 )
9463 }
9464 EditPrediction::Edit {
9465 edits,
9466 edit_preview,
9467 snapshot,
9468 display_mode: _,
9469 } => {
9470 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9471
9472 let (highlighted_edits, has_more_lines) =
9473 if let Some(edit_preview) = edit_preview.as_ref() {
9474 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9475 .first_line_preview()
9476 } else {
9477 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9478 };
9479
9480 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9481 .with_default_highlights(&style.text, highlighted_edits.highlights);
9482
9483 let preview = h_flex()
9484 .gap_1()
9485 .min_w_16()
9486 .child(styled_text)
9487 .when(has_more_lines, |parent| parent.child("…"));
9488
9489 let left = if supports_jump && first_edit_row != cursor_point.row {
9490 render_relative_row_jump("", cursor_point.row, first_edit_row)
9491 .into_any_element()
9492 } else {
9493 let icon_name =
9494 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9495 Icon::new(icon_name).into_any_element()
9496 };
9497
9498 Some(
9499 h_flex()
9500 .h_full()
9501 .flex_1()
9502 .gap_2()
9503 .pr_1()
9504 .overflow_x_hidden()
9505 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9506 .child(left)
9507 .child(preview),
9508 )
9509 }
9510 }
9511 }
9512
9513 pub fn render_context_menu(
9514 &self,
9515 style: &EditorStyle,
9516 max_height_in_lines: u32,
9517 window: &mut Window,
9518 cx: &mut Context<Editor>,
9519 ) -> Option<AnyElement> {
9520 let menu = self.context_menu.borrow();
9521 let menu = menu.as_ref()?;
9522 if !menu.visible() {
9523 return None;
9524 };
9525 Some(menu.render(style, max_height_in_lines, window, cx))
9526 }
9527
9528 fn render_context_menu_aside(
9529 &mut self,
9530 max_size: Size<Pixels>,
9531 window: &mut Window,
9532 cx: &mut Context<Editor>,
9533 ) -> Option<AnyElement> {
9534 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9535 if menu.visible() {
9536 menu.render_aside(max_size, window, cx)
9537 } else {
9538 None
9539 }
9540 })
9541 }
9542
9543 fn hide_context_menu(
9544 &mut self,
9545 window: &mut Window,
9546 cx: &mut Context<Self>,
9547 ) -> Option<CodeContextMenu> {
9548 cx.notify();
9549 self.completion_tasks.clear();
9550 let context_menu = self.context_menu.borrow_mut().take();
9551 self.stale_edit_prediction_in_menu.take();
9552 self.update_visible_edit_prediction(window, cx);
9553 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9554 && let Some(completion_provider) = &self.completion_provider
9555 {
9556 completion_provider.selection_changed(None, window, cx);
9557 }
9558 context_menu
9559 }
9560
9561 fn show_snippet_choices(
9562 &mut self,
9563 choices: &Vec<String>,
9564 selection: Range<Anchor>,
9565 cx: &mut Context<Self>,
9566 ) {
9567 let Some((_, buffer, _)) = self
9568 .buffer()
9569 .read(cx)
9570 .excerpt_containing(selection.start, cx)
9571 else {
9572 return;
9573 };
9574 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9575 else {
9576 return;
9577 };
9578 if buffer != end_buffer {
9579 log::error!("expected anchor range to have matching buffer IDs");
9580 return;
9581 }
9582
9583 let id = post_inc(&mut self.next_completion_id);
9584 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9585 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9586 CompletionsMenu::new_snippet_choices(
9587 id,
9588 true,
9589 choices,
9590 selection,
9591 buffer,
9592 snippet_sort_order,
9593 ),
9594 ));
9595 }
9596
9597 pub fn insert_snippet(
9598 &mut self,
9599 insertion_ranges: &[Range<usize>],
9600 snippet: Snippet,
9601 window: &mut Window,
9602 cx: &mut Context<Self>,
9603 ) -> Result<()> {
9604 struct Tabstop<T> {
9605 is_end_tabstop: bool,
9606 ranges: Vec<Range<T>>,
9607 choices: Option<Vec<String>>,
9608 }
9609
9610 let tabstops = self.buffer.update(cx, |buffer, cx| {
9611 let snippet_text: Arc<str> = snippet.text.clone().into();
9612 let edits = insertion_ranges
9613 .iter()
9614 .cloned()
9615 .map(|range| (range, snippet_text.clone()));
9616 let autoindent_mode = AutoindentMode::Block {
9617 original_indent_columns: Vec::new(),
9618 };
9619 buffer.edit(edits, Some(autoindent_mode), cx);
9620
9621 let snapshot = &*buffer.read(cx);
9622 let snippet = &snippet;
9623 snippet
9624 .tabstops
9625 .iter()
9626 .map(|tabstop| {
9627 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9628 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9629 });
9630 let mut tabstop_ranges = tabstop
9631 .ranges
9632 .iter()
9633 .flat_map(|tabstop_range| {
9634 let mut delta = 0_isize;
9635 insertion_ranges.iter().map(move |insertion_range| {
9636 let insertion_start = insertion_range.start as isize + delta;
9637 delta +=
9638 snippet.text.len() as isize - insertion_range.len() as isize;
9639
9640 let start = ((insertion_start + tabstop_range.start) as usize)
9641 .min(snapshot.len());
9642 let end = ((insertion_start + tabstop_range.end) as usize)
9643 .min(snapshot.len());
9644 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9645 })
9646 })
9647 .collect::<Vec<_>>();
9648 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9649
9650 Tabstop {
9651 is_end_tabstop,
9652 ranges: tabstop_ranges,
9653 choices: tabstop.choices.clone(),
9654 }
9655 })
9656 .collect::<Vec<_>>()
9657 });
9658 if let Some(tabstop) = tabstops.first() {
9659 self.change_selections(Default::default(), window, cx, |s| {
9660 // Reverse order so that the first range is the newest created selection.
9661 // Completions will use it and autoscroll will prioritize it.
9662 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9663 });
9664
9665 if let Some(choices) = &tabstop.choices
9666 && let Some(selection) = tabstop.ranges.first()
9667 {
9668 self.show_snippet_choices(choices, selection.clone(), cx)
9669 }
9670
9671 // If we're already at the last tabstop and it's at the end of the snippet,
9672 // we're done, we don't need to keep the state around.
9673 if !tabstop.is_end_tabstop {
9674 let choices = tabstops
9675 .iter()
9676 .map(|tabstop| tabstop.choices.clone())
9677 .collect();
9678
9679 let ranges = tabstops
9680 .into_iter()
9681 .map(|tabstop| tabstop.ranges)
9682 .collect::<Vec<_>>();
9683
9684 self.snippet_stack.push(SnippetState {
9685 active_index: 0,
9686 ranges,
9687 choices,
9688 });
9689 }
9690
9691 // Check whether the just-entered snippet ends with an auto-closable bracket.
9692 if self.autoclose_regions.is_empty() {
9693 let snapshot = self.buffer.read(cx).snapshot(cx);
9694 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9695 let selection_head = selection.head();
9696 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9697 continue;
9698 };
9699
9700 let mut bracket_pair = None;
9701 let max_lookup_length = scope
9702 .brackets()
9703 .map(|(pair, _)| {
9704 pair.start
9705 .as_str()
9706 .chars()
9707 .count()
9708 .max(pair.end.as_str().chars().count())
9709 })
9710 .max();
9711 if let Some(max_lookup_length) = max_lookup_length {
9712 let next_text = snapshot
9713 .chars_at(selection_head)
9714 .take(max_lookup_length)
9715 .collect::<String>();
9716 let prev_text = snapshot
9717 .reversed_chars_at(selection_head)
9718 .take(max_lookup_length)
9719 .collect::<String>();
9720
9721 for (pair, enabled) in scope.brackets() {
9722 if enabled
9723 && pair.close
9724 && prev_text.starts_with(pair.start.as_str())
9725 && next_text.starts_with(pair.end.as_str())
9726 {
9727 bracket_pair = Some(pair.clone());
9728 break;
9729 }
9730 }
9731 }
9732
9733 if let Some(pair) = bracket_pair {
9734 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9735 let autoclose_enabled =
9736 self.use_autoclose && snapshot_settings.use_autoclose;
9737 if autoclose_enabled {
9738 let start = snapshot.anchor_after(selection_head);
9739 let end = snapshot.anchor_after(selection_head);
9740 self.autoclose_regions.push(AutocloseRegion {
9741 selection_id: selection.id,
9742 range: start..end,
9743 pair,
9744 });
9745 }
9746 }
9747 }
9748 }
9749 }
9750 Ok(())
9751 }
9752
9753 pub fn move_to_next_snippet_tabstop(
9754 &mut self,
9755 window: &mut Window,
9756 cx: &mut Context<Self>,
9757 ) -> bool {
9758 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9759 }
9760
9761 pub fn move_to_prev_snippet_tabstop(
9762 &mut self,
9763 window: &mut Window,
9764 cx: &mut Context<Self>,
9765 ) -> bool {
9766 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9767 }
9768
9769 pub fn move_to_snippet_tabstop(
9770 &mut self,
9771 bias: Bias,
9772 window: &mut Window,
9773 cx: &mut Context<Self>,
9774 ) -> bool {
9775 if let Some(mut snippet) = self.snippet_stack.pop() {
9776 match bias {
9777 Bias::Left => {
9778 if snippet.active_index > 0 {
9779 snippet.active_index -= 1;
9780 } else {
9781 self.snippet_stack.push(snippet);
9782 return false;
9783 }
9784 }
9785 Bias::Right => {
9786 if snippet.active_index + 1 < snippet.ranges.len() {
9787 snippet.active_index += 1;
9788 } else {
9789 self.snippet_stack.push(snippet);
9790 return false;
9791 }
9792 }
9793 }
9794 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9795 self.change_selections(Default::default(), window, cx, |s| {
9796 // Reverse order so that the first range is the newest created selection.
9797 // Completions will use it and autoscroll will prioritize it.
9798 s.select_ranges(current_ranges.iter().rev().cloned())
9799 });
9800
9801 if let Some(choices) = &snippet.choices[snippet.active_index]
9802 && let Some(selection) = current_ranges.first()
9803 {
9804 self.show_snippet_choices(choices, selection.clone(), cx);
9805 }
9806
9807 // If snippet state is not at the last tabstop, push it back on the stack
9808 if snippet.active_index + 1 < snippet.ranges.len() {
9809 self.snippet_stack.push(snippet);
9810 }
9811 return true;
9812 }
9813 }
9814
9815 false
9816 }
9817
9818 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9819 self.transact(window, cx, |this, window, cx| {
9820 this.select_all(&SelectAll, window, cx);
9821 this.insert("", window, cx);
9822 });
9823 }
9824
9825 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9826 if self.read_only(cx) {
9827 return;
9828 }
9829 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9830 self.transact(window, cx, |this, window, cx| {
9831 this.select_autoclose_pair(window, cx);
9832
9833 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9834
9835 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9836 if !this.linked_edit_ranges.is_empty() {
9837 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9838 let snapshot = this.buffer.read(cx).snapshot(cx);
9839
9840 for selection in selections.iter() {
9841 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9842 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9843 if selection_start.buffer_id != selection_end.buffer_id {
9844 continue;
9845 }
9846 if let Some(ranges) =
9847 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9848 {
9849 for (buffer, entries) in ranges {
9850 linked_ranges.entry(buffer).or_default().extend(entries);
9851 }
9852 }
9853 }
9854 }
9855
9856 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9857 for selection in &mut selections {
9858 if selection.is_empty() {
9859 let old_head = selection.head();
9860 let mut new_head =
9861 movement::left(&display_map, old_head.to_display_point(&display_map))
9862 .to_point(&display_map);
9863 if let Some((buffer, line_buffer_range)) = display_map
9864 .buffer_snapshot()
9865 .buffer_line_for_row(MultiBufferRow(old_head.row))
9866 {
9867 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9868 let indent_len = match indent_size.kind {
9869 IndentKind::Space => {
9870 buffer.settings_at(line_buffer_range.start, cx).tab_size
9871 }
9872 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9873 };
9874 if old_head.column <= indent_size.len && old_head.column > 0 {
9875 let indent_len = indent_len.get();
9876 new_head = cmp::min(
9877 new_head,
9878 MultiBufferPoint::new(
9879 old_head.row,
9880 ((old_head.column - 1) / indent_len) * indent_len,
9881 ),
9882 );
9883 }
9884 }
9885
9886 selection.set_head(new_head, SelectionGoal::None);
9887 }
9888 }
9889
9890 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9891 this.insert("", window, cx);
9892 let empty_str: Arc<str> = Arc::from("");
9893 for (buffer, edits) in linked_ranges {
9894 let snapshot = buffer.read(cx).snapshot();
9895 use text::ToPoint as TP;
9896
9897 let edits = edits
9898 .into_iter()
9899 .map(|range| {
9900 let end_point = TP::to_point(&range.end, &snapshot);
9901 let mut start_point = TP::to_point(&range.start, &snapshot);
9902
9903 if end_point == start_point {
9904 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9905 .saturating_sub(1);
9906 start_point =
9907 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9908 };
9909
9910 (start_point..end_point, empty_str.clone())
9911 })
9912 .sorted_by_key(|(range, _)| range.start)
9913 .collect::<Vec<_>>();
9914 buffer.update(cx, |this, cx| {
9915 this.edit(edits, None, cx);
9916 })
9917 }
9918 this.refresh_edit_prediction(true, false, window, cx);
9919 refresh_linked_ranges(this, window, cx);
9920 });
9921 }
9922
9923 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9924 if self.read_only(cx) {
9925 return;
9926 }
9927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9928 self.transact(window, cx, |this, window, cx| {
9929 this.change_selections(Default::default(), window, cx, |s| {
9930 s.move_with(|map, selection| {
9931 if selection.is_empty() {
9932 let cursor = movement::right(map, selection.head());
9933 selection.end = cursor;
9934 selection.reversed = true;
9935 selection.goal = SelectionGoal::None;
9936 }
9937 })
9938 });
9939 this.insert("", window, cx);
9940 this.refresh_edit_prediction(true, false, window, cx);
9941 });
9942 }
9943
9944 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9945 if self.mode.is_single_line() {
9946 cx.propagate();
9947 return;
9948 }
9949
9950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9951 if self.move_to_prev_snippet_tabstop(window, cx) {
9952 return;
9953 }
9954 self.outdent(&Outdent, window, cx);
9955 }
9956
9957 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9958 if self.mode.is_single_line() {
9959 cx.propagate();
9960 return;
9961 }
9962
9963 if self.move_to_next_snippet_tabstop(window, cx) {
9964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9965 return;
9966 }
9967 if self.read_only(cx) {
9968 return;
9969 }
9970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9971 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
9972 let buffer = self.buffer.read(cx);
9973 let snapshot = buffer.snapshot(cx);
9974 let rows_iter = selections.iter().map(|s| s.head().row);
9975 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9976
9977 let has_some_cursor_in_whitespace = selections
9978 .iter()
9979 .filter(|selection| selection.is_empty())
9980 .any(|selection| {
9981 let cursor = selection.head();
9982 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9983 cursor.column < current_indent.len
9984 });
9985
9986 let mut edits = Vec::new();
9987 let mut prev_edited_row = 0;
9988 let mut row_delta = 0;
9989 for selection in &mut selections {
9990 if selection.start.row != prev_edited_row {
9991 row_delta = 0;
9992 }
9993 prev_edited_row = selection.end.row;
9994
9995 // If the selection is non-empty, then increase the indentation of the selected lines.
9996 if !selection.is_empty() {
9997 row_delta =
9998 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9999 continue;
10000 }
10001
10002 let cursor = selection.head();
10003 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10004 if let Some(suggested_indent) =
10005 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10006 {
10007 // Don't do anything if already at suggested indent
10008 // and there is any other cursor which is not
10009 if has_some_cursor_in_whitespace
10010 && cursor.column == current_indent.len
10011 && current_indent.len == suggested_indent.len
10012 {
10013 continue;
10014 }
10015
10016 // Adjust line and move cursor to suggested indent
10017 // if cursor is not at suggested indent
10018 if cursor.column < suggested_indent.len
10019 && cursor.column <= current_indent.len
10020 && current_indent.len <= suggested_indent.len
10021 {
10022 selection.start = Point::new(cursor.row, suggested_indent.len);
10023 selection.end = selection.start;
10024 if row_delta == 0 {
10025 edits.extend(Buffer::edit_for_indent_size_adjustment(
10026 cursor.row,
10027 current_indent,
10028 suggested_indent,
10029 ));
10030 row_delta = suggested_indent.len - current_indent.len;
10031 }
10032 continue;
10033 }
10034
10035 // If current indent is more than suggested indent
10036 // only move cursor to current indent and skip indent
10037 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10038 selection.start = Point::new(cursor.row, current_indent.len);
10039 selection.end = selection.start;
10040 continue;
10041 }
10042 }
10043
10044 // Otherwise, insert a hard or soft tab.
10045 let settings = buffer.language_settings_at(cursor, cx);
10046 let tab_size = if settings.hard_tabs {
10047 IndentSize::tab()
10048 } else {
10049 let tab_size = settings.tab_size.get();
10050 let indent_remainder = snapshot
10051 .text_for_range(Point::new(cursor.row, 0)..cursor)
10052 .flat_map(str::chars)
10053 .fold(row_delta % tab_size, |counter: u32, c| {
10054 if c == '\t' {
10055 0
10056 } else {
10057 (counter + 1) % tab_size
10058 }
10059 });
10060
10061 let chars_to_next_tab_stop = tab_size - indent_remainder;
10062 IndentSize::spaces(chars_to_next_tab_stop)
10063 };
10064 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10065 selection.end = selection.start;
10066 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10067 row_delta += tab_size.len;
10068 }
10069
10070 self.transact(window, cx, |this, window, cx| {
10071 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10072 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10073 this.refresh_edit_prediction(true, false, window, cx);
10074 });
10075 }
10076
10077 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10078 if self.read_only(cx) {
10079 return;
10080 }
10081 if self.mode.is_single_line() {
10082 cx.propagate();
10083 return;
10084 }
10085
10086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10087 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10088 let mut prev_edited_row = 0;
10089 let mut row_delta = 0;
10090 let mut edits = Vec::new();
10091 let buffer = self.buffer.read(cx);
10092 let snapshot = buffer.snapshot(cx);
10093 for selection in &mut selections {
10094 if selection.start.row != prev_edited_row {
10095 row_delta = 0;
10096 }
10097 prev_edited_row = selection.end.row;
10098
10099 row_delta =
10100 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10101 }
10102
10103 self.transact(window, cx, |this, window, cx| {
10104 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10105 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10106 });
10107 }
10108
10109 fn indent_selection(
10110 buffer: &MultiBuffer,
10111 snapshot: &MultiBufferSnapshot,
10112 selection: &mut Selection<Point>,
10113 edits: &mut Vec<(Range<Point>, String)>,
10114 delta_for_start_row: u32,
10115 cx: &App,
10116 ) -> u32 {
10117 let settings = buffer.language_settings_at(selection.start, cx);
10118 let tab_size = settings.tab_size.get();
10119 let indent_kind = if settings.hard_tabs {
10120 IndentKind::Tab
10121 } else {
10122 IndentKind::Space
10123 };
10124 let mut start_row = selection.start.row;
10125 let mut end_row = selection.end.row + 1;
10126
10127 // If a selection ends at the beginning of a line, don't indent
10128 // that last line.
10129 if selection.end.column == 0 && selection.end.row > selection.start.row {
10130 end_row -= 1;
10131 }
10132
10133 // Avoid re-indenting a row that has already been indented by a
10134 // previous selection, but still update this selection's column
10135 // to reflect that indentation.
10136 if delta_for_start_row > 0 {
10137 start_row += 1;
10138 selection.start.column += delta_for_start_row;
10139 if selection.end.row == selection.start.row {
10140 selection.end.column += delta_for_start_row;
10141 }
10142 }
10143
10144 let mut delta_for_end_row = 0;
10145 let has_multiple_rows = start_row + 1 != end_row;
10146 for row in start_row..end_row {
10147 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10148 let indent_delta = match (current_indent.kind, indent_kind) {
10149 (IndentKind::Space, IndentKind::Space) => {
10150 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10151 IndentSize::spaces(columns_to_next_tab_stop)
10152 }
10153 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10154 (_, IndentKind::Tab) => IndentSize::tab(),
10155 };
10156
10157 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10158 0
10159 } else {
10160 selection.start.column
10161 };
10162 let row_start = Point::new(row, start);
10163 edits.push((
10164 row_start..row_start,
10165 indent_delta.chars().collect::<String>(),
10166 ));
10167
10168 // Update this selection's endpoints to reflect the indentation.
10169 if row == selection.start.row {
10170 selection.start.column += indent_delta.len;
10171 }
10172 if row == selection.end.row {
10173 selection.end.column += indent_delta.len;
10174 delta_for_end_row = indent_delta.len;
10175 }
10176 }
10177
10178 if selection.start.row == selection.end.row {
10179 delta_for_start_row + delta_for_end_row
10180 } else {
10181 delta_for_end_row
10182 }
10183 }
10184
10185 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10186 if self.read_only(cx) {
10187 return;
10188 }
10189 if self.mode.is_single_line() {
10190 cx.propagate();
10191 return;
10192 }
10193
10194 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10195 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10196 let selections = self.selections.all::<Point>(&display_map);
10197 let mut deletion_ranges = Vec::new();
10198 let mut last_outdent = None;
10199 {
10200 let buffer = self.buffer.read(cx);
10201 let snapshot = buffer.snapshot(cx);
10202 for selection in &selections {
10203 let settings = buffer.language_settings_at(selection.start, cx);
10204 let tab_size = settings.tab_size.get();
10205 let mut rows = selection.spanned_rows(false, &display_map);
10206
10207 // Avoid re-outdenting a row that has already been outdented by a
10208 // previous selection.
10209 if let Some(last_row) = last_outdent
10210 && last_row == rows.start
10211 {
10212 rows.start = rows.start.next_row();
10213 }
10214 let has_multiple_rows = rows.len() > 1;
10215 for row in rows.iter_rows() {
10216 let indent_size = snapshot.indent_size_for_line(row);
10217 if indent_size.len > 0 {
10218 let deletion_len = match indent_size.kind {
10219 IndentKind::Space => {
10220 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10221 if columns_to_prev_tab_stop == 0 {
10222 tab_size
10223 } else {
10224 columns_to_prev_tab_stop
10225 }
10226 }
10227 IndentKind::Tab => 1,
10228 };
10229 let start = if has_multiple_rows
10230 || deletion_len > selection.start.column
10231 || indent_size.len < selection.start.column
10232 {
10233 0
10234 } else {
10235 selection.start.column - deletion_len
10236 };
10237 deletion_ranges.push(
10238 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10239 );
10240 last_outdent = Some(row);
10241 }
10242 }
10243 }
10244 }
10245
10246 self.transact(window, cx, |this, window, cx| {
10247 this.buffer.update(cx, |buffer, cx| {
10248 let empty_str: Arc<str> = Arc::default();
10249 buffer.edit(
10250 deletion_ranges
10251 .into_iter()
10252 .map(|range| (range, empty_str.clone())),
10253 None,
10254 cx,
10255 );
10256 });
10257 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10258 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10259 });
10260 }
10261
10262 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10263 if self.read_only(cx) {
10264 return;
10265 }
10266 if self.mode.is_single_line() {
10267 cx.propagate();
10268 return;
10269 }
10270
10271 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10272 let selections = self
10273 .selections
10274 .all::<usize>(&self.display_snapshot(cx))
10275 .into_iter()
10276 .map(|s| s.range());
10277
10278 self.transact(window, cx, |this, window, cx| {
10279 this.buffer.update(cx, |buffer, cx| {
10280 buffer.autoindent_ranges(selections, cx);
10281 });
10282 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10283 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10284 });
10285 }
10286
10287 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10288 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10290 let selections = self.selections.all::<Point>(&display_map);
10291
10292 let mut new_cursors = Vec::new();
10293 let mut edit_ranges = Vec::new();
10294 let mut selections = selections.iter().peekable();
10295 while let Some(selection) = selections.next() {
10296 let mut rows = selection.spanned_rows(false, &display_map);
10297
10298 // Accumulate contiguous regions of rows that we want to delete.
10299 while let Some(next_selection) = selections.peek() {
10300 let next_rows = next_selection.spanned_rows(false, &display_map);
10301 if next_rows.start <= rows.end {
10302 rows.end = next_rows.end;
10303 selections.next().unwrap();
10304 } else {
10305 break;
10306 }
10307 }
10308
10309 let buffer = display_map.buffer_snapshot();
10310 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10311 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10312 // If there's a line after the range, delete the \n from the end of the row range
10313 (
10314 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10315 rows.end,
10316 )
10317 } else {
10318 // If there isn't a line after the range, delete the \n from the line before the
10319 // start of the row range
10320 edit_start = edit_start.saturating_sub(1);
10321 (buffer.len(), rows.start.previous_row())
10322 };
10323
10324 let text_layout_details = self.text_layout_details(window);
10325 let x = display_map.x_for_display_point(
10326 selection.head().to_display_point(&display_map),
10327 &text_layout_details,
10328 );
10329 let row = Point::new(target_row.0, 0)
10330 .to_display_point(&display_map)
10331 .row();
10332 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10333
10334 new_cursors.push((
10335 selection.id,
10336 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10337 SelectionGoal::None,
10338 ));
10339 edit_ranges.push(edit_start..edit_end);
10340 }
10341
10342 self.transact(window, cx, |this, window, cx| {
10343 let buffer = this.buffer.update(cx, |buffer, cx| {
10344 let empty_str: Arc<str> = Arc::default();
10345 buffer.edit(
10346 edit_ranges
10347 .into_iter()
10348 .map(|range| (range, empty_str.clone())),
10349 None,
10350 cx,
10351 );
10352 buffer.snapshot(cx)
10353 });
10354 let new_selections = new_cursors
10355 .into_iter()
10356 .map(|(id, cursor, goal)| {
10357 let cursor = cursor.to_point(&buffer);
10358 Selection {
10359 id,
10360 start: cursor,
10361 end: cursor,
10362 reversed: false,
10363 goal,
10364 }
10365 })
10366 .collect();
10367
10368 this.change_selections(Default::default(), window, cx, |s| {
10369 s.select(new_selections);
10370 });
10371 });
10372 }
10373
10374 pub fn join_lines_impl(
10375 &mut self,
10376 insert_whitespace: bool,
10377 window: &mut Window,
10378 cx: &mut Context<Self>,
10379 ) {
10380 if self.read_only(cx) {
10381 return;
10382 }
10383 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10384 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10385 let start = MultiBufferRow(selection.start.row);
10386 // Treat single line selections as if they include the next line. Otherwise this action
10387 // would do nothing for single line selections individual cursors.
10388 let end = if selection.start.row == selection.end.row {
10389 MultiBufferRow(selection.start.row + 1)
10390 } else {
10391 MultiBufferRow(selection.end.row)
10392 };
10393
10394 if let Some(last_row_range) = row_ranges.last_mut()
10395 && start <= last_row_range.end
10396 {
10397 last_row_range.end = end;
10398 continue;
10399 }
10400 row_ranges.push(start..end);
10401 }
10402
10403 let snapshot = self.buffer.read(cx).snapshot(cx);
10404 let mut cursor_positions = Vec::new();
10405 for row_range in &row_ranges {
10406 let anchor = snapshot.anchor_before(Point::new(
10407 row_range.end.previous_row().0,
10408 snapshot.line_len(row_range.end.previous_row()),
10409 ));
10410 cursor_positions.push(anchor..anchor);
10411 }
10412
10413 self.transact(window, cx, |this, window, cx| {
10414 for row_range in row_ranges.into_iter().rev() {
10415 for row in row_range.iter_rows().rev() {
10416 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10417 let next_line_row = row.next_row();
10418 let indent = snapshot.indent_size_for_line(next_line_row);
10419 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10420
10421 let replace =
10422 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10423 " "
10424 } else {
10425 ""
10426 };
10427
10428 this.buffer.update(cx, |buffer, cx| {
10429 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10430 });
10431 }
10432 }
10433
10434 this.change_selections(Default::default(), window, cx, |s| {
10435 s.select_anchor_ranges(cursor_positions)
10436 });
10437 });
10438 }
10439
10440 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10441 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10442 self.join_lines_impl(true, window, cx);
10443 }
10444
10445 pub fn sort_lines_case_sensitive(
10446 &mut self,
10447 _: &SortLinesCaseSensitive,
10448 window: &mut Window,
10449 cx: &mut Context<Self>,
10450 ) {
10451 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10452 }
10453
10454 pub fn sort_lines_by_length(
10455 &mut self,
10456 _: &SortLinesByLength,
10457 window: &mut Window,
10458 cx: &mut Context<Self>,
10459 ) {
10460 self.manipulate_immutable_lines(window, cx, |lines| {
10461 lines.sort_by_key(|&line| line.chars().count())
10462 })
10463 }
10464
10465 pub fn sort_lines_case_insensitive(
10466 &mut self,
10467 _: &SortLinesCaseInsensitive,
10468 window: &mut Window,
10469 cx: &mut Context<Self>,
10470 ) {
10471 self.manipulate_immutable_lines(window, cx, |lines| {
10472 lines.sort_by_key(|line| line.to_lowercase())
10473 })
10474 }
10475
10476 pub fn unique_lines_case_insensitive(
10477 &mut self,
10478 _: &UniqueLinesCaseInsensitive,
10479 window: &mut Window,
10480 cx: &mut Context<Self>,
10481 ) {
10482 self.manipulate_immutable_lines(window, cx, |lines| {
10483 let mut seen = HashSet::default();
10484 lines.retain(|line| seen.insert(line.to_lowercase()));
10485 })
10486 }
10487
10488 pub fn unique_lines_case_sensitive(
10489 &mut self,
10490 _: &UniqueLinesCaseSensitive,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 self.manipulate_immutable_lines(window, cx, |lines| {
10495 let mut seen = HashSet::default();
10496 lines.retain(|line| seen.insert(*line));
10497 })
10498 }
10499
10500 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10501 let snapshot = self.buffer.read(cx).snapshot(cx);
10502 for selection in self.selections.disjoint_anchors_arc().iter() {
10503 if snapshot
10504 .language_at(selection.start)
10505 .and_then(|lang| lang.config().wrap_characters.as_ref())
10506 .is_some()
10507 {
10508 return true;
10509 }
10510 }
10511 false
10512 }
10513
10514 fn wrap_selections_in_tag(
10515 &mut self,
10516 _: &WrapSelectionsInTag,
10517 window: &mut Window,
10518 cx: &mut Context<Self>,
10519 ) {
10520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10521
10522 let snapshot = self.buffer.read(cx).snapshot(cx);
10523
10524 let mut edits = Vec::new();
10525 let mut boundaries = Vec::new();
10526
10527 for selection in self
10528 .selections
10529 .all::<Point>(&self.display_snapshot(cx))
10530 .iter()
10531 {
10532 let Some(wrap_config) = snapshot
10533 .language_at(selection.start)
10534 .and_then(|lang| lang.config().wrap_characters.clone())
10535 else {
10536 continue;
10537 };
10538
10539 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10540 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10541
10542 let start_before = snapshot.anchor_before(selection.start);
10543 let end_after = snapshot.anchor_after(selection.end);
10544
10545 edits.push((start_before..start_before, open_tag));
10546 edits.push((end_after..end_after, close_tag));
10547
10548 boundaries.push((
10549 start_before,
10550 end_after,
10551 wrap_config.start_prefix.len(),
10552 wrap_config.end_suffix.len(),
10553 ));
10554 }
10555
10556 if edits.is_empty() {
10557 return;
10558 }
10559
10560 self.transact(window, cx, |this, window, cx| {
10561 let buffer = this.buffer.update(cx, |buffer, cx| {
10562 buffer.edit(edits, None, cx);
10563 buffer.snapshot(cx)
10564 });
10565
10566 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10567 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10568 boundaries.into_iter()
10569 {
10570 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10571 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10572 new_selections.push(open_offset..open_offset);
10573 new_selections.push(close_offset..close_offset);
10574 }
10575
10576 this.change_selections(Default::default(), window, cx, |s| {
10577 s.select_ranges(new_selections);
10578 });
10579
10580 this.request_autoscroll(Autoscroll::fit(), cx);
10581 });
10582 }
10583
10584 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10585 let Some(project) = self.project.clone() else {
10586 return;
10587 };
10588 self.reload(project, window, cx)
10589 .detach_and_notify_err(window, cx);
10590 }
10591
10592 pub fn restore_file(
10593 &mut self,
10594 _: &::git::RestoreFile,
10595 window: &mut Window,
10596 cx: &mut Context<Self>,
10597 ) {
10598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10599 let mut buffer_ids = HashSet::default();
10600 let snapshot = self.buffer().read(cx).snapshot(cx);
10601 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10602 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10603 }
10604
10605 let buffer = self.buffer().read(cx);
10606 let ranges = buffer_ids
10607 .into_iter()
10608 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10609 .collect::<Vec<_>>();
10610
10611 self.restore_hunks_in_ranges(ranges, window, cx);
10612 }
10613
10614 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10616 let selections = self
10617 .selections
10618 .all(&self.display_snapshot(cx))
10619 .into_iter()
10620 .map(|s| s.range())
10621 .collect();
10622 self.restore_hunks_in_ranges(selections, window, cx);
10623 }
10624
10625 pub fn restore_hunks_in_ranges(
10626 &mut self,
10627 ranges: Vec<Range<Point>>,
10628 window: &mut Window,
10629 cx: &mut Context<Editor>,
10630 ) {
10631 let mut revert_changes = HashMap::default();
10632 let chunk_by = self
10633 .snapshot(window, cx)
10634 .hunks_for_ranges(ranges)
10635 .into_iter()
10636 .chunk_by(|hunk| hunk.buffer_id);
10637 for (buffer_id, hunks) in &chunk_by {
10638 let hunks = hunks.collect::<Vec<_>>();
10639 for hunk in &hunks {
10640 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10641 }
10642 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10643 }
10644 drop(chunk_by);
10645 if !revert_changes.is_empty() {
10646 self.transact(window, cx, |editor, window, cx| {
10647 editor.restore(revert_changes, window, cx);
10648 });
10649 }
10650 }
10651
10652 pub fn open_active_item_in_terminal(
10653 &mut self,
10654 _: &OpenInTerminal,
10655 window: &mut Window,
10656 cx: &mut Context<Self>,
10657 ) {
10658 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10659 let project_path = buffer.read(cx).project_path(cx)?;
10660 let project = self.project()?.read(cx);
10661 let entry = project.entry_for_path(&project_path, cx)?;
10662 let parent = match &entry.canonical_path {
10663 Some(canonical_path) => canonical_path.to_path_buf(),
10664 None => project.absolute_path(&project_path, cx)?,
10665 }
10666 .parent()?
10667 .to_path_buf();
10668 Some(parent)
10669 }) {
10670 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10671 }
10672 }
10673
10674 fn set_breakpoint_context_menu(
10675 &mut self,
10676 display_row: DisplayRow,
10677 position: Option<Anchor>,
10678 clicked_point: gpui::Point<Pixels>,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) {
10682 let source = self
10683 .buffer
10684 .read(cx)
10685 .snapshot(cx)
10686 .anchor_before(Point::new(display_row.0, 0u32));
10687
10688 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10689
10690 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10691 self,
10692 source,
10693 clicked_point,
10694 context_menu,
10695 window,
10696 cx,
10697 );
10698 }
10699
10700 fn add_edit_breakpoint_block(
10701 &mut self,
10702 anchor: Anchor,
10703 breakpoint: &Breakpoint,
10704 edit_action: BreakpointPromptEditAction,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 ) {
10708 let weak_editor = cx.weak_entity();
10709 let bp_prompt = cx.new(|cx| {
10710 BreakpointPromptEditor::new(
10711 weak_editor,
10712 anchor,
10713 breakpoint.clone(),
10714 edit_action,
10715 window,
10716 cx,
10717 )
10718 });
10719
10720 let height = bp_prompt.update(cx, |this, cx| {
10721 this.prompt
10722 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10723 });
10724 let cloned_prompt = bp_prompt.clone();
10725 let blocks = vec![BlockProperties {
10726 style: BlockStyle::Sticky,
10727 placement: BlockPlacement::Above(anchor),
10728 height: Some(height),
10729 render: Arc::new(move |cx| {
10730 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10731 cloned_prompt.clone().into_any_element()
10732 }),
10733 priority: 0,
10734 }];
10735
10736 let focus_handle = bp_prompt.focus_handle(cx);
10737 window.focus(&focus_handle);
10738
10739 let block_ids = self.insert_blocks(blocks, None, cx);
10740 bp_prompt.update(cx, |prompt, _| {
10741 prompt.add_block_ids(block_ids);
10742 });
10743 }
10744
10745 pub(crate) fn breakpoint_at_row(
10746 &self,
10747 row: u32,
10748 window: &mut Window,
10749 cx: &mut Context<Self>,
10750 ) -> Option<(Anchor, Breakpoint)> {
10751 let snapshot = self.snapshot(window, cx);
10752 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10753
10754 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10755 }
10756
10757 pub(crate) fn breakpoint_at_anchor(
10758 &self,
10759 breakpoint_position: Anchor,
10760 snapshot: &EditorSnapshot,
10761 cx: &mut Context<Self>,
10762 ) -> Option<(Anchor, Breakpoint)> {
10763 let buffer = self
10764 .buffer
10765 .read(cx)
10766 .buffer_for_anchor(breakpoint_position, cx)?;
10767
10768 let enclosing_excerpt = breakpoint_position.excerpt_id;
10769 let buffer_snapshot = buffer.read(cx).snapshot();
10770
10771 let row = buffer_snapshot
10772 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10773 .row;
10774
10775 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10776 let anchor_end = snapshot
10777 .buffer_snapshot()
10778 .anchor_after(Point::new(row, line_len));
10779
10780 self.breakpoint_store
10781 .as_ref()?
10782 .read_with(cx, |breakpoint_store, cx| {
10783 breakpoint_store
10784 .breakpoints(
10785 &buffer,
10786 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10787 &buffer_snapshot,
10788 cx,
10789 )
10790 .next()
10791 .and_then(|(bp, _)| {
10792 let breakpoint_row = buffer_snapshot
10793 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10794 .row;
10795
10796 if breakpoint_row == row {
10797 snapshot
10798 .buffer_snapshot()
10799 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10800 .map(|position| (position, bp.bp.clone()))
10801 } else {
10802 None
10803 }
10804 })
10805 })
10806 }
10807
10808 pub fn edit_log_breakpoint(
10809 &mut self,
10810 _: &EditLogBreakpoint,
10811 window: &mut Window,
10812 cx: &mut Context<Self>,
10813 ) {
10814 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10815 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10816 message: None,
10817 state: BreakpointState::Enabled,
10818 condition: None,
10819 hit_condition: None,
10820 });
10821
10822 self.add_edit_breakpoint_block(
10823 anchor,
10824 &breakpoint,
10825 BreakpointPromptEditAction::Log,
10826 window,
10827 cx,
10828 );
10829 }
10830 }
10831
10832 fn breakpoints_at_cursors(
10833 &self,
10834 window: &mut Window,
10835 cx: &mut Context<Self>,
10836 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10837 let snapshot = self.snapshot(window, cx);
10838 let cursors = self
10839 .selections
10840 .disjoint_anchors_arc()
10841 .iter()
10842 .map(|selection| {
10843 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10844
10845 let breakpoint_position = self
10846 .breakpoint_at_row(cursor_position.row, window, cx)
10847 .map(|bp| bp.0)
10848 .unwrap_or_else(|| {
10849 snapshot
10850 .display_snapshot
10851 .buffer_snapshot()
10852 .anchor_after(Point::new(cursor_position.row, 0))
10853 });
10854
10855 let breakpoint = self
10856 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10857 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10858
10859 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10860 })
10861 // 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.
10862 .collect::<HashMap<Anchor, _>>();
10863
10864 cursors.into_iter().collect()
10865 }
10866
10867 pub fn enable_breakpoint(
10868 &mut self,
10869 _: &crate::actions::EnableBreakpoint,
10870 window: &mut Window,
10871 cx: &mut Context<Self>,
10872 ) {
10873 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10874 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10875 continue;
10876 };
10877 self.edit_breakpoint_at_anchor(
10878 anchor,
10879 breakpoint,
10880 BreakpointEditAction::InvertState,
10881 cx,
10882 );
10883 }
10884 }
10885
10886 pub fn disable_breakpoint(
10887 &mut self,
10888 _: &crate::actions::DisableBreakpoint,
10889 window: &mut Window,
10890 cx: &mut Context<Self>,
10891 ) {
10892 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10893 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10894 continue;
10895 };
10896 self.edit_breakpoint_at_anchor(
10897 anchor,
10898 breakpoint,
10899 BreakpointEditAction::InvertState,
10900 cx,
10901 );
10902 }
10903 }
10904
10905 pub fn toggle_breakpoint(
10906 &mut self,
10907 _: &crate::actions::ToggleBreakpoint,
10908 window: &mut Window,
10909 cx: &mut Context<Self>,
10910 ) {
10911 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10912 if let Some(breakpoint) = breakpoint {
10913 self.edit_breakpoint_at_anchor(
10914 anchor,
10915 breakpoint,
10916 BreakpointEditAction::Toggle,
10917 cx,
10918 );
10919 } else {
10920 self.edit_breakpoint_at_anchor(
10921 anchor,
10922 Breakpoint::new_standard(),
10923 BreakpointEditAction::Toggle,
10924 cx,
10925 );
10926 }
10927 }
10928 }
10929
10930 pub fn edit_breakpoint_at_anchor(
10931 &mut self,
10932 breakpoint_position: Anchor,
10933 breakpoint: Breakpoint,
10934 edit_action: BreakpointEditAction,
10935 cx: &mut Context<Self>,
10936 ) {
10937 let Some(breakpoint_store) = &self.breakpoint_store else {
10938 return;
10939 };
10940
10941 let Some(buffer) = self
10942 .buffer
10943 .read(cx)
10944 .buffer_for_anchor(breakpoint_position, cx)
10945 else {
10946 return;
10947 };
10948
10949 breakpoint_store.update(cx, |breakpoint_store, cx| {
10950 breakpoint_store.toggle_breakpoint(
10951 buffer,
10952 BreakpointWithPosition {
10953 position: breakpoint_position.text_anchor,
10954 bp: breakpoint,
10955 },
10956 edit_action,
10957 cx,
10958 );
10959 });
10960
10961 cx.notify();
10962 }
10963
10964 #[cfg(any(test, feature = "test-support"))]
10965 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10966 self.breakpoint_store.clone()
10967 }
10968
10969 pub fn prepare_restore_change(
10970 &self,
10971 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10972 hunk: &MultiBufferDiffHunk,
10973 cx: &mut App,
10974 ) -> Option<()> {
10975 if hunk.is_created_file() {
10976 return None;
10977 }
10978 let buffer = self.buffer.read(cx);
10979 let diff = buffer.diff_for(hunk.buffer_id)?;
10980 let buffer = buffer.buffer(hunk.buffer_id)?;
10981 let buffer = buffer.read(cx);
10982 let original_text = diff
10983 .read(cx)
10984 .base_text()
10985 .as_rope()
10986 .slice(hunk.diff_base_byte_range.clone());
10987 let buffer_snapshot = buffer.snapshot();
10988 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10989 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10990 probe
10991 .0
10992 .start
10993 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10994 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10995 }) {
10996 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10997 Some(())
10998 } else {
10999 None
11000 }
11001 }
11002
11003 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11004 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11005 }
11006
11007 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11008 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11009 }
11010
11011 fn manipulate_lines<M>(
11012 &mut self,
11013 window: &mut Window,
11014 cx: &mut Context<Self>,
11015 mut manipulate: M,
11016 ) where
11017 M: FnMut(&str) -> LineManipulationResult,
11018 {
11019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11020
11021 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11022 let buffer = self.buffer.read(cx).snapshot(cx);
11023
11024 let mut edits = Vec::new();
11025
11026 let selections = self.selections.all::<Point>(&display_map);
11027 let mut selections = selections.iter().peekable();
11028 let mut contiguous_row_selections = Vec::new();
11029 let mut new_selections = Vec::new();
11030 let mut added_lines = 0;
11031 let mut removed_lines = 0;
11032
11033 while let Some(selection) = selections.next() {
11034 let (start_row, end_row) = consume_contiguous_rows(
11035 &mut contiguous_row_selections,
11036 selection,
11037 &display_map,
11038 &mut selections,
11039 );
11040
11041 let start_point = Point::new(start_row.0, 0);
11042 let end_point = Point::new(
11043 end_row.previous_row().0,
11044 buffer.line_len(end_row.previous_row()),
11045 );
11046 let text = buffer
11047 .text_for_range(start_point..end_point)
11048 .collect::<String>();
11049
11050 let LineManipulationResult {
11051 new_text,
11052 line_count_before,
11053 line_count_after,
11054 } = manipulate(&text);
11055
11056 edits.push((start_point..end_point, new_text));
11057
11058 // Selections must change based on added and removed line count
11059 let start_row =
11060 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11061 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11062 new_selections.push(Selection {
11063 id: selection.id,
11064 start: start_row,
11065 end: end_row,
11066 goal: SelectionGoal::None,
11067 reversed: selection.reversed,
11068 });
11069
11070 if line_count_after > line_count_before {
11071 added_lines += line_count_after - line_count_before;
11072 } else if line_count_before > line_count_after {
11073 removed_lines += line_count_before - line_count_after;
11074 }
11075 }
11076
11077 self.transact(window, cx, |this, window, cx| {
11078 let buffer = this.buffer.update(cx, |buffer, cx| {
11079 buffer.edit(edits, None, cx);
11080 buffer.snapshot(cx)
11081 });
11082
11083 // Recalculate offsets on newly edited buffer
11084 let new_selections = new_selections
11085 .iter()
11086 .map(|s| {
11087 let start_point = Point::new(s.start.0, 0);
11088 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11089 Selection {
11090 id: s.id,
11091 start: buffer.point_to_offset(start_point),
11092 end: buffer.point_to_offset(end_point),
11093 goal: s.goal,
11094 reversed: s.reversed,
11095 }
11096 })
11097 .collect();
11098
11099 this.change_selections(Default::default(), window, cx, |s| {
11100 s.select(new_selections);
11101 });
11102
11103 this.request_autoscroll(Autoscroll::fit(), cx);
11104 });
11105 }
11106
11107 fn manipulate_immutable_lines<Fn>(
11108 &mut self,
11109 window: &mut Window,
11110 cx: &mut Context<Self>,
11111 mut callback: Fn,
11112 ) where
11113 Fn: FnMut(&mut Vec<&str>),
11114 {
11115 self.manipulate_lines(window, cx, |text| {
11116 let mut lines: Vec<&str> = text.split('\n').collect();
11117 let line_count_before = lines.len();
11118
11119 callback(&mut lines);
11120
11121 LineManipulationResult {
11122 new_text: lines.join("\n"),
11123 line_count_before,
11124 line_count_after: lines.len(),
11125 }
11126 });
11127 }
11128
11129 fn manipulate_mutable_lines<Fn>(
11130 &mut self,
11131 window: &mut Window,
11132 cx: &mut Context<Self>,
11133 mut callback: Fn,
11134 ) where
11135 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11136 {
11137 self.manipulate_lines(window, cx, |text| {
11138 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11139 let line_count_before = lines.len();
11140
11141 callback(&mut lines);
11142
11143 LineManipulationResult {
11144 new_text: lines.join("\n"),
11145 line_count_before,
11146 line_count_after: lines.len(),
11147 }
11148 });
11149 }
11150
11151 pub fn convert_indentation_to_spaces(
11152 &mut self,
11153 _: &ConvertIndentationToSpaces,
11154 window: &mut Window,
11155 cx: &mut Context<Self>,
11156 ) {
11157 let settings = self.buffer.read(cx).language_settings(cx);
11158 let tab_size = settings.tab_size.get() as usize;
11159
11160 self.manipulate_mutable_lines(window, cx, |lines| {
11161 // Allocates a reasonably sized scratch buffer once for the whole loop
11162 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11163 // Avoids recomputing spaces that could be inserted many times
11164 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11165 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11166 .collect();
11167
11168 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11169 let mut chars = line.as_ref().chars();
11170 let mut col = 0;
11171 let mut changed = false;
11172
11173 for ch in chars.by_ref() {
11174 match ch {
11175 ' ' => {
11176 reindented_line.push(' ');
11177 col += 1;
11178 }
11179 '\t' => {
11180 // \t are converted to spaces depending on the current column
11181 let spaces_len = tab_size - (col % tab_size);
11182 reindented_line.extend(&space_cache[spaces_len - 1]);
11183 col += spaces_len;
11184 changed = true;
11185 }
11186 _ => {
11187 // If we dont append before break, the character is consumed
11188 reindented_line.push(ch);
11189 break;
11190 }
11191 }
11192 }
11193
11194 if !changed {
11195 reindented_line.clear();
11196 continue;
11197 }
11198 // Append the rest of the line and replace old reference with new one
11199 reindented_line.extend(chars);
11200 *line = Cow::Owned(reindented_line.clone());
11201 reindented_line.clear();
11202 }
11203 });
11204 }
11205
11206 pub fn convert_indentation_to_tabs(
11207 &mut self,
11208 _: &ConvertIndentationToTabs,
11209 window: &mut Window,
11210 cx: &mut Context<Self>,
11211 ) {
11212 let settings = self.buffer.read(cx).language_settings(cx);
11213 let tab_size = settings.tab_size.get() as usize;
11214
11215 self.manipulate_mutable_lines(window, cx, |lines| {
11216 // Allocates a reasonably sized buffer once for the whole loop
11217 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11218 // Avoids recomputing spaces that could be inserted many times
11219 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11220 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11221 .collect();
11222
11223 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11224 let mut chars = line.chars();
11225 let mut spaces_count = 0;
11226 let mut first_non_indent_char = None;
11227 let mut changed = false;
11228
11229 for ch in chars.by_ref() {
11230 match ch {
11231 ' ' => {
11232 // Keep track of spaces. Append \t when we reach tab_size
11233 spaces_count += 1;
11234 changed = true;
11235 if spaces_count == tab_size {
11236 reindented_line.push('\t');
11237 spaces_count = 0;
11238 }
11239 }
11240 '\t' => {
11241 reindented_line.push('\t');
11242 spaces_count = 0;
11243 }
11244 _ => {
11245 // Dont append it yet, we might have remaining spaces
11246 first_non_indent_char = Some(ch);
11247 break;
11248 }
11249 }
11250 }
11251
11252 if !changed {
11253 reindented_line.clear();
11254 continue;
11255 }
11256 // Remaining spaces that didn't make a full tab stop
11257 if spaces_count > 0 {
11258 reindented_line.extend(&space_cache[spaces_count - 1]);
11259 }
11260 // If we consume an extra character that was not indentation, add it back
11261 if let Some(extra_char) = first_non_indent_char {
11262 reindented_line.push(extra_char);
11263 }
11264 // Append the rest of the line and replace old reference with new one
11265 reindented_line.extend(chars);
11266 *line = Cow::Owned(reindented_line.clone());
11267 reindented_line.clear();
11268 }
11269 });
11270 }
11271
11272 pub fn convert_to_upper_case(
11273 &mut self,
11274 _: &ConvertToUpperCase,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 ) {
11278 self.manipulate_text(window, cx, |text| text.to_uppercase())
11279 }
11280
11281 pub fn convert_to_lower_case(
11282 &mut self,
11283 _: &ConvertToLowerCase,
11284 window: &mut Window,
11285 cx: &mut Context<Self>,
11286 ) {
11287 self.manipulate_text(window, cx, |text| text.to_lowercase())
11288 }
11289
11290 pub fn convert_to_title_case(
11291 &mut self,
11292 _: &ConvertToTitleCase,
11293 window: &mut Window,
11294 cx: &mut Context<Self>,
11295 ) {
11296 self.manipulate_text(window, cx, |text| {
11297 text.split('\n')
11298 .map(|line| line.to_case(Case::Title))
11299 .join("\n")
11300 })
11301 }
11302
11303 pub fn convert_to_snake_case(
11304 &mut self,
11305 _: &ConvertToSnakeCase,
11306 window: &mut Window,
11307 cx: &mut Context<Self>,
11308 ) {
11309 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11310 }
11311
11312 pub fn convert_to_kebab_case(
11313 &mut self,
11314 _: &ConvertToKebabCase,
11315 window: &mut Window,
11316 cx: &mut Context<Self>,
11317 ) {
11318 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11319 }
11320
11321 pub fn convert_to_upper_camel_case(
11322 &mut self,
11323 _: &ConvertToUpperCamelCase,
11324 window: &mut Window,
11325 cx: &mut Context<Self>,
11326 ) {
11327 self.manipulate_text(window, cx, |text| {
11328 text.split('\n')
11329 .map(|line| line.to_case(Case::UpperCamel))
11330 .join("\n")
11331 })
11332 }
11333
11334 pub fn convert_to_lower_camel_case(
11335 &mut self,
11336 _: &ConvertToLowerCamelCase,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11341 }
11342
11343 pub fn convert_to_opposite_case(
11344 &mut self,
11345 _: &ConvertToOppositeCase,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) {
11349 self.manipulate_text(window, cx, |text| {
11350 text.chars()
11351 .fold(String::with_capacity(text.len()), |mut t, c| {
11352 if c.is_uppercase() {
11353 t.extend(c.to_lowercase());
11354 } else {
11355 t.extend(c.to_uppercase());
11356 }
11357 t
11358 })
11359 })
11360 }
11361
11362 pub fn convert_to_sentence_case(
11363 &mut self,
11364 _: &ConvertToSentenceCase,
11365 window: &mut Window,
11366 cx: &mut Context<Self>,
11367 ) {
11368 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11369 }
11370
11371 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11372 self.manipulate_text(window, cx, |text| {
11373 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11374 if has_upper_case_characters {
11375 text.to_lowercase()
11376 } else {
11377 text.to_uppercase()
11378 }
11379 })
11380 }
11381
11382 pub fn convert_to_rot13(
11383 &mut self,
11384 _: &ConvertToRot13,
11385 window: &mut Window,
11386 cx: &mut Context<Self>,
11387 ) {
11388 self.manipulate_text(window, cx, |text| {
11389 text.chars()
11390 .map(|c| match c {
11391 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11392 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11393 _ => c,
11394 })
11395 .collect()
11396 })
11397 }
11398
11399 pub fn convert_to_rot47(
11400 &mut self,
11401 _: &ConvertToRot47,
11402 window: &mut Window,
11403 cx: &mut Context<Self>,
11404 ) {
11405 self.manipulate_text(window, cx, |text| {
11406 text.chars()
11407 .map(|c| {
11408 let code_point = c as u32;
11409 if code_point >= 33 && code_point <= 126 {
11410 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11411 }
11412 c
11413 })
11414 .collect()
11415 })
11416 }
11417
11418 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11419 where
11420 Fn: FnMut(&str) -> String,
11421 {
11422 let buffer = self.buffer.read(cx).snapshot(cx);
11423
11424 let mut new_selections = Vec::new();
11425 let mut edits = Vec::new();
11426 let mut selection_adjustment = 0i32;
11427
11428 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11429 let selection_is_empty = selection.is_empty();
11430
11431 let (start, end) = if selection_is_empty {
11432 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11433 (word_range.start, word_range.end)
11434 } else {
11435 (
11436 buffer.point_to_offset(selection.start),
11437 buffer.point_to_offset(selection.end),
11438 )
11439 };
11440
11441 let text = buffer.text_for_range(start..end).collect::<String>();
11442 let old_length = text.len() as i32;
11443 let text = callback(&text);
11444
11445 new_selections.push(Selection {
11446 start: (start as i32 - selection_adjustment) as usize,
11447 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11448 goal: SelectionGoal::None,
11449 id: selection.id,
11450 reversed: selection.reversed,
11451 });
11452
11453 selection_adjustment += old_length - text.len() as i32;
11454
11455 edits.push((start..end, text));
11456 }
11457
11458 self.transact(window, cx, |this, window, cx| {
11459 this.buffer.update(cx, |buffer, cx| {
11460 buffer.edit(edits, None, cx);
11461 });
11462
11463 this.change_selections(Default::default(), window, cx, |s| {
11464 s.select(new_selections);
11465 });
11466
11467 this.request_autoscroll(Autoscroll::fit(), cx);
11468 });
11469 }
11470
11471 pub fn move_selection_on_drop(
11472 &mut self,
11473 selection: &Selection<Anchor>,
11474 target: DisplayPoint,
11475 is_cut: bool,
11476 window: &mut Window,
11477 cx: &mut Context<Self>,
11478 ) {
11479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11480 let buffer = display_map.buffer_snapshot();
11481 let mut edits = Vec::new();
11482 let insert_point = display_map
11483 .clip_point(target, Bias::Left)
11484 .to_point(&display_map);
11485 let text = buffer
11486 .text_for_range(selection.start..selection.end)
11487 .collect::<String>();
11488 if is_cut {
11489 edits.push(((selection.start..selection.end), String::new()));
11490 }
11491 let insert_anchor = buffer.anchor_before(insert_point);
11492 edits.push(((insert_anchor..insert_anchor), text));
11493 let last_edit_start = insert_anchor.bias_left(buffer);
11494 let last_edit_end = insert_anchor.bias_right(buffer);
11495 self.transact(window, cx, |this, window, cx| {
11496 this.buffer.update(cx, |buffer, cx| {
11497 buffer.edit(edits, None, cx);
11498 });
11499 this.change_selections(Default::default(), window, cx, |s| {
11500 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11501 });
11502 });
11503 }
11504
11505 pub fn clear_selection_drag_state(&mut self) {
11506 self.selection_drag_state = SelectionDragState::None;
11507 }
11508
11509 pub fn duplicate(
11510 &mut self,
11511 upwards: bool,
11512 whole_lines: bool,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11517
11518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11519 let buffer = display_map.buffer_snapshot();
11520 let selections = self.selections.all::<Point>(&display_map);
11521
11522 let mut edits = Vec::new();
11523 let mut selections_iter = selections.iter().peekable();
11524 while let Some(selection) = selections_iter.next() {
11525 let mut rows = selection.spanned_rows(false, &display_map);
11526 // duplicate line-wise
11527 if whole_lines || selection.start == selection.end {
11528 // Avoid duplicating the same lines twice.
11529 while let Some(next_selection) = selections_iter.peek() {
11530 let next_rows = next_selection.spanned_rows(false, &display_map);
11531 if next_rows.start < rows.end {
11532 rows.end = next_rows.end;
11533 selections_iter.next().unwrap();
11534 } else {
11535 break;
11536 }
11537 }
11538
11539 // Copy the text from the selected row region and splice it either at the start
11540 // or end of the region.
11541 let start = Point::new(rows.start.0, 0);
11542 let end = Point::new(
11543 rows.end.previous_row().0,
11544 buffer.line_len(rows.end.previous_row()),
11545 );
11546
11547 let mut text = buffer.text_for_range(start..end).collect::<String>();
11548
11549 let insert_location = if upwards {
11550 // When duplicating upward, we need to insert before the current line.
11551 // If we're on the last line and it doesn't end with a newline,
11552 // we need to add a newline before the duplicated content.
11553 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11554 && buffer.max_point().column > 0
11555 && !text.ends_with('\n');
11556
11557 if needs_leading_newline {
11558 text.insert(0, '\n');
11559 end
11560 } else {
11561 text.push('\n');
11562 Point::new(rows.end.0, 0)
11563 }
11564 } else {
11565 text.push('\n');
11566 start
11567 };
11568 edits.push((insert_location..insert_location, text));
11569 } else {
11570 // duplicate character-wise
11571 let start = selection.start;
11572 let end = selection.end;
11573 let text = buffer.text_for_range(start..end).collect::<String>();
11574 edits.push((selection.end..selection.end, text));
11575 }
11576 }
11577
11578 self.transact(window, cx, |this, _, cx| {
11579 this.buffer.update(cx, |buffer, cx| {
11580 buffer.edit(edits, None, cx);
11581 });
11582
11583 this.request_autoscroll(Autoscroll::fit(), cx);
11584 });
11585 }
11586
11587 pub fn duplicate_line_up(
11588 &mut self,
11589 _: &DuplicateLineUp,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) {
11593 self.duplicate(true, true, window, cx);
11594 }
11595
11596 pub fn duplicate_line_down(
11597 &mut self,
11598 _: &DuplicateLineDown,
11599 window: &mut Window,
11600 cx: &mut Context<Self>,
11601 ) {
11602 self.duplicate(false, true, window, cx);
11603 }
11604
11605 pub fn duplicate_selection(
11606 &mut self,
11607 _: &DuplicateSelection,
11608 window: &mut Window,
11609 cx: &mut Context<Self>,
11610 ) {
11611 self.duplicate(false, false, window, cx);
11612 }
11613
11614 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11616 if self.mode.is_single_line() {
11617 cx.propagate();
11618 return;
11619 }
11620
11621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11622 let buffer = self.buffer.read(cx).snapshot(cx);
11623
11624 let mut edits = Vec::new();
11625 let mut unfold_ranges = Vec::new();
11626 let mut refold_creases = Vec::new();
11627
11628 let selections = self.selections.all::<Point>(&display_map);
11629 let mut selections = selections.iter().peekable();
11630 let mut contiguous_row_selections = Vec::new();
11631 let mut new_selections = Vec::new();
11632
11633 while let Some(selection) = selections.next() {
11634 // Find all the selections that span a contiguous row range
11635 let (start_row, end_row) = consume_contiguous_rows(
11636 &mut contiguous_row_selections,
11637 selection,
11638 &display_map,
11639 &mut selections,
11640 );
11641
11642 // Move the text spanned by the row range to be before the line preceding the row range
11643 if start_row.0 > 0 {
11644 let range_to_move = Point::new(
11645 start_row.previous_row().0,
11646 buffer.line_len(start_row.previous_row()),
11647 )
11648 ..Point::new(
11649 end_row.previous_row().0,
11650 buffer.line_len(end_row.previous_row()),
11651 );
11652 let insertion_point = display_map
11653 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11654 .0;
11655
11656 // Don't move lines across excerpts
11657 if buffer
11658 .excerpt_containing(insertion_point..range_to_move.end)
11659 .is_some()
11660 {
11661 let text = buffer
11662 .text_for_range(range_to_move.clone())
11663 .flat_map(|s| s.chars())
11664 .skip(1)
11665 .chain(['\n'])
11666 .collect::<String>();
11667
11668 edits.push((
11669 buffer.anchor_after(range_to_move.start)
11670 ..buffer.anchor_before(range_to_move.end),
11671 String::new(),
11672 ));
11673 let insertion_anchor = buffer.anchor_after(insertion_point);
11674 edits.push((insertion_anchor..insertion_anchor, text));
11675
11676 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11677
11678 // Move selections up
11679 new_selections.extend(contiguous_row_selections.drain(..).map(
11680 |mut selection| {
11681 selection.start.row -= row_delta;
11682 selection.end.row -= row_delta;
11683 selection
11684 },
11685 ));
11686
11687 // Move folds up
11688 unfold_ranges.push(range_to_move.clone());
11689 for fold in display_map.folds_in_range(
11690 buffer.anchor_before(range_to_move.start)
11691 ..buffer.anchor_after(range_to_move.end),
11692 ) {
11693 let mut start = fold.range.start.to_point(&buffer);
11694 let mut end = fold.range.end.to_point(&buffer);
11695 start.row -= row_delta;
11696 end.row -= row_delta;
11697 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11698 }
11699 }
11700 }
11701
11702 // If we didn't move line(s), preserve the existing selections
11703 new_selections.append(&mut contiguous_row_selections);
11704 }
11705
11706 self.transact(window, cx, |this, window, cx| {
11707 this.unfold_ranges(&unfold_ranges, true, true, cx);
11708 this.buffer.update(cx, |buffer, cx| {
11709 for (range, text) in edits {
11710 buffer.edit([(range, text)], None, cx);
11711 }
11712 });
11713 this.fold_creases(refold_creases, true, window, cx);
11714 this.change_selections(Default::default(), window, cx, |s| {
11715 s.select(new_selections);
11716 })
11717 });
11718 }
11719
11720 pub fn move_line_down(
11721 &mut self,
11722 _: &MoveLineDown,
11723 window: &mut Window,
11724 cx: &mut Context<Self>,
11725 ) {
11726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11727 if self.mode.is_single_line() {
11728 cx.propagate();
11729 return;
11730 }
11731
11732 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11733 let buffer = self.buffer.read(cx).snapshot(cx);
11734
11735 let mut edits = Vec::new();
11736 let mut unfold_ranges = Vec::new();
11737 let mut refold_creases = Vec::new();
11738
11739 let selections = self.selections.all::<Point>(&display_map);
11740 let mut selections = selections.iter().peekable();
11741 let mut contiguous_row_selections = Vec::new();
11742 let mut new_selections = Vec::new();
11743
11744 while let Some(selection) = selections.next() {
11745 // Find all the selections that span a contiguous row range
11746 let (start_row, end_row) = consume_contiguous_rows(
11747 &mut contiguous_row_selections,
11748 selection,
11749 &display_map,
11750 &mut selections,
11751 );
11752
11753 // Move the text spanned by the row range to be after the last line of the row range
11754 if end_row.0 <= buffer.max_point().row {
11755 let range_to_move =
11756 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11757 let insertion_point = display_map
11758 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11759 .0;
11760
11761 // Don't move lines across excerpt boundaries
11762 if buffer
11763 .excerpt_containing(range_to_move.start..insertion_point)
11764 .is_some()
11765 {
11766 let mut text = String::from("\n");
11767 text.extend(buffer.text_for_range(range_to_move.clone()));
11768 text.pop(); // Drop trailing newline
11769 edits.push((
11770 buffer.anchor_after(range_to_move.start)
11771 ..buffer.anchor_before(range_to_move.end),
11772 String::new(),
11773 ));
11774 let insertion_anchor = buffer.anchor_after(insertion_point);
11775 edits.push((insertion_anchor..insertion_anchor, text));
11776
11777 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11778
11779 // Move selections down
11780 new_selections.extend(contiguous_row_selections.drain(..).map(
11781 |mut selection| {
11782 selection.start.row += row_delta;
11783 selection.end.row += row_delta;
11784 selection
11785 },
11786 ));
11787
11788 // Move folds down
11789 unfold_ranges.push(range_to_move.clone());
11790 for fold in display_map.folds_in_range(
11791 buffer.anchor_before(range_to_move.start)
11792 ..buffer.anchor_after(range_to_move.end),
11793 ) {
11794 let mut start = fold.range.start.to_point(&buffer);
11795 let mut end = fold.range.end.to_point(&buffer);
11796 start.row += row_delta;
11797 end.row += row_delta;
11798 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11799 }
11800 }
11801 }
11802
11803 // If we didn't move line(s), preserve the existing selections
11804 new_selections.append(&mut contiguous_row_selections);
11805 }
11806
11807 self.transact(window, cx, |this, window, cx| {
11808 this.unfold_ranges(&unfold_ranges, true, true, cx);
11809 this.buffer.update(cx, |buffer, cx| {
11810 for (range, text) in edits {
11811 buffer.edit([(range, text)], None, cx);
11812 }
11813 });
11814 this.fold_creases(refold_creases, true, window, cx);
11815 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11816 });
11817 }
11818
11819 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11821 let text_layout_details = &self.text_layout_details(window);
11822 self.transact(window, cx, |this, window, cx| {
11823 let edits = this.change_selections(Default::default(), window, cx, |s| {
11824 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11825 s.move_with(|display_map, selection| {
11826 if !selection.is_empty() {
11827 return;
11828 }
11829
11830 let mut head = selection.head();
11831 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11832 if head.column() == display_map.line_len(head.row()) {
11833 transpose_offset = display_map
11834 .buffer_snapshot()
11835 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11836 }
11837
11838 if transpose_offset == 0 {
11839 return;
11840 }
11841
11842 *head.column_mut() += 1;
11843 head = display_map.clip_point(head, Bias::Right);
11844 let goal = SelectionGoal::HorizontalPosition(
11845 display_map
11846 .x_for_display_point(head, text_layout_details)
11847 .into(),
11848 );
11849 selection.collapse_to(head, goal);
11850
11851 let transpose_start = display_map
11852 .buffer_snapshot()
11853 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11854 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11855 let transpose_end = display_map
11856 .buffer_snapshot()
11857 .clip_offset(transpose_offset + 1, Bias::Right);
11858 if let Some(ch) = display_map
11859 .buffer_snapshot()
11860 .chars_at(transpose_start)
11861 .next()
11862 {
11863 edits.push((transpose_start..transpose_offset, String::new()));
11864 edits.push((transpose_end..transpose_end, ch.to_string()));
11865 }
11866 }
11867 });
11868 edits
11869 });
11870 this.buffer
11871 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11872 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11873 this.change_selections(Default::default(), window, cx, |s| {
11874 s.select(selections);
11875 });
11876 });
11877 }
11878
11879 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11881 if self.mode.is_single_line() {
11882 cx.propagate();
11883 return;
11884 }
11885
11886 self.rewrap_impl(RewrapOptions::default(), cx)
11887 }
11888
11889 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11890 let buffer = self.buffer.read(cx).snapshot(cx);
11891 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11892
11893 #[derive(Clone, Debug, PartialEq)]
11894 enum CommentFormat {
11895 /// single line comment, with prefix for line
11896 Line(String),
11897 /// single line within a block comment, with prefix for line
11898 BlockLine(String),
11899 /// a single line of a block comment that includes the initial delimiter
11900 BlockCommentWithStart(BlockCommentConfig),
11901 /// a single line of a block comment that includes the ending delimiter
11902 BlockCommentWithEnd(BlockCommentConfig),
11903 }
11904
11905 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11906 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11907 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11908 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11909 .peekable();
11910
11911 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11912 row
11913 } else {
11914 return Vec::new();
11915 };
11916
11917 let language_settings = buffer.language_settings_at(selection.head(), cx);
11918 let language_scope = buffer.language_scope_at(selection.head());
11919
11920 let indent_and_prefix_for_row =
11921 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11922 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11923 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11924 &language_scope
11925 {
11926 let indent_end = Point::new(row, indent.len);
11927 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11928 let line_text_after_indent = buffer
11929 .text_for_range(indent_end..line_end)
11930 .collect::<String>();
11931
11932 let is_within_comment_override = buffer
11933 .language_scope_at(indent_end)
11934 .is_some_and(|scope| scope.override_name() == Some("comment"));
11935 let comment_delimiters = if is_within_comment_override {
11936 // we are within a comment syntax node, but we don't
11937 // yet know what kind of comment: block, doc or line
11938 match (
11939 language_scope.documentation_comment(),
11940 language_scope.block_comment(),
11941 ) {
11942 (Some(config), _) | (_, Some(config))
11943 if buffer.contains_str_at(indent_end, &config.start) =>
11944 {
11945 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11946 }
11947 (Some(config), _) | (_, Some(config))
11948 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11949 {
11950 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11951 }
11952 (Some(config), _) | (_, Some(config))
11953 if buffer.contains_str_at(indent_end, &config.prefix) =>
11954 {
11955 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11956 }
11957 (_, _) => language_scope
11958 .line_comment_prefixes()
11959 .iter()
11960 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11961 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11962 }
11963 } else {
11964 // we not in an overridden comment node, but we may
11965 // be within a non-overridden line comment node
11966 language_scope
11967 .line_comment_prefixes()
11968 .iter()
11969 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11970 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11971 };
11972
11973 let rewrap_prefix = language_scope
11974 .rewrap_prefixes()
11975 .iter()
11976 .find_map(|prefix_regex| {
11977 prefix_regex.find(&line_text_after_indent).map(|mat| {
11978 if mat.start() == 0 {
11979 Some(mat.as_str().to_string())
11980 } else {
11981 None
11982 }
11983 })
11984 })
11985 .flatten();
11986 (comment_delimiters, rewrap_prefix)
11987 } else {
11988 (None, None)
11989 };
11990 (indent, comment_prefix, rewrap_prefix)
11991 };
11992
11993 let mut ranges = Vec::new();
11994 let from_empty_selection = selection.is_empty();
11995
11996 let mut current_range_start = first_row;
11997 let mut prev_row = first_row;
11998 let (
11999 mut current_range_indent,
12000 mut current_range_comment_delimiters,
12001 mut current_range_rewrap_prefix,
12002 ) = indent_and_prefix_for_row(first_row);
12003
12004 for row in non_blank_rows_iter.skip(1) {
12005 let has_paragraph_break = row > prev_row + 1;
12006
12007 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12008 indent_and_prefix_for_row(row);
12009
12010 let has_indent_change = row_indent != current_range_indent;
12011 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12012
12013 let has_boundary_change = has_comment_change
12014 || row_rewrap_prefix.is_some()
12015 || (has_indent_change && current_range_comment_delimiters.is_some());
12016
12017 if has_paragraph_break || has_boundary_change {
12018 ranges.push((
12019 language_settings.clone(),
12020 Point::new(current_range_start, 0)
12021 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12022 current_range_indent,
12023 current_range_comment_delimiters.clone(),
12024 current_range_rewrap_prefix.clone(),
12025 from_empty_selection,
12026 ));
12027 current_range_start = row;
12028 current_range_indent = row_indent;
12029 current_range_comment_delimiters = row_comment_delimiters;
12030 current_range_rewrap_prefix = row_rewrap_prefix;
12031 }
12032 prev_row = row;
12033 }
12034
12035 ranges.push((
12036 language_settings.clone(),
12037 Point::new(current_range_start, 0)
12038 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12039 current_range_indent,
12040 current_range_comment_delimiters,
12041 current_range_rewrap_prefix,
12042 from_empty_selection,
12043 ));
12044
12045 ranges
12046 });
12047
12048 let mut edits = Vec::new();
12049 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12050
12051 for (
12052 language_settings,
12053 wrap_range,
12054 mut indent_size,
12055 comment_prefix,
12056 rewrap_prefix,
12057 from_empty_selection,
12058 ) in wrap_ranges
12059 {
12060 let mut start_row = wrap_range.start.row;
12061 let mut end_row = wrap_range.end.row;
12062
12063 // Skip selections that overlap with a range that has already been rewrapped.
12064 let selection_range = start_row..end_row;
12065 if rewrapped_row_ranges
12066 .iter()
12067 .any(|range| range.overlaps(&selection_range))
12068 {
12069 continue;
12070 }
12071
12072 let tab_size = language_settings.tab_size;
12073
12074 let (line_prefix, inside_comment) = match &comment_prefix {
12075 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12076 (Some(prefix.as_str()), true)
12077 }
12078 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12079 (Some(prefix.as_ref()), true)
12080 }
12081 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12082 start: _,
12083 end: _,
12084 prefix,
12085 tab_size,
12086 })) => {
12087 indent_size.len += tab_size;
12088 (Some(prefix.as_ref()), true)
12089 }
12090 None => (None, false),
12091 };
12092 let indent_prefix = indent_size.chars().collect::<String>();
12093 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12094
12095 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12096 RewrapBehavior::InComments => inside_comment,
12097 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12098 RewrapBehavior::Anywhere => true,
12099 };
12100
12101 let should_rewrap = options.override_language_settings
12102 || allow_rewrap_based_on_language
12103 || self.hard_wrap.is_some();
12104 if !should_rewrap {
12105 continue;
12106 }
12107
12108 if from_empty_selection {
12109 'expand_upwards: while start_row > 0 {
12110 let prev_row = start_row - 1;
12111 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12112 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12113 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12114 {
12115 start_row = prev_row;
12116 } else {
12117 break 'expand_upwards;
12118 }
12119 }
12120
12121 'expand_downwards: while end_row < buffer.max_point().row {
12122 let next_row = end_row + 1;
12123 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12124 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12125 && !buffer.is_line_blank(MultiBufferRow(next_row))
12126 {
12127 end_row = next_row;
12128 } else {
12129 break 'expand_downwards;
12130 }
12131 }
12132 }
12133
12134 let start = Point::new(start_row, 0);
12135 let start_offset = ToOffset::to_offset(&start, &buffer);
12136 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12137 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12138 let mut first_line_delimiter = None;
12139 let mut last_line_delimiter = None;
12140 let Some(lines_without_prefixes) = selection_text
12141 .lines()
12142 .enumerate()
12143 .map(|(ix, line)| {
12144 let line_trimmed = line.trim_start();
12145 if rewrap_prefix.is_some() && ix > 0 {
12146 Ok(line_trimmed)
12147 } else if let Some(
12148 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12149 start,
12150 prefix,
12151 end,
12152 tab_size,
12153 })
12154 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12155 start,
12156 prefix,
12157 end,
12158 tab_size,
12159 }),
12160 ) = &comment_prefix
12161 {
12162 let line_trimmed = line_trimmed
12163 .strip_prefix(start.as_ref())
12164 .map(|s| {
12165 let mut indent_size = indent_size;
12166 indent_size.len -= tab_size;
12167 let indent_prefix: String = indent_size.chars().collect();
12168 first_line_delimiter = Some((indent_prefix, start));
12169 s.trim_start()
12170 })
12171 .unwrap_or(line_trimmed);
12172 let line_trimmed = line_trimmed
12173 .strip_suffix(end.as_ref())
12174 .map(|s| {
12175 last_line_delimiter = Some(end);
12176 s.trim_end()
12177 })
12178 .unwrap_or(line_trimmed);
12179 let line_trimmed = line_trimmed
12180 .strip_prefix(prefix.as_ref())
12181 .unwrap_or(line_trimmed);
12182 Ok(line_trimmed)
12183 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12184 line_trimmed.strip_prefix(prefix).with_context(|| {
12185 format!("line did not start with prefix {prefix:?}: {line:?}")
12186 })
12187 } else {
12188 line_trimmed
12189 .strip_prefix(&line_prefix.trim_start())
12190 .with_context(|| {
12191 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12192 })
12193 }
12194 })
12195 .collect::<Result<Vec<_>, _>>()
12196 .log_err()
12197 else {
12198 continue;
12199 };
12200
12201 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12202 buffer
12203 .language_settings_at(Point::new(start_row, 0), cx)
12204 .preferred_line_length as usize
12205 });
12206
12207 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12208 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12209 } else {
12210 line_prefix.clone()
12211 };
12212
12213 let wrapped_text = {
12214 let mut wrapped_text = wrap_with_prefix(
12215 line_prefix,
12216 subsequent_lines_prefix,
12217 lines_without_prefixes.join("\n"),
12218 wrap_column,
12219 tab_size,
12220 options.preserve_existing_whitespace,
12221 );
12222
12223 if let Some((indent, delimiter)) = first_line_delimiter {
12224 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12225 }
12226 if let Some(last_line) = last_line_delimiter {
12227 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12228 }
12229
12230 wrapped_text
12231 };
12232
12233 // TODO: should always use char-based diff while still supporting cursor behavior that
12234 // matches vim.
12235 let mut diff_options = DiffOptions::default();
12236 if options.override_language_settings {
12237 diff_options.max_word_diff_len = 0;
12238 diff_options.max_word_diff_line_count = 0;
12239 } else {
12240 diff_options.max_word_diff_len = usize::MAX;
12241 diff_options.max_word_diff_line_count = usize::MAX;
12242 }
12243
12244 for (old_range, new_text) in
12245 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12246 {
12247 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12248 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12249 edits.push((edit_start..edit_end, new_text));
12250 }
12251
12252 rewrapped_row_ranges.push(start_row..=end_row);
12253 }
12254
12255 self.buffer
12256 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12257 }
12258
12259 pub fn cut_common(
12260 &mut self,
12261 cut_no_selection_line: bool,
12262 window: &mut Window,
12263 cx: &mut Context<Self>,
12264 ) -> ClipboardItem {
12265 let mut text = String::new();
12266 let buffer = self.buffer.read(cx).snapshot(cx);
12267 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12268 let mut clipboard_selections = Vec::with_capacity(selections.len());
12269 {
12270 let max_point = buffer.max_point();
12271 let mut is_first = true;
12272 for selection in &mut selections {
12273 let is_entire_line =
12274 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12275 if is_entire_line {
12276 selection.start = Point::new(selection.start.row, 0);
12277 if !selection.is_empty() && selection.end.column == 0 {
12278 selection.end = cmp::min(max_point, selection.end);
12279 } else {
12280 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12281 }
12282 selection.goal = SelectionGoal::None;
12283 }
12284 if is_first {
12285 is_first = false;
12286 } else {
12287 text += "\n";
12288 }
12289 let mut len = 0;
12290 for chunk in buffer.text_for_range(selection.start..selection.end) {
12291 text.push_str(chunk);
12292 len += chunk.len();
12293 }
12294 clipboard_selections.push(ClipboardSelection {
12295 len,
12296 is_entire_line,
12297 first_line_indent: buffer
12298 .indent_size_for_line(MultiBufferRow(selection.start.row))
12299 .len,
12300 });
12301 }
12302 }
12303
12304 self.transact(window, cx, |this, window, cx| {
12305 this.change_selections(Default::default(), window, cx, |s| {
12306 s.select(selections);
12307 });
12308 this.insert("", window, cx);
12309 });
12310 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12311 }
12312
12313 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12314 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12315 let item = self.cut_common(true, window, cx);
12316 cx.write_to_clipboard(item);
12317 }
12318
12319 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12321 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12322 s.move_with(|snapshot, sel| {
12323 if sel.is_empty() {
12324 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12325 }
12326 if sel.is_empty() {
12327 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12328 }
12329 });
12330 });
12331 let item = self.cut_common(false, window, cx);
12332 cx.set_global(KillRing(item))
12333 }
12334
12335 pub fn kill_ring_yank(
12336 &mut self,
12337 _: &KillRingYank,
12338 window: &mut Window,
12339 cx: &mut Context<Self>,
12340 ) {
12341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12342 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12343 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12344 (kill_ring.text().to_string(), kill_ring.metadata_json())
12345 } else {
12346 return;
12347 }
12348 } else {
12349 return;
12350 };
12351 self.do_paste(&text, metadata, false, window, cx);
12352 }
12353
12354 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12355 self.do_copy(true, cx);
12356 }
12357
12358 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12359 self.do_copy(false, cx);
12360 }
12361
12362 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12363 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12364 let buffer = self.buffer.read(cx).read(cx);
12365 let mut text = String::new();
12366
12367 let mut clipboard_selections = Vec::with_capacity(selections.len());
12368 {
12369 let max_point = buffer.max_point();
12370 let mut is_first = true;
12371 for selection in &selections {
12372 let mut start = selection.start;
12373 let mut end = selection.end;
12374 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12375 let mut add_trailing_newline = false;
12376 if is_entire_line {
12377 start = Point::new(start.row, 0);
12378 let next_line_start = Point::new(end.row + 1, 0);
12379 if next_line_start <= max_point {
12380 end = next_line_start;
12381 } else {
12382 // We're on the last line without a trailing newline.
12383 // Copy to the end of the line and add a newline afterwards.
12384 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12385 add_trailing_newline = true;
12386 }
12387 }
12388
12389 let mut trimmed_selections = Vec::new();
12390 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12391 let row = MultiBufferRow(start.row);
12392 let first_indent = buffer.indent_size_for_line(row);
12393 if first_indent.len == 0 || start.column > first_indent.len {
12394 trimmed_selections.push(start..end);
12395 } else {
12396 trimmed_selections.push(
12397 Point::new(row.0, first_indent.len)
12398 ..Point::new(row.0, buffer.line_len(row)),
12399 );
12400 for row in start.row + 1..=end.row {
12401 let mut line_len = buffer.line_len(MultiBufferRow(row));
12402 if row == end.row {
12403 line_len = end.column;
12404 }
12405 if line_len == 0 {
12406 trimmed_selections
12407 .push(Point::new(row, 0)..Point::new(row, line_len));
12408 continue;
12409 }
12410 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12411 if row_indent_size.len >= first_indent.len {
12412 trimmed_selections.push(
12413 Point::new(row, first_indent.len)..Point::new(row, line_len),
12414 );
12415 } else {
12416 trimmed_selections.clear();
12417 trimmed_selections.push(start..end);
12418 break;
12419 }
12420 }
12421 }
12422 } else {
12423 trimmed_selections.push(start..end);
12424 }
12425
12426 for trimmed_range in trimmed_selections {
12427 if is_first {
12428 is_first = false;
12429 } else {
12430 text += "\n";
12431 }
12432 let mut len = 0;
12433 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12434 text.push_str(chunk);
12435 len += chunk.len();
12436 }
12437 if add_trailing_newline {
12438 text.push('\n');
12439 len += 1;
12440 }
12441 clipboard_selections.push(ClipboardSelection {
12442 len,
12443 is_entire_line,
12444 first_line_indent: buffer
12445 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12446 .len,
12447 });
12448 }
12449 }
12450 }
12451
12452 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12453 text,
12454 clipboard_selections,
12455 ));
12456 }
12457
12458 pub fn do_paste(
12459 &mut self,
12460 text: &String,
12461 clipboard_selections: Option<Vec<ClipboardSelection>>,
12462 handle_entire_lines: bool,
12463 window: &mut Window,
12464 cx: &mut Context<Self>,
12465 ) {
12466 if self.read_only(cx) {
12467 return;
12468 }
12469
12470 let clipboard_text = Cow::Borrowed(text.as_str());
12471
12472 self.transact(window, cx, |this, window, cx| {
12473 let had_active_edit_prediction = this.has_active_edit_prediction();
12474 let display_map = this.display_snapshot(cx);
12475 let old_selections = this.selections.all::<usize>(&display_map);
12476 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12477
12478 if let Some(mut clipboard_selections) = clipboard_selections {
12479 let all_selections_were_entire_line =
12480 clipboard_selections.iter().all(|s| s.is_entire_line);
12481 let first_selection_indent_column =
12482 clipboard_selections.first().map(|s| s.first_line_indent);
12483 if clipboard_selections.len() != old_selections.len() {
12484 clipboard_selections.drain(..);
12485 }
12486 let mut auto_indent_on_paste = true;
12487
12488 this.buffer.update(cx, |buffer, cx| {
12489 let snapshot = buffer.read(cx);
12490 auto_indent_on_paste = snapshot
12491 .language_settings_at(cursor_offset, cx)
12492 .auto_indent_on_paste;
12493
12494 let mut start_offset = 0;
12495 let mut edits = Vec::new();
12496 let mut original_indent_columns = Vec::new();
12497 for (ix, selection) in old_selections.iter().enumerate() {
12498 let to_insert;
12499 let entire_line;
12500 let original_indent_column;
12501 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12502 let end_offset = start_offset + clipboard_selection.len;
12503 to_insert = &clipboard_text[start_offset..end_offset];
12504 entire_line = clipboard_selection.is_entire_line;
12505 start_offset = end_offset + 1;
12506 original_indent_column = Some(clipboard_selection.first_line_indent);
12507 } else {
12508 to_insert = &*clipboard_text;
12509 entire_line = all_selections_were_entire_line;
12510 original_indent_column = first_selection_indent_column
12511 }
12512
12513 let (range, to_insert) =
12514 if selection.is_empty() && handle_entire_lines && entire_line {
12515 // If the corresponding selection was empty when this slice of the
12516 // clipboard text was written, then the entire line containing the
12517 // selection was copied. If this selection is also currently empty,
12518 // then paste the line before the current line of the buffer.
12519 let column = selection.start.to_point(&snapshot).column as usize;
12520 let line_start = selection.start - column;
12521 (line_start..line_start, Cow::Borrowed(to_insert))
12522 } else {
12523 let language = snapshot.language_at(selection.head());
12524 let range = selection.range();
12525 if let Some(language) = language
12526 && language.name() == "Markdown".into()
12527 {
12528 edit_for_markdown_paste(
12529 &snapshot,
12530 range,
12531 to_insert,
12532 url::Url::parse(to_insert).ok(),
12533 )
12534 } else {
12535 (range, Cow::Borrowed(to_insert))
12536 }
12537 };
12538
12539 edits.push((range, to_insert));
12540 original_indent_columns.push(original_indent_column);
12541 }
12542 drop(snapshot);
12543
12544 buffer.edit(
12545 edits,
12546 if auto_indent_on_paste {
12547 Some(AutoindentMode::Block {
12548 original_indent_columns,
12549 })
12550 } else {
12551 None
12552 },
12553 cx,
12554 );
12555 });
12556
12557 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12558 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12559 } else {
12560 let url = url::Url::parse(&clipboard_text).ok();
12561
12562 let auto_indent_mode = if !clipboard_text.is_empty() {
12563 Some(AutoindentMode::Block {
12564 original_indent_columns: Vec::new(),
12565 })
12566 } else {
12567 None
12568 };
12569
12570 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12571 let snapshot = buffer.snapshot(cx);
12572
12573 let anchors = old_selections
12574 .iter()
12575 .map(|s| {
12576 let anchor = snapshot.anchor_after(s.head());
12577 s.map(|_| anchor)
12578 })
12579 .collect::<Vec<_>>();
12580
12581 let mut edits = Vec::new();
12582
12583 for selection in old_selections.iter() {
12584 let language = snapshot.language_at(selection.head());
12585 let range = selection.range();
12586
12587 let (edit_range, edit_text) = if let Some(language) = language
12588 && language.name() == "Markdown".into()
12589 {
12590 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12591 } else {
12592 (range, clipboard_text.clone())
12593 };
12594
12595 edits.push((edit_range, edit_text));
12596 }
12597
12598 drop(snapshot);
12599 buffer.edit(edits, auto_indent_mode, cx);
12600
12601 anchors
12602 });
12603
12604 this.change_selections(Default::default(), window, cx, |s| {
12605 s.select_anchors(selection_anchors);
12606 });
12607 }
12608
12609 let trigger_in_words =
12610 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12611
12612 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12613 });
12614 }
12615
12616 pub fn diff_clipboard_with_selection(
12617 &mut self,
12618 _: &DiffClipboardWithSelection,
12619 window: &mut Window,
12620 cx: &mut Context<Self>,
12621 ) {
12622 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12623
12624 if selections.is_empty() {
12625 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12626 return;
12627 };
12628
12629 let clipboard_text = match cx.read_from_clipboard() {
12630 Some(item) => match item.entries().first() {
12631 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12632 _ => None,
12633 },
12634 None => None,
12635 };
12636
12637 let Some(clipboard_text) = clipboard_text else {
12638 log::warn!("Clipboard doesn't contain text.");
12639 return;
12640 };
12641
12642 window.dispatch_action(
12643 Box::new(DiffClipboardWithSelectionData {
12644 clipboard_text,
12645 editor: cx.entity(),
12646 }),
12647 cx,
12648 );
12649 }
12650
12651 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12652 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12653 if let Some(item) = cx.read_from_clipboard() {
12654 let entries = item.entries();
12655
12656 match entries.first() {
12657 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12658 // of all the pasted entries.
12659 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12660 .do_paste(
12661 clipboard_string.text(),
12662 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12663 true,
12664 window,
12665 cx,
12666 ),
12667 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12668 }
12669 }
12670 }
12671
12672 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12673 if self.read_only(cx) {
12674 return;
12675 }
12676
12677 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12678
12679 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12680 if let Some((selections, _)) =
12681 self.selection_history.transaction(transaction_id).cloned()
12682 {
12683 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12684 s.select_anchors(selections.to_vec());
12685 });
12686 } else {
12687 log::error!(
12688 "No entry in selection_history found for undo. \
12689 This may correspond to a bug where undo does not update the selection. \
12690 If this is occurring, please add details to \
12691 https://github.com/zed-industries/zed/issues/22692"
12692 );
12693 }
12694 self.request_autoscroll(Autoscroll::fit(), cx);
12695 self.unmark_text(window, cx);
12696 self.refresh_edit_prediction(true, false, window, cx);
12697 cx.emit(EditorEvent::Edited { transaction_id });
12698 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12699 }
12700 }
12701
12702 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12703 if self.read_only(cx) {
12704 return;
12705 }
12706
12707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12708
12709 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12710 if let Some((_, Some(selections))) =
12711 self.selection_history.transaction(transaction_id).cloned()
12712 {
12713 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12714 s.select_anchors(selections.to_vec());
12715 });
12716 } else {
12717 log::error!(
12718 "No entry in selection_history found for redo. \
12719 This may correspond to a bug where undo does not update the selection. \
12720 If this is occurring, please add details to \
12721 https://github.com/zed-industries/zed/issues/22692"
12722 );
12723 }
12724 self.request_autoscroll(Autoscroll::fit(), cx);
12725 self.unmark_text(window, cx);
12726 self.refresh_edit_prediction(true, false, window, cx);
12727 cx.emit(EditorEvent::Edited { transaction_id });
12728 }
12729 }
12730
12731 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12732 self.buffer
12733 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12734 }
12735
12736 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12737 self.buffer
12738 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12739 }
12740
12741 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12743 self.change_selections(Default::default(), window, cx, |s| {
12744 s.move_with(|map, selection| {
12745 let cursor = if selection.is_empty() {
12746 movement::left(map, selection.start)
12747 } else {
12748 selection.start
12749 };
12750 selection.collapse_to(cursor, SelectionGoal::None);
12751 });
12752 })
12753 }
12754
12755 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12757 self.change_selections(Default::default(), window, cx, |s| {
12758 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12759 })
12760 }
12761
12762 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12764 self.change_selections(Default::default(), window, cx, |s| {
12765 s.move_with(|map, selection| {
12766 let cursor = if selection.is_empty() {
12767 movement::right(map, selection.end)
12768 } else {
12769 selection.end
12770 };
12771 selection.collapse_to(cursor, SelectionGoal::None)
12772 });
12773 })
12774 }
12775
12776 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12778 self.change_selections(Default::default(), window, cx, |s| {
12779 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12780 });
12781 }
12782
12783 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12784 if self.take_rename(true, window, cx).is_some() {
12785 return;
12786 }
12787
12788 if self.mode.is_single_line() {
12789 cx.propagate();
12790 return;
12791 }
12792
12793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12794
12795 let text_layout_details = &self.text_layout_details(window);
12796 let selection_count = self.selections.count();
12797 let first_selection = self.selections.first_anchor();
12798
12799 self.change_selections(Default::default(), window, cx, |s| {
12800 s.move_with(|map, selection| {
12801 if !selection.is_empty() {
12802 selection.goal = SelectionGoal::None;
12803 }
12804 let (cursor, goal) = movement::up(
12805 map,
12806 selection.start,
12807 selection.goal,
12808 false,
12809 text_layout_details,
12810 );
12811 selection.collapse_to(cursor, goal);
12812 });
12813 });
12814
12815 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12816 {
12817 cx.propagate();
12818 }
12819 }
12820
12821 pub fn move_up_by_lines(
12822 &mut self,
12823 action: &MoveUpByLines,
12824 window: &mut Window,
12825 cx: &mut Context<Self>,
12826 ) {
12827 if self.take_rename(true, window, cx).is_some() {
12828 return;
12829 }
12830
12831 if self.mode.is_single_line() {
12832 cx.propagate();
12833 return;
12834 }
12835
12836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12837
12838 let text_layout_details = &self.text_layout_details(window);
12839
12840 self.change_selections(Default::default(), window, cx, |s| {
12841 s.move_with(|map, selection| {
12842 if !selection.is_empty() {
12843 selection.goal = SelectionGoal::None;
12844 }
12845 let (cursor, goal) = movement::up_by_rows(
12846 map,
12847 selection.start,
12848 action.lines,
12849 selection.goal,
12850 false,
12851 text_layout_details,
12852 );
12853 selection.collapse_to(cursor, goal);
12854 });
12855 })
12856 }
12857
12858 pub fn move_down_by_lines(
12859 &mut self,
12860 action: &MoveDownByLines,
12861 window: &mut Window,
12862 cx: &mut Context<Self>,
12863 ) {
12864 if self.take_rename(true, window, cx).is_some() {
12865 return;
12866 }
12867
12868 if self.mode.is_single_line() {
12869 cx.propagate();
12870 return;
12871 }
12872
12873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12874
12875 let text_layout_details = &self.text_layout_details(window);
12876
12877 self.change_selections(Default::default(), window, cx, |s| {
12878 s.move_with(|map, selection| {
12879 if !selection.is_empty() {
12880 selection.goal = SelectionGoal::None;
12881 }
12882 let (cursor, goal) = movement::down_by_rows(
12883 map,
12884 selection.start,
12885 action.lines,
12886 selection.goal,
12887 false,
12888 text_layout_details,
12889 );
12890 selection.collapse_to(cursor, goal);
12891 });
12892 })
12893 }
12894
12895 pub fn select_down_by_lines(
12896 &mut self,
12897 action: &SelectDownByLines,
12898 window: &mut Window,
12899 cx: &mut Context<Self>,
12900 ) {
12901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12902 let text_layout_details = &self.text_layout_details(window);
12903 self.change_selections(Default::default(), window, cx, |s| {
12904 s.move_heads_with(|map, head, goal| {
12905 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12906 })
12907 })
12908 }
12909
12910 pub fn select_up_by_lines(
12911 &mut self,
12912 action: &SelectUpByLines,
12913 window: &mut Window,
12914 cx: &mut Context<Self>,
12915 ) {
12916 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12917 let text_layout_details = &self.text_layout_details(window);
12918 self.change_selections(Default::default(), window, cx, |s| {
12919 s.move_heads_with(|map, head, goal| {
12920 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12921 })
12922 })
12923 }
12924
12925 pub fn select_page_up(
12926 &mut self,
12927 _: &SelectPageUp,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 let Some(row_count) = self.visible_row_count() else {
12932 return;
12933 };
12934
12935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12936
12937 let text_layout_details = &self.text_layout_details(window);
12938
12939 self.change_selections(Default::default(), window, cx, |s| {
12940 s.move_heads_with(|map, head, goal| {
12941 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12942 })
12943 })
12944 }
12945
12946 pub fn move_page_up(
12947 &mut self,
12948 action: &MovePageUp,
12949 window: &mut Window,
12950 cx: &mut Context<Self>,
12951 ) {
12952 if self.take_rename(true, window, cx).is_some() {
12953 return;
12954 }
12955
12956 if self
12957 .context_menu
12958 .borrow_mut()
12959 .as_mut()
12960 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12961 .unwrap_or(false)
12962 {
12963 return;
12964 }
12965
12966 if matches!(self.mode, EditorMode::SingleLine) {
12967 cx.propagate();
12968 return;
12969 }
12970
12971 let Some(row_count) = self.visible_row_count() else {
12972 return;
12973 };
12974
12975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12976
12977 let effects = if action.center_cursor {
12978 SelectionEffects::scroll(Autoscroll::center())
12979 } else {
12980 SelectionEffects::default()
12981 };
12982
12983 let text_layout_details = &self.text_layout_details(window);
12984
12985 self.change_selections(effects, window, cx, |s| {
12986 s.move_with(|map, selection| {
12987 if !selection.is_empty() {
12988 selection.goal = SelectionGoal::None;
12989 }
12990 let (cursor, goal) = movement::up_by_rows(
12991 map,
12992 selection.end,
12993 row_count,
12994 selection.goal,
12995 false,
12996 text_layout_details,
12997 );
12998 selection.collapse_to(cursor, goal);
12999 });
13000 });
13001 }
13002
13003 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13005 let text_layout_details = &self.text_layout_details(window);
13006 self.change_selections(Default::default(), window, cx, |s| {
13007 s.move_heads_with(|map, head, goal| {
13008 movement::up(map, head, goal, false, text_layout_details)
13009 })
13010 })
13011 }
13012
13013 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13014 self.take_rename(true, window, cx);
13015
13016 if self.mode.is_single_line() {
13017 cx.propagate();
13018 return;
13019 }
13020
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13022
13023 let text_layout_details = &self.text_layout_details(window);
13024 let selection_count = self.selections.count();
13025 let first_selection = self.selections.first_anchor();
13026
13027 self.change_selections(Default::default(), window, cx, |s| {
13028 s.move_with(|map, selection| {
13029 if !selection.is_empty() {
13030 selection.goal = SelectionGoal::None;
13031 }
13032 let (cursor, goal) = movement::down(
13033 map,
13034 selection.end,
13035 selection.goal,
13036 false,
13037 text_layout_details,
13038 );
13039 selection.collapse_to(cursor, goal);
13040 });
13041 });
13042
13043 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13044 {
13045 cx.propagate();
13046 }
13047 }
13048
13049 pub fn select_page_down(
13050 &mut self,
13051 _: &SelectPageDown,
13052 window: &mut Window,
13053 cx: &mut Context<Self>,
13054 ) {
13055 let Some(row_count) = self.visible_row_count() else {
13056 return;
13057 };
13058
13059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13060
13061 let text_layout_details = &self.text_layout_details(window);
13062
13063 self.change_selections(Default::default(), window, cx, |s| {
13064 s.move_heads_with(|map, head, goal| {
13065 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13066 })
13067 })
13068 }
13069
13070 pub fn move_page_down(
13071 &mut self,
13072 action: &MovePageDown,
13073 window: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 if self.take_rename(true, window, cx).is_some() {
13077 return;
13078 }
13079
13080 if self
13081 .context_menu
13082 .borrow_mut()
13083 .as_mut()
13084 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13085 .unwrap_or(false)
13086 {
13087 return;
13088 }
13089
13090 if matches!(self.mode, EditorMode::SingleLine) {
13091 cx.propagate();
13092 return;
13093 }
13094
13095 let Some(row_count) = self.visible_row_count() else {
13096 return;
13097 };
13098
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100
13101 let effects = if action.center_cursor {
13102 SelectionEffects::scroll(Autoscroll::center())
13103 } else {
13104 SelectionEffects::default()
13105 };
13106
13107 let text_layout_details = &self.text_layout_details(window);
13108 self.change_selections(effects, window, cx, |s| {
13109 s.move_with(|map, selection| {
13110 if !selection.is_empty() {
13111 selection.goal = SelectionGoal::None;
13112 }
13113 let (cursor, goal) = movement::down_by_rows(
13114 map,
13115 selection.end,
13116 row_count,
13117 selection.goal,
13118 false,
13119 text_layout_details,
13120 );
13121 selection.collapse_to(cursor, goal);
13122 });
13123 });
13124 }
13125
13126 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13128 let text_layout_details = &self.text_layout_details(window);
13129 self.change_selections(Default::default(), window, cx, |s| {
13130 s.move_heads_with(|map, head, goal| {
13131 movement::down(map, head, goal, false, text_layout_details)
13132 })
13133 });
13134 }
13135
13136 pub fn context_menu_first(
13137 &mut self,
13138 _: &ContextMenuFirst,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) {
13142 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13143 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13144 }
13145 }
13146
13147 pub fn context_menu_prev(
13148 &mut self,
13149 _: &ContextMenuPrevious,
13150 window: &mut Window,
13151 cx: &mut Context<Self>,
13152 ) {
13153 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13154 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13155 }
13156 }
13157
13158 pub fn context_menu_next(
13159 &mut self,
13160 _: &ContextMenuNext,
13161 window: &mut Window,
13162 cx: &mut Context<Self>,
13163 ) {
13164 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13165 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13166 }
13167 }
13168
13169 pub fn context_menu_last(
13170 &mut self,
13171 _: &ContextMenuLast,
13172 window: &mut Window,
13173 cx: &mut Context<Self>,
13174 ) {
13175 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13176 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13177 }
13178 }
13179
13180 pub fn signature_help_prev(
13181 &mut self,
13182 _: &SignatureHelpPrevious,
13183 _: &mut Window,
13184 cx: &mut Context<Self>,
13185 ) {
13186 if let Some(popover) = self.signature_help_state.popover_mut() {
13187 if popover.current_signature == 0 {
13188 popover.current_signature = popover.signatures.len() - 1;
13189 } else {
13190 popover.current_signature -= 1;
13191 }
13192 cx.notify();
13193 }
13194 }
13195
13196 pub fn signature_help_next(
13197 &mut self,
13198 _: &SignatureHelpNext,
13199 _: &mut Window,
13200 cx: &mut Context<Self>,
13201 ) {
13202 if let Some(popover) = self.signature_help_state.popover_mut() {
13203 if popover.current_signature + 1 == popover.signatures.len() {
13204 popover.current_signature = 0;
13205 } else {
13206 popover.current_signature += 1;
13207 }
13208 cx.notify();
13209 }
13210 }
13211
13212 pub fn move_to_previous_word_start(
13213 &mut self,
13214 _: &MoveToPreviousWordStart,
13215 window: &mut Window,
13216 cx: &mut Context<Self>,
13217 ) {
13218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13219 self.change_selections(Default::default(), window, cx, |s| {
13220 s.move_cursors_with(|map, head, _| {
13221 (
13222 movement::previous_word_start(map, head),
13223 SelectionGoal::None,
13224 )
13225 });
13226 })
13227 }
13228
13229 pub fn move_to_previous_subword_start(
13230 &mut self,
13231 _: &MoveToPreviousSubwordStart,
13232 window: &mut Window,
13233 cx: &mut Context<Self>,
13234 ) {
13235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13236 self.change_selections(Default::default(), window, cx, |s| {
13237 s.move_cursors_with(|map, head, _| {
13238 (
13239 movement::previous_subword_start(map, head),
13240 SelectionGoal::None,
13241 )
13242 });
13243 })
13244 }
13245
13246 pub fn select_to_previous_word_start(
13247 &mut self,
13248 _: &SelectToPreviousWordStart,
13249 window: &mut Window,
13250 cx: &mut Context<Self>,
13251 ) {
13252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13253 self.change_selections(Default::default(), window, cx, |s| {
13254 s.move_heads_with(|map, head, _| {
13255 (
13256 movement::previous_word_start(map, head),
13257 SelectionGoal::None,
13258 )
13259 });
13260 })
13261 }
13262
13263 pub fn select_to_previous_subword_start(
13264 &mut self,
13265 _: &SelectToPreviousSubwordStart,
13266 window: &mut Window,
13267 cx: &mut Context<Self>,
13268 ) {
13269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13270 self.change_selections(Default::default(), window, cx, |s| {
13271 s.move_heads_with(|map, head, _| {
13272 (
13273 movement::previous_subword_start(map, head),
13274 SelectionGoal::None,
13275 )
13276 });
13277 })
13278 }
13279
13280 pub fn delete_to_previous_word_start(
13281 &mut self,
13282 action: &DeleteToPreviousWordStart,
13283 window: &mut Window,
13284 cx: &mut Context<Self>,
13285 ) {
13286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13287 self.transact(window, cx, |this, window, cx| {
13288 this.select_autoclose_pair(window, cx);
13289 this.change_selections(Default::default(), window, cx, |s| {
13290 s.move_with(|map, selection| {
13291 if selection.is_empty() {
13292 let mut cursor = if action.ignore_newlines {
13293 movement::previous_word_start(map, selection.head())
13294 } else {
13295 movement::previous_word_start_or_newline(map, selection.head())
13296 };
13297 cursor = movement::adjust_greedy_deletion(
13298 map,
13299 selection.head(),
13300 cursor,
13301 action.ignore_brackets,
13302 );
13303 selection.set_head(cursor, SelectionGoal::None);
13304 }
13305 });
13306 });
13307 this.insert("", window, cx);
13308 });
13309 }
13310
13311 pub fn delete_to_previous_subword_start(
13312 &mut self,
13313 _: &DeleteToPreviousSubwordStart,
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.select_autoclose_pair(window, cx);
13320 this.change_selections(Default::default(), window, cx, |s| {
13321 s.move_with(|map, selection| {
13322 if selection.is_empty() {
13323 let mut cursor = movement::previous_subword_start(map, selection.head());
13324 cursor =
13325 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13326 selection.set_head(cursor, SelectionGoal::None);
13327 }
13328 });
13329 });
13330 this.insert("", window, cx);
13331 });
13332 }
13333
13334 pub fn move_to_next_word_end(
13335 &mut self,
13336 _: &MoveToNextWordEnd,
13337 window: &mut Window,
13338 cx: &mut Context<Self>,
13339 ) {
13340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13341 self.change_selections(Default::default(), window, cx, |s| {
13342 s.move_cursors_with(|map, head, _| {
13343 (movement::next_word_end(map, head), SelectionGoal::None)
13344 });
13345 })
13346 }
13347
13348 pub fn move_to_next_subword_end(
13349 &mut self,
13350 _: &MoveToNextSubwordEnd,
13351 window: &mut Window,
13352 cx: &mut Context<Self>,
13353 ) {
13354 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13355 self.change_selections(Default::default(), window, cx, |s| {
13356 s.move_cursors_with(|map, head, _| {
13357 (movement::next_subword_end(map, head), SelectionGoal::None)
13358 });
13359 })
13360 }
13361
13362 pub fn select_to_next_word_end(
13363 &mut self,
13364 _: &SelectToNextWordEnd,
13365 window: &mut Window,
13366 cx: &mut Context<Self>,
13367 ) {
13368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13369 self.change_selections(Default::default(), window, cx, |s| {
13370 s.move_heads_with(|map, head, _| {
13371 (movement::next_word_end(map, head), SelectionGoal::None)
13372 });
13373 })
13374 }
13375
13376 pub fn select_to_next_subword_end(
13377 &mut self,
13378 _: &SelectToNextSubwordEnd,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383 self.change_selections(Default::default(), window, cx, |s| {
13384 s.move_heads_with(|map, head, _| {
13385 (movement::next_subword_end(map, head), SelectionGoal::None)
13386 });
13387 })
13388 }
13389
13390 pub fn delete_to_next_word_end(
13391 &mut self,
13392 action: &DeleteToNextWordEnd,
13393 window: &mut Window,
13394 cx: &mut Context<Self>,
13395 ) {
13396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13397 self.transact(window, cx, |this, window, cx| {
13398 this.change_selections(Default::default(), window, cx, |s| {
13399 s.move_with(|map, selection| {
13400 if selection.is_empty() {
13401 let mut cursor = if action.ignore_newlines {
13402 movement::next_word_end(map, selection.head())
13403 } else {
13404 movement::next_word_end_or_newline(map, selection.head())
13405 };
13406 cursor = movement::adjust_greedy_deletion(
13407 map,
13408 selection.head(),
13409 cursor,
13410 action.ignore_brackets,
13411 );
13412 selection.set_head(cursor, SelectionGoal::None);
13413 }
13414 });
13415 });
13416 this.insert("", window, cx);
13417 });
13418 }
13419
13420 pub fn delete_to_next_subword_end(
13421 &mut self,
13422 _: &DeleteToNextSubwordEnd,
13423 window: &mut Window,
13424 cx: &mut Context<Self>,
13425 ) {
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13427 self.transact(window, cx, |this, window, cx| {
13428 this.change_selections(Default::default(), window, cx, |s| {
13429 s.move_with(|map, selection| {
13430 if selection.is_empty() {
13431 let mut cursor = movement::next_subword_end(map, selection.head());
13432 cursor =
13433 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13434 selection.set_head(cursor, SelectionGoal::None);
13435 }
13436 });
13437 });
13438 this.insert("", window, cx);
13439 });
13440 }
13441
13442 pub fn move_to_beginning_of_line(
13443 &mut self,
13444 action: &MoveToBeginningOfLine,
13445 window: &mut Window,
13446 cx: &mut Context<Self>,
13447 ) {
13448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13449 self.change_selections(Default::default(), window, cx, |s| {
13450 s.move_cursors_with(|map, head, _| {
13451 (
13452 movement::indented_line_beginning(
13453 map,
13454 head,
13455 action.stop_at_soft_wraps,
13456 action.stop_at_indent,
13457 ),
13458 SelectionGoal::None,
13459 )
13460 });
13461 })
13462 }
13463
13464 pub fn select_to_beginning_of_line(
13465 &mut self,
13466 action: &SelectToBeginningOfLine,
13467 window: &mut Window,
13468 cx: &mut Context<Self>,
13469 ) {
13470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13471 self.change_selections(Default::default(), window, cx, |s| {
13472 s.move_heads_with(|map, head, _| {
13473 (
13474 movement::indented_line_beginning(
13475 map,
13476 head,
13477 action.stop_at_soft_wraps,
13478 action.stop_at_indent,
13479 ),
13480 SelectionGoal::None,
13481 )
13482 });
13483 });
13484 }
13485
13486 pub fn delete_to_beginning_of_line(
13487 &mut self,
13488 action: &DeleteToBeginningOfLine,
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.change_selections(Default::default(), window, cx, |s| {
13495 s.move_with(|_, selection| {
13496 selection.reversed = true;
13497 });
13498 });
13499
13500 this.select_to_beginning_of_line(
13501 &SelectToBeginningOfLine {
13502 stop_at_soft_wraps: false,
13503 stop_at_indent: action.stop_at_indent,
13504 },
13505 window,
13506 cx,
13507 );
13508 this.backspace(&Backspace, window, cx);
13509 });
13510 }
13511
13512 pub fn move_to_end_of_line(
13513 &mut self,
13514 action: &MoveToEndOfLine,
13515 window: &mut Window,
13516 cx: &mut Context<Self>,
13517 ) {
13518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13519 self.change_selections(Default::default(), window, cx, |s| {
13520 s.move_cursors_with(|map, head, _| {
13521 (
13522 movement::line_end(map, head, action.stop_at_soft_wraps),
13523 SelectionGoal::None,
13524 )
13525 });
13526 })
13527 }
13528
13529 pub fn select_to_end_of_line(
13530 &mut self,
13531 action: &SelectToEndOfLine,
13532 window: &mut Window,
13533 cx: &mut Context<Self>,
13534 ) {
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.move_heads_with(|map, head, _| {
13538 (
13539 movement::line_end(map, head, action.stop_at_soft_wraps),
13540 SelectionGoal::None,
13541 )
13542 });
13543 })
13544 }
13545
13546 pub fn delete_to_end_of_line(
13547 &mut self,
13548 _: &DeleteToEndOfLine,
13549 window: &mut Window,
13550 cx: &mut Context<Self>,
13551 ) {
13552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13553 self.transact(window, cx, |this, window, cx| {
13554 this.select_to_end_of_line(
13555 &SelectToEndOfLine {
13556 stop_at_soft_wraps: false,
13557 },
13558 window,
13559 cx,
13560 );
13561 this.delete(&Delete, window, cx);
13562 });
13563 }
13564
13565 pub fn cut_to_end_of_line(
13566 &mut self,
13567 action: &CutToEndOfLine,
13568 window: &mut Window,
13569 cx: &mut Context<Self>,
13570 ) {
13571 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13572 self.transact(window, cx, |this, window, cx| {
13573 this.select_to_end_of_line(
13574 &SelectToEndOfLine {
13575 stop_at_soft_wraps: false,
13576 },
13577 window,
13578 cx,
13579 );
13580 if !action.stop_at_newlines {
13581 this.change_selections(Default::default(), window, cx, |s| {
13582 s.move_with(|_, sel| {
13583 if sel.is_empty() {
13584 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13585 }
13586 });
13587 });
13588 }
13589 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13590 let item = this.cut_common(false, window, cx);
13591 cx.write_to_clipboard(item);
13592 });
13593 }
13594
13595 pub fn move_to_start_of_paragraph(
13596 &mut self,
13597 _: &MoveToStartOfParagraph,
13598 window: &mut Window,
13599 cx: &mut Context<Self>,
13600 ) {
13601 if matches!(self.mode, EditorMode::SingleLine) {
13602 cx.propagate();
13603 return;
13604 }
13605 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13606 self.change_selections(Default::default(), window, cx, |s| {
13607 s.move_with(|map, selection| {
13608 selection.collapse_to(
13609 movement::start_of_paragraph(map, selection.head(), 1),
13610 SelectionGoal::None,
13611 )
13612 });
13613 })
13614 }
13615
13616 pub fn move_to_end_of_paragraph(
13617 &mut self,
13618 _: &MoveToEndOfParagraph,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 if matches!(self.mode, EditorMode::SingleLine) {
13623 cx.propagate();
13624 return;
13625 }
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13627 self.change_selections(Default::default(), window, cx, |s| {
13628 s.move_with(|map, selection| {
13629 selection.collapse_to(
13630 movement::end_of_paragraph(map, selection.head(), 1),
13631 SelectionGoal::None,
13632 )
13633 });
13634 })
13635 }
13636
13637 pub fn select_to_start_of_paragraph(
13638 &mut self,
13639 _: &SelectToStartOfParagraph,
13640 window: &mut Window,
13641 cx: &mut Context<Self>,
13642 ) {
13643 if matches!(self.mode, EditorMode::SingleLine) {
13644 cx.propagate();
13645 return;
13646 }
13647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13648 self.change_selections(Default::default(), window, cx, |s| {
13649 s.move_heads_with(|map, head, _| {
13650 (
13651 movement::start_of_paragraph(map, head, 1),
13652 SelectionGoal::None,
13653 )
13654 });
13655 })
13656 }
13657
13658 pub fn select_to_end_of_paragraph(
13659 &mut self,
13660 _: &SelectToEndOfParagraph,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 if matches!(self.mode, EditorMode::SingleLine) {
13665 cx.propagate();
13666 return;
13667 }
13668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13669 self.change_selections(Default::default(), window, cx, |s| {
13670 s.move_heads_with(|map, head, _| {
13671 (
13672 movement::end_of_paragraph(map, head, 1),
13673 SelectionGoal::None,
13674 )
13675 });
13676 })
13677 }
13678
13679 pub fn move_to_start_of_excerpt(
13680 &mut self,
13681 _: &MoveToStartOfExcerpt,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 if matches!(self.mode, EditorMode::SingleLine) {
13686 cx.propagate();
13687 return;
13688 }
13689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13690 self.change_selections(Default::default(), window, cx, |s| {
13691 s.move_with(|map, selection| {
13692 selection.collapse_to(
13693 movement::start_of_excerpt(
13694 map,
13695 selection.head(),
13696 workspace::searchable::Direction::Prev,
13697 ),
13698 SelectionGoal::None,
13699 )
13700 });
13701 })
13702 }
13703
13704 pub fn move_to_start_of_next_excerpt(
13705 &mut self,
13706 _: &MoveToStartOfNextExcerpt,
13707 window: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 if matches!(self.mode, EditorMode::SingleLine) {
13711 cx.propagate();
13712 return;
13713 }
13714
13715 self.change_selections(Default::default(), window, cx, |s| {
13716 s.move_with(|map, selection| {
13717 selection.collapse_to(
13718 movement::start_of_excerpt(
13719 map,
13720 selection.head(),
13721 workspace::searchable::Direction::Next,
13722 ),
13723 SelectionGoal::None,
13724 )
13725 });
13726 })
13727 }
13728
13729 pub fn move_to_end_of_excerpt(
13730 &mut self,
13731 _: &MoveToEndOfExcerpt,
13732 window: &mut Window,
13733 cx: &mut Context<Self>,
13734 ) {
13735 if matches!(self.mode, EditorMode::SingleLine) {
13736 cx.propagate();
13737 return;
13738 }
13739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13740 self.change_selections(Default::default(), window, cx, |s| {
13741 s.move_with(|map, selection| {
13742 selection.collapse_to(
13743 movement::end_of_excerpt(
13744 map,
13745 selection.head(),
13746 workspace::searchable::Direction::Next,
13747 ),
13748 SelectionGoal::None,
13749 )
13750 });
13751 })
13752 }
13753
13754 pub fn move_to_end_of_previous_excerpt(
13755 &mut self,
13756 _: &MoveToEndOfPreviousExcerpt,
13757 window: &mut Window,
13758 cx: &mut Context<Self>,
13759 ) {
13760 if matches!(self.mode, EditorMode::SingleLine) {
13761 cx.propagate();
13762 return;
13763 }
13764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13765 self.change_selections(Default::default(), window, cx, |s| {
13766 s.move_with(|map, selection| {
13767 selection.collapse_to(
13768 movement::end_of_excerpt(
13769 map,
13770 selection.head(),
13771 workspace::searchable::Direction::Prev,
13772 ),
13773 SelectionGoal::None,
13774 )
13775 });
13776 })
13777 }
13778
13779 pub fn select_to_start_of_excerpt(
13780 &mut self,
13781 _: &SelectToStartOfExcerpt,
13782 window: &mut Window,
13783 cx: &mut Context<Self>,
13784 ) {
13785 if matches!(self.mode, EditorMode::SingleLine) {
13786 cx.propagate();
13787 return;
13788 }
13789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13790 self.change_selections(Default::default(), window, cx, |s| {
13791 s.move_heads_with(|map, head, _| {
13792 (
13793 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13794 SelectionGoal::None,
13795 )
13796 });
13797 })
13798 }
13799
13800 pub fn select_to_start_of_next_excerpt(
13801 &mut self,
13802 _: &SelectToStartOfNextExcerpt,
13803 window: &mut Window,
13804 cx: &mut Context<Self>,
13805 ) {
13806 if matches!(self.mode, EditorMode::SingleLine) {
13807 cx.propagate();
13808 return;
13809 }
13810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13811 self.change_selections(Default::default(), window, cx, |s| {
13812 s.move_heads_with(|map, head, _| {
13813 (
13814 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13815 SelectionGoal::None,
13816 )
13817 });
13818 })
13819 }
13820
13821 pub fn select_to_end_of_excerpt(
13822 &mut self,
13823 _: &SelectToEndOfExcerpt,
13824 window: &mut Window,
13825 cx: &mut Context<Self>,
13826 ) {
13827 if matches!(self.mode, EditorMode::SingleLine) {
13828 cx.propagate();
13829 return;
13830 }
13831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13832 self.change_selections(Default::default(), window, cx, |s| {
13833 s.move_heads_with(|map, head, _| {
13834 (
13835 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13836 SelectionGoal::None,
13837 )
13838 });
13839 })
13840 }
13841
13842 pub fn select_to_end_of_previous_excerpt(
13843 &mut self,
13844 _: &SelectToEndOfPreviousExcerpt,
13845 window: &mut Window,
13846 cx: &mut Context<Self>,
13847 ) {
13848 if matches!(self.mode, EditorMode::SingleLine) {
13849 cx.propagate();
13850 return;
13851 }
13852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13853 self.change_selections(Default::default(), window, cx, |s| {
13854 s.move_heads_with(|map, head, _| {
13855 (
13856 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13857 SelectionGoal::None,
13858 )
13859 });
13860 })
13861 }
13862
13863 pub fn move_to_beginning(
13864 &mut self,
13865 _: &MoveToBeginning,
13866 window: &mut Window,
13867 cx: &mut Context<Self>,
13868 ) {
13869 if matches!(self.mode, EditorMode::SingleLine) {
13870 cx.propagate();
13871 return;
13872 }
13873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13874 self.change_selections(Default::default(), window, cx, |s| {
13875 s.select_ranges(vec![0..0]);
13876 });
13877 }
13878
13879 pub fn select_to_beginning(
13880 &mut self,
13881 _: &SelectToBeginning,
13882 window: &mut Window,
13883 cx: &mut Context<Self>,
13884 ) {
13885 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13886 selection.set_head(Point::zero(), SelectionGoal::None);
13887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13888 self.change_selections(Default::default(), window, cx, |s| {
13889 s.select(vec![selection]);
13890 });
13891 }
13892
13893 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13894 if matches!(self.mode, EditorMode::SingleLine) {
13895 cx.propagate();
13896 return;
13897 }
13898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13899 let cursor = self.buffer.read(cx).read(cx).len();
13900 self.change_selections(Default::default(), window, cx, |s| {
13901 s.select_ranges(vec![cursor..cursor])
13902 });
13903 }
13904
13905 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13906 self.nav_history = nav_history;
13907 }
13908
13909 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13910 self.nav_history.as_ref()
13911 }
13912
13913 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13914 self.push_to_nav_history(
13915 self.selections.newest_anchor().head(),
13916 None,
13917 false,
13918 true,
13919 cx,
13920 );
13921 }
13922
13923 fn push_to_nav_history(
13924 &mut self,
13925 cursor_anchor: Anchor,
13926 new_position: Option<Point>,
13927 is_deactivate: bool,
13928 always: bool,
13929 cx: &mut Context<Self>,
13930 ) {
13931 if let Some(nav_history) = self.nav_history.as_mut() {
13932 let buffer = self.buffer.read(cx).read(cx);
13933 let cursor_position = cursor_anchor.to_point(&buffer);
13934 let scroll_state = self.scroll_manager.anchor();
13935 let scroll_top_row = scroll_state.top_row(&buffer);
13936 drop(buffer);
13937
13938 if let Some(new_position) = new_position {
13939 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13940 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13941 return;
13942 }
13943 }
13944
13945 nav_history.push(
13946 Some(NavigationData {
13947 cursor_anchor,
13948 cursor_position,
13949 scroll_anchor: scroll_state,
13950 scroll_top_row,
13951 }),
13952 cx,
13953 );
13954 cx.emit(EditorEvent::PushedToNavHistory {
13955 anchor: cursor_anchor,
13956 is_deactivate,
13957 })
13958 }
13959 }
13960
13961 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13963 let buffer = self.buffer.read(cx).snapshot(cx);
13964 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
13965 selection.set_head(buffer.len(), SelectionGoal::None);
13966 self.change_selections(Default::default(), window, cx, |s| {
13967 s.select(vec![selection]);
13968 });
13969 }
13970
13971 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13973 let end = self.buffer.read(cx).read(cx).len();
13974 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13975 s.select_ranges(vec![0..end]);
13976 });
13977 }
13978
13979 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13982 let mut selections = self.selections.all::<Point>(&display_map);
13983 let max_point = display_map.buffer_snapshot().max_point();
13984 for selection in &mut selections {
13985 let rows = selection.spanned_rows(true, &display_map);
13986 selection.start = Point::new(rows.start.0, 0);
13987 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13988 selection.reversed = false;
13989 }
13990 self.change_selections(Default::default(), window, cx, |s| {
13991 s.select(selections);
13992 });
13993 }
13994
13995 pub fn split_selection_into_lines(
13996 &mut self,
13997 action: &SplitSelectionIntoLines,
13998 window: &mut Window,
13999 cx: &mut Context<Self>,
14000 ) {
14001 let selections = self
14002 .selections
14003 .all::<Point>(&self.display_snapshot(cx))
14004 .into_iter()
14005 .map(|selection| selection.start..selection.end)
14006 .collect::<Vec<_>>();
14007 self.unfold_ranges(&selections, true, true, cx);
14008
14009 let mut new_selection_ranges = Vec::new();
14010 {
14011 let buffer = self.buffer.read(cx).read(cx);
14012 for selection in selections {
14013 for row in selection.start.row..selection.end.row {
14014 let line_start = Point::new(row, 0);
14015 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14016
14017 if action.keep_selections {
14018 // Keep the selection range for each line
14019 let selection_start = if row == selection.start.row {
14020 selection.start
14021 } else {
14022 line_start
14023 };
14024 new_selection_ranges.push(selection_start..line_end);
14025 } else {
14026 // Collapse to cursor at end of line
14027 new_selection_ranges.push(line_end..line_end);
14028 }
14029 }
14030
14031 let is_multiline_selection = selection.start.row != selection.end.row;
14032 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14033 // so this action feels more ergonomic when paired with other selection operations
14034 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14035 if !should_skip_last {
14036 if action.keep_selections {
14037 if is_multiline_selection {
14038 let line_start = Point::new(selection.end.row, 0);
14039 new_selection_ranges.push(line_start..selection.end);
14040 } else {
14041 new_selection_ranges.push(selection.start..selection.end);
14042 }
14043 } else {
14044 new_selection_ranges.push(selection.end..selection.end);
14045 }
14046 }
14047 }
14048 }
14049 self.change_selections(Default::default(), window, cx, |s| {
14050 s.select_ranges(new_selection_ranges);
14051 });
14052 }
14053
14054 pub fn add_selection_above(
14055 &mut self,
14056 action: &AddSelectionAbove,
14057 window: &mut Window,
14058 cx: &mut Context<Self>,
14059 ) {
14060 self.add_selection(true, action.skip_soft_wrap, window, cx);
14061 }
14062
14063 pub fn add_selection_below(
14064 &mut self,
14065 action: &AddSelectionBelow,
14066 window: &mut Window,
14067 cx: &mut Context<Self>,
14068 ) {
14069 self.add_selection(false, action.skip_soft_wrap, window, cx);
14070 }
14071
14072 fn add_selection(
14073 &mut self,
14074 above: bool,
14075 skip_soft_wrap: bool,
14076 window: &mut Window,
14077 cx: &mut Context<Self>,
14078 ) {
14079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14080
14081 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14082 let all_selections = self.selections.all::<Point>(&display_map);
14083 let text_layout_details = self.text_layout_details(window);
14084
14085 let (mut columnar_selections, new_selections_to_columnarize) = {
14086 if let Some(state) = self.add_selections_state.as_ref() {
14087 let columnar_selection_ids: HashSet<_> = state
14088 .groups
14089 .iter()
14090 .flat_map(|group| group.stack.iter())
14091 .copied()
14092 .collect();
14093
14094 all_selections
14095 .into_iter()
14096 .partition(|s| columnar_selection_ids.contains(&s.id))
14097 } else {
14098 (Vec::new(), all_selections)
14099 }
14100 };
14101
14102 let mut state = self
14103 .add_selections_state
14104 .take()
14105 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14106
14107 for selection in new_selections_to_columnarize {
14108 let range = selection.display_range(&display_map).sorted();
14109 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14110 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14111 let positions = start_x.min(end_x)..start_x.max(end_x);
14112 let mut stack = Vec::new();
14113 for row in range.start.row().0..=range.end.row().0 {
14114 if let Some(selection) = self.selections.build_columnar_selection(
14115 &display_map,
14116 DisplayRow(row),
14117 &positions,
14118 selection.reversed,
14119 &text_layout_details,
14120 ) {
14121 stack.push(selection.id);
14122 columnar_selections.push(selection);
14123 }
14124 }
14125 if !stack.is_empty() {
14126 if above {
14127 stack.reverse();
14128 }
14129 state.groups.push(AddSelectionsGroup { above, stack });
14130 }
14131 }
14132
14133 let mut final_selections = Vec::new();
14134 let end_row = if above {
14135 DisplayRow(0)
14136 } else {
14137 display_map.max_point().row()
14138 };
14139
14140 let mut last_added_item_per_group = HashMap::default();
14141 for group in state.groups.iter_mut() {
14142 if let Some(last_id) = group.stack.last() {
14143 last_added_item_per_group.insert(*last_id, group);
14144 }
14145 }
14146
14147 for selection in columnar_selections {
14148 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14149 if above == group.above {
14150 let range = selection.display_range(&display_map).sorted();
14151 debug_assert_eq!(range.start.row(), range.end.row());
14152 let mut row = range.start.row();
14153 let positions =
14154 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14155 Pixels::from(start)..Pixels::from(end)
14156 } else {
14157 let start_x =
14158 display_map.x_for_display_point(range.start, &text_layout_details);
14159 let end_x =
14160 display_map.x_for_display_point(range.end, &text_layout_details);
14161 start_x.min(end_x)..start_x.max(end_x)
14162 };
14163
14164 let mut maybe_new_selection = None;
14165 let direction = if above { -1 } else { 1 };
14166
14167 while row != end_row {
14168 if skip_soft_wrap {
14169 row = display_map
14170 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14171 .row();
14172 } else if above {
14173 row.0 -= 1;
14174 } else {
14175 row.0 += 1;
14176 }
14177
14178 if let Some(new_selection) = self.selections.build_columnar_selection(
14179 &display_map,
14180 row,
14181 &positions,
14182 selection.reversed,
14183 &text_layout_details,
14184 ) {
14185 maybe_new_selection = Some(new_selection);
14186 break;
14187 }
14188 }
14189
14190 if let Some(new_selection) = maybe_new_selection {
14191 group.stack.push(new_selection.id);
14192 if above {
14193 final_selections.push(new_selection);
14194 final_selections.push(selection);
14195 } else {
14196 final_selections.push(selection);
14197 final_selections.push(new_selection);
14198 }
14199 } else {
14200 final_selections.push(selection);
14201 }
14202 } else {
14203 group.stack.pop();
14204 }
14205 } else {
14206 final_selections.push(selection);
14207 }
14208 }
14209
14210 self.change_selections(Default::default(), window, cx, |s| {
14211 s.select(final_selections);
14212 });
14213
14214 let final_selection_ids: HashSet<_> = self
14215 .selections
14216 .all::<Point>(&display_map)
14217 .iter()
14218 .map(|s| s.id)
14219 .collect();
14220 state.groups.retain_mut(|group| {
14221 // selections might get merged above so we remove invalid items from stacks
14222 group.stack.retain(|id| final_selection_ids.contains(id));
14223
14224 // single selection in stack can be treated as initial state
14225 group.stack.len() > 1
14226 });
14227
14228 if !state.groups.is_empty() {
14229 self.add_selections_state = Some(state);
14230 }
14231 }
14232
14233 fn select_match_ranges(
14234 &mut self,
14235 range: Range<usize>,
14236 reversed: bool,
14237 replace_newest: bool,
14238 auto_scroll: Option<Autoscroll>,
14239 window: &mut Window,
14240 cx: &mut Context<Editor>,
14241 ) {
14242 self.unfold_ranges(
14243 std::slice::from_ref(&range),
14244 false,
14245 auto_scroll.is_some(),
14246 cx,
14247 );
14248 let effects = if let Some(scroll) = auto_scroll {
14249 SelectionEffects::scroll(scroll)
14250 } else {
14251 SelectionEffects::no_scroll()
14252 };
14253 self.change_selections(effects, window, cx, |s| {
14254 if replace_newest {
14255 s.delete(s.newest_anchor().id);
14256 }
14257 if reversed {
14258 s.insert_range(range.end..range.start);
14259 } else {
14260 s.insert_range(range);
14261 }
14262 });
14263 }
14264
14265 pub fn select_next_match_internal(
14266 &mut self,
14267 display_map: &DisplaySnapshot,
14268 replace_newest: bool,
14269 autoscroll: Option<Autoscroll>,
14270 window: &mut Window,
14271 cx: &mut Context<Self>,
14272 ) -> Result<()> {
14273 let buffer = display_map.buffer_snapshot();
14274 let mut selections = self.selections.all::<usize>(&display_map);
14275 if let Some(mut select_next_state) = self.select_next_state.take() {
14276 let query = &select_next_state.query;
14277 if !select_next_state.done {
14278 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14279 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14280 let mut next_selected_range = None;
14281
14282 // Collect and sort selection ranges for efficient overlap checking
14283 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14284 selection_ranges.sort_by_key(|r| r.start);
14285
14286 let bytes_after_last_selection =
14287 buffer.bytes_in_range(last_selection.end..buffer.len());
14288 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14289 let query_matches = query
14290 .stream_find_iter(bytes_after_last_selection)
14291 .map(|result| (last_selection.end, result))
14292 .chain(
14293 query
14294 .stream_find_iter(bytes_before_first_selection)
14295 .map(|result| (0, result)),
14296 );
14297
14298 for (start_offset, query_match) in query_matches {
14299 let query_match = query_match.unwrap(); // can only fail due to I/O
14300 let offset_range =
14301 start_offset + query_match.start()..start_offset + query_match.end();
14302
14303 if !select_next_state.wordwise
14304 || (!buffer.is_inside_word(offset_range.start, None)
14305 && !buffer.is_inside_word(offset_range.end, None))
14306 {
14307 // Use binary search to check for overlap (O(log n))
14308 let overlaps = selection_ranges
14309 .binary_search_by(|range| {
14310 if range.end <= offset_range.start {
14311 std::cmp::Ordering::Less
14312 } else if range.start >= offset_range.end {
14313 std::cmp::Ordering::Greater
14314 } else {
14315 std::cmp::Ordering::Equal
14316 }
14317 })
14318 .is_ok();
14319
14320 if !overlaps {
14321 next_selected_range = Some(offset_range);
14322 break;
14323 }
14324 }
14325 }
14326
14327 if let Some(next_selected_range) = next_selected_range {
14328 self.select_match_ranges(
14329 next_selected_range,
14330 last_selection.reversed,
14331 replace_newest,
14332 autoscroll,
14333 window,
14334 cx,
14335 );
14336 } else {
14337 select_next_state.done = true;
14338 }
14339 }
14340
14341 self.select_next_state = Some(select_next_state);
14342 } else {
14343 let mut only_carets = true;
14344 let mut same_text_selected = true;
14345 let mut selected_text = None;
14346
14347 let mut selections_iter = selections.iter().peekable();
14348 while let Some(selection) = selections_iter.next() {
14349 if selection.start != selection.end {
14350 only_carets = false;
14351 }
14352
14353 if same_text_selected {
14354 if selected_text.is_none() {
14355 selected_text =
14356 Some(buffer.text_for_range(selection.range()).collect::<String>());
14357 }
14358
14359 if let Some(next_selection) = selections_iter.peek() {
14360 if next_selection.range().len() == selection.range().len() {
14361 let next_selected_text = buffer
14362 .text_for_range(next_selection.range())
14363 .collect::<String>();
14364 if Some(next_selected_text) != selected_text {
14365 same_text_selected = false;
14366 selected_text = None;
14367 }
14368 } else {
14369 same_text_selected = false;
14370 selected_text = None;
14371 }
14372 }
14373 }
14374 }
14375
14376 if only_carets {
14377 for selection in &mut selections {
14378 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14379 selection.start = word_range.start;
14380 selection.end = word_range.end;
14381 selection.goal = SelectionGoal::None;
14382 selection.reversed = false;
14383 self.select_match_ranges(
14384 selection.start..selection.end,
14385 selection.reversed,
14386 replace_newest,
14387 autoscroll,
14388 window,
14389 cx,
14390 );
14391 }
14392
14393 if selections.len() == 1 {
14394 let selection = selections
14395 .last()
14396 .expect("ensured that there's only one selection");
14397 let query = buffer
14398 .text_for_range(selection.start..selection.end)
14399 .collect::<String>();
14400 let is_empty = query.is_empty();
14401 let select_state = SelectNextState {
14402 query: AhoCorasick::new(&[query])?,
14403 wordwise: true,
14404 done: is_empty,
14405 };
14406 self.select_next_state = Some(select_state);
14407 } else {
14408 self.select_next_state = None;
14409 }
14410 } else if let Some(selected_text) = selected_text {
14411 self.select_next_state = Some(SelectNextState {
14412 query: AhoCorasick::new(&[selected_text])?,
14413 wordwise: false,
14414 done: false,
14415 });
14416 self.select_next_match_internal(
14417 display_map,
14418 replace_newest,
14419 autoscroll,
14420 window,
14421 cx,
14422 )?;
14423 }
14424 }
14425 Ok(())
14426 }
14427
14428 pub fn select_all_matches(
14429 &mut self,
14430 _action: &SelectAllMatches,
14431 window: &mut Window,
14432 cx: &mut Context<Self>,
14433 ) -> Result<()> {
14434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14435
14436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14437
14438 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14439 let Some(select_next_state) = self.select_next_state.as_mut() else {
14440 return Ok(());
14441 };
14442 if select_next_state.done {
14443 return Ok(());
14444 }
14445
14446 let mut new_selections = Vec::new();
14447
14448 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14449 let buffer = display_map.buffer_snapshot();
14450 let query_matches = select_next_state
14451 .query
14452 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14453
14454 for query_match in query_matches.into_iter() {
14455 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14456 let offset_range = if reversed {
14457 query_match.end()..query_match.start()
14458 } else {
14459 query_match.start()..query_match.end()
14460 };
14461
14462 if !select_next_state.wordwise
14463 || (!buffer.is_inside_word(offset_range.start, None)
14464 && !buffer.is_inside_word(offset_range.end, None))
14465 {
14466 new_selections.push(offset_range.start..offset_range.end);
14467 }
14468 }
14469
14470 select_next_state.done = true;
14471
14472 if new_selections.is_empty() {
14473 log::error!("bug: new_selections is empty in select_all_matches");
14474 return Ok(());
14475 }
14476
14477 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14478 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14479 selections.select_ranges(new_selections)
14480 });
14481
14482 Ok(())
14483 }
14484
14485 pub fn select_next(
14486 &mut self,
14487 action: &SelectNext,
14488 window: &mut Window,
14489 cx: &mut Context<Self>,
14490 ) -> Result<()> {
14491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14493 self.select_next_match_internal(
14494 &display_map,
14495 action.replace_newest,
14496 Some(Autoscroll::newest()),
14497 window,
14498 cx,
14499 )?;
14500 Ok(())
14501 }
14502
14503 pub fn select_previous(
14504 &mut self,
14505 action: &SelectPrevious,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) -> Result<()> {
14509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14511 let buffer = display_map.buffer_snapshot();
14512 let mut selections = self.selections.all::<usize>(&display_map);
14513 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14514 let query = &select_prev_state.query;
14515 if !select_prev_state.done {
14516 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14517 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14518 let mut next_selected_range = None;
14519 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14520 let bytes_before_last_selection =
14521 buffer.reversed_bytes_in_range(0..last_selection.start);
14522 let bytes_after_first_selection =
14523 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14524 let query_matches = query
14525 .stream_find_iter(bytes_before_last_selection)
14526 .map(|result| (last_selection.start, result))
14527 .chain(
14528 query
14529 .stream_find_iter(bytes_after_first_selection)
14530 .map(|result| (buffer.len(), result)),
14531 );
14532 for (end_offset, query_match) in query_matches {
14533 let query_match = query_match.unwrap(); // can only fail due to I/O
14534 let offset_range =
14535 end_offset - query_match.end()..end_offset - query_match.start();
14536
14537 if !select_prev_state.wordwise
14538 || (!buffer.is_inside_word(offset_range.start, None)
14539 && !buffer.is_inside_word(offset_range.end, None))
14540 {
14541 next_selected_range = Some(offset_range);
14542 break;
14543 }
14544 }
14545
14546 if let Some(next_selected_range) = next_selected_range {
14547 self.select_match_ranges(
14548 next_selected_range,
14549 last_selection.reversed,
14550 action.replace_newest,
14551 Some(Autoscroll::newest()),
14552 window,
14553 cx,
14554 );
14555 } else {
14556 select_prev_state.done = true;
14557 }
14558 }
14559
14560 self.select_prev_state = Some(select_prev_state);
14561 } else {
14562 let mut only_carets = true;
14563 let mut same_text_selected = true;
14564 let mut selected_text = None;
14565
14566 let mut selections_iter = selections.iter().peekable();
14567 while let Some(selection) = selections_iter.next() {
14568 if selection.start != selection.end {
14569 only_carets = false;
14570 }
14571
14572 if same_text_selected {
14573 if selected_text.is_none() {
14574 selected_text =
14575 Some(buffer.text_for_range(selection.range()).collect::<String>());
14576 }
14577
14578 if let Some(next_selection) = selections_iter.peek() {
14579 if next_selection.range().len() == selection.range().len() {
14580 let next_selected_text = buffer
14581 .text_for_range(next_selection.range())
14582 .collect::<String>();
14583 if Some(next_selected_text) != selected_text {
14584 same_text_selected = false;
14585 selected_text = None;
14586 }
14587 } else {
14588 same_text_selected = false;
14589 selected_text = None;
14590 }
14591 }
14592 }
14593 }
14594
14595 if only_carets {
14596 for selection in &mut selections {
14597 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14598 selection.start = word_range.start;
14599 selection.end = word_range.end;
14600 selection.goal = SelectionGoal::None;
14601 selection.reversed = false;
14602 self.select_match_ranges(
14603 selection.start..selection.end,
14604 selection.reversed,
14605 action.replace_newest,
14606 Some(Autoscroll::newest()),
14607 window,
14608 cx,
14609 );
14610 }
14611 if selections.len() == 1 {
14612 let selection = selections
14613 .last()
14614 .expect("ensured that there's only one selection");
14615 let query = buffer
14616 .text_for_range(selection.start..selection.end)
14617 .collect::<String>();
14618 let is_empty = query.is_empty();
14619 let select_state = SelectNextState {
14620 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14621 wordwise: true,
14622 done: is_empty,
14623 };
14624 self.select_prev_state = Some(select_state);
14625 } else {
14626 self.select_prev_state = None;
14627 }
14628 } else if let Some(selected_text) = selected_text {
14629 self.select_prev_state = Some(SelectNextState {
14630 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14631 wordwise: false,
14632 done: false,
14633 });
14634 self.select_previous(action, window, cx)?;
14635 }
14636 }
14637 Ok(())
14638 }
14639
14640 pub fn find_next_match(
14641 &mut self,
14642 _: &FindNextMatch,
14643 window: &mut Window,
14644 cx: &mut Context<Self>,
14645 ) -> Result<()> {
14646 let selections = self.selections.disjoint_anchors_arc();
14647 match selections.first() {
14648 Some(first) if selections.len() >= 2 => {
14649 self.change_selections(Default::default(), window, cx, |s| {
14650 s.select_ranges([first.range()]);
14651 });
14652 }
14653 _ => self.select_next(
14654 &SelectNext {
14655 replace_newest: true,
14656 },
14657 window,
14658 cx,
14659 )?,
14660 }
14661 Ok(())
14662 }
14663
14664 pub fn find_previous_match(
14665 &mut self,
14666 _: &FindPreviousMatch,
14667 window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) -> Result<()> {
14670 let selections = self.selections.disjoint_anchors_arc();
14671 match selections.last() {
14672 Some(last) if selections.len() >= 2 => {
14673 self.change_selections(Default::default(), window, cx, |s| {
14674 s.select_ranges([last.range()]);
14675 });
14676 }
14677 _ => self.select_previous(
14678 &SelectPrevious {
14679 replace_newest: true,
14680 },
14681 window,
14682 cx,
14683 )?,
14684 }
14685 Ok(())
14686 }
14687
14688 pub fn toggle_comments(
14689 &mut self,
14690 action: &ToggleComments,
14691 window: &mut Window,
14692 cx: &mut Context<Self>,
14693 ) {
14694 if self.read_only(cx) {
14695 return;
14696 }
14697 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14698 let text_layout_details = &self.text_layout_details(window);
14699 self.transact(window, cx, |this, window, cx| {
14700 let mut selections = this
14701 .selections
14702 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14703 let mut edits = Vec::new();
14704 let mut selection_edit_ranges = Vec::new();
14705 let mut last_toggled_row = None;
14706 let snapshot = this.buffer.read(cx).read(cx);
14707 let empty_str: Arc<str> = Arc::default();
14708 let mut suffixes_inserted = Vec::new();
14709 let ignore_indent = action.ignore_indent;
14710
14711 fn comment_prefix_range(
14712 snapshot: &MultiBufferSnapshot,
14713 row: MultiBufferRow,
14714 comment_prefix: &str,
14715 comment_prefix_whitespace: &str,
14716 ignore_indent: bool,
14717 ) -> Range<Point> {
14718 let indent_size = if ignore_indent {
14719 0
14720 } else {
14721 snapshot.indent_size_for_line(row).len
14722 };
14723
14724 let start = Point::new(row.0, indent_size);
14725
14726 let mut line_bytes = snapshot
14727 .bytes_in_range(start..snapshot.max_point())
14728 .flatten()
14729 .copied();
14730
14731 // If this line currently begins with the line comment prefix, then record
14732 // the range containing the prefix.
14733 if line_bytes
14734 .by_ref()
14735 .take(comment_prefix.len())
14736 .eq(comment_prefix.bytes())
14737 {
14738 // Include any whitespace that matches the comment prefix.
14739 let matching_whitespace_len = line_bytes
14740 .zip(comment_prefix_whitespace.bytes())
14741 .take_while(|(a, b)| a == b)
14742 .count() as u32;
14743 let end = Point::new(
14744 start.row,
14745 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14746 );
14747 start..end
14748 } else {
14749 start..start
14750 }
14751 }
14752
14753 fn comment_suffix_range(
14754 snapshot: &MultiBufferSnapshot,
14755 row: MultiBufferRow,
14756 comment_suffix: &str,
14757 comment_suffix_has_leading_space: bool,
14758 ) -> Range<Point> {
14759 let end = Point::new(row.0, snapshot.line_len(row));
14760 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14761
14762 let mut line_end_bytes = snapshot
14763 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14764 .flatten()
14765 .copied();
14766
14767 let leading_space_len = if suffix_start_column > 0
14768 && line_end_bytes.next() == Some(b' ')
14769 && comment_suffix_has_leading_space
14770 {
14771 1
14772 } else {
14773 0
14774 };
14775
14776 // If this line currently begins with the line comment prefix, then record
14777 // the range containing the prefix.
14778 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14779 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14780 start..end
14781 } else {
14782 end..end
14783 }
14784 }
14785
14786 // TODO: Handle selections that cross excerpts
14787 for selection in &mut selections {
14788 let start_column = snapshot
14789 .indent_size_for_line(MultiBufferRow(selection.start.row))
14790 .len;
14791 let language = if let Some(language) =
14792 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14793 {
14794 language
14795 } else {
14796 continue;
14797 };
14798
14799 selection_edit_ranges.clear();
14800
14801 // If multiple selections contain a given row, avoid processing that
14802 // row more than once.
14803 let mut start_row = MultiBufferRow(selection.start.row);
14804 if last_toggled_row == Some(start_row) {
14805 start_row = start_row.next_row();
14806 }
14807 let end_row =
14808 if selection.end.row > selection.start.row && selection.end.column == 0 {
14809 MultiBufferRow(selection.end.row - 1)
14810 } else {
14811 MultiBufferRow(selection.end.row)
14812 };
14813 last_toggled_row = Some(end_row);
14814
14815 if start_row > end_row {
14816 continue;
14817 }
14818
14819 // If the language has line comments, toggle those.
14820 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14821
14822 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14823 if ignore_indent {
14824 full_comment_prefixes = full_comment_prefixes
14825 .into_iter()
14826 .map(|s| Arc::from(s.trim_end()))
14827 .collect();
14828 }
14829
14830 if !full_comment_prefixes.is_empty() {
14831 let first_prefix = full_comment_prefixes
14832 .first()
14833 .expect("prefixes is non-empty");
14834 let prefix_trimmed_lengths = full_comment_prefixes
14835 .iter()
14836 .map(|p| p.trim_end_matches(' ').len())
14837 .collect::<SmallVec<[usize; 4]>>();
14838
14839 let mut all_selection_lines_are_comments = true;
14840
14841 for row in start_row.0..=end_row.0 {
14842 let row = MultiBufferRow(row);
14843 if start_row < end_row && snapshot.is_line_blank(row) {
14844 continue;
14845 }
14846
14847 let prefix_range = full_comment_prefixes
14848 .iter()
14849 .zip(prefix_trimmed_lengths.iter().copied())
14850 .map(|(prefix, trimmed_prefix_len)| {
14851 comment_prefix_range(
14852 snapshot.deref(),
14853 row,
14854 &prefix[..trimmed_prefix_len],
14855 &prefix[trimmed_prefix_len..],
14856 ignore_indent,
14857 )
14858 })
14859 .max_by_key(|range| range.end.column - range.start.column)
14860 .expect("prefixes is non-empty");
14861
14862 if prefix_range.is_empty() {
14863 all_selection_lines_are_comments = false;
14864 }
14865
14866 selection_edit_ranges.push(prefix_range);
14867 }
14868
14869 if all_selection_lines_are_comments {
14870 edits.extend(
14871 selection_edit_ranges
14872 .iter()
14873 .cloned()
14874 .map(|range| (range, empty_str.clone())),
14875 );
14876 } else {
14877 let min_column = selection_edit_ranges
14878 .iter()
14879 .map(|range| range.start.column)
14880 .min()
14881 .unwrap_or(0);
14882 edits.extend(selection_edit_ranges.iter().map(|range| {
14883 let position = Point::new(range.start.row, min_column);
14884 (position..position, first_prefix.clone())
14885 }));
14886 }
14887 } else if let Some(BlockCommentConfig {
14888 start: full_comment_prefix,
14889 end: comment_suffix,
14890 ..
14891 }) = language.block_comment()
14892 {
14893 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14894 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14895 let prefix_range = comment_prefix_range(
14896 snapshot.deref(),
14897 start_row,
14898 comment_prefix,
14899 comment_prefix_whitespace,
14900 ignore_indent,
14901 );
14902 let suffix_range = comment_suffix_range(
14903 snapshot.deref(),
14904 end_row,
14905 comment_suffix.trim_start_matches(' '),
14906 comment_suffix.starts_with(' '),
14907 );
14908
14909 if prefix_range.is_empty() || suffix_range.is_empty() {
14910 edits.push((
14911 prefix_range.start..prefix_range.start,
14912 full_comment_prefix.clone(),
14913 ));
14914 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14915 suffixes_inserted.push((end_row, comment_suffix.len()));
14916 } else {
14917 edits.push((prefix_range, empty_str.clone()));
14918 edits.push((suffix_range, empty_str.clone()));
14919 }
14920 } else {
14921 continue;
14922 }
14923 }
14924
14925 drop(snapshot);
14926 this.buffer.update(cx, |buffer, cx| {
14927 buffer.edit(edits, None, cx);
14928 });
14929
14930 // Adjust selections so that they end before any comment suffixes that
14931 // were inserted.
14932 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14933 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
14934 let snapshot = this.buffer.read(cx).read(cx);
14935 for selection in &mut selections {
14936 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14937 match row.cmp(&MultiBufferRow(selection.end.row)) {
14938 Ordering::Less => {
14939 suffixes_inserted.next();
14940 continue;
14941 }
14942 Ordering::Greater => break,
14943 Ordering::Equal => {
14944 if selection.end.column == snapshot.line_len(row) {
14945 if selection.is_empty() {
14946 selection.start.column -= suffix_len as u32;
14947 }
14948 selection.end.column -= suffix_len as u32;
14949 }
14950 break;
14951 }
14952 }
14953 }
14954 }
14955
14956 drop(snapshot);
14957 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14958
14959 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
14960 let selections_on_single_row = selections.windows(2).all(|selections| {
14961 selections[0].start.row == selections[1].start.row
14962 && selections[0].end.row == selections[1].end.row
14963 && selections[0].start.row == selections[0].end.row
14964 });
14965 let selections_selecting = selections
14966 .iter()
14967 .any(|selection| selection.start != selection.end);
14968 let advance_downwards = action.advance_downwards
14969 && selections_on_single_row
14970 && !selections_selecting
14971 && !matches!(this.mode, EditorMode::SingleLine);
14972
14973 if advance_downwards {
14974 let snapshot = this.buffer.read(cx).snapshot(cx);
14975
14976 this.change_selections(Default::default(), window, cx, |s| {
14977 s.move_cursors_with(|display_snapshot, display_point, _| {
14978 let mut point = display_point.to_point(display_snapshot);
14979 point.row += 1;
14980 point = snapshot.clip_point(point, Bias::Left);
14981 let display_point = point.to_display_point(display_snapshot);
14982 let goal = SelectionGoal::HorizontalPosition(
14983 display_snapshot
14984 .x_for_display_point(display_point, text_layout_details)
14985 .into(),
14986 );
14987 (display_point, goal)
14988 })
14989 });
14990 }
14991 });
14992 }
14993
14994 pub fn select_enclosing_symbol(
14995 &mut self,
14996 _: &SelectEnclosingSymbol,
14997 window: &mut Window,
14998 cx: &mut Context<Self>,
14999 ) {
15000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15001
15002 let buffer = self.buffer.read(cx).snapshot(cx);
15003 let old_selections = self
15004 .selections
15005 .all::<usize>(&self.display_snapshot(cx))
15006 .into_boxed_slice();
15007
15008 fn update_selection(
15009 selection: &Selection<usize>,
15010 buffer_snap: &MultiBufferSnapshot,
15011 ) -> Option<Selection<usize>> {
15012 let cursor = selection.head();
15013 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15014 for symbol in symbols.iter().rev() {
15015 let start = symbol.range.start.to_offset(buffer_snap);
15016 let end = symbol.range.end.to_offset(buffer_snap);
15017 let new_range = start..end;
15018 if start < selection.start || end > selection.end {
15019 return Some(Selection {
15020 id: selection.id,
15021 start: new_range.start,
15022 end: new_range.end,
15023 goal: SelectionGoal::None,
15024 reversed: selection.reversed,
15025 });
15026 }
15027 }
15028 None
15029 }
15030
15031 let mut selected_larger_symbol = false;
15032 let new_selections = old_selections
15033 .iter()
15034 .map(|selection| match update_selection(selection, &buffer) {
15035 Some(new_selection) => {
15036 if new_selection.range() != selection.range() {
15037 selected_larger_symbol = true;
15038 }
15039 new_selection
15040 }
15041 None => selection.clone(),
15042 })
15043 .collect::<Vec<_>>();
15044
15045 if selected_larger_symbol {
15046 self.change_selections(Default::default(), window, cx, |s| {
15047 s.select(new_selections);
15048 });
15049 }
15050 }
15051
15052 pub fn select_larger_syntax_node(
15053 &mut self,
15054 _: &SelectLargerSyntaxNode,
15055 window: &mut Window,
15056 cx: &mut Context<Self>,
15057 ) {
15058 let Some(visible_row_count) = self.visible_row_count() else {
15059 return;
15060 };
15061 let old_selections: Box<[_]> = self
15062 .selections
15063 .all::<usize>(&self.display_snapshot(cx))
15064 .into();
15065 if old_selections.is_empty() {
15066 return;
15067 }
15068
15069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15070
15071 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15072 let buffer = self.buffer.read(cx).snapshot(cx);
15073
15074 let mut selected_larger_node = false;
15075 let mut new_selections = old_selections
15076 .iter()
15077 .map(|selection| {
15078 let old_range = selection.start..selection.end;
15079
15080 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15081 // manually select word at selection
15082 if ["string_content", "inline"].contains(&node.kind()) {
15083 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15084 // ignore if word is already selected
15085 if !word_range.is_empty() && old_range != word_range {
15086 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15087 // only select word if start and end point belongs to same word
15088 if word_range == last_word_range {
15089 selected_larger_node = true;
15090 return Selection {
15091 id: selection.id,
15092 start: word_range.start,
15093 end: word_range.end,
15094 goal: SelectionGoal::None,
15095 reversed: selection.reversed,
15096 };
15097 }
15098 }
15099 }
15100 }
15101
15102 let mut new_range = old_range.clone();
15103 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15104 new_range = range;
15105 if !node.is_named() {
15106 continue;
15107 }
15108 if !display_map.intersects_fold(new_range.start)
15109 && !display_map.intersects_fold(new_range.end)
15110 {
15111 break;
15112 }
15113 }
15114
15115 selected_larger_node |= new_range != old_range;
15116 Selection {
15117 id: selection.id,
15118 start: new_range.start,
15119 end: new_range.end,
15120 goal: SelectionGoal::None,
15121 reversed: selection.reversed,
15122 }
15123 })
15124 .collect::<Vec<_>>();
15125
15126 if !selected_larger_node {
15127 return; // don't put this call in the history
15128 }
15129
15130 // scroll based on transformation done to the last selection created by the user
15131 let (last_old, last_new) = old_selections
15132 .last()
15133 .zip(new_selections.last().cloned())
15134 .expect("old_selections isn't empty");
15135
15136 // revert selection
15137 let is_selection_reversed = {
15138 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15139 new_selections.last_mut().expect("checked above").reversed =
15140 should_newest_selection_be_reversed;
15141 should_newest_selection_be_reversed
15142 };
15143
15144 if selected_larger_node {
15145 self.select_syntax_node_history.disable_clearing = true;
15146 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15147 s.select(new_selections.clone());
15148 });
15149 self.select_syntax_node_history.disable_clearing = false;
15150 }
15151
15152 let start_row = last_new.start.to_display_point(&display_map).row().0;
15153 let end_row = last_new.end.to_display_point(&display_map).row().0;
15154 let selection_height = end_row - start_row + 1;
15155 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15156
15157 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15158 let scroll_behavior = if fits_on_the_screen {
15159 self.request_autoscroll(Autoscroll::fit(), cx);
15160 SelectSyntaxNodeScrollBehavior::FitSelection
15161 } else if is_selection_reversed {
15162 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15163 SelectSyntaxNodeScrollBehavior::CursorTop
15164 } else {
15165 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15166 SelectSyntaxNodeScrollBehavior::CursorBottom
15167 };
15168
15169 self.select_syntax_node_history.push((
15170 old_selections,
15171 scroll_behavior,
15172 is_selection_reversed,
15173 ));
15174 }
15175
15176 pub fn select_smaller_syntax_node(
15177 &mut self,
15178 _: &SelectSmallerSyntaxNode,
15179 window: &mut Window,
15180 cx: &mut Context<Self>,
15181 ) {
15182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15183
15184 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15185 self.select_syntax_node_history.pop()
15186 {
15187 if let Some(selection) = selections.last_mut() {
15188 selection.reversed = is_selection_reversed;
15189 }
15190
15191 self.select_syntax_node_history.disable_clearing = true;
15192 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15193 s.select(selections.to_vec());
15194 });
15195 self.select_syntax_node_history.disable_clearing = false;
15196
15197 match scroll_behavior {
15198 SelectSyntaxNodeScrollBehavior::CursorTop => {
15199 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15200 }
15201 SelectSyntaxNodeScrollBehavior::FitSelection => {
15202 self.request_autoscroll(Autoscroll::fit(), cx);
15203 }
15204 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15205 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15206 }
15207 }
15208 }
15209 }
15210
15211 pub fn unwrap_syntax_node(
15212 &mut self,
15213 _: &UnwrapSyntaxNode,
15214 window: &mut Window,
15215 cx: &mut Context<Self>,
15216 ) {
15217 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15218
15219 let buffer = self.buffer.read(cx).snapshot(cx);
15220 let selections = self
15221 .selections
15222 .all::<usize>(&self.display_snapshot(cx))
15223 .into_iter()
15224 // subtracting the offset requires sorting
15225 .sorted_by_key(|i| i.start);
15226
15227 let full_edits = selections
15228 .into_iter()
15229 .filter_map(|selection| {
15230 let child = if selection.is_empty()
15231 && let Some((_, ancestor_range)) =
15232 buffer.syntax_ancestor(selection.start..selection.end)
15233 {
15234 ancestor_range
15235 } else {
15236 selection.range()
15237 };
15238
15239 let mut parent = child.clone();
15240 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15241 parent = ancestor_range;
15242 if parent.start < child.start || parent.end > child.end {
15243 break;
15244 }
15245 }
15246
15247 if parent == child {
15248 return None;
15249 }
15250 let text = buffer.text_for_range(child).collect::<String>();
15251 Some((selection.id, parent, text))
15252 })
15253 .collect::<Vec<_>>();
15254 if full_edits.is_empty() {
15255 return;
15256 }
15257
15258 self.transact(window, cx, |this, window, cx| {
15259 this.buffer.update(cx, |buffer, cx| {
15260 buffer.edit(
15261 full_edits
15262 .iter()
15263 .map(|(_, p, t)| (p.clone(), t.clone()))
15264 .collect::<Vec<_>>(),
15265 None,
15266 cx,
15267 );
15268 });
15269 this.change_selections(Default::default(), window, cx, |s| {
15270 let mut offset = 0;
15271 let mut selections = vec![];
15272 for (id, parent, text) in full_edits {
15273 let start = parent.start - offset;
15274 offset += parent.len() - text.len();
15275 selections.push(Selection {
15276 id,
15277 start,
15278 end: start + text.len(),
15279 reversed: false,
15280 goal: Default::default(),
15281 });
15282 }
15283 s.select(selections);
15284 });
15285 });
15286 }
15287
15288 pub fn select_next_syntax_node(
15289 &mut self,
15290 _: &SelectNextSyntaxNode,
15291 window: &mut Window,
15292 cx: &mut Context<Self>,
15293 ) {
15294 let old_selections: Box<[_]> = self
15295 .selections
15296 .all::<usize>(&self.display_snapshot(cx))
15297 .into();
15298 if old_selections.is_empty() {
15299 return;
15300 }
15301
15302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15303
15304 let buffer = self.buffer.read(cx).snapshot(cx);
15305 let mut selected_sibling = false;
15306
15307 let new_selections = old_selections
15308 .iter()
15309 .map(|selection| {
15310 let old_range = selection.start..selection.end;
15311
15312 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15313 let new_range = node.byte_range();
15314 selected_sibling = true;
15315 Selection {
15316 id: selection.id,
15317 start: new_range.start,
15318 end: new_range.end,
15319 goal: SelectionGoal::None,
15320 reversed: selection.reversed,
15321 }
15322 } else {
15323 selection.clone()
15324 }
15325 })
15326 .collect::<Vec<_>>();
15327
15328 if selected_sibling {
15329 self.change_selections(
15330 SelectionEffects::scroll(Autoscroll::fit()),
15331 window,
15332 cx,
15333 |s| {
15334 s.select(new_selections);
15335 },
15336 );
15337 }
15338 }
15339
15340 pub fn select_prev_syntax_node(
15341 &mut self,
15342 _: &SelectPreviousSyntaxNode,
15343 window: &mut Window,
15344 cx: &mut Context<Self>,
15345 ) {
15346 let old_selections: Box<[_]> = self
15347 .selections
15348 .all::<usize>(&self.display_snapshot(cx))
15349 .into();
15350 if old_selections.is_empty() {
15351 return;
15352 }
15353
15354 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15355
15356 let buffer = self.buffer.read(cx).snapshot(cx);
15357 let mut selected_sibling = false;
15358
15359 let new_selections = old_selections
15360 .iter()
15361 .map(|selection| {
15362 let old_range = selection.start..selection.end;
15363
15364 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15365 let new_range = node.byte_range();
15366 selected_sibling = true;
15367 Selection {
15368 id: selection.id,
15369 start: new_range.start,
15370 end: new_range.end,
15371 goal: SelectionGoal::None,
15372 reversed: selection.reversed,
15373 }
15374 } else {
15375 selection.clone()
15376 }
15377 })
15378 .collect::<Vec<_>>();
15379
15380 if selected_sibling {
15381 self.change_selections(
15382 SelectionEffects::scroll(Autoscroll::fit()),
15383 window,
15384 cx,
15385 |s| {
15386 s.select(new_selections);
15387 },
15388 );
15389 }
15390 }
15391
15392 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15393 if !EditorSettings::get_global(cx).gutter.runnables {
15394 self.clear_tasks();
15395 return Task::ready(());
15396 }
15397 let project = self.project().map(Entity::downgrade);
15398 let task_sources = self.lsp_task_sources(cx);
15399 let multi_buffer = self.buffer.downgrade();
15400 cx.spawn_in(window, async move |editor, cx| {
15401 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15402 let Some(project) = project.and_then(|p| p.upgrade()) else {
15403 return;
15404 };
15405 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15406 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15407 }) else {
15408 return;
15409 };
15410
15411 let hide_runnables = project
15412 .update(cx, |project, _| project.is_via_collab())
15413 .unwrap_or(true);
15414 if hide_runnables {
15415 return;
15416 }
15417 let new_rows =
15418 cx.background_spawn({
15419 let snapshot = display_snapshot.clone();
15420 async move {
15421 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15422 }
15423 })
15424 .await;
15425 let Ok(lsp_tasks) =
15426 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15427 else {
15428 return;
15429 };
15430 let lsp_tasks = lsp_tasks.await;
15431
15432 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15433 lsp_tasks
15434 .into_iter()
15435 .flat_map(|(kind, tasks)| {
15436 tasks.into_iter().filter_map(move |(location, task)| {
15437 Some((kind.clone(), location?, task))
15438 })
15439 })
15440 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15441 let buffer = location.target.buffer;
15442 let buffer_snapshot = buffer.read(cx).snapshot();
15443 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15444 |(excerpt_id, snapshot, _)| {
15445 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15446 display_snapshot
15447 .buffer_snapshot()
15448 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15449 } else {
15450 None
15451 }
15452 },
15453 );
15454 if let Some(offset) = offset {
15455 let task_buffer_range =
15456 location.target.range.to_point(&buffer_snapshot);
15457 let context_buffer_range =
15458 task_buffer_range.to_offset(&buffer_snapshot);
15459 let context_range = BufferOffset(context_buffer_range.start)
15460 ..BufferOffset(context_buffer_range.end);
15461
15462 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15463 .or_insert_with(|| RunnableTasks {
15464 templates: Vec::new(),
15465 offset,
15466 column: task_buffer_range.start.column,
15467 extra_variables: HashMap::default(),
15468 context_range,
15469 })
15470 .templates
15471 .push((kind, task.original_task().clone()));
15472 }
15473
15474 acc
15475 })
15476 }) else {
15477 return;
15478 };
15479
15480 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15481 buffer.language_settings(cx).tasks.prefer_lsp
15482 }) else {
15483 return;
15484 };
15485
15486 let rows = Self::runnable_rows(
15487 project,
15488 display_snapshot,
15489 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15490 new_rows,
15491 cx.clone(),
15492 )
15493 .await;
15494 editor
15495 .update(cx, |editor, _| {
15496 editor.clear_tasks();
15497 for (key, mut value) in rows {
15498 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15499 value.templates.extend(lsp_tasks.templates);
15500 }
15501
15502 editor.insert_tasks(key, value);
15503 }
15504 for (key, value) in lsp_tasks_by_rows {
15505 editor.insert_tasks(key, value);
15506 }
15507 })
15508 .ok();
15509 })
15510 }
15511 fn fetch_runnable_ranges(
15512 snapshot: &DisplaySnapshot,
15513 range: Range<Anchor>,
15514 ) -> Vec<language::RunnableRange> {
15515 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15516 }
15517
15518 fn runnable_rows(
15519 project: Entity<Project>,
15520 snapshot: DisplaySnapshot,
15521 prefer_lsp: bool,
15522 runnable_ranges: Vec<RunnableRange>,
15523 cx: AsyncWindowContext,
15524 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15525 cx.spawn(async move |cx| {
15526 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15527 for mut runnable in runnable_ranges {
15528 let Some(tasks) = cx
15529 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15530 .ok()
15531 else {
15532 continue;
15533 };
15534 let mut tasks = tasks.await;
15535
15536 if prefer_lsp {
15537 tasks.retain(|(task_kind, _)| {
15538 !matches!(task_kind, TaskSourceKind::Language { .. })
15539 });
15540 }
15541 if tasks.is_empty() {
15542 continue;
15543 }
15544
15545 let point = runnable
15546 .run_range
15547 .start
15548 .to_point(&snapshot.buffer_snapshot());
15549 let Some(row) = snapshot
15550 .buffer_snapshot()
15551 .buffer_line_for_row(MultiBufferRow(point.row))
15552 .map(|(_, range)| range.start.row)
15553 else {
15554 continue;
15555 };
15556
15557 let context_range =
15558 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15559 runnable_rows.push((
15560 (runnable.buffer_id, row),
15561 RunnableTasks {
15562 templates: tasks,
15563 offset: snapshot
15564 .buffer_snapshot()
15565 .anchor_before(runnable.run_range.start),
15566 context_range,
15567 column: point.column,
15568 extra_variables: runnable.extra_captures,
15569 },
15570 ));
15571 }
15572 runnable_rows
15573 })
15574 }
15575
15576 fn templates_with_tags(
15577 project: &Entity<Project>,
15578 runnable: &mut Runnable,
15579 cx: &mut App,
15580 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15581 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15582 let (worktree_id, file) = project
15583 .buffer_for_id(runnable.buffer, cx)
15584 .and_then(|buffer| buffer.read(cx).file())
15585 .map(|file| (file.worktree_id(cx), file.clone()))
15586 .unzip();
15587
15588 (
15589 project.task_store().read(cx).task_inventory().cloned(),
15590 worktree_id,
15591 file,
15592 )
15593 });
15594
15595 let tags = mem::take(&mut runnable.tags);
15596 let language = runnable.language.clone();
15597 cx.spawn(async move |cx| {
15598 let mut templates_with_tags = Vec::new();
15599 if let Some(inventory) = inventory {
15600 for RunnableTag(tag) in tags {
15601 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15602 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15603 }) else {
15604 return templates_with_tags;
15605 };
15606 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15607 move |(_, template)| {
15608 template.tags.iter().any(|source_tag| source_tag == &tag)
15609 },
15610 ));
15611 }
15612 }
15613 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15614
15615 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15616 // Strongest source wins; if we have worktree tag binding, prefer that to
15617 // global and language bindings;
15618 // if we have a global binding, prefer that to language binding.
15619 let first_mismatch = templates_with_tags
15620 .iter()
15621 .position(|(tag_source, _)| tag_source != leading_tag_source);
15622 if let Some(index) = first_mismatch {
15623 templates_with_tags.truncate(index);
15624 }
15625 }
15626
15627 templates_with_tags
15628 })
15629 }
15630
15631 pub fn move_to_enclosing_bracket(
15632 &mut self,
15633 _: &MoveToEnclosingBracket,
15634 window: &mut Window,
15635 cx: &mut Context<Self>,
15636 ) {
15637 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15638 self.change_selections(Default::default(), window, cx, |s| {
15639 s.move_offsets_with(|snapshot, selection| {
15640 let Some(enclosing_bracket_ranges) =
15641 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15642 else {
15643 return;
15644 };
15645
15646 let mut best_length = usize::MAX;
15647 let mut best_inside = false;
15648 let mut best_in_bracket_range = false;
15649 let mut best_destination = None;
15650 for (open, close) in enclosing_bracket_ranges {
15651 let close = close.to_inclusive();
15652 let length = close.end() - open.start;
15653 let inside = selection.start >= open.end && selection.end <= *close.start();
15654 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15655 || close.contains(&selection.head());
15656
15657 // If best is next to a bracket and current isn't, skip
15658 if !in_bracket_range && best_in_bracket_range {
15659 continue;
15660 }
15661
15662 // Prefer smaller lengths unless best is inside and current isn't
15663 if length > best_length && (best_inside || !inside) {
15664 continue;
15665 }
15666
15667 best_length = length;
15668 best_inside = inside;
15669 best_in_bracket_range = in_bracket_range;
15670 best_destination = Some(
15671 if close.contains(&selection.start) && close.contains(&selection.end) {
15672 if inside { open.end } else { open.start }
15673 } else if inside {
15674 *close.start()
15675 } else {
15676 *close.end()
15677 },
15678 );
15679 }
15680
15681 if let Some(destination) = best_destination {
15682 selection.collapse_to(destination, SelectionGoal::None);
15683 }
15684 })
15685 });
15686 }
15687
15688 pub fn undo_selection(
15689 &mut self,
15690 _: &UndoSelection,
15691 window: &mut Window,
15692 cx: &mut Context<Self>,
15693 ) {
15694 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15695 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15696 self.selection_history.mode = SelectionHistoryMode::Undoing;
15697 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15698 this.end_selection(window, cx);
15699 this.change_selections(
15700 SelectionEffects::scroll(Autoscroll::newest()),
15701 window,
15702 cx,
15703 |s| s.select_anchors(entry.selections.to_vec()),
15704 );
15705 });
15706 self.selection_history.mode = SelectionHistoryMode::Normal;
15707
15708 self.select_next_state = entry.select_next_state;
15709 self.select_prev_state = entry.select_prev_state;
15710 self.add_selections_state = entry.add_selections_state;
15711 }
15712 }
15713
15714 pub fn redo_selection(
15715 &mut self,
15716 _: &RedoSelection,
15717 window: &mut Window,
15718 cx: &mut Context<Self>,
15719 ) {
15720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15721 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15722 self.selection_history.mode = SelectionHistoryMode::Redoing;
15723 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15724 this.end_selection(window, cx);
15725 this.change_selections(
15726 SelectionEffects::scroll(Autoscroll::newest()),
15727 window,
15728 cx,
15729 |s| s.select_anchors(entry.selections.to_vec()),
15730 );
15731 });
15732 self.selection_history.mode = SelectionHistoryMode::Normal;
15733
15734 self.select_next_state = entry.select_next_state;
15735 self.select_prev_state = entry.select_prev_state;
15736 self.add_selections_state = entry.add_selections_state;
15737 }
15738 }
15739
15740 pub fn expand_excerpts(
15741 &mut self,
15742 action: &ExpandExcerpts,
15743 _: &mut Window,
15744 cx: &mut Context<Self>,
15745 ) {
15746 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15747 }
15748
15749 pub fn expand_excerpts_down(
15750 &mut self,
15751 action: &ExpandExcerptsDown,
15752 _: &mut Window,
15753 cx: &mut Context<Self>,
15754 ) {
15755 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15756 }
15757
15758 pub fn expand_excerpts_up(
15759 &mut self,
15760 action: &ExpandExcerptsUp,
15761 _: &mut Window,
15762 cx: &mut Context<Self>,
15763 ) {
15764 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15765 }
15766
15767 pub fn expand_excerpts_for_direction(
15768 &mut self,
15769 lines: u32,
15770 direction: ExpandExcerptDirection,
15771
15772 cx: &mut Context<Self>,
15773 ) {
15774 let selections = self.selections.disjoint_anchors_arc();
15775
15776 let lines = if lines == 0 {
15777 EditorSettings::get_global(cx).expand_excerpt_lines
15778 } else {
15779 lines
15780 };
15781
15782 self.buffer.update(cx, |buffer, cx| {
15783 let snapshot = buffer.snapshot(cx);
15784 let mut excerpt_ids = selections
15785 .iter()
15786 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15787 .collect::<Vec<_>>();
15788 excerpt_ids.sort();
15789 excerpt_ids.dedup();
15790 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15791 })
15792 }
15793
15794 pub fn expand_excerpt(
15795 &mut self,
15796 excerpt: ExcerptId,
15797 direction: ExpandExcerptDirection,
15798 window: &mut Window,
15799 cx: &mut Context<Self>,
15800 ) {
15801 let current_scroll_position = self.scroll_position(cx);
15802 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15803 let mut should_scroll_up = false;
15804
15805 if direction == ExpandExcerptDirection::Down {
15806 let multi_buffer = self.buffer.read(cx);
15807 let snapshot = multi_buffer.snapshot(cx);
15808 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15809 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15810 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15811 {
15812 let buffer_snapshot = buffer.read(cx).snapshot();
15813 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15814 let last_row = buffer_snapshot.max_point().row;
15815 let lines_below = last_row.saturating_sub(excerpt_end_row);
15816 should_scroll_up = lines_below >= lines_to_expand;
15817 }
15818 }
15819
15820 self.buffer.update(cx, |buffer, cx| {
15821 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15822 });
15823
15824 if should_scroll_up {
15825 let new_scroll_position =
15826 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15827 self.set_scroll_position(new_scroll_position, window, cx);
15828 }
15829 }
15830
15831 pub fn go_to_singleton_buffer_point(
15832 &mut self,
15833 point: Point,
15834 window: &mut Window,
15835 cx: &mut Context<Self>,
15836 ) {
15837 self.go_to_singleton_buffer_range(point..point, window, cx);
15838 }
15839
15840 pub fn go_to_singleton_buffer_range(
15841 &mut self,
15842 range: Range<Point>,
15843 window: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) {
15846 let multibuffer = self.buffer().read(cx);
15847 let Some(buffer) = multibuffer.as_singleton() else {
15848 return;
15849 };
15850 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15851 return;
15852 };
15853 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15854 return;
15855 };
15856 self.change_selections(
15857 SelectionEffects::default().nav_history(true),
15858 window,
15859 cx,
15860 |s| s.select_anchor_ranges([start..end]),
15861 );
15862 }
15863
15864 pub fn go_to_diagnostic(
15865 &mut self,
15866 action: &GoToDiagnostic,
15867 window: &mut Window,
15868 cx: &mut Context<Self>,
15869 ) {
15870 if !self.diagnostics_enabled() {
15871 return;
15872 }
15873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15874 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15875 }
15876
15877 pub fn go_to_prev_diagnostic(
15878 &mut self,
15879 action: &GoToPreviousDiagnostic,
15880 window: &mut Window,
15881 cx: &mut Context<Self>,
15882 ) {
15883 if !self.diagnostics_enabled() {
15884 return;
15885 }
15886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15887 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15888 }
15889
15890 pub fn go_to_diagnostic_impl(
15891 &mut self,
15892 direction: Direction,
15893 severity: GoToDiagnosticSeverityFilter,
15894 window: &mut Window,
15895 cx: &mut Context<Self>,
15896 ) {
15897 let buffer = self.buffer.read(cx).snapshot(cx);
15898 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15899
15900 let mut active_group_id = None;
15901 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15902 && active_group.active_range.start.to_offset(&buffer) == selection.start
15903 {
15904 active_group_id = Some(active_group.group_id);
15905 }
15906
15907 fn filtered<'a>(
15908 snapshot: EditorSnapshot,
15909 severity: GoToDiagnosticSeverityFilter,
15910 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15911 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15912 diagnostics
15913 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15914 .filter(|entry| entry.range.start != entry.range.end)
15915 .filter(|entry| !entry.diagnostic.is_unnecessary)
15916 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15917 }
15918
15919 let snapshot = self.snapshot(window, cx);
15920 let before = filtered(
15921 snapshot.clone(),
15922 severity,
15923 buffer
15924 .diagnostics_in_range(0..selection.start)
15925 .filter(|entry| entry.range.start <= selection.start),
15926 );
15927 let after = filtered(
15928 snapshot,
15929 severity,
15930 buffer
15931 .diagnostics_in_range(selection.start..buffer.len())
15932 .filter(|entry| entry.range.start >= selection.start),
15933 );
15934
15935 let mut found: Option<DiagnosticEntryRef<usize>> = None;
15936 if direction == Direction::Prev {
15937 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15938 {
15939 for diagnostic in prev_diagnostics.into_iter().rev() {
15940 if diagnostic.range.start != selection.start
15941 || active_group_id
15942 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15943 {
15944 found = Some(diagnostic);
15945 break 'outer;
15946 }
15947 }
15948 }
15949 } else {
15950 for diagnostic in after.chain(before) {
15951 if diagnostic.range.start != selection.start
15952 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15953 {
15954 found = Some(diagnostic);
15955 break;
15956 }
15957 }
15958 }
15959 let Some(next_diagnostic) = found else {
15960 return;
15961 };
15962
15963 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15964 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15965 return;
15966 };
15967 self.change_selections(Default::default(), window, cx, |s| {
15968 s.select_ranges(vec![
15969 next_diagnostic.range.start..next_diagnostic.range.start,
15970 ])
15971 });
15972 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15973 self.refresh_edit_prediction(false, true, window, cx);
15974 }
15975
15976 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15978 let snapshot = self.snapshot(window, cx);
15979 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
15980 self.go_to_hunk_before_or_after_position(
15981 &snapshot,
15982 selection.head(),
15983 Direction::Next,
15984 window,
15985 cx,
15986 );
15987 }
15988
15989 pub fn go_to_hunk_before_or_after_position(
15990 &mut self,
15991 snapshot: &EditorSnapshot,
15992 position: Point,
15993 direction: Direction,
15994 window: &mut Window,
15995 cx: &mut Context<Editor>,
15996 ) {
15997 let row = if direction == Direction::Next {
15998 self.hunk_after_position(snapshot, position)
15999 .map(|hunk| hunk.row_range.start)
16000 } else {
16001 self.hunk_before_position(snapshot, position)
16002 };
16003
16004 if let Some(row) = row {
16005 let destination = Point::new(row.0, 0);
16006 let autoscroll = Autoscroll::center();
16007
16008 self.unfold_ranges(&[destination..destination], false, false, cx);
16009 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16010 s.select_ranges([destination..destination]);
16011 });
16012 }
16013 }
16014
16015 fn hunk_after_position(
16016 &mut self,
16017 snapshot: &EditorSnapshot,
16018 position: Point,
16019 ) -> Option<MultiBufferDiffHunk> {
16020 snapshot
16021 .buffer_snapshot()
16022 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16023 .find(|hunk| hunk.row_range.start.0 > position.row)
16024 .or_else(|| {
16025 snapshot
16026 .buffer_snapshot()
16027 .diff_hunks_in_range(Point::zero()..position)
16028 .find(|hunk| hunk.row_range.end.0 < position.row)
16029 })
16030 }
16031
16032 fn go_to_prev_hunk(
16033 &mut self,
16034 _: &GoToPreviousHunk,
16035 window: &mut Window,
16036 cx: &mut Context<Self>,
16037 ) {
16038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16039 let snapshot = self.snapshot(window, cx);
16040 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16041 self.go_to_hunk_before_or_after_position(
16042 &snapshot,
16043 selection.head(),
16044 Direction::Prev,
16045 window,
16046 cx,
16047 );
16048 }
16049
16050 fn hunk_before_position(
16051 &mut self,
16052 snapshot: &EditorSnapshot,
16053 position: Point,
16054 ) -> Option<MultiBufferRow> {
16055 snapshot
16056 .buffer_snapshot()
16057 .diff_hunk_before(position)
16058 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16059 }
16060
16061 fn go_to_next_change(
16062 &mut self,
16063 _: &GoToNextChange,
16064 window: &mut Window,
16065 cx: &mut Context<Self>,
16066 ) {
16067 if let Some(selections) = self
16068 .change_list
16069 .next_change(1, Direction::Next)
16070 .map(|s| s.to_vec())
16071 {
16072 self.change_selections(Default::default(), window, cx, |s| {
16073 let map = s.display_map();
16074 s.select_display_ranges(selections.iter().map(|a| {
16075 let point = a.to_display_point(&map);
16076 point..point
16077 }))
16078 })
16079 }
16080 }
16081
16082 fn go_to_previous_change(
16083 &mut self,
16084 _: &GoToPreviousChange,
16085 window: &mut Window,
16086 cx: &mut Context<Self>,
16087 ) {
16088 if let Some(selections) = self
16089 .change_list
16090 .next_change(1, Direction::Prev)
16091 .map(|s| s.to_vec())
16092 {
16093 self.change_selections(Default::default(), window, cx, |s| {
16094 let map = s.display_map();
16095 s.select_display_ranges(selections.iter().map(|a| {
16096 let point = a.to_display_point(&map);
16097 point..point
16098 }))
16099 })
16100 }
16101 }
16102
16103 pub fn go_to_next_document_highlight(
16104 &mut self,
16105 _: &GoToNextDocumentHighlight,
16106 window: &mut Window,
16107 cx: &mut Context<Self>,
16108 ) {
16109 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16110 }
16111
16112 pub fn go_to_prev_document_highlight(
16113 &mut self,
16114 _: &GoToPreviousDocumentHighlight,
16115 window: &mut Window,
16116 cx: &mut Context<Self>,
16117 ) {
16118 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16119 }
16120
16121 pub fn go_to_document_highlight_before_or_after_position(
16122 &mut self,
16123 direction: Direction,
16124 window: &mut Window,
16125 cx: &mut Context<Editor>,
16126 ) {
16127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16128 let snapshot = self.snapshot(window, cx);
16129 let buffer = &snapshot.buffer_snapshot();
16130 let position = self
16131 .selections
16132 .newest::<Point>(&snapshot.display_snapshot)
16133 .head();
16134 let anchor_position = buffer.anchor_after(position);
16135
16136 // Get all document highlights (both read and write)
16137 let mut all_highlights = Vec::new();
16138
16139 if let Some((_, read_highlights)) = self
16140 .background_highlights
16141 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16142 {
16143 all_highlights.extend(read_highlights.iter());
16144 }
16145
16146 if let Some((_, write_highlights)) = self
16147 .background_highlights
16148 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16149 {
16150 all_highlights.extend(write_highlights.iter());
16151 }
16152
16153 if all_highlights.is_empty() {
16154 return;
16155 }
16156
16157 // Sort highlights by position
16158 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16159
16160 let target_highlight = match direction {
16161 Direction::Next => {
16162 // Find the first highlight after the current position
16163 all_highlights
16164 .iter()
16165 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16166 }
16167 Direction::Prev => {
16168 // Find the last highlight before the current position
16169 all_highlights
16170 .iter()
16171 .rev()
16172 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16173 }
16174 };
16175
16176 if let Some(highlight) = target_highlight {
16177 let destination = highlight.start.to_point(buffer);
16178 let autoscroll = Autoscroll::center();
16179
16180 self.unfold_ranges(&[destination..destination], false, false, cx);
16181 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16182 s.select_ranges([destination..destination]);
16183 });
16184 }
16185 }
16186
16187 fn go_to_line<T: 'static>(
16188 &mut self,
16189 position: Anchor,
16190 highlight_color: Option<Hsla>,
16191 window: &mut Window,
16192 cx: &mut Context<Self>,
16193 ) {
16194 let snapshot = self.snapshot(window, cx).display_snapshot;
16195 let position = position.to_point(&snapshot.buffer_snapshot());
16196 let start = snapshot
16197 .buffer_snapshot()
16198 .clip_point(Point::new(position.row, 0), Bias::Left);
16199 let end = start + Point::new(1, 0);
16200 let start = snapshot.buffer_snapshot().anchor_before(start);
16201 let end = snapshot.buffer_snapshot().anchor_before(end);
16202
16203 self.highlight_rows::<T>(
16204 start..end,
16205 highlight_color
16206 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16207 Default::default(),
16208 cx,
16209 );
16210
16211 if self.buffer.read(cx).is_singleton() {
16212 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16213 }
16214 }
16215
16216 pub fn go_to_definition(
16217 &mut self,
16218 _: &GoToDefinition,
16219 window: &mut Window,
16220 cx: &mut Context<Self>,
16221 ) -> Task<Result<Navigated>> {
16222 let definition =
16223 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16224 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16225 cx.spawn_in(window, async move |editor, cx| {
16226 if definition.await? == Navigated::Yes {
16227 return Ok(Navigated::Yes);
16228 }
16229 match fallback_strategy {
16230 GoToDefinitionFallback::None => Ok(Navigated::No),
16231 GoToDefinitionFallback::FindAllReferences => {
16232 match editor.update_in(cx, |editor, window, cx| {
16233 editor.find_all_references(&FindAllReferences, window, cx)
16234 })? {
16235 Some(references) => references.await,
16236 None => Ok(Navigated::No),
16237 }
16238 }
16239 }
16240 })
16241 }
16242
16243 pub fn go_to_declaration(
16244 &mut self,
16245 _: &GoToDeclaration,
16246 window: &mut Window,
16247 cx: &mut Context<Self>,
16248 ) -> Task<Result<Navigated>> {
16249 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16250 }
16251
16252 pub fn go_to_declaration_split(
16253 &mut self,
16254 _: &GoToDeclaration,
16255 window: &mut Window,
16256 cx: &mut Context<Self>,
16257 ) -> Task<Result<Navigated>> {
16258 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16259 }
16260
16261 pub fn go_to_implementation(
16262 &mut self,
16263 _: &GoToImplementation,
16264 window: &mut Window,
16265 cx: &mut Context<Self>,
16266 ) -> Task<Result<Navigated>> {
16267 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16268 }
16269
16270 pub fn go_to_implementation_split(
16271 &mut self,
16272 _: &GoToImplementationSplit,
16273 window: &mut Window,
16274 cx: &mut Context<Self>,
16275 ) -> Task<Result<Navigated>> {
16276 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16277 }
16278
16279 pub fn go_to_type_definition(
16280 &mut self,
16281 _: &GoToTypeDefinition,
16282 window: &mut Window,
16283 cx: &mut Context<Self>,
16284 ) -> Task<Result<Navigated>> {
16285 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16286 }
16287
16288 pub fn go_to_definition_split(
16289 &mut self,
16290 _: &GoToDefinitionSplit,
16291 window: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) -> Task<Result<Navigated>> {
16294 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16295 }
16296
16297 pub fn go_to_type_definition_split(
16298 &mut self,
16299 _: &GoToTypeDefinitionSplit,
16300 window: &mut Window,
16301 cx: &mut Context<Self>,
16302 ) -> Task<Result<Navigated>> {
16303 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16304 }
16305
16306 fn go_to_definition_of_kind(
16307 &mut self,
16308 kind: GotoDefinitionKind,
16309 split: bool,
16310 window: &mut Window,
16311 cx: &mut Context<Self>,
16312 ) -> Task<Result<Navigated>> {
16313 let Some(provider) = self.semantics_provider.clone() else {
16314 return Task::ready(Ok(Navigated::No));
16315 };
16316 let head = self
16317 .selections
16318 .newest::<usize>(&self.display_snapshot(cx))
16319 .head();
16320 let buffer = self.buffer.read(cx);
16321 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16322 return Task::ready(Ok(Navigated::No));
16323 };
16324 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16325 return Task::ready(Ok(Navigated::No));
16326 };
16327
16328 cx.spawn_in(window, async move |editor, cx| {
16329 let Some(definitions) = definitions.await? else {
16330 return Ok(Navigated::No);
16331 };
16332 let navigated = editor
16333 .update_in(cx, |editor, window, cx| {
16334 editor.navigate_to_hover_links(
16335 Some(kind),
16336 definitions
16337 .into_iter()
16338 .filter(|location| {
16339 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16340 })
16341 .map(HoverLink::Text)
16342 .collect::<Vec<_>>(),
16343 split,
16344 window,
16345 cx,
16346 )
16347 })?
16348 .await?;
16349 anyhow::Ok(navigated)
16350 })
16351 }
16352
16353 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16354 let selection = self.selections.newest_anchor();
16355 let head = selection.head();
16356 let tail = selection.tail();
16357
16358 let Some((buffer, start_position)) =
16359 self.buffer.read(cx).text_anchor_for_position(head, cx)
16360 else {
16361 return;
16362 };
16363
16364 let end_position = if head != tail {
16365 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16366 return;
16367 };
16368 Some(pos)
16369 } else {
16370 None
16371 };
16372
16373 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16374 let url = if let Some(end_pos) = end_position {
16375 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16376 } else {
16377 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16378 };
16379
16380 if let Some(url) = url {
16381 cx.update(|window, cx| {
16382 if parse_zed_link(&url, cx).is_some() {
16383 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16384 } else {
16385 cx.open_url(&url);
16386 }
16387 })?;
16388 }
16389
16390 anyhow::Ok(())
16391 });
16392
16393 url_finder.detach();
16394 }
16395
16396 pub fn open_selected_filename(
16397 &mut self,
16398 _: &OpenSelectedFilename,
16399 window: &mut Window,
16400 cx: &mut Context<Self>,
16401 ) {
16402 let Some(workspace) = self.workspace() else {
16403 return;
16404 };
16405
16406 let position = self.selections.newest_anchor().head();
16407
16408 let Some((buffer, buffer_position)) =
16409 self.buffer.read(cx).text_anchor_for_position(position, cx)
16410 else {
16411 return;
16412 };
16413
16414 let project = self.project.clone();
16415
16416 cx.spawn_in(window, async move |_, cx| {
16417 let result = find_file(&buffer, project, buffer_position, cx).await;
16418
16419 if let Some((_, path)) = result {
16420 workspace
16421 .update_in(cx, |workspace, window, cx| {
16422 workspace.open_resolved_path(path, window, cx)
16423 })?
16424 .await?;
16425 }
16426 anyhow::Ok(())
16427 })
16428 .detach();
16429 }
16430
16431 pub(crate) fn navigate_to_hover_links(
16432 &mut self,
16433 kind: Option<GotoDefinitionKind>,
16434 definitions: Vec<HoverLink>,
16435 split: bool,
16436 window: &mut Window,
16437 cx: &mut Context<Editor>,
16438 ) -> Task<Result<Navigated>> {
16439 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16440 let mut first_url_or_file = None;
16441 let definitions: Vec<_> = definitions
16442 .into_iter()
16443 .filter_map(|def| match def {
16444 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16445 HoverLink::InlayHint(lsp_location, server_id) => {
16446 let computation =
16447 self.compute_target_location(lsp_location, server_id, window, cx);
16448 Some(cx.background_spawn(computation))
16449 }
16450 HoverLink::Url(url) => {
16451 first_url_or_file = Some(Either::Left(url));
16452 None
16453 }
16454 HoverLink::File(path) => {
16455 first_url_or_file = Some(Either::Right(path));
16456 None
16457 }
16458 })
16459 .collect();
16460
16461 let workspace = self.workspace();
16462
16463 cx.spawn_in(window, async move |editor, cx| {
16464 let locations: Vec<Location> = future::join_all(definitions)
16465 .await
16466 .into_iter()
16467 .filter_map(|location| location.transpose())
16468 .collect::<Result<_>>()
16469 .context("location tasks")?;
16470 let mut locations = cx.update(|_, cx| {
16471 locations
16472 .into_iter()
16473 .map(|location| {
16474 let buffer = location.buffer.read(cx);
16475 (location.buffer, location.range.to_point(buffer))
16476 })
16477 .into_group_map()
16478 })?;
16479 let mut num_locations = 0;
16480 for ranges in locations.values_mut() {
16481 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16482 ranges.dedup();
16483 num_locations += ranges.len();
16484 }
16485
16486 if num_locations > 1 {
16487 let Some(workspace) = workspace else {
16488 return Ok(Navigated::No);
16489 };
16490
16491 let tab_kind = match kind {
16492 Some(GotoDefinitionKind::Implementation) => "Implementations",
16493 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16494 Some(GotoDefinitionKind::Declaration) => "Declarations",
16495 Some(GotoDefinitionKind::Type) => "Types",
16496 };
16497 let title = editor
16498 .update_in(cx, |_, _, cx| {
16499 let target = locations
16500 .iter()
16501 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16502 .map(|(buffer, location)| {
16503 buffer
16504 .read(cx)
16505 .text_for_range(location.clone())
16506 .collect::<String>()
16507 })
16508 .filter(|text| !text.contains('\n'))
16509 .unique()
16510 .take(3)
16511 .join(", ");
16512 if target.is_empty() {
16513 tab_kind.to_owned()
16514 } else {
16515 format!("{tab_kind} for {target}")
16516 }
16517 })
16518 .context("buffer title")?;
16519
16520 let opened = workspace
16521 .update_in(cx, |workspace, window, cx| {
16522 Self::open_locations_in_multibuffer(
16523 workspace,
16524 locations,
16525 title,
16526 split,
16527 MultibufferSelectionMode::First,
16528 window,
16529 cx,
16530 )
16531 })
16532 .is_ok();
16533
16534 anyhow::Ok(Navigated::from_bool(opened))
16535 } else if num_locations == 0 {
16536 // If there is one url or file, open it directly
16537 match first_url_or_file {
16538 Some(Either::Left(url)) => {
16539 cx.update(|_, cx| cx.open_url(&url))?;
16540 Ok(Navigated::Yes)
16541 }
16542 Some(Either::Right(path)) => {
16543 let Some(workspace) = workspace else {
16544 return Ok(Navigated::No);
16545 };
16546
16547 workspace
16548 .update_in(cx, |workspace, window, cx| {
16549 workspace.open_resolved_path(path, window, cx)
16550 })?
16551 .await?;
16552 Ok(Navigated::Yes)
16553 }
16554 None => Ok(Navigated::No),
16555 }
16556 } else {
16557 let Some(workspace) = workspace else {
16558 return Ok(Navigated::No);
16559 };
16560
16561 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16562 let target_range = target_ranges.first().unwrap().clone();
16563
16564 editor.update_in(cx, |editor, window, cx| {
16565 let range = target_range.to_point(target_buffer.read(cx));
16566 let range = editor.range_for_match(&range);
16567 let range = collapse_multiline_range(range);
16568
16569 if !split
16570 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16571 {
16572 editor.go_to_singleton_buffer_range(range, window, cx);
16573 } else {
16574 let pane = workspace.read(cx).active_pane().clone();
16575 window.defer(cx, move |window, cx| {
16576 let target_editor: Entity<Self> =
16577 workspace.update(cx, |workspace, cx| {
16578 let pane = if split {
16579 workspace.adjacent_pane(window, cx)
16580 } else {
16581 workspace.active_pane().clone()
16582 };
16583
16584 workspace.open_project_item(
16585 pane,
16586 target_buffer.clone(),
16587 true,
16588 true,
16589 window,
16590 cx,
16591 )
16592 });
16593 target_editor.update(cx, |target_editor, cx| {
16594 // When selecting a definition in a different buffer, disable the nav history
16595 // to avoid creating a history entry at the previous cursor location.
16596 pane.update(cx, |pane, _| pane.disable_history());
16597 target_editor.go_to_singleton_buffer_range(range, window, cx);
16598 pane.update(cx, |pane, _| pane.enable_history());
16599 });
16600 });
16601 }
16602 Navigated::Yes
16603 })
16604 }
16605 })
16606 }
16607
16608 fn compute_target_location(
16609 &self,
16610 lsp_location: lsp::Location,
16611 server_id: LanguageServerId,
16612 window: &mut Window,
16613 cx: &mut Context<Self>,
16614 ) -> Task<anyhow::Result<Option<Location>>> {
16615 let Some(project) = self.project.clone() else {
16616 return Task::ready(Ok(None));
16617 };
16618
16619 cx.spawn_in(window, async move |editor, cx| {
16620 let location_task = editor.update(cx, |_, cx| {
16621 project.update(cx, |project, cx| {
16622 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16623 })
16624 })?;
16625 let location = Some({
16626 let target_buffer_handle = location_task.await.context("open local buffer")?;
16627 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16628 let target_start = target_buffer
16629 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16630 let target_end = target_buffer
16631 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16632 target_buffer.anchor_after(target_start)
16633 ..target_buffer.anchor_before(target_end)
16634 })?;
16635 Location {
16636 buffer: target_buffer_handle,
16637 range,
16638 }
16639 });
16640 Ok(location)
16641 })
16642 }
16643
16644 pub fn find_all_references(
16645 &mut self,
16646 _: &FindAllReferences,
16647 window: &mut Window,
16648 cx: &mut Context<Self>,
16649 ) -> Option<Task<Result<Navigated>>> {
16650 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16651 let multi_buffer = self.buffer.read(cx);
16652 let head = selection.head();
16653
16654 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16655 let head_anchor = multi_buffer_snapshot.anchor_at(
16656 head,
16657 if head < selection.tail() {
16658 Bias::Right
16659 } else {
16660 Bias::Left
16661 },
16662 );
16663
16664 match self
16665 .find_all_references_task_sources
16666 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16667 {
16668 Ok(_) => {
16669 log::info!(
16670 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16671 );
16672 return None;
16673 }
16674 Err(i) => {
16675 self.find_all_references_task_sources.insert(i, head_anchor);
16676 }
16677 }
16678
16679 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16680 let workspace = self.workspace()?;
16681 let project = workspace.read(cx).project().clone();
16682 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16683 Some(cx.spawn_in(window, async move |editor, cx| {
16684 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16685 if let Ok(i) = editor
16686 .find_all_references_task_sources
16687 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16688 {
16689 editor.find_all_references_task_sources.remove(i);
16690 }
16691 });
16692
16693 let Some(locations) = references.await? else {
16694 return anyhow::Ok(Navigated::No);
16695 };
16696 let mut locations = cx.update(|_, cx| {
16697 locations
16698 .into_iter()
16699 .map(|location| {
16700 let buffer = location.buffer.read(cx);
16701 (location.buffer, location.range.to_point(buffer))
16702 })
16703 .into_group_map()
16704 })?;
16705 if locations.is_empty() {
16706 return anyhow::Ok(Navigated::No);
16707 }
16708 for ranges in locations.values_mut() {
16709 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16710 ranges.dedup();
16711 }
16712
16713 workspace.update_in(cx, |workspace, window, cx| {
16714 let target = locations
16715 .iter()
16716 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16717 .map(|(buffer, location)| {
16718 buffer
16719 .read(cx)
16720 .text_for_range(location.clone())
16721 .collect::<String>()
16722 })
16723 .filter(|text| !text.contains('\n'))
16724 .unique()
16725 .take(3)
16726 .join(", ");
16727 let title = if target.is_empty() {
16728 "References".to_owned()
16729 } else {
16730 format!("References to {target}")
16731 };
16732 Self::open_locations_in_multibuffer(
16733 workspace,
16734 locations,
16735 title,
16736 false,
16737 MultibufferSelectionMode::First,
16738 window,
16739 cx,
16740 );
16741 Navigated::Yes
16742 })
16743 }))
16744 }
16745
16746 /// Opens a multibuffer with the given project locations in it
16747 pub fn open_locations_in_multibuffer(
16748 workspace: &mut Workspace,
16749 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16750 title: String,
16751 split: bool,
16752 multibuffer_selection_mode: MultibufferSelectionMode,
16753 window: &mut Window,
16754 cx: &mut Context<Workspace>,
16755 ) {
16756 if locations.is_empty() {
16757 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16758 return;
16759 }
16760
16761 let capability = workspace.project().read(cx).capability();
16762 let mut ranges = <Vec<Range<Anchor>>>::new();
16763
16764 // a key to find existing multibuffer editors with the same set of locations
16765 // to prevent us from opening more and more multibuffer tabs for searches and the like
16766 let mut key = (title.clone(), vec![]);
16767 let excerpt_buffer = cx.new(|cx| {
16768 let key = &mut key.1;
16769 let mut multibuffer = MultiBuffer::new(capability);
16770 for (buffer, mut ranges_for_buffer) in locations {
16771 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16772 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16773 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16774 PathKey::for_buffer(&buffer, cx),
16775 buffer.clone(),
16776 ranges_for_buffer,
16777 multibuffer_context_lines(cx),
16778 cx,
16779 );
16780 ranges.extend(new_ranges)
16781 }
16782
16783 multibuffer.with_title(title)
16784 });
16785 let existing = workspace.active_pane().update(cx, |pane, cx| {
16786 pane.items()
16787 .filter_map(|item| item.downcast::<Editor>())
16788 .find(|editor| {
16789 editor
16790 .read(cx)
16791 .lookup_key
16792 .as_ref()
16793 .and_then(|it| {
16794 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16795 })
16796 .is_some_and(|it| *it == key)
16797 })
16798 });
16799 let editor = existing.unwrap_or_else(|| {
16800 cx.new(|cx| {
16801 let mut editor = Editor::for_multibuffer(
16802 excerpt_buffer,
16803 Some(workspace.project().clone()),
16804 window,
16805 cx,
16806 );
16807 editor.lookup_key = Some(Box::new(key));
16808 editor
16809 })
16810 });
16811 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
16812 MultibufferSelectionMode::First => {
16813 if let Some(first_range) = ranges.first() {
16814 editor.change_selections(
16815 SelectionEffects::no_scroll(),
16816 window,
16817 cx,
16818 |selections| {
16819 selections.clear_disjoint();
16820 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
16821 },
16822 );
16823 }
16824 editor.highlight_background::<Self>(
16825 &ranges,
16826 |theme| theme.colors().editor_highlighted_line_background,
16827 cx,
16828 );
16829 }
16830 MultibufferSelectionMode::All => {
16831 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16832 selections.clear_disjoint();
16833 selections.select_anchor_ranges(ranges);
16834 });
16835 }
16836 });
16837
16838 let item = Box::new(editor);
16839 let item_id = item.item_id();
16840
16841 if split {
16842 let pane = workspace.adjacent_pane(window, cx);
16843 workspace.add_item(pane, item, None, true, true, window, cx);
16844 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16845 let (preview_item_id, preview_item_idx) =
16846 workspace.active_pane().read_with(cx, |pane, _| {
16847 (pane.preview_item_id(), pane.preview_item_idx())
16848 });
16849
16850 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16851
16852 if let Some(preview_item_id) = preview_item_id {
16853 workspace.active_pane().update(cx, |pane, cx| {
16854 pane.remove_item(preview_item_id, false, false, window, cx);
16855 });
16856 }
16857 } else {
16858 workspace.add_item_to_active_pane(item, None, true, window, cx);
16859 }
16860 workspace.active_pane().update(cx, |pane, cx| {
16861 pane.set_preview_item_id(Some(item_id), cx);
16862 });
16863 }
16864
16865 pub fn rename(
16866 &mut self,
16867 _: &Rename,
16868 window: &mut Window,
16869 cx: &mut Context<Self>,
16870 ) -> Option<Task<Result<()>>> {
16871 use language::ToOffset as _;
16872
16873 let provider = self.semantics_provider.clone()?;
16874 let selection = self.selections.newest_anchor().clone();
16875 let (cursor_buffer, cursor_buffer_position) = self
16876 .buffer
16877 .read(cx)
16878 .text_anchor_for_position(selection.head(), cx)?;
16879 let (tail_buffer, cursor_buffer_position_end) = self
16880 .buffer
16881 .read(cx)
16882 .text_anchor_for_position(selection.tail(), cx)?;
16883 if tail_buffer != cursor_buffer {
16884 return None;
16885 }
16886
16887 let snapshot = cursor_buffer.read(cx).snapshot();
16888 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16889 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16890 let prepare_rename = provider
16891 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16892 .unwrap_or_else(|| Task::ready(Ok(None)));
16893 drop(snapshot);
16894
16895 Some(cx.spawn_in(window, async move |this, cx| {
16896 let rename_range = if let Some(range) = prepare_rename.await? {
16897 Some(range)
16898 } else {
16899 this.update(cx, |this, cx| {
16900 let buffer = this.buffer.read(cx).snapshot(cx);
16901 let mut buffer_highlights = this
16902 .document_highlights_for_position(selection.head(), &buffer)
16903 .filter(|highlight| {
16904 highlight.start.excerpt_id == selection.head().excerpt_id
16905 && highlight.end.excerpt_id == selection.head().excerpt_id
16906 });
16907 buffer_highlights
16908 .next()
16909 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16910 })?
16911 };
16912 if let Some(rename_range) = rename_range {
16913 this.update_in(cx, |this, window, cx| {
16914 let snapshot = cursor_buffer.read(cx).snapshot();
16915 let rename_buffer_range = rename_range.to_offset(&snapshot);
16916 let cursor_offset_in_rename_range =
16917 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16918 let cursor_offset_in_rename_range_end =
16919 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16920
16921 this.take_rename(false, window, cx);
16922 let buffer = this.buffer.read(cx).read(cx);
16923 let cursor_offset = selection.head().to_offset(&buffer);
16924 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16925 let rename_end = rename_start + rename_buffer_range.len();
16926 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16927 let mut old_highlight_id = None;
16928 let old_name: Arc<str> = buffer
16929 .chunks(rename_start..rename_end, true)
16930 .map(|chunk| {
16931 if old_highlight_id.is_none() {
16932 old_highlight_id = chunk.syntax_highlight_id;
16933 }
16934 chunk.text
16935 })
16936 .collect::<String>()
16937 .into();
16938
16939 drop(buffer);
16940
16941 // Position the selection in the rename editor so that it matches the current selection.
16942 this.show_local_selections = false;
16943 let rename_editor = cx.new(|cx| {
16944 let mut editor = Editor::single_line(window, cx);
16945 editor.buffer.update(cx, |buffer, cx| {
16946 buffer.edit([(0..0, old_name.clone())], None, cx)
16947 });
16948 let rename_selection_range = match cursor_offset_in_rename_range
16949 .cmp(&cursor_offset_in_rename_range_end)
16950 {
16951 Ordering::Equal => {
16952 editor.select_all(&SelectAll, window, cx);
16953 return editor;
16954 }
16955 Ordering::Less => {
16956 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16957 }
16958 Ordering::Greater => {
16959 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16960 }
16961 };
16962 if rename_selection_range.end > old_name.len() {
16963 editor.select_all(&SelectAll, window, cx);
16964 } else {
16965 editor.change_selections(Default::default(), window, cx, |s| {
16966 s.select_ranges([rename_selection_range]);
16967 });
16968 }
16969 editor
16970 });
16971 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16972 if e == &EditorEvent::Focused {
16973 cx.emit(EditorEvent::FocusedIn)
16974 }
16975 })
16976 .detach();
16977
16978 let write_highlights =
16979 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16980 let read_highlights =
16981 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16982 let ranges = write_highlights
16983 .iter()
16984 .flat_map(|(_, ranges)| ranges.iter())
16985 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16986 .cloned()
16987 .collect();
16988
16989 this.highlight_text::<Rename>(
16990 ranges,
16991 HighlightStyle {
16992 fade_out: Some(0.6),
16993 ..Default::default()
16994 },
16995 cx,
16996 );
16997 let rename_focus_handle = rename_editor.focus_handle(cx);
16998 window.focus(&rename_focus_handle);
16999 let block_id = this.insert_blocks(
17000 [BlockProperties {
17001 style: BlockStyle::Flex,
17002 placement: BlockPlacement::Below(range.start),
17003 height: Some(1),
17004 render: Arc::new({
17005 let rename_editor = rename_editor.clone();
17006 move |cx: &mut BlockContext| {
17007 let mut text_style = cx.editor_style.text.clone();
17008 if let Some(highlight_style) = old_highlight_id
17009 .and_then(|h| h.style(&cx.editor_style.syntax))
17010 {
17011 text_style = text_style.highlight(highlight_style);
17012 }
17013 div()
17014 .block_mouse_except_scroll()
17015 .pl(cx.anchor_x)
17016 .child(EditorElement::new(
17017 &rename_editor,
17018 EditorStyle {
17019 background: cx.theme().system().transparent,
17020 local_player: cx.editor_style.local_player,
17021 text: text_style,
17022 scrollbar_width: cx.editor_style.scrollbar_width,
17023 syntax: cx.editor_style.syntax.clone(),
17024 status: cx.editor_style.status.clone(),
17025 inlay_hints_style: HighlightStyle {
17026 font_weight: Some(FontWeight::BOLD),
17027 ..make_inlay_hints_style(cx.app)
17028 },
17029 edit_prediction_styles: make_suggestion_styles(
17030 cx.app,
17031 ),
17032 ..EditorStyle::default()
17033 },
17034 ))
17035 .into_any_element()
17036 }
17037 }),
17038 priority: 0,
17039 }],
17040 Some(Autoscroll::fit()),
17041 cx,
17042 )[0];
17043 this.pending_rename = Some(RenameState {
17044 range,
17045 old_name,
17046 editor: rename_editor,
17047 block_id,
17048 });
17049 })?;
17050 }
17051
17052 Ok(())
17053 }))
17054 }
17055
17056 pub fn confirm_rename(
17057 &mut self,
17058 _: &ConfirmRename,
17059 window: &mut Window,
17060 cx: &mut Context<Self>,
17061 ) -> Option<Task<Result<()>>> {
17062 let rename = self.take_rename(false, window, cx)?;
17063 let workspace = self.workspace()?.downgrade();
17064 let (buffer, start) = self
17065 .buffer
17066 .read(cx)
17067 .text_anchor_for_position(rename.range.start, cx)?;
17068 let (end_buffer, _) = self
17069 .buffer
17070 .read(cx)
17071 .text_anchor_for_position(rename.range.end, cx)?;
17072 if buffer != end_buffer {
17073 return None;
17074 }
17075
17076 let old_name = rename.old_name;
17077 let new_name = rename.editor.read(cx).text(cx);
17078
17079 let rename = self.semantics_provider.as_ref()?.perform_rename(
17080 &buffer,
17081 start,
17082 new_name.clone(),
17083 cx,
17084 )?;
17085
17086 Some(cx.spawn_in(window, async move |editor, cx| {
17087 let project_transaction = rename.await?;
17088 Self::open_project_transaction(
17089 &editor,
17090 workspace,
17091 project_transaction,
17092 format!("Rename: {} → {}", old_name, new_name),
17093 cx,
17094 )
17095 .await?;
17096
17097 editor.update(cx, |editor, cx| {
17098 editor.refresh_document_highlights(cx);
17099 })?;
17100 Ok(())
17101 }))
17102 }
17103
17104 fn take_rename(
17105 &mut self,
17106 moving_cursor: bool,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) -> Option<RenameState> {
17110 let rename = self.pending_rename.take()?;
17111 if rename.editor.focus_handle(cx).is_focused(window) {
17112 window.focus(&self.focus_handle);
17113 }
17114
17115 self.remove_blocks(
17116 [rename.block_id].into_iter().collect(),
17117 Some(Autoscroll::fit()),
17118 cx,
17119 );
17120 self.clear_highlights::<Rename>(cx);
17121 self.show_local_selections = true;
17122
17123 if moving_cursor {
17124 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17125 editor
17126 .selections
17127 .newest::<usize>(&editor.display_snapshot(cx))
17128 .head()
17129 });
17130
17131 // Update the selection to match the position of the selection inside
17132 // the rename editor.
17133 let snapshot = self.buffer.read(cx).read(cx);
17134 let rename_range = rename.range.to_offset(&snapshot);
17135 let cursor_in_editor = snapshot
17136 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17137 .min(rename_range.end);
17138 drop(snapshot);
17139
17140 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17141 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17142 });
17143 } else {
17144 self.refresh_document_highlights(cx);
17145 }
17146
17147 Some(rename)
17148 }
17149
17150 pub fn pending_rename(&self) -> Option<&RenameState> {
17151 self.pending_rename.as_ref()
17152 }
17153
17154 fn format(
17155 &mut self,
17156 _: &Format,
17157 window: &mut Window,
17158 cx: &mut Context<Self>,
17159 ) -> Option<Task<Result<()>>> {
17160 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17161
17162 let project = match &self.project {
17163 Some(project) => project.clone(),
17164 None => return None,
17165 };
17166
17167 Some(self.perform_format(
17168 project,
17169 FormatTrigger::Manual,
17170 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17171 window,
17172 cx,
17173 ))
17174 }
17175
17176 fn format_selections(
17177 &mut self,
17178 _: &FormatSelections,
17179 window: &mut Window,
17180 cx: &mut Context<Self>,
17181 ) -> Option<Task<Result<()>>> {
17182 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17183
17184 let project = match &self.project {
17185 Some(project) => project.clone(),
17186 None => return None,
17187 };
17188
17189 let ranges = self
17190 .selections
17191 .all_adjusted(&self.display_snapshot(cx))
17192 .into_iter()
17193 .map(|selection| selection.range())
17194 .collect_vec();
17195
17196 Some(self.perform_format(
17197 project,
17198 FormatTrigger::Manual,
17199 FormatTarget::Ranges(ranges),
17200 window,
17201 cx,
17202 ))
17203 }
17204
17205 fn perform_format(
17206 &mut self,
17207 project: Entity<Project>,
17208 trigger: FormatTrigger,
17209 target: FormatTarget,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 ) -> Task<Result<()>> {
17213 let buffer = self.buffer.clone();
17214 let (buffers, target) = match target {
17215 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17216 FormatTarget::Ranges(selection_ranges) => {
17217 let multi_buffer = buffer.read(cx);
17218 let snapshot = multi_buffer.read(cx);
17219 let mut buffers = HashSet::default();
17220 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17221 BTreeMap::new();
17222 for selection_range in selection_ranges {
17223 for (buffer, buffer_range, _) in
17224 snapshot.range_to_buffer_ranges(selection_range)
17225 {
17226 let buffer_id = buffer.remote_id();
17227 let start = buffer.anchor_before(buffer_range.start);
17228 let end = buffer.anchor_after(buffer_range.end);
17229 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17230 buffer_id_to_ranges
17231 .entry(buffer_id)
17232 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17233 .or_insert_with(|| vec![start..end]);
17234 }
17235 }
17236 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17237 }
17238 };
17239
17240 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17241 let selections_prev = transaction_id_prev
17242 .and_then(|transaction_id_prev| {
17243 // default to selections as they were after the last edit, if we have them,
17244 // instead of how they are now.
17245 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17246 // will take you back to where you made the last edit, instead of staying where you scrolled
17247 self.selection_history
17248 .transaction(transaction_id_prev)
17249 .map(|t| t.0.clone())
17250 })
17251 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17252
17253 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17254 let format = project.update(cx, |project, cx| {
17255 project.format(buffers, target, true, trigger, cx)
17256 });
17257
17258 cx.spawn_in(window, async move |editor, cx| {
17259 let transaction = futures::select_biased! {
17260 transaction = format.log_err().fuse() => transaction,
17261 () = timeout => {
17262 log::warn!("timed out waiting for formatting");
17263 None
17264 }
17265 };
17266
17267 buffer
17268 .update(cx, |buffer, cx| {
17269 if let Some(transaction) = transaction
17270 && !buffer.is_singleton()
17271 {
17272 buffer.push_transaction(&transaction.0, cx);
17273 }
17274 cx.notify();
17275 })
17276 .ok();
17277
17278 if let Some(transaction_id_now) =
17279 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17280 {
17281 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17282 if has_new_transaction {
17283 _ = editor.update(cx, |editor, _| {
17284 editor
17285 .selection_history
17286 .insert_transaction(transaction_id_now, selections_prev);
17287 });
17288 }
17289 }
17290
17291 Ok(())
17292 })
17293 }
17294
17295 fn organize_imports(
17296 &mut self,
17297 _: &OrganizeImports,
17298 window: &mut Window,
17299 cx: &mut Context<Self>,
17300 ) -> Option<Task<Result<()>>> {
17301 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17302 let project = match &self.project {
17303 Some(project) => project.clone(),
17304 None => return None,
17305 };
17306 Some(self.perform_code_action_kind(
17307 project,
17308 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17309 window,
17310 cx,
17311 ))
17312 }
17313
17314 fn perform_code_action_kind(
17315 &mut self,
17316 project: Entity<Project>,
17317 kind: CodeActionKind,
17318 window: &mut Window,
17319 cx: &mut Context<Self>,
17320 ) -> Task<Result<()>> {
17321 let buffer = self.buffer.clone();
17322 let buffers = buffer.read(cx).all_buffers();
17323 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17324 let apply_action = project.update(cx, |project, cx| {
17325 project.apply_code_action_kind(buffers, kind, true, cx)
17326 });
17327 cx.spawn_in(window, async move |_, cx| {
17328 let transaction = futures::select_biased! {
17329 () = timeout => {
17330 log::warn!("timed out waiting for executing code action");
17331 None
17332 }
17333 transaction = apply_action.log_err().fuse() => transaction,
17334 };
17335 buffer
17336 .update(cx, |buffer, cx| {
17337 // check if we need this
17338 if let Some(transaction) = transaction
17339 && !buffer.is_singleton()
17340 {
17341 buffer.push_transaction(&transaction.0, cx);
17342 }
17343 cx.notify();
17344 })
17345 .ok();
17346 Ok(())
17347 })
17348 }
17349
17350 pub fn restart_language_server(
17351 &mut self,
17352 _: &RestartLanguageServer,
17353 _: &mut Window,
17354 cx: &mut Context<Self>,
17355 ) {
17356 if let Some(project) = self.project.clone() {
17357 self.buffer.update(cx, |multi_buffer, cx| {
17358 project.update(cx, |project, cx| {
17359 project.restart_language_servers_for_buffers(
17360 multi_buffer.all_buffers().into_iter().collect(),
17361 HashSet::default(),
17362 cx,
17363 );
17364 });
17365 })
17366 }
17367 }
17368
17369 pub fn stop_language_server(
17370 &mut self,
17371 _: &StopLanguageServer,
17372 _: &mut Window,
17373 cx: &mut Context<Self>,
17374 ) {
17375 if let Some(project) = self.project.clone() {
17376 self.buffer.update(cx, |multi_buffer, cx| {
17377 project.update(cx, |project, cx| {
17378 project.stop_language_servers_for_buffers(
17379 multi_buffer.all_buffers().into_iter().collect(),
17380 HashSet::default(),
17381 cx,
17382 );
17383 });
17384 });
17385 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17386 }
17387 }
17388
17389 fn cancel_language_server_work(
17390 workspace: &mut Workspace,
17391 _: &actions::CancelLanguageServerWork,
17392 _: &mut Window,
17393 cx: &mut Context<Workspace>,
17394 ) {
17395 let project = workspace.project();
17396 let buffers = workspace
17397 .active_item(cx)
17398 .and_then(|item| item.act_as::<Editor>(cx))
17399 .map_or(HashSet::default(), |editor| {
17400 editor.read(cx).buffer.read(cx).all_buffers()
17401 });
17402 project.update(cx, |project, cx| {
17403 project.cancel_language_server_work_for_buffers(buffers, cx);
17404 });
17405 }
17406
17407 fn show_character_palette(
17408 &mut self,
17409 _: &ShowCharacterPalette,
17410 window: &mut Window,
17411 _: &mut Context<Self>,
17412 ) {
17413 window.show_character_palette();
17414 }
17415
17416 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17417 if !self.diagnostics_enabled() {
17418 return;
17419 }
17420
17421 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17422 let buffer = self.buffer.read(cx).snapshot(cx);
17423 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17424 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17425 let is_valid = buffer
17426 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17427 .any(|entry| {
17428 entry.diagnostic.is_primary
17429 && !entry.range.is_empty()
17430 && entry.range.start == primary_range_start
17431 && entry.diagnostic.message == active_diagnostics.active_message
17432 });
17433
17434 if !is_valid {
17435 self.dismiss_diagnostics(cx);
17436 }
17437 }
17438 }
17439
17440 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17441 match &self.active_diagnostics {
17442 ActiveDiagnostic::Group(group) => Some(group),
17443 _ => None,
17444 }
17445 }
17446
17447 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17448 if !self.diagnostics_enabled() {
17449 return;
17450 }
17451 self.dismiss_diagnostics(cx);
17452 self.active_diagnostics = ActiveDiagnostic::All;
17453 }
17454
17455 fn activate_diagnostics(
17456 &mut self,
17457 buffer_id: BufferId,
17458 diagnostic: DiagnosticEntryRef<'_, usize>,
17459 window: &mut Window,
17460 cx: &mut Context<Self>,
17461 ) {
17462 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17463 return;
17464 }
17465 self.dismiss_diagnostics(cx);
17466 let snapshot = self.snapshot(window, cx);
17467 let buffer = self.buffer.read(cx).snapshot(cx);
17468 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17469 return;
17470 };
17471
17472 let diagnostic_group = buffer
17473 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17474 .collect::<Vec<_>>();
17475
17476 let blocks =
17477 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17478
17479 let blocks = self.display_map.update(cx, |display_map, cx| {
17480 display_map.insert_blocks(blocks, cx).into_iter().collect()
17481 });
17482 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17483 active_range: buffer.anchor_before(diagnostic.range.start)
17484 ..buffer.anchor_after(diagnostic.range.end),
17485 active_message: diagnostic.diagnostic.message.clone(),
17486 group_id: diagnostic.diagnostic.group_id,
17487 blocks,
17488 });
17489 cx.notify();
17490 }
17491
17492 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17493 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17494 return;
17495 };
17496
17497 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17498 if let ActiveDiagnostic::Group(group) = prev {
17499 self.display_map.update(cx, |display_map, cx| {
17500 display_map.remove_blocks(group.blocks, cx);
17501 });
17502 cx.notify();
17503 }
17504 }
17505
17506 /// Disable inline diagnostics rendering for this editor.
17507 pub fn disable_inline_diagnostics(&mut self) {
17508 self.inline_diagnostics_enabled = false;
17509 self.inline_diagnostics_update = Task::ready(());
17510 self.inline_diagnostics.clear();
17511 }
17512
17513 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17514 self.diagnostics_enabled = false;
17515 self.dismiss_diagnostics(cx);
17516 self.inline_diagnostics_update = Task::ready(());
17517 self.inline_diagnostics.clear();
17518 }
17519
17520 pub fn disable_word_completions(&mut self) {
17521 self.word_completions_enabled = false;
17522 }
17523
17524 pub fn diagnostics_enabled(&self) -> bool {
17525 self.diagnostics_enabled && self.mode.is_full()
17526 }
17527
17528 pub fn inline_diagnostics_enabled(&self) -> bool {
17529 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17530 }
17531
17532 pub fn show_inline_diagnostics(&self) -> bool {
17533 self.show_inline_diagnostics
17534 }
17535
17536 pub fn toggle_inline_diagnostics(
17537 &mut self,
17538 _: &ToggleInlineDiagnostics,
17539 window: &mut Window,
17540 cx: &mut Context<Editor>,
17541 ) {
17542 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17543 self.refresh_inline_diagnostics(false, window, cx);
17544 }
17545
17546 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17547 self.diagnostics_max_severity = severity;
17548 self.display_map.update(cx, |display_map, _| {
17549 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17550 });
17551 }
17552
17553 pub fn toggle_diagnostics(
17554 &mut self,
17555 _: &ToggleDiagnostics,
17556 window: &mut Window,
17557 cx: &mut Context<Editor>,
17558 ) {
17559 if !self.diagnostics_enabled() {
17560 return;
17561 }
17562
17563 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17564 EditorSettings::get_global(cx)
17565 .diagnostics_max_severity
17566 .filter(|severity| severity != &DiagnosticSeverity::Off)
17567 .unwrap_or(DiagnosticSeverity::Hint)
17568 } else {
17569 DiagnosticSeverity::Off
17570 };
17571 self.set_max_diagnostics_severity(new_severity, cx);
17572 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17573 self.active_diagnostics = ActiveDiagnostic::None;
17574 self.inline_diagnostics_update = Task::ready(());
17575 self.inline_diagnostics.clear();
17576 } else {
17577 self.refresh_inline_diagnostics(false, window, cx);
17578 }
17579
17580 cx.notify();
17581 }
17582
17583 pub fn toggle_minimap(
17584 &mut self,
17585 _: &ToggleMinimap,
17586 window: &mut Window,
17587 cx: &mut Context<Editor>,
17588 ) {
17589 if self.supports_minimap(cx) {
17590 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17591 }
17592 }
17593
17594 fn refresh_inline_diagnostics(
17595 &mut self,
17596 debounce: bool,
17597 window: &mut Window,
17598 cx: &mut Context<Self>,
17599 ) {
17600 let max_severity = ProjectSettings::get_global(cx)
17601 .diagnostics
17602 .inline
17603 .max_severity
17604 .unwrap_or(self.diagnostics_max_severity);
17605
17606 if !self.inline_diagnostics_enabled()
17607 || !self.show_inline_diagnostics
17608 || max_severity == DiagnosticSeverity::Off
17609 {
17610 self.inline_diagnostics_update = Task::ready(());
17611 self.inline_diagnostics.clear();
17612 return;
17613 }
17614
17615 let debounce_ms = ProjectSettings::get_global(cx)
17616 .diagnostics
17617 .inline
17618 .update_debounce_ms;
17619 let debounce = if debounce && debounce_ms > 0 {
17620 Some(Duration::from_millis(debounce_ms))
17621 } else {
17622 None
17623 };
17624 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17625 if let Some(debounce) = debounce {
17626 cx.background_executor().timer(debounce).await;
17627 }
17628 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17629 editor
17630 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17631 .ok()
17632 }) else {
17633 return;
17634 };
17635
17636 let new_inline_diagnostics = cx
17637 .background_spawn(async move {
17638 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17639 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17640 let message = diagnostic_entry
17641 .diagnostic
17642 .message
17643 .split_once('\n')
17644 .map(|(line, _)| line)
17645 .map(SharedString::new)
17646 .unwrap_or_else(|| {
17647 SharedString::new(&*diagnostic_entry.diagnostic.message)
17648 });
17649 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17650 let (Ok(i) | Err(i)) = inline_diagnostics
17651 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17652 inline_diagnostics.insert(
17653 i,
17654 (
17655 start_anchor,
17656 InlineDiagnostic {
17657 message,
17658 group_id: diagnostic_entry.diagnostic.group_id,
17659 start: diagnostic_entry.range.start.to_point(&snapshot),
17660 is_primary: diagnostic_entry.diagnostic.is_primary,
17661 severity: diagnostic_entry.diagnostic.severity,
17662 },
17663 ),
17664 );
17665 }
17666 inline_diagnostics
17667 })
17668 .await;
17669
17670 editor
17671 .update(cx, |editor, cx| {
17672 editor.inline_diagnostics = new_inline_diagnostics;
17673 cx.notify();
17674 })
17675 .ok();
17676 });
17677 }
17678
17679 fn pull_diagnostics(
17680 &mut self,
17681 buffer_id: Option<BufferId>,
17682 window: &Window,
17683 cx: &mut Context<Self>,
17684 ) -> Option<()> {
17685 if self.ignore_lsp_data() {
17686 return None;
17687 }
17688 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17689 .diagnostics
17690 .lsp_pull_diagnostics;
17691 if !pull_diagnostics_settings.enabled {
17692 return None;
17693 }
17694 let project = self.project()?.downgrade();
17695 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17696 let mut buffers = self.buffer.read(cx).all_buffers();
17697 buffers.retain(|buffer| {
17698 let buffer_id_to_retain = buffer.read(cx).remote_id();
17699 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17700 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17701 });
17702 if buffers.is_empty() {
17703 self.pull_diagnostics_task = Task::ready(());
17704 return None;
17705 }
17706
17707 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17708 cx.background_executor().timer(debounce).await;
17709
17710 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17711 buffers
17712 .into_iter()
17713 .filter_map(|buffer| {
17714 project
17715 .update(cx, |project, cx| {
17716 project.lsp_store().update(cx, |lsp_store, cx| {
17717 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17718 })
17719 })
17720 .ok()
17721 })
17722 .collect::<FuturesUnordered<_>>()
17723 }) else {
17724 return;
17725 };
17726
17727 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17728 match pull_task {
17729 Ok(()) => {
17730 if editor
17731 .update_in(cx, |editor, window, cx| {
17732 editor.update_diagnostics_state(window, cx);
17733 })
17734 .is_err()
17735 {
17736 return;
17737 }
17738 }
17739 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17740 }
17741 }
17742 });
17743
17744 Some(())
17745 }
17746
17747 pub fn set_selections_from_remote(
17748 &mut self,
17749 selections: Vec<Selection<Anchor>>,
17750 pending_selection: Option<Selection<Anchor>>,
17751 window: &mut Window,
17752 cx: &mut Context<Self>,
17753 ) {
17754 let old_cursor_position = self.selections.newest_anchor().head();
17755 self.selections.change_with(cx, |s| {
17756 s.select_anchors(selections);
17757 if let Some(pending_selection) = pending_selection {
17758 s.set_pending(pending_selection, SelectMode::Character);
17759 } else {
17760 s.clear_pending();
17761 }
17762 });
17763 self.selections_did_change(
17764 false,
17765 &old_cursor_position,
17766 SelectionEffects::default(),
17767 window,
17768 cx,
17769 );
17770 }
17771
17772 pub fn transact(
17773 &mut self,
17774 window: &mut Window,
17775 cx: &mut Context<Self>,
17776 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17777 ) -> Option<TransactionId> {
17778 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17779 this.start_transaction_at(Instant::now(), window, cx);
17780 update(this, window, cx);
17781 this.end_transaction_at(Instant::now(), cx)
17782 })
17783 }
17784
17785 pub fn start_transaction_at(
17786 &mut self,
17787 now: Instant,
17788 window: &mut Window,
17789 cx: &mut Context<Self>,
17790 ) -> Option<TransactionId> {
17791 self.end_selection(window, cx);
17792 if let Some(tx_id) = self
17793 .buffer
17794 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17795 {
17796 self.selection_history
17797 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17798 cx.emit(EditorEvent::TransactionBegun {
17799 transaction_id: tx_id,
17800 });
17801 Some(tx_id)
17802 } else {
17803 None
17804 }
17805 }
17806
17807 pub fn end_transaction_at(
17808 &mut self,
17809 now: Instant,
17810 cx: &mut Context<Self>,
17811 ) -> Option<TransactionId> {
17812 if let Some(transaction_id) = self
17813 .buffer
17814 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17815 {
17816 if let Some((_, end_selections)) =
17817 self.selection_history.transaction_mut(transaction_id)
17818 {
17819 *end_selections = Some(self.selections.disjoint_anchors_arc());
17820 } else {
17821 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17822 }
17823
17824 cx.emit(EditorEvent::Edited { transaction_id });
17825 Some(transaction_id)
17826 } else {
17827 None
17828 }
17829 }
17830
17831 pub fn modify_transaction_selection_history(
17832 &mut self,
17833 transaction_id: TransactionId,
17834 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17835 ) -> bool {
17836 self.selection_history
17837 .transaction_mut(transaction_id)
17838 .map(modify)
17839 .is_some()
17840 }
17841
17842 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17843 if self.selection_mark_mode {
17844 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17845 s.move_with(|_, sel| {
17846 sel.collapse_to(sel.head(), SelectionGoal::None);
17847 });
17848 })
17849 }
17850 self.selection_mark_mode = true;
17851 cx.notify();
17852 }
17853
17854 pub fn swap_selection_ends(
17855 &mut self,
17856 _: &actions::SwapSelectionEnds,
17857 window: &mut Window,
17858 cx: &mut Context<Self>,
17859 ) {
17860 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17861 s.move_with(|_, sel| {
17862 if sel.start != sel.end {
17863 sel.reversed = !sel.reversed
17864 }
17865 });
17866 });
17867 self.request_autoscroll(Autoscroll::newest(), cx);
17868 cx.notify();
17869 }
17870
17871 pub fn toggle_focus(
17872 workspace: &mut Workspace,
17873 _: &actions::ToggleFocus,
17874 window: &mut Window,
17875 cx: &mut Context<Workspace>,
17876 ) {
17877 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17878 return;
17879 };
17880 workspace.activate_item(&item, true, true, window, cx);
17881 }
17882
17883 pub fn toggle_fold(
17884 &mut self,
17885 _: &actions::ToggleFold,
17886 window: &mut Window,
17887 cx: &mut Context<Self>,
17888 ) {
17889 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17891 let selection = self.selections.newest::<Point>(&display_map);
17892
17893 let range = if selection.is_empty() {
17894 let point = selection.head().to_display_point(&display_map);
17895 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17896 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17897 .to_point(&display_map);
17898 start..end
17899 } else {
17900 selection.range()
17901 };
17902 if display_map.folds_in_range(range).next().is_some() {
17903 self.unfold_lines(&Default::default(), window, cx)
17904 } else {
17905 self.fold(&Default::default(), window, cx)
17906 }
17907 } else {
17908 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17909 let buffer_ids: HashSet<_> = self
17910 .selections
17911 .disjoint_anchor_ranges()
17912 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17913 .collect();
17914
17915 let should_unfold = buffer_ids
17916 .iter()
17917 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17918
17919 for buffer_id in buffer_ids {
17920 if should_unfold {
17921 self.unfold_buffer(buffer_id, cx);
17922 } else {
17923 self.fold_buffer(buffer_id, cx);
17924 }
17925 }
17926 }
17927 }
17928
17929 pub fn toggle_fold_recursive(
17930 &mut self,
17931 _: &actions::ToggleFoldRecursive,
17932 window: &mut Window,
17933 cx: &mut Context<Self>,
17934 ) {
17935 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17936
17937 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17938 let range = if selection.is_empty() {
17939 let point = selection.head().to_display_point(&display_map);
17940 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17941 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17942 .to_point(&display_map);
17943 start..end
17944 } else {
17945 selection.range()
17946 };
17947 if display_map.folds_in_range(range).next().is_some() {
17948 self.unfold_recursive(&Default::default(), window, cx)
17949 } else {
17950 self.fold_recursive(&Default::default(), window, cx)
17951 }
17952 }
17953
17954 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17955 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17956 let mut to_fold = Vec::new();
17957 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17958 let selections = self.selections.all_adjusted(&display_map);
17959
17960 for selection in selections {
17961 let range = selection.range().sorted();
17962 let buffer_start_row = range.start.row;
17963
17964 if range.start.row != range.end.row {
17965 let mut found = false;
17966 let mut row = range.start.row;
17967 while row <= range.end.row {
17968 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17969 {
17970 found = true;
17971 row = crease.range().end.row + 1;
17972 to_fold.push(crease);
17973 } else {
17974 row += 1
17975 }
17976 }
17977 if found {
17978 continue;
17979 }
17980 }
17981
17982 for row in (0..=range.start.row).rev() {
17983 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17984 && crease.range().end.row >= buffer_start_row
17985 {
17986 to_fold.push(crease);
17987 if row <= range.start.row {
17988 break;
17989 }
17990 }
17991 }
17992 }
17993
17994 self.fold_creases(to_fold, true, window, cx);
17995 } else {
17996 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17997 let buffer_ids = self
17998 .selections
17999 .disjoint_anchor_ranges()
18000 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18001 .collect::<HashSet<_>>();
18002 for buffer_id in buffer_ids {
18003 self.fold_buffer(buffer_id, cx);
18004 }
18005 }
18006 }
18007
18008 pub fn toggle_fold_all(
18009 &mut self,
18010 _: &actions::ToggleFoldAll,
18011 window: &mut Window,
18012 cx: &mut Context<Self>,
18013 ) {
18014 if self.buffer.read(cx).is_singleton() {
18015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18016 let has_folds = display_map
18017 .folds_in_range(0..display_map.buffer_snapshot().len())
18018 .next()
18019 .is_some();
18020
18021 if has_folds {
18022 self.unfold_all(&actions::UnfoldAll, window, cx);
18023 } else {
18024 self.fold_all(&actions::FoldAll, window, cx);
18025 }
18026 } else {
18027 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18028 let should_unfold = buffer_ids
18029 .iter()
18030 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18031
18032 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18033 editor
18034 .update_in(cx, |editor, _, cx| {
18035 for buffer_id in buffer_ids {
18036 if should_unfold {
18037 editor.unfold_buffer(buffer_id, cx);
18038 } else {
18039 editor.fold_buffer(buffer_id, cx);
18040 }
18041 }
18042 })
18043 .ok();
18044 });
18045 }
18046 }
18047
18048 fn fold_at_level(
18049 &mut self,
18050 fold_at: &FoldAtLevel,
18051 window: &mut Window,
18052 cx: &mut Context<Self>,
18053 ) {
18054 if !self.buffer.read(cx).is_singleton() {
18055 return;
18056 }
18057
18058 let fold_at_level = fold_at.0;
18059 let snapshot = self.buffer.read(cx).snapshot(cx);
18060 let mut to_fold = Vec::new();
18061 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18062
18063 let row_ranges_to_keep: Vec<Range<u32>> = self
18064 .selections
18065 .all::<Point>(&self.display_snapshot(cx))
18066 .into_iter()
18067 .map(|sel| sel.start.row..sel.end.row)
18068 .collect();
18069
18070 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18071 while start_row < end_row {
18072 match self
18073 .snapshot(window, cx)
18074 .crease_for_buffer_row(MultiBufferRow(start_row))
18075 {
18076 Some(crease) => {
18077 let nested_start_row = crease.range().start.row + 1;
18078 let nested_end_row = crease.range().end.row;
18079
18080 if current_level < fold_at_level {
18081 stack.push((nested_start_row, nested_end_row, current_level + 1));
18082 } else if current_level == fold_at_level {
18083 // Fold iff there is no selection completely contained within the fold region
18084 if !row_ranges_to_keep.iter().any(|selection| {
18085 selection.end >= nested_start_row
18086 && selection.start <= nested_end_row
18087 }) {
18088 to_fold.push(crease);
18089 }
18090 }
18091
18092 start_row = nested_end_row + 1;
18093 }
18094 None => start_row += 1,
18095 }
18096 }
18097 }
18098
18099 self.fold_creases(to_fold, true, window, cx);
18100 }
18101
18102 pub fn fold_at_level_1(
18103 &mut self,
18104 _: &actions::FoldAtLevel1,
18105 window: &mut Window,
18106 cx: &mut Context<Self>,
18107 ) {
18108 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18109 }
18110
18111 pub fn fold_at_level_2(
18112 &mut self,
18113 _: &actions::FoldAtLevel2,
18114 window: &mut Window,
18115 cx: &mut Context<Self>,
18116 ) {
18117 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18118 }
18119
18120 pub fn fold_at_level_3(
18121 &mut self,
18122 _: &actions::FoldAtLevel3,
18123 window: &mut Window,
18124 cx: &mut Context<Self>,
18125 ) {
18126 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18127 }
18128
18129 pub fn fold_at_level_4(
18130 &mut self,
18131 _: &actions::FoldAtLevel4,
18132 window: &mut Window,
18133 cx: &mut Context<Self>,
18134 ) {
18135 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18136 }
18137
18138 pub fn fold_at_level_5(
18139 &mut self,
18140 _: &actions::FoldAtLevel5,
18141 window: &mut Window,
18142 cx: &mut Context<Self>,
18143 ) {
18144 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18145 }
18146
18147 pub fn fold_at_level_6(
18148 &mut self,
18149 _: &actions::FoldAtLevel6,
18150 window: &mut Window,
18151 cx: &mut Context<Self>,
18152 ) {
18153 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18154 }
18155
18156 pub fn fold_at_level_7(
18157 &mut self,
18158 _: &actions::FoldAtLevel7,
18159 window: &mut Window,
18160 cx: &mut Context<Self>,
18161 ) {
18162 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18163 }
18164
18165 pub fn fold_at_level_8(
18166 &mut self,
18167 _: &actions::FoldAtLevel8,
18168 window: &mut Window,
18169 cx: &mut Context<Self>,
18170 ) {
18171 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18172 }
18173
18174 pub fn fold_at_level_9(
18175 &mut self,
18176 _: &actions::FoldAtLevel9,
18177 window: &mut Window,
18178 cx: &mut Context<Self>,
18179 ) {
18180 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18181 }
18182
18183 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18184 if self.buffer.read(cx).is_singleton() {
18185 let mut fold_ranges = Vec::new();
18186 let snapshot = self.buffer.read(cx).snapshot(cx);
18187
18188 for row in 0..snapshot.max_row().0 {
18189 if let Some(foldable_range) = self
18190 .snapshot(window, cx)
18191 .crease_for_buffer_row(MultiBufferRow(row))
18192 {
18193 fold_ranges.push(foldable_range);
18194 }
18195 }
18196
18197 self.fold_creases(fold_ranges, true, window, cx);
18198 } else {
18199 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18200 editor
18201 .update_in(cx, |editor, _, cx| {
18202 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18203 editor.fold_buffer(buffer_id, cx);
18204 }
18205 })
18206 .ok();
18207 });
18208 }
18209 }
18210
18211 pub fn fold_function_bodies(
18212 &mut self,
18213 _: &actions::FoldFunctionBodies,
18214 window: &mut Window,
18215 cx: &mut Context<Self>,
18216 ) {
18217 let snapshot = self.buffer.read(cx).snapshot(cx);
18218
18219 let ranges = snapshot
18220 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18221 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18222 .collect::<Vec<_>>();
18223
18224 let creases = ranges
18225 .into_iter()
18226 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18227 .collect();
18228
18229 self.fold_creases(creases, true, window, cx);
18230 }
18231
18232 pub fn fold_recursive(
18233 &mut self,
18234 _: &actions::FoldRecursive,
18235 window: &mut Window,
18236 cx: &mut Context<Self>,
18237 ) {
18238 let mut to_fold = Vec::new();
18239 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18240 let selections = self.selections.all_adjusted(&display_map);
18241
18242 for selection in selections {
18243 let range = selection.range().sorted();
18244 let buffer_start_row = range.start.row;
18245
18246 if range.start.row != range.end.row {
18247 let mut found = false;
18248 for row in range.start.row..=range.end.row {
18249 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18250 found = true;
18251 to_fold.push(crease);
18252 }
18253 }
18254 if found {
18255 continue;
18256 }
18257 }
18258
18259 for row in (0..=range.start.row).rev() {
18260 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18261 if crease.range().end.row >= buffer_start_row {
18262 to_fold.push(crease);
18263 } else {
18264 break;
18265 }
18266 }
18267 }
18268 }
18269
18270 self.fold_creases(to_fold, true, window, cx);
18271 }
18272
18273 pub fn fold_at(
18274 &mut self,
18275 buffer_row: MultiBufferRow,
18276 window: &mut Window,
18277 cx: &mut Context<Self>,
18278 ) {
18279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18280
18281 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18282 let autoscroll = self
18283 .selections
18284 .all::<Point>(&display_map)
18285 .iter()
18286 .any(|selection| crease.range().overlaps(&selection.range()));
18287
18288 self.fold_creases(vec![crease], autoscroll, window, cx);
18289 }
18290 }
18291
18292 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18293 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18294 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18295 let buffer = display_map.buffer_snapshot();
18296 let selections = self.selections.all::<Point>(&display_map);
18297 let ranges = selections
18298 .iter()
18299 .map(|s| {
18300 let range = s.display_range(&display_map).sorted();
18301 let mut start = range.start.to_point(&display_map);
18302 let mut end = range.end.to_point(&display_map);
18303 start.column = 0;
18304 end.column = buffer.line_len(MultiBufferRow(end.row));
18305 start..end
18306 })
18307 .collect::<Vec<_>>();
18308
18309 self.unfold_ranges(&ranges, true, true, cx);
18310 } else {
18311 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18312 let buffer_ids = self
18313 .selections
18314 .disjoint_anchor_ranges()
18315 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18316 .collect::<HashSet<_>>();
18317 for buffer_id in buffer_ids {
18318 self.unfold_buffer(buffer_id, cx);
18319 }
18320 }
18321 }
18322
18323 pub fn unfold_recursive(
18324 &mut self,
18325 _: &UnfoldRecursive,
18326 _window: &mut Window,
18327 cx: &mut Context<Self>,
18328 ) {
18329 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18330 let selections = self.selections.all::<Point>(&display_map);
18331 let ranges = selections
18332 .iter()
18333 .map(|s| {
18334 let mut range = s.display_range(&display_map).sorted();
18335 *range.start.column_mut() = 0;
18336 *range.end.column_mut() = display_map.line_len(range.end.row());
18337 let start = range.start.to_point(&display_map);
18338 let end = range.end.to_point(&display_map);
18339 start..end
18340 })
18341 .collect::<Vec<_>>();
18342
18343 self.unfold_ranges(&ranges, true, true, cx);
18344 }
18345
18346 pub fn unfold_at(
18347 &mut self,
18348 buffer_row: MultiBufferRow,
18349 _window: &mut Window,
18350 cx: &mut Context<Self>,
18351 ) {
18352 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18353
18354 let intersection_range = Point::new(buffer_row.0, 0)
18355 ..Point::new(
18356 buffer_row.0,
18357 display_map.buffer_snapshot().line_len(buffer_row),
18358 );
18359
18360 let autoscroll = self
18361 .selections
18362 .all::<Point>(&display_map)
18363 .iter()
18364 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18365
18366 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18367 }
18368
18369 pub fn unfold_all(
18370 &mut self,
18371 _: &actions::UnfoldAll,
18372 _window: &mut Window,
18373 cx: &mut Context<Self>,
18374 ) {
18375 if self.buffer.read(cx).is_singleton() {
18376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18377 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18378 } else {
18379 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18380 editor
18381 .update(cx, |editor, cx| {
18382 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18383 editor.unfold_buffer(buffer_id, cx);
18384 }
18385 })
18386 .ok();
18387 });
18388 }
18389 }
18390
18391 pub fn fold_selected_ranges(
18392 &mut self,
18393 _: &FoldSelectedRanges,
18394 window: &mut Window,
18395 cx: &mut Context<Self>,
18396 ) {
18397 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18398 let selections = self.selections.all_adjusted(&display_map);
18399 let ranges = selections
18400 .into_iter()
18401 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18402 .collect::<Vec<_>>();
18403 self.fold_creases(ranges, true, window, cx);
18404 }
18405
18406 pub fn fold_ranges<T: ToOffset + Clone>(
18407 &mut self,
18408 ranges: Vec<Range<T>>,
18409 auto_scroll: bool,
18410 window: &mut Window,
18411 cx: &mut Context<Self>,
18412 ) {
18413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18414 let ranges = ranges
18415 .into_iter()
18416 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18417 .collect::<Vec<_>>();
18418 self.fold_creases(ranges, auto_scroll, window, cx);
18419 }
18420
18421 pub fn fold_creases<T: ToOffset + Clone>(
18422 &mut self,
18423 creases: Vec<Crease<T>>,
18424 auto_scroll: bool,
18425 _window: &mut Window,
18426 cx: &mut Context<Self>,
18427 ) {
18428 if creases.is_empty() {
18429 return;
18430 }
18431
18432 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18433
18434 if auto_scroll {
18435 self.request_autoscroll(Autoscroll::fit(), cx);
18436 }
18437
18438 cx.notify();
18439
18440 self.scrollbar_marker_state.dirty = true;
18441 self.folds_did_change(cx);
18442 }
18443
18444 /// Removes any folds whose ranges intersect any of the given ranges.
18445 pub fn unfold_ranges<T: ToOffset + Clone>(
18446 &mut self,
18447 ranges: &[Range<T>],
18448 inclusive: bool,
18449 auto_scroll: bool,
18450 cx: &mut Context<Self>,
18451 ) {
18452 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18453 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18454 });
18455 self.folds_did_change(cx);
18456 }
18457
18458 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18459 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18460 return;
18461 }
18462 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18463 self.display_map.update(cx, |display_map, cx| {
18464 display_map.fold_buffers([buffer_id], cx)
18465 });
18466 cx.emit(EditorEvent::BufferFoldToggled {
18467 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18468 folded: true,
18469 });
18470 cx.notify();
18471 }
18472
18473 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18474 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18475 return;
18476 }
18477 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18478 self.display_map.update(cx, |display_map, cx| {
18479 display_map.unfold_buffers([buffer_id], cx);
18480 });
18481 cx.emit(EditorEvent::BufferFoldToggled {
18482 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18483 folded: false,
18484 });
18485 cx.notify();
18486 }
18487
18488 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18489 self.display_map.read(cx).is_buffer_folded(buffer)
18490 }
18491
18492 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18493 self.display_map.read(cx).folded_buffers()
18494 }
18495
18496 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18497 self.display_map.update(cx, |display_map, cx| {
18498 display_map.disable_header_for_buffer(buffer_id, cx);
18499 });
18500 cx.notify();
18501 }
18502
18503 /// Removes any folds with the given ranges.
18504 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18505 &mut self,
18506 ranges: &[Range<T>],
18507 type_id: TypeId,
18508 auto_scroll: bool,
18509 cx: &mut Context<Self>,
18510 ) {
18511 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18512 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18513 });
18514 self.folds_did_change(cx);
18515 }
18516
18517 fn remove_folds_with<T: ToOffset + Clone>(
18518 &mut self,
18519 ranges: &[Range<T>],
18520 auto_scroll: bool,
18521 cx: &mut Context<Self>,
18522 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18523 ) {
18524 if ranges.is_empty() {
18525 return;
18526 }
18527
18528 let mut buffers_affected = HashSet::default();
18529 let multi_buffer = self.buffer().read(cx);
18530 for range in ranges {
18531 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18532 buffers_affected.insert(buffer.read(cx).remote_id());
18533 };
18534 }
18535
18536 self.display_map.update(cx, update);
18537
18538 if auto_scroll {
18539 self.request_autoscroll(Autoscroll::fit(), cx);
18540 }
18541
18542 cx.notify();
18543 self.scrollbar_marker_state.dirty = true;
18544 self.active_indent_guides_state.dirty = true;
18545 }
18546
18547 pub fn update_renderer_widths(
18548 &mut self,
18549 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18550 cx: &mut Context<Self>,
18551 ) -> bool {
18552 self.display_map
18553 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18554 }
18555
18556 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18557 self.display_map.read(cx).fold_placeholder.clone()
18558 }
18559
18560 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18561 self.buffer.update(cx, |buffer, cx| {
18562 buffer.set_all_diff_hunks_expanded(cx);
18563 });
18564 }
18565
18566 pub fn expand_all_diff_hunks(
18567 &mut self,
18568 _: &ExpandAllDiffHunks,
18569 _window: &mut Window,
18570 cx: &mut Context<Self>,
18571 ) {
18572 self.buffer.update(cx, |buffer, cx| {
18573 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18574 });
18575 }
18576
18577 pub fn collapse_all_diff_hunks(
18578 &mut self,
18579 _: &CollapseAllDiffHunks,
18580 _window: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) {
18583 self.buffer.update(cx, |buffer, cx| {
18584 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18585 });
18586 }
18587
18588 pub fn toggle_selected_diff_hunks(
18589 &mut self,
18590 _: &ToggleSelectedDiffHunks,
18591 _window: &mut Window,
18592 cx: &mut Context<Self>,
18593 ) {
18594 let ranges: Vec<_> = self
18595 .selections
18596 .disjoint_anchors()
18597 .iter()
18598 .map(|s| s.range())
18599 .collect();
18600 self.toggle_diff_hunks_in_ranges(ranges, cx);
18601 }
18602
18603 pub fn diff_hunks_in_ranges<'a>(
18604 &'a self,
18605 ranges: &'a [Range<Anchor>],
18606 buffer: &'a MultiBufferSnapshot,
18607 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18608 ranges.iter().flat_map(move |range| {
18609 let end_excerpt_id = range.end.excerpt_id;
18610 let range = range.to_point(buffer);
18611 let mut peek_end = range.end;
18612 if range.end.row < buffer.max_row().0 {
18613 peek_end = Point::new(range.end.row + 1, 0);
18614 }
18615 buffer
18616 .diff_hunks_in_range(range.start..peek_end)
18617 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18618 })
18619 }
18620
18621 pub fn has_stageable_diff_hunks_in_ranges(
18622 &self,
18623 ranges: &[Range<Anchor>],
18624 snapshot: &MultiBufferSnapshot,
18625 ) -> bool {
18626 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18627 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18628 }
18629
18630 pub fn toggle_staged_selected_diff_hunks(
18631 &mut self,
18632 _: &::git::ToggleStaged,
18633 _: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 let snapshot = self.buffer.read(cx).snapshot(cx);
18637 let ranges: Vec<_> = self
18638 .selections
18639 .disjoint_anchors()
18640 .iter()
18641 .map(|s| s.range())
18642 .collect();
18643 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18644 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18645 }
18646
18647 pub fn set_render_diff_hunk_controls(
18648 &mut self,
18649 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18650 cx: &mut Context<Self>,
18651 ) {
18652 self.render_diff_hunk_controls = render_diff_hunk_controls;
18653 cx.notify();
18654 }
18655
18656 pub fn stage_and_next(
18657 &mut self,
18658 _: &::git::StageAndNext,
18659 window: &mut Window,
18660 cx: &mut Context<Self>,
18661 ) {
18662 self.do_stage_or_unstage_and_next(true, window, cx);
18663 }
18664
18665 pub fn unstage_and_next(
18666 &mut self,
18667 _: &::git::UnstageAndNext,
18668 window: &mut Window,
18669 cx: &mut Context<Self>,
18670 ) {
18671 self.do_stage_or_unstage_and_next(false, window, cx);
18672 }
18673
18674 pub fn stage_or_unstage_diff_hunks(
18675 &mut self,
18676 stage: bool,
18677 ranges: Vec<Range<Anchor>>,
18678 cx: &mut Context<Self>,
18679 ) {
18680 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18681 cx.spawn(async move |this, cx| {
18682 task.await?;
18683 this.update(cx, |this, cx| {
18684 let snapshot = this.buffer.read(cx).snapshot(cx);
18685 let chunk_by = this
18686 .diff_hunks_in_ranges(&ranges, &snapshot)
18687 .chunk_by(|hunk| hunk.buffer_id);
18688 for (buffer_id, hunks) in &chunk_by {
18689 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18690 }
18691 })
18692 })
18693 .detach_and_log_err(cx);
18694 }
18695
18696 fn save_buffers_for_ranges_if_needed(
18697 &mut self,
18698 ranges: &[Range<Anchor>],
18699 cx: &mut Context<Editor>,
18700 ) -> Task<Result<()>> {
18701 let multibuffer = self.buffer.read(cx);
18702 let snapshot = multibuffer.read(cx);
18703 let buffer_ids: HashSet<_> = ranges
18704 .iter()
18705 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18706 .collect();
18707 drop(snapshot);
18708
18709 let mut buffers = HashSet::default();
18710 for buffer_id in buffer_ids {
18711 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18712 let buffer = buffer_entity.read(cx);
18713 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18714 {
18715 buffers.insert(buffer_entity);
18716 }
18717 }
18718 }
18719
18720 if let Some(project) = &self.project {
18721 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18722 } else {
18723 Task::ready(Ok(()))
18724 }
18725 }
18726
18727 fn do_stage_or_unstage_and_next(
18728 &mut self,
18729 stage: bool,
18730 window: &mut Window,
18731 cx: &mut Context<Self>,
18732 ) {
18733 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18734
18735 if ranges.iter().any(|range| range.start != range.end) {
18736 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18737 return;
18738 }
18739
18740 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18741 let snapshot = self.snapshot(window, cx);
18742 let position = self
18743 .selections
18744 .newest::<Point>(&snapshot.display_snapshot)
18745 .head();
18746 let mut row = snapshot
18747 .buffer_snapshot()
18748 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18749 .find(|hunk| hunk.row_range.start.0 > position.row)
18750 .map(|hunk| hunk.row_range.start);
18751
18752 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18753 // Outside of the project diff editor, wrap around to the beginning.
18754 if !all_diff_hunks_expanded {
18755 row = row.or_else(|| {
18756 snapshot
18757 .buffer_snapshot()
18758 .diff_hunks_in_range(Point::zero()..position)
18759 .find(|hunk| hunk.row_range.end.0 < position.row)
18760 .map(|hunk| hunk.row_range.start)
18761 });
18762 }
18763
18764 if let Some(row) = row {
18765 let destination = Point::new(row.0, 0);
18766 let autoscroll = Autoscroll::center();
18767
18768 self.unfold_ranges(&[destination..destination], false, false, cx);
18769 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18770 s.select_ranges([destination..destination]);
18771 });
18772 }
18773 }
18774
18775 fn do_stage_or_unstage(
18776 &self,
18777 stage: bool,
18778 buffer_id: BufferId,
18779 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18780 cx: &mut App,
18781 ) -> Option<()> {
18782 let project = self.project()?;
18783 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18784 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18785 let buffer_snapshot = buffer.read(cx).snapshot();
18786 let file_exists = buffer_snapshot
18787 .file()
18788 .is_some_and(|file| file.disk_state().exists());
18789 diff.update(cx, |diff, cx| {
18790 diff.stage_or_unstage_hunks(
18791 stage,
18792 &hunks
18793 .map(|hunk| buffer_diff::DiffHunk {
18794 buffer_range: hunk.buffer_range,
18795 diff_base_byte_range: hunk.diff_base_byte_range,
18796 secondary_status: hunk.secondary_status,
18797 range: Point::zero()..Point::zero(), // unused
18798 })
18799 .collect::<Vec<_>>(),
18800 &buffer_snapshot,
18801 file_exists,
18802 cx,
18803 )
18804 });
18805 None
18806 }
18807
18808 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18809 let ranges: Vec<_> = self
18810 .selections
18811 .disjoint_anchors()
18812 .iter()
18813 .map(|s| s.range())
18814 .collect();
18815 self.buffer
18816 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18817 }
18818
18819 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18820 self.buffer.update(cx, |buffer, cx| {
18821 let ranges = vec![Anchor::min()..Anchor::max()];
18822 if !buffer.all_diff_hunks_expanded()
18823 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18824 {
18825 buffer.collapse_diff_hunks(ranges, cx);
18826 true
18827 } else {
18828 false
18829 }
18830 })
18831 }
18832
18833 fn toggle_diff_hunks_in_ranges(
18834 &mut self,
18835 ranges: Vec<Range<Anchor>>,
18836 cx: &mut Context<Editor>,
18837 ) {
18838 self.buffer.update(cx, |buffer, cx| {
18839 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18840 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18841 })
18842 }
18843
18844 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18845 self.buffer.update(cx, |buffer, cx| {
18846 let snapshot = buffer.snapshot(cx);
18847 let excerpt_id = range.end.excerpt_id;
18848 let point_range = range.to_point(&snapshot);
18849 let expand = !buffer.single_hunk_is_expanded(range, cx);
18850 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18851 })
18852 }
18853
18854 pub(crate) fn apply_all_diff_hunks(
18855 &mut self,
18856 _: &ApplyAllDiffHunks,
18857 window: &mut Window,
18858 cx: &mut Context<Self>,
18859 ) {
18860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18861
18862 let buffers = self.buffer.read(cx).all_buffers();
18863 for branch_buffer in buffers {
18864 branch_buffer.update(cx, |branch_buffer, cx| {
18865 branch_buffer.merge_into_base(Vec::new(), cx);
18866 });
18867 }
18868
18869 if let Some(project) = self.project.clone() {
18870 self.save(
18871 SaveOptions {
18872 format: true,
18873 autosave: false,
18874 },
18875 project,
18876 window,
18877 cx,
18878 )
18879 .detach_and_log_err(cx);
18880 }
18881 }
18882
18883 pub(crate) fn apply_selected_diff_hunks(
18884 &mut self,
18885 _: &ApplyDiffHunk,
18886 window: &mut Window,
18887 cx: &mut Context<Self>,
18888 ) {
18889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18890 let snapshot = self.snapshot(window, cx);
18891 let hunks = snapshot.hunks_for_ranges(
18892 self.selections
18893 .all(&snapshot.display_snapshot)
18894 .into_iter()
18895 .map(|selection| selection.range()),
18896 );
18897 let mut ranges_by_buffer = HashMap::default();
18898 self.transact(window, cx, |editor, _window, cx| {
18899 for hunk in hunks {
18900 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18901 ranges_by_buffer
18902 .entry(buffer.clone())
18903 .or_insert_with(Vec::new)
18904 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18905 }
18906 }
18907
18908 for (buffer, ranges) in ranges_by_buffer {
18909 buffer.update(cx, |buffer, cx| {
18910 buffer.merge_into_base(ranges, cx);
18911 });
18912 }
18913 });
18914
18915 if let Some(project) = self.project.clone() {
18916 self.save(
18917 SaveOptions {
18918 format: true,
18919 autosave: false,
18920 },
18921 project,
18922 window,
18923 cx,
18924 )
18925 .detach_and_log_err(cx);
18926 }
18927 }
18928
18929 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18930 if hovered != self.gutter_hovered {
18931 self.gutter_hovered = hovered;
18932 cx.notify();
18933 }
18934 }
18935
18936 pub fn insert_blocks(
18937 &mut self,
18938 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18939 autoscroll: Option<Autoscroll>,
18940 cx: &mut Context<Self>,
18941 ) -> Vec<CustomBlockId> {
18942 let blocks = self
18943 .display_map
18944 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18945 if let Some(autoscroll) = autoscroll {
18946 self.request_autoscroll(autoscroll, cx);
18947 }
18948 cx.notify();
18949 blocks
18950 }
18951
18952 pub fn resize_blocks(
18953 &mut self,
18954 heights: HashMap<CustomBlockId, u32>,
18955 autoscroll: Option<Autoscroll>,
18956 cx: &mut Context<Self>,
18957 ) {
18958 self.display_map
18959 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18960 if let Some(autoscroll) = autoscroll {
18961 self.request_autoscroll(autoscroll, cx);
18962 }
18963 cx.notify();
18964 }
18965
18966 pub fn replace_blocks(
18967 &mut self,
18968 renderers: HashMap<CustomBlockId, RenderBlock>,
18969 autoscroll: Option<Autoscroll>,
18970 cx: &mut Context<Self>,
18971 ) {
18972 self.display_map
18973 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18974 if let Some(autoscroll) = autoscroll {
18975 self.request_autoscroll(autoscroll, cx);
18976 }
18977 cx.notify();
18978 }
18979
18980 pub fn remove_blocks(
18981 &mut self,
18982 block_ids: HashSet<CustomBlockId>,
18983 autoscroll: Option<Autoscroll>,
18984 cx: &mut Context<Self>,
18985 ) {
18986 self.display_map.update(cx, |display_map, cx| {
18987 display_map.remove_blocks(block_ids, cx)
18988 });
18989 if let Some(autoscroll) = autoscroll {
18990 self.request_autoscroll(autoscroll, cx);
18991 }
18992 cx.notify();
18993 }
18994
18995 pub fn row_for_block(
18996 &self,
18997 block_id: CustomBlockId,
18998 cx: &mut Context<Self>,
18999 ) -> Option<DisplayRow> {
19000 self.display_map
19001 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19002 }
19003
19004 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19005 self.focused_block = Some(focused_block);
19006 }
19007
19008 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19009 self.focused_block.take()
19010 }
19011
19012 pub fn insert_creases(
19013 &mut self,
19014 creases: impl IntoIterator<Item = Crease<Anchor>>,
19015 cx: &mut Context<Self>,
19016 ) -> Vec<CreaseId> {
19017 self.display_map
19018 .update(cx, |map, cx| map.insert_creases(creases, cx))
19019 }
19020
19021 pub fn remove_creases(
19022 &mut self,
19023 ids: impl IntoIterator<Item = CreaseId>,
19024 cx: &mut Context<Self>,
19025 ) -> Vec<(CreaseId, Range<Anchor>)> {
19026 self.display_map
19027 .update(cx, |map, cx| map.remove_creases(ids, cx))
19028 }
19029
19030 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19031 self.display_map
19032 .update(cx, |map, cx| map.snapshot(cx))
19033 .longest_row()
19034 }
19035
19036 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19037 self.display_map
19038 .update(cx, |map, cx| map.snapshot(cx))
19039 .max_point()
19040 }
19041
19042 pub fn text(&self, cx: &App) -> String {
19043 self.buffer.read(cx).read(cx).text()
19044 }
19045
19046 pub fn is_empty(&self, cx: &App) -> bool {
19047 self.buffer.read(cx).read(cx).is_empty()
19048 }
19049
19050 pub fn text_option(&self, cx: &App) -> Option<String> {
19051 let text = self.text(cx);
19052 let text = text.trim();
19053
19054 if text.is_empty() {
19055 return None;
19056 }
19057
19058 Some(text.to_string())
19059 }
19060
19061 pub fn set_text(
19062 &mut self,
19063 text: impl Into<Arc<str>>,
19064 window: &mut Window,
19065 cx: &mut Context<Self>,
19066 ) {
19067 self.transact(window, cx, |this, _, cx| {
19068 this.buffer
19069 .read(cx)
19070 .as_singleton()
19071 .expect("you can only call set_text on editors for singleton buffers")
19072 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19073 });
19074 }
19075
19076 pub fn display_text(&self, cx: &mut App) -> String {
19077 self.display_map
19078 .update(cx, |map, cx| map.snapshot(cx))
19079 .text()
19080 }
19081
19082 fn create_minimap(
19083 &self,
19084 minimap_settings: MinimapSettings,
19085 window: &mut Window,
19086 cx: &mut Context<Self>,
19087 ) -> Option<Entity<Self>> {
19088 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19089 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19090 }
19091
19092 fn initialize_new_minimap(
19093 &self,
19094 minimap_settings: MinimapSettings,
19095 window: &mut Window,
19096 cx: &mut Context<Self>,
19097 ) -> Entity<Self> {
19098 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19099
19100 let mut minimap = Editor::new_internal(
19101 EditorMode::Minimap {
19102 parent: cx.weak_entity(),
19103 },
19104 self.buffer.clone(),
19105 None,
19106 Some(self.display_map.clone()),
19107 window,
19108 cx,
19109 );
19110 minimap.scroll_manager.clone_state(&self.scroll_manager);
19111 minimap.set_text_style_refinement(TextStyleRefinement {
19112 font_size: Some(MINIMAP_FONT_SIZE),
19113 font_weight: Some(MINIMAP_FONT_WEIGHT),
19114 ..Default::default()
19115 });
19116 minimap.update_minimap_configuration(minimap_settings, cx);
19117 cx.new(|_| minimap)
19118 }
19119
19120 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19121 let current_line_highlight = minimap_settings
19122 .current_line_highlight
19123 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19124 self.set_current_line_highlight(Some(current_line_highlight));
19125 }
19126
19127 pub fn minimap(&self) -> Option<&Entity<Self>> {
19128 self.minimap
19129 .as_ref()
19130 .filter(|_| self.minimap_visibility.visible())
19131 }
19132
19133 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19134 let mut wrap_guides = smallvec![];
19135
19136 if self.show_wrap_guides == Some(false) {
19137 return wrap_guides;
19138 }
19139
19140 let settings = self.buffer.read(cx).language_settings(cx);
19141 if settings.show_wrap_guides {
19142 match self.soft_wrap_mode(cx) {
19143 SoftWrap::Column(soft_wrap) => {
19144 wrap_guides.push((soft_wrap as usize, true));
19145 }
19146 SoftWrap::Bounded(soft_wrap) => {
19147 wrap_guides.push((soft_wrap as usize, true));
19148 }
19149 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19150 }
19151 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19152 }
19153
19154 wrap_guides
19155 }
19156
19157 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19158 let settings = self.buffer.read(cx).language_settings(cx);
19159 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19160 match mode {
19161 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19162 SoftWrap::None
19163 }
19164 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19165 language_settings::SoftWrap::PreferredLineLength => {
19166 SoftWrap::Column(settings.preferred_line_length)
19167 }
19168 language_settings::SoftWrap::Bounded => {
19169 SoftWrap::Bounded(settings.preferred_line_length)
19170 }
19171 }
19172 }
19173
19174 pub fn set_soft_wrap_mode(
19175 &mut self,
19176 mode: language_settings::SoftWrap,
19177
19178 cx: &mut Context<Self>,
19179 ) {
19180 self.soft_wrap_mode_override = Some(mode);
19181 cx.notify();
19182 }
19183
19184 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19185 self.hard_wrap = hard_wrap;
19186 cx.notify();
19187 }
19188
19189 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19190 self.text_style_refinement = Some(style);
19191 }
19192
19193 /// called by the Element so we know what style we were most recently rendered with.
19194 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19195 // We intentionally do not inform the display map about the minimap style
19196 // so that wrapping is not recalculated and stays consistent for the editor
19197 // and its linked minimap.
19198 if !self.mode.is_minimap() {
19199 let font = style.text.font();
19200 let font_size = style.text.font_size.to_pixels(window.rem_size());
19201 let display_map = self
19202 .placeholder_display_map
19203 .as_ref()
19204 .filter(|_| self.is_empty(cx))
19205 .unwrap_or(&self.display_map);
19206
19207 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19208 }
19209 self.style = Some(style);
19210 }
19211
19212 pub fn style(&self) -> Option<&EditorStyle> {
19213 self.style.as_ref()
19214 }
19215
19216 // Called by the element. This method is not designed to be called outside of the editor
19217 // element's layout code because it does not notify when rewrapping is computed synchronously.
19218 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19219 if self.is_empty(cx) {
19220 self.placeholder_display_map
19221 .as_ref()
19222 .map_or(false, |display_map| {
19223 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19224 })
19225 } else {
19226 self.display_map
19227 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19228 }
19229 }
19230
19231 pub fn set_soft_wrap(&mut self) {
19232 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19233 }
19234
19235 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19236 if self.soft_wrap_mode_override.is_some() {
19237 self.soft_wrap_mode_override.take();
19238 } else {
19239 let soft_wrap = match self.soft_wrap_mode(cx) {
19240 SoftWrap::GitDiff => return,
19241 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19242 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19243 language_settings::SoftWrap::None
19244 }
19245 };
19246 self.soft_wrap_mode_override = Some(soft_wrap);
19247 }
19248 cx.notify();
19249 }
19250
19251 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19252 let Some(workspace) = self.workspace() else {
19253 return;
19254 };
19255 let fs = workspace.read(cx).app_state().fs.clone();
19256 let current_show = TabBarSettings::get_global(cx).show;
19257 update_settings_file(fs, cx, move |setting, _| {
19258 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19259 });
19260 }
19261
19262 pub fn toggle_indent_guides(
19263 &mut self,
19264 _: &ToggleIndentGuides,
19265 _: &mut Window,
19266 cx: &mut Context<Self>,
19267 ) {
19268 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19269 self.buffer
19270 .read(cx)
19271 .language_settings(cx)
19272 .indent_guides
19273 .enabled
19274 });
19275 self.show_indent_guides = Some(!currently_enabled);
19276 cx.notify();
19277 }
19278
19279 fn should_show_indent_guides(&self) -> Option<bool> {
19280 self.show_indent_guides
19281 }
19282
19283 pub fn toggle_line_numbers(
19284 &mut self,
19285 _: &ToggleLineNumbers,
19286 _: &mut Window,
19287 cx: &mut Context<Self>,
19288 ) {
19289 let mut editor_settings = EditorSettings::get_global(cx).clone();
19290 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19291 EditorSettings::override_global(editor_settings, cx);
19292 }
19293
19294 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19295 if let Some(show_line_numbers) = self.show_line_numbers {
19296 return show_line_numbers;
19297 }
19298 EditorSettings::get_global(cx).gutter.line_numbers
19299 }
19300
19301 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19302 self.use_relative_line_numbers
19303 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19304 }
19305
19306 pub fn toggle_relative_line_numbers(
19307 &mut self,
19308 _: &ToggleRelativeLineNumbers,
19309 _: &mut Window,
19310 cx: &mut Context<Self>,
19311 ) {
19312 let is_relative = self.should_use_relative_line_numbers(cx);
19313 self.set_relative_line_number(Some(!is_relative), cx)
19314 }
19315
19316 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19317 self.use_relative_line_numbers = is_relative;
19318 cx.notify();
19319 }
19320
19321 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19322 self.show_gutter = show_gutter;
19323 cx.notify();
19324 }
19325
19326 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19327 self.show_scrollbars = ScrollbarAxes {
19328 horizontal: show,
19329 vertical: show,
19330 };
19331 cx.notify();
19332 }
19333
19334 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19335 self.show_scrollbars.vertical = show;
19336 cx.notify();
19337 }
19338
19339 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19340 self.show_scrollbars.horizontal = show;
19341 cx.notify();
19342 }
19343
19344 pub fn set_minimap_visibility(
19345 &mut self,
19346 minimap_visibility: MinimapVisibility,
19347 window: &mut Window,
19348 cx: &mut Context<Self>,
19349 ) {
19350 if self.minimap_visibility != minimap_visibility {
19351 if minimap_visibility.visible() && self.minimap.is_none() {
19352 let minimap_settings = EditorSettings::get_global(cx).minimap;
19353 self.minimap =
19354 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19355 }
19356 self.minimap_visibility = minimap_visibility;
19357 cx.notify();
19358 }
19359 }
19360
19361 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19362 self.set_show_scrollbars(false, cx);
19363 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19364 }
19365
19366 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19367 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19368 }
19369
19370 /// Normally the text in full mode and auto height editors is padded on the
19371 /// left side by roughly half a character width for improved hit testing.
19372 ///
19373 /// Use this method to disable this for cases where this is not wanted (e.g.
19374 /// if you want to align the editor text with some other text above or below)
19375 /// or if you want to add this padding to single-line editors.
19376 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19377 self.offset_content = offset_content;
19378 cx.notify();
19379 }
19380
19381 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19382 self.show_line_numbers = Some(show_line_numbers);
19383 cx.notify();
19384 }
19385
19386 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19387 self.disable_expand_excerpt_buttons = true;
19388 cx.notify();
19389 }
19390
19391 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19392 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19393 cx.notify();
19394 }
19395
19396 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19397 self.show_code_actions = Some(show_code_actions);
19398 cx.notify();
19399 }
19400
19401 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19402 self.show_runnables = Some(show_runnables);
19403 cx.notify();
19404 }
19405
19406 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19407 self.show_breakpoints = Some(show_breakpoints);
19408 cx.notify();
19409 }
19410
19411 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19412 if self.display_map.read(cx).masked != masked {
19413 self.display_map.update(cx, |map, _| map.masked = masked);
19414 }
19415 cx.notify()
19416 }
19417
19418 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19419 self.show_wrap_guides = Some(show_wrap_guides);
19420 cx.notify();
19421 }
19422
19423 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19424 self.show_indent_guides = Some(show_indent_guides);
19425 cx.notify();
19426 }
19427
19428 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19429 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19430 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19431 && let Some(dir) = file.abs_path(cx).parent()
19432 {
19433 return Some(dir.to_owned());
19434 }
19435 }
19436
19437 None
19438 }
19439
19440 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19441 self.active_excerpt(cx)?
19442 .1
19443 .read(cx)
19444 .file()
19445 .and_then(|f| f.as_local())
19446 }
19447
19448 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19449 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19450 let buffer = buffer.read(cx);
19451 if let Some(project_path) = buffer.project_path(cx) {
19452 let project = self.project()?.read(cx);
19453 project.absolute_path(&project_path, cx)
19454 } else {
19455 buffer
19456 .file()
19457 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19458 }
19459 })
19460 }
19461
19462 pub fn reveal_in_finder(
19463 &mut self,
19464 _: &RevealInFileManager,
19465 _window: &mut Window,
19466 cx: &mut Context<Self>,
19467 ) {
19468 if let Some(target) = self.target_file(cx) {
19469 cx.reveal_path(&target.abs_path(cx));
19470 }
19471 }
19472
19473 pub fn copy_path(
19474 &mut self,
19475 _: &zed_actions::workspace::CopyPath,
19476 _window: &mut Window,
19477 cx: &mut Context<Self>,
19478 ) {
19479 if let Some(path) = self.target_file_abs_path(cx)
19480 && let Some(path) = path.to_str()
19481 {
19482 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19483 } else {
19484 cx.propagate();
19485 }
19486 }
19487
19488 pub fn copy_relative_path(
19489 &mut self,
19490 _: &zed_actions::workspace::CopyRelativePath,
19491 _window: &mut Window,
19492 cx: &mut Context<Self>,
19493 ) {
19494 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19495 let project = self.project()?.read(cx);
19496 let path = buffer.read(cx).file()?.path();
19497 let path = path.display(project.path_style(cx));
19498 Some(path)
19499 }) {
19500 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19501 } else {
19502 cx.propagate();
19503 }
19504 }
19505
19506 /// Returns the project path for the editor's buffer, if any buffer is
19507 /// opened in the editor.
19508 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19509 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19510 buffer.read(cx).project_path(cx)
19511 } else {
19512 None
19513 }
19514 }
19515
19516 // Returns true if the editor handled a go-to-line request
19517 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19518 maybe!({
19519 let breakpoint_store = self.breakpoint_store.as_ref()?;
19520
19521 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19522 else {
19523 self.clear_row_highlights::<ActiveDebugLine>();
19524 return None;
19525 };
19526
19527 let position = active_stack_frame.position;
19528 let buffer_id = position.buffer_id?;
19529 let snapshot = self
19530 .project
19531 .as_ref()?
19532 .read(cx)
19533 .buffer_for_id(buffer_id, cx)?
19534 .read(cx)
19535 .snapshot();
19536
19537 let mut handled = false;
19538 for (id, ExcerptRange { context, .. }) in
19539 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19540 {
19541 if context.start.cmp(&position, &snapshot).is_ge()
19542 || context.end.cmp(&position, &snapshot).is_lt()
19543 {
19544 continue;
19545 }
19546 let snapshot = self.buffer.read(cx).snapshot(cx);
19547 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19548
19549 handled = true;
19550 self.clear_row_highlights::<ActiveDebugLine>();
19551
19552 self.go_to_line::<ActiveDebugLine>(
19553 multibuffer_anchor,
19554 Some(cx.theme().colors().editor_debugger_active_line_background),
19555 window,
19556 cx,
19557 );
19558
19559 cx.notify();
19560 }
19561
19562 handled.then_some(())
19563 })
19564 .is_some()
19565 }
19566
19567 pub fn copy_file_name_without_extension(
19568 &mut self,
19569 _: &CopyFileNameWithoutExtension,
19570 _: &mut Window,
19571 cx: &mut Context<Self>,
19572 ) {
19573 if let Some(file) = self.target_file(cx)
19574 && let Some(file_stem) = file.path().file_stem()
19575 {
19576 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19577 }
19578 }
19579
19580 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19581 if let Some(file) = self.target_file(cx)
19582 && let Some(name) = file.path().file_name()
19583 {
19584 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19585 }
19586 }
19587
19588 pub fn toggle_git_blame(
19589 &mut self,
19590 _: &::git::Blame,
19591 window: &mut Window,
19592 cx: &mut Context<Self>,
19593 ) {
19594 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19595
19596 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19597 self.start_git_blame(true, window, cx);
19598 }
19599
19600 cx.notify();
19601 }
19602
19603 pub fn toggle_git_blame_inline(
19604 &mut self,
19605 _: &ToggleGitBlameInline,
19606 window: &mut Window,
19607 cx: &mut Context<Self>,
19608 ) {
19609 self.toggle_git_blame_inline_internal(true, window, cx);
19610 cx.notify();
19611 }
19612
19613 pub fn open_git_blame_commit(
19614 &mut self,
19615 _: &OpenGitBlameCommit,
19616 window: &mut Window,
19617 cx: &mut Context<Self>,
19618 ) {
19619 self.open_git_blame_commit_internal(window, cx);
19620 }
19621
19622 fn open_git_blame_commit_internal(
19623 &mut self,
19624 window: &mut Window,
19625 cx: &mut Context<Self>,
19626 ) -> Option<()> {
19627 let blame = self.blame.as_ref()?;
19628 let snapshot = self.snapshot(window, cx);
19629 let cursor = self
19630 .selections
19631 .newest::<Point>(&snapshot.display_snapshot)
19632 .head();
19633 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19634 let (_, blame_entry) = blame
19635 .update(cx, |blame, cx| {
19636 blame
19637 .blame_for_rows(
19638 &[RowInfo {
19639 buffer_id: Some(buffer.remote_id()),
19640 buffer_row: Some(point.row),
19641 ..Default::default()
19642 }],
19643 cx,
19644 )
19645 .next()
19646 })
19647 .flatten()?;
19648 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19649 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19650 let workspace = self.workspace()?.downgrade();
19651 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19652 None
19653 }
19654
19655 pub fn git_blame_inline_enabled(&self) -> bool {
19656 self.git_blame_inline_enabled
19657 }
19658
19659 pub fn toggle_selection_menu(
19660 &mut self,
19661 _: &ToggleSelectionMenu,
19662 _: &mut Window,
19663 cx: &mut Context<Self>,
19664 ) {
19665 self.show_selection_menu = self
19666 .show_selection_menu
19667 .map(|show_selections_menu| !show_selections_menu)
19668 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19669
19670 cx.notify();
19671 }
19672
19673 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19674 self.show_selection_menu
19675 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19676 }
19677
19678 fn start_git_blame(
19679 &mut self,
19680 user_triggered: bool,
19681 window: &mut Window,
19682 cx: &mut Context<Self>,
19683 ) {
19684 if let Some(project) = self.project() {
19685 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19686 && buffer.read(cx).file().is_none()
19687 {
19688 return;
19689 }
19690
19691 let focused = self.focus_handle(cx).contains_focused(window, cx);
19692
19693 let project = project.clone();
19694 let blame = cx
19695 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19696 self.blame_subscription =
19697 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19698 self.blame = Some(blame);
19699 }
19700 }
19701
19702 fn toggle_git_blame_inline_internal(
19703 &mut self,
19704 user_triggered: bool,
19705 window: &mut Window,
19706 cx: &mut Context<Self>,
19707 ) {
19708 if self.git_blame_inline_enabled {
19709 self.git_blame_inline_enabled = false;
19710 self.show_git_blame_inline = false;
19711 self.show_git_blame_inline_delay_task.take();
19712 } else {
19713 self.git_blame_inline_enabled = true;
19714 self.start_git_blame_inline(user_triggered, window, cx);
19715 }
19716
19717 cx.notify();
19718 }
19719
19720 fn start_git_blame_inline(
19721 &mut self,
19722 user_triggered: bool,
19723 window: &mut Window,
19724 cx: &mut Context<Self>,
19725 ) {
19726 self.start_git_blame(user_triggered, window, cx);
19727
19728 if ProjectSettings::get_global(cx)
19729 .git
19730 .inline_blame_delay()
19731 .is_some()
19732 {
19733 self.start_inline_blame_timer(window, cx);
19734 } else {
19735 self.show_git_blame_inline = true
19736 }
19737 }
19738
19739 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19740 self.blame.as_ref()
19741 }
19742
19743 pub fn show_git_blame_gutter(&self) -> bool {
19744 self.show_git_blame_gutter
19745 }
19746
19747 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19748 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19749 }
19750
19751 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19752 self.show_git_blame_inline
19753 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19754 && !self.newest_selection_head_on_empty_line(cx)
19755 && self.has_blame_entries(cx)
19756 }
19757
19758 fn has_blame_entries(&self, cx: &App) -> bool {
19759 self.blame()
19760 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19761 }
19762
19763 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19764 let cursor_anchor = self.selections.newest_anchor().head();
19765
19766 let snapshot = self.buffer.read(cx).snapshot(cx);
19767 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19768
19769 snapshot.line_len(buffer_row) == 0
19770 }
19771
19772 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19773 let buffer_and_selection = maybe!({
19774 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19775 let selection_range = selection.range();
19776
19777 let multi_buffer = self.buffer().read(cx);
19778 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19779 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19780
19781 let (buffer, range, _) = if selection.reversed {
19782 buffer_ranges.first()
19783 } else {
19784 buffer_ranges.last()
19785 }?;
19786
19787 let selection = text::ToPoint::to_point(&range.start, buffer).row
19788 ..text::ToPoint::to_point(&range.end, buffer).row;
19789 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19790 });
19791
19792 let Some((buffer, selection)) = buffer_and_selection else {
19793 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19794 };
19795
19796 let Some(project) = self.project() else {
19797 return Task::ready(Err(anyhow!("editor does not have project")));
19798 };
19799
19800 project.update(cx, |project, cx| {
19801 project.get_permalink_to_line(&buffer, selection, cx)
19802 })
19803 }
19804
19805 pub fn copy_permalink_to_line(
19806 &mut self,
19807 _: &CopyPermalinkToLine,
19808 window: &mut Window,
19809 cx: &mut Context<Self>,
19810 ) {
19811 let permalink_task = self.get_permalink_to_line(cx);
19812 let workspace = self.workspace();
19813
19814 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19815 Ok(permalink) => {
19816 cx.update(|_, cx| {
19817 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19818 })
19819 .ok();
19820 }
19821 Err(err) => {
19822 let message = format!("Failed to copy permalink: {err}");
19823
19824 anyhow::Result::<()>::Err(err).log_err();
19825
19826 if let Some(workspace) = workspace {
19827 workspace
19828 .update_in(cx, |workspace, _, cx| {
19829 struct CopyPermalinkToLine;
19830
19831 workspace.show_toast(
19832 Toast::new(
19833 NotificationId::unique::<CopyPermalinkToLine>(),
19834 message,
19835 ),
19836 cx,
19837 )
19838 })
19839 .ok();
19840 }
19841 }
19842 })
19843 .detach();
19844 }
19845
19846 pub fn copy_file_location(
19847 &mut self,
19848 _: &CopyFileLocation,
19849 _: &mut Window,
19850 cx: &mut Context<Self>,
19851 ) {
19852 let selection = self
19853 .selections
19854 .newest::<Point>(&self.display_snapshot(cx))
19855 .start
19856 .row
19857 + 1;
19858 if let Some(file) = self.target_file(cx) {
19859 let path = file.path().display(file.path_style(cx));
19860 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19861 }
19862 }
19863
19864 pub fn open_permalink_to_line(
19865 &mut self,
19866 _: &OpenPermalinkToLine,
19867 window: &mut Window,
19868 cx: &mut Context<Self>,
19869 ) {
19870 let permalink_task = self.get_permalink_to_line(cx);
19871 let workspace = self.workspace();
19872
19873 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19874 Ok(permalink) => {
19875 cx.update(|_, cx| {
19876 cx.open_url(permalink.as_ref());
19877 })
19878 .ok();
19879 }
19880 Err(err) => {
19881 let message = format!("Failed to open permalink: {err}");
19882
19883 anyhow::Result::<()>::Err(err).log_err();
19884
19885 if let Some(workspace) = workspace {
19886 workspace
19887 .update(cx, |workspace, cx| {
19888 struct OpenPermalinkToLine;
19889
19890 workspace.show_toast(
19891 Toast::new(
19892 NotificationId::unique::<OpenPermalinkToLine>(),
19893 message,
19894 ),
19895 cx,
19896 )
19897 })
19898 .ok();
19899 }
19900 }
19901 })
19902 .detach();
19903 }
19904
19905 pub fn insert_uuid_v4(
19906 &mut self,
19907 _: &InsertUuidV4,
19908 window: &mut Window,
19909 cx: &mut Context<Self>,
19910 ) {
19911 self.insert_uuid(UuidVersion::V4, window, cx);
19912 }
19913
19914 pub fn insert_uuid_v7(
19915 &mut self,
19916 _: &InsertUuidV7,
19917 window: &mut Window,
19918 cx: &mut Context<Self>,
19919 ) {
19920 self.insert_uuid(UuidVersion::V7, window, cx);
19921 }
19922
19923 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19925 self.transact(window, cx, |this, window, cx| {
19926 let edits = this
19927 .selections
19928 .all::<Point>(&this.display_snapshot(cx))
19929 .into_iter()
19930 .map(|selection| {
19931 let uuid = match version {
19932 UuidVersion::V4 => uuid::Uuid::new_v4(),
19933 UuidVersion::V7 => uuid::Uuid::now_v7(),
19934 };
19935
19936 (selection.range(), uuid.to_string())
19937 });
19938 this.edit(edits, cx);
19939 this.refresh_edit_prediction(true, false, window, cx);
19940 });
19941 }
19942
19943 pub fn open_selections_in_multibuffer(
19944 &mut self,
19945 _: &OpenSelectionsInMultibuffer,
19946 window: &mut Window,
19947 cx: &mut Context<Self>,
19948 ) {
19949 let multibuffer = self.buffer.read(cx);
19950
19951 let Some(buffer) = multibuffer.as_singleton() else {
19952 return;
19953 };
19954
19955 let Some(workspace) = self.workspace() else {
19956 return;
19957 };
19958
19959 let title = multibuffer.title(cx).to_string();
19960
19961 let locations = self
19962 .selections
19963 .all_anchors(cx)
19964 .iter()
19965 .map(|selection| {
19966 (
19967 buffer.clone(),
19968 (selection.start.text_anchor..selection.end.text_anchor)
19969 .to_point(buffer.read(cx)),
19970 )
19971 })
19972 .into_group_map();
19973
19974 cx.spawn_in(window, async move |_, cx| {
19975 workspace.update_in(cx, |workspace, window, cx| {
19976 Self::open_locations_in_multibuffer(
19977 workspace,
19978 locations,
19979 format!("Selections for '{title}'"),
19980 false,
19981 MultibufferSelectionMode::All,
19982 window,
19983 cx,
19984 );
19985 })
19986 })
19987 .detach();
19988 }
19989
19990 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19991 /// last highlight added will be used.
19992 ///
19993 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19994 pub fn highlight_rows<T: 'static>(
19995 &mut self,
19996 range: Range<Anchor>,
19997 color: Hsla,
19998 options: RowHighlightOptions,
19999 cx: &mut Context<Self>,
20000 ) {
20001 let snapshot = self.buffer().read(cx).snapshot(cx);
20002 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20003 let ix = row_highlights.binary_search_by(|highlight| {
20004 Ordering::Equal
20005 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20006 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20007 });
20008
20009 if let Err(mut ix) = ix {
20010 let index = post_inc(&mut self.highlight_order);
20011
20012 // If this range intersects with the preceding highlight, then merge it with
20013 // the preceding highlight. Otherwise insert a new highlight.
20014 let mut merged = false;
20015 if ix > 0 {
20016 let prev_highlight = &mut row_highlights[ix - 1];
20017 if prev_highlight
20018 .range
20019 .end
20020 .cmp(&range.start, &snapshot)
20021 .is_ge()
20022 {
20023 ix -= 1;
20024 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20025 prev_highlight.range.end = range.end;
20026 }
20027 merged = true;
20028 prev_highlight.index = index;
20029 prev_highlight.color = color;
20030 prev_highlight.options = options;
20031 }
20032 }
20033
20034 if !merged {
20035 row_highlights.insert(
20036 ix,
20037 RowHighlight {
20038 range,
20039 index,
20040 color,
20041 options,
20042 type_id: TypeId::of::<T>(),
20043 },
20044 );
20045 }
20046
20047 // If any of the following highlights intersect with this one, merge them.
20048 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20049 let highlight = &row_highlights[ix];
20050 if next_highlight
20051 .range
20052 .start
20053 .cmp(&highlight.range.end, &snapshot)
20054 .is_le()
20055 {
20056 if next_highlight
20057 .range
20058 .end
20059 .cmp(&highlight.range.end, &snapshot)
20060 .is_gt()
20061 {
20062 row_highlights[ix].range.end = next_highlight.range.end;
20063 }
20064 row_highlights.remove(ix + 1);
20065 } else {
20066 break;
20067 }
20068 }
20069 }
20070 }
20071
20072 /// Remove any highlighted row ranges of the given type that intersect the
20073 /// given ranges.
20074 pub fn remove_highlighted_rows<T: 'static>(
20075 &mut self,
20076 ranges_to_remove: Vec<Range<Anchor>>,
20077 cx: &mut Context<Self>,
20078 ) {
20079 let snapshot = self.buffer().read(cx).snapshot(cx);
20080 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20081 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20082 row_highlights.retain(|highlight| {
20083 while let Some(range_to_remove) = ranges_to_remove.peek() {
20084 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20085 Ordering::Less | Ordering::Equal => {
20086 ranges_to_remove.next();
20087 }
20088 Ordering::Greater => {
20089 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20090 Ordering::Less | Ordering::Equal => {
20091 return false;
20092 }
20093 Ordering::Greater => break,
20094 }
20095 }
20096 }
20097 }
20098
20099 true
20100 })
20101 }
20102
20103 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20104 pub fn clear_row_highlights<T: 'static>(&mut self) {
20105 self.highlighted_rows.remove(&TypeId::of::<T>());
20106 }
20107
20108 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20109 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20110 self.highlighted_rows
20111 .get(&TypeId::of::<T>())
20112 .map_or(&[] as &[_], |vec| vec.as_slice())
20113 .iter()
20114 .map(|highlight| (highlight.range.clone(), highlight.color))
20115 }
20116
20117 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20118 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20119 /// Allows to ignore certain kinds of highlights.
20120 pub fn highlighted_display_rows(
20121 &self,
20122 window: &mut Window,
20123 cx: &mut App,
20124 ) -> BTreeMap<DisplayRow, LineHighlight> {
20125 let snapshot = self.snapshot(window, cx);
20126 let mut used_highlight_orders = HashMap::default();
20127 self.highlighted_rows
20128 .iter()
20129 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20130 .fold(
20131 BTreeMap::<DisplayRow, LineHighlight>::new(),
20132 |mut unique_rows, highlight| {
20133 let start = highlight.range.start.to_display_point(&snapshot);
20134 let end = highlight.range.end.to_display_point(&snapshot);
20135 let start_row = start.row().0;
20136 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20137 && end.column() == 0
20138 {
20139 end.row().0.saturating_sub(1)
20140 } else {
20141 end.row().0
20142 };
20143 for row in start_row..=end_row {
20144 let used_index =
20145 used_highlight_orders.entry(row).or_insert(highlight.index);
20146 if highlight.index >= *used_index {
20147 *used_index = highlight.index;
20148 unique_rows.insert(
20149 DisplayRow(row),
20150 LineHighlight {
20151 include_gutter: highlight.options.include_gutter,
20152 border: None,
20153 background: highlight.color.into(),
20154 type_id: Some(highlight.type_id),
20155 },
20156 );
20157 }
20158 }
20159 unique_rows
20160 },
20161 )
20162 }
20163
20164 pub fn highlighted_display_row_for_autoscroll(
20165 &self,
20166 snapshot: &DisplaySnapshot,
20167 ) -> Option<DisplayRow> {
20168 self.highlighted_rows
20169 .values()
20170 .flat_map(|highlighted_rows| highlighted_rows.iter())
20171 .filter_map(|highlight| {
20172 if highlight.options.autoscroll {
20173 Some(highlight.range.start.to_display_point(snapshot).row())
20174 } else {
20175 None
20176 }
20177 })
20178 .min()
20179 }
20180
20181 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20182 self.highlight_background::<SearchWithinRange>(
20183 ranges,
20184 |colors| colors.colors().editor_document_highlight_read_background,
20185 cx,
20186 )
20187 }
20188
20189 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20190 self.breadcrumb_header = Some(new_header);
20191 }
20192
20193 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20194 self.clear_background_highlights::<SearchWithinRange>(cx);
20195 }
20196
20197 pub fn highlight_background<T: 'static>(
20198 &mut self,
20199 ranges: &[Range<Anchor>],
20200 color_fetcher: fn(&Theme) -> Hsla,
20201 cx: &mut Context<Self>,
20202 ) {
20203 self.background_highlights.insert(
20204 HighlightKey::Type(TypeId::of::<T>()),
20205 (color_fetcher, Arc::from(ranges)),
20206 );
20207 self.scrollbar_marker_state.dirty = true;
20208 cx.notify();
20209 }
20210
20211 pub fn highlight_background_key<T: 'static>(
20212 &mut self,
20213 key: usize,
20214 ranges: &[Range<Anchor>],
20215 color_fetcher: fn(&Theme) -> Hsla,
20216 cx: &mut Context<Self>,
20217 ) {
20218 self.background_highlights.insert(
20219 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20220 (color_fetcher, Arc::from(ranges)),
20221 );
20222 self.scrollbar_marker_state.dirty = true;
20223 cx.notify();
20224 }
20225
20226 pub fn clear_background_highlights<T: 'static>(
20227 &mut self,
20228 cx: &mut Context<Self>,
20229 ) -> Option<BackgroundHighlight> {
20230 let text_highlights = self
20231 .background_highlights
20232 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20233 if !text_highlights.1.is_empty() {
20234 self.scrollbar_marker_state.dirty = true;
20235 cx.notify();
20236 }
20237 Some(text_highlights)
20238 }
20239
20240 pub fn highlight_gutter<T: 'static>(
20241 &mut self,
20242 ranges: impl Into<Vec<Range<Anchor>>>,
20243 color_fetcher: fn(&App) -> Hsla,
20244 cx: &mut Context<Self>,
20245 ) {
20246 self.gutter_highlights
20247 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20248 cx.notify();
20249 }
20250
20251 pub fn clear_gutter_highlights<T: 'static>(
20252 &mut self,
20253 cx: &mut Context<Self>,
20254 ) -> Option<GutterHighlight> {
20255 cx.notify();
20256 self.gutter_highlights.remove(&TypeId::of::<T>())
20257 }
20258
20259 pub fn insert_gutter_highlight<T: 'static>(
20260 &mut self,
20261 range: Range<Anchor>,
20262 color_fetcher: fn(&App) -> Hsla,
20263 cx: &mut Context<Self>,
20264 ) {
20265 let snapshot = self.buffer().read(cx).snapshot(cx);
20266 let mut highlights = self
20267 .gutter_highlights
20268 .remove(&TypeId::of::<T>())
20269 .map(|(_, highlights)| highlights)
20270 .unwrap_or_default();
20271 let ix = highlights.binary_search_by(|highlight| {
20272 Ordering::Equal
20273 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20274 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20275 });
20276 if let Err(ix) = ix {
20277 highlights.insert(ix, range);
20278 }
20279 self.gutter_highlights
20280 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20281 }
20282
20283 pub fn remove_gutter_highlights<T: 'static>(
20284 &mut self,
20285 ranges_to_remove: Vec<Range<Anchor>>,
20286 cx: &mut Context<Self>,
20287 ) {
20288 let snapshot = self.buffer().read(cx).snapshot(cx);
20289 let Some((color_fetcher, mut gutter_highlights)) =
20290 self.gutter_highlights.remove(&TypeId::of::<T>())
20291 else {
20292 return;
20293 };
20294 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20295 gutter_highlights.retain(|highlight| {
20296 while let Some(range_to_remove) = ranges_to_remove.peek() {
20297 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20298 Ordering::Less | Ordering::Equal => {
20299 ranges_to_remove.next();
20300 }
20301 Ordering::Greater => {
20302 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20303 Ordering::Less | Ordering::Equal => {
20304 return false;
20305 }
20306 Ordering::Greater => break,
20307 }
20308 }
20309 }
20310 }
20311
20312 true
20313 });
20314 self.gutter_highlights
20315 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20316 }
20317
20318 #[cfg(feature = "test-support")]
20319 pub fn all_text_highlights(
20320 &self,
20321 window: &mut Window,
20322 cx: &mut Context<Self>,
20323 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20324 let snapshot = self.snapshot(window, cx);
20325 self.display_map.update(cx, |display_map, _| {
20326 display_map
20327 .all_text_highlights()
20328 .map(|highlight| {
20329 let (style, ranges) = highlight.as_ref();
20330 (
20331 *style,
20332 ranges
20333 .iter()
20334 .map(|range| range.clone().to_display_points(&snapshot))
20335 .collect(),
20336 )
20337 })
20338 .collect()
20339 })
20340 }
20341
20342 #[cfg(feature = "test-support")]
20343 pub fn all_text_background_highlights(
20344 &self,
20345 window: &mut Window,
20346 cx: &mut Context<Self>,
20347 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20348 let snapshot = self.snapshot(window, cx);
20349 let buffer = &snapshot.buffer_snapshot();
20350 let start = buffer.anchor_before(0);
20351 let end = buffer.anchor_after(buffer.len());
20352 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20353 }
20354
20355 #[cfg(any(test, feature = "test-support"))]
20356 pub fn sorted_background_highlights_in_range(
20357 &self,
20358 search_range: Range<Anchor>,
20359 display_snapshot: &DisplaySnapshot,
20360 theme: &Theme,
20361 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20362 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20363 res.sort_by(|a, b| {
20364 a.0.start
20365 .cmp(&b.0.start)
20366 .then_with(|| a.0.end.cmp(&b.0.end))
20367 .then_with(|| a.1.cmp(&b.1))
20368 });
20369 res
20370 }
20371
20372 #[cfg(feature = "test-support")]
20373 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20374 let snapshot = self.buffer().read(cx).snapshot(cx);
20375
20376 let highlights = self
20377 .background_highlights
20378 .get(&HighlightKey::Type(TypeId::of::<
20379 items::BufferSearchHighlights,
20380 >()));
20381
20382 if let Some((_color, ranges)) = highlights {
20383 ranges
20384 .iter()
20385 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20386 .collect_vec()
20387 } else {
20388 vec![]
20389 }
20390 }
20391
20392 fn document_highlights_for_position<'a>(
20393 &'a self,
20394 position: Anchor,
20395 buffer: &'a MultiBufferSnapshot,
20396 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20397 let read_highlights = self
20398 .background_highlights
20399 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20400 .map(|h| &h.1);
20401 let write_highlights = self
20402 .background_highlights
20403 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20404 .map(|h| &h.1);
20405 let left_position = position.bias_left(buffer);
20406 let right_position = position.bias_right(buffer);
20407 read_highlights
20408 .into_iter()
20409 .chain(write_highlights)
20410 .flat_map(move |ranges| {
20411 let start_ix = match ranges.binary_search_by(|probe| {
20412 let cmp = probe.end.cmp(&left_position, buffer);
20413 if cmp.is_ge() {
20414 Ordering::Greater
20415 } else {
20416 Ordering::Less
20417 }
20418 }) {
20419 Ok(i) | Err(i) => i,
20420 };
20421
20422 ranges[start_ix..]
20423 .iter()
20424 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20425 })
20426 }
20427
20428 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20429 self.background_highlights
20430 .get(&HighlightKey::Type(TypeId::of::<T>()))
20431 .is_some_and(|(_, highlights)| !highlights.is_empty())
20432 }
20433
20434 /// Returns all background highlights for a given range.
20435 ///
20436 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20437 pub fn background_highlights_in_range(
20438 &self,
20439 search_range: Range<Anchor>,
20440 display_snapshot: &DisplaySnapshot,
20441 theme: &Theme,
20442 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20443 let mut results = Vec::new();
20444 for (color_fetcher, ranges) in self.background_highlights.values() {
20445 let color = color_fetcher(theme);
20446 let start_ix = match ranges.binary_search_by(|probe| {
20447 let cmp = probe
20448 .end
20449 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20450 if cmp.is_gt() {
20451 Ordering::Greater
20452 } else {
20453 Ordering::Less
20454 }
20455 }) {
20456 Ok(i) | Err(i) => i,
20457 };
20458 for range in &ranges[start_ix..] {
20459 if range
20460 .start
20461 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20462 .is_ge()
20463 {
20464 break;
20465 }
20466
20467 let start = range.start.to_display_point(display_snapshot);
20468 let end = range.end.to_display_point(display_snapshot);
20469 results.push((start..end, color))
20470 }
20471 }
20472 results
20473 }
20474
20475 pub fn gutter_highlights_in_range(
20476 &self,
20477 search_range: Range<Anchor>,
20478 display_snapshot: &DisplaySnapshot,
20479 cx: &App,
20480 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20481 let mut results = Vec::new();
20482 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20483 let color = color_fetcher(cx);
20484 let start_ix = match ranges.binary_search_by(|probe| {
20485 let cmp = probe
20486 .end
20487 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20488 if cmp.is_gt() {
20489 Ordering::Greater
20490 } else {
20491 Ordering::Less
20492 }
20493 }) {
20494 Ok(i) | Err(i) => i,
20495 };
20496 for range in &ranges[start_ix..] {
20497 if range
20498 .start
20499 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20500 .is_ge()
20501 {
20502 break;
20503 }
20504
20505 let start = range.start.to_display_point(display_snapshot);
20506 let end = range.end.to_display_point(display_snapshot);
20507 results.push((start..end, color))
20508 }
20509 }
20510 results
20511 }
20512
20513 /// Get the text ranges corresponding to the redaction query
20514 pub fn redacted_ranges(
20515 &self,
20516 search_range: Range<Anchor>,
20517 display_snapshot: &DisplaySnapshot,
20518 cx: &App,
20519 ) -> Vec<Range<DisplayPoint>> {
20520 display_snapshot
20521 .buffer_snapshot()
20522 .redacted_ranges(search_range, |file| {
20523 if let Some(file) = file {
20524 file.is_private()
20525 && EditorSettings::get(
20526 Some(SettingsLocation {
20527 worktree_id: file.worktree_id(cx),
20528 path: file.path().as_ref(),
20529 }),
20530 cx,
20531 )
20532 .redact_private_values
20533 } else {
20534 false
20535 }
20536 })
20537 .map(|range| {
20538 range.start.to_display_point(display_snapshot)
20539 ..range.end.to_display_point(display_snapshot)
20540 })
20541 .collect()
20542 }
20543
20544 pub fn highlight_text_key<T: 'static>(
20545 &mut self,
20546 key: usize,
20547 ranges: Vec<Range<Anchor>>,
20548 style: HighlightStyle,
20549 cx: &mut Context<Self>,
20550 ) {
20551 self.display_map.update(cx, |map, _| {
20552 map.highlight_text(
20553 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20554 ranges,
20555 style,
20556 );
20557 });
20558 cx.notify();
20559 }
20560
20561 pub fn highlight_text<T: 'static>(
20562 &mut self,
20563 ranges: Vec<Range<Anchor>>,
20564 style: HighlightStyle,
20565 cx: &mut Context<Self>,
20566 ) {
20567 self.display_map.update(cx, |map, _| {
20568 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20569 });
20570 cx.notify();
20571 }
20572
20573 pub fn text_highlights<'a, T: 'static>(
20574 &'a self,
20575 cx: &'a App,
20576 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20577 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20578 }
20579
20580 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20581 let cleared = self
20582 .display_map
20583 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20584 if cleared {
20585 cx.notify();
20586 }
20587 }
20588
20589 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20590 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20591 && self.focus_handle.is_focused(window)
20592 }
20593
20594 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20595 self.show_cursor_when_unfocused = is_enabled;
20596 cx.notify();
20597 }
20598
20599 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20600 cx.notify();
20601 }
20602
20603 fn on_debug_session_event(
20604 &mut self,
20605 _session: Entity<Session>,
20606 event: &SessionEvent,
20607 cx: &mut Context<Self>,
20608 ) {
20609 if let SessionEvent::InvalidateInlineValue = event {
20610 self.refresh_inline_values(cx);
20611 }
20612 }
20613
20614 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20615 let Some(project) = self.project.clone() else {
20616 return;
20617 };
20618
20619 if !self.inline_value_cache.enabled {
20620 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20621 self.splice_inlays(&inlays, Vec::new(), cx);
20622 return;
20623 }
20624
20625 let current_execution_position = self
20626 .highlighted_rows
20627 .get(&TypeId::of::<ActiveDebugLine>())
20628 .and_then(|lines| lines.last().map(|line| line.range.end));
20629
20630 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20631 let inline_values = editor
20632 .update(cx, |editor, cx| {
20633 let Some(current_execution_position) = current_execution_position else {
20634 return Some(Task::ready(Ok(Vec::new())));
20635 };
20636
20637 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20638 let snapshot = buffer.snapshot(cx);
20639
20640 let excerpt = snapshot.excerpt_containing(
20641 current_execution_position..current_execution_position,
20642 )?;
20643
20644 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20645 })?;
20646
20647 let range =
20648 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20649
20650 project.inline_values(buffer, range, cx)
20651 })
20652 .ok()
20653 .flatten()?
20654 .await
20655 .context("refreshing debugger inlays")
20656 .log_err()?;
20657
20658 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20659
20660 for (buffer_id, inline_value) in inline_values
20661 .into_iter()
20662 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20663 {
20664 buffer_inline_values
20665 .entry(buffer_id)
20666 .or_default()
20667 .push(inline_value);
20668 }
20669
20670 editor
20671 .update(cx, |editor, cx| {
20672 let snapshot = editor.buffer.read(cx).snapshot(cx);
20673 let mut new_inlays = Vec::default();
20674
20675 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20676 let buffer_id = buffer_snapshot.remote_id();
20677 buffer_inline_values
20678 .get(&buffer_id)
20679 .into_iter()
20680 .flatten()
20681 .for_each(|hint| {
20682 let inlay = Inlay::debugger(
20683 post_inc(&mut editor.next_inlay_id),
20684 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20685 hint.text(),
20686 );
20687 if !inlay.text().chars().contains(&'\n') {
20688 new_inlays.push(inlay);
20689 }
20690 });
20691 }
20692
20693 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20694 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20695
20696 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20697 })
20698 .ok()?;
20699 Some(())
20700 });
20701 }
20702
20703 fn on_buffer_event(
20704 &mut self,
20705 multibuffer: &Entity<MultiBuffer>,
20706 event: &multi_buffer::Event,
20707 window: &mut Window,
20708 cx: &mut Context<Self>,
20709 ) {
20710 match event {
20711 multi_buffer::Event::Edited { edited_buffer } => {
20712 self.scrollbar_marker_state.dirty = true;
20713 self.active_indent_guides_state.dirty = true;
20714 self.refresh_active_diagnostics(cx);
20715 self.refresh_code_actions(window, cx);
20716 self.refresh_selected_text_highlights(true, window, cx);
20717 self.refresh_single_line_folds(window, cx);
20718 refresh_matching_bracket_highlights(self, cx);
20719 if self.has_active_edit_prediction() {
20720 self.update_visible_edit_prediction(window, cx);
20721 }
20722
20723 if let Some(buffer) = edited_buffer {
20724 if buffer.read(cx).file().is_none() {
20725 cx.emit(EditorEvent::TitleChanged);
20726 }
20727
20728 if self.project.is_some() {
20729 let buffer_id = buffer.read(cx).remote_id();
20730 self.register_buffer(buffer_id, cx);
20731 self.update_lsp_data(Some(buffer_id), window, cx);
20732 self.refresh_inlay_hints(
20733 InlayHintRefreshReason::BufferEdited(buffer_id),
20734 cx,
20735 );
20736 }
20737 }
20738
20739 cx.emit(EditorEvent::BufferEdited);
20740 cx.emit(SearchEvent::MatchesInvalidated);
20741
20742 let Some(project) = &self.project else { return };
20743 let (telemetry, is_via_ssh) = {
20744 let project = project.read(cx);
20745 let telemetry = project.client().telemetry().clone();
20746 let is_via_ssh = project.is_via_remote_server();
20747 (telemetry, is_via_ssh)
20748 };
20749 telemetry.log_edit_event("editor", is_via_ssh);
20750 }
20751 multi_buffer::Event::ExcerptsAdded {
20752 buffer,
20753 predecessor,
20754 excerpts,
20755 } => {
20756 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20757 let buffer_id = buffer.read(cx).remote_id();
20758 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20759 && let Some(project) = &self.project
20760 {
20761 update_uncommitted_diff_for_buffer(
20762 cx.entity(),
20763 project,
20764 [buffer.clone()],
20765 self.buffer.clone(),
20766 cx,
20767 )
20768 .detach();
20769 }
20770 self.update_lsp_data(Some(buffer_id), window, cx);
20771 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20772 cx.emit(EditorEvent::ExcerptsAdded {
20773 buffer: buffer.clone(),
20774 predecessor: *predecessor,
20775 excerpts: excerpts.clone(),
20776 });
20777 }
20778 multi_buffer::Event::ExcerptsRemoved {
20779 ids,
20780 removed_buffer_ids,
20781 } => {
20782 if let Some(inlay_hints) = &mut self.inlay_hints {
20783 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
20784 }
20785 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20786 for buffer_id in removed_buffer_ids {
20787 self.registered_buffers.remove(buffer_id);
20788 }
20789 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20790 cx.emit(EditorEvent::ExcerptsRemoved {
20791 ids: ids.clone(),
20792 removed_buffer_ids: removed_buffer_ids.clone(),
20793 });
20794 }
20795 multi_buffer::Event::ExcerptsEdited {
20796 excerpt_ids,
20797 buffer_ids,
20798 } => {
20799 self.display_map.update(cx, |map, cx| {
20800 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20801 });
20802 cx.emit(EditorEvent::ExcerptsEdited {
20803 ids: excerpt_ids.clone(),
20804 });
20805 }
20806 multi_buffer::Event::ExcerptsExpanded { ids } => {
20807 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20808 self.refresh_document_highlights(cx);
20809 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20810 }
20811 multi_buffer::Event::Reparsed(buffer_id) => {
20812 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20813 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20814
20815 cx.emit(EditorEvent::Reparsed(*buffer_id));
20816 }
20817 multi_buffer::Event::DiffHunksToggled => {
20818 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20819 }
20820 multi_buffer::Event::LanguageChanged(buffer_id) => {
20821 self.registered_buffers.remove(&buffer_id);
20822 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20823 cx.emit(EditorEvent::Reparsed(*buffer_id));
20824 cx.notify();
20825 }
20826 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20827 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20828 multi_buffer::Event::FileHandleChanged
20829 | multi_buffer::Event::Reloaded
20830 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20831 multi_buffer::Event::DiagnosticsUpdated => {
20832 self.update_diagnostics_state(window, cx);
20833 }
20834 _ => {}
20835 };
20836 }
20837
20838 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20839 if !self.diagnostics_enabled() {
20840 return;
20841 }
20842 self.refresh_active_diagnostics(cx);
20843 self.refresh_inline_diagnostics(true, window, cx);
20844 self.scrollbar_marker_state.dirty = true;
20845 cx.notify();
20846 }
20847
20848 pub fn start_temporary_diff_override(&mut self) {
20849 self.load_diff_task.take();
20850 self.temporary_diff_override = true;
20851 }
20852
20853 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20854 self.temporary_diff_override = false;
20855 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20856 self.buffer.update(cx, |buffer, cx| {
20857 buffer.set_all_diff_hunks_collapsed(cx);
20858 });
20859
20860 if let Some(project) = self.project.clone() {
20861 self.load_diff_task = Some(
20862 update_uncommitted_diff_for_buffer(
20863 cx.entity(),
20864 &project,
20865 self.buffer.read(cx).all_buffers(),
20866 self.buffer.clone(),
20867 cx,
20868 )
20869 .shared(),
20870 );
20871 }
20872 }
20873
20874 fn on_display_map_changed(
20875 &mut self,
20876 _: Entity<DisplayMap>,
20877 _: &mut Window,
20878 cx: &mut Context<Self>,
20879 ) {
20880 cx.notify();
20881 }
20882
20883 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20884 if self.diagnostics_enabled() {
20885 let new_severity = EditorSettings::get_global(cx)
20886 .diagnostics_max_severity
20887 .unwrap_or(DiagnosticSeverity::Hint);
20888 self.set_max_diagnostics_severity(new_severity, cx);
20889 }
20890 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20891 self.update_edit_prediction_settings(cx);
20892 self.refresh_edit_prediction(true, false, window, cx);
20893 self.refresh_inline_values(cx);
20894 self.refresh_inlay_hints(
20895 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20896 self.selections.newest_anchor().head(),
20897 &self.buffer.read(cx).snapshot(cx),
20898 cx,
20899 )),
20900 cx,
20901 );
20902
20903 let old_cursor_shape = self.cursor_shape;
20904 let old_show_breadcrumbs = self.show_breadcrumbs;
20905
20906 {
20907 let editor_settings = EditorSettings::get_global(cx);
20908 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20909 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20910 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20911 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20912 }
20913
20914 if old_cursor_shape != self.cursor_shape {
20915 cx.emit(EditorEvent::CursorShapeChanged);
20916 }
20917
20918 if old_show_breadcrumbs != self.show_breadcrumbs {
20919 cx.emit(EditorEvent::BreadcrumbsChanged);
20920 }
20921
20922 let project_settings = ProjectSettings::get_global(cx);
20923 self.serialize_dirty_buffers =
20924 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20925
20926 if self.mode.is_full() {
20927 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20928 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20929 if self.show_inline_diagnostics != show_inline_diagnostics {
20930 self.show_inline_diagnostics = show_inline_diagnostics;
20931 self.refresh_inline_diagnostics(false, window, cx);
20932 }
20933
20934 if self.git_blame_inline_enabled != inline_blame_enabled {
20935 self.toggle_git_blame_inline_internal(false, window, cx);
20936 }
20937
20938 let minimap_settings = EditorSettings::get_global(cx).minimap;
20939 if self.minimap_visibility != MinimapVisibility::Disabled {
20940 if self.minimap_visibility.settings_visibility()
20941 != minimap_settings.minimap_enabled()
20942 {
20943 self.set_minimap_visibility(
20944 MinimapVisibility::for_mode(self.mode(), cx),
20945 window,
20946 cx,
20947 );
20948 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20949 minimap_entity.update(cx, |minimap_editor, cx| {
20950 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20951 })
20952 }
20953 }
20954 }
20955
20956 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20957 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20958 }) {
20959 if !inlay_splice.is_empty() {
20960 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20961 }
20962 self.refresh_colors_for_visible_range(None, window, cx);
20963 }
20964
20965 cx.notify();
20966 }
20967
20968 pub fn set_searchable(&mut self, searchable: bool) {
20969 self.searchable = searchable;
20970 }
20971
20972 pub fn searchable(&self) -> bool {
20973 self.searchable
20974 }
20975
20976 fn open_proposed_changes_editor(
20977 &mut self,
20978 _: &OpenProposedChangesEditor,
20979 window: &mut Window,
20980 cx: &mut Context<Self>,
20981 ) {
20982 let Some(workspace) = self.workspace() else {
20983 cx.propagate();
20984 return;
20985 };
20986
20987 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
20988 let multi_buffer = self.buffer.read(cx);
20989 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20990 let mut new_selections_by_buffer = HashMap::default();
20991 for selection in selections {
20992 for (buffer, range, _) in
20993 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20994 {
20995 let mut range = range.to_point(buffer);
20996 range.start.column = 0;
20997 range.end.column = buffer.line_len(range.end.row);
20998 new_selections_by_buffer
20999 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21000 .or_insert(Vec::new())
21001 .push(range)
21002 }
21003 }
21004
21005 let proposed_changes_buffers = new_selections_by_buffer
21006 .into_iter()
21007 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21008 .collect::<Vec<_>>();
21009 let proposed_changes_editor = cx.new(|cx| {
21010 ProposedChangesEditor::new(
21011 "Proposed changes",
21012 proposed_changes_buffers,
21013 self.project.clone(),
21014 window,
21015 cx,
21016 )
21017 });
21018
21019 window.defer(cx, move |window, cx| {
21020 workspace.update(cx, |workspace, cx| {
21021 workspace.active_pane().update(cx, |pane, cx| {
21022 pane.add_item(
21023 Box::new(proposed_changes_editor),
21024 true,
21025 true,
21026 None,
21027 window,
21028 cx,
21029 );
21030 });
21031 });
21032 });
21033 }
21034
21035 pub fn open_excerpts_in_split(
21036 &mut self,
21037 _: &OpenExcerptsSplit,
21038 window: &mut Window,
21039 cx: &mut Context<Self>,
21040 ) {
21041 self.open_excerpts_common(None, true, window, cx)
21042 }
21043
21044 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21045 self.open_excerpts_common(None, false, window, cx)
21046 }
21047
21048 fn open_excerpts_common(
21049 &mut self,
21050 jump_data: Option<JumpData>,
21051 split: bool,
21052 window: &mut Window,
21053 cx: &mut Context<Self>,
21054 ) {
21055 let Some(workspace) = self.workspace() else {
21056 cx.propagate();
21057 return;
21058 };
21059
21060 if self.buffer.read(cx).is_singleton() {
21061 cx.propagate();
21062 return;
21063 }
21064
21065 let mut new_selections_by_buffer = HashMap::default();
21066 match &jump_data {
21067 Some(JumpData::MultiBufferPoint {
21068 excerpt_id,
21069 position,
21070 anchor,
21071 line_offset_from_top,
21072 }) => {
21073 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21074 if let Some(buffer) = multi_buffer_snapshot
21075 .buffer_id_for_excerpt(*excerpt_id)
21076 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21077 {
21078 let buffer_snapshot = buffer.read(cx).snapshot();
21079 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21080 language::ToPoint::to_point(anchor, &buffer_snapshot)
21081 } else {
21082 buffer_snapshot.clip_point(*position, Bias::Left)
21083 };
21084 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21085 new_selections_by_buffer.insert(
21086 buffer,
21087 (
21088 vec![jump_to_offset..jump_to_offset],
21089 Some(*line_offset_from_top),
21090 ),
21091 );
21092 }
21093 }
21094 Some(JumpData::MultiBufferRow {
21095 row,
21096 line_offset_from_top,
21097 }) => {
21098 let point = MultiBufferPoint::new(row.0, 0);
21099 if let Some((buffer, buffer_point, _)) =
21100 self.buffer.read(cx).point_to_buffer_point(point, cx)
21101 {
21102 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21103 new_selections_by_buffer
21104 .entry(buffer)
21105 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21106 .0
21107 .push(buffer_offset..buffer_offset)
21108 }
21109 }
21110 None => {
21111 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21112 let multi_buffer = self.buffer.read(cx);
21113 for selection in selections {
21114 for (snapshot, range, _, anchor) in multi_buffer
21115 .snapshot(cx)
21116 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21117 {
21118 if let Some(anchor) = anchor {
21119 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21120 else {
21121 continue;
21122 };
21123 let offset = text::ToOffset::to_offset(
21124 &anchor.text_anchor,
21125 &buffer_handle.read(cx).snapshot(),
21126 );
21127 let range = offset..offset;
21128 new_selections_by_buffer
21129 .entry(buffer_handle)
21130 .or_insert((Vec::new(), None))
21131 .0
21132 .push(range)
21133 } else {
21134 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21135 else {
21136 continue;
21137 };
21138 new_selections_by_buffer
21139 .entry(buffer_handle)
21140 .or_insert((Vec::new(), None))
21141 .0
21142 .push(range)
21143 }
21144 }
21145 }
21146 }
21147 }
21148
21149 new_selections_by_buffer
21150 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21151
21152 if new_selections_by_buffer.is_empty() {
21153 return;
21154 }
21155
21156 // We defer the pane interaction because we ourselves are a workspace item
21157 // and activating a new item causes the pane to call a method on us reentrantly,
21158 // which panics if we're on the stack.
21159 window.defer(cx, move |window, cx| {
21160 workspace.update(cx, |workspace, cx| {
21161 let pane = if split {
21162 workspace.adjacent_pane(window, cx)
21163 } else {
21164 workspace.active_pane().clone()
21165 };
21166
21167 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21168 let editor = buffer
21169 .read(cx)
21170 .file()
21171 .is_none()
21172 .then(|| {
21173 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21174 // so `workspace.open_project_item` will never find them, always opening a new editor.
21175 // Instead, we try to activate the existing editor in the pane first.
21176 let (editor, pane_item_index) =
21177 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21178 let editor = item.downcast::<Editor>()?;
21179 let singleton_buffer =
21180 editor.read(cx).buffer().read(cx).as_singleton()?;
21181 if singleton_buffer == buffer {
21182 Some((editor, i))
21183 } else {
21184 None
21185 }
21186 })?;
21187 pane.update(cx, |pane, cx| {
21188 pane.activate_item(pane_item_index, true, true, window, cx)
21189 });
21190 Some(editor)
21191 })
21192 .flatten()
21193 .unwrap_or_else(|| {
21194 workspace.open_project_item::<Self>(
21195 pane.clone(),
21196 buffer,
21197 true,
21198 true,
21199 window,
21200 cx,
21201 )
21202 });
21203
21204 editor.update(cx, |editor, cx| {
21205 let autoscroll = match scroll_offset {
21206 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21207 None => Autoscroll::newest(),
21208 };
21209 let nav_history = editor.nav_history.take();
21210 editor.change_selections(
21211 SelectionEffects::scroll(autoscroll),
21212 window,
21213 cx,
21214 |s| {
21215 s.select_ranges(ranges);
21216 },
21217 );
21218 editor.nav_history = nav_history;
21219 });
21220 }
21221 })
21222 });
21223 }
21224
21225 // For now, don't allow opening excerpts in buffers that aren't backed by
21226 // regular project files.
21227 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21228 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21229 }
21230
21231 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21232 let snapshot = self.buffer.read(cx).read(cx);
21233 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21234 Some(
21235 ranges
21236 .iter()
21237 .map(move |range| {
21238 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21239 })
21240 .collect(),
21241 )
21242 }
21243
21244 fn selection_replacement_ranges(
21245 &self,
21246 range: Range<OffsetUtf16>,
21247 cx: &mut App,
21248 ) -> Vec<Range<OffsetUtf16>> {
21249 let selections = self
21250 .selections
21251 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21252 let newest_selection = selections
21253 .iter()
21254 .max_by_key(|selection| selection.id)
21255 .unwrap();
21256 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21257 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21258 let snapshot = self.buffer.read(cx).read(cx);
21259 selections
21260 .into_iter()
21261 .map(|mut selection| {
21262 selection.start.0 =
21263 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21264 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21265 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21266 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21267 })
21268 .collect()
21269 }
21270
21271 fn report_editor_event(
21272 &self,
21273 reported_event: ReportEditorEvent,
21274 file_extension: Option<String>,
21275 cx: &App,
21276 ) {
21277 if cfg!(any(test, feature = "test-support")) {
21278 return;
21279 }
21280
21281 let Some(project) = &self.project else { return };
21282
21283 // If None, we are in a file without an extension
21284 let file = self
21285 .buffer
21286 .read(cx)
21287 .as_singleton()
21288 .and_then(|b| b.read(cx).file());
21289 let file_extension = file_extension.or(file
21290 .as_ref()
21291 .and_then(|file| Path::new(file.file_name(cx)).extension())
21292 .and_then(|e| e.to_str())
21293 .map(|a| a.to_string()));
21294
21295 let vim_mode = vim_enabled(cx);
21296
21297 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21298 let copilot_enabled = edit_predictions_provider
21299 == language::language_settings::EditPredictionProvider::Copilot;
21300 let copilot_enabled_for_language = self
21301 .buffer
21302 .read(cx)
21303 .language_settings(cx)
21304 .show_edit_predictions;
21305
21306 let project = project.read(cx);
21307 let event_type = reported_event.event_type();
21308
21309 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21310 telemetry::event!(
21311 event_type,
21312 type = if auto_saved {"autosave"} else {"manual"},
21313 file_extension,
21314 vim_mode,
21315 copilot_enabled,
21316 copilot_enabled_for_language,
21317 edit_predictions_provider,
21318 is_via_ssh = project.is_via_remote_server(),
21319 );
21320 } else {
21321 telemetry::event!(
21322 event_type,
21323 file_extension,
21324 vim_mode,
21325 copilot_enabled,
21326 copilot_enabled_for_language,
21327 edit_predictions_provider,
21328 is_via_ssh = project.is_via_remote_server(),
21329 );
21330 };
21331 }
21332
21333 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21334 /// with each line being an array of {text, highlight} objects.
21335 fn copy_highlight_json(
21336 &mut self,
21337 _: &CopyHighlightJson,
21338 window: &mut Window,
21339 cx: &mut Context<Self>,
21340 ) {
21341 #[derive(Serialize)]
21342 struct Chunk<'a> {
21343 text: String,
21344 highlight: Option<&'a str>,
21345 }
21346
21347 let snapshot = self.buffer.read(cx).snapshot(cx);
21348 let range = self
21349 .selected_text_range(false, window, cx)
21350 .and_then(|selection| {
21351 if selection.range.is_empty() {
21352 None
21353 } else {
21354 Some(selection.range)
21355 }
21356 })
21357 .unwrap_or_else(|| 0..snapshot.len());
21358
21359 let chunks = snapshot.chunks(range, true);
21360 let mut lines = Vec::new();
21361 let mut line: VecDeque<Chunk> = VecDeque::new();
21362
21363 let Some(style) = self.style.as_ref() else {
21364 return;
21365 };
21366
21367 for chunk in chunks {
21368 let highlight = chunk
21369 .syntax_highlight_id
21370 .and_then(|id| id.name(&style.syntax));
21371 let mut chunk_lines = chunk.text.split('\n').peekable();
21372 while let Some(text) = chunk_lines.next() {
21373 let mut merged_with_last_token = false;
21374 if let Some(last_token) = line.back_mut()
21375 && last_token.highlight == highlight
21376 {
21377 last_token.text.push_str(text);
21378 merged_with_last_token = true;
21379 }
21380
21381 if !merged_with_last_token {
21382 line.push_back(Chunk {
21383 text: text.into(),
21384 highlight,
21385 });
21386 }
21387
21388 if chunk_lines.peek().is_some() {
21389 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21390 line.pop_front();
21391 }
21392 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21393 line.pop_back();
21394 }
21395
21396 lines.push(mem::take(&mut line));
21397 }
21398 }
21399 }
21400
21401 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21402 return;
21403 };
21404 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21405 }
21406
21407 pub fn open_context_menu(
21408 &mut self,
21409 _: &OpenContextMenu,
21410 window: &mut Window,
21411 cx: &mut Context<Self>,
21412 ) {
21413 self.request_autoscroll(Autoscroll::newest(), cx);
21414 let position = self
21415 .selections
21416 .newest_display(&self.display_snapshot(cx))
21417 .start;
21418 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21419 }
21420
21421 pub fn replay_insert_event(
21422 &mut self,
21423 text: &str,
21424 relative_utf16_range: Option<Range<isize>>,
21425 window: &mut Window,
21426 cx: &mut Context<Self>,
21427 ) {
21428 if !self.input_enabled {
21429 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21430 return;
21431 }
21432 if let Some(relative_utf16_range) = relative_utf16_range {
21433 let selections = self
21434 .selections
21435 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21436 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21437 let new_ranges = selections.into_iter().map(|range| {
21438 let start = OffsetUtf16(
21439 range
21440 .head()
21441 .0
21442 .saturating_add_signed(relative_utf16_range.start),
21443 );
21444 let end = OffsetUtf16(
21445 range
21446 .head()
21447 .0
21448 .saturating_add_signed(relative_utf16_range.end),
21449 );
21450 start..end
21451 });
21452 s.select_ranges(new_ranges);
21453 });
21454 }
21455
21456 self.handle_input(text, window, cx);
21457 }
21458
21459 pub fn is_focused(&self, window: &Window) -> bool {
21460 self.focus_handle.is_focused(window)
21461 }
21462
21463 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21464 cx.emit(EditorEvent::Focused);
21465
21466 if let Some(descendant) = self
21467 .last_focused_descendant
21468 .take()
21469 .and_then(|descendant| descendant.upgrade())
21470 {
21471 window.focus(&descendant);
21472 } else {
21473 if let Some(blame) = self.blame.as_ref() {
21474 blame.update(cx, GitBlame::focus)
21475 }
21476
21477 self.blink_manager.update(cx, BlinkManager::enable);
21478 self.show_cursor_names(window, cx);
21479 self.buffer.update(cx, |buffer, cx| {
21480 buffer.finalize_last_transaction(cx);
21481 if self.leader_id.is_none() {
21482 buffer.set_active_selections(
21483 &self.selections.disjoint_anchors_arc(),
21484 self.selections.line_mode(),
21485 self.cursor_shape,
21486 cx,
21487 );
21488 }
21489 });
21490 }
21491 }
21492
21493 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21494 cx.emit(EditorEvent::FocusedIn)
21495 }
21496
21497 fn handle_focus_out(
21498 &mut self,
21499 event: FocusOutEvent,
21500 _window: &mut Window,
21501 cx: &mut Context<Self>,
21502 ) {
21503 if event.blurred != self.focus_handle {
21504 self.last_focused_descendant = Some(event.blurred);
21505 }
21506 self.selection_drag_state = SelectionDragState::None;
21507 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21508 }
21509
21510 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21511 self.blink_manager.update(cx, BlinkManager::disable);
21512 self.buffer
21513 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21514
21515 if let Some(blame) = self.blame.as_ref() {
21516 blame.update(cx, GitBlame::blur)
21517 }
21518 if !self.hover_state.focused(window, cx) {
21519 hide_hover(self, cx);
21520 }
21521 if !self
21522 .context_menu
21523 .borrow()
21524 .as_ref()
21525 .is_some_and(|context_menu| context_menu.focused(window, cx))
21526 {
21527 self.hide_context_menu(window, cx);
21528 }
21529 self.take_active_edit_prediction(cx);
21530 cx.emit(EditorEvent::Blurred);
21531 cx.notify();
21532 }
21533
21534 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21535 let mut pending: String = window
21536 .pending_input_keystrokes()
21537 .into_iter()
21538 .flatten()
21539 .filter_map(|keystroke| {
21540 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21541 keystroke.key_char.clone()
21542 } else {
21543 None
21544 }
21545 })
21546 .collect();
21547
21548 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21549 pending = "".to_string();
21550 }
21551
21552 let existing_pending = self
21553 .text_highlights::<PendingInput>(cx)
21554 .map(|(_, ranges)| ranges.to_vec());
21555 if existing_pending.is_none() && pending.is_empty() {
21556 return;
21557 }
21558 let transaction =
21559 self.transact(window, cx, |this, window, cx| {
21560 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21561 let edits = selections
21562 .iter()
21563 .map(|selection| (selection.end..selection.end, pending.clone()));
21564 this.edit(edits, cx);
21565 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21566 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21567 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21568 }));
21569 });
21570 if let Some(existing_ranges) = existing_pending {
21571 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21572 this.edit(edits, cx);
21573 }
21574 });
21575
21576 let snapshot = self.snapshot(window, cx);
21577 let ranges = self
21578 .selections
21579 .all::<usize>(&snapshot.display_snapshot)
21580 .into_iter()
21581 .map(|selection| {
21582 snapshot.buffer_snapshot().anchor_after(selection.end)
21583 ..snapshot
21584 .buffer_snapshot()
21585 .anchor_before(selection.end + pending.len())
21586 })
21587 .collect();
21588
21589 if pending.is_empty() {
21590 self.clear_highlights::<PendingInput>(cx);
21591 } else {
21592 self.highlight_text::<PendingInput>(
21593 ranges,
21594 HighlightStyle {
21595 underline: Some(UnderlineStyle {
21596 thickness: px(1.),
21597 color: None,
21598 wavy: false,
21599 }),
21600 ..Default::default()
21601 },
21602 cx,
21603 );
21604 }
21605
21606 self.ime_transaction = self.ime_transaction.or(transaction);
21607 if let Some(transaction) = self.ime_transaction {
21608 self.buffer.update(cx, |buffer, cx| {
21609 buffer.group_until_transaction(transaction, cx);
21610 });
21611 }
21612
21613 if self.text_highlights::<PendingInput>(cx).is_none() {
21614 self.ime_transaction.take();
21615 }
21616 }
21617
21618 pub fn register_action_renderer(
21619 &mut self,
21620 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21621 ) -> Subscription {
21622 let id = self.next_editor_action_id.post_inc();
21623 self.editor_actions
21624 .borrow_mut()
21625 .insert(id, Box::new(listener));
21626
21627 let editor_actions = self.editor_actions.clone();
21628 Subscription::new(move || {
21629 editor_actions.borrow_mut().remove(&id);
21630 })
21631 }
21632
21633 pub fn register_action<A: Action>(
21634 &mut self,
21635 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21636 ) -> Subscription {
21637 let id = self.next_editor_action_id.post_inc();
21638 let listener = Arc::new(listener);
21639 self.editor_actions.borrow_mut().insert(
21640 id,
21641 Box::new(move |_, window, _| {
21642 let listener = listener.clone();
21643 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21644 let action = action.downcast_ref().unwrap();
21645 if phase == DispatchPhase::Bubble {
21646 listener(action, window, cx)
21647 }
21648 })
21649 }),
21650 );
21651
21652 let editor_actions = self.editor_actions.clone();
21653 Subscription::new(move || {
21654 editor_actions.borrow_mut().remove(&id);
21655 })
21656 }
21657
21658 pub fn file_header_size(&self) -> u32 {
21659 FILE_HEADER_HEIGHT
21660 }
21661
21662 pub fn restore(
21663 &mut self,
21664 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21665 window: &mut Window,
21666 cx: &mut Context<Self>,
21667 ) {
21668 let workspace = self.workspace();
21669 let project = self.project();
21670 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21671 let mut tasks = Vec::new();
21672 for (buffer_id, changes) in revert_changes {
21673 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21674 buffer.update(cx, |buffer, cx| {
21675 buffer.edit(
21676 changes
21677 .into_iter()
21678 .map(|(range, text)| (range, text.to_string())),
21679 None,
21680 cx,
21681 );
21682 });
21683
21684 if let Some(project) =
21685 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21686 {
21687 project.update(cx, |project, cx| {
21688 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21689 })
21690 }
21691 }
21692 }
21693 tasks
21694 });
21695 cx.spawn_in(window, async move |_, cx| {
21696 for (buffer, task) in save_tasks {
21697 let result = task.await;
21698 if result.is_err() {
21699 let Some(path) = buffer
21700 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21701 .ok()
21702 else {
21703 continue;
21704 };
21705 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21706 let Some(task) = cx
21707 .update_window_entity(workspace, |workspace, window, cx| {
21708 workspace
21709 .open_path_preview(path, None, false, false, false, window, cx)
21710 })
21711 .ok()
21712 else {
21713 continue;
21714 };
21715 task.await.log_err();
21716 }
21717 }
21718 }
21719 })
21720 .detach();
21721 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21722 selections.refresh()
21723 });
21724 }
21725
21726 pub fn to_pixel_point(
21727 &self,
21728 source: multi_buffer::Anchor,
21729 editor_snapshot: &EditorSnapshot,
21730 window: &mut Window,
21731 ) -> Option<gpui::Point<Pixels>> {
21732 let source_point = source.to_display_point(editor_snapshot);
21733 self.display_to_pixel_point(source_point, editor_snapshot, window)
21734 }
21735
21736 pub fn display_to_pixel_point(
21737 &self,
21738 source: DisplayPoint,
21739 editor_snapshot: &EditorSnapshot,
21740 window: &mut Window,
21741 ) -> Option<gpui::Point<Pixels>> {
21742 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21743 let text_layout_details = self.text_layout_details(window);
21744 let scroll_top = text_layout_details
21745 .scroll_anchor
21746 .scroll_position(editor_snapshot)
21747 .y;
21748
21749 if source.row().as_f64() < scroll_top.floor() {
21750 return None;
21751 }
21752 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21753 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21754 Some(gpui::Point::new(source_x, source_y))
21755 }
21756
21757 pub fn has_visible_completions_menu(&self) -> bool {
21758 !self.edit_prediction_preview_is_active()
21759 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21760 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21761 })
21762 }
21763
21764 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21765 if self.mode.is_minimap() {
21766 return;
21767 }
21768 self.addons
21769 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21770 }
21771
21772 pub fn unregister_addon<T: Addon>(&mut self) {
21773 self.addons.remove(&std::any::TypeId::of::<T>());
21774 }
21775
21776 pub fn addon<T: Addon>(&self) -> Option<&T> {
21777 let type_id = std::any::TypeId::of::<T>();
21778 self.addons
21779 .get(&type_id)
21780 .and_then(|item| item.to_any().downcast_ref::<T>())
21781 }
21782
21783 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21784 let type_id = std::any::TypeId::of::<T>();
21785 self.addons
21786 .get_mut(&type_id)
21787 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21788 }
21789
21790 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21791 let text_layout_details = self.text_layout_details(window);
21792 let style = &text_layout_details.editor_style;
21793 let font_id = window.text_system().resolve_font(&style.text.font());
21794 let font_size = style.text.font_size.to_pixels(window.rem_size());
21795 let line_height = style.text.line_height_in_pixels(window.rem_size());
21796 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21797 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21798
21799 CharacterDimensions {
21800 em_width,
21801 em_advance,
21802 line_height,
21803 }
21804 }
21805
21806 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21807 self.load_diff_task.clone()
21808 }
21809
21810 fn read_metadata_from_db(
21811 &mut self,
21812 item_id: u64,
21813 workspace_id: WorkspaceId,
21814 window: &mut Window,
21815 cx: &mut Context<Editor>,
21816 ) {
21817 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21818 && !self.mode.is_minimap()
21819 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21820 {
21821 let buffer_snapshot = OnceCell::new();
21822
21823 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21824 && !folds.is_empty()
21825 {
21826 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21827 self.fold_ranges(
21828 folds
21829 .into_iter()
21830 .map(|(start, end)| {
21831 snapshot.clip_offset(start, Bias::Left)
21832 ..snapshot.clip_offset(end, Bias::Right)
21833 })
21834 .collect(),
21835 false,
21836 window,
21837 cx,
21838 );
21839 }
21840
21841 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21842 && !selections.is_empty()
21843 {
21844 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21845 // skip adding the initial selection to selection history
21846 self.selection_history.mode = SelectionHistoryMode::Skipping;
21847 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21848 s.select_ranges(selections.into_iter().map(|(start, end)| {
21849 snapshot.clip_offset(start, Bias::Left)
21850 ..snapshot.clip_offset(end, Bias::Right)
21851 }));
21852 });
21853 self.selection_history.mode = SelectionHistoryMode::Normal;
21854 };
21855 }
21856
21857 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21858 }
21859
21860 fn update_lsp_data(
21861 &mut self,
21862 for_buffer: Option<BufferId>,
21863 window: &mut Window,
21864 cx: &mut Context<'_, Self>,
21865 ) {
21866 self.pull_diagnostics(for_buffer, window, cx);
21867 self.refresh_colors_for_visible_range(for_buffer, window, cx);
21868 }
21869
21870 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
21871 if self.ignore_lsp_data() {
21872 return;
21873 }
21874 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
21875 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
21876 }
21877 }
21878
21879 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
21880 if !self.registered_buffers.contains_key(&buffer_id)
21881 && let Some(project) = self.project.as_ref()
21882 {
21883 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
21884 project.update(cx, |project, cx| {
21885 self.registered_buffers.insert(
21886 buffer_id,
21887 project.register_buffer_with_language_servers(&buffer, cx),
21888 );
21889 });
21890 } else {
21891 self.registered_buffers.remove(&buffer_id);
21892 }
21893 }
21894 }
21895
21896 fn ignore_lsp_data(&self) -> bool {
21897 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
21898 // skip any LSP updates for it.
21899 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
21900 }
21901}
21902
21903fn edit_for_markdown_paste<'a>(
21904 buffer: &MultiBufferSnapshot,
21905 range: Range<usize>,
21906 to_insert: &'a str,
21907 url: Option<url::Url>,
21908) -> (Range<usize>, Cow<'a, str>) {
21909 if url.is_none() {
21910 return (range, Cow::Borrowed(to_insert));
21911 };
21912
21913 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21914
21915 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21916 Cow::Borrowed(to_insert)
21917 } else {
21918 Cow::Owned(format!("[{old_text}]({to_insert})"))
21919 };
21920 (range, new_text)
21921}
21922
21923fn vim_enabled(cx: &App) -> bool {
21924 vim_mode_setting::VimModeSetting::try_get(cx)
21925 .map(|vim_mode| vim_mode.0)
21926 .unwrap_or(false)
21927}
21928
21929fn process_completion_for_edit(
21930 completion: &Completion,
21931 intent: CompletionIntent,
21932 buffer: &Entity<Buffer>,
21933 cursor_position: &text::Anchor,
21934 cx: &mut Context<Editor>,
21935) -> CompletionEdit {
21936 let buffer = buffer.read(cx);
21937 let buffer_snapshot = buffer.snapshot();
21938 let (snippet, new_text) = if completion.is_snippet() {
21939 let mut snippet_source = completion.new_text.clone();
21940 // Workaround for typescript language server issues so that methods don't expand within
21941 // strings and functions with type expressions. The previous point is used because the query
21942 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21943 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
21944 let previous_point = if previous_point.column > 0 {
21945 cursor_position.to_previous_offset(&buffer_snapshot)
21946 } else {
21947 cursor_position.to_offset(&buffer_snapshot)
21948 };
21949 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21950 && scope.prefers_label_for_snippet_in_completion()
21951 && let Some(label) = completion.label()
21952 && matches!(
21953 completion.kind(),
21954 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21955 )
21956 {
21957 snippet_source = label;
21958 }
21959 match Snippet::parse(&snippet_source).log_err() {
21960 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21961 None => (None, completion.new_text.clone()),
21962 }
21963 } else {
21964 (None, completion.new_text.clone())
21965 };
21966
21967 let mut range_to_replace = {
21968 let replace_range = &completion.replace_range;
21969 if let CompletionSource::Lsp {
21970 insert_range: Some(insert_range),
21971 ..
21972 } = &completion.source
21973 {
21974 debug_assert_eq!(
21975 insert_range.start, replace_range.start,
21976 "insert_range and replace_range should start at the same position"
21977 );
21978 debug_assert!(
21979 insert_range
21980 .start
21981 .cmp(cursor_position, &buffer_snapshot)
21982 .is_le(),
21983 "insert_range should start before or at cursor position"
21984 );
21985 debug_assert!(
21986 replace_range
21987 .start
21988 .cmp(cursor_position, &buffer_snapshot)
21989 .is_le(),
21990 "replace_range should start before or at cursor position"
21991 );
21992
21993 let should_replace = match intent {
21994 CompletionIntent::CompleteWithInsert => false,
21995 CompletionIntent::CompleteWithReplace => true,
21996 CompletionIntent::Complete | CompletionIntent::Compose => {
21997 let insert_mode =
21998 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21999 .completions
22000 .lsp_insert_mode;
22001 match insert_mode {
22002 LspInsertMode::Insert => false,
22003 LspInsertMode::Replace => true,
22004 LspInsertMode::ReplaceSubsequence => {
22005 let mut text_to_replace = buffer.chars_for_range(
22006 buffer.anchor_before(replace_range.start)
22007 ..buffer.anchor_after(replace_range.end),
22008 );
22009 let mut current_needle = text_to_replace.next();
22010 for haystack_ch in completion.label.text.chars() {
22011 if let Some(needle_ch) = current_needle
22012 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22013 {
22014 current_needle = text_to_replace.next();
22015 }
22016 }
22017 current_needle.is_none()
22018 }
22019 LspInsertMode::ReplaceSuffix => {
22020 if replace_range
22021 .end
22022 .cmp(cursor_position, &buffer_snapshot)
22023 .is_gt()
22024 {
22025 let range_after_cursor = *cursor_position..replace_range.end;
22026 let text_after_cursor = buffer
22027 .text_for_range(
22028 buffer.anchor_before(range_after_cursor.start)
22029 ..buffer.anchor_after(range_after_cursor.end),
22030 )
22031 .collect::<String>()
22032 .to_ascii_lowercase();
22033 completion
22034 .label
22035 .text
22036 .to_ascii_lowercase()
22037 .ends_with(&text_after_cursor)
22038 } else {
22039 true
22040 }
22041 }
22042 }
22043 }
22044 };
22045
22046 if should_replace {
22047 replace_range.clone()
22048 } else {
22049 insert_range.clone()
22050 }
22051 } else {
22052 replace_range.clone()
22053 }
22054 };
22055
22056 if range_to_replace
22057 .end
22058 .cmp(cursor_position, &buffer_snapshot)
22059 .is_lt()
22060 {
22061 range_to_replace.end = *cursor_position;
22062 }
22063
22064 CompletionEdit {
22065 new_text,
22066 replace_range: range_to_replace.to_offset(buffer),
22067 snippet,
22068 }
22069}
22070
22071struct CompletionEdit {
22072 new_text: String,
22073 replace_range: Range<usize>,
22074 snippet: Option<Snippet>,
22075}
22076
22077fn insert_extra_newline_brackets(
22078 buffer: &MultiBufferSnapshot,
22079 range: Range<usize>,
22080 language: &language::LanguageScope,
22081) -> bool {
22082 let leading_whitespace_len = buffer
22083 .reversed_chars_at(range.start)
22084 .take_while(|c| c.is_whitespace() && *c != '\n')
22085 .map(|c| c.len_utf8())
22086 .sum::<usize>();
22087 let trailing_whitespace_len = buffer
22088 .chars_at(range.end)
22089 .take_while(|c| c.is_whitespace() && *c != '\n')
22090 .map(|c| c.len_utf8())
22091 .sum::<usize>();
22092 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22093
22094 language.brackets().any(|(pair, enabled)| {
22095 let pair_start = pair.start.trim_end();
22096 let pair_end = pair.end.trim_start();
22097
22098 enabled
22099 && pair.newline
22100 && buffer.contains_str_at(range.end, pair_end)
22101 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22102 })
22103}
22104
22105fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22106 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22107 [(buffer, range, _)] => (*buffer, range.clone()),
22108 _ => return false,
22109 };
22110 let pair = {
22111 let mut result: Option<BracketMatch> = None;
22112
22113 for pair in buffer
22114 .all_bracket_ranges(range.clone())
22115 .filter(move |pair| {
22116 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22117 })
22118 {
22119 let len = pair.close_range.end - pair.open_range.start;
22120
22121 if let Some(existing) = &result {
22122 let existing_len = existing.close_range.end - existing.open_range.start;
22123 if len > existing_len {
22124 continue;
22125 }
22126 }
22127
22128 result = Some(pair);
22129 }
22130
22131 result
22132 };
22133 let Some(pair) = pair else {
22134 return false;
22135 };
22136 pair.newline_only
22137 && buffer
22138 .chars_for_range(pair.open_range.end..range.start)
22139 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22140 .all(|c| c.is_whitespace() && c != '\n')
22141}
22142
22143fn update_uncommitted_diff_for_buffer(
22144 editor: Entity<Editor>,
22145 project: &Entity<Project>,
22146 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22147 buffer: Entity<MultiBuffer>,
22148 cx: &mut App,
22149) -> Task<()> {
22150 let mut tasks = Vec::new();
22151 project.update(cx, |project, cx| {
22152 for buffer in buffers {
22153 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22154 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22155 }
22156 }
22157 });
22158 cx.spawn(async move |cx| {
22159 let diffs = future::join_all(tasks).await;
22160 if editor
22161 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22162 .unwrap_or(false)
22163 {
22164 return;
22165 }
22166
22167 buffer
22168 .update(cx, |buffer, cx| {
22169 for diff in diffs.into_iter().flatten() {
22170 buffer.add_diff(diff, cx);
22171 }
22172 })
22173 .ok();
22174 })
22175}
22176
22177fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22178 let tab_size = tab_size.get() as usize;
22179 let mut width = offset;
22180
22181 for ch in text.chars() {
22182 width += if ch == '\t' {
22183 tab_size - (width % tab_size)
22184 } else {
22185 1
22186 };
22187 }
22188
22189 width - offset
22190}
22191
22192#[cfg(test)]
22193mod tests {
22194 use super::*;
22195
22196 #[test]
22197 fn test_string_size_with_expanded_tabs() {
22198 let nz = |val| NonZeroU32::new(val).unwrap();
22199 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22200 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22201 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22202 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22203 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22204 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22205 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22206 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22207 }
22208}
22209
22210/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22211struct WordBreakingTokenizer<'a> {
22212 input: &'a str,
22213}
22214
22215impl<'a> WordBreakingTokenizer<'a> {
22216 fn new(input: &'a str) -> Self {
22217 Self { input }
22218 }
22219}
22220
22221fn is_char_ideographic(ch: char) -> bool {
22222 use unicode_script::Script::*;
22223 use unicode_script::UnicodeScript;
22224 matches!(ch.script(), Han | Tangut | Yi)
22225}
22226
22227fn is_grapheme_ideographic(text: &str) -> bool {
22228 text.chars().any(is_char_ideographic)
22229}
22230
22231fn is_grapheme_whitespace(text: &str) -> bool {
22232 text.chars().any(|x| x.is_whitespace())
22233}
22234
22235fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22236 text.chars()
22237 .next()
22238 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22239}
22240
22241#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22242enum WordBreakToken<'a> {
22243 Word { token: &'a str, grapheme_len: usize },
22244 InlineWhitespace { token: &'a str, grapheme_len: usize },
22245 Newline,
22246}
22247
22248impl<'a> Iterator for WordBreakingTokenizer<'a> {
22249 /// Yields a span, the count of graphemes in the token, and whether it was
22250 /// whitespace. Note that it also breaks at word boundaries.
22251 type Item = WordBreakToken<'a>;
22252
22253 fn next(&mut self) -> Option<Self::Item> {
22254 use unicode_segmentation::UnicodeSegmentation;
22255 if self.input.is_empty() {
22256 return None;
22257 }
22258
22259 let mut iter = self.input.graphemes(true).peekable();
22260 let mut offset = 0;
22261 let mut grapheme_len = 0;
22262 if let Some(first_grapheme) = iter.next() {
22263 let is_newline = first_grapheme == "\n";
22264 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22265 offset += first_grapheme.len();
22266 grapheme_len += 1;
22267 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22268 if let Some(grapheme) = iter.peek().copied()
22269 && should_stay_with_preceding_ideograph(grapheme)
22270 {
22271 offset += grapheme.len();
22272 grapheme_len += 1;
22273 }
22274 } else {
22275 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22276 let mut next_word_bound = words.peek().copied();
22277 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22278 next_word_bound = words.next();
22279 }
22280 while let Some(grapheme) = iter.peek().copied() {
22281 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22282 break;
22283 };
22284 if is_grapheme_whitespace(grapheme) != is_whitespace
22285 || (grapheme == "\n") != is_newline
22286 {
22287 break;
22288 };
22289 offset += grapheme.len();
22290 grapheme_len += 1;
22291 iter.next();
22292 }
22293 }
22294 let token = &self.input[..offset];
22295 self.input = &self.input[offset..];
22296 if token == "\n" {
22297 Some(WordBreakToken::Newline)
22298 } else if is_whitespace {
22299 Some(WordBreakToken::InlineWhitespace {
22300 token,
22301 grapheme_len,
22302 })
22303 } else {
22304 Some(WordBreakToken::Word {
22305 token,
22306 grapheme_len,
22307 })
22308 }
22309 } else {
22310 None
22311 }
22312 }
22313}
22314
22315#[test]
22316fn test_word_breaking_tokenizer() {
22317 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22318 ("", &[]),
22319 (" ", &[whitespace(" ", 2)]),
22320 ("Ʒ", &[word("Ʒ", 1)]),
22321 ("Ǽ", &[word("Ǽ", 1)]),
22322 ("⋑", &[word("⋑", 1)]),
22323 ("⋑⋑", &[word("⋑⋑", 2)]),
22324 (
22325 "原理,进而",
22326 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22327 ),
22328 (
22329 "hello world",
22330 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22331 ),
22332 (
22333 "hello, world",
22334 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22335 ),
22336 (
22337 " hello world",
22338 &[
22339 whitespace(" ", 2),
22340 word("hello", 5),
22341 whitespace(" ", 1),
22342 word("world", 5),
22343 ],
22344 ),
22345 (
22346 "这是什么 \n 钢笔",
22347 &[
22348 word("这", 1),
22349 word("是", 1),
22350 word("什", 1),
22351 word("么", 1),
22352 whitespace(" ", 1),
22353 newline(),
22354 whitespace(" ", 1),
22355 word("钢", 1),
22356 word("笔", 1),
22357 ],
22358 ),
22359 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22360 ];
22361
22362 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22363 WordBreakToken::Word {
22364 token,
22365 grapheme_len,
22366 }
22367 }
22368
22369 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22370 WordBreakToken::InlineWhitespace {
22371 token,
22372 grapheme_len,
22373 }
22374 }
22375
22376 fn newline() -> WordBreakToken<'static> {
22377 WordBreakToken::Newline
22378 }
22379
22380 for (input, result) in tests {
22381 assert_eq!(
22382 WordBreakingTokenizer::new(input)
22383 .collect::<Vec<_>>()
22384 .as_slice(),
22385 *result,
22386 );
22387 }
22388}
22389
22390fn wrap_with_prefix(
22391 first_line_prefix: String,
22392 subsequent_lines_prefix: String,
22393 unwrapped_text: String,
22394 wrap_column: usize,
22395 tab_size: NonZeroU32,
22396 preserve_existing_whitespace: bool,
22397) -> String {
22398 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22399 let subsequent_lines_prefix_len =
22400 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22401 let mut wrapped_text = String::new();
22402 let mut current_line = first_line_prefix;
22403 let mut is_first_line = true;
22404
22405 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22406 let mut current_line_len = first_line_prefix_len;
22407 let mut in_whitespace = false;
22408 for token in tokenizer {
22409 let have_preceding_whitespace = in_whitespace;
22410 match token {
22411 WordBreakToken::Word {
22412 token,
22413 grapheme_len,
22414 } => {
22415 in_whitespace = false;
22416 let current_prefix_len = if is_first_line {
22417 first_line_prefix_len
22418 } else {
22419 subsequent_lines_prefix_len
22420 };
22421 if current_line_len + grapheme_len > wrap_column
22422 && current_line_len != current_prefix_len
22423 {
22424 wrapped_text.push_str(current_line.trim_end());
22425 wrapped_text.push('\n');
22426 is_first_line = false;
22427 current_line = subsequent_lines_prefix.clone();
22428 current_line_len = subsequent_lines_prefix_len;
22429 }
22430 current_line.push_str(token);
22431 current_line_len += grapheme_len;
22432 }
22433 WordBreakToken::InlineWhitespace {
22434 mut token,
22435 mut grapheme_len,
22436 } => {
22437 in_whitespace = true;
22438 if have_preceding_whitespace && !preserve_existing_whitespace {
22439 continue;
22440 }
22441 if !preserve_existing_whitespace {
22442 // Keep a single whitespace grapheme as-is
22443 if let Some(first) =
22444 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22445 {
22446 token = first;
22447 } else {
22448 token = " ";
22449 }
22450 grapheme_len = 1;
22451 }
22452 let current_prefix_len = if is_first_line {
22453 first_line_prefix_len
22454 } else {
22455 subsequent_lines_prefix_len
22456 };
22457 if current_line_len + grapheme_len > wrap_column {
22458 wrapped_text.push_str(current_line.trim_end());
22459 wrapped_text.push('\n');
22460 is_first_line = false;
22461 current_line = subsequent_lines_prefix.clone();
22462 current_line_len = subsequent_lines_prefix_len;
22463 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22464 current_line.push_str(token);
22465 current_line_len += grapheme_len;
22466 }
22467 }
22468 WordBreakToken::Newline => {
22469 in_whitespace = true;
22470 let current_prefix_len = if is_first_line {
22471 first_line_prefix_len
22472 } else {
22473 subsequent_lines_prefix_len
22474 };
22475 if preserve_existing_whitespace {
22476 wrapped_text.push_str(current_line.trim_end());
22477 wrapped_text.push('\n');
22478 is_first_line = false;
22479 current_line = subsequent_lines_prefix.clone();
22480 current_line_len = subsequent_lines_prefix_len;
22481 } else if have_preceding_whitespace {
22482 continue;
22483 } else if current_line_len + 1 > wrap_column
22484 && current_line_len != current_prefix_len
22485 {
22486 wrapped_text.push_str(current_line.trim_end());
22487 wrapped_text.push('\n');
22488 is_first_line = false;
22489 current_line = subsequent_lines_prefix.clone();
22490 current_line_len = subsequent_lines_prefix_len;
22491 } else if current_line_len != current_prefix_len {
22492 current_line.push(' ');
22493 current_line_len += 1;
22494 }
22495 }
22496 }
22497 }
22498
22499 if !current_line.is_empty() {
22500 wrapped_text.push_str(¤t_line);
22501 }
22502 wrapped_text
22503}
22504
22505#[test]
22506fn test_wrap_with_prefix() {
22507 assert_eq!(
22508 wrap_with_prefix(
22509 "# ".to_string(),
22510 "# ".to_string(),
22511 "abcdefg".to_string(),
22512 4,
22513 NonZeroU32::new(4).unwrap(),
22514 false,
22515 ),
22516 "# abcdefg"
22517 );
22518 assert_eq!(
22519 wrap_with_prefix(
22520 "".to_string(),
22521 "".to_string(),
22522 "\thello world".to_string(),
22523 8,
22524 NonZeroU32::new(4).unwrap(),
22525 false,
22526 ),
22527 "hello\nworld"
22528 );
22529 assert_eq!(
22530 wrap_with_prefix(
22531 "// ".to_string(),
22532 "// ".to_string(),
22533 "xx \nyy zz aa bb cc".to_string(),
22534 12,
22535 NonZeroU32::new(4).unwrap(),
22536 false,
22537 ),
22538 "// xx yy zz\n// aa bb cc"
22539 );
22540 assert_eq!(
22541 wrap_with_prefix(
22542 String::new(),
22543 String::new(),
22544 "这是什么 \n 钢笔".to_string(),
22545 3,
22546 NonZeroU32::new(4).unwrap(),
22547 false,
22548 ),
22549 "这是什\n么 钢\n笔"
22550 );
22551 assert_eq!(
22552 wrap_with_prefix(
22553 String::new(),
22554 String::new(),
22555 format!("foo{}bar", '\u{2009}'), // thin space
22556 80,
22557 NonZeroU32::new(4).unwrap(),
22558 false,
22559 ),
22560 format!("foo{}bar", '\u{2009}')
22561 );
22562}
22563
22564pub trait CollaborationHub {
22565 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22566 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22567 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22568}
22569
22570impl CollaborationHub for Entity<Project> {
22571 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22572 self.read(cx).collaborators()
22573 }
22574
22575 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22576 self.read(cx).user_store().read(cx).participant_indices()
22577 }
22578
22579 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22580 let this = self.read(cx);
22581 let user_ids = this.collaborators().values().map(|c| c.user_id);
22582 this.user_store().read(cx).participant_names(user_ids, cx)
22583 }
22584}
22585
22586pub trait SemanticsProvider {
22587 fn hover(
22588 &self,
22589 buffer: &Entity<Buffer>,
22590 position: text::Anchor,
22591 cx: &mut App,
22592 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22593
22594 fn inline_values(
22595 &self,
22596 buffer_handle: Entity<Buffer>,
22597 range: Range<text::Anchor>,
22598 cx: &mut App,
22599 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22600
22601 fn applicable_inlay_chunks(
22602 &self,
22603 buffer_id: BufferId,
22604 ranges: &[Range<text::Anchor>],
22605 cx: &App,
22606 ) -> Vec<Range<BufferRow>>;
22607
22608 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22609
22610 fn inlay_hints(
22611 &self,
22612 invalidate: InvalidationStrategy,
22613 buffer: Entity<Buffer>,
22614 ranges: Vec<Range<text::Anchor>>,
22615 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22616 cx: &mut App,
22617 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22618
22619 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22620
22621 fn document_highlights(
22622 &self,
22623 buffer: &Entity<Buffer>,
22624 position: text::Anchor,
22625 cx: &mut App,
22626 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22627
22628 fn definitions(
22629 &self,
22630 buffer: &Entity<Buffer>,
22631 position: text::Anchor,
22632 kind: GotoDefinitionKind,
22633 cx: &mut App,
22634 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22635
22636 fn range_for_rename(
22637 &self,
22638 buffer: &Entity<Buffer>,
22639 position: text::Anchor,
22640 cx: &mut App,
22641 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22642
22643 fn perform_rename(
22644 &self,
22645 buffer: &Entity<Buffer>,
22646 position: text::Anchor,
22647 new_name: String,
22648 cx: &mut App,
22649 ) -> Option<Task<Result<ProjectTransaction>>>;
22650}
22651
22652pub trait CompletionProvider {
22653 fn completions(
22654 &self,
22655 excerpt_id: ExcerptId,
22656 buffer: &Entity<Buffer>,
22657 buffer_position: text::Anchor,
22658 trigger: CompletionContext,
22659 window: &mut Window,
22660 cx: &mut Context<Editor>,
22661 ) -> Task<Result<Vec<CompletionResponse>>>;
22662
22663 fn resolve_completions(
22664 &self,
22665 _buffer: Entity<Buffer>,
22666 _completion_indices: Vec<usize>,
22667 _completions: Rc<RefCell<Box<[Completion]>>>,
22668 _cx: &mut Context<Editor>,
22669 ) -> Task<Result<bool>> {
22670 Task::ready(Ok(false))
22671 }
22672
22673 fn apply_additional_edits_for_completion(
22674 &self,
22675 _buffer: Entity<Buffer>,
22676 _completions: Rc<RefCell<Box<[Completion]>>>,
22677 _completion_index: usize,
22678 _push_to_history: bool,
22679 _cx: &mut Context<Editor>,
22680 ) -> Task<Result<Option<language::Transaction>>> {
22681 Task::ready(Ok(None))
22682 }
22683
22684 fn is_completion_trigger(
22685 &self,
22686 buffer: &Entity<Buffer>,
22687 position: language::Anchor,
22688 text: &str,
22689 trigger_in_words: bool,
22690 menu_is_open: bool,
22691 cx: &mut Context<Editor>,
22692 ) -> bool;
22693
22694 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22695
22696 fn sort_completions(&self) -> bool {
22697 true
22698 }
22699
22700 fn filter_completions(&self) -> bool {
22701 true
22702 }
22703}
22704
22705pub trait CodeActionProvider {
22706 fn id(&self) -> Arc<str>;
22707
22708 fn code_actions(
22709 &self,
22710 buffer: &Entity<Buffer>,
22711 range: Range<text::Anchor>,
22712 window: &mut Window,
22713 cx: &mut App,
22714 ) -> Task<Result<Vec<CodeAction>>>;
22715
22716 fn apply_code_action(
22717 &self,
22718 buffer_handle: Entity<Buffer>,
22719 action: CodeAction,
22720 excerpt_id: ExcerptId,
22721 push_to_history: bool,
22722 window: &mut Window,
22723 cx: &mut App,
22724 ) -> Task<Result<ProjectTransaction>>;
22725}
22726
22727impl CodeActionProvider for Entity<Project> {
22728 fn id(&self) -> Arc<str> {
22729 "project".into()
22730 }
22731
22732 fn code_actions(
22733 &self,
22734 buffer: &Entity<Buffer>,
22735 range: Range<text::Anchor>,
22736 _window: &mut Window,
22737 cx: &mut App,
22738 ) -> Task<Result<Vec<CodeAction>>> {
22739 self.update(cx, |project, cx| {
22740 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22741 let code_actions = project.code_actions(buffer, range, None, cx);
22742 cx.background_spawn(async move {
22743 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22744 Ok(code_lens_actions
22745 .context("code lens fetch")?
22746 .into_iter()
22747 .flatten()
22748 .chain(
22749 code_actions
22750 .context("code action fetch")?
22751 .into_iter()
22752 .flatten(),
22753 )
22754 .collect())
22755 })
22756 })
22757 }
22758
22759 fn apply_code_action(
22760 &self,
22761 buffer_handle: Entity<Buffer>,
22762 action: CodeAction,
22763 _excerpt_id: ExcerptId,
22764 push_to_history: bool,
22765 _window: &mut Window,
22766 cx: &mut App,
22767 ) -> Task<Result<ProjectTransaction>> {
22768 self.update(cx, |project, cx| {
22769 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22770 })
22771 }
22772}
22773
22774fn snippet_completions(
22775 project: &Project,
22776 buffer: &Entity<Buffer>,
22777 buffer_position: text::Anchor,
22778 cx: &mut App,
22779) -> Task<Result<CompletionResponse>> {
22780 let languages = buffer.read(cx).languages_at(buffer_position);
22781 let snippet_store = project.snippets().read(cx);
22782
22783 let scopes: Vec<_> = languages
22784 .iter()
22785 .filter_map(|language| {
22786 let language_name = language.lsp_id();
22787 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22788
22789 if snippets.is_empty() {
22790 None
22791 } else {
22792 Some((language.default_scope(), snippets))
22793 }
22794 })
22795 .collect();
22796
22797 if scopes.is_empty() {
22798 return Task::ready(Ok(CompletionResponse {
22799 completions: vec![],
22800 display_options: CompletionDisplayOptions::default(),
22801 is_incomplete: false,
22802 }));
22803 }
22804
22805 let snapshot = buffer.read(cx).text_snapshot();
22806 let executor = cx.background_executor().clone();
22807
22808 cx.background_spawn(async move {
22809 let mut is_incomplete = false;
22810 let mut completions: Vec<Completion> = Vec::new();
22811 for (scope, snippets) in scopes.into_iter() {
22812 let classifier =
22813 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22814
22815 const MAX_WORD_PREFIX_LEN: usize = 128;
22816 let last_word: String = snapshot
22817 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22818 .take(MAX_WORD_PREFIX_LEN)
22819 .take_while(|c| classifier.is_word(*c))
22820 .collect::<String>()
22821 .chars()
22822 .rev()
22823 .collect();
22824
22825 if last_word.is_empty() {
22826 return Ok(CompletionResponse {
22827 completions: vec![],
22828 display_options: CompletionDisplayOptions::default(),
22829 is_incomplete: true,
22830 });
22831 }
22832
22833 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22834 let to_lsp = |point: &text::Anchor| {
22835 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22836 point_to_lsp(end)
22837 };
22838 let lsp_end = to_lsp(&buffer_position);
22839
22840 let candidates = snippets
22841 .iter()
22842 .enumerate()
22843 .flat_map(|(ix, snippet)| {
22844 snippet
22845 .prefix
22846 .iter()
22847 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22848 })
22849 .collect::<Vec<StringMatchCandidate>>();
22850
22851 const MAX_RESULTS: usize = 100;
22852 let mut matches = fuzzy::match_strings(
22853 &candidates,
22854 &last_word,
22855 last_word.chars().any(|c| c.is_uppercase()),
22856 true,
22857 MAX_RESULTS,
22858 &Default::default(),
22859 executor.clone(),
22860 )
22861 .await;
22862
22863 if matches.len() >= MAX_RESULTS {
22864 is_incomplete = true;
22865 }
22866
22867 // Remove all candidates where the query's start does not match the start of any word in the candidate
22868 if let Some(query_start) = last_word.chars().next() {
22869 matches.retain(|string_match| {
22870 split_words(&string_match.string).any(|word| {
22871 // Check that the first codepoint of the word as lowercase matches the first
22872 // codepoint of the query as lowercase
22873 word.chars()
22874 .flat_map(|codepoint| codepoint.to_lowercase())
22875 .zip(query_start.to_lowercase())
22876 .all(|(word_cp, query_cp)| word_cp == query_cp)
22877 })
22878 });
22879 }
22880
22881 let matched_strings = matches
22882 .into_iter()
22883 .map(|m| m.string)
22884 .collect::<HashSet<_>>();
22885
22886 completions.extend(snippets.iter().filter_map(|snippet| {
22887 let matching_prefix = snippet
22888 .prefix
22889 .iter()
22890 .find(|prefix| matched_strings.contains(*prefix))?;
22891 let start = as_offset - last_word.len();
22892 let start = snapshot.anchor_before(start);
22893 let range = start..buffer_position;
22894 let lsp_start = to_lsp(&start);
22895 let lsp_range = lsp::Range {
22896 start: lsp_start,
22897 end: lsp_end,
22898 };
22899 Some(Completion {
22900 replace_range: range,
22901 new_text: snippet.body.clone(),
22902 source: CompletionSource::Lsp {
22903 insert_range: None,
22904 server_id: LanguageServerId(usize::MAX),
22905 resolved: true,
22906 lsp_completion: Box::new(lsp::CompletionItem {
22907 label: snippet.prefix.first().unwrap().clone(),
22908 kind: Some(CompletionItemKind::SNIPPET),
22909 label_details: snippet.description.as_ref().map(|description| {
22910 lsp::CompletionItemLabelDetails {
22911 detail: Some(description.clone()),
22912 description: None,
22913 }
22914 }),
22915 insert_text_format: Some(InsertTextFormat::SNIPPET),
22916 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22917 lsp::InsertReplaceEdit {
22918 new_text: snippet.body.clone(),
22919 insert: lsp_range,
22920 replace: lsp_range,
22921 },
22922 )),
22923 filter_text: Some(snippet.body.clone()),
22924 sort_text: Some(char::MAX.to_string()),
22925 ..lsp::CompletionItem::default()
22926 }),
22927 lsp_defaults: None,
22928 },
22929 label: CodeLabel::plain(matching_prefix.clone(), None),
22930 icon_path: None,
22931 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22932 single_line: snippet.name.clone().into(),
22933 plain_text: snippet
22934 .description
22935 .clone()
22936 .map(|description| description.into()),
22937 }),
22938 insert_text_mode: None,
22939 confirm: None,
22940 })
22941 }))
22942 }
22943
22944 Ok(CompletionResponse {
22945 completions,
22946 display_options: CompletionDisplayOptions::default(),
22947 is_incomplete,
22948 })
22949 })
22950}
22951
22952impl CompletionProvider for Entity<Project> {
22953 fn completions(
22954 &self,
22955 _excerpt_id: ExcerptId,
22956 buffer: &Entity<Buffer>,
22957 buffer_position: text::Anchor,
22958 options: CompletionContext,
22959 _window: &mut Window,
22960 cx: &mut Context<Editor>,
22961 ) -> Task<Result<Vec<CompletionResponse>>> {
22962 self.update(cx, |project, cx| {
22963 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22964 let project_completions = project.completions(buffer, buffer_position, options, cx);
22965 cx.background_spawn(async move {
22966 let mut responses = project_completions.await?;
22967 let snippets = snippets.await?;
22968 if !snippets.completions.is_empty() {
22969 responses.push(snippets);
22970 }
22971 Ok(responses)
22972 })
22973 })
22974 }
22975
22976 fn resolve_completions(
22977 &self,
22978 buffer: Entity<Buffer>,
22979 completion_indices: Vec<usize>,
22980 completions: Rc<RefCell<Box<[Completion]>>>,
22981 cx: &mut Context<Editor>,
22982 ) -> Task<Result<bool>> {
22983 self.update(cx, |project, cx| {
22984 project.lsp_store().update(cx, |lsp_store, cx| {
22985 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22986 })
22987 })
22988 }
22989
22990 fn apply_additional_edits_for_completion(
22991 &self,
22992 buffer: Entity<Buffer>,
22993 completions: Rc<RefCell<Box<[Completion]>>>,
22994 completion_index: usize,
22995 push_to_history: bool,
22996 cx: &mut Context<Editor>,
22997 ) -> Task<Result<Option<language::Transaction>>> {
22998 self.update(cx, |project, cx| {
22999 project.lsp_store().update(cx, |lsp_store, cx| {
23000 lsp_store.apply_additional_edits_for_completion(
23001 buffer,
23002 completions,
23003 completion_index,
23004 push_to_history,
23005 cx,
23006 )
23007 })
23008 })
23009 }
23010
23011 fn is_completion_trigger(
23012 &self,
23013 buffer: &Entity<Buffer>,
23014 position: language::Anchor,
23015 text: &str,
23016 trigger_in_words: bool,
23017 menu_is_open: bool,
23018 cx: &mut Context<Editor>,
23019 ) -> bool {
23020 let mut chars = text.chars();
23021 let char = if let Some(char) = chars.next() {
23022 char
23023 } else {
23024 return false;
23025 };
23026 if chars.next().is_some() {
23027 return false;
23028 }
23029
23030 let buffer = buffer.read(cx);
23031 let snapshot = buffer.snapshot();
23032 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23033 return false;
23034 }
23035 let classifier = snapshot
23036 .char_classifier_at(position)
23037 .scope_context(Some(CharScopeContext::Completion));
23038 if trigger_in_words && classifier.is_word(char) {
23039 return true;
23040 }
23041
23042 buffer.completion_triggers().contains(text)
23043 }
23044}
23045
23046impl SemanticsProvider for Entity<Project> {
23047 fn hover(
23048 &self,
23049 buffer: &Entity<Buffer>,
23050 position: text::Anchor,
23051 cx: &mut App,
23052 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23053 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23054 }
23055
23056 fn document_highlights(
23057 &self,
23058 buffer: &Entity<Buffer>,
23059 position: text::Anchor,
23060 cx: &mut App,
23061 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23062 Some(self.update(cx, |project, cx| {
23063 project.document_highlights(buffer, position, cx)
23064 }))
23065 }
23066
23067 fn definitions(
23068 &self,
23069 buffer: &Entity<Buffer>,
23070 position: text::Anchor,
23071 kind: GotoDefinitionKind,
23072 cx: &mut App,
23073 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23074 Some(self.update(cx, |project, cx| match kind {
23075 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23076 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23077 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23078 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23079 }))
23080 }
23081
23082 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23083 self.update(cx, |project, cx| {
23084 if project
23085 .active_debug_session(cx)
23086 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23087 {
23088 return true;
23089 }
23090
23091 buffer.update(cx, |buffer, cx| {
23092 project.any_language_server_supports_inlay_hints(buffer, cx)
23093 })
23094 })
23095 }
23096
23097 fn inline_values(
23098 &self,
23099 buffer_handle: Entity<Buffer>,
23100 range: Range<text::Anchor>,
23101 cx: &mut App,
23102 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23103 self.update(cx, |project, cx| {
23104 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23105
23106 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23107 })
23108 }
23109
23110 fn applicable_inlay_chunks(
23111 &self,
23112 buffer_id: BufferId,
23113 ranges: &[Range<text::Anchor>],
23114 cx: &App,
23115 ) -> Vec<Range<BufferRow>> {
23116 self.read(cx)
23117 .lsp_store()
23118 .read(cx)
23119 .applicable_inlay_chunks(buffer_id, ranges)
23120 }
23121
23122 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23123 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23124 lsp_store.invalidate_inlay_hints(for_buffers)
23125 });
23126 }
23127
23128 fn inlay_hints(
23129 &self,
23130 invalidate: InvalidationStrategy,
23131 buffer: Entity<Buffer>,
23132 ranges: Vec<Range<text::Anchor>>,
23133 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23134 cx: &mut App,
23135 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23136 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23137 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23138 }))
23139 }
23140
23141 fn range_for_rename(
23142 &self,
23143 buffer: &Entity<Buffer>,
23144 position: text::Anchor,
23145 cx: &mut App,
23146 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23147 Some(self.update(cx, |project, cx| {
23148 let buffer = buffer.clone();
23149 let task = project.prepare_rename(buffer.clone(), position, cx);
23150 cx.spawn(async move |_, cx| {
23151 Ok(match task.await? {
23152 PrepareRenameResponse::Success(range) => Some(range),
23153 PrepareRenameResponse::InvalidPosition => None,
23154 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23155 // Fallback on using TreeSitter info to determine identifier range
23156 buffer.read_with(cx, |buffer, _| {
23157 let snapshot = buffer.snapshot();
23158 let (range, kind) = snapshot.surrounding_word(position, None);
23159 if kind != Some(CharKind::Word) {
23160 return None;
23161 }
23162 Some(
23163 snapshot.anchor_before(range.start)
23164 ..snapshot.anchor_after(range.end),
23165 )
23166 })?
23167 }
23168 })
23169 })
23170 }))
23171 }
23172
23173 fn perform_rename(
23174 &self,
23175 buffer: &Entity<Buffer>,
23176 position: text::Anchor,
23177 new_name: String,
23178 cx: &mut App,
23179 ) -> Option<Task<Result<ProjectTransaction>>> {
23180 Some(self.update(cx, |project, cx| {
23181 project.perform_rename(buffer.clone(), position, new_name, cx)
23182 }))
23183 }
23184}
23185
23186fn consume_contiguous_rows(
23187 contiguous_row_selections: &mut Vec<Selection<Point>>,
23188 selection: &Selection<Point>,
23189 display_map: &DisplaySnapshot,
23190 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23191) -> (MultiBufferRow, MultiBufferRow) {
23192 contiguous_row_selections.push(selection.clone());
23193 let start_row = starting_row(selection, display_map);
23194 let mut end_row = ending_row(selection, display_map);
23195
23196 while let Some(next_selection) = selections.peek() {
23197 if next_selection.start.row <= end_row.0 {
23198 end_row = ending_row(next_selection, display_map);
23199 contiguous_row_selections.push(selections.next().unwrap().clone());
23200 } else {
23201 break;
23202 }
23203 }
23204 (start_row, end_row)
23205}
23206
23207fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23208 if selection.start.column > 0 {
23209 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23210 } else {
23211 MultiBufferRow(selection.start.row)
23212 }
23213}
23214
23215fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23216 if next_selection.end.column > 0 || next_selection.is_empty() {
23217 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23218 } else {
23219 MultiBufferRow(next_selection.end.row)
23220 }
23221}
23222
23223impl EditorSnapshot {
23224 pub fn remote_selections_in_range<'a>(
23225 &'a self,
23226 range: &'a Range<Anchor>,
23227 collaboration_hub: &dyn CollaborationHub,
23228 cx: &'a App,
23229 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23230 let participant_names = collaboration_hub.user_names(cx);
23231 let participant_indices = collaboration_hub.user_participant_indices(cx);
23232 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23233 let collaborators_by_replica_id = collaborators_by_peer_id
23234 .values()
23235 .map(|collaborator| (collaborator.replica_id, collaborator))
23236 .collect::<HashMap<_, _>>();
23237 self.buffer_snapshot()
23238 .selections_in_range(range, false)
23239 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23240 if replica_id == ReplicaId::AGENT {
23241 Some(RemoteSelection {
23242 replica_id,
23243 selection,
23244 cursor_shape,
23245 line_mode,
23246 collaborator_id: CollaboratorId::Agent,
23247 user_name: Some("Agent".into()),
23248 color: cx.theme().players().agent(),
23249 })
23250 } else {
23251 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23252 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23253 let user_name = participant_names.get(&collaborator.user_id).cloned();
23254 Some(RemoteSelection {
23255 replica_id,
23256 selection,
23257 cursor_shape,
23258 line_mode,
23259 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23260 user_name,
23261 color: if let Some(index) = participant_index {
23262 cx.theme().players().color_for_participant(index.0)
23263 } else {
23264 cx.theme().players().absent()
23265 },
23266 })
23267 }
23268 })
23269 }
23270
23271 pub fn hunks_for_ranges(
23272 &self,
23273 ranges: impl IntoIterator<Item = Range<Point>>,
23274 ) -> Vec<MultiBufferDiffHunk> {
23275 let mut hunks = Vec::new();
23276 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23277 HashMap::default();
23278 for query_range in ranges {
23279 let query_rows =
23280 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23281 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23282 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23283 ) {
23284 // Include deleted hunks that are adjacent to the query range, because
23285 // otherwise they would be missed.
23286 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23287 if hunk.status().is_deleted() {
23288 intersects_range |= hunk.row_range.start == query_rows.end;
23289 intersects_range |= hunk.row_range.end == query_rows.start;
23290 }
23291 if intersects_range {
23292 if !processed_buffer_rows
23293 .entry(hunk.buffer_id)
23294 .or_default()
23295 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23296 {
23297 continue;
23298 }
23299 hunks.push(hunk);
23300 }
23301 }
23302 }
23303
23304 hunks
23305 }
23306
23307 fn display_diff_hunks_for_rows<'a>(
23308 &'a self,
23309 display_rows: Range<DisplayRow>,
23310 folded_buffers: &'a HashSet<BufferId>,
23311 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23312 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23313 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23314
23315 self.buffer_snapshot()
23316 .diff_hunks_in_range(buffer_start..buffer_end)
23317 .filter_map(|hunk| {
23318 if folded_buffers.contains(&hunk.buffer_id) {
23319 return None;
23320 }
23321
23322 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23323 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23324
23325 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23326 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23327
23328 let display_hunk = if hunk_display_start.column() != 0 {
23329 DisplayDiffHunk::Folded {
23330 display_row: hunk_display_start.row(),
23331 }
23332 } else {
23333 let mut end_row = hunk_display_end.row();
23334 if hunk_display_end.column() > 0 {
23335 end_row.0 += 1;
23336 }
23337 let is_created_file = hunk.is_created_file();
23338 DisplayDiffHunk::Unfolded {
23339 status: hunk.status(),
23340 diff_base_byte_range: hunk.diff_base_byte_range,
23341 display_row_range: hunk_display_start.row()..end_row,
23342 multi_buffer_range: Anchor::range_in_buffer(
23343 hunk.excerpt_id,
23344 hunk.buffer_id,
23345 hunk.buffer_range,
23346 ),
23347 is_created_file,
23348 }
23349 };
23350
23351 Some(display_hunk)
23352 })
23353 }
23354
23355 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23356 self.display_snapshot
23357 .buffer_snapshot()
23358 .language_at(position)
23359 }
23360
23361 pub fn is_focused(&self) -> bool {
23362 self.is_focused
23363 }
23364
23365 pub fn placeholder_text(&self) -> Option<String> {
23366 self.placeholder_display_snapshot
23367 .as_ref()
23368 .map(|display_map| display_map.text())
23369 }
23370
23371 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23372 self.scroll_anchor.scroll_position(&self.display_snapshot)
23373 }
23374
23375 fn gutter_dimensions(
23376 &self,
23377 font_id: FontId,
23378 font_size: Pixels,
23379 max_line_number_width: Pixels,
23380 cx: &App,
23381 ) -> Option<GutterDimensions> {
23382 if !self.show_gutter {
23383 return None;
23384 }
23385
23386 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23387 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23388
23389 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23390 matches!(
23391 ProjectSettings::get_global(cx).git.git_gutter,
23392 GitGutterSetting::TrackedFiles
23393 )
23394 });
23395 let gutter_settings = EditorSettings::get_global(cx).gutter;
23396 let show_line_numbers = self
23397 .show_line_numbers
23398 .unwrap_or(gutter_settings.line_numbers);
23399 let line_gutter_width = if show_line_numbers {
23400 // Avoid flicker-like gutter resizes when the line number gains another digit by
23401 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23402 let min_width_for_number_on_gutter =
23403 ch_advance * gutter_settings.min_line_number_digits as f32;
23404 max_line_number_width.max(min_width_for_number_on_gutter)
23405 } else {
23406 0.0.into()
23407 };
23408
23409 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23410 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23411
23412 let git_blame_entries_width =
23413 self.git_blame_gutter_max_author_length
23414 .map(|max_author_length| {
23415 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23416 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23417
23418 /// The number of characters to dedicate to gaps and margins.
23419 const SPACING_WIDTH: usize = 4;
23420
23421 let max_char_count = max_author_length.min(renderer.max_author_length())
23422 + ::git::SHORT_SHA_LENGTH
23423 + MAX_RELATIVE_TIMESTAMP.len()
23424 + SPACING_WIDTH;
23425
23426 ch_advance * max_char_count
23427 });
23428
23429 let is_singleton = self.buffer_snapshot().is_singleton();
23430
23431 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23432 left_padding += if !is_singleton {
23433 ch_width * 4.0
23434 } else if show_runnables || show_breakpoints {
23435 ch_width * 3.0
23436 } else if show_git_gutter && show_line_numbers {
23437 ch_width * 2.0
23438 } else if show_git_gutter || show_line_numbers {
23439 ch_width
23440 } else {
23441 px(0.)
23442 };
23443
23444 let shows_folds = is_singleton && gutter_settings.folds;
23445
23446 let right_padding = if shows_folds && show_line_numbers {
23447 ch_width * 4.0
23448 } else if shows_folds || (!is_singleton && show_line_numbers) {
23449 ch_width * 3.0
23450 } else if show_line_numbers {
23451 ch_width
23452 } else {
23453 px(0.)
23454 };
23455
23456 Some(GutterDimensions {
23457 left_padding,
23458 right_padding,
23459 width: line_gutter_width + left_padding + right_padding,
23460 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23461 git_blame_entries_width,
23462 })
23463 }
23464
23465 pub fn render_crease_toggle(
23466 &self,
23467 buffer_row: MultiBufferRow,
23468 row_contains_cursor: bool,
23469 editor: Entity<Editor>,
23470 window: &mut Window,
23471 cx: &mut App,
23472 ) -> Option<AnyElement> {
23473 let folded = self.is_line_folded(buffer_row);
23474 let mut is_foldable = false;
23475
23476 if let Some(crease) = self
23477 .crease_snapshot
23478 .query_row(buffer_row, self.buffer_snapshot())
23479 {
23480 is_foldable = true;
23481 match crease {
23482 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23483 if let Some(render_toggle) = render_toggle {
23484 let toggle_callback =
23485 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23486 if folded {
23487 editor.update(cx, |editor, cx| {
23488 editor.fold_at(buffer_row, window, cx)
23489 });
23490 } else {
23491 editor.update(cx, |editor, cx| {
23492 editor.unfold_at(buffer_row, window, cx)
23493 });
23494 }
23495 });
23496 return Some((render_toggle)(
23497 buffer_row,
23498 folded,
23499 toggle_callback,
23500 window,
23501 cx,
23502 ));
23503 }
23504 }
23505 }
23506 }
23507
23508 is_foldable |= self.starts_indent(buffer_row);
23509
23510 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23511 Some(
23512 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23513 .toggle_state(folded)
23514 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23515 if folded {
23516 this.unfold_at(buffer_row, window, cx);
23517 } else {
23518 this.fold_at(buffer_row, window, cx);
23519 }
23520 }))
23521 .into_any_element(),
23522 )
23523 } else {
23524 None
23525 }
23526 }
23527
23528 pub fn render_crease_trailer(
23529 &self,
23530 buffer_row: MultiBufferRow,
23531 window: &mut Window,
23532 cx: &mut App,
23533 ) -> Option<AnyElement> {
23534 let folded = self.is_line_folded(buffer_row);
23535 if let Crease::Inline { render_trailer, .. } = self
23536 .crease_snapshot
23537 .query_row(buffer_row, self.buffer_snapshot())?
23538 {
23539 let render_trailer = render_trailer.as_ref()?;
23540 Some(render_trailer(buffer_row, folded, window, cx))
23541 } else {
23542 None
23543 }
23544 }
23545}
23546
23547impl Deref for EditorSnapshot {
23548 type Target = DisplaySnapshot;
23549
23550 fn deref(&self) -> &Self::Target {
23551 &self.display_snapshot
23552 }
23553}
23554
23555#[derive(Clone, Debug, PartialEq, Eq)]
23556pub enum EditorEvent {
23557 InputIgnored {
23558 text: Arc<str>,
23559 },
23560 InputHandled {
23561 utf16_range_to_replace: Option<Range<isize>>,
23562 text: Arc<str>,
23563 },
23564 ExcerptsAdded {
23565 buffer: Entity<Buffer>,
23566 predecessor: ExcerptId,
23567 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23568 },
23569 ExcerptsRemoved {
23570 ids: Vec<ExcerptId>,
23571 removed_buffer_ids: Vec<BufferId>,
23572 },
23573 BufferFoldToggled {
23574 ids: Vec<ExcerptId>,
23575 folded: bool,
23576 },
23577 ExcerptsEdited {
23578 ids: Vec<ExcerptId>,
23579 },
23580 ExcerptsExpanded {
23581 ids: Vec<ExcerptId>,
23582 },
23583 BufferEdited,
23584 Edited {
23585 transaction_id: clock::Lamport,
23586 },
23587 Reparsed(BufferId),
23588 Focused,
23589 FocusedIn,
23590 Blurred,
23591 DirtyChanged,
23592 Saved,
23593 TitleChanged,
23594 SelectionsChanged {
23595 local: bool,
23596 },
23597 ScrollPositionChanged {
23598 local: bool,
23599 autoscroll: bool,
23600 },
23601 TransactionUndone {
23602 transaction_id: clock::Lamport,
23603 },
23604 TransactionBegun {
23605 transaction_id: clock::Lamport,
23606 },
23607 CursorShapeChanged,
23608 BreadcrumbsChanged,
23609 PushedToNavHistory {
23610 anchor: Anchor,
23611 is_deactivate: bool,
23612 },
23613}
23614
23615impl EventEmitter<EditorEvent> for Editor {}
23616
23617impl Focusable for Editor {
23618 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23619 self.focus_handle.clone()
23620 }
23621}
23622
23623impl Render for Editor {
23624 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23625 let settings = ThemeSettings::get_global(cx);
23626
23627 let mut text_style = match self.mode {
23628 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23629 color: cx.theme().colors().editor_foreground,
23630 font_family: settings.ui_font.family.clone(),
23631 font_features: settings.ui_font.features.clone(),
23632 font_fallbacks: settings.ui_font.fallbacks.clone(),
23633 font_size: rems(0.875).into(),
23634 font_weight: settings.ui_font.weight,
23635 line_height: relative(settings.buffer_line_height.value()),
23636 ..Default::default()
23637 },
23638 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23639 color: cx.theme().colors().editor_foreground,
23640 font_family: settings.buffer_font.family.clone(),
23641 font_features: settings.buffer_font.features.clone(),
23642 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23643 font_size: settings.buffer_font_size(cx).into(),
23644 font_weight: settings.buffer_font.weight,
23645 line_height: relative(settings.buffer_line_height.value()),
23646 ..Default::default()
23647 },
23648 };
23649 if let Some(text_style_refinement) = &self.text_style_refinement {
23650 text_style.refine(text_style_refinement)
23651 }
23652
23653 let background = match self.mode {
23654 EditorMode::SingleLine => cx.theme().system().transparent,
23655 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23656 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23657 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23658 };
23659
23660 EditorElement::new(
23661 &cx.entity(),
23662 EditorStyle {
23663 background,
23664 border: cx.theme().colors().border,
23665 local_player: cx.theme().players().local(),
23666 text: text_style,
23667 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23668 syntax: cx.theme().syntax().clone(),
23669 status: cx.theme().status().clone(),
23670 inlay_hints_style: make_inlay_hints_style(cx),
23671 edit_prediction_styles: make_suggestion_styles(cx),
23672 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23673 show_underlines: self.diagnostics_enabled(),
23674 },
23675 )
23676 }
23677}
23678
23679impl EntityInputHandler for Editor {
23680 fn text_for_range(
23681 &mut self,
23682 range_utf16: Range<usize>,
23683 adjusted_range: &mut Option<Range<usize>>,
23684 _: &mut Window,
23685 cx: &mut Context<Self>,
23686 ) -> Option<String> {
23687 let snapshot = self.buffer.read(cx).read(cx);
23688 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23689 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23690 if (start.0..end.0) != range_utf16 {
23691 adjusted_range.replace(start.0..end.0);
23692 }
23693 Some(snapshot.text_for_range(start..end).collect())
23694 }
23695
23696 fn selected_text_range(
23697 &mut self,
23698 ignore_disabled_input: bool,
23699 _: &mut Window,
23700 cx: &mut Context<Self>,
23701 ) -> Option<UTF16Selection> {
23702 // Prevent the IME menu from appearing when holding down an alphabetic key
23703 // while input is disabled.
23704 if !ignore_disabled_input && !self.input_enabled {
23705 return None;
23706 }
23707
23708 let selection = self
23709 .selections
23710 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23711 let range = selection.range();
23712
23713 Some(UTF16Selection {
23714 range: range.start.0..range.end.0,
23715 reversed: selection.reversed,
23716 })
23717 }
23718
23719 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23720 let snapshot = self.buffer.read(cx).read(cx);
23721 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23722 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23723 }
23724
23725 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23726 self.clear_highlights::<InputComposition>(cx);
23727 self.ime_transaction.take();
23728 }
23729
23730 fn replace_text_in_range(
23731 &mut self,
23732 range_utf16: Option<Range<usize>>,
23733 text: &str,
23734 window: &mut Window,
23735 cx: &mut Context<Self>,
23736 ) {
23737 if !self.input_enabled {
23738 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23739 return;
23740 }
23741
23742 self.transact(window, cx, |this, window, cx| {
23743 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23744 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23745 Some(this.selection_replacement_ranges(range_utf16, cx))
23746 } else {
23747 this.marked_text_ranges(cx)
23748 };
23749
23750 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23751 let newest_selection_id = this.selections.newest_anchor().id;
23752 this.selections
23753 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23754 .iter()
23755 .zip(ranges_to_replace.iter())
23756 .find_map(|(selection, range)| {
23757 if selection.id == newest_selection_id {
23758 Some(
23759 (range.start.0 as isize - selection.head().0 as isize)
23760 ..(range.end.0 as isize - selection.head().0 as isize),
23761 )
23762 } else {
23763 None
23764 }
23765 })
23766 });
23767
23768 cx.emit(EditorEvent::InputHandled {
23769 utf16_range_to_replace: range_to_replace,
23770 text: text.into(),
23771 });
23772
23773 if let Some(new_selected_ranges) = new_selected_ranges {
23774 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23775 selections.select_ranges(new_selected_ranges)
23776 });
23777 this.backspace(&Default::default(), window, cx);
23778 }
23779
23780 this.handle_input(text, window, cx);
23781 });
23782
23783 if let Some(transaction) = self.ime_transaction {
23784 self.buffer.update(cx, |buffer, cx| {
23785 buffer.group_until_transaction(transaction, cx);
23786 });
23787 }
23788
23789 self.unmark_text(window, cx);
23790 }
23791
23792 fn replace_and_mark_text_in_range(
23793 &mut self,
23794 range_utf16: Option<Range<usize>>,
23795 text: &str,
23796 new_selected_range_utf16: Option<Range<usize>>,
23797 window: &mut Window,
23798 cx: &mut Context<Self>,
23799 ) {
23800 if !self.input_enabled {
23801 return;
23802 }
23803
23804 let transaction = self.transact(window, cx, |this, window, cx| {
23805 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23806 let snapshot = this.buffer.read(cx).read(cx);
23807 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23808 for marked_range in &mut marked_ranges {
23809 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23810 marked_range.start.0 += relative_range_utf16.start;
23811 marked_range.start =
23812 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23813 marked_range.end =
23814 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23815 }
23816 }
23817 Some(marked_ranges)
23818 } else if let Some(range_utf16) = range_utf16 {
23819 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23820 Some(this.selection_replacement_ranges(range_utf16, cx))
23821 } else {
23822 None
23823 };
23824
23825 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23826 let newest_selection_id = this.selections.newest_anchor().id;
23827 this.selections
23828 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23829 .iter()
23830 .zip(ranges_to_replace.iter())
23831 .find_map(|(selection, range)| {
23832 if selection.id == newest_selection_id {
23833 Some(
23834 (range.start.0 as isize - selection.head().0 as isize)
23835 ..(range.end.0 as isize - selection.head().0 as isize),
23836 )
23837 } else {
23838 None
23839 }
23840 })
23841 });
23842
23843 cx.emit(EditorEvent::InputHandled {
23844 utf16_range_to_replace: range_to_replace,
23845 text: text.into(),
23846 });
23847
23848 if let Some(ranges) = ranges_to_replace {
23849 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23850 s.select_ranges(ranges)
23851 });
23852 }
23853
23854 let marked_ranges = {
23855 let snapshot = this.buffer.read(cx).read(cx);
23856 this.selections
23857 .disjoint_anchors_arc()
23858 .iter()
23859 .map(|selection| {
23860 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23861 })
23862 .collect::<Vec<_>>()
23863 };
23864
23865 if text.is_empty() {
23866 this.unmark_text(window, cx);
23867 } else {
23868 this.highlight_text::<InputComposition>(
23869 marked_ranges.clone(),
23870 HighlightStyle {
23871 underline: Some(UnderlineStyle {
23872 thickness: px(1.),
23873 color: None,
23874 wavy: false,
23875 }),
23876 ..Default::default()
23877 },
23878 cx,
23879 );
23880 }
23881
23882 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23883 let use_autoclose = this.use_autoclose;
23884 let use_auto_surround = this.use_auto_surround;
23885 this.set_use_autoclose(false);
23886 this.set_use_auto_surround(false);
23887 this.handle_input(text, window, cx);
23888 this.set_use_autoclose(use_autoclose);
23889 this.set_use_auto_surround(use_auto_surround);
23890
23891 if let Some(new_selected_range) = new_selected_range_utf16 {
23892 let snapshot = this.buffer.read(cx).read(cx);
23893 let new_selected_ranges = marked_ranges
23894 .into_iter()
23895 .map(|marked_range| {
23896 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23897 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23898 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23899 snapshot.clip_offset_utf16(new_start, Bias::Left)
23900 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23901 })
23902 .collect::<Vec<_>>();
23903
23904 drop(snapshot);
23905 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23906 selections.select_ranges(new_selected_ranges)
23907 });
23908 }
23909 });
23910
23911 self.ime_transaction = self.ime_transaction.or(transaction);
23912 if let Some(transaction) = self.ime_transaction {
23913 self.buffer.update(cx, |buffer, cx| {
23914 buffer.group_until_transaction(transaction, cx);
23915 });
23916 }
23917
23918 if self.text_highlights::<InputComposition>(cx).is_none() {
23919 self.ime_transaction.take();
23920 }
23921 }
23922
23923 fn bounds_for_range(
23924 &mut self,
23925 range_utf16: Range<usize>,
23926 element_bounds: gpui::Bounds<Pixels>,
23927 window: &mut Window,
23928 cx: &mut Context<Self>,
23929 ) -> Option<gpui::Bounds<Pixels>> {
23930 let text_layout_details = self.text_layout_details(window);
23931 let CharacterDimensions {
23932 em_width,
23933 em_advance,
23934 line_height,
23935 } = self.character_dimensions(window);
23936
23937 let snapshot = self.snapshot(window, cx);
23938 let scroll_position = snapshot.scroll_position();
23939 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23940
23941 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23942 let x = Pixels::from(
23943 ScrollOffset::from(
23944 snapshot.x_for_display_point(start, &text_layout_details)
23945 + self.gutter_dimensions.full_width(),
23946 ) - scroll_left,
23947 );
23948 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23949
23950 Some(Bounds {
23951 origin: element_bounds.origin + point(x, y),
23952 size: size(em_width, line_height),
23953 })
23954 }
23955
23956 fn character_index_for_point(
23957 &mut self,
23958 point: gpui::Point<Pixels>,
23959 _window: &mut Window,
23960 _cx: &mut Context<Self>,
23961 ) -> Option<usize> {
23962 let position_map = self.last_position_map.as_ref()?;
23963 if !position_map.text_hitbox.contains(&point) {
23964 return None;
23965 }
23966 let display_point = position_map.point_for_position(point).previous_valid;
23967 let anchor = position_map
23968 .snapshot
23969 .display_point_to_anchor(display_point, Bias::Left);
23970 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
23971 Some(utf16_offset.0)
23972 }
23973}
23974
23975trait SelectionExt {
23976 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23977 fn spanned_rows(
23978 &self,
23979 include_end_if_at_line_start: bool,
23980 map: &DisplaySnapshot,
23981 ) -> Range<MultiBufferRow>;
23982}
23983
23984impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23985 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23986 let start = self
23987 .start
23988 .to_point(map.buffer_snapshot())
23989 .to_display_point(map);
23990 let end = self
23991 .end
23992 .to_point(map.buffer_snapshot())
23993 .to_display_point(map);
23994 if self.reversed {
23995 end..start
23996 } else {
23997 start..end
23998 }
23999 }
24000
24001 fn spanned_rows(
24002 &self,
24003 include_end_if_at_line_start: bool,
24004 map: &DisplaySnapshot,
24005 ) -> Range<MultiBufferRow> {
24006 let start = self.start.to_point(map.buffer_snapshot());
24007 let mut end = self.end.to_point(map.buffer_snapshot());
24008 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24009 end.row -= 1;
24010 }
24011
24012 let buffer_start = map.prev_line_boundary(start).0;
24013 let buffer_end = map.next_line_boundary(end).0;
24014 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24015 }
24016}
24017
24018impl<T: InvalidationRegion> InvalidationStack<T> {
24019 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24020 where
24021 S: Clone + ToOffset,
24022 {
24023 while let Some(region) = self.last() {
24024 let all_selections_inside_invalidation_ranges =
24025 if selections.len() == region.ranges().len() {
24026 selections
24027 .iter()
24028 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24029 .all(|(selection, invalidation_range)| {
24030 let head = selection.head().to_offset(buffer);
24031 invalidation_range.start <= head && invalidation_range.end >= head
24032 })
24033 } else {
24034 false
24035 };
24036
24037 if all_selections_inside_invalidation_ranges {
24038 break;
24039 } else {
24040 self.pop();
24041 }
24042 }
24043 }
24044}
24045
24046impl<T> Default for InvalidationStack<T> {
24047 fn default() -> Self {
24048 Self(Default::default())
24049 }
24050}
24051
24052impl<T> Deref for InvalidationStack<T> {
24053 type Target = Vec<T>;
24054
24055 fn deref(&self) -> &Self::Target {
24056 &self.0
24057 }
24058}
24059
24060impl<T> DerefMut for InvalidationStack<T> {
24061 fn deref_mut(&mut self) -> &mut Self::Target {
24062 &mut self.0
24063 }
24064}
24065
24066impl InvalidationRegion for SnippetState {
24067 fn ranges(&self) -> &[Range<Anchor>] {
24068 &self.ranges[self.active_index]
24069 }
24070}
24071
24072fn edit_prediction_edit_text(
24073 current_snapshot: &BufferSnapshot,
24074 edits: &[(Range<Anchor>, String)],
24075 edit_preview: &EditPreview,
24076 include_deletions: bool,
24077 cx: &App,
24078) -> HighlightedText {
24079 let edits = edits
24080 .iter()
24081 .map(|(anchor, text)| {
24082 (
24083 anchor.start.text_anchor..anchor.end.text_anchor,
24084 text.clone(),
24085 )
24086 })
24087 .collect::<Vec<_>>();
24088
24089 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24090}
24091
24092fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24093 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24094 // Just show the raw edit text with basic styling
24095 let mut text = String::new();
24096 let mut highlights = Vec::new();
24097
24098 let insertion_highlight_style = HighlightStyle {
24099 color: Some(cx.theme().colors().text),
24100 ..Default::default()
24101 };
24102
24103 for (_, edit_text) in edits {
24104 let start_offset = text.len();
24105 text.push_str(edit_text);
24106 let end_offset = text.len();
24107
24108 if start_offset < end_offset {
24109 highlights.push((start_offset..end_offset, insertion_highlight_style));
24110 }
24111 }
24112
24113 HighlightedText {
24114 text: text.into(),
24115 highlights,
24116 }
24117}
24118
24119pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24120 match severity {
24121 lsp::DiagnosticSeverity::ERROR => colors.error,
24122 lsp::DiagnosticSeverity::WARNING => colors.warning,
24123 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24124 lsp::DiagnosticSeverity::HINT => colors.info,
24125 _ => colors.ignored,
24126 }
24127}
24128
24129pub fn styled_runs_for_code_label<'a>(
24130 label: &'a CodeLabel,
24131 syntax_theme: &'a theme::SyntaxTheme,
24132) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24133 let fade_out = HighlightStyle {
24134 fade_out: Some(0.35),
24135 ..Default::default()
24136 };
24137
24138 let mut prev_end = label.filter_range.end;
24139 label
24140 .runs
24141 .iter()
24142 .enumerate()
24143 .flat_map(move |(ix, (range, highlight_id))| {
24144 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24145 style
24146 } else {
24147 return Default::default();
24148 };
24149 let muted_style = style.highlight(fade_out);
24150
24151 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24152 if range.start >= label.filter_range.end {
24153 if range.start > prev_end {
24154 runs.push((prev_end..range.start, fade_out));
24155 }
24156 runs.push((range.clone(), muted_style));
24157 } else if range.end <= label.filter_range.end {
24158 runs.push((range.clone(), style));
24159 } else {
24160 runs.push((range.start..label.filter_range.end, style));
24161 runs.push((label.filter_range.end..range.end, muted_style));
24162 }
24163 prev_end = cmp::max(prev_end, range.end);
24164
24165 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24166 runs.push((prev_end..label.text.len(), fade_out));
24167 }
24168
24169 runs
24170 })
24171}
24172
24173pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24174 let mut prev_index = 0;
24175 let mut prev_codepoint: Option<char> = None;
24176 text.char_indices()
24177 .chain([(text.len(), '\0')])
24178 .filter_map(move |(index, codepoint)| {
24179 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24180 let is_boundary = index == text.len()
24181 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24182 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24183 if is_boundary {
24184 let chunk = &text[prev_index..index];
24185 prev_index = index;
24186 Some(chunk)
24187 } else {
24188 None
24189 }
24190 })
24191}
24192
24193pub trait RangeToAnchorExt: Sized {
24194 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24195
24196 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24197 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24198 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24199 }
24200}
24201
24202impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24203 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24204 let start_offset = self.start.to_offset(snapshot);
24205 let end_offset = self.end.to_offset(snapshot);
24206 if start_offset == end_offset {
24207 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24208 } else {
24209 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24210 }
24211 }
24212}
24213
24214pub trait RowExt {
24215 fn as_f64(&self) -> f64;
24216
24217 fn next_row(&self) -> Self;
24218
24219 fn previous_row(&self) -> Self;
24220
24221 fn minus(&self, other: Self) -> u32;
24222}
24223
24224impl RowExt for DisplayRow {
24225 fn as_f64(&self) -> f64 {
24226 self.0 as _
24227 }
24228
24229 fn next_row(&self) -> Self {
24230 Self(self.0 + 1)
24231 }
24232
24233 fn previous_row(&self) -> Self {
24234 Self(self.0.saturating_sub(1))
24235 }
24236
24237 fn minus(&self, other: Self) -> u32 {
24238 self.0 - other.0
24239 }
24240}
24241
24242impl RowExt for MultiBufferRow {
24243 fn as_f64(&self) -> f64 {
24244 self.0 as _
24245 }
24246
24247 fn next_row(&self) -> Self {
24248 Self(self.0 + 1)
24249 }
24250
24251 fn previous_row(&self) -> Self {
24252 Self(self.0.saturating_sub(1))
24253 }
24254
24255 fn minus(&self, other: Self) -> u32 {
24256 self.0 - other.0
24257 }
24258}
24259
24260trait RowRangeExt {
24261 type Row;
24262
24263 fn len(&self) -> usize;
24264
24265 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24266}
24267
24268impl RowRangeExt for Range<MultiBufferRow> {
24269 type Row = MultiBufferRow;
24270
24271 fn len(&self) -> usize {
24272 (self.end.0 - self.start.0) as usize
24273 }
24274
24275 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24276 (self.start.0..self.end.0).map(MultiBufferRow)
24277 }
24278}
24279
24280impl RowRangeExt for Range<DisplayRow> {
24281 type Row = DisplayRow;
24282
24283 fn len(&self) -> usize {
24284 (self.end.0 - self.start.0) as usize
24285 }
24286
24287 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24288 (self.start.0..self.end.0).map(DisplayRow)
24289 }
24290}
24291
24292/// If select range has more than one line, we
24293/// just point the cursor to range.start.
24294fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24295 if range.start.row == range.end.row {
24296 range
24297 } else {
24298 range.start..range.start
24299 }
24300}
24301pub struct KillRing(ClipboardItem);
24302impl Global for KillRing {}
24303
24304const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24305
24306enum BreakpointPromptEditAction {
24307 Log,
24308 Condition,
24309 HitCondition,
24310}
24311
24312struct BreakpointPromptEditor {
24313 pub(crate) prompt: Entity<Editor>,
24314 editor: WeakEntity<Editor>,
24315 breakpoint_anchor: Anchor,
24316 breakpoint: Breakpoint,
24317 edit_action: BreakpointPromptEditAction,
24318 block_ids: HashSet<CustomBlockId>,
24319 editor_margins: Arc<Mutex<EditorMargins>>,
24320 _subscriptions: Vec<Subscription>,
24321}
24322
24323impl BreakpointPromptEditor {
24324 const MAX_LINES: u8 = 4;
24325
24326 fn new(
24327 editor: WeakEntity<Editor>,
24328 breakpoint_anchor: Anchor,
24329 breakpoint: Breakpoint,
24330 edit_action: BreakpointPromptEditAction,
24331 window: &mut Window,
24332 cx: &mut Context<Self>,
24333 ) -> Self {
24334 let base_text = match edit_action {
24335 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24336 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24337 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24338 }
24339 .map(|msg| msg.to_string())
24340 .unwrap_or_default();
24341
24342 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24343 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24344
24345 let prompt = cx.new(|cx| {
24346 let mut prompt = Editor::new(
24347 EditorMode::AutoHeight {
24348 min_lines: 1,
24349 max_lines: Some(Self::MAX_LINES as usize),
24350 },
24351 buffer,
24352 None,
24353 window,
24354 cx,
24355 );
24356 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24357 prompt.set_show_cursor_when_unfocused(false, cx);
24358 prompt.set_placeholder_text(
24359 match edit_action {
24360 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24361 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24362 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24363 },
24364 window,
24365 cx,
24366 );
24367
24368 prompt
24369 });
24370
24371 Self {
24372 prompt,
24373 editor,
24374 breakpoint_anchor,
24375 breakpoint,
24376 edit_action,
24377 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24378 block_ids: Default::default(),
24379 _subscriptions: vec![],
24380 }
24381 }
24382
24383 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24384 self.block_ids.extend(block_ids)
24385 }
24386
24387 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24388 if let Some(editor) = self.editor.upgrade() {
24389 let message = self
24390 .prompt
24391 .read(cx)
24392 .buffer
24393 .read(cx)
24394 .as_singleton()
24395 .expect("A multi buffer in breakpoint prompt isn't possible")
24396 .read(cx)
24397 .as_rope()
24398 .to_string();
24399
24400 editor.update(cx, |editor, cx| {
24401 editor.edit_breakpoint_at_anchor(
24402 self.breakpoint_anchor,
24403 self.breakpoint.clone(),
24404 match self.edit_action {
24405 BreakpointPromptEditAction::Log => {
24406 BreakpointEditAction::EditLogMessage(message.into())
24407 }
24408 BreakpointPromptEditAction::Condition => {
24409 BreakpointEditAction::EditCondition(message.into())
24410 }
24411 BreakpointPromptEditAction::HitCondition => {
24412 BreakpointEditAction::EditHitCondition(message.into())
24413 }
24414 },
24415 cx,
24416 );
24417
24418 editor.remove_blocks(self.block_ids.clone(), None, cx);
24419 cx.focus_self(window);
24420 });
24421 }
24422 }
24423
24424 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24425 self.editor
24426 .update(cx, |editor, cx| {
24427 editor.remove_blocks(self.block_ids.clone(), None, cx);
24428 window.focus(&editor.focus_handle);
24429 })
24430 .log_err();
24431 }
24432
24433 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24434 let settings = ThemeSettings::get_global(cx);
24435 let text_style = TextStyle {
24436 color: if self.prompt.read(cx).read_only(cx) {
24437 cx.theme().colors().text_disabled
24438 } else {
24439 cx.theme().colors().text
24440 },
24441 font_family: settings.buffer_font.family.clone(),
24442 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24443 font_size: settings.buffer_font_size(cx).into(),
24444 font_weight: settings.buffer_font.weight,
24445 line_height: relative(settings.buffer_line_height.value()),
24446 ..Default::default()
24447 };
24448 EditorElement::new(
24449 &self.prompt,
24450 EditorStyle {
24451 background: cx.theme().colors().editor_background,
24452 local_player: cx.theme().players().local(),
24453 text: text_style,
24454 ..Default::default()
24455 },
24456 )
24457 }
24458}
24459
24460impl Render for BreakpointPromptEditor {
24461 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24462 let editor_margins = *self.editor_margins.lock();
24463 let gutter_dimensions = editor_margins.gutter;
24464 h_flex()
24465 .key_context("Editor")
24466 .bg(cx.theme().colors().editor_background)
24467 .border_y_1()
24468 .border_color(cx.theme().status().info_border)
24469 .size_full()
24470 .py(window.line_height() / 2.5)
24471 .on_action(cx.listener(Self::confirm))
24472 .on_action(cx.listener(Self::cancel))
24473 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24474 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24475 }
24476}
24477
24478impl Focusable for BreakpointPromptEditor {
24479 fn focus_handle(&self, cx: &App) -> FocusHandle {
24480 self.prompt.focus_handle(cx)
24481 }
24482}
24483
24484fn all_edits_insertions_or_deletions(
24485 edits: &Vec<(Range<Anchor>, String)>,
24486 snapshot: &MultiBufferSnapshot,
24487) -> bool {
24488 let mut all_insertions = true;
24489 let mut all_deletions = true;
24490
24491 for (range, new_text) in edits.iter() {
24492 let range_is_empty = range.to_offset(snapshot).is_empty();
24493 let text_is_empty = new_text.is_empty();
24494
24495 if range_is_empty != text_is_empty {
24496 if range_is_empty {
24497 all_deletions = false;
24498 } else {
24499 all_insertions = false;
24500 }
24501 } else {
24502 return false;
24503 }
24504
24505 if !all_insertions && !all_deletions {
24506 return false;
24507 }
24508 }
24509 all_insertions || all_deletions
24510}
24511
24512struct MissingEditPredictionKeybindingTooltip;
24513
24514impl Render for MissingEditPredictionKeybindingTooltip {
24515 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24516 ui::tooltip_container(cx, |container, cx| {
24517 container
24518 .flex_shrink_0()
24519 .max_w_80()
24520 .min_h(rems_from_px(124.))
24521 .justify_between()
24522 .child(
24523 v_flex()
24524 .flex_1()
24525 .text_ui_sm(cx)
24526 .child(Label::new("Conflict with Accept Keybinding"))
24527 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24528 )
24529 .child(
24530 h_flex()
24531 .pb_1()
24532 .gap_1()
24533 .items_end()
24534 .w_full()
24535 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24536 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24537 }))
24538 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24539 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24540 })),
24541 )
24542 })
24543 }
24544}
24545
24546#[derive(Debug, Clone, Copy, PartialEq)]
24547pub struct LineHighlight {
24548 pub background: Background,
24549 pub border: Option<gpui::Hsla>,
24550 pub include_gutter: bool,
24551 pub type_id: Option<TypeId>,
24552}
24553
24554struct LineManipulationResult {
24555 pub new_text: String,
24556 pub line_count_before: usize,
24557 pub line_count_after: usize,
24558}
24559
24560fn render_diff_hunk_controls(
24561 row: u32,
24562 status: &DiffHunkStatus,
24563 hunk_range: Range<Anchor>,
24564 is_created_file: bool,
24565 line_height: Pixels,
24566 editor: &Entity<Editor>,
24567 _window: &mut Window,
24568 cx: &mut App,
24569) -> AnyElement {
24570 h_flex()
24571 .h(line_height)
24572 .mr_1()
24573 .gap_1()
24574 .px_0p5()
24575 .pb_1()
24576 .border_x_1()
24577 .border_b_1()
24578 .border_color(cx.theme().colors().border_variant)
24579 .rounded_b_lg()
24580 .bg(cx.theme().colors().editor_background)
24581 .gap_1()
24582 .block_mouse_except_scroll()
24583 .shadow_md()
24584 .child(if status.has_secondary_hunk() {
24585 Button::new(("stage", row as u64), "Stage")
24586 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24587 .tooltip({
24588 let focus_handle = editor.focus_handle(cx);
24589 move |_window, cx| {
24590 Tooltip::for_action_in(
24591 "Stage Hunk",
24592 &::git::ToggleStaged,
24593 &focus_handle,
24594 cx,
24595 )
24596 }
24597 })
24598 .on_click({
24599 let editor = editor.clone();
24600 move |_event, _window, cx| {
24601 editor.update(cx, |editor, cx| {
24602 editor.stage_or_unstage_diff_hunks(
24603 true,
24604 vec![hunk_range.start..hunk_range.start],
24605 cx,
24606 );
24607 });
24608 }
24609 })
24610 } else {
24611 Button::new(("unstage", row as u64), "Unstage")
24612 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24613 .tooltip({
24614 let focus_handle = editor.focus_handle(cx);
24615 move |_window, cx| {
24616 Tooltip::for_action_in(
24617 "Unstage Hunk",
24618 &::git::ToggleStaged,
24619 &focus_handle,
24620 cx,
24621 )
24622 }
24623 })
24624 .on_click({
24625 let editor = editor.clone();
24626 move |_event, _window, cx| {
24627 editor.update(cx, |editor, cx| {
24628 editor.stage_or_unstage_diff_hunks(
24629 false,
24630 vec![hunk_range.start..hunk_range.start],
24631 cx,
24632 );
24633 });
24634 }
24635 })
24636 })
24637 .child(
24638 Button::new(("restore", row as u64), "Restore")
24639 .tooltip({
24640 let focus_handle = editor.focus_handle(cx);
24641 move |_window, cx| {
24642 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24643 }
24644 })
24645 .on_click({
24646 let editor = editor.clone();
24647 move |_event, window, cx| {
24648 editor.update(cx, |editor, cx| {
24649 let snapshot = editor.snapshot(window, cx);
24650 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24651 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24652 });
24653 }
24654 })
24655 .disabled(is_created_file),
24656 )
24657 .when(
24658 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24659 |el| {
24660 el.child(
24661 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24662 .shape(IconButtonShape::Square)
24663 .icon_size(IconSize::Small)
24664 // .disabled(!has_multiple_hunks)
24665 .tooltip({
24666 let focus_handle = editor.focus_handle(cx);
24667 move |_window, cx| {
24668 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24669 }
24670 })
24671 .on_click({
24672 let editor = editor.clone();
24673 move |_event, window, cx| {
24674 editor.update(cx, |editor, cx| {
24675 let snapshot = editor.snapshot(window, cx);
24676 let position =
24677 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24678 editor.go_to_hunk_before_or_after_position(
24679 &snapshot,
24680 position,
24681 Direction::Next,
24682 window,
24683 cx,
24684 );
24685 editor.expand_selected_diff_hunks(cx);
24686 });
24687 }
24688 }),
24689 )
24690 .child(
24691 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24692 .shape(IconButtonShape::Square)
24693 .icon_size(IconSize::Small)
24694 // .disabled(!has_multiple_hunks)
24695 .tooltip({
24696 let focus_handle = editor.focus_handle(cx);
24697 move |_window, cx| {
24698 Tooltip::for_action_in(
24699 "Previous Hunk",
24700 &GoToPreviousHunk,
24701 &focus_handle,
24702 cx,
24703 )
24704 }
24705 })
24706 .on_click({
24707 let editor = editor.clone();
24708 move |_event, window, cx| {
24709 editor.update(cx, |editor, cx| {
24710 let snapshot = editor.snapshot(window, cx);
24711 let point =
24712 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24713 editor.go_to_hunk_before_or_after_position(
24714 &snapshot,
24715 point,
24716 Direction::Prev,
24717 window,
24718 cx,
24719 );
24720 editor.expand_selected_diff_hunks(cx);
24721 });
24722 }
24723 }),
24724 )
24725 },
24726 )
24727 .into_any_element()
24728}
24729
24730pub fn multibuffer_context_lines(cx: &App) -> u32 {
24731 EditorSettings::try_get(cx)
24732 .map(|settings| settings.excerpt_context_lines)
24733 .unwrap_or(2)
24734 .min(32)
24735}