1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
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::{AGENT_REPLICA_ID, 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, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
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, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, 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,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 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::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
742type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
743
744#[derive(Default)]
745struct ScrollbarMarkerState {
746 scrollbar_size: Size<Pixels>,
747 dirty: bool,
748 markers: Arc<[PaintQuad]>,
749 pending_refresh: Option<Task<Result<()>>>,
750}
751
752impl ScrollbarMarkerState {
753 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
754 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
755 }
756}
757
758#[derive(Clone, Copy, PartialEq, Eq)]
759pub enum MinimapVisibility {
760 Disabled,
761 Enabled {
762 /// The configuration currently present in the users settings.
763 setting_configuration: bool,
764 /// Whether to override the currently set visibility from the users setting.
765 toggle_override: bool,
766 },
767}
768
769impl MinimapVisibility {
770 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
771 if mode.is_full() {
772 Self::Enabled {
773 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
774 toggle_override: false,
775 }
776 } else {
777 Self::Disabled
778 }
779 }
780
781 fn hidden(&self) -> Self {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => Self::Enabled {
787 setting_configuration,
788 toggle_override: setting_configuration,
789 },
790 Self::Disabled => Self::Disabled,
791 }
792 }
793
794 fn disabled(&self) -> bool {
795 matches!(*self, Self::Disabled)
796 }
797
798 fn settings_visibility(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 ..
803 } => setting_configuration,
804 _ => false,
805 }
806 }
807
808 fn visible(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 toggle_override,
813 } => setting_configuration ^ toggle_override,
814 _ => false,
815 }
816 }
817
818 fn toggle_visibility(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 toggle_override,
822 setting_configuration,
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: !toggle_override,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 inlay_hint_cache: InlayHintCache,
1127 next_inlay_id: u32,
1128 next_color_inlay_id: u32,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 serialize_dirty_buffers: bool,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 refresh_colors_task: Task<()>,
1194 folding_newlines: Task<()>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1199enum NextScrollCursorCenterTopBottom {
1200 #[default]
1201 Center,
1202 Top,
1203 Bottom,
1204}
1205
1206impl NextScrollCursorCenterTopBottom {
1207 fn next(&self) -> Self {
1208 match self {
1209 Self::Center => Self::Top,
1210 Self::Top => Self::Bottom,
1211 Self::Bottom => Self::Center,
1212 }
1213 }
1214}
1215
1216#[derive(Clone)]
1217pub struct EditorSnapshot {
1218 pub mode: EditorMode,
1219 show_gutter: bool,
1220 show_line_numbers: Option<bool>,
1221 show_git_diff_gutter: Option<bool>,
1222 show_code_actions: Option<bool>,
1223 show_runnables: Option<bool>,
1224 show_breakpoints: Option<bool>,
1225 git_blame_gutter_max_author_length: Option<usize>,
1226 pub display_snapshot: DisplaySnapshot,
1227 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1228 is_focused: bool,
1229 scroll_anchor: ScrollAnchor,
1230 ongoing_scroll: OngoingScroll,
1231 current_line_highlight: CurrentLineHighlight,
1232 gutter_hovered: bool,
1233}
1234
1235#[derive(Default, Debug, Clone, Copy)]
1236pub struct GutterDimensions {
1237 pub left_padding: Pixels,
1238 pub right_padding: Pixels,
1239 pub width: Pixels,
1240 pub margin: Pixels,
1241 pub git_blame_entries_width: Option<Pixels>,
1242}
1243
1244impl GutterDimensions {
1245 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1246 Self {
1247 margin: Self::default_gutter_margin(font_id, font_size, cx),
1248 ..Default::default()
1249 }
1250 }
1251
1252 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1253 -cx.text_system().descent(font_id, font_size)
1254 }
1255 /// The full width of the space taken up by the gutter.
1256 pub fn full_width(&self) -> Pixels {
1257 self.margin + self.width
1258 }
1259
1260 /// The width of the space reserved for the fold indicators,
1261 /// use alongside 'justify_end' and `gutter_width` to
1262 /// right align content with the line numbers
1263 pub fn fold_area_width(&self) -> Pixels {
1264 self.margin + self.right_padding
1265 }
1266}
1267
1268struct CharacterDimensions {
1269 em_width: Pixels,
1270 em_advance: Pixels,
1271 line_height: Pixels,
1272}
1273
1274#[derive(Debug)]
1275pub struct RemoteSelection {
1276 pub replica_id: ReplicaId,
1277 pub selection: Selection<Anchor>,
1278 pub cursor_shape: CursorShape,
1279 pub collaborator_id: CollaboratorId,
1280 pub line_mode: bool,
1281 pub user_name: Option<SharedString>,
1282 pub color: PlayerColor,
1283}
1284
1285#[derive(Clone, Debug)]
1286struct SelectionHistoryEntry {
1287 selections: Arc<[Selection<Anchor>]>,
1288 select_next_state: Option<SelectNextState>,
1289 select_prev_state: Option<SelectNextState>,
1290 add_selections_state: Option<AddSelectionsState>,
1291}
1292
1293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1294enum SelectionHistoryMode {
1295 Normal,
1296 Undoing,
1297 Redoing,
1298 Skipping,
1299}
1300
1301#[derive(Clone, PartialEq, Eq, Hash)]
1302struct HoveredCursor {
1303 replica_id: u16,
1304 selection_id: usize,
1305}
1306
1307impl Default for SelectionHistoryMode {
1308 fn default() -> Self {
1309 Self::Normal
1310 }
1311}
1312
1313#[derive(Debug)]
1314/// SelectionEffects controls the side-effects of updating the selection.
1315///
1316/// The default behaviour does "what you mostly want":
1317/// - it pushes to the nav history if the cursor moved by >10 lines
1318/// - it re-triggers completion requests
1319/// - it scrolls to fit
1320///
1321/// You might want to modify these behaviours. For example when doing a "jump"
1322/// like go to definition, we always want to add to nav history; but when scrolling
1323/// in vim mode we never do.
1324///
1325/// Similarly, you might want to disable scrolling if you don't want the viewport to
1326/// move.
1327#[derive(Clone)]
1328pub struct SelectionEffects {
1329 nav_history: Option<bool>,
1330 completions: bool,
1331 scroll: Option<Autoscroll>,
1332}
1333
1334impl Default for SelectionEffects {
1335 fn default() -> Self {
1336 Self {
1337 nav_history: None,
1338 completions: true,
1339 scroll: Some(Autoscroll::fit()),
1340 }
1341 }
1342}
1343impl SelectionEffects {
1344 pub fn scroll(scroll: Autoscroll) -> Self {
1345 Self {
1346 scroll: Some(scroll),
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn no_scroll() -> Self {
1352 Self {
1353 scroll: None,
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn completions(self, completions: bool) -> Self {
1359 Self {
1360 completions,
1361 ..self
1362 }
1363 }
1364
1365 pub fn nav_history(self, nav_history: bool) -> Self {
1366 Self {
1367 nav_history: Some(nav_history),
1368 ..self
1369 }
1370 }
1371}
1372
1373struct DeferredSelectionEffectsState {
1374 changed: bool,
1375 effects: SelectionEffects,
1376 old_cursor_position: Anchor,
1377 history_entry: SelectionHistoryEntry,
1378}
1379
1380#[derive(Default)]
1381struct SelectionHistory {
1382 #[allow(clippy::type_complexity)]
1383 selections_by_transaction:
1384 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1385 mode: SelectionHistoryMode,
1386 undo_stack: VecDeque<SelectionHistoryEntry>,
1387 redo_stack: VecDeque<SelectionHistoryEntry>,
1388}
1389
1390impl SelectionHistory {
1391 #[track_caller]
1392 fn insert_transaction(
1393 &mut self,
1394 transaction_id: TransactionId,
1395 selections: Arc<[Selection<Anchor>]>,
1396 ) {
1397 if selections.is_empty() {
1398 log::error!(
1399 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1400 std::panic::Location::caller()
1401 );
1402 return;
1403 }
1404 self.selections_by_transaction
1405 .insert(transaction_id, (selections, None));
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction(
1410 &self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get(&transaction_id)
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction_mut(
1418 &mut self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get_mut(&transaction_id)
1422 }
1423
1424 fn push(&mut self, entry: SelectionHistoryEntry) {
1425 if !entry.selections.is_empty() {
1426 match self.mode {
1427 SelectionHistoryMode::Normal => {
1428 self.push_undo(entry);
1429 self.redo_stack.clear();
1430 }
1431 SelectionHistoryMode::Undoing => self.push_redo(entry),
1432 SelectionHistoryMode::Redoing => self.push_undo(entry),
1433 SelectionHistoryMode::Skipping => {}
1434 }
1435 }
1436 }
1437
1438 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .undo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.undo_stack.push_back(entry);
1445 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.undo_stack.pop_front();
1447 }
1448 }
1449 }
1450
1451 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1452 if self
1453 .redo_stack
1454 .back()
1455 .is_none_or(|e| e.selections != entry.selections)
1456 {
1457 self.redo_stack.push_back(entry);
1458 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1459 self.redo_stack.pop_front();
1460 }
1461 }
1462 }
1463}
1464
1465#[derive(Clone, Copy)]
1466pub struct RowHighlightOptions {
1467 pub autoscroll: bool,
1468 pub include_gutter: bool,
1469}
1470
1471impl Default for RowHighlightOptions {
1472 fn default() -> Self {
1473 Self {
1474 autoscroll: Default::default(),
1475 include_gutter: true,
1476 }
1477 }
1478}
1479
1480struct RowHighlight {
1481 index: usize,
1482 range: Range<Anchor>,
1483 color: Hsla,
1484 options: RowHighlightOptions,
1485 type_id: TypeId,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsState {
1490 groups: Vec<AddSelectionsGroup>,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsGroup {
1495 above: bool,
1496 stack: Vec<usize>,
1497}
1498
1499#[derive(Clone)]
1500struct SelectNextState {
1501 query: AhoCorasick,
1502 wordwise: bool,
1503 done: bool,
1504}
1505
1506impl std::fmt::Debug for SelectNextState {
1507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508 f.debug_struct(std::any::type_name::<Self>())
1509 .field("wordwise", &self.wordwise)
1510 .field("done", &self.done)
1511 .finish()
1512 }
1513}
1514
1515#[derive(Debug)]
1516struct AutocloseRegion {
1517 selection_id: usize,
1518 range: Range<Anchor>,
1519 pair: BracketPair,
1520}
1521
1522#[derive(Debug)]
1523struct SnippetState {
1524 ranges: Vec<Vec<Range<Anchor>>>,
1525 active_index: usize,
1526 choices: Vec<Option<Vec<String>>>,
1527}
1528
1529#[doc(hidden)]
1530pub struct RenameState {
1531 pub range: Range<Anchor>,
1532 pub old_name: Arc<str>,
1533 pub editor: Entity<Editor>,
1534 block_id: CustomBlockId,
1535}
1536
1537struct InvalidationStack<T>(Vec<T>);
1538
1539struct RegisteredEditPredictionProvider {
1540 provider: Arc<dyn EditPredictionProviderHandle>,
1541 _subscription: Subscription,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545pub struct ActiveDiagnosticGroup {
1546 pub active_range: Range<Anchor>,
1547 pub active_message: String,
1548 pub group_id: usize,
1549 pub blocks: HashSet<CustomBlockId>,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553
1554pub(crate) enum ActiveDiagnostic {
1555 None,
1556 All,
1557 Group(ActiveDiagnosticGroup),
1558}
1559
1560#[derive(Serialize, Deserialize, Clone, Debug)]
1561pub struct ClipboardSelection {
1562 /// The number of bytes in this selection.
1563 pub len: usize,
1564 /// Whether this was a full-line selection.
1565 pub is_entire_line: bool,
1566 /// The indentation of the first line when this content was originally copied.
1567 pub first_line_indent: u32,
1568}
1569
1570// selections, scroll behavior, was newest selection reversed
1571type SelectSyntaxNodeHistoryState = (
1572 Box<[Selection<usize>]>,
1573 SelectSyntaxNodeScrollBehavior,
1574 bool,
1575);
1576
1577#[derive(Default)]
1578struct SelectSyntaxNodeHistory {
1579 stack: Vec<SelectSyntaxNodeHistoryState>,
1580 // disable temporarily to allow changing selections without losing the stack
1581 pub disable_clearing: bool,
1582}
1583
1584impl SelectSyntaxNodeHistory {
1585 pub fn try_clear(&mut self) {
1586 if !self.disable_clearing {
1587 self.stack.clear();
1588 }
1589 }
1590
1591 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1592 self.stack.push(selection);
1593 }
1594
1595 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1596 self.stack.pop()
1597 }
1598}
1599
1600enum SelectSyntaxNodeScrollBehavior {
1601 CursorTop,
1602 FitSelection,
1603 CursorBottom,
1604}
1605
1606#[derive(Debug)]
1607pub(crate) struct NavigationData {
1608 cursor_anchor: Anchor,
1609 cursor_position: Point,
1610 scroll_anchor: ScrollAnchor,
1611 scroll_top_row: u32,
1612}
1613
1614#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1615pub enum GotoDefinitionKind {
1616 Symbol,
1617 Declaration,
1618 Type,
1619 Implementation,
1620}
1621
1622#[derive(Debug, Clone)]
1623enum InlayHintRefreshReason {
1624 ModifiersChanged(bool),
1625 Toggle(bool),
1626 SettingsChange(InlayHintSettings),
1627 NewLinesShown,
1628 BufferEdited(HashSet<Arc<Language>>),
1629 RefreshRequested,
1630 ExcerptsRemoved(Vec<ExcerptId>),
1631}
1632
1633impl InlayHintRefreshReason {
1634 fn description(&self) -> &'static str {
1635 match self {
1636 Self::ModifiersChanged(_) => "modifiers changed",
1637 Self::Toggle(_) => "toggle",
1638 Self::SettingsChange(_) => "settings change",
1639 Self::NewLinesShown => "new lines shown",
1640 Self::BufferEdited(_) => "buffer edited",
1641 Self::RefreshRequested => "refresh requested",
1642 Self::ExcerptsRemoved(_) => "excerpts removed",
1643 }
1644 }
1645}
1646
1647pub enum FormatTarget {
1648 Buffers(HashSet<Entity<Buffer>>),
1649 Ranges(Vec<Range<MultiBufferPoint>>),
1650}
1651
1652pub(crate) struct FocusedBlock {
1653 id: BlockId,
1654 focus_handle: WeakFocusHandle,
1655}
1656
1657#[derive(Clone)]
1658enum JumpData {
1659 MultiBufferRow {
1660 row: MultiBufferRow,
1661 line_offset_from_top: u32,
1662 },
1663 MultiBufferPoint {
1664 excerpt_id: ExcerptId,
1665 position: Point,
1666 anchor: text::Anchor,
1667 line_offset_from_top: u32,
1668 },
1669}
1670
1671pub enum MultibufferSelectionMode {
1672 First,
1673 All,
1674}
1675
1676#[derive(Clone, Copy, Debug, Default)]
1677pub struct RewrapOptions {
1678 pub override_language_settings: bool,
1679 pub preserve_existing_whitespace: bool,
1680}
1681
1682impl Editor {
1683 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1687 }
1688
1689 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1690 let buffer = cx.new(|cx| Buffer::local("", cx));
1691 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1692 Self::new(EditorMode::full(), buffer, None, window, cx)
1693 }
1694
1695 pub fn auto_height(
1696 min_lines: usize,
1697 max_lines: usize,
1698 window: &mut Window,
1699 cx: &mut Context<Self>,
1700 ) -> Self {
1701 let buffer = cx.new(|cx| Buffer::local("", cx));
1702 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1703 Self::new(
1704 EditorMode::AutoHeight {
1705 min_lines,
1706 max_lines: Some(max_lines),
1707 },
1708 buffer,
1709 None,
1710 window,
1711 cx,
1712 )
1713 }
1714
1715 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1716 /// The editor grows as tall as needed to fit its content.
1717 pub fn auto_height_unbounded(
1718 min_lines: usize,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| Buffer::local("", cx));
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(
1725 EditorMode::AutoHeight {
1726 min_lines,
1727 max_lines: None,
1728 },
1729 buffer,
1730 None,
1731 window,
1732 cx,
1733 )
1734 }
1735
1736 pub fn for_buffer(
1737 buffer: Entity<Buffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1743 Self::new(EditorMode::full(), buffer, project, window, cx)
1744 }
1745
1746 pub fn for_multibuffer(
1747 buffer: Entity<MultiBuffer>,
1748 project: Option<Entity<Project>>,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 Self::new(EditorMode::full(), buffer, project, window, cx)
1753 }
1754
1755 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1756 let mut clone = Self::new(
1757 self.mode.clone(),
1758 self.buffer.clone(),
1759 self.project.clone(),
1760 window,
1761 cx,
1762 );
1763 self.display_map.update(cx, |display_map, cx| {
1764 let snapshot = display_map.snapshot(cx);
1765 clone.display_map.update(cx, |display_map, cx| {
1766 display_map.set_state(&snapshot, cx);
1767 });
1768 });
1769 clone.folds_did_change(cx);
1770 clone.selections.clone_state(&self.selections);
1771 clone.scroll_manager.clone_state(&self.scroll_manager);
1772 clone.searchable = self.searchable;
1773 clone.read_only = self.read_only;
1774 clone
1775 }
1776
1777 pub fn new(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 Editor::new_internal(mode, buffer, project, None, window, cx)
1785 }
1786
1787 fn new_internal(
1788 mode: EditorMode,
1789 buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 display_map: Option<Entity<DisplayMap>>,
1792 window: &mut Window,
1793 cx: &mut Context<Self>,
1794 ) -> Self {
1795 debug_assert!(
1796 display_map.is_none() || mode.is_minimap(),
1797 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1798 );
1799
1800 let full_mode = mode.is_full();
1801 let is_minimap = mode.is_minimap();
1802 let diagnostics_max_severity = if full_mode {
1803 EditorSettings::get_global(cx)
1804 .diagnostics_max_severity
1805 .unwrap_or(DiagnosticSeverity::Hint)
1806 } else {
1807 DiagnosticSeverity::Off
1808 };
1809 let style = window.text_style();
1810 let font_size = style.font_size.to_pixels(window.rem_size());
1811 let editor = cx.entity().downgrade();
1812 let fold_placeholder = FoldPlaceholder {
1813 constrain_width: false,
1814 render: Arc::new(move |fold_id, fold_range, cx| {
1815 let editor = editor.clone();
1816 div()
1817 .id(fold_id)
1818 .bg(cx.theme().colors().ghost_element_background)
1819 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1820 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1821 .rounded_xs()
1822 .size_full()
1823 .cursor_pointer()
1824 .child("⋯")
1825 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1826 .on_click(move |_, _window, cx| {
1827 editor
1828 .update(cx, |editor, cx| {
1829 editor.unfold_ranges(
1830 &[fold_range.start..fold_range.end],
1831 true,
1832 false,
1833 cx,
1834 );
1835 cx.stop_propagation();
1836 })
1837 .ok();
1838 })
1839 .into_any()
1840 }),
1841 merge_adjacent: true,
1842 ..FoldPlaceholder::default()
1843 };
1844 let display_map = display_map.unwrap_or_else(|| {
1845 cx.new(|cx| {
1846 DisplayMap::new(
1847 buffer.clone(),
1848 style.font(),
1849 font_size,
1850 None,
1851 FILE_HEADER_HEIGHT,
1852 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1853 fold_placeholder,
1854 diagnostics_max_severity,
1855 cx,
1856 )
1857 })
1858 });
1859
1860 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1861
1862 let blink_manager = cx.new(|cx| {
1863 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1864 if is_minimap {
1865 blink_manager.disable(cx);
1866 }
1867 blink_manager
1868 });
1869
1870 let soft_wrap_mode_override =
1871 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1872
1873 let mut project_subscriptions = Vec::new();
1874 if full_mode && let Some(project) = project.as_ref() {
1875 project_subscriptions.push(cx.subscribe_in(
1876 project,
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 project::Event::RefreshCodeLens => {
1880 // we always query lens with actions, without storing them, always refreshing them
1881 }
1882 project::Event::RefreshInlayHints => {
1883 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1884 }
1885 project::Event::LanguageServerAdded(..)
1886 | project::Event::LanguageServerRemoved(..) => {
1887 if editor.tasks_update_task.is_none() {
1888 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1889 }
1890 }
1891 project::Event::SnippetEdit(id, snippet_edits) => {
1892 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1893 let focus_handle = editor.focus_handle(cx);
1894 if focus_handle.is_focused(window) {
1895 let snapshot = buffer.read(cx).snapshot();
1896 for (range, snippet) in snippet_edits {
1897 let editor_range =
1898 language::range_from_lsp(*range).to_offset(&snapshot);
1899 editor
1900 .insert_snippet(
1901 &[editor_range],
1902 snippet.clone(),
1903 window,
1904 cx,
1905 )
1906 .ok();
1907 }
1908 }
1909 }
1910 }
1911 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1912 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1913 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1914 }
1915 }
1916
1917 project::Event::EntryRenamed(transaction) => {
1918 let Some(workspace) = editor.workspace() else {
1919 return;
1920 };
1921 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1922 else {
1923 return;
1924 };
1925 if active_editor.entity_id() == cx.entity_id() {
1926 let edited_buffers_already_open = {
1927 let other_editors: Vec<Entity<Editor>> = workspace
1928 .read(cx)
1929 .panes()
1930 .iter()
1931 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1932 .filter(|editor| editor.entity_id() != cx.entity_id())
1933 .collect();
1934
1935 transaction.0.keys().all(|buffer| {
1936 other_editors.iter().any(|editor| {
1937 let multi_buffer = editor.read(cx).buffer();
1938 multi_buffer.read(cx).is_singleton()
1939 && multi_buffer.read(cx).as_singleton().map_or(
1940 false,
1941 |singleton| {
1942 singleton.entity_id() == buffer.entity_id()
1943 },
1944 )
1945 })
1946 })
1947 };
1948
1949 if !edited_buffers_already_open {
1950 let workspace = workspace.downgrade();
1951 let transaction = transaction.clone();
1952 cx.defer_in(window, move |_, window, cx| {
1953 cx.spawn_in(window, async move |editor, cx| {
1954 Self::open_project_transaction(
1955 &editor,
1956 workspace,
1957 transaction,
1958 "Rename".to_string(),
1959 cx,
1960 )
1961 .await
1962 .ok()
1963 })
1964 .detach();
1965 });
1966 }
1967 }
1968 }
1969
1970 _ => {}
1971 },
1972 ));
1973 if let Some(task_inventory) = project
1974 .read(cx)
1975 .task_store()
1976 .read(cx)
1977 .task_inventory()
1978 .cloned()
1979 {
1980 project_subscriptions.push(cx.observe_in(
1981 &task_inventory,
1982 window,
1983 |editor, _, window, cx| {
1984 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1985 },
1986 ));
1987 };
1988
1989 project_subscriptions.push(cx.subscribe_in(
1990 &project.read(cx).breakpoint_store(),
1991 window,
1992 |editor, _, event, window, cx| match event {
1993 BreakpointStoreEvent::ClearDebugLines => {
1994 editor.clear_row_highlights::<ActiveDebugLine>();
1995 editor.refresh_inline_values(cx);
1996 }
1997 BreakpointStoreEvent::SetDebugLine => {
1998 if editor.go_to_active_debug_line(window, cx) {
1999 cx.stop_propagation();
2000 }
2001
2002 editor.refresh_inline_values(cx);
2003 }
2004 _ => {}
2005 },
2006 ));
2007 let git_store = project.read(cx).git_store().clone();
2008 let project = project.clone();
2009 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2010 if let GitStoreEvent::RepositoryUpdated(
2011 _,
2012 RepositoryEvent::Updated {
2013 new_instance: true, ..
2014 },
2015 _,
2016 ) = event
2017 {
2018 this.load_diff_task = Some(
2019 update_uncommitted_diff_for_buffer(
2020 cx.entity(),
2021 &project,
2022 this.buffer.read(cx).all_buffers(),
2023 this.buffer.clone(),
2024 cx,
2025 )
2026 .shared(),
2027 );
2028 }
2029 }));
2030 }
2031
2032 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2033
2034 let inlay_hint_settings =
2035 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2036 let focus_handle = cx.focus_handle();
2037 if !is_minimap {
2038 cx.on_focus(&focus_handle, window, Self::handle_focus)
2039 .detach();
2040 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2041 .detach();
2042 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2043 .detach();
2044 cx.on_blur(&focus_handle, window, Self::handle_blur)
2045 .detach();
2046 cx.observe_pending_input(window, Self::observe_pending_input)
2047 .detach();
2048 }
2049
2050 let show_indent_guides =
2051 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2052 Some(false)
2053 } else {
2054 None
2055 };
2056
2057 let breakpoint_store = match (&mode, project.as_ref()) {
2058 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2059 _ => None,
2060 };
2061
2062 let mut code_action_providers = Vec::new();
2063 let mut load_uncommitted_diff = None;
2064 if let Some(project) = project.clone() {
2065 load_uncommitted_diff = Some(
2066 update_uncommitted_diff_for_buffer(
2067 cx.entity(),
2068 &project,
2069 buffer.read(cx).all_buffers(),
2070 buffer.clone(),
2071 cx,
2072 )
2073 .shared(),
2074 );
2075 code_action_providers.push(Rc::new(project) as Rc<_>);
2076 }
2077
2078 let mut editor = Self {
2079 focus_handle,
2080 show_cursor_when_unfocused: false,
2081 last_focused_descendant: None,
2082 buffer: buffer.clone(),
2083 display_map: display_map.clone(),
2084 placeholder_display_map: None,
2085 selections,
2086 scroll_manager: ScrollManager::new(cx),
2087 columnar_selection_state: None,
2088 add_selections_state: None,
2089 select_next_state: None,
2090 select_prev_state: None,
2091 selection_history: SelectionHistory::default(),
2092 defer_selection_effects: false,
2093 deferred_selection_effects_state: None,
2094 autoclose_regions: Vec::new(),
2095 snippet_stack: InvalidationStack::default(),
2096 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2097 ime_transaction: None,
2098 active_diagnostics: ActiveDiagnostic::None,
2099 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2100 inline_diagnostics_update: Task::ready(()),
2101 inline_diagnostics: Vec::new(),
2102 soft_wrap_mode_override,
2103 diagnostics_max_severity,
2104 hard_wrap: None,
2105 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2106 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2107 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2108 project,
2109 blink_manager: blink_manager.clone(),
2110 show_local_selections: true,
2111 show_scrollbars: ScrollbarAxes {
2112 horizontal: full_mode,
2113 vertical: full_mode,
2114 },
2115 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2116 offset_content: !matches!(mode, EditorMode::SingleLine),
2117 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2118 show_gutter: full_mode,
2119 show_line_numbers: (!full_mode).then_some(false),
2120 use_relative_line_numbers: None,
2121 disable_expand_excerpt_buttons: !full_mode,
2122 show_git_diff_gutter: None,
2123 show_code_actions: None,
2124 show_runnables: None,
2125 show_breakpoints: None,
2126 show_wrap_guides: None,
2127 show_indent_guides,
2128 highlight_order: 0,
2129 highlighted_rows: HashMap::default(),
2130 background_highlights: HashMap::default(),
2131 gutter_highlights: HashMap::default(),
2132 scrollbar_marker_state: ScrollbarMarkerState::default(),
2133 active_indent_guides_state: ActiveIndentGuidesState::default(),
2134 nav_history: None,
2135 context_menu: RefCell::new(None),
2136 context_menu_options: None,
2137 mouse_context_menu: None,
2138 completion_tasks: Vec::new(),
2139 inline_blame_popover: None,
2140 inline_blame_popover_show_task: None,
2141 signature_help_state: SignatureHelpState::default(),
2142 auto_signature_help: None,
2143 find_all_references_task_sources: Vec::new(),
2144 next_completion_id: 0,
2145 next_inlay_id: 0,
2146 code_action_providers,
2147 available_code_actions: None,
2148 code_actions_task: None,
2149 quick_selection_highlight_task: None,
2150 debounced_selection_highlight_task: None,
2151 document_highlights_task: None,
2152 linked_editing_range_task: None,
2153 pending_rename: None,
2154 searchable: !is_minimap,
2155 cursor_shape: EditorSettings::get_global(cx)
2156 .cursor_shape
2157 .unwrap_or_default(),
2158 current_line_highlight: None,
2159 autoindent_mode: Some(AutoindentMode::EachLine),
2160 collapse_matches: false,
2161 workspace: None,
2162 input_enabled: !is_minimap,
2163 use_modal_editing: full_mode,
2164 read_only: is_minimap,
2165 use_autoclose: true,
2166 use_auto_surround: true,
2167 auto_replace_emoji_shortcode: false,
2168 jsx_tag_auto_close_enabled_in_any_buffer: false,
2169 leader_id: None,
2170 remote_id: None,
2171 hover_state: HoverState::default(),
2172 pending_mouse_down: None,
2173 hovered_link_state: None,
2174 edit_prediction_provider: None,
2175 active_edit_prediction: None,
2176 stale_edit_prediction_in_menu: None,
2177 edit_prediction_preview: EditPredictionPreview::Inactive {
2178 released_too_fast: false,
2179 },
2180 inline_diagnostics_enabled: full_mode,
2181 diagnostics_enabled: full_mode,
2182 word_completions_enabled: full_mode,
2183 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2184 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2185 gutter_hovered: false,
2186 pixel_position_of_newest_cursor: None,
2187 last_bounds: None,
2188 last_position_map: None,
2189 expect_bounds_change: None,
2190 gutter_dimensions: GutterDimensions::default(),
2191 style: None,
2192 show_cursor_names: false,
2193 hovered_cursors: HashMap::default(),
2194 next_editor_action_id: EditorActionId::default(),
2195 editor_actions: Rc::default(),
2196 edit_predictions_hidden_for_vim_mode: false,
2197 show_edit_predictions_override: None,
2198 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2199 edit_prediction_settings: EditPredictionSettings::Disabled,
2200 edit_prediction_indent_conflict: false,
2201 edit_prediction_requires_modifier_in_indent_conflict: true,
2202 custom_context_menu: None,
2203 show_git_blame_gutter: false,
2204 show_git_blame_inline: false,
2205 show_selection_menu: None,
2206 show_git_blame_inline_delay_task: None,
2207 git_blame_inline_enabled: full_mode
2208 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2209 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2210 serialize_dirty_buffers: !is_minimap
2211 && ProjectSettings::get_global(cx)
2212 .session
2213 .restore_unsaved_buffers,
2214 blame: None,
2215 blame_subscription: None,
2216 tasks: BTreeMap::default(),
2217
2218 breakpoint_store,
2219 gutter_breakpoint_indicator: (None, None),
2220 hovered_diff_hunk_row: None,
2221 _subscriptions: (!is_minimap)
2222 .then(|| {
2223 vec![
2224 cx.observe(&buffer, Self::on_buffer_changed),
2225 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2226 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2227 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2228 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2229 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2230 cx.observe_window_activation(window, |editor, window, cx| {
2231 let active = window.is_window_active();
2232 editor.blink_manager.update(cx, |blink_manager, cx| {
2233 if active {
2234 blink_manager.enable(cx);
2235 } else {
2236 blink_manager.disable(cx);
2237 }
2238 });
2239 if active {
2240 editor.show_mouse_cursor(cx);
2241 }
2242 }),
2243 ]
2244 })
2245 .unwrap_or_default(),
2246 tasks_update_task: None,
2247 pull_diagnostics_task: Task::ready(()),
2248 colors: None,
2249 refresh_colors_task: Task::ready(()),
2250 next_color_inlay_id: 0,
2251 linked_edit_ranges: Default::default(),
2252 in_project_search: false,
2253 previous_search_ranges: None,
2254 breadcrumb_header: None,
2255 focused_block: None,
2256 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2257 addons: HashMap::default(),
2258 registered_buffers: HashMap::default(),
2259 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2260 selection_mark_mode: false,
2261 toggle_fold_multiple_buffers: Task::ready(()),
2262 serialize_selections: Task::ready(()),
2263 serialize_folds: Task::ready(()),
2264 text_style_refinement: None,
2265 load_diff_task: load_uncommitted_diff,
2266 temporary_diff_override: false,
2267 mouse_cursor_hidden: false,
2268 minimap: None,
2269 hide_mouse_mode: EditorSettings::get_global(cx)
2270 .hide_mouse
2271 .unwrap_or_default(),
2272 change_list: ChangeList::new(),
2273 mode,
2274 selection_drag_state: SelectionDragState::None,
2275 folding_newlines: Task::ready(()),
2276 lookup_key: None,
2277 };
2278
2279 if is_minimap {
2280 return editor;
2281 }
2282
2283 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2284 editor
2285 ._subscriptions
2286 .push(cx.observe(breakpoints, |_, _, cx| {
2287 cx.notify();
2288 }));
2289 }
2290 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2291 editor._subscriptions.extend(project_subscriptions);
2292
2293 editor._subscriptions.push(cx.subscribe_in(
2294 &cx.entity(),
2295 window,
2296 |editor, _, e: &EditorEvent, window, cx| match e {
2297 EditorEvent::ScrollPositionChanged { local, .. } => {
2298 if *local {
2299 let new_anchor = editor.scroll_manager.anchor();
2300 let snapshot = editor.snapshot(window, cx);
2301 editor.update_restoration_data(cx, move |data| {
2302 data.scroll_position = (
2303 new_anchor.top_row(snapshot.buffer_snapshot()),
2304 new_anchor.offset,
2305 );
2306 });
2307 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2308 editor.inline_blame_popover.take();
2309 }
2310 }
2311 EditorEvent::Edited { .. } => {
2312 if !vim_enabled(cx) {
2313 let (map, selections) = editor.selections.all_adjusted_display(cx);
2314 let pop_state = editor
2315 .change_list
2316 .last()
2317 .map(|previous| {
2318 previous.len() == selections.len()
2319 && previous.iter().enumerate().all(|(ix, p)| {
2320 p.to_display_point(&map).row()
2321 == selections[ix].head().row()
2322 })
2323 })
2324 .unwrap_or(false);
2325 let new_positions = selections
2326 .into_iter()
2327 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2328 .collect();
2329 editor
2330 .change_list
2331 .push_to_change_list(pop_state, new_positions);
2332 }
2333 }
2334 _ => (),
2335 },
2336 ));
2337
2338 if let Some(dap_store) = editor
2339 .project
2340 .as_ref()
2341 .map(|project| project.read(cx).dap_store())
2342 {
2343 let weak_editor = cx.weak_entity();
2344
2345 editor
2346 ._subscriptions
2347 .push(
2348 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2349 let session_entity = cx.entity();
2350 weak_editor
2351 .update(cx, |editor, cx| {
2352 editor._subscriptions.push(
2353 cx.subscribe(&session_entity, Self::on_debug_session_event),
2354 );
2355 })
2356 .ok();
2357 }),
2358 );
2359
2360 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2361 editor
2362 ._subscriptions
2363 .push(cx.subscribe(&session, Self::on_debug_session_event));
2364 }
2365 }
2366
2367 // skip adding the initial selection to selection history
2368 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2369 editor.end_selection(window, cx);
2370 editor.selection_history.mode = SelectionHistoryMode::Normal;
2371
2372 editor.scroll_manager.show_scrollbars(window, cx);
2373 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2374
2375 if full_mode {
2376 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2377 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2378
2379 if editor.git_blame_inline_enabled {
2380 editor.start_git_blame_inline(false, window, cx);
2381 }
2382
2383 editor.go_to_active_debug_line(window, cx);
2384
2385 if let Some(buffer) = buffer.read(cx).as_singleton()
2386 && let Some(project) = editor.project()
2387 {
2388 let handle = project.update(cx, |project, cx| {
2389 project.register_buffer_with_language_servers(&buffer, cx)
2390 });
2391 editor
2392 .registered_buffers
2393 .insert(buffer.read(cx).remote_id(), handle);
2394 }
2395
2396 editor.minimap =
2397 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2398 editor.colors = Some(LspColorData::new(cx));
2399 editor.update_lsp_data(false, None, window, cx);
2400 }
2401
2402 if editor.mode.is_full() {
2403 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2404 }
2405
2406 editor
2407 }
2408
2409 pub fn deploy_mouse_context_menu(
2410 &mut self,
2411 position: gpui::Point<Pixels>,
2412 context_menu: Entity<ContextMenu>,
2413 window: &mut Window,
2414 cx: &mut Context<Self>,
2415 ) {
2416 self.mouse_context_menu = Some(MouseContextMenu::new(
2417 self,
2418 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2419 context_menu,
2420 window,
2421 cx,
2422 ));
2423 }
2424
2425 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2426 self.mouse_context_menu
2427 .as_ref()
2428 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2429 }
2430
2431 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2432 if self
2433 .selections
2434 .pending_anchor()
2435 .is_some_and(|pending_selection| {
2436 let snapshot = self.buffer().read(cx).snapshot(cx);
2437 pending_selection.range().includes(range, &snapshot)
2438 })
2439 {
2440 return true;
2441 }
2442
2443 self.selections
2444 .disjoint_in_range::<usize>(range.clone(), cx)
2445 .into_iter()
2446 .any(|selection| {
2447 // This is needed to cover a corner case, if we just check for an existing
2448 // selection in the fold range, having a cursor at the start of the fold
2449 // marks it as selected. Non-empty selections don't cause this.
2450 let length = selection.end - selection.start;
2451 length > 0
2452 })
2453 }
2454
2455 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2456 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2457 }
2458
2459 fn key_context_internal(
2460 &self,
2461 has_active_edit_prediction: bool,
2462 window: &Window,
2463 cx: &App,
2464 ) -> KeyContext {
2465 let mut key_context = KeyContext::new_with_defaults();
2466 key_context.add("Editor");
2467 let mode = match self.mode {
2468 EditorMode::SingleLine => "single_line",
2469 EditorMode::AutoHeight { .. } => "auto_height",
2470 EditorMode::Minimap { .. } => "minimap",
2471 EditorMode::Full { .. } => "full",
2472 };
2473
2474 if EditorSettings::jupyter_enabled(cx) {
2475 key_context.add("jupyter");
2476 }
2477
2478 key_context.set("mode", mode);
2479 if self.pending_rename.is_some() {
2480 key_context.add("renaming");
2481 }
2482
2483 match self.context_menu.borrow().as_ref() {
2484 Some(CodeContextMenu::Completions(menu)) => {
2485 if menu.visible() {
2486 key_context.add("menu");
2487 key_context.add("showing_completions");
2488 }
2489 }
2490 Some(CodeContextMenu::CodeActions(menu)) => {
2491 if menu.visible() {
2492 key_context.add("menu");
2493 key_context.add("showing_code_actions")
2494 }
2495 }
2496 None => {}
2497 }
2498
2499 if self.signature_help_state.has_multiple_signatures() {
2500 key_context.add("showing_signature_help");
2501 }
2502
2503 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2504 if !self.focus_handle(cx).contains_focused(window, cx)
2505 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2506 {
2507 for addon in self.addons.values() {
2508 addon.extend_key_context(&mut key_context, cx)
2509 }
2510 }
2511
2512 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2513 if let Some(extension) = singleton_buffer
2514 .read(cx)
2515 .file()
2516 .and_then(|file| file.path().extension())
2517 {
2518 key_context.set("extension", extension.to_string());
2519 }
2520 } else {
2521 key_context.add("multibuffer");
2522 }
2523
2524 if has_active_edit_prediction {
2525 if self.edit_prediction_in_conflict() {
2526 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2527 } else {
2528 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2529 key_context.add("copilot_suggestion");
2530 }
2531 }
2532
2533 if self.selection_mark_mode {
2534 key_context.add("selection_mode");
2535 }
2536
2537 key_context
2538 }
2539
2540 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2541 self.last_bounds.as_ref()
2542 }
2543
2544 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2545 if self.mouse_cursor_hidden {
2546 self.mouse_cursor_hidden = false;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2552 let hide_mouse_cursor = match origin {
2553 HideMouseCursorOrigin::TypingAction => {
2554 matches!(
2555 self.hide_mouse_mode,
2556 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2557 )
2558 }
2559 HideMouseCursorOrigin::MovementAction => {
2560 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2561 }
2562 };
2563 if self.mouse_cursor_hidden != hide_mouse_cursor {
2564 self.mouse_cursor_hidden = hide_mouse_cursor;
2565 cx.notify();
2566 }
2567 }
2568
2569 pub fn edit_prediction_in_conflict(&self) -> bool {
2570 if !self.show_edit_predictions_in_menu() {
2571 return false;
2572 }
2573
2574 let showing_completions = self
2575 .context_menu
2576 .borrow()
2577 .as_ref()
2578 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2579
2580 showing_completions
2581 || self.edit_prediction_requires_modifier()
2582 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2583 // bindings to insert tab characters.
2584 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2585 }
2586
2587 pub fn accept_edit_prediction_keybind(
2588 &self,
2589 accept_partial: bool,
2590 window: &Window,
2591 cx: &App,
2592 ) -> AcceptEditPredictionBinding {
2593 let key_context = self.key_context_internal(true, window, cx);
2594 let in_conflict = self.edit_prediction_in_conflict();
2595
2596 let bindings = if accept_partial {
2597 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2598 } else {
2599 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2600 };
2601
2602 // TODO: if the binding contains multiple keystrokes, display all of them, not
2603 // just the first one.
2604 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2605 !in_conflict
2606 || binding
2607 .keystrokes()
2608 .first()
2609 .is_some_and(|keystroke| keystroke.modifiers().modified())
2610 }))
2611 }
2612
2613 pub fn new_file(
2614 workspace: &mut Workspace,
2615 _: &workspace::NewFile,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) {
2619 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2620 "Failed to create buffer",
2621 window,
2622 cx,
2623 |e, _, _| match e.error_code() {
2624 ErrorCode::RemoteUpgradeRequired => Some(format!(
2625 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2626 e.error_tag("required").unwrap_or("the latest version")
2627 )),
2628 _ => None,
2629 },
2630 );
2631 }
2632
2633 pub fn new_in_workspace(
2634 workspace: &mut Workspace,
2635 window: &mut Window,
2636 cx: &mut Context<Workspace>,
2637 ) -> Task<Result<Entity<Editor>>> {
2638 let project = workspace.project().clone();
2639 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2640
2641 cx.spawn_in(window, async move |workspace, cx| {
2642 let buffer = create.await?;
2643 workspace.update_in(cx, |workspace, window, cx| {
2644 let editor =
2645 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2646 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2647 editor
2648 })
2649 })
2650 }
2651
2652 fn new_file_vertical(
2653 workspace: &mut Workspace,
2654 _: &workspace::NewFileSplitVertical,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2659 }
2660
2661 fn new_file_horizontal(
2662 workspace: &mut Workspace,
2663 _: &workspace::NewFileSplitHorizontal,
2664 window: &mut Window,
2665 cx: &mut Context<Workspace>,
2666 ) {
2667 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2668 }
2669
2670 fn new_file_in_direction(
2671 workspace: &mut Workspace,
2672 direction: SplitDirection,
2673 window: &mut Window,
2674 cx: &mut Context<Workspace>,
2675 ) {
2676 let project = workspace.project().clone();
2677 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2678
2679 cx.spawn_in(window, async move |workspace, cx| {
2680 let buffer = create.await?;
2681 workspace.update_in(cx, move |workspace, window, cx| {
2682 workspace.split_item(
2683 direction,
2684 Box::new(
2685 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2686 ),
2687 window,
2688 cx,
2689 )
2690 })?;
2691 anyhow::Ok(())
2692 })
2693 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2694 match e.error_code() {
2695 ErrorCode::RemoteUpgradeRequired => Some(format!(
2696 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2697 e.error_tag("required").unwrap_or("the latest version")
2698 )),
2699 _ => None,
2700 }
2701 });
2702 }
2703
2704 pub fn leader_id(&self) -> Option<CollaboratorId> {
2705 self.leader_id
2706 }
2707
2708 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2709 &self.buffer
2710 }
2711
2712 pub fn project(&self) -> Option<&Entity<Project>> {
2713 self.project.as_ref()
2714 }
2715
2716 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2717 self.workspace.as_ref()?.0.upgrade()
2718 }
2719
2720 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2721 self.buffer().read(cx).title(cx)
2722 }
2723
2724 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2725 let git_blame_gutter_max_author_length = self
2726 .render_git_blame_gutter(cx)
2727 .then(|| {
2728 if let Some(blame) = self.blame.as_ref() {
2729 let max_author_length =
2730 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2731 Some(max_author_length)
2732 } else {
2733 None
2734 }
2735 })
2736 .flatten();
2737
2738 EditorSnapshot {
2739 mode: self.mode.clone(),
2740 show_gutter: self.show_gutter,
2741 show_line_numbers: self.show_line_numbers,
2742 show_git_diff_gutter: self.show_git_diff_gutter,
2743 show_code_actions: self.show_code_actions,
2744 show_runnables: self.show_runnables,
2745 show_breakpoints: self.show_breakpoints,
2746 git_blame_gutter_max_author_length,
2747 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2748 placeholder_display_snapshot: self
2749 .placeholder_display_map
2750 .as_ref()
2751 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2752 scroll_anchor: self.scroll_manager.anchor(),
2753 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2754 is_focused: self.focus_handle.is_focused(window),
2755 current_line_highlight: self
2756 .current_line_highlight
2757 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2758 gutter_hovered: self.gutter_hovered,
2759 }
2760 }
2761
2762 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2763 self.buffer.read(cx).language_at(point, cx)
2764 }
2765
2766 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2767 self.buffer.read(cx).read(cx).file_at(point).cloned()
2768 }
2769
2770 pub fn active_excerpt(
2771 &self,
2772 cx: &App,
2773 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2774 self.buffer
2775 .read(cx)
2776 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2777 }
2778
2779 pub fn mode(&self) -> &EditorMode {
2780 &self.mode
2781 }
2782
2783 pub fn set_mode(&mut self, mode: EditorMode) {
2784 self.mode = mode;
2785 }
2786
2787 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2788 self.collaboration_hub.as_deref()
2789 }
2790
2791 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2792 self.collaboration_hub = Some(hub);
2793 }
2794
2795 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2796 self.in_project_search = in_project_search;
2797 }
2798
2799 pub fn set_custom_context_menu(
2800 &mut self,
2801 f: impl 'static
2802 + Fn(
2803 &mut Self,
2804 DisplayPoint,
2805 &mut Window,
2806 &mut Context<Self>,
2807 ) -> Option<Entity<ui::ContextMenu>>,
2808 ) {
2809 self.custom_context_menu = Some(Box::new(f))
2810 }
2811
2812 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2813 self.completion_provider = provider;
2814 }
2815
2816 #[cfg(any(test, feature = "test-support"))]
2817 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2818 self.completion_provider.clone()
2819 }
2820
2821 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2822 self.semantics_provider.clone()
2823 }
2824
2825 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2826 self.semantics_provider = provider;
2827 }
2828
2829 pub fn set_edit_prediction_provider<T>(
2830 &mut self,
2831 provider: Option<Entity<T>>,
2832 window: &mut Window,
2833 cx: &mut Context<Self>,
2834 ) where
2835 T: EditPredictionProvider,
2836 {
2837 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2838 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2839 if this.focus_handle.is_focused(window) {
2840 this.update_visible_edit_prediction(window, cx);
2841 }
2842 }),
2843 provider: Arc::new(provider),
2844 });
2845 self.update_edit_prediction_settings(cx);
2846 self.refresh_edit_prediction(false, false, window, cx);
2847 }
2848
2849 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2850 self.placeholder_display_map
2851 .as_ref()
2852 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2853 }
2854
2855 pub fn set_placeholder_text(
2856 &mut self,
2857 placeholder_text: &str,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) {
2861 let multibuffer = cx
2862 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2863
2864 let style = window.text_style();
2865
2866 self.placeholder_display_map = Some(cx.new(|cx| {
2867 DisplayMap::new(
2868 multibuffer,
2869 style.font(),
2870 style.font_size.to_pixels(window.rem_size()),
2871 None,
2872 FILE_HEADER_HEIGHT,
2873 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2874 Default::default(),
2875 DiagnosticSeverity::Off,
2876 cx,
2877 )
2878 }));
2879 cx.notify();
2880 }
2881
2882 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2883 self.cursor_shape = cursor_shape;
2884
2885 // Disrupt blink for immediate user feedback that the cursor shape has changed
2886 self.blink_manager.update(cx, BlinkManager::show_cursor);
2887
2888 cx.notify();
2889 }
2890
2891 pub fn set_current_line_highlight(
2892 &mut self,
2893 current_line_highlight: Option<CurrentLineHighlight>,
2894 ) {
2895 self.current_line_highlight = current_line_highlight;
2896 }
2897
2898 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2899 self.collapse_matches = collapse_matches;
2900 }
2901
2902 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2903 let buffers = self.buffer.read(cx).all_buffers();
2904 let Some(project) = self.project.as_ref() else {
2905 return;
2906 };
2907 project.update(cx, |project, cx| {
2908 for buffer in buffers {
2909 self.registered_buffers
2910 .entry(buffer.read(cx).remote_id())
2911 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2912 }
2913 })
2914 }
2915
2916 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2917 if self.collapse_matches {
2918 return range.start..range.start;
2919 }
2920 range.clone()
2921 }
2922
2923 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2924 if self.display_map.read(cx).clip_at_line_ends != clip {
2925 self.display_map
2926 .update(cx, |map, _| map.clip_at_line_ends = clip);
2927 }
2928 }
2929
2930 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2931 self.input_enabled = input_enabled;
2932 }
2933
2934 pub fn set_edit_predictions_hidden_for_vim_mode(
2935 &mut self,
2936 hidden: bool,
2937 window: &mut Window,
2938 cx: &mut Context<Self>,
2939 ) {
2940 if hidden != self.edit_predictions_hidden_for_vim_mode {
2941 self.edit_predictions_hidden_for_vim_mode = hidden;
2942 if hidden {
2943 self.update_visible_edit_prediction(window, cx);
2944 } else {
2945 self.refresh_edit_prediction(true, false, window, cx);
2946 }
2947 }
2948 }
2949
2950 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2951 self.menu_edit_predictions_policy = value;
2952 }
2953
2954 pub fn set_autoindent(&mut self, autoindent: bool) {
2955 if autoindent {
2956 self.autoindent_mode = Some(AutoindentMode::EachLine);
2957 } else {
2958 self.autoindent_mode = None;
2959 }
2960 }
2961
2962 pub fn read_only(&self, cx: &App) -> bool {
2963 self.read_only || self.buffer.read(cx).read_only()
2964 }
2965
2966 pub fn set_read_only(&mut self, read_only: bool) {
2967 self.read_only = read_only;
2968 }
2969
2970 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2971 self.use_autoclose = autoclose;
2972 }
2973
2974 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2975 self.use_auto_surround = auto_surround;
2976 }
2977
2978 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2979 self.auto_replace_emoji_shortcode = auto_replace;
2980 }
2981
2982 pub fn toggle_edit_predictions(
2983 &mut self,
2984 _: &ToggleEditPrediction,
2985 window: &mut Window,
2986 cx: &mut Context<Self>,
2987 ) {
2988 if self.show_edit_predictions_override.is_some() {
2989 self.set_show_edit_predictions(None, window, cx);
2990 } else {
2991 let show_edit_predictions = !self.edit_predictions_enabled();
2992 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2993 }
2994 }
2995
2996 pub fn set_show_edit_predictions(
2997 &mut self,
2998 show_edit_predictions: Option<bool>,
2999 window: &mut Window,
3000 cx: &mut Context<Self>,
3001 ) {
3002 self.show_edit_predictions_override = show_edit_predictions;
3003 self.update_edit_prediction_settings(cx);
3004
3005 if let Some(false) = show_edit_predictions {
3006 self.discard_edit_prediction(false, cx);
3007 } else {
3008 self.refresh_edit_prediction(false, true, window, cx);
3009 }
3010 }
3011
3012 fn edit_predictions_disabled_in_scope(
3013 &self,
3014 buffer: &Entity<Buffer>,
3015 buffer_position: language::Anchor,
3016 cx: &App,
3017 ) -> bool {
3018 let snapshot = buffer.read(cx).snapshot();
3019 let settings = snapshot.settings_at(buffer_position, cx);
3020
3021 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3022 return false;
3023 };
3024
3025 scope.override_name().is_some_and(|scope_name| {
3026 settings
3027 .edit_predictions_disabled_in
3028 .iter()
3029 .any(|s| s == scope_name)
3030 })
3031 }
3032
3033 pub fn set_use_modal_editing(&mut self, to: bool) {
3034 self.use_modal_editing = to;
3035 }
3036
3037 pub fn use_modal_editing(&self) -> bool {
3038 self.use_modal_editing
3039 }
3040
3041 fn selections_did_change(
3042 &mut self,
3043 local: bool,
3044 old_cursor_position: &Anchor,
3045 effects: SelectionEffects,
3046 window: &mut Window,
3047 cx: &mut Context<Self>,
3048 ) {
3049 window.invalidate_character_coordinates();
3050
3051 // Copy selections to primary selection buffer
3052 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3053 if local {
3054 let selections = self.selections.all::<usize>(cx);
3055 let buffer_handle = self.buffer.read(cx).read(cx);
3056
3057 let mut text = String::new();
3058 for (index, selection) in selections.iter().enumerate() {
3059 let text_for_selection = buffer_handle
3060 .text_for_range(selection.start..selection.end)
3061 .collect::<String>();
3062
3063 text.push_str(&text_for_selection);
3064 if index != selections.len() - 1 {
3065 text.push('\n');
3066 }
3067 }
3068
3069 if !text.is_empty() {
3070 cx.write_to_primary(ClipboardItem::new_string(text));
3071 }
3072 }
3073
3074 let selection_anchors = self.selections.disjoint_anchors_arc();
3075
3076 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3077 self.buffer.update(cx, |buffer, cx| {
3078 buffer.set_active_selections(
3079 &selection_anchors,
3080 self.selections.line_mode(),
3081 self.cursor_shape,
3082 cx,
3083 )
3084 });
3085 }
3086 let display_map = self
3087 .display_map
3088 .update(cx, |display_map, cx| display_map.snapshot(cx));
3089 let buffer = display_map.buffer_snapshot();
3090 if self.selections.count() == 1 {
3091 self.add_selections_state = None;
3092 }
3093 self.select_next_state = None;
3094 self.select_prev_state = None;
3095 self.select_syntax_node_history.try_clear();
3096 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3097 self.snippet_stack.invalidate(&selection_anchors, buffer);
3098 self.take_rename(false, window, cx);
3099
3100 let newest_selection = self.selections.newest_anchor();
3101 let new_cursor_position = newest_selection.head();
3102 let selection_start = newest_selection.start;
3103
3104 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3105 self.push_to_nav_history(
3106 *old_cursor_position,
3107 Some(new_cursor_position.to_point(buffer)),
3108 false,
3109 effects.nav_history == Some(true),
3110 cx,
3111 );
3112 }
3113
3114 if local {
3115 if let Some(buffer_id) = new_cursor_position.buffer_id
3116 && !self.registered_buffers.contains_key(&buffer_id)
3117 && let Some(project) = self.project.as_ref()
3118 {
3119 project.update(cx, |project, cx| {
3120 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3121 return;
3122 };
3123 self.registered_buffers.insert(
3124 buffer_id,
3125 project.register_buffer_with_language_servers(&buffer, cx),
3126 );
3127 })
3128 }
3129
3130 let mut context_menu = self.context_menu.borrow_mut();
3131 let completion_menu = match context_menu.as_ref() {
3132 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3133 Some(CodeContextMenu::CodeActions(_)) => {
3134 *context_menu = None;
3135 None
3136 }
3137 None => None,
3138 };
3139 let completion_position = completion_menu.map(|menu| menu.initial_position);
3140 drop(context_menu);
3141
3142 if effects.completions
3143 && let Some(completion_position) = completion_position
3144 {
3145 let start_offset = selection_start.to_offset(buffer);
3146 let position_matches = start_offset == completion_position.to_offset(buffer);
3147 let continue_showing = if position_matches {
3148 if self.snippet_stack.is_empty() {
3149 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3150 == Some(CharKind::Word)
3151 } else {
3152 // Snippet choices can be shown even when the cursor is in whitespace.
3153 // Dismissing the menu with actions like backspace is handled by
3154 // invalidation regions.
3155 true
3156 }
3157 } else {
3158 false
3159 };
3160
3161 if continue_showing {
3162 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3163 } else {
3164 self.hide_context_menu(window, cx);
3165 }
3166 }
3167
3168 hide_hover(self, cx);
3169
3170 if old_cursor_position.to_display_point(&display_map).row()
3171 != new_cursor_position.to_display_point(&display_map).row()
3172 {
3173 self.available_code_actions.take();
3174 }
3175 self.refresh_code_actions(window, cx);
3176 self.refresh_document_highlights(cx);
3177 self.refresh_selected_text_highlights(false, window, cx);
3178 refresh_matching_bracket_highlights(self, cx);
3179 self.update_visible_edit_prediction(window, cx);
3180 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3181 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3182 self.inline_blame_popover.take();
3183 if self.git_blame_inline_enabled {
3184 self.start_inline_blame_timer(window, cx);
3185 }
3186 }
3187
3188 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3189 cx.emit(EditorEvent::SelectionsChanged { local });
3190
3191 let selections = &self.selections.disjoint_anchors_arc();
3192 if selections.len() == 1 {
3193 cx.emit(SearchEvent::ActiveMatchChanged)
3194 }
3195 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3196 let inmemory_selections = selections
3197 .iter()
3198 .map(|s| {
3199 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3200 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3201 })
3202 .collect();
3203 self.update_restoration_data(cx, |data| {
3204 data.selections = inmemory_selections;
3205 });
3206
3207 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3208 && let Some(workspace_id) =
3209 self.workspace.as_ref().and_then(|workspace| workspace.1)
3210 {
3211 let snapshot = self.buffer().read(cx).snapshot(cx);
3212 let selections = selections.clone();
3213 let background_executor = cx.background_executor().clone();
3214 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3215 self.serialize_selections = cx.background_spawn(async move {
3216 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3217 let db_selections = selections
3218 .iter()
3219 .map(|selection| {
3220 (
3221 selection.start.to_offset(&snapshot),
3222 selection.end.to_offset(&snapshot),
3223 )
3224 })
3225 .collect();
3226
3227 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3228 .await
3229 .with_context(|| {
3230 format!(
3231 "persisting editor selections for editor {editor_id}, \
3232 workspace {workspace_id:?}"
3233 )
3234 })
3235 .log_err();
3236 });
3237 }
3238 }
3239
3240 cx.notify();
3241 }
3242
3243 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3244 use text::ToOffset as _;
3245 use text::ToPoint as _;
3246
3247 if self.mode.is_minimap()
3248 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3249 {
3250 return;
3251 }
3252
3253 if !self.buffer().read(cx).is_singleton() {
3254 return;
3255 }
3256
3257 let display_snapshot = self
3258 .display_map
3259 .update(cx, |display_map, cx| display_map.snapshot(cx));
3260 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3261 return;
3262 };
3263 let inmemory_folds = display_snapshot
3264 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3265 .map(|fold| {
3266 fold.range.start.text_anchor.to_point(&snapshot)
3267 ..fold.range.end.text_anchor.to_point(&snapshot)
3268 })
3269 .collect();
3270 self.update_restoration_data(cx, |data| {
3271 data.folds = inmemory_folds;
3272 });
3273
3274 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3275 return;
3276 };
3277 let background_executor = cx.background_executor().clone();
3278 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3279 let db_folds = display_snapshot
3280 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3281 .map(|fold| {
3282 (
3283 fold.range.start.text_anchor.to_offset(&snapshot),
3284 fold.range.end.text_anchor.to_offset(&snapshot),
3285 )
3286 })
3287 .collect();
3288 self.serialize_folds = cx.background_spawn(async move {
3289 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3290 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3291 .await
3292 .with_context(|| {
3293 format!(
3294 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3295 )
3296 })
3297 .log_err();
3298 });
3299 }
3300
3301 pub fn sync_selections(
3302 &mut self,
3303 other: Entity<Editor>,
3304 cx: &mut Context<Self>,
3305 ) -> gpui::Subscription {
3306 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3307 if !other_selections.is_empty() {
3308 self.selections.change_with(cx, |selections| {
3309 selections.select_anchors(other_selections);
3310 });
3311 }
3312
3313 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3314 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3315 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3316 if other_selections.is_empty() {
3317 return;
3318 }
3319 this.selections.change_with(cx, |selections| {
3320 selections.select_anchors(other_selections);
3321 });
3322 }
3323 });
3324
3325 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3326 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3327 let these_selections = this.selections.disjoint_anchors().to_vec();
3328 if these_selections.is_empty() {
3329 return;
3330 }
3331 other.update(cx, |other_editor, cx| {
3332 other_editor.selections.change_with(cx, |selections| {
3333 selections.select_anchors(these_selections);
3334 })
3335 });
3336 }
3337 });
3338
3339 Subscription::join(other_subscription, this_subscription)
3340 }
3341
3342 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3343 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3344 /// effects of selection change occur at the end of the transaction.
3345 pub fn change_selections<R>(
3346 &mut self,
3347 effects: SelectionEffects,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3351 ) -> R {
3352 if let Some(state) = &mut self.deferred_selection_effects_state {
3353 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3354 state.effects.completions = effects.completions;
3355 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3356 let (changed, result) = self.selections.change_with(cx, change);
3357 state.changed |= changed;
3358 return result;
3359 }
3360 let mut state = DeferredSelectionEffectsState {
3361 changed: false,
3362 effects,
3363 old_cursor_position: self.selections.newest_anchor().head(),
3364 history_entry: SelectionHistoryEntry {
3365 selections: self.selections.disjoint_anchors_arc(),
3366 select_next_state: self.select_next_state.clone(),
3367 select_prev_state: self.select_prev_state.clone(),
3368 add_selections_state: self.add_selections_state.clone(),
3369 },
3370 };
3371 let (changed, result) = self.selections.change_with(cx, change);
3372 state.changed = state.changed || changed;
3373 if self.defer_selection_effects {
3374 self.deferred_selection_effects_state = Some(state);
3375 } else {
3376 self.apply_selection_effects(state, window, cx);
3377 }
3378 result
3379 }
3380
3381 /// Defers the effects of selection change, so that the effects of multiple calls to
3382 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3383 /// to selection history and the state of popovers based on selection position aren't
3384 /// erroneously updated.
3385 pub fn with_selection_effects_deferred<R>(
3386 &mut self,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3390 ) -> R {
3391 let already_deferred = self.defer_selection_effects;
3392 self.defer_selection_effects = true;
3393 let result = update(self, window, cx);
3394 if !already_deferred {
3395 self.defer_selection_effects = false;
3396 if let Some(state) = self.deferred_selection_effects_state.take() {
3397 self.apply_selection_effects(state, window, cx);
3398 }
3399 }
3400 result
3401 }
3402
3403 fn apply_selection_effects(
3404 &mut self,
3405 state: DeferredSelectionEffectsState,
3406 window: &mut Window,
3407 cx: &mut Context<Self>,
3408 ) {
3409 if state.changed {
3410 self.selection_history.push(state.history_entry);
3411
3412 if let Some(autoscroll) = state.effects.scroll {
3413 self.request_autoscroll(autoscroll, cx);
3414 }
3415
3416 let old_cursor_position = &state.old_cursor_position;
3417
3418 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3419
3420 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3421 self.show_signature_help(&ShowSignatureHelp, window, cx);
3422 }
3423 }
3424 }
3425
3426 pub fn edit<I, S, T>(&mut self, edits: I, 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
3437 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3438 }
3439
3440 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3441 where
3442 I: IntoIterator<Item = (Range<S>, T)>,
3443 S: ToOffset,
3444 T: Into<Arc<str>>,
3445 {
3446 if self.read_only(cx) {
3447 return;
3448 }
3449
3450 self.buffer.update(cx, |buffer, cx| {
3451 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3452 });
3453 }
3454
3455 pub fn edit_with_block_indent<I, S, T>(
3456 &mut self,
3457 edits: I,
3458 original_indent_columns: Vec<Option<u32>>,
3459 cx: &mut Context<Self>,
3460 ) where
3461 I: IntoIterator<Item = (Range<S>, T)>,
3462 S: ToOffset,
3463 T: Into<Arc<str>>,
3464 {
3465 if self.read_only(cx) {
3466 return;
3467 }
3468
3469 self.buffer.update(cx, |buffer, cx| {
3470 buffer.edit(
3471 edits,
3472 Some(AutoindentMode::Block {
3473 original_indent_columns,
3474 }),
3475 cx,
3476 )
3477 });
3478 }
3479
3480 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3481 self.hide_context_menu(window, cx);
3482
3483 match phase {
3484 SelectPhase::Begin {
3485 position,
3486 add,
3487 click_count,
3488 } => self.begin_selection(position, add, click_count, window, cx),
3489 SelectPhase::BeginColumnar {
3490 position,
3491 goal_column,
3492 reset,
3493 mode,
3494 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3495 SelectPhase::Extend {
3496 position,
3497 click_count,
3498 } => self.extend_selection(position, click_count, window, cx),
3499 SelectPhase::Update {
3500 position,
3501 goal_column,
3502 scroll_delta,
3503 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3504 SelectPhase::End => self.end_selection(window, cx),
3505 }
3506 }
3507
3508 fn extend_selection(
3509 &mut self,
3510 position: DisplayPoint,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3516 let tail = self.selections.newest::<usize>(cx).tail();
3517 self.begin_selection(position, false, click_count, window, cx);
3518
3519 let position = position.to_offset(&display_map, Bias::Left);
3520 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3521
3522 let mut pending_selection = self
3523 .selections
3524 .pending_anchor()
3525 .cloned()
3526 .expect("extend_selection not called with pending selection");
3527 if position >= tail {
3528 pending_selection.start = tail_anchor;
3529 } else {
3530 pending_selection.end = tail_anchor;
3531 pending_selection.reversed = true;
3532 }
3533
3534 let mut pending_mode = self.selections.pending_mode().unwrap();
3535 match &mut pending_mode {
3536 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3537 _ => {}
3538 }
3539
3540 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3541 SelectionEffects::scroll(Autoscroll::fit())
3542 } else {
3543 SelectionEffects::no_scroll()
3544 };
3545
3546 self.change_selections(effects, window, cx, |s| {
3547 s.set_pending(pending_selection.clone(), pending_mode)
3548 });
3549 }
3550
3551 fn begin_selection(
3552 &mut self,
3553 position: DisplayPoint,
3554 add: bool,
3555 click_count: usize,
3556 window: &mut Window,
3557 cx: &mut Context<Self>,
3558 ) {
3559 if !self.focus_handle.is_focused(window) {
3560 self.last_focused_descendant = None;
3561 window.focus(&self.focus_handle);
3562 }
3563
3564 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3565 let buffer = display_map.buffer_snapshot();
3566 let position = display_map.clip_point(position, Bias::Left);
3567
3568 let start;
3569 let end;
3570 let mode;
3571 let mut auto_scroll;
3572 match click_count {
3573 1 => {
3574 start = buffer.anchor_before(position.to_point(&display_map));
3575 end = start;
3576 mode = SelectMode::Character;
3577 auto_scroll = true;
3578 }
3579 2 => {
3580 let position = display_map
3581 .clip_point(position, Bias::Left)
3582 .to_offset(&display_map, Bias::Left);
3583 let (range, _) = buffer.surrounding_word(position, None);
3584 start = buffer.anchor_before(range.start);
3585 end = buffer.anchor_before(range.end);
3586 mode = SelectMode::Word(start..end);
3587 auto_scroll = true;
3588 }
3589 3 => {
3590 let position = display_map
3591 .clip_point(position, Bias::Left)
3592 .to_point(&display_map);
3593 let line_start = display_map.prev_line_boundary(position).0;
3594 let next_line_start = buffer.clip_point(
3595 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3596 Bias::Left,
3597 );
3598 start = buffer.anchor_before(line_start);
3599 end = buffer.anchor_before(next_line_start);
3600 mode = SelectMode::Line(start..end);
3601 auto_scroll = true;
3602 }
3603 _ => {
3604 start = buffer.anchor_before(0);
3605 end = buffer.anchor_before(buffer.len());
3606 mode = SelectMode::All;
3607 auto_scroll = false;
3608 }
3609 }
3610 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3611
3612 let point_to_delete: Option<usize> = {
3613 let selected_points: Vec<Selection<Point>> =
3614 self.selections.disjoint_in_range(start..end, cx);
3615
3616 if !add || click_count > 1 {
3617 None
3618 } else if !selected_points.is_empty() {
3619 Some(selected_points[0].id)
3620 } else {
3621 let clicked_point_already_selected =
3622 self.selections.disjoint_anchors().iter().find(|selection| {
3623 selection.start.to_point(buffer) == start.to_point(buffer)
3624 || selection.end.to_point(buffer) == end.to_point(buffer)
3625 });
3626
3627 clicked_point_already_selected.map(|selection| selection.id)
3628 }
3629 };
3630
3631 let selections_count = self.selections.count();
3632 let effects = if auto_scroll {
3633 SelectionEffects::default()
3634 } else {
3635 SelectionEffects::no_scroll()
3636 };
3637
3638 self.change_selections(effects, window, cx, |s| {
3639 if let Some(point_to_delete) = point_to_delete {
3640 s.delete(point_to_delete);
3641
3642 if selections_count == 1 {
3643 s.set_pending_anchor_range(start..end, mode);
3644 }
3645 } else {
3646 if !add {
3647 s.clear_disjoint();
3648 }
3649
3650 s.set_pending_anchor_range(start..end, mode);
3651 }
3652 });
3653 }
3654
3655 fn begin_columnar_selection(
3656 &mut self,
3657 position: DisplayPoint,
3658 goal_column: u32,
3659 reset: bool,
3660 mode: ColumnarMode,
3661 window: &mut Window,
3662 cx: &mut Context<Self>,
3663 ) {
3664 if !self.focus_handle.is_focused(window) {
3665 self.last_focused_descendant = None;
3666 window.focus(&self.focus_handle);
3667 }
3668
3669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3670
3671 if reset {
3672 let pointer_position = display_map
3673 .buffer_snapshot()
3674 .anchor_before(position.to_point(&display_map));
3675
3676 self.change_selections(
3677 SelectionEffects::scroll(Autoscroll::newest()),
3678 window,
3679 cx,
3680 |s| {
3681 s.clear_disjoint();
3682 s.set_pending_anchor_range(
3683 pointer_position..pointer_position,
3684 SelectMode::Character,
3685 );
3686 },
3687 );
3688 };
3689
3690 let tail = self.selections.newest::<Point>(cx).tail();
3691 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3692 self.columnar_selection_state = match mode {
3693 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3694 selection_tail: selection_anchor,
3695 display_point: if reset {
3696 if position.column() != goal_column {
3697 Some(DisplayPoint::new(position.row(), goal_column))
3698 } else {
3699 None
3700 }
3701 } else {
3702 None
3703 },
3704 }),
3705 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3706 selection_tail: selection_anchor,
3707 }),
3708 };
3709
3710 if !reset {
3711 self.select_columns(position, goal_column, &display_map, window, cx);
3712 }
3713 }
3714
3715 fn update_selection(
3716 &mut self,
3717 position: DisplayPoint,
3718 goal_column: u32,
3719 scroll_delta: gpui::Point<f32>,
3720 window: &mut Window,
3721 cx: &mut Context<Self>,
3722 ) {
3723 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3724
3725 if self.columnar_selection_state.is_some() {
3726 self.select_columns(position, goal_column, &display_map, window, cx);
3727 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3728 let buffer = display_map.buffer_snapshot();
3729 let head;
3730 let tail;
3731 let mode = self.selections.pending_mode().unwrap();
3732 match &mode {
3733 SelectMode::Character => {
3734 head = position.to_point(&display_map);
3735 tail = pending.tail().to_point(buffer);
3736 }
3737 SelectMode::Word(original_range) => {
3738 let offset = display_map
3739 .clip_point(position, Bias::Left)
3740 .to_offset(&display_map, Bias::Left);
3741 let original_range = original_range.to_offset(buffer);
3742
3743 let head_offset = if buffer.is_inside_word(offset, None)
3744 || original_range.contains(&offset)
3745 {
3746 let (word_range, _) = buffer.surrounding_word(offset, None);
3747 if word_range.start < original_range.start {
3748 word_range.start
3749 } else {
3750 word_range.end
3751 }
3752 } else {
3753 offset
3754 };
3755
3756 head = head_offset.to_point(buffer);
3757 if head_offset <= original_range.start {
3758 tail = original_range.end.to_point(buffer);
3759 } else {
3760 tail = original_range.start.to_point(buffer);
3761 }
3762 }
3763 SelectMode::Line(original_range) => {
3764 let original_range = original_range.to_point(display_map.buffer_snapshot());
3765
3766 let position = display_map
3767 .clip_point(position, Bias::Left)
3768 .to_point(&display_map);
3769 let line_start = display_map.prev_line_boundary(position).0;
3770 let next_line_start = buffer.clip_point(
3771 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3772 Bias::Left,
3773 );
3774
3775 if line_start < original_range.start {
3776 head = line_start
3777 } else {
3778 head = next_line_start
3779 }
3780
3781 if head <= original_range.start {
3782 tail = original_range.end;
3783 } else {
3784 tail = original_range.start;
3785 }
3786 }
3787 SelectMode::All => {
3788 return;
3789 }
3790 };
3791
3792 if head < tail {
3793 pending.start = buffer.anchor_before(head);
3794 pending.end = buffer.anchor_before(tail);
3795 pending.reversed = true;
3796 } else {
3797 pending.start = buffer.anchor_before(tail);
3798 pending.end = buffer.anchor_before(head);
3799 pending.reversed = false;
3800 }
3801
3802 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3803 s.set_pending(pending.clone(), mode);
3804 });
3805 } else {
3806 log::error!("update_selection dispatched with no pending selection");
3807 return;
3808 }
3809
3810 self.apply_scroll_delta(scroll_delta, window, cx);
3811 cx.notify();
3812 }
3813
3814 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3815 self.columnar_selection_state.take();
3816 if self.selections.pending_anchor().is_some() {
3817 let selections = self.selections.all::<usize>(cx);
3818 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3819 s.select(selections);
3820 s.clear_pending();
3821 });
3822 }
3823 }
3824
3825 fn select_columns(
3826 &mut self,
3827 head: DisplayPoint,
3828 goal_column: u32,
3829 display_map: &DisplaySnapshot,
3830 window: &mut Window,
3831 cx: &mut Context<Self>,
3832 ) {
3833 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3834 return;
3835 };
3836
3837 let tail = match columnar_state {
3838 ColumnarSelectionState::FromMouse {
3839 selection_tail,
3840 display_point,
3841 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3842 ColumnarSelectionState::FromSelection { selection_tail } => {
3843 selection_tail.to_display_point(display_map)
3844 }
3845 };
3846
3847 let start_row = cmp::min(tail.row(), head.row());
3848 let end_row = cmp::max(tail.row(), head.row());
3849 let start_column = cmp::min(tail.column(), goal_column);
3850 let end_column = cmp::max(tail.column(), goal_column);
3851 let reversed = start_column < tail.column();
3852
3853 let selection_ranges = (start_row.0..=end_row.0)
3854 .map(DisplayRow)
3855 .filter_map(|row| {
3856 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3857 || start_column <= display_map.line_len(row))
3858 && !display_map.is_block_line(row)
3859 {
3860 let start = display_map
3861 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3862 .to_point(display_map);
3863 let end = display_map
3864 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3865 .to_point(display_map);
3866 if reversed {
3867 Some(end..start)
3868 } else {
3869 Some(start..end)
3870 }
3871 } else {
3872 None
3873 }
3874 })
3875 .collect::<Vec<_>>();
3876
3877 let ranges = match columnar_state {
3878 ColumnarSelectionState::FromMouse { .. } => {
3879 let mut non_empty_ranges = selection_ranges
3880 .iter()
3881 .filter(|selection_range| selection_range.start != selection_range.end)
3882 .peekable();
3883 if non_empty_ranges.peek().is_some() {
3884 non_empty_ranges.cloned().collect()
3885 } else {
3886 selection_ranges
3887 }
3888 }
3889 _ => selection_ranges,
3890 };
3891
3892 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3893 s.select_ranges(ranges);
3894 });
3895 cx.notify();
3896 }
3897
3898 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3899 self.selections
3900 .all_adjusted(cx)
3901 .iter()
3902 .any(|selection| !selection.is_empty())
3903 }
3904
3905 pub fn has_pending_nonempty_selection(&self) -> bool {
3906 let pending_nonempty_selection = match self.selections.pending_anchor() {
3907 Some(Selection { start, end, .. }) => start != end,
3908 None => false,
3909 };
3910
3911 pending_nonempty_selection
3912 || (self.columnar_selection_state.is_some()
3913 && self.selections.disjoint_anchors().len() > 1)
3914 }
3915
3916 pub fn has_pending_selection(&self) -> bool {
3917 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3918 }
3919
3920 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3921 self.selection_mark_mode = false;
3922 self.selection_drag_state = SelectionDragState::None;
3923
3924 if self.clear_expanded_diff_hunks(cx) {
3925 cx.notify();
3926 return;
3927 }
3928 if self.dismiss_menus_and_popups(true, window, cx) {
3929 return;
3930 }
3931
3932 if self.mode.is_full()
3933 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3934 {
3935 return;
3936 }
3937
3938 cx.propagate();
3939 }
3940
3941 pub fn dismiss_menus_and_popups(
3942 &mut self,
3943 is_user_requested: bool,
3944 window: &mut Window,
3945 cx: &mut Context<Self>,
3946 ) -> bool {
3947 if self.take_rename(false, window, cx).is_some() {
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(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(cx).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 linked_editing_ranges::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>(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.selections.all_adjusted(cx).into_iter().enumerate() {
4768 let cursor = selection.head();
4769 let row = cursor.row;
4770
4771 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4772
4773 let newline = "\n".to_string();
4774 edits.push((start_of_line..start_of_line, newline));
4775
4776 rows.push(row + rows_inserted as u32);
4777 }
4778
4779 self.transact(window, cx, |editor, window, cx| {
4780 editor.edit(edits, cx);
4781
4782 editor.change_selections(Default::default(), window, cx, |s| {
4783 let mut index = 0;
4784 s.move_cursors_with(|map, _, _| {
4785 let row = rows[index];
4786 index += 1;
4787
4788 let point = Point::new(row, 0);
4789 let boundary = map.next_line_boundary(point).1;
4790 let clipped = map.clip_point(boundary, Bias::Left);
4791
4792 (clipped, SelectionGoal::None)
4793 });
4794 });
4795
4796 let mut indent_edits = Vec::new();
4797 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4798 for row in rows {
4799 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4800 for (row, indent) in indents {
4801 if indent.len == 0 {
4802 continue;
4803 }
4804
4805 let text = match indent.kind {
4806 IndentKind::Space => " ".repeat(indent.len as usize),
4807 IndentKind::Tab => "\t".repeat(indent.len as usize),
4808 };
4809 let point = Point::new(row.0, 0);
4810 indent_edits.push((point..point, text));
4811 }
4812 }
4813 editor.edit(indent_edits, cx);
4814 });
4815 }
4816
4817 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4819
4820 let buffer = self.buffer.read(cx);
4821 let snapshot = buffer.snapshot(cx);
4822
4823 let mut edits = Vec::new();
4824 let mut rows = Vec::new();
4825 let mut rows_inserted = 0;
4826
4827 for selection in self.selections.all_adjusted(cx) {
4828 let cursor = selection.head();
4829 let row = cursor.row;
4830
4831 let point = Point::new(row + 1, 0);
4832 let start_of_line = snapshot.clip_point(point, Bias::Left);
4833
4834 let newline = "\n".to_string();
4835 edits.push((start_of_line..start_of_line, newline));
4836
4837 rows_inserted += 1;
4838 rows.push(row + rows_inserted);
4839 }
4840
4841 self.transact(window, cx, |editor, window, cx| {
4842 editor.edit(edits, cx);
4843
4844 editor.change_selections(Default::default(), window, cx, |s| {
4845 let mut index = 0;
4846 s.move_cursors_with(|map, _, _| {
4847 let row = rows[index];
4848 index += 1;
4849
4850 let point = Point::new(row, 0);
4851 let boundary = map.next_line_boundary(point).1;
4852 let clipped = map.clip_point(boundary, Bias::Left);
4853
4854 (clipped, SelectionGoal::None)
4855 });
4856 });
4857
4858 let mut indent_edits = Vec::new();
4859 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4860 for row in rows {
4861 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4862 for (row, indent) in indents {
4863 if indent.len == 0 {
4864 continue;
4865 }
4866
4867 let text = match indent.kind {
4868 IndentKind::Space => " ".repeat(indent.len as usize),
4869 IndentKind::Tab => "\t".repeat(indent.len as usize),
4870 };
4871 let point = Point::new(row.0, 0);
4872 indent_edits.push((point..point, text));
4873 }
4874 }
4875 editor.edit(indent_edits, cx);
4876 });
4877 }
4878
4879 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4880 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4881 original_indent_columns: Vec::new(),
4882 });
4883 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4884 }
4885
4886 fn insert_with_autoindent_mode(
4887 &mut self,
4888 text: &str,
4889 autoindent_mode: Option<AutoindentMode>,
4890 window: &mut Window,
4891 cx: &mut Context<Self>,
4892 ) {
4893 if self.read_only(cx) {
4894 return;
4895 }
4896
4897 let text: Arc<str> = text.into();
4898 self.transact(window, cx, |this, window, cx| {
4899 let old_selections = this.selections.all_adjusted(cx);
4900 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4901 let anchors = {
4902 let snapshot = buffer.read(cx);
4903 old_selections
4904 .iter()
4905 .map(|s| {
4906 let anchor = snapshot.anchor_after(s.head());
4907 s.map(|_| anchor)
4908 })
4909 .collect::<Vec<_>>()
4910 };
4911 buffer.edit(
4912 old_selections
4913 .iter()
4914 .map(|s| (s.start..s.end, text.clone())),
4915 autoindent_mode,
4916 cx,
4917 );
4918 anchors
4919 });
4920
4921 this.change_selections(Default::default(), window, cx, |s| {
4922 s.select_anchors(selection_anchors);
4923 });
4924
4925 cx.notify();
4926 });
4927 }
4928
4929 fn trigger_completion_on_input(
4930 &mut self,
4931 text: &str,
4932 trigger_in_words: bool,
4933 window: &mut Window,
4934 cx: &mut Context<Self>,
4935 ) {
4936 let completions_source = self
4937 .context_menu
4938 .borrow()
4939 .as_ref()
4940 .and_then(|menu| match menu {
4941 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4942 CodeContextMenu::CodeActions(_) => None,
4943 });
4944
4945 match completions_source {
4946 Some(CompletionsMenuSource::Words { .. }) => {
4947 self.open_or_update_completions_menu(
4948 Some(CompletionsMenuSource::Words {
4949 ignore_threshold: false,
4950 }),
4951 None,
4952 window,
4953 cx,
4954 );
4955 }
4956 Some(CompletionsMenuSource::Normal)
4957 | Some(CompletionsMenuSource::SnippetChoices)
4958 | None
4959 if self.is_completion_trigger(
4960 text,
4961 trigger_in_words,
4962 completions_source.is_some(),
4963 cx,
4964 ) =>
4965 {
4966 self.show_completions(
4967 &ShowCompletions {
4968 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4969 },
4970 window,
4971 cx,
4972 )
4973 }
4974 _ => {
4975 self.hide_context_menu(window, cx);
4976 }
4977 }
4978 }
4979
4980 fn is_completion_trigger(
4981 &self,
4982 text: &str,
4983 trigger_in_words: bool,
4984 menu_is_open: bool,
4985 cx: &mut Context<Self>,
4986 ) -> bool {
4987 let position = self.selections.newest_anchor().head();
4988 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4989 return false;
4990 };
4991
4992 if let Some(completion_provider) = &self.completion_provider {
4993 completion_provider.is_completion_trigger(
4994 &buffer,
4995 position.text_anchor,
4996 text,
4997 trigger_in_words,
4998 menu_is_open,
4999 cx,
5000 )
5001 } else {
5002 false
5003 }
5004 }
5005
5006 /// If any empty selections is touching the start of its innermost containing autoclose
5007 /// region, expand it to select the brackets.
5008 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5009 let selections = self.selections.all::<usize>(cx);
5010 let buffer = self.buffer.read(cx).read(cx);
5011 let new_selections = self
5012 .selections_with_autoclose_regions(selections, &buffer)
5013 .map(|(mut selection, region)| {
5014 if !selection.is_empty() {
5015 return selection;
5016 }
5017
5018 if let Some(region) = region {
5019 let mut range = region.range.to_offset(&buffer);
5020 if selection.start == range.start && range.start >= region.pair.start.len() {
5021 range.start -= region.pair.start.len();
5022 if buffer.contains_str_at(range.start, ®ion.pair.start)
5023 && buffer.contains_str_at(range.end, ®ion.pair.end)
5024 {
5025 range.end += region.pair.end.len();
5026 selection.start = range.start;
5027 selection.end = range.end;
5028
5029 return selection;
5030 }
5031 }
5032 }
5033
5034 let always_treat_brackets_as_autoclosed = buffer
5035 .language_settings_at(selection.start, cx)
5036 .always_treat_brackets_as_autoclosed;
5037
5038 if !always_treat_brackets_as_autoclosed {
5039 return selection;
5040 }
5041
5042 if let Some(scope) = buffer.language_scope_at(selection.start) {
5043 for (pair, enabled) in scope.brackets() {
5044 if !enabled || !pair.close {
5045 continue;
5046 }
5047
5048 if buffer.contains_str_at(selection.start, &pair.end) {
5049 let pair_start_len = pair.start.len();
5050 if buffer.contains_str_at(
5051 selection.start.saturating_sub(pair_start_len),
5052 &pair.start,
5053 ) {
5054 selection.start -= pair_start_len;
5055 selection.end += pair.end.len();
5056
5057 return selection;
5058 }
5059 }
5060 }
5061 }
5062
5063 selection
5064 })
5065 .collect();
5066
5067 drop(buffer);
5068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5069 selections.select(new_selections)
5070 });
5071 }
5072
5073 /// Iterate the given selections, and for each one, find the smallest surrounding
5074 /// autoclose region. This uses the ordering of the selections and the autoclose
5075 /// regions to avoid repeated comparisons.
5076 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5077 &'a self,
5078 selections: impl IntoIterator<Item = Selection<D>>,
5079 buffer: &'a MultiBufferSnapshot,
5080 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5081 let mut i = 0;
5082 let mut regions = self.autoclose_regions.as_slice();
5083 selections.into_iter().map(move |selection| {
5084 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5085
5086 let mut enclosing = None;
5087 while let Some(pair_state) = regions.get(i) {
5088 if pair_state.range.end.to_offset(buffer) < range.start {
5089 regions = ®ions[i + 1..];
5090 i = 0;
5091 } else if pair_state.range.start.to_offset(buffer) > range.end {
5092 break;
5093 } else {
5094 if pair_state.selection_id == selection.id {
5095 enclosing = Some(pair_state);
5096 }
5097 i += 1;
5098 }
5099 }
5100
5101 (selection, enclosing)
5102 })
5103 }
5104
5105 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5106 fn invalidate_autoclose_regions(
5107 &mut self,
5108 mut selections: &[Selection<Anchor>],
5109 buffer: &MultiBufferSnapshot,
5110 ) {
5111 self.autoclose_regions.retain(|state| {
5112 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5113 return false;
5114 }
5115
5116 let mut i = 0;
5117 while let Some(selection) = selections.get(i) {
5118 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5119 selections = &selections[1..];
5120 continue;
5121 }
5122 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5123 break;
5124 }
5125 if selection.id == state.selection_id {
5126 return true;
5127 } else {
5128 i += 1;
5129 }
5130 }
5131 false
5132 });
5133 }
5134
5135 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5136 let offset = position.to_offset(buffer);
5137 let (word_range, kind) =
5138 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5139 if offset > word_range.start && kind == Some(CharKind::Word) {
5140 Some(
5141 buffer
5142 .text_for_range(word_range.start..offset)
5143 .collect::<String>(),
5144 )
5145 } else {
5146 None
5147 }
5148 }
5149
5150 pub fn toggle_inline_values(
5151 &mut self,
5152 _: &ToggleInlineValues,
5153 _: &mut Window,
5154 cx: &mut Context<Self>,
5155 ) {
5156 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5157
5158 self.refresh_inline_values(cx);
5159 }
5160
5161 pub fn toggle_inlay_hints(
5162 &mut self,
5163 _: &ToggleInlayHints,
5164 _: &mut Window,
5165 cx: &mut Context<Self>,
5166 ) {
5167 self.refresh_inlay_hints(
5168 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5169 cx,
5170 );
5171 }
5172
5173 pub fn inlay_hints_enabled(&self) -> bool {
5174 self.inlay_hint_cache.enabled
5175 }
5176
5177 pub fn inline_values_enabled(&self) -> bool {
5178 self.inline_value_cache.enabled
5179 }
5180
5181 #[cfg(any(test, feature = "test-support"))]
5182 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5183 self.display_map
5184 .read(cx)
5185 .current_inlays()
5186 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5187 .cloned()
5188 .collect()
5189 }
5190
5191 #[cfg(any(test, feature = "test-support"))]
5192 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5193 self.display_map
5194 .read(cx)
5195 .current_inlays()
5196 .cloned()
5197 .collect()
5198 }
5199
5200 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5201 if self.semantics_provider.is_none() || !self.mode.is_full() {
5202 return;
5203 }
5204
5205 let reason_description = reason.description();
5206 let ignore_debounce = matches!(
5207 reason,
5208 InlayHintRefreshReason::SettingsChange(_)
5209 | InlayHintRefreshReason::Toggle(_)
5210 | InlayHintRefreshReason::ExcerptsRemoved(_)
5211 | InlayHintRefreshReason::ModifiersChanged(_)
5212 );
5213 let (invalidate_cache, required_languages) = match reason {
5214 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5215 match self.inlay_hint_cache.modifiers_override(enabled) {
5216 Some(enabled) => {
5217 if enabled {
5218 (InvalidationStrategy::RefreshRequested, None)
5219 } else {
5220 self.splice_inlays(
5221 &self
5222 .visible_inlay_hints(cx)
5223 .iter()
5224 .map(|inlay| inlay.id)
5225 .collect::<Vec<InlayId>>(),
5226 Vec::new(),
5227 cx,
5228 );
5229 return;
5230 }
5231 }
5232 None => return,
5233 }
5234 }
5235 InlayHintRefreshReason::Toggle(enabled) => {
5236 if self.inlay_hint_cache.toggle(enabled) {
5237 if enabled {
5238 (InvalidationStrategy::RefreshRequested, None)
5239 } else {
5240 self.splice_inlays(
5241 &self
5242 .visible_inlay_hints(cx)
5243 .iter()
5244 .map(|inlay| inlay.id)
5245 .collect::<Vec<InlayId>>(),
5246 Vec::new(),
5247 cx,
5248 );
5249 return;
5250 }
5251 } else {
5252 return;
5253 }
5254 }
5255 InlayHintRefreshReason::SettingsChange(new_settings) => {
5256 match self.inlay_hint_cache.update_settings(
5257 &self.buffer,
5258 new_settings,
5259 self.visible_inlay_hints(cx),
5260 cx,
5261 ) {
5262 ControlFlow::Break(Some(InlaySplice {
5263 to_remove,
5264 to_insert,
5265 })) => {
5266 self.splice_inlays(&to_remove, to_insert, cx);
5267 return;
5268 }
5269 ControlFlow::Break(None) => return,
5270 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5271 }
5272 }
5273 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5274 if let Some(InlaySplice {
5275 to_remove,
5276 to_insert,
5277 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5278 {
5279 self.splice_inlays(&to_remove, to_insert, cx);
5280 }
5281 self.display_map.update(cx, |display_map, _| {
5282 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5283 });
5284 return;
5285 }
5286 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5287 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5288 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5289 }
5290 InlayHintRefreshReason::RefreshRequested => {
5291 (InvalidationStrategy::RefreshRequested, None)
5292 }
5293 };
5294
5295 if let Some(InlaySplice {
5296 to_remove,
5297 to_insert,
5298 }) = self.inlay_hint_cache.spawn_hint_refresh(
5299 reason_description,
5300 self.visible_excerpts(required_languages.as_ref(), cx),
5301 invalidate_cache,
5302 ignore_debounce,
5303 cx,
5304 ) {
5305 self.splice_inlays(&to_remove, to_insert, cx);
5306 }
5307 }
5308
5309 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5310 self.display_map
5311 .read(cx)
5312 .current_inlays()
5313 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5314 .cloned()
5315 .collect()
5316 }
5317
5318 pub fn visible_excerpts(
5319 &self,
5320 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5321 cx: &mut Context<Editor>,
5322 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5323 let Some(project) = self.project() else {
5324 return HashMap::default();
5325 };
5326 let project = project.read(cx);
5327 let multi_buffer = self.buffer().read(cx);
5328 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5329 let multi_buffer_visible_start = self
5330 .scroll_manager
5331 .anchor()
5332 .anchor
5333 .to_point(&multi_buffer_snapshot);
5334 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5335 multi_buffer_visible_start
5336 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5337 Bias::Left,
5338 );
5339 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5340 multi_buffer_snapshot
5341 .range_to_buffer_ranges(multi_buffer_visible_range)
5342 .into_iter()
5343 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5344 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5345 let buffer_file = project::File::from_dyn(buffer.file())?;
5346 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5347 let worktree_entry = buffer_worktree
5348 .read(cx)
5349 .entry_for_id(buffer_file.project_entry_id()?)?;
5350 if worktree_entry.is_ignored {
5351 return None;
5352 }
5353
5354 let language = buffer.language()?;
5355 if let Some(restrict_to_languages) = restrict_to_languages
5356 && !restrict_to_languages.contains(language)
5357 {
5358 return None;
5359 }
5360 Some((
5361 excerpt_id,
5362 (
5363 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5364 buffer.version().clone(),
5365 excerpt_visible_range,
5366 ),
5367 ))
5368 })
5369 .collect()
5370 }
5371
5372 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5373 TextLayoutDetails {
5374 text_system: window.text_system().clone(),
5375 editor_style: self.style.clone().unwrap(),
5376 rem_size: window.rem_size(),
5377 scroll_anchor: self.scroll_manager.anchor(),
5378 visible_rows: self.visible_line_count(),
5379 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5380 }
5381 }
5382
5383 pub fn splice_inlays(
5384 &self,
5385 to_remove: &[InlayId],
5386 to_insert: Vec<Inlay>,
5387 cx: &mut Context<Self>,
5388 ) {
5389 self.display_map.update(cx, |display_map, cx| {
5390 display_map.splice_inlays(to_remove, to_insert, cx)
5391 });
5392 cx.notify();
5393 }
5394
5395 fn trigger_on_type_formatting(
5396 &self,
5397 input: String,
5398 window: &mut Window,
5399 cx: &mut Context<Self>,
5400 ) -> Option<Task<Result<()>>> {
5401 if input.len() != 1 {
5402 return None;
5403 }
5404
5405 let project = self.project()?;
5406 let position = self.selections.newest_anchor().head();
5407 let (buffer, buffer_position) = self
5408 .buffer
5409 .read(cx)
5410 .text_anchor_for_position(position, cx)?;
5411
5412 let settings = language_settings::language_settings(
5413 buffer
5414 .read(cx)
5415 .language_at(buffer_position)
5416 .map(|l| l.name()),
5417 buffer.read(cx).file(),
5418 cx,
5419 );
5420 if !settings.use_on_type_format {
5421 return None;
5422 }
5423
5424 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5425 // hence we do LSP request & edit on host side only — add formats to host's history.
5426 let push_to_lsp_host_history = true;
5427 // If this is not the host, append its history with new edits.
5428 let push_to_client_history = project.read(cx).is_via_collab();
5429
5430 let on_type_formatting = project.update(cx, |project, cx| {
5431 project.on_type_format(
5432 buffer.clone(),
5433 buffer_position,
5434 input,
5435 push_to_lsp_host_history,
5436 cx,
5437 )
5438 });
5439 Some(cx.spawn_in(window, async move |editor, cx| {
5440 if let Some(transaction) = on_type_formatting.await? {
5441 if push_to_client_history {
5442 buffer
5443 .update(cx, |buffer, _| {
5444 buffer.push_transaction(transaction, Instant::now());
5445 buffer.finalize_last_transaction();
5446 })
5447 .ok();
5448 }
5449 editor.update(cx, |editor, cx| {
5450 editor.refresh_document_highlights(cx);
5451 })?;
5452 }
5453 Ok(())
5454 }))
5455 }
5456
5457 pub fn show_word_completions(
5458 &mut self,
5459 _: &ShowWordCompletions,
5460 window: &mut Window,
5461 cx: &mut Context<Self>,
5462 ) {
5463 self.open_or_update_completions_menu(
5464 Some(CompletionsMenuSource::Words {
5465 ignore_threshold: true,
5466 }),
5467 None,
5468 window,
5469 cx,
5470 );
5471 }
5472
5473 pub fn show_completions(
5474 &mut self,
5475 options: &ShowCompletions,
5476 window: &mut Window,
5477 cx: &mut Context<Self>,
5478 ) {
5479 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5480 }
5481
5482 fn open_or_update_completions_menu(
5483 &mut self,
5484 requested_source: Option<CompletionsMenuSource>,
5485 trigger: Option<&str>,
5486 window: &mut Window,
5487 cx: &mut Context<Self>,
5488 ) {
5489 if self.pending_rename.is_some() {
5490 return;
5491 }
5492
5493 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5494
5495 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5496 // inserted and selected. To handle that case, the start of the selection is used so that
5497 // the menu starts with all choices.
5498 let position = self
5499 .selections
5500 .newest_anchor()
5501 .start
5502 .bias_right(&multibuffer_snapshot);
5503 if position.diff_base_anchor.is_some() {
5504 return;
5505 }
5506 let buffer_position = multibuffer_snapshot.anchor_before(position);
5507 let Some(buffer) = buffer_position
5508 .buffer_id
5509 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5510 else {
5511 return;
5512 };
5513 let buffer_snapshot = buffer.read(cx).snapshot();
5514
5515 let query: Option<Arc<String>> =
5516 Self::completion_query(&multibuffer_snapshot, buffer_position)
5517 .map(|query| query.into());
5518
5519 drop(multibuffer_snapshot);
5520
5521 // Hide the current completions menu when query is empty. Without this, cached
5522 // completions from before the trigger char may be reused (#32774).
5523 if query.is_none() {
5524 let menu_is_open = matches!(
5525 self.context_menu.borrow().as_ref(),
5526 Some(CodeContextMenu::Completions(_))
5527 );
5528 if menu_is_open {
5529 self.hide_context_menu(window, cx);
5530 }
5531 }
5532
5533 let mut ignore_word_threshold = false;
5534 let provider = match requested_source {
5535 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5536 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5537 ignore_word_threshold = ignore_threshold;
5538 None
5539 }
5540 Some(CompletionsMenuSource::SnippetChoices) => {
5541 log::error!("bug: SnippetChoices requested_source is not handled");
5542 None
5543 }
5544 };
5545
5546 let sort_completions = provider
5547 .as_ref()
5548 .is_some_and(|provider| provider.sort_completions());
5549
5550 let filter_completions = provider
5551 .as_ref()
5552 .is_none_or(|provider| provider.filter_completions());
5553
5554 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5555 if filter_completions {
5556 menu.filter(query.clone(), provider.clone(), window, cx);
5557 }
5558 // When `is_incomplete` is false, no need to re-query completions when the current query
5559 // is a suffix of the initial query.
5560 if !menu.is_incomplete {
5561 // If the new query is a suffix of the old query (typing more characters) and
5562 // the previous result was complete, the existing completions can be filtered.
5563 //
5564 // Note that this is always true for snippet completions.
5565 let query_matches = match (&menu.initial_query, &query) {
5566 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5567 (None, _) => true,
5568 _ => false,
5569 };
5570 if query_matches {
5571 let position_matches = if menu.initial_position == position {
5572 true
5573 } else {
5574 let snapshot = self.buffer.read(cx).read(cx);
5575 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5576 };
5577 if position_matches {
5578 return;
5579 }
5580 }
5581 }
5582 };
5583
5584 let trigger_kind = match trigger {
5585 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5586 CompletionTriggerKind::TRIGGER_CHARACTER
5587 }
5588 _ => CompletionTriggerKind::INVOKED,
5589 };
5590 let completion_context = CompletionContext {
5591 trigger_character: trigger.and_then(|trigger| {
5592 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5593 Some(String::from(trigger))
5594 } else {
5595 None
5596 }
5597 }),
5598 trigger_kind,
5599 };
5600
5601 let Anchor {
5602 excerpt_id: buffer_excerpt_id,
5603 text_anchor: buffer_position,
5604 ..
5605 } = buffer_position;
5606
5607 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5608 buffer_snapshot.surrounding_word(buffer_position, None)
5609 {
5610 let word_to_exclude = buffer_snapshot
5611 .text_for_range(word_range.clone())
5612 .collect::<String>();
5613 (
5614 buffer_snapshot.anchor_before(word_range.start)
5615 ..buffer_snapshot.anchor_after(buffer_position),
5616 Some(word_to_exclude),
5617 )
5618 } else {
5619 (buffer_position..buffer_position, None)
5620 };
5621
5622 let language = buffer_snapshot
5623 .language_at(buffer_position)
5624 .map(|language| language.name());
5625
5626 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5627 .completions
5628 .clone();
5629
5630 let show_completion_documentation = buffer_snapshot
5631 .settings_at(buffer_position, cx)
5632 .show_completion_documentation;
5633
5634 // The document can be large, so stay in reasonable bounds when searching for words,
5635 // otherwise completion pop-up might be slow to appear.
5636 const WORD_LOOKUP_ROWS: u32 = 5_000;
5637 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5638 let min_word_search = buffer_snapshot.clip_point(
5639 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5640 Bias::Left,
5641 );
5642 let max_word_search = buffer_snapshot.clip_point(
5643 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5644 Bias::Right,
5645 );
5646 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5647 ..buffer_snapshot.point_to_offset(max_word_search);
5648
5649 let skip_digits = query
5650 .as_ref()
5651 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5652
5653 let omit_word_completions = !self.word_completions_enabled
5654 || (!ignore_word_threshold
5655 && match &query {
5656 Some(query) => query.chars().count() < completion_settings.words_min_length,
5657 None => completion_settings.words_min_length != 0,
5658 });
5659
5660 let (mut words, provider_responses) = match &provider {
5661 Some(provider) => {
5662 let provider_responses = provider.completions(
5663 buffer_excerpt_id,
5664 &buffer,
5665 buffer_position,
5666 completion_context,
5667 window,
5668 cx,
5669 );
5670
5671 let words = match (omit_word_completions, completion_settings.words) {
5672 (true, _) | (_, WordsCompletionMode::Disabled) => {
5673 Task::ready(BTreeMap::default())
5674 }
5675 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5676 .background_spawn(async move {
5677 buffer_snapshot.words_in_range(WordsQuery {
5678 fuzzy_contents: None,
5679 range: word_search_range,
5680 skip_digits,
5681 })
5682 }),
5683 };
5684
5685 (words, provider_responses)
5686 }
5687 None => {
5688 let words = if omit_word_completions {
5689 Task::ready(BTreeMap::default())
5690 } else {
5691 cx.background_spawn(async move {
5692 buffer_snapshot.words_in_range(WordsQuery {
5693 fuzzy_contents: None,
5694 range: word_search_range,
5695 skip_digits,
5696 })
5697 })
5698 };
5699 (words, Task::ready(Ok(Vec::new())))
5700 }
5701 };
5702
5703 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5704
5705 let id = post_inc(&mut self.next_completion_id);
5706 let task = cx.spawn_in(window, async move |editor, cx| {
5707 let Ok(()) = editor.update(cx, |this, _| {
5708 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5709 }) else {
5710 return;
5711 };
5712
5713 // TODO: Ideally completions from different sources would be selectively re-queried, so
5714 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5715 let mut completions = Vec::new();
5716 let mut is_incomplete = false;
5717 let mut display_options: Option<CompletionDisplayOptions> = None;
5718 if let Some(provider_responses) = provider_responses.await.log_err()
5719 && !provider_responses.is_empty()
5720 {
5721 for response in provider_responses {
5722 completions.extend(response.completions);
5723 is_incomplete = is_incomplete || response.is_incomplete;
5724 match display_options.as_mut() {
5725 None => {
5726 display_options = Some(response.display_options);
5727 }
5728 Some(options) => options.merge(&response.display_options),
5729 }
5730 }
5731 if completion_settings.words == WordsCompletionMode::Fallback {
5732 words = Task::ready(BTreeMap::default());
5733 }
5734 }
5735 let display_options = display_options.unwrap_or_default();
5736
5737 let mut words = words.await;
5738 if let Some(word_to_exclude) = &word_to_exclude {
5739 words.remove(word_to_exclude);
5740 }
5741 for lsp_completion in &completions {
5742 words.remove(&lsp_completion.new_text);
5743 }
5744 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5745 replace_range: word_replace_range.clone(),
5746 new_text: word.clone(),
5747 label: CodeLabel::plain(word, None),
5748 icon_path: None,
5749 documentation: None,
5750 source: CompletionSource::BufferWord {
5751 word_range,
5752 resolved: false,
5753 },
5754 insert_text_mode: Some(InsertTextMode::AS_IS),
5755 confirm: None,
5756 }));
5757
5758 let menu = if completions.is_empty() {
5759 None
5760 } else {
5761 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5762 let languages = editor
5763 .workspace
5764 .as_ref()
5765 .and_then(|(workspace, _)| workspace.upgrade())
5766 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5767 let menu = CompletionsMenu::new(
5768 id,
5769 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5770 sort_completions,
5771 show_completion_documentation,
5772 position,
5773 query.clone(),
5774 is_incomplete,
5775 buffer.clone(),
5776 completions.into(),
5777 display_options,
5778 snippet_sort_order,
5779 languages,
5780 language,
5781 cx,
5782 );
5783
5784 let query = if filter_completions { query } else { None };
5785 let matches_task = if let Some(query) = query {
5786 menu.do_async_filtering(query, cx)
5787 } else {
5788 Task::ready(menu.unfiltered_matches())
5789 };
5790 (menu, matches_task)
5791 }) else {
5792 return;
5793 };
5794
5795 let matches = matches_task.await;
5796
5797 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5798 // Newer menu already set, so exit.
5799 if let Some(CodeContextMenu::Completions(prev_menu)) =
5800 editor.context_menu.borrow().as_ref()
5801 && prev_menu.id > id
5802 {
5803 return;
5804 };
5805
5806 // Only valid to take prev_menu because it the new menu is immediately set
5807 // below, or the menu is hidden.
5808 if let Some(CodeContextMenu::Completions(prev_menu)) =
5809 editor.context_menu.borrow_mut().take()
5810 {
5811 let position_matches =
5812 if prev_menu.initial_position == menu.initial_position {
5813 true
5814 } else {
5815 let snapshot = editor.buffer.read(cx).read(cx);
5816 prev_menu.initial_position.to_offset(&snapshot)
5817 == menu.initial_position.to_offset(&snapshot)
5818 };
5819 if position_matches {
5820 // Preserve markdown cache before `set_filter_results` because it will
5821 // try to populate the documentation cache.
5822 menu.preserve_markdown_cache(prev_menu);
5823 }
5824 };
5825
5826 menu.set_filter_results(matches, provider, window, cx);
5827 }) else {
5828 return;
5829 };
5830
5831 menu.visible().then_some(menu)
5832 };
5833
5834 editor
5835 .update_in(cx, |editor, window, cx| {
5836 if editor.focus_handle.is_focused(window)
5837 && let Some(menu) = menu
5838 {
5839 *editor.context_menu.borrow_mut() =
5840 Some(CodeContextMenu::Completions(menu));
5841
5842 crate::hover_popover::hide_hover(editor, cx);
5843 if editor.show_edit_predictions_in_menu() {
5844 editor.update_visible_edit_prediction(window, cx);
5845 } else {
5846 editor.discard_edit_prediction(false, cx);
5847 }
5848
5849 cx.notify();
5850 return;
5851 }
5852
5853 if editor.completion_tasks.len() <= 1 {
5854 // If there are no more completion tasks and the last menu was empty, we should hide it.
5855 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5856 // If it was already hidden and we don't show edit predictions in the menu,
5857 // we should also show the edit prediction when available.
5858 if was_hidden && editor.show_edit_predictions_in_menu() {
5859 editor.update_visible_edit_prediction(window, cx);
5860 }
5861 }
5862 })
5863 .ok();
5864 });
5865
5866 self.completion_tasks.push((id, task));
5867 }
5868
5869 #[cfg(feature = "test-support")]
5870 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5871 let menu = self.context_menu.borrow();
5872 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5873 let completions = menu.completions.borrow();
5874 Some(completions.to_vec())
5875 } else {
5876 None
5877 }
5878 }
5879
5880 pub fn with_completions_menu_matching_id<R>(
5881 &self,
5882 id: CompletionId,
5883 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5884 ) -> R {
5885 let mut context_menu = self.context_menu.borrow_mut();
5886 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5887 return f(None);
5888 };
5889 if completions_menu.id != id {
5890 return f(None);
5891 }
5892 f(Some(completions_menu))
5893 }
5894
5895 pub fn confirm_completion(
5896 &mut self,
5897 action: &ConfirmCompletion,
5898 window: &mut Window,
5899 cx: &mut Context<Self>,
5900 ) -> Option<Task<Result<()>>> {
5901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5902 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5903 }
5904
5905 pub fn confirm_completion_insert(
5906 &mut self,
5907 _: &ConfirmCompletionInsert,
5908 window: &mut Window,
5909 cx: &mut Context<Self>,
5910 ) -> Option<Task<Result<()>>> {
5911 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5912 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5913 }
5914
5915 pub fn confirm_completion_replace(
5916 &mut self,
5917 _: &ConfirmCompletionReplace,
5918 window: &mut Window,
5919 cx: &mut Context<Self>,
5920 ) -> Option<Task<Result<()>>> {
5921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5922 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5923 }
5924
5925 pub fn compose_completion(
5926 &mut self,
5927 action: &ComposeCompletion,
5928 window: &mut Window,
5929 cx: &mut Context<Self>,
5930 ) -> Option<Task<Result<()>>> {
5931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5932 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5933 }
5934
5935 fn do_completion(
5936 &mut self,
5937 item_ix: Option<usize>,
5938 intent: CompletionIntent,
5939 window: &mut Window,
5940 cx: &mut Context<Editor>,
5941 ) -> Option<Task<Result<()>>> {
5942 use language::ToOffset as _;
5943
5944 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5945 else {
5946 return None;
5947 };
5948
5949 let candidate_id = {
5950 let entries = completions_menu.entries.borrow();
5951 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5952 if self.show_edit_predictions_in_menu() {
5953 self.discard_edit_prediction(true, cx);
5954 }
5955 mat.candidate_id
5956 };
5957
5958 let completion = completions_menu
5959 .completions
5960 .borrow()
5961 .get(candidate_id)?
5962 .clone();
5963 cx.stop_propagation();
5964
5965 let buffer_handle = completions_menu.buffer.clone();
5966
5967 let CompletionEdit {
5968 new_text,
5969 snippet,
5970 replace_range,
5971 } = process_completion_for_edit(
5972 &completion,
5973 intent,
5974 &buffer_handle,
5975 &completions_menu.initial_position.text_anchor,
5976 cx,
5977 );
5978
5979 let buffer = buffer_handle.read(cx);
5980 let snapshot = self.buffer.read(cx).snapshot(cx);
5981 let newest_anchor = self.selections.newest_anchor();
5982 let replace_range_multibuffer = {
5983 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5984 let multibuffer_anchor = snapshot
5985 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5986 .unwrap()
5987 ..snapshot
5988 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5989 .unwrap();
5990 multibuffer_anchor.start.to_offset(&snapshot)
5991 ..multibuffer_anchor.end.to_offset(&snapshot)
5992 };
5993 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5994 return None;
5995 }
5996
5997 let old_text = buffer
5998 .text_for_range(replace_range.clone())
5999 .collect::<String>();
6000 let lookbehind = newest_anchor
6001 .start
6002 .text_anchor
6003 .to_offset(buffer)
6004 .saturating_sub(replace_range.start);
6005 let lookahead = replace_range
6006 .end
6007 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6008 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6009 let suffix = &old_text[lookbehind.min(old_text.len())..];
6010
6011 let selections = self.selections.all::<usize>(cx);
6012 let mut ranges = Vec::new();
6013 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6014
6015 for selection in &selections {
6016 let range = if selection.id == newest_anchor.id {
6017 replace_range_multibuffer.clone()
6018 } else {
6019 let mut range = selection.range();
6020
6021 // if prefix is present, don't duplicate it
6022 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6023 range.start = range.start.saturating_sub(lookbehind);
6024
6025 // if suffix is also present, mimic the newest cursor and replace it
6026 if selection.id != newest_anchor.id
6027 && snapshot.contains_str_at(range.end, suffix)
6028 {
6029 range.end += lookahead;
6030 }
6031 }
6032 range
6033 };
6034
6035 ranges.push(range.clone());
6036
6037 if !self.linked_edit_ranges.is_empty() {
6038 let start_anchor = snapshot.anchor_before(range.start);
6039 let end_anchor = snapshot.anchor_after(range.end);
6040 if let Some(ranges) = self
6041 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6042 {
6043 for (buffer, edits) in ranges {
6044 linked_edits
6045 .entry(buffer.clone())
6046 .or_default()
6047 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6048 }
6049 }
6050 }
6051 }
6052
6053 let common_prefix_len = old_text
6054 .chars()
6055 .zip(new_text.chars())
6056 .take_while(|(a, b)| a == b)
6057 .map(|(a, _)| a.len_utf8())
6058 .sum::<usize>();
6059
6060 cx.emit(EditorEvent::InputHandled {
6061 utf16_range_to_replace: None,
6062 text: new_text[common_prefix_len..].into(),
6063 });
6064
6065 self.transact(window, cx, |editor, window, cx| {
6066 if let Some(mut snippet) = snippet {
6067 snippet.text = new_text.to_string();
6068 editor
6069 .insert_snippet(&ranges, snippet, window, cx)
6070 .log_err();
6071 } else {
6072 editor.buffer.update(cx, |multi_buffer, cx| {
6073 let auto_indent = match completion.insert_text_mode {
6074 Some(InsertTextMode::AS_IS) => None,
6075 _ => editor.autoindent_mode.clone(),
6076 };
6077 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6078 multi_buffer.edit(edits, auto_indent, cx);
6079 });
6080 }
6081 for (buffer, edits) in linked_edits {
6082 buffer.update(cx, |buffer, cx| {
6083 let snapshot = buffer.snapshot();
6084 let edits = edits
6085 .into_iter()
6086 .map(|(range, text)| {
6087 use text::ToPoint as TP;
6088 let end_point = TP::to_point(&range.end, &snapshot);
6089 let start_point = TP::to_point(&range.start, &snapshot);
6090 (start_point..end_point, text)
6091 })
6092 .sorted_by_key(|(range, _)| range.start);
6093 buffer.edit(edits, None, cx);
6094 })
6095 }
6096
6097 editor.refresh_edit_prediction(true, false, window, cx);
6098 });
6099 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6100
6101 let show_new_completions_on_confirm = completion
6102 .confirm
6103 .as_ref()
6104 .is_some_and(|confirm| confirm(intent, window, cx));
6105 if show_new_completions_on_confirm {
6106 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6107 }
6108
6109 let provider = self.completion_provider.as_ref()?;
6110 drop(completion);
6111 let apply_edits = provider.apply_additional_edits_for_completion(
6112 buffer_handle,
6113 completions_menu.completions.clone(),
6114 candidate_id,
6115 true,
6116 cx,
6117 );
6118
6119 let editor_settings = EditorSettings::get_global(cx);
6120 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6121 // After the code completion is finished, users often want to know what signatures are needed.
6122 // so we should automatically call signature_help
6123 self.show_signature_help(&ShowSignatureHelp, window, cx);
6124 }
6125
6126 Some(cx.foreground_executor().spawn(async move {
6127 apply_edits.await?;
6128 Ok(())
6129 }))
6130 }
6131
6132 pub fn toggle_code_actions(
6133 &mut self,
6134 action: &ToggleCodeActions,
6135 window: &mut Window,
6136 cx: &mut Context<Self>,
6137 ) {
6138 let quick_launch = action.quick_launch;
6139 let mut context_menu = self.context_menu.borrow_mut();
6140 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6141 if code_actions.deployed_from == action.deployed_from {
6142 // Toggle if we're selecting the same one
6143 *context_menu = None;
6144 cx.notify();
6145 return;
6146 } else {
6147 // Otherwise, clear it and start a new one
6148 *context_menu = None;
6149 cx.notify();
6150 }
6151 }
6152 drop(context_menu);
6153 let snapshot = self.snapshot(window, cx);
6154 let deployed_from = action.deployed_from.clone();
6155 let action = action.clone();
6156 self.completion_tasks.clear();
6157 self.discard_edit_prediction(false, cx);
6158
6159 let multibuffer_point = match &action.deployed_from {
6160 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6161 DisplayPoint::new(*row, 0).to_point(&snapshot)
6162 }
6163 _ => self.selections.newest::<Point>(cx).head(),
6164 };
6165 let Some((buffer, buffer_row)) = snapshot
6166 .buffer_snapshot()
6167 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6168 .and_then(|(buffer_snapshot, range)| {
6169 self.buffer()
6170 .read(cx)
6171 .buffer(buffer_snapshot.remote_id())
6172 .map(|buffer| (buffer, range.start.row))
6173 })
6174 else {
6175 return;
6176 };
6177 let buffer_id = buffer.read(cx).remote_id();
6178 let tasks = self
6179 .tasks
6180 .get(&(buffer_id, buffer_row))
6181 .map(|t| Arc::new(t.to_owned()));
6182
6183 if !self.focus_handle.is_focused(window) {
6184 return;
6185 }
6186 let project = self.project.clone();
6187
6188 let code_actions_task = match deployed_from {
6189 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6190 _ => self.code_actions(buffer_row, window, cx),
6191 };
6192
6193 let runnable_task = match deployed_from {
6194 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6195 _ => {
6196 let mut task_context_task = Task::ready(None);
6197 if let Some(tasks) = &tasks
6198 && let Some(project) = project
6199 {
6200 task_context_task =
6201 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6202 }
6203
6204 cx.spawn_in(window, {
6205 let buffer = buffer.clone();
6206 async move |editor, cx| {
6207 let task_context = task_context_task.await;
6208
6209 let resolved_tasks =
6210 tasks
6211 .zip(task_context.clone())
6212 .map(|(tasks, task_context)| ResolvedTasks {
6213 templates: tasks.resolve(&task_context).collect(),
6214 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6215 multibuffer_point.row,
6216 tasks.column,
6217 )),
6218 });
6219 let debug_scenarios = editor
6220 .update(cx, |editor, cx| {
6221 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6222 })?
6223 .await;
6224 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6225 }
6226 })
6227 }
6228 };
6229
6230 cx.spawn_in(window, async move |editor, cx| {
6231 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6232 let code_actions = code_actions_task.await;
6233 let spawn_straight_away = quick_launch
6234 && resolved_tasks
6235 .as_ref()
6236 .is_some_and(|tasks| tasks.templates.len() == 1)
6237 && code_actions
6238 .as_ref()
6239 .is_none_or(|actions| actions.is_empty())
6240 && debug_scenarios.is_empty();
6241
6242 editor.update_in(cx, |editor, window, cx| {
6243 crate::hover_popover::hide_hover(editor, cx);
6244 let actions = CodeActionContents::new(
6245 resolved_tasks,
6246 code_actions,
6247 debug_scenarios,
6248 task_context.unwrap_or_default(),
6249 );
6250
6251 // Don't show the menu if there are no actions available
6252 if actions.is_empty() {
6253 cx.notify();
6254 return Task::ready(Ok(()));
6255 }
6256
6257 *editor.context_menu.borrow_mut() =
6258 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6259 buffer,
6260 actions,
6261 selected_item: Default::default(),
6262 scroll_handle: UniformListScrollHandle::default(),
6263 deployed_from,
6264 }));
6265 cx.notify();
6266 if spawn_straight_away
6267 && let Some(task) = editor.confirm_code_action(
6268 &ConfirmCodeAction { item_ix: Some(0) },
6269 window,
6270 cx,
6271 )
6272 {
6273 return task;
6274 }
6275
6276 Task::ready(Ok(()))
6277 })
6278 })
6279 .detach_and_log_err(cx);
6280 }
6281
6282 fn debug_scenarios(
6283 &mut self,
6284 resolved_tasks: &Option<ResolvedTasks>,
6285 buffer: &Entity<Buffer>,
6286 cx: &mut App,
6287 ) -> Task<Vec<task::DebugScenario>> {
6288 maybe!({
6289 let project = self.project()?;
6290 let dap_store = project.read(cx).dap_store();
6291 let mut scenarios = vec![];
6292 let resolved_tasks = resolved_tasks.as_ref()?;
6293 let buffer = buffer.read(cx);
6294 let language = buffer.language()?;
6295 let file = buffer.file();
6296 let debug_adapter = language_settings(language.name().into(), file, cx)
6297 .debuggers
6298 .first()
6299 .map(SharedString::from)
6300 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6301
6302 dap_store.update(cx, |dap_store, cx| {
6303 for (_, task) in &resolved_tasks.templates {
6304 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6305 task.original_task().clone(),
6306 debug_adapter.clone().into(),
6307 task.display_label().to_owned().into(),
6308 cx,
6309 );
6310 scenarios.push(maybe_scenario);
6311 }
6312 });
6313 Some(cx.background_spawn(async move {
6314 futures::future::join_all(scenarios)
6315 .await
6316 .into_iter()
6317 .flatten()
6318 .collect::<Vec<_>>()
6319 }))
6320 })
6321 .unwrap_or_else(|| Task::ready(vec![]))
6322 }
6323
6324 fn code_actions(
6325 &mut self,
6326 buffer_row: u32,
6327 window: &mut Window,
6328 cx: &mut Context<Self>,
6329 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6330 let mut task = self.code_actions_task.take();
6331 cx.spawn_in(window, async move |editor, cx| {
6332 while let Some(prev_task) = task {
6333 prev_task.await.log_err();
6334 task = editor
6335 .update(cx, |this, _| this.code_actions_task.take())
6336 .ok()?;
6337 }
6338
6339 editor
6340 .update(cx, |editor, cx| {
6341 editor
6342 .available_code_actions
6343 .clone()
6344 .and_then(|(location, code_actions)| {
6345 let snapshot = location.buffer.read(cx).snapshot();
6346 let point_range = location.range.to_point(&snapshot);
6347 let point_range = point_range.start.row..=point_range.end.row;
6348 if point_range.contains(&buffer_row) {
6349 Some(code_actions)
6350 } else {
6351 None
6352 }
6353 })
6354 })
6355 .ok()
6356 .flatten()
6357 })
6358 }
6359
6360 pub fn confirm_code_action(
6361 &mut self,
6362 action: &ConfirmCodeAction,
6363 window: &mut Window,
6364 cx: &mut Context<Self>,
6365 ) -> Option<Task<Result<()>>> {
6366 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6367
6368 let actions_menu =
6369 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6370 menu
6371 } else {
6372 return None;
6373 };
6374
6375 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6376 let action = actions_menu.actions.get(action_ix)?;
6377 let title = action.label();
6378 let buffer = actions_menu.buffer;
6379 let workspace = self.workspace()?;
6380
6381 match action {
6382 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6383 workspace.update(cx, |workspace, cx| {
6384 workspace.schedule_resolved_task(
6385 task_source_kind,
6386 resolved_task,
6387 false,
6388 window,
6389 cx,
6390 );
6391
6392 Some(Task::ready(Ok(())))
6393 })
6394 }
6395 CodeActionsItem::CodeAction {
6396 excerpt_id,
6397 action,
6398 provider,
6399 } => {
6400 let apply_code_action =
6401 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6402 let workspace = workspace.downgrade();
6403 Some(cx.spawn_in(window, async move |editor, cx| {
6404 let project_transaction = apply_code_action.await?;
6405 Self::open_project_transaction(
6406 &editor,
6407 workspace,
6408 project_transaction,
6409 title,
6410 cx,
6411 )
6412 .await
6413 }))
6414 }
6415 CodeActionsItem::DebugScenario(scenario) => {
6416 let context = actions_menu.actions.context;
6417
6418 workspace.update(cx, |workspace, cx| {
6419 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6420 workspace.start_debug_session(
6421 scenario,
6422 context,
6423 Some(buffer),
6424 None,
6425 window,
6426 cx,
6427 );
6428 });
6429 Some(Task::ready(Ok(())))
6430 }
6431 }
6432 }
6433
6434 pub async fn open_project_transaction(
6435 editor: &WeakEntity<Editor>,
6436 workspace: WeakEntity<Workspace>,
6437 transaction: ProjectTransaction,
6438 title: String,
6439 cx: &mut AsyncWindowContext,
6440 ) -> Result<()> {
6441 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6442 cx.update(|_, cx| {
6443 entries.sort_unstable_by_key(|(buffer, _)| {
6444 buffer.read(cx).file().map(|f| f.path().clone())
6445 });
6446 })?;
6447 if entries.is_empty() {
6448 return Ok(());
6449 }
6450
6451 // If the project transaction's edits are all contained within this editor, then
6452 // avoid opening a new editor to display them.
6453
6454 if let [(buffer, transaction)] = &*entries {
6455 let excerpt = editor.update(cx, |editor, cx| {
6456 editor
6457 .buffer()
6458 .read(cx)
6459 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6460 })?;
6461 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6462 && excerpted_buffer == *buffer
6463 {
6464 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6465 let excerpt_range = excerpt_range.to_offset(buffer);
6466 buffer
6467 .edited_ranges_for_transaction::<usize>(transaction)
6468 .all(|range| {
6469 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6470 })
6471 })?;
6472
6473 if all_edits_within_excerpt {
6474 return Ok(());
6475 }
6476 }
6477 }
6478
6479 let mut ranges_to_highlight = Vec::new();
6480 let excerpt_buffer = cx.new(|cx| {
6481 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6482 for (buffer_handle, transaction) in &entries {
6483 let edited_ranges = buffer_handle
6484 .read(cx)
6485 .edited_ranges_for_transaction::<Point>(transaction)
6486 .collect::<Vec<_>>();
6487 let (ranges, _) = multibuffer.set_excerpts_for_path(
6488 PathKey::for_buffer(buffer_handle, cx),
6489 buffer_handle.clone(),
6490 edited_ranges,
6491 multibuffer_context_lines(cx),
6492 cx,
6493 );
6494
6495 ranges_to_highlight.extend(ranges);
6496 }
6497 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6498 multibuffer
6499 })?;
6500
6501 workspace.update_in(cx, |workspace, window, cx| {
6502 let project = workspace.project().clone();
6503 let editor =
6504 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6505 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6506 editor.update(cx, |editor, cx| {
6507 editor.highlight_background::<Self>(
6508 &ranges_to_highlight,
6509 |theme| theme.colors().editor_highlighted_line_background,
6510 cx,
6511 );
6512 });
6513 })?;
6514
6515 Ok(())
6516 }
6517
6518 pub fn clear_code_action_providers(&mut self) {
6519 self.code_action_providers.clear();
6520 self.available_code_actions.take();
6521 }
6522
6523 pub fn add_code_action_provider(
6524 &mut self,
6525 provider: Rc<dyn CodeActionProvider>,
6526 window: &mut Window,
6527 cx: &mut Context<Self>,
6528 ) {
6529 if self
6530 .code_action_providers
6531 .iter()
6532 .any(|existing_provider| existing_provider.id() == provider.id())
6533 {
6534 return;
6535 }
6536
6537 self.code_action_providers.push(provider);
6538 self.refresh_code_actions(window, cx);
6539 }
6540
6541 pub fn remove_code_action_provider(
6542 &mut self,
6543 id: Arc<str>,
6544 window: &mut Window,
6545 cx: &mut Context<Self>,
6546 ) {
6547 self.code_action_providers
6548 .retain(|provider| provider.id() != id);
6549 self.refresh_code_actions(window, cx);
6550 }
6551
6552 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6553 !self.code_action_providers.is_empty()
6554 && EditorSettings::get_global(cx).toolbar.code_actions
6555 }
6556
6557 pub fn has_available_code_actions(&self) -> bool {
6558 self.available_code_actions
6559 .as_ref()
6560 .is_some_and(|(_, actions)| !actions.is_empty())
6561 }
6562
6563 fn render_inline_code_actions(
6564 &self,
6565 icon_size: ui::IconSize,
6566 display_row: DisplayRow,
6567 is_active: bool,
6568 cx: &mut Context<Self>,
6569 ) -> AnyElement {
6570 let show_tooltip = !self.context_menu_visible();
6571 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6572 .icon_size(icon_size)
6573 .shape(ui::IconButtonShape::Square)
6574 .icon_color(ui::Color::Hidden)
6575 .toggle_state(is_active)
6576 .when(show_tooltip, |this| {
6577 this.tooltip({
6578 let focus_handle = self.focus_handle.clone();
6579 move |window, cx| {
6580 Tooltip::for_action_in(
6581 "Toggle Code Actions",
6582 &ToggleCodeActions {
6583 deployed_from: None,
6584 quick_launch: false,
6585 },
6586 &focus_handle,
6587 window,
6588 cx,
6589 )
6590 }
6591 })
6592 })
6593 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6594 window.focus(&editor.focus_handle(cx));
6595 editor.toggle_code_actions(
6596 &crate::actions::ToggleCodeActions {
6597 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6598 display_row,
6599 )),
6600 quick_launch: false,
6601 },
6602 window,
6603 cx,
6604 );
6605 }))
6606 .into_any_element()
6607 }
6608
6609 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6610 &self.context_menu
6611 }
6612
6613 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6614 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6615 cx.background_executor()
6616 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6617 .await;
6618
6619 let (start_buffer, start, _, end, newest_selection) = this
6620 .update(cx, |this, cx| {
6621 let newest_selection = this.selections.newest_anchor().clone();
6622 if newest_selection.head().diff_base_anchor.is_some() {
6623 return None;
6624 }
6625 let newest_selection_adjusted = this.selections.newest_adjusted(cx);
6626 let buffer = this.buffer.read(cx);
6627
6628 let (start_buffer, start) =
6629 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6630 let (end_buffer, end) =
6631 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6632
6633 Some((start_buffer, start, end_buffer, end, newest_selection))
6634 })?
6635 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6636 .context(
6637 "Expected selection to lie in a single buffer when refreshing code actions",
6638 )?;
6639 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6640 let providers = this.code_action_providers.clone();
6641 let tasks = this
6642 .code_action_providers
6643 .iter()
6644 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6645 .collect::<Vec<_>>();
6646 (providers, tasks)
6647 })?;
6648
6649 let mut actions = Vec::new();
6650 for (provider, provider_actions) in
6651 providers.into_iter().zip(future::join_all(tasks).await)
6652 {
6653 if let Some(provider_actions) = provider_actions.log_err() {
6654 actions.extend(provider_actions.into_iter().map(|action| {
6655 AvailableCodeAction {
6656 excerpt_id: newest_selection.start.excerpt_id,
6657 action,
6658 provider: provider.clone(),
6659 }
6660 }));
6661 }
6662 }
6663
6664 this.update(cx, |this, cx| {
6665 this.available_code_actions = if actions.is_empty() {
6666 None
6667 } else {
6668 Some((
6669 Location {
6670 buffer: start_buffer,
6671 range: start..end,
6672 },
6673 actions.into(),
6674 ))
6675 };
6676 cx.notify();
6677 })
6678 }));
6679 }
6680
6681 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6682 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6683 self.show_git_blame_inline = false;
6684
6685 self.show_git_blame_inline_delay_task =
6686 Some(cx.spawn_in(window, async move |this, cx| {
6687 cx.background_executor().timer(delay).await;
6688
6689 this.update(cx, |this, cx| {
6690 this.show_git_blame_inline = true;
6691 cx.notify();
6692 })
6693 .log_err();
6694 }));
6695 }
6696 }
6697
6698 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6699 let snapshot = self.snapshot(window, cx);
6700 let cursor = self.selections.newest::<Point>(cx).head();
6701 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6702 else {
6703 return;
6704 };
6705
6706 let Some(blame) = self.blame.as_ref() else {
6707 return;
6708 };
6709
6710 let row_info = RowInfo {
6711 buffer_id: Some(buffer.remote_id()),
6712 buffer_row: Some(point.row),
6713 ..Default::default()
6714 };
6715 let Some((buffer, blame_entry)) = blame
6716 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6717 .flatten()
6718 else {
6719 return;
6720 };
6721
6722 let anchor = self.selections.newest_anchor().head();
6723 let position = self.to_pixel_point(anchor, &snapshot, window);
6724 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6725 self.show_blame_popover(
6726 buffer,
6727 &blame_entry,
6728 position + last_bounds.origin,
6729 true,
6730 cx,
6731 );
6732 };
6733 }
6734
6735 fn show_blame_popover(
6736 &mut self,
6737 buffer: BufferId,
6738 blame_entry: &BlameEntry,
6739 position: gpui::Point<Pixels>,
6740 ignore_timeout: bool,
6741 cx: &mut Context<Self>,
6742 ) {
6743 if let Some(state) = &mut self.inline_blame_popover {
6744 state.hide_task.take();
6745 } else {
6746 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6747 let blame_entry = blame_entry.clone();
6748 let show_task = cx.spawn(async move |editor, cx| {
6749 if !ignore_timeout {
6750 cx.background_executor()
6751 .timer(std::time::Duration::from_millis(blame_popover_delay))
6752 .await;
6753 }
6754 editor
6755 .update(cx, |editor, cx| {
6756 editor.inline_blame_popover_show_task.take();
6757 let Some(blame) = editor.blame.as_ref() else {
6758 return;
6759 };
6760 let blame = blame.read(cx);
6761 let details = blame.details_for_entry(buffer, &blame_entry);
6762 let markdown = cx.new(|cx| {
6763 Markdown::new(
6764 details
6765 .as_ref()
6766 .map(|message| message.message.clone())
6767 .unwrap_or_default(),
6768 None,
6769 None,
6770 cx,
6771 )
6772 });
6773 editor.inline_blame_popover = Some(InlineBlamePopover {
6774 position,
6775 hide_task: None,
6776 popover_bounds: None,
6777 popover_state: InlineBlamePopoverState {
6778 scroll_handle: ScrollHandle::new(),
6779 commit_message: details,
6780 markdown,
6781 },
6782 keyboard_grace: ignore_timeout,
6783 });
6784 cx.notify();
6785 })
6786 .ok();
6787 });
6788 self.inline_blame_popover_show_task = Some(show_task);
6789 }
6790 }
6791
6792 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6793 self.inline_blame_popover_show_task.take();
6794 if let Some(state) = &mut self.inline_blame_popover {
6795 let hide_task = cx.spawn(async move |editor, cx| {
6796 cx.background_executor()
6797 .timer(std::time::Duration::from_millis(100))
6798 .await;
6799 editor
6800 .update(cx, |editor, cx| {
6801 editor.inline_blame_popover.take();
6802 cx.notify();
6803 })
6804 .ok();
6805 });
6806 state.hide_task = Some(hide_task);
6807 }
6808 }
6809
6810 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6811 if self.pending_rename.is_some() {
6812 return None;
6813 }
6814
6815 let provider = self.semantics_provider.clone()?;
6816 let buffer = self.buffer.read(cx);
6817 let newest_selection = self.selections.newest_anchor().clone();
6818 let cursor_position = newest_selection.head();
6819 let (cursor_buffer, cursor_buffer_position) =
6820 buffer.text_anchor_for_position(cursor_position, cx)?;
6821 let (tail_buffer, tail_buffer_position) =
6822 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6823 if cursor_buffer != tail_buffer {
6824 return None;
6825 }
6826
6827 let snapshot = cursor_buffer.read(cx).snapshot();
6828 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6829 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6830 if start_word_range != end_word_range {
6831 self.document_highlights_task.take();
6832 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6833 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6834 return None;
6835 }
6836
6837 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6838 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6839 cx.background_executor()
6840 .timer(Duration::from_millis(debounce))
6841 .await;
6842
6843 let highlights = if let Some(highlights) = cx
6844 .update(|cx| {
6845 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6846 })
6847 .ok()
6848 .flatten()
6849 {
6850 highlights.await.log_err()
6851 } else {
6852 None
6853 };
6854
6855 if let Some(highlights) = highlights {
6856 this.update(cx, |this, cx| {
6857 if this.pending_rename.is_some() {
6858 return;
6859 }
6860
6861 let buffer = this.buffer.read(cx);
6862 if buffer
6863 .text_anchor_for_position(cursor_position, cx)
6864 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6865 {
6866 return;
6867 }
6868
6869 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6870 let mut write_ranges = Vec::new();
6871 let mut read_ranges = Vec::new();
6872 for highlight in highlights {
6873 let buffer_id = cursor_buffer.read(cx).remote_id();
6874 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6875 {
6876 let start = highlight
6877 .range
6878 .start
6879 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6880 let end = highlight
6881 .range
6882 .end
6883 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6884 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6885 continue;
6886 }
6887
6888 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6889 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6890 write_ranges.push(range);
6891 } else {
6892 read_ranges.push(range);
6893 }
6894 }
6895 }
6896
6897 this.highlight_background::<DocumentHighlightRead>(
6898 &read_ranges,
6899 |theme| theme.colors().editor_document_highlight_read_background,
6900 cx,
6901 );
6902 this.highlight_background::<DocumentHighlightWrite>(
6903 &write_ranges,
6904 |theme| theme.colors().editor_document_highlight_write_background,
6905 cx,
6906 );
6907 cx.notify();
6908 })
6909 .log_err();
6910 }
6911 }));
6912 None
6913 }
6914
6915 fn prepare_highlight_query_from_selection(
6916 &mut self,
6917 cx: &mut Context<Editor>,
6918 ) -> Option<(String, Range<Anchor>)> {
6919 if matches!(self.mode, EditorMode::SingleLine) {
6920 return None;
6921 }
6922 if !EditorSettings::get_global(cx).selection_highlight {
6923 return None;
6924 }
6925 if self.selections.count() != 1 || self.selections.line_mode() {
6926 return None;
6927 }
6928 let selection = self.selections.newest_anchor();
6929 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6930 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6931 ..selection.end.to_point(&multi_buffer_snapshot);
6932 // If the selection spans multiple rows OR it is empty
6933 if selection_point_range.start.row != selection_point_range.end.row
6934 || selection_point_range.start.column == selection_point_range.end.column
6935 {
6936 return None;
6937 }
6938
6939 let query = multi_buffer_snapshot
6940 .text_for_range(selection.range())
6941 .collect::<String>();
6942 if query.trim().is_empty() {
6943 return None;
6944 }
6945 Some((query, selection.range()))
6946 }
6947
6948 fn update_selection_occurrence_highlights(
6949 &mut self,
6950 query_text: String,
6951 query_range: Range<Anchor>,
6952 multi_buffer_range_to_query: Range<Point>,
6953 use_debounce: bool,
6954 window: &mut Window,
6955 cx: &mut Context<Editor>,
6956 ) -> Task<()> {
6957 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6958 cx.spawn_in(window, async move |editor, cx| {
6959 if use_debounce {
6960 cx.background_executor()
6961 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6962 .await;
6963 }
6964 let match_task = cx.background_spawn(async move {
6965 let buffer_ranges = multi_buffer_snapshot
6966 .range_to_buffer_ranges(multi_buffer_range_to_query)
6967 .into_iter()
6968 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6969 let mut match_ranges = Vec::new();
6970 let Ok(regex) = project::search::SearchQuery::text(
6971 query_text.clone(),
6972 false,
6973 false,
6974 false,
6975 Default::default(),
6976 Default::default(),
6977 false,
6978 None,
6979 ) else {
6980 return Vec::default();
6981 };
6982 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6983 match_ranges.extend(
6984 regex
6985 .search(buffer_snapshot, Some(search_range.clone()))
6986 .await
6987 .into_iter()
6988 .filter_map(|match_range| {
6989 let match_start = buffer_snapshot
6990 .anchor_after(search_range.start + match_range.start);
6991 let match_end = buffer_snapshot
6992 .anchor_before(search_range.start + match_range.end);
6993 let match_anchor_range = Anchor::range_in_buffer(
6994 excerpt_id,
6995 buffer_snapshot.remote_id(),
6996 match_start..match_end,
6997 );
6998 (match_anchor_range != query_range).then_some(match_anchor_range)
6999 }),
7000 );
7001 }
7002 match_ranges
7003 });
7004 let match_ranges = match_task.await;
7005 editor
7006 .update_in(cx, |editor, _, cx| {
7007 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7008 if !match_ranges.is_empty() {
7009 editor.highlight_background::<SelectedTextHighlight>(
7010 &match_ranges,
7011 |theme| theme.colors().editor_document_highlight_bracket_background,
7012 cx,
7013 )
7014 }
7015 })
7016 .log_err();
7017 })
7018 }
7019
7020 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7021 struct NewlineFold;
7022 let type_id = std::any::TypeId::of::<NewlineFold>();
7023 if !self.mode.is_single_line() {
7024 return;
7025 }
7026 let snapshot = self.snapshot(window, cx);
7027 if snapshot.buffer_snapshot().max_point().row == 0 {
7028 return;
7029 }
7030 let task = cx.background_spawn(async move {
7031 let new_newlines = snapshot
7032 .buffer_chars_at(0)
7033 .filter_map(|(c, i)| {
7034 if c == '\n' {
7035 Some(
7036 snapshot.buffer_snapshot().anchor_after(i)
7037 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7038 )
7039 } else {
7040 None
7041 }
7042 })
7043 .collect::<Vec<_>>();
7044 let existing_newlines = snapshot
7045 .folds_in_range(0..snapshot.buffer_snapshot().len())
7046 .filter_map(|fold| {
7047 if fold.placeholder.type_tag == Some(type_id) {
7048 Some(fold.range.start..fold.range.end)
7049 } else {
7050 None
7051 }
7052 })
7053 .collect::<Vec<_>>();
7054
7055 (new_newlines, existing_newlines)
7056 });
7057 self.folding_newlines = cx.spawn(async move |this, cx| {
7058 let (new_newlines, existing_newlines) = task.await;
7059 if new_newlines == existing_newlines {
7060 return;
7061 }
7062 let placeholder = FoldPlaceholder {
7063 render: Arc::new(move |_, _, cx| {
7064 div()
7065 .bg(cx.theme().status().hint_background)
7066 .border_b_1()
7067 .size_full()
7068 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7069 .border_color(cx.theme().status().hint)
7070 .child("\\n")
7071 .into_any()
7072 }),
7073 constrain_width: false,
7074 merge_adjacent: false,
7075 type_tag: Some(type_id),
7076 };
7077 let creases = new_newlines
7078 .into_iter()
7079 .map(|range| Crease::simple(range, placeholder.clone()))
7080 .collect();
7081 this.update(cx, |this, cx| {
7082 this.display_map.update(cx, |display_map, cx| {
7083 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7084 display_map.fold(creases, cx);
7085 });
7086 })
7087 .ok();
7088 });
7089 }
7090
7091 fn refresh_selected_text_highlights(
7092 &mut self,
7093 on_buffer_edit: bool,
7094 window: &mut Window,
7095 cx: &mut Context<Editor>,
7096 ) {
7097 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7098 else {
7099 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7100 self.quick_selection_highlight_task.take();
7101 self.debounced_selection_highlight_task.take();
7102 return;
7103 };
7104 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7105 if on_buffer_edit
7106 || self
7107 .quick_selection_highlight_task
7108 .as_ref()
7109 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7110 {
7111 let multi_buffer_visible_start = self
7112 .scroll_manager
7113 .anchor()
7114 .anchor
7115 .to_point(&multi_buffer_snapshot);
7116 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7117 multi_buffer_visible_start
7118 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7119 Bias::Left,
7120 );
7121 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7122 self.quick_selection_highlight_task = Some((
7123 query_range.clone(),
7124 self.update_selection_occurrence_highlights(
7125 query_text.clone(),
7126 query_range.clone(),
7127 multi_buffer_visible_range,
7128 false,
7129 window,
7130 cx,
7131 ),
7132 ));
7133 }
7134 if on_buffer_edit
7135 || self
7136 .debounced_selection_highlight_task
7137 .as_ref()
7138 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7139 {
7140 let multi_buffer_start = multi_buffer_snapshot
7141 .anchor_before(0)
7142 .to_point(&multi_buffer_snapshot);
7143 let multi_buffer_end = multi_buffer_snapshot
7144 .anchor_after(multi_buffer_snapshot.len())
7145 .to_point(&multi_buffer_snapshot);
7146 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7147 self.debounced_selection_highlight_task = Some((
7148 query_range.clone(),
7149 self.update_selection_occurrence_highlights(
7150 query_text,
7151 query_range,
7152 multi_buffer_full_range,
7153 true,
7154 window,
7155 cx,
7156 ),
7157 ));
7158 }
7159 }
7160
7161 pub fn refresh_edit_prediction(
7162 &mut self,
7163 debounce: bool,
7164 user_requested: bool,
7165 window: &mut Window,
7166 cx: &mut Context<Self>,
7167 ) -> Option<()> {
7168 if DisableAiSettings::get_global(cx).disable_ai {
7169 return None;
7170 }
7171
7172 let provider = self.edit_prediction_provider()?;
7173 let cursor = self.selections.newest_anchor().head();
7174 let (buffer, cursor_buffer_position) =
7175 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7176
7177 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7178 self.discard_edit_prediction(false, cx);
7179 return None;
7180 }
7181
7182 self.update_visible_edit_prediction(window, cx);
7183
7184 if !user_requested
7185 && (!self.should_show_edit_predictions()
7186 || !self.is_focused(window)
7187 || buffer.read(cx).is_empty())
7188 {
7189 self.discard_edit_prediction(false, cx);
7190 return None;
7191 }
7192
7193 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7194 Some(())
7195 }
7196
7197 fn show_edit_predictions_in_menu(&self) -> bool {
7198 match self.edit_prediction_settings {
7199 EditPredictionSettings::Disabled => false,
7200 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7201 }
7202 }
7203
7204 pub fn edit_predictions_enabled(&self) -> bool {
7205 match self.edit_prediction_settings {
7206 EditPredictionSettings::Disabled => false,
7207 EditPredictionSettings::Enabled { .. } => true,
7208 }
7209 }
7210
7211 fn edit_prediction_requires_modifier(&self) -> bool {
7212 match self.edit_prediction_settings {
7213 EditPredictionSettings::Disabled => false,
7214 EditPredictionSettings::Enabled {
7215 preview_requires_modifier,
7216 ..
7217 } => preview_requires_modifier,
7218 }
7219 }
7220
7221 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7222 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7223 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7224 self.discard_edit_prediction(false, cx);
7225 } else {
7226 let selection = self.selections.newest_anchor();
7227 let cursor = selection.head();
7228
7229 if let Some((buffer, cursor_buffer_position)) =
7230 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7231 {
7232 self.edit_prediction_settings =
7233 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7234 }
7235 }
7236 }
7237
7238 fn edit_prediction_settings_at_position(
7239 &self,
7240 buffer: &Entity<Buffer>,
7241 buffer_position: language::Anchor,
7242 cx: &App,
7243 ) -> EditPredictionSettings {
7244 if !self.mode.is_full()
7245 || !self.show_edit_predictions_override.unwrap_or(true)
7246 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7247 {
7248 return EditPredictionSettings::Disabled;
7249 }
7250
7251 let buffer = buffer.read(cx);
7252
7253 let file = buffer.file();
7254
7255 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7256 return EditPredictionSettings::Disabled;
7257 };
7258
7259 let by_provider = matches!(
7260 self.menu_edit_predictions_policy,
7261 MenuEditPredictionsPolicy::ByProvider
7262 );
7263
7264 let show_in_menu = by_provider
7265 && self
7266 .edit_prediction_provider
7267 .as_ref()
7268 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7269
7270 let preview_requires_modifier =
7271 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7272
7273 EditPredictionSettings::Enabled {
7274 show_in_menu,
7275 preview_requires_modifier,
7276 }
7277 }
7278
7279 fn should_show_edit_predictions(&self) -> bool {
7280 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7281 }
7282
7283 pub fn edit_prediction_preview_is_active(&self) -> bool {
7284 matches!(
7285 self.edit_prediction_preview,
7286 EditPredictionPreview::Active { .. }
7287 )
7288 }
7289
7290 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7291 let cursor = self.selections.newest_anchor().head();
7292 if let Some((buffer, cursor_position)) =
7293 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7294 {
7295 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7296 } else {
7297 false
7298 }
7299 }
7300
7301 pub fn supports_minimap(&self, cx: &App) -> bool {
7302 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7303 }
7304
7305 fn edit_predictions_enabled_in_buffer(
7306 &self,
7307 buffer: &Entity<Buffer>,
7308 buffer_position: language::Anchor,
7309 cx: &App,
7310 ) -> bool {
7311 maybe!({
7312 if self.read_only(cx) {
7313 return Some(false);
7314 }
7315 let provider = self.edit_prediction_provider()?;
7316 if !provider.is_enabled(buffer, buffer_position, cx) {
7317 return Some(false);
7318 }
7319 let buffer = buffer.read(cx);
7320 let Some(file) = buffer.file() else {
7321 return Some(true);
7322 };
7323 let settings = all_language_settings(Some(file), cx);
7324 Some(settings.edit_predictions_enabled_for_file(file, cx))
7325 })
7326 .unwrap_or(false)
7327 }
7328
7329 fn cycle_edit_prediction(
7330 &mut self,
7331 direction: Direction,
7332 window: &mut Window,
7333 cx: &mut Context<Self>,
7334 ) -> Option<()> {
7335 let provider = self.edit_prediction_provider()?;
7336 let cursor = self.selections.newest_anchor().head();
7337 let (buffer, cursor_buffer_position) =
7338 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7339 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7340 return None;
7341 }
7342
7343 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7344 self.update_visible_edit_prediction(window, cx);
7345
7346 Some(())
7347 }
7348
7349 pub fn show_edit_prediction(
7350 &mut self,
7351 _: &ShowEditPrediction,
7352 window: &mut Window,
7353 cx: &mut Context<Self>,
7354 ) {
7355 if !self.has_active_edit_prediction() {
7356 self.refresh_edit_prediction(false, true, window, cx);
7357 return;
7358 }
7359
7360 self.update_visible_edit_prediction(window, cx);
7361 }
7362
7363 pub fn display_cursor_names(
7364 &mut self,
7365 _: &DisplayCursorNames,
7366 window: &mut Window,
7367 cx: &mut Context<Self>,
7368 ) {
7369 self.show_cursor_names(window, cx);
7370 }
7371
7372 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7373 self.show_cursor_names = true;
7374 cx.notify();
7375 cx.spawn_in(window, async move |this, cx| {
7376 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7377 this.update(cx, |this, cx| {
7378 this.show_cursor_names = false;
7379 cx.notify()
7380 })
7381 .ok()
7382 })
7383 .detach();
7384 }
7385
7386 pub fn next_edit_prediction(
7387 &mut self,
7388 _: &NextEditPrediction,
7389 window: &mut Window,
7390 cx: &mut Context<Self>,
7391 ) {
7392 if self.has_active_edit_prediction() {
7393 self.cycle_edit_prediction(Direction::Next, window, cx);
7394 } else {
7395 let is_copilot_disabled = self
7396 .refresh_edit_prediction(false, true, window, cx)
7397 .is_none();
7398 if is_copilot_disabled {
7399 cx.propagate();
7400 }
7401 }
7402 }
7403
7404 pub fn previous_edit_prediction(
7405 &mut self,
7406 _: &PreviousEditPrediction,
7407 window: &mut Window,
7408 cx: &mut Context<Self>,
7409 ) {
7410 if self.has_active_edit_prediction() {
7411 self.cycle_edit_prediction(Direction::Prev, window, cx);
7412 } else {
7413 let is_copilot_disabled = self
7414 .refresh_edit_prediction(false, true, window, cx)
7415 .is_none();
7416 if is_copilot_disabled {
7417 cx.propagate();
7418 }
7419 }
7420 }
7421
7422 pub fn accept_edit_prediction(
7423 &mut self,
7424 _: &AcceptEditPrediction,
7425 window: &mut Window,
7426 cx: &mut Context<Self>,
7427 ) {
7428 if self.show_edit_predictions_in_menu() {
7429 self.hide_context_menu(window, cx);
7430 }
7431
7432 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7433 return;
7434 };
7435
7436 match &active_edit_prediction.completion {
7437 EditPrediction::MoveWithin { target, .. } => {
7438 let target = *target;
7439
7440 if let Some(position_map) = &self.last_position_map {
7441 if position_map
7442 .visible_row_range
7443 .contains(&target.to_display_point(&position_map.snapshot).row())
7444 || !self.edit_prediction_requires_modifier()
7445 {
7446 self.unfold_ranges(&[target..target], true, false, cx);
7447 // Note that this is also done in vim's handler of the Tab action.
7448 self.change_selections(
7449 SelectionEffects::scroll(Autoscroll::newest()),
7450 window,
7451 cx,
7452 |selections| {
7453 selections.select_anchor_ranges([target..target]);
7454 },
7455 );
7456 self.clear_row_highlights::<EditPredictionPreview>();
7457
7458 self.edit_prediction_preview
7459 .set_previous_scroll_position(None);
7460 } else {
7461 self.edit_prediction_preview
7462 .set_previous_scroll_position(Some(
7463 position_map.snapshot.scroll_anchor,
7464 ));
7465
7466 self.highlight_rows::<EditPredictionPreview>(
7467 target..target,
7468 cx.theme().colors().editor_highlighted_line_background,
7469 RowHighlightOptions {
7470 autoscroll: true,
7471 ..Default::default()
7472 },
7473 cx,
7474 );
7475 self.request_autoscroll(Autoscroll::fit(), cx);
7476 }
7477 }
7478 }
7479 EditPrediction::MoveOutside { snapshot, target } => {
7480 if let Some(workspace) = self.workspace() {
7481 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7482 .detach_and_log_err(cx);
7483 }
7484 }
7485 EditPrediction::Edit { edits, .. } => {
7486 self.report_edit_prediction_event(
7487 active_edit_prediction.completion_id.clone(),
7488 true,
7489 cx,
7490 );
7491
7492 if let Some(provider) = self.edit_prediction_provider() {
7493 provider.accept(cx);
7494 }
7495
7496 // Store the transaction ID and selections before applying the edit
7497 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7498
7499 let snapshot = self.buffer.read(cx).snapshot(cx);
7500 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7501
7502 self.buffer.update(cx, |buffer, cx| {
7503 buffer.edit(edits.iter().cloned(), None, cx)
7504 });
7505
7506 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7507 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7508 });
7509
7510 let selections = self.selections.disjoint_anchors_arc();
7511 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7512 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7513 if has_new_transaction {
7514 self.selection_history
7515 .insert_transaction(transaction_id_now, selections);
7516 }
7517 }
7518
7519 self.update_visible_edit_prediction(window, cx);
7520 if self.active_edit_prediction.is_none() {
7521 self.refresh_edit_prediction(true, true, window, cx);
7522 }
7523
7524 cx.notify();
7525 }
7526 }
7527
7528 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7529 }
7530
7531 pub fn accept_partial_edit_prediction(
7532 &mut self,
7533 _: &AcceptPartialEditPrediction,
7534 window: &mut Window,
7535 cx: &mut Context<Self>,
7536 ) {
7537 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7538 return;
7539 };
7540 if self.selections.count() != 1 {
7541 return;
7542 }
7543
7544 match &active_edit_prediction.completion {
7545 EditPrediction::MoveWithin { target, .. } => {
7546 let target = *target;
7547 self.change_selections(
7548 SelectionEffects::scroll(Autoscroll::newest()),
7549 window,
7550 cx,
7551 |selections| {
7552 selections.select_anchor_ranges([target..target]);
7553 },
7554 );
7555 }
7556 EditPrediction::MoveOutside { snapshot, target } => {
7557 if let Some(workspace) = self.workspace() {
7558 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7559 .detach_and_log_err(cx);
7560 }
7561 }
7562 EditPrediction::Edit { edits, .. } => {
7563 self.report_edit_prediction_event(
7564 active_edit_prediction.completion_id.clone(),
7565 true,
7566 cx,
7567 );
7568
7569 // Find an insertion that starts at the cursor position.
7570 let snapshot = self.buffer.read(cx).snapshot(cx);
7571 let cursor_offset = self.selections.newest::<usize>(cx).head();
7572 let insertion = edits.iter().find_map(|(range, text)| {
7573 let range = range.to_offset(&snapshot);
7574 if range.is_empty() && range.start == cursor_offset {
7575 Some(text)
7576 } else {
7577 None
7578 }
7579 });
7580
7581 if let Some(text) = insertion {
7582 let mut partial_completion = text
7583 .chars()
7584 .by_ref()
7585 .take_while(|c| c.is_alphabetic())
7586 .collect::<String>();
7587 if partial_completion.is_empty() {
7588 partial_completion = text
7589 .chars()
7590 .by_ref()
7591 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7592 .collect::<String>();
7593 }
7594
7595 cx.emit(EditorEvent::InputHandled {
7596 utf16_range_to_replace: None,
7597 text: partial_completion.clone().into(),
7598 });
7599
7600 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7601
7602 self.refresh_edit_prediction(true, true, window, cx);
7603 cx.notify();
7604 } else {
7605 self.accept_edit_prediction(&Default::default(), window, cx);
7606 }
7607 }
7608 }
7609 }
7610
7611 fn discard_edit_prediction(
7612 &mut self,
7613 should_report_edit_prediction_event: bool,
7614 cx: &mut Context<Self>,
7615 ) -> bool {
7616 if should_report_edit_prediction_event {
7617 let completion_id = self
7618 .active_edit_prediction
7619 .as_ref()
7620 .and_then(|active_completion| active_completion.completion_id.clone());
7621
7622 self.report_edit_prediction_event(completion_id, false, cx);
7623 }
7624
7625 if let Some(provider) = self.edit_prediction_provider() {
7626 provider.discard(cx);
7627 }
7628
7629 self.take_active_edit_prediction(cx)
7630 }
7631
7632 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7633 let Some(provider) = self.edit_prediction_provider() else {
7634 return;
7635 };
7636
7637 let Some((_, buffer, _)) = self
7638 .buffer
7639 .read(cx)
7640 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7641 else {
7642 return;
7643 };
7644
7645 let extension = buffer
7646 .read(cx)
7647 .file()
7648 .and_then(|file| Some(file.path().extension()?.to_string()));
7649
7650 let event_type = match accepted {
7651 true => "Edit Prediction Accepted",
7652 false => "Edit Prediction Discarded",
7653 };
7654 telemetry::event!(
7655 event_type,
7656 provider = provider.name(),
7657 prediction_id = id,
7658 suggestion_accepted = accepted,
7659 file_extension = extension,
7660 );
7661 }
7662
7663 fn open_editor_at_anchor(
7664 snapshot: &language::BufferSnapshot,
7665 target: language::Anchor,
7666 workspace: &Entity<Workspace>,
7667 window: &mut Window,
7668 cx: &mut App,
7669 ) -> Task<Result<()>> {
7670 workspace.update(cx, |workspace, cx| {
7671 let path = snapshot.file().map(|file| file.full_path(cx));
7672 let Some(path) =
7673 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7674 else {
7675 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7676 };
7677 let target = text::ToPoint::to_point(&target, snapshot);
7678 let item = workspace.open_path(path, None, true, window, cx);
7679 window.spawn(cx, async move |cx| {
7680 let Some(editor) = item.await?.downcast::<Editor>() else {
7681 return Ok(());
7682 };
7683 editor
7684 .update_in(cx, |editor, window, cx| {
7685 editor.go_to_singleton_buffer_point(target, window, cx);
7686 })
7687 .ok();
7688 anyhow::Ok(())
7689 })
7690 })
7691 }
7692
7693 pub fn has_active_edit_prediction(&self) -> bool {
7694 self.active_edit_prediction.is_some()
7695 }
7696
7697 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7698 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7699 return false;
7700 };
7701
7702 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7703 self.clear_highlights::<EditPredictionHighlight>(cx);
7704 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7705 true
7706 }
7707
7708 /// Returns true when we're displaying the edit prediction popover below the cursor
7709 /// like we are not previewing and the LSP autocomplete menu is visible
7710 /// or we are in `when_holding_modifier` mode.
7711 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7712 if self.edit_prediction_preview_is_active()
7713 || !self.show_edit_predictions_in_menu()
7714 || !self.edit_predictions_enabled()
7715 {
7716 return false;
7717 }
7718
7719 if self.has_visible_completions_menu() {
7720 return true;
7721 }
7722
7723 has_completion && self.edit_prediction_requires_modifier()
7724 }
7725
7726 fn handle_modifiers_changed(
7727 &mut self,
7728 modifiers: Modifiers,
7729 position_map: &PositionMap,
7730 window: &mut Window,
7731 cx: &mut Context<Self>,
7732 ) {
7733 if self.show_edit_predictions_in_menu() {
7734 self.update_edit_prediction_preview(&modifiers, window, cx);
7735 }
7736
7737 self.update_selection_mode(&modifiers, position_map, window, cx);
7738
7739 let mouse_position = window.mouse_position();
7740 if !position_map.text_hitbox.is_hovered(window) {
7741 return;
7742 }
7743
7744 self.update_hovered_link(
7745 position_map.point_for_position(mouse_position),
7746 &position_map.snapshot,
7747 modifiers,
7748 window,
7749 cx,
7750 )
7751 }
7752
7753 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7754 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7755 if invert {
7756 match multi_cursor_setting {
7757 MultiCursorModifier::Alt => modifiers.alt,
7758 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7759 }
7760 } else {
7761 match multi_cursor_setting {
7762 MultiCursorModifier::Alt => modifiers.secondary(),
7763 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7764 }
7765 }
7766 }
7767
7768 fn columnar_selection_mode(
7769 modifiers: &Modifiers,
7770 cx: &mut Context<Self>,
7771 ) -> Option<ColumnarMode> {
7772 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7773 if Self::multi_cursor_modifier(false, modifiers, cx) {
7774 Some(ColumnarMode::FromMouse)
7775 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7776 Some(ColumnarMode::FromSelection)
7777 } else {
7778 None
7779 }
7780 } else {
7781 None
7782 }
7783 }
7784
7785 fn update_selection_mode(
7786 &mut self,
7787 modifiers: &Modifiers,
7788 position_map: &PositionMap,
7789 window: &mut Window,
7790 cx: &mut Context<Self>,
7791 ) {
7792 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7793 return;
7794 };
7795 if self.selections.pending_anchor().is_none() {
7796 return;
7797 }
7798
7799 let mouse_position = window.mouse_position();
7800 let point_for_position = position_map.point_for_position(mouse_position);
7801 let position = point_for_position.previous_valid;
7802
7803 self.select(
7804 SelectPhase::BeginColumnar {
7805 position,
7806 reset: false,
7807 mode,
7808 goal_column: point_for_position.exact_unclipped.column(),
7809 },
7810 window,
7811 cx,
7812 );
7813 }
7814
7815 fn update_edit_prediction_preview(
7816 &mut self,
7817 modifiers: &Modifiers,
7818 window: &mut Window,
7819 cx: &mut Context<Self>,
7820 ) {
7821 let mut modifiers_held = false;
7822 if let Some(accept_keystroke) = self
7823 .accept_edit_prediction_keybind(false, window, cx)
7824 .keystroke()
7825 {
7826 modifiers_held = modifiers_held
7827 || (accept_keystroke.modifiers() == modifiers
7828 && accept_keystroke.modifiers().modified());
7829 };
7830 if let Some(accept_partial_keystroke) = self
7831 .accept_edit_prediction_keybind(true, window, cx)
7832 .keystroke()
7833 {
7834 modifiers_held = modifiers_held
7835 || (accept_partial_keystroke.modifiers() == modifiers
7836 && accept_partial_keystroke.modifiers().modified());
7837 }
7838
7839 if modifiers_held {
7840 if matches!(
7841 self.edit_prediction_preview,
7842 EditPredictionPreview::Inactive { .. }
7843 ) {
7844 self.edit_prediction_preview = EditPredictionPreview::Active {
7845 previous_scroll_position: None,
7846 since: Instant::now(),
7847 };
7848
7849 self.update_visible_edit_prediction(window, cx);
7850 cx.notify();
7851 }
7852 } else if let EditPredictionPreview::Active {
7853 previous_scroll_position,
7854 since,
7855 } = self.edit_prediction_preview
7856 {
7857 if let (Some(previous_scroll_position), Some(position_map)) =
7858 (previous_scroll_position, self.last_position_map.as_ref())
7859 {
7860 self.set_scroll_position(
7861 previous_scroll_position
7862 .scroll_position(&position_map.snapshot.display_snapshot),
7863 window,
7864 cx,
7865 );
7866 }
7867
7868 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7869 released_too_fast: since.elapsed() < Duration::from_millis(200),
7870 };
7871 self.clear_row_highlights::<EditPredictionPreview>();
7872 self.update_visible_edit_prediction(window, cx);
7873 cx.notify();
7874 }
7875 }
7876
7877 fn update_visible_edit_prediction(
7878 &mut self,
7879 _window: &mut Window,
7880 cx: &mut Context<Self>,
7881 ) -> Option<()> {
7882 if DisableAiSettings::get_global(cx).disable_ai {
7883 return None;
7884 }
7885
7886 if self.ime_transaction.is_some() {
7887 self.discard_edit_prediction(false, cx);
7888 return None;
7889 }
7890
7891 let selection = self.selections.newest_anchor();
7892 let cursor = selection.head();
7893 let multibuffer = self.buffer.read(cx).snapshot(cx);
7894 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7895 let excerpt_id = cursor.excerpt_id;
7896
7897 let show_in_menu = self.show_edit_predictions_in_menu();
7898 let completions_menu_has_precedence = !show_in_menu
7899 && (self.context_menu.borrow().is_some()
7900 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7901
7902 if completions_menu_has_precedence
7903 || !offset_selection.is_empty()
7904 || self
7905 .active_edit_prediction
7906 .as_ref()
7907 .is_some_and(|completion| {
7908 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7909 return false;
7910 };
7911 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7912 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7913 !invalidation_range.contains(&offset_selection.head())
7914 })
7915 {
7916 self.discard_edit_prediction(false, cx);
7917 return None;
7918 }
7919
7920 self.take_active_edit_prediction(cx);
7921 let Some(provider) = self.edit_prediction_provider() else {
7922 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7923 return None;
7924 };
7925
7926 let (buffer, cursor_buffer_position) =
7927 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7928
7929 self.edit_prediction_settings =
7930 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7931
7932 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7933
7934 if self.edit_prediction_indent_conflict {
7935 let cursor_point = cursor.to_point(&multibuffer);
7936
7937 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7938
7939 if let Some((_, indent)) = indents.iter().next()
7940 && indent.len == cursor_point.column
7941 {
7942 self.edit_prediction_indent_conflict = false;
7943 }
7944 }
7945
7946 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7947
7948 let (completion_id, edits, edit_preview) = match edit_prediction {
7949 edit_prediction::EditPrediction::Local {
7950 id,
7951 edits,
7952 edit_preview,
7953 } => (id, edits, edit_preview),
7954 edit_prediction::EditPrediction::Jump {
7955 id,
7956 snapshot,
7957 target,
7958 } => {
7959 self.stale_edit_prediction_in_menu = None;
7960 self.active_edit_prediction = Some(EditPredictionState {
7961 inlay_ids: vec![],
7962 completion: EditPrediction::MoveOutside { snapshot, target },
7963 completion_id: id,
7964 invalidation_range: None,
7965 });
7966 cx.notify();
7967 return Some(());
7968 }
7969 };
7970
7971 let edits = edits
7972 .into_iter()
7973 .flat_map(|(range, new_text)| {
7974 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7975 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7976 Some((start..end, new_text))
7977 })
7978 .collect::<Vec<_>>();
7979 if edits.is_empty() {
7980 return None;
7981 }
7982
7983 let first_edit_start = edits.first().unwrap().0.start;
7984 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7985 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7986
7987 let last_edit_end = edits.last().unwrap().0.end;
7988 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7989 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7990
7991 let cursor_row = cursor.to_point(&multibuffer).row;
7992
7993 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7994
7995 let mut inlay_ids = Vec::new();
7996 let invalidation_row_range;
7997 let move_invalidation_row_range = if cursor_row < edit_start_row {
7998 Some(cursor_row..edit_end_row)
7999 } else if cursor_row > edit_end_row {
8000 Some(edit_start_row..cursor_row)
8001 } else {
8002 None
8003 };
8004 let supports_jump = self
8005 .edit_prediction_provider
8006 .as_ref()
8007 .map(|provider| provider.provider.supports_jump_to_edit())
8008 .unwrap_or(true);
8009
8010 let is_move = supports_jump
8011 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8012 let completion = if is_move {
8013 invalidation_row_range =
8014 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8015 let target = first_edit_start;
8016 EditPrediction::MoveWithin { target, snapshot }
8017 } else {
8018 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8019 && !self.edit_predictions_hidden_for_vim_mode;
8020
8021 if show_completions_in_buffer {
8022 if edits
8023 .iter()
8024 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8025 {
8026 let mut inlays = Vec::new();
8027 for (range, new_text) in &edits {
8028 let inlay = Inlay::edit_prediction(
8029 post_inc(&mut self.next_inlay_id),
8030 range.start,
8031 new_text.as_str(),
8032 );
8033 inlay_ids.push(inlay.id);
8034 inlays.push(inlay);
8035 }
8036
8037 self.splice_inlays(&[], inlays, cx);
8038 } else {
8039 let background_color = cx.theme().status().deleted_background;
8040 self.highlight_text::<EditPredictionHighlight>(
8041 edits.iter().map(|(range, _)| range.clone()).collect(),
8042 HighlightStyle {
8043 background_color: Some(background_color),
8044 ..Default::default()
8045 },
8046 cx,
8047 );
8048 }
8049 }
8050
8051 invalidation_row_range = edit_start_row..edit_end_row;
8052
8053 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8054 if provider.show_tab_accept_marker() {
8055 EditDisplayMode::TabAccept
8056 } else {
8057 EditDisplayMode::Inline
8058 }
8059 } else {
8060 EditDisplayMode::DiffPopover
8061 };
8062
8063 EditPrediction::Edit {
8064 edits,
8065 edit_preview,
8066 display_mode,
8067 snapshot,
8068 }
8069 };
8070
8071 let invalidation_range = multibuffer
8072 .anchor_before(Point::new(invalidation_row_range.start, 0))
8073 ..multibuffer.anchor_after(Point::new(
8074 invalidation_row_range.end,
8075 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8076 ));
8077
8078 self.stale_edit_prediction_in_menu = None;
8079 self.active_edit_prediction = Some(EditPredictionState {
8080 inlay_ids,
8081 completion,
8082 completion_id,
8083 invalidation_range: Some(invalidation_range),
8084 });
8085
8086 cx.notify();
8087
8088 Some(())
8089 }
8090
8091 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8092 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8093 }
8094
8095 fn clear_tasks(&mut self) {
8096 self.tasks.clear()
8097 }
8098
8099 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8100 if self.tasks.insert(key, value).is_some() {
8101 // This case should hopefully be rare, but just in case...
8102 log::error!(
8103 "multiple different run targets found on a single line, only the last target will be rendered"
8104 )
8105 }
8106 }
8107
8108 /// Get all display points of breakpoints that will be rendered within editor
8109 ///
8110 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8111 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8112 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8113 fn active_breakpoints(
8114 &self,
8115 range: Range<DisplayRow>,
8116 window: &mut Window,
8117 cx: &mut Context<Self>,
8118 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8119 let mut breakpoint_display_points = HashMap::default();
8120
8121 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8122 return breakpoint_display_points;
8123 };
8124
8125 let snapshot = self.snapshot(window, cx);
8126
8127 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8128 let Some(project) = self.project() else {
8129 return breakpoint_display_points;
8130 };
8131
8132 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8133 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8134
8135 for (buffer_snapshot, range, excerpt_id) in
8136 multi_buffer_snapshot.range_to_buffer_ranges(range)
8137 {
8138 let Some(buffer) = project
8139 .read(cx)
8140 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8141 else {
8142 continue;
8143 };
8144 let breakpoints = breakpoint_store.read(cx).breakpoints(
8145 &buffer,
8146 Some(
8147 buffer_snapshot.anchor_before(range.start)
8148 ..buffer_snapshot.anchor_after(range.end),
8149 ),
8150 buffer_snapshot,
8151 cx,
8152 );
8153 for (breakpoint, state) in breakpoints {
8154 let multi_buffer_anchor =
8155 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8156 let position = multi_buffer_anchor
8157 .to_point(multi_buffer_snapshot)
8158 .to_display_point(&snapshot);
8159
8160 breakpoint_display_points.insert(
8161 position.row(),
8162 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8163 );
8164 }
8165 }
8166
8167 breakpoint_display_points
8168 }
8169
8170 fn breakpoint_context_menu(
8171 &self,
8172 anchor: Anchor,
8173 window: &mut Window,
8174 cx: &mut Context<Self>,
8175 ) -> Entity<ui::ContextMenu> {
8176 let weak_editor = cx.weak_entity();
8177 let focus_handle = self.focus_handle(cx);
8178
8179 let row = self
8180 .buffer
8181 .read(cx)
8182 .snapshot(cx)
8183 .summary_for_anchor::<Point>(&anchor)
8184 .row;
8185
8186 let breakpoint = self
8187 .breakpoint_at_row(row, window, cx)
8188 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8189
8190 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8191 "Edit Log Breakpoint"
8192 } else {
8193 "Set Log Breakpoint"
8194 };
8195
8196 let condition_breakpoint_msg = if breakpoint
8197 .as_ref()
8198 .is_some_and(|bp| bp.1.condition.is_some())
8199 {
8200 "Edit Condition Breakpoint"
8201 } else {
8202 "Set Condition Breakpoint"
8203 };
8204
8205 let hit_condition_breakpoint_msg = if breakpoint
8206 .as_ref()
8207 .is_some_and(|bp| bp.1.hit_condition.is_some())
8208 {
8209 "Edit Hit Condition Breakpoint"
8210 } else {
8211 "Set Hit Condition Breakpoint"
8212 };
8213
8214 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8215 "Unset Breakpoint"
8216 } else {
8217 "Set Breakpoint"
8218 };
8219
8220 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8221
8222 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8223 BreakpointState::Enabled => Some("Disable"),
8224 BreakpointState::Disabled => Some("Enable"),
8225 });
8226
8227 let (anchor, breakpoint) =
8228 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8229
8230 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8231 menu.on_blur_subscription(Subscription::new(|| {}))
8232 .context(focus_handle)
8233 .when(run_to_cursor, |this| {
8234 let weak_editor = weak_editor.clone();
8235 this.entry("Run to cursor", None, move |window, cx| {
8236 weak_editor
8237 .update(cx, |editor, cx| {
8238 editor.change_selections(
8239 SelectionEffects::no_scroll(),
8240 window,
8241 cx,
8242 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8243 );
8244 })
8245 .ok();
8246
8247 window.dispatch_action(Box::new(RunToCursor), cx);
8248 })
8249 .separator()
8250 })
8251 .when_some(toggle_state_msg, |this, msg| {
8252 this.entry(msg, None, {
8253 let weak_editor = weak_editor.clone();
8254 let breakpoint = breakpoint.clone();
8255 move |_window, cx| {
8256 weak_editor
8257 .update(cx, |this, cx| {
8258 this.edit_breakpoint_at_anchor(
8259 anchor,
8260 breakpoint.as_ref().clone(),
8261 BreakpointEditAction::InvertState,
8262 cx,
8263 );
8264 })
8265 .log_err();
8266 }
8267 })
8268 })
8269 .entry(set_breakpoint_msg, None, {
8270 let weak_editor = weak_editor.clone();
8271 let breakpoint = breakpoint.clone();
8272 move |_window, cx| {
8273 weak_editor
8274 .update(cx, |this, cx| {
8275 this.edit_breakpoint_at_anchor(
8276 anchor,
8277 breakpoint.as_ref().clone(),
8278 BreakpointEditAction::Toggle,
8279 cx,
8280 );
8281 })
8282 .log_err();
8283 }
8284 })
8285 .entry(log_breakpoint_msg, None, {
8286 let breakpoint = breakpoint.clone();
8287 let weak_editor = weak_editor.clone();
8288 move |window, cx| {
8289 weak_editor
8290 .update(cx, |this, cx| {
8291 this.add_edit_breakpoint_block(
8292 anchor,
8293 breakpoint.as_ref(),
8294 BreakpointPromptEditAction::Log,
8295 window,
8296 cx,
8297 );
8298 })
8299 .log_err();
8300 }
8301 })
8302 .entry(condition_breakpoint_msg, None, {
8303 let breakpoint = breakpoint.clone();
8304 let weak_editor = weak_editor.clone();
8305 move |window, cx| {
8306 weak_editor
8307 .update(cx, |this, cx| {
8308 this.add_edit_breakpoint_block(
8309 anchor,
8310 breakpoint.as_ref(),
8311 BreakpointPromptEditAction::Condition,
8312 window,
8313 cx,
8314 );
8315 })
8316 .log_err();
8317 }
8318 })
8319 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8320 weak_editor
8321 .update(cx, |this, cx| {
8322 this.add_edit_breakpoint_block(
8323 anchor,
8324 breakpoint.as_ref(),
8325 BreakpointPromptEditAction::HitCondition,
8326 window,
8327 cx,
8328 );
8329 })
8330 .log_err();
8331 })
8332 })
8333 }
8334
8335 fn render_breakpoint(
8336 &self,
8337 position: Anchor,
8338 row: DisplayRow,
8339 breakpoint: &Breakpoint,
8340 state: Option<BreakpointSessionState>,
8341 cx: &mut Context<Self>,
8342 ) -> IconButton {
8343 let is_rejected = state.is_some_and(|s| !s.verified);
8344 // Is it a breakpoint that shows up when hovering over gutter?
8345 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8346 (false, false),
8347 |PhantomBreakpointIndicator {
8348 is_active,
8349 display_row,
8350 collides_with_existing_breakpoint,
8351 }| {
8352 (
8353 is_active && display_row == row,
8354 collides_with_existing_breakpoint,
8355 )
8356 },
8357 );
8358
8359 let (color, icon) = {
8360 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8361 (false, false) => ui::IconName::DebugBreakpoint,
8362 (true, false) => ui::IconName::DebugLogBreakpoint,
8363 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8364 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8365 };
8366
8367 let color = if is_phantom {
8368 Color::Hint
8369 } else if is_rejected {
8370 Color::Disabled
8371 } else {
8372 Color::Debugger
8373 };
8374
8375 (color, icon)
8376 };
8377
8378 let breakpoint = Arc::from(breakpoint.clone());
8379
8380 let alt_as_text = gpui::Keystroke {
8381 modifiers: Modifiers::secondary_key(),
8382 ..Default::default()
8383 };
8384 let primary_action_text = if breakpoint.is_disabled() {
8385 "Enable breakpoint"
8386 } else if is_phantom && !collides_with_existing {
8387 "Set breakpoint"
8388 } else {
8389 "Unset breakpoint"
8390 };
8391 let focus_handle = self.focus_handle.clone();
8392
8393 let meta = if is_rejected {
8394 SharedString::from("No executable code is associated with this line.")
8395 } else if collides_with_existing && !breakpoint.is_disabled() {
8396 SharedString::from(format!(
8397 "{alt_as_text}-click to disable,\nright-click for more options."
8398 ))
8399 } else {
8400 SharedString::from("Right-click for more options.")
8401 };
8402 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8403 .icon_size(IconSize::XSmall)
8404 .size(ui::ButtonSize::None)
8405 .when(is_rejected, |this| {
8406 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8407 })
8408 .icon_color(color)
8409 .style(ButtonStyle::Transparent)
8410 .on_click(cx.listener({
8411 move |editor, event: &ClickEvent, window, cx| {
8412 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8413 BreakpointEditAction::InvertState
8414 } else {
8415 BreakpointEditAction::Toggle
8416 };
8417
8418 window.focus(&editor.focus_handle(cx));
8419 editor.edit_breakpoint_at_anchor(
8420 position,
8421 breakpoint.as_ref().clone(),
8422 edit_action,
8423 cx,
8424 );
8425 }
8426 }))
8427 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8428 editor.set_breakpoint_context_menu(
8429 row,
8430 Some(position),
8431 event.position(),
8432 window,
8433 cx,
8434 );
8435 }))
8436 .tooltip(move |window, cx| {
8437 Tooltip::with_meta_in(
8438 primary_action_text,
8439 Some(&ToggleBreakpoint),
8440 meta.clone(),
8441 &focus_handle,
8442 window,
8443 cx,
8444 )
8445 })
8446 }
8447
8448 fn build_tasks_context(
8449 project: &Entity<Project>,
8450 buffer: &Entity<Buffer>,
8451 buffer_row: u32,
8452 tasks: &Arc<RunnableTasks>,
8453 cx: &mut Context<Self>,
8454 ) -> Task<Option<task::TaskContext>> {
8455 let position = Point::new(buffer_row, tasks.column);
8456 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8457 let location = Location {
8458 buffer: buffer.clone(),
8459 range: range_start..range_start,
8460 };
8461 // Fill in the environmental variables from the tree-sitter captures
8462 let mut captured_task_variables = TaskVariables::default();
8463 for (capture_name, value) in tasks.extra_variables.clone() {
8464 captured_task_variables.insert(
8465 task::VariableName::Custom(capture_name.into()),
8466 value.clone(),
8467 );
8468 }
8469 project.update(cx, |project, cx| {
8470 project.task_store().update(cx, |task_store, cx| {
8471 task_store.task_context_for_location(captured_task_variables, location, cx)
8472 })
8473 })
8474 }
8475
8476 pub fn spawn_nearest_task(
8477 &mut self,
8478 action: &SpawnNearestTask,
8479 window: &mut Window,
8480 cx: &mut Context<Self>,
8481 ) {
8482 let Some((workspace, _)) = self.workspace.clone() else {
8483 return;
8484 };
8485 let Some(project) = self.project.clone() else {
8486 return;
8487 };
8488
8489 // Try to find a closest, enclosing node using tree-sitter that has a task
8490 let Some((buffer, buffer_row, tasks)) = self
8491 .find_enclosing_node_task(cx)
8492 // Or find the task that's closest in row-distance.
8493 .or_else(|| self.find_closest_task(cx))
8494 else {
8495 return;
8496 };
8497
8498 let reveal_strategy = action.reveal;
8499 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8500 cx.spawn_in(window, async move |_, cx| {
8501 let context = task_context.await?;
8502 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8503
8504 let resolved = &mut resolved_task.resolved;
8505 resolved.reveal = reveal_strategy;
8506
8507 workspace
8508 .update_in(cx, |workspace, window, cx| {
8509 workspace.schedule_resolved_task(
8510 task_source_kind,
8511 resolved_task,
8512 false,
8513 window,
8514 cx,
8515 );
8516 })
8517 .ok()
8518 })
8519 .detach();
8520 }
8521
8522 fn find_closest_task(
8523 &mut self,
8524 cx: &mut Context<Self>,
8525 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8526 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8527
8528 let ((buffer_id, row), tasks) = self
8529 .tasks
8530 .iter()
8531 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8532
8533 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8534 let tasks = Arc::new(tasks.to_owned());
8535 Some((buffer, *row, tasks))
8536 }
8537
8538 fn find_enclosing_node_task(
8539 &mut self,
8540 cx: &mut Context<Self>,
8541 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8542 let snapshot = self.buffer.read(cx).snapshot(cx);
8543 let offset = self.selections.newest::<usize>(cx).head();
8544 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8545 let buffer_id = excerpt.buffer().remote_id();
8546
8547 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8548 let mut cursor = layer.node().walk();
8549
8550 while cursor.goto_first_child_for_byte(offset).is_some() {
8551 if cursor.node().end_byte() == offset {
8552 cursor.goto_next_sibling();
8553 }
8554 }
8555
8556 // Ascend to the smallest ancestor that contains the range and has a task.
8557 loop {
8558 let node = cursor.node();
8559 let node_range = node.byte_range();
8560 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8561
8562 // Check if this node contains our offset
8563 if node_range.start <= offset && node_range.end >= offset {
8564 // If it contains offset, check for task
8565 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8566 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8567 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8568 }
8569 }
8570
8571 if !cursor.goto_parent() {
8572 break;
8573 }
8574 }
8575 None
8576 }
8577
8578 fn render_run_indicator(
8579 &self,
8580 _style: &EditorStyle,
8581 is_active: bool,
8582 row: DisplayRow,
8583 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8584 cx: &mut Context<Self>,
8585 ) -> IconButton {
8586 let color = Color::Muted;
8587 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8588
8589 IconButton::new(
8590 ("run_indicator", row.0 as usize),
8591 ui::IconName::PlayOutlined,
8592 )
8593 .shape(ui::IconButtonShape::Square)
8594 .icon_size(IconSize::XSmall)
8595 .icon_color(color)
8596 .toggle_state(is_active)
8597 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8598 let quick_launch = match e {
8599 ClickEvent::Keyboard(_) => true,
8600 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8601 };
8602
8603 window.focus(&editor.focus_handle(cx));
8604 editor.toggle_code_actions(
8605 &ToggleCodeActions {
8606 deployed_from: Some(CodeActionSource::RunMenu(row)),
8607 quick_launch,
8608 },
8609 window,
8610 cx,
8611 );
8612 }))
8613 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8614 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8615 }))
8616 }
8617
8618 pub fn context_menu_visible(&self) -> bool {
8619 !self.edit_prediction_preview_is_active()
8620 && self
8621 .context_menu
8622 .borrow()
8623 .as_ref()
8624 .is_some_and(|menu| menu.visible())
8625 }
8626
8627 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8628 self.context_menu
8629 .borrow()
8630 .as_ref()
8631 .map(|menu| menu.origin())
8632 }
8633
8634 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8635 self.context_menu_options = Some(options);
8636 }
8637
8638 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8639 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8640
8641 fn render_edit_prediction_popover(
8642 &mut self,
8643 text_bounds: &Bounds<Pixels>,
8644 content_origin: gpui::Point<Pixels>,
8645 right_margin: Pixels,
8646 editor_snapshot: &EditorSnapshot,
8647 visible_row_range: Range<DisplayRow>,
8648 scroll_top: ScrollOffset,
8649 scroll_bottom: ScrollOffset,
8650 line_layouts: &[LineWithInvisibles],
8651 line_height: Pixels,
8652 scroll_position: gpui::Point<ScrollOffset>,
8653 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8654 newest_selection_head: Option<DisplayPoint>,
8655 editor_width: Pixels,
8656 style: &EditorStyle,
8657 window: &mut Window,
8658 cx: &mut App,
8659 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8660 if self.mode().is_minimap() {
8661 return None;
8662 }
8663 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8664
8665 if self.edit_prediction_visible_in_cursor_popover(true) {
8666 return None;
8667 }
8668
8669 match &active_edit_prediction.completion {
8670 EditPrediction::MoveWithin { target, .. } => {
8671 let target_display_point = target.to_display_point(editor_snapshot);
8672
8673 if self.edit_prediction_requires_modifier() {
8674 if !self.edit_prediction_preview_is_active() {
8675 return None;
8676 }
8677
8678 self.render_edit_prediction_modifier_jump_popover(
8679 text_bounds,
8680 content_origin,
8681 visible_row_range,
8682 line_layouts,
8683 line_height,
8684 scroll_pixel_position,
8685 newest_selection_head,
8686 target_display_point,
8687 window,
8688 cx,
8689 )
8690 } else {
8691 self.render_edit_prediction_eager_jump_popover(
8692 text_bounds,
8693 content_origin,
8694 editor_snapshot,
8695 visible_row_range,
8696 scroll_top,
8697 scroll_bottom,
8698 line_height,
8699 scroll_pixel_position,
8700 target_display_point,
8701 editor_width,
8702 window,
8703 cx,
8704 )
8705 }
8706 }
8707 EditPrediction::Edit {
8708 display_mode: EditDisplayMode::Inline,
8709 ..
8710 } => None,
8711 EditPrediction::Edit {
8712 display_mode: EditDisplayMode::TabAccept,
8713 edits,
8714 ..
8715 } => {
8716 let range = &edits.first()?.0;
8717 let target_display_point = range.end.to_display_point(editor_snapshot);
8718
8719 self.render_edit_prediction_end_of_line_popover(
8720 "Accept",
8721 editor_snapshot,
8722 visible_row_range,
8723 target_display_point,
8724 line_height,
8725 scroll_pixel_position,
8726 content_origin,
8727 editor_width,
8728 window,
8729 cx,
8730 )
8731 }
8732 EditPrediction::Edit {
8733 edits,
8734 edit_preview,
8735 display_mode: EditDisplayMode::DiffPopover,
8736 snapshot,
8737 } => self.render_edit_prediction_diff_popover(
8738 text_bounds,
8739 content_origin,
8740 right_margin,
8741 editor_snapshot,
8742 visible_row_range,
8743 line_layouts,
8744 line_height,
8745 scroll_position,
8746 scroll_pixel_position,
8747 newest_selection_head,
8748 editor_width,
8749 style,
8750 edits,
8751 edit_preview,
8752 snapshot,
8753 window,
8754 cx,
8755 ),
8756 EditPrediction::MoveOutside { snapshot, .. } => {
8757 let file_name = snapshot
8758 .file()
8759 .map(|file| file.file_name(cx))
8760 .unwrap_or("untitled");
8761 let mut element = self
8762 .render_edit_prediction_line_popover(
8763 format!("Jump to {file_name}"),
8764 Some(IconName::ZedPredict),
8765 window,
8766 cx,
8767 )
8768 .into_any();
8769
8770 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8771 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8772 let origin_y = text_bounds.size.height - size.height - px(30.);
8773 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8774 element.prepaint_at(origin, window, cx);
8775
8776 Some((element, origin))
8777 }
8778 }
8779 }
8780
8781 fn render_edit_prediction_modifier_jump_popover(
8782 &mut self,
8783 text_bounds: &Bounds<Pixels>,
8784 content_origin: gpui::Point<Pixels>,
8785 visible_row_range: Range<DisplayRow>,
8786 line_layouts: &[LineWithInvisibles],
8787 line_height: Pixels,
8788 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8789 newest_selection_head: Option<DisplayPoint>,
8790 target_display_point: DisplayPoint,
8791 window: &mut Window,
8792 cx: &mut App,
8793 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8794 let scrolled_content_origin =
8795 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8796
8797 const SCROLL_PADDING_Y: Pixels = px(12.);
8798
8799 if target_display_point.row() < visible_row_range.start {
8800 return self.render_edit_prediction_scroll_popover(
8801 |_| SCROLL_PADDING_Y,
8802 IconName::ArrowUp,
8803 visible_row_range,
8804 line_layouts,
8805 newest_selection_head,
8806 scrolled_content_origin,
8807 window,
8808 cx,
8809 );
8810 } else if target_display_point.row() >= visible_row_range.end {
8811 return self.render_edit_prediction_scroll_popover(
8812 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8813 IconName::ArrowDown,
8814 visible_row_range,
8815 line_layouts,
8816 newest_selection_head,
8817 scrolled_content_origin,
8818 window,
8819 cx,
8820 );
8821 }
8822
8823 const POLE_WIDTH: Pixels = px(2.);
8824
8825 let line_layout =
8826 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8827 let target_column = target_display_point.column() as usize;
8828
8829 let target_x = line_layout.x_for_index(target_column);
8830 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8831 - scroll_pixel_position.y;
8832
8833 let flag_on_right = target_x < text_bounds.size.width / 2.;
8834
8835 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8836 border_color.l += 0.001;
8837
8838 let mut element = v_flex()
8839 .items_end()
8840 .when(flag_on_right, |el| el.items_start())
8841 .child(if flag_on_right {
8842 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8843 .rounded_bl(px(0.))
8844 .rounded_tl(px(0.))
8845 .border_l_2()
8846 .border_color(border_color)
8847 } else {
8848 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8849 .rounded_br(px(0.))
8850 .rounded_tr(px(0.))
8851 .border_r_2()
8852 .border_color(border_color)
8853 })
8854 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8855 .into_any();
8856
8857 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8858
8859 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8860 - point(
8861 if flag_on_right {
8862 POLE_WIDTH
8863 } else {
8864 size.width - POLE_WIDTH
8865 },
8866 size.height - line_height,
8867 );
8868
8869 origin.x = origin.x.max(content_origin.x);
8870
8871 element.prepaint_at(origin, window, cx);
8872
8873 Some((element, origin))
8874 }
8875
8876 fn render_edit_prediction_scroll_popover(
8877 &mut self,
8878 to_y: impl Fn(Size<Pixels>) -> Pixels,
8879 scroll_icon: IconName,
8880 visible_row_range: Range<DisplayRow>,
8881 line_layouts: &[LineWithInvisibles],
8882 newest_selection_head: Option<DisplayPoint>,
8883 scrolled_content_origin: gpui::Point<Pixels>,
8884 window: &mut Window,
8885 cx: &mut App,
8886 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8887 let mut element = self
8888 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8889 .into_any();
8890
8891 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8892
8893 let cursor = newest_selection_head?;
8894 let cursor_row_layout =
8895 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8896 let cursor_column = cursor.column() as usize;
8897
8898 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8899
8900 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8901
8902 element.prepaint_at(origin, window, cx);
8903 Some((element, origin))
8904 }
8905
8906 fn render_edit_prediction_eager_jump_popover(
8907 &mut self,
8908 text_bounds: &Bounds<Pixels>,
8909 content_origin: gpui::Point<Pixels>,
8910 editor_snapshot: &EditorSnapshot,
8911 visible_row_range: Range<DisplayRow>,
8912 scroll_top: ScrollOffset,
8913 scroll_bottom: ScrollOffset,
8914 line_height: Pixels,
8915 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8916 target_display_point: DisplayPoint,
8917 editor_width: Pixels,
8918 window: &mut Window,
8919 cx: &mut App,
8920 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8921 if target_display_point.row().as_f64() < scroll_top {
8922 let mut element = self
8923 .render_edit_prediction_line_popover(
8924 "Jump to Edit",
8925 Some(IconName::ArrowUp),
8926 window,
8927 cx,
8928 )
8929 .into_any();
8930
8931 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8932 let offset = point(
8933 (text_bounds.size.width - size.width) / 2.,
8934 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8935 );
8936
8937 let origin = text_bounds.origin + offset;
8938 element.prepaint_at(origin, window, cx);
8939 Some((element, origin))
8940 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8941 let mut element = self
8942 .render_edit_prediction_line_popover(
8943 "Jump to Edit",
8944 Some(IconName::ArrowDown),
8945 window,
8946 cx,
8947 )
8948 .into_any();
8949
8950 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8951 let offset = point(
8952 (text_bounds.size.width - size.width) / 2.,
8953 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8954 );
8955
8956 let origin = text_bounds.origin + offset;
8957 element.prepaint_at(origin, window, cx);
8958 Some((element, origin))
8959 } else {
8960 self.render_edit_prediction_end_of_line_popover(
8961 "Jump to Edit",
8962 editor_snapshot,
8963 visible_row_range,
8964 target_display_point,
8965 line_height,
8966 scroll_pixel_position,
8967 content_origin,
8968 editor_width,
8969 window,
8970 cx,
8971 )
8972 }
8973 }
8974
8975 fn render_edit_prediction_end_of_line_popover(
8976 self: &mut Editor,
8977 label: &'static str,
8978 editor_snapshot: &EditorSnapshot,
8979 visible_row_range: Range<DisplayRow>,
8980 target_display_point: DisplayPoint,
8981 line_height: Pixels,
8982 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8983 content_origin: gpui::Point<Pixels>,
8984 editor_width: Pixels,
8985 window: &mut Window,
8986 cx: &mut App,
8987 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8988 let target_line_end = DisplayPoint::new(
8989 target_display_point.row(),
8990 editor_snapshot.line_len(target_display_point.row()),
8991 );
8992
8993 let mut element = self
8994 .render_edit_prediction_line_popover(label, None, window, cx)
8995 .into_any();
8996
8997 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8998
8999 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9000
9001 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9002 let mut origin = start_point
9003 + line_origin
9004 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9005 origin.x = origin.x.max(content_origin.x);
9006
9007 let max_x = content_origin.x + editor_width - size.width;
9008
9009 if origin.x > max_x {
9010 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9011
9012 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9013 origin.y += offset;
9014 IconName::ArrowUp
9015 } else {
9016 origin.y -= offset;
9017 IconName::ArrowDown
9018 };
9019
9020 element = self
9021 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9022 .into_any();
9023
9024 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9025
9026 origin.x = content_origin.x + editor_width - size.width - px(2.);
9027 }
9028
9029 element.prepaint_at(origin, window, cx);
9030 Some((element, origin))
9031 }
9032
9033 fn render_edit_prediction_diff_popover(
9034 self: &Editor,
9035 text_bounds: &Bounds<Pixels>,
9036 content_origin: gpui::Point<Pixels>,
9037 right_margin: Pixels,
9038 editor_snapshot: &EditorSnapshot,
9039 visible_row_range: Range<DisplayRow>,
9040 line_layouts: &[LineWithInvisibles],
9041 line_height: Pixels,
9042 scroll_position: gpui::Point<ScrollOffset>,
9043 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9044 newest_selection_head: Option<DisplayPoint>,
9045 editor_width: Pixels,
9046 style: &EditorStyle,
9047 edits: &Vec<(Range<Anchor>, String)>,
9048 edit_preview: &Option<language::EditPreview>,
9049 snapshot: &language::BufferSnapshot,
9050 window: &mut Window,
9051 cx: &mut App,
9052 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9053 let edit_start = edits
9054 .first()
9055 .unwrap()
9056 .0
9057 .start
9058 .to_display_point(editor_snapshot);
9059 let edit_end = edits
9060 .last()
9061 .unwrap()
9062 .0
9063 .end
9064 .to_display_point(editor_snapshot);
9065
9066 let is_visible = visible_row_range.contains(&edit_start.row())
9067 || visible_row_range.contains(&edit_end.row());
9068 if !is_visible {
9069 return None;
9070 }
9071
9072 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9073 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9074 } else {
9075 // Fallback for providers without edit_preview
9076 crate::edit_prediction_fallback_text(edits, cx)
9077 };
9078
9079 let styled_text = highlighted_edits.to_styled_text(&style.text);
9080 let line_count = highlighted_edits.text.lines().count();
9081
9082 const BORDER_WIDTH: Pixels = px(1.);
9083
9084 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9085 let has_keybind = keybind.is_some();
9086
9087 let mut element = h_flex()
9088 .items_start()
9089 .child(
9090 h_flex()
9091 .bg(cx.theme().colors().editor_background)
9092 .border(BORDER_WIDTH)
9093 .shadow_xs()
9094 .border_color(cx.theme().colors().border)
9095 .rounded_l_lg()
9096 .when(line_count > 1, |el| el.rounded_br_lg())
9097 .pr_1()
9098 .child(styled_text),
9099 )
9100 .child(
9101 h_flex()
9102 .h(line_height + BORDER_WIDTH * 2.)
9103 .px_1p5()
9104 .gap_1()
9105 // Workaround: For some reason, there's a gap if we don't do this
9106 .ml(-BORDER_WIDTH)
9107 .shadow(vec![gpui::BoxShadow {
9108 color: gpui::black().opacity(0.05),
9109 offset: point(px(1.), px(1.)),
9110 blur_radius: px(2.),
9111 spread_radius: px(0.),
9112 }])
9113 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9114 .border(BORDER_WIDTH)
9115 .border_color(cx.theme().colors().border)
9116 .rounded_r_lg()
9117 .id("edit_prediction_diff_popover_keybind")
9118 .when(!has_keybind, |el| {
9119 let status_colors = cx.theme().status();
9120
9121 el.bg(status_colors.error_background)
9122 .border_color(status_colors.error.opacity(0.6))
9123 .child(Icon::new(IconName::Info).color(Color::Error))
9124 .cursor_default()
9125 .hoverable_tooltip(move |_window, cx| {
9126 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9127 })
9128 })
9129 .children(keybind),
9130 )
9131 .into_any();
9132
9133 let longest_row =
9134 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9135 let longest_line_width = if visible_row_range.contains(&longest_row) {
9136 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9137 } else {
9138 layout_line(
9139 longest_row,
9140 editor_snapshot,
9141 style,
9142 editor_width,
9143 |_| false,
9144 window,
9145 cx,
9146 )
9147 .width
9148 };
9149
9150 let viewport_bounds =
9151 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9152 right: -right_margin,
9153 ..Default::default()
9154 });
9155
9156 let x_after_longest = Pixels::from(
9157 ScrollPixelOffset::from(
9158 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9159 ) - scroll_pixel_position.x,
9160 );
9161
9162 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9163
9164 // Fully visible if it can be displayed within the window (allow overlapping other
9165 // panes). However, this is only allowed if the popover starts within text_bounds.
9166 let can_position_to_the_right = x_after_longest < text_bounds.right()
9167 && x_after_longest + element_bounds.width < viewport_bounds.right();
9168
9169 let mut origin = if can_position_to_the_right {
9170 point(
9171 x_after_longest,
9172 text_bounds.origin.y
9173 + Pixels::from(
9174 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9175 - scroll_pixel_position.y,
9176 ),
9177 )
9178 } else {
9179 let cursor_row = newest_selection_head.map(|head| head.row());
9180 let above_edit = edit_start
9181 .row()
9182 .0
9183 .checked_sub(line_count as u32)
9184 .map(DisplayRow);
9185 let below_edit = Some(edit_end.row() + 1);
9186 let above_cursor =
9187 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9188 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9189
9190 // Place the edit popover adjacent to the edit if there is a location
9191 // available that is onscreen and does not obscure the cursor. Otherwise,
9192 // place it adjacent to the cursor.
9193 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9194 .into_iter()
9195 .flatten()
9196 .find(|&start_row| {
9197 let end_row = start_row + line_count as u32;
9198 visible_row_range.contains(&start_row)
9199 && visible_row_range.contains(&end_row)
9200 && cursor_row
9201 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9202 })?;
9203
9204 content_origin
9205 + point(
9206 Pixels::from(-scroll_pixel_position.x),
9207 Pixels::from(
9208 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9209 ),
9210 )
9211 };
9212
9213 origin.x -= BORDER_WIDTH;
9214
9215 window.defer_draw(element, origin, 1);
9216
9217 // Do not return an element, since it will already be drawn due to defer_draw.
9218 None
9219 }
9220
9221 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9222 px(30.)
9223 }
9224
9225 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9226 if self.read_only(cx) {
9227 cx.theme().players().read_only()
9228 } else {
9229 self.style.as_ref().unwrap().local_player
9230 }
9231 }
9232
9233 fn render_edit_prediction_accept_keybind(
9234 &self,
9235 window: &mut Window,
9236 cx: &App,
9237 ) -> Option<AnyElement> {
9238 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9239 let accept_keystroke = accept_binding.keystroke()?;
9240
9241 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9242
9243 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9244 Color::Accent
9245 } else {
9246 Color::Muted
9247 };
9248
9249 h_flex()
9250 .px_0p5()
9251 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9252 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9253 .text_size(TextSize::XSmall.rems(cx))
9254 .child(h_flex().children(ui::render_modifiers(
9255 accept_keystroke.modifiers(),
9256 PlatformStyle::platform(),
9257 Some(modifiers_color),
9258 Some(IconSize::XSmall.rems().into()),
9259 true,
9260 )))
9261 .when(is_platform_style_mac, |parent| {
9262 parent.child(accept_keystroke.key().to_string())
9263 })
9264 .when(!is_platform_style_mac, |parent| {
9265 parent.child(
9266 Key::new(
9267 util::capitalize(accept_keystroke.key()),
9268 Some(Color::Default),
9269 )
9270 .size(Some(IconSize::XSmall.rems().into())),
9271 )
9272 })
9273 .into_any()
9274 .into()
9275 }
9276
9277 fn render_edit_prediction_line_popover(
9278 &self,
9279 label: impl Into<SharedString>,
9280 icon: Option<IconName>,
9281 window: &mut Window,
9282 cx: &App,
9283 ) -> Stateful<Div> {
9284 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9285
9286 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9287 let has_keybind = keybind.is_some();
9288
9289 h_flex()
9290 .id("ep-line-popover")
9291 .py_0p5()
9292 .pl_1()
9293 .pr(padding_right)
9294 .gap_1()
9295 .rounded_md()
9296 .border_1()
9297 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9298 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9299 .shadow_xs()
9300 .when(!has_keybind, |el| {
9301 let status_colors = cx.theme().status();
9302
9303 el.bg(status_colors.error_background)
9304 .border_color(status_colors.error.opacity(0.6))
9305 .pl_2()
9306 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9307 .cursor_default()
9308 .hoverable_tooltip(move |_window, cx| {
9309 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9310 })
9311 })
9312 .children(keybind)
9313 .child(
9314 Label::new(label)
9315 .size(LabelSize::Small)
9316 .when(!has_keybind, |el| {
9317 el.color(cx.theme().status().error.into()).strikethrough()
9318 }),
9319 )
9320 .when(!has_keybind, |el| {
9321 el.child(
9322 h_flex().ml_1().child(
9323 Icon::new(IconName::Info)
9324 .size(IconSize::Small)
9325 .color(cx.theme().status().error.into()),
9326 ),
9327 )
9328 })
9329 .when_some(icon, |element, icon| {
9330 element.child(
9331 div()
9332 .mt(px(1.5))
9333 .child(Icon::new(icon).size(IconSize::Small)),
9334 )
9335 })
9336 }
9337
9338 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9339 let accent_color = cx.theme().colors().text_accent;
9340 let editor_bg_color = cx.theme().colors().editor_background;
9341 editor_bg_color.blend(accent_color.opacity(0.1))
9342 }
9343
9344 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9345 let accent_color = cx.theme().colors().text_accent;
9346 let editor_bg_color = cx.theme().colors().editor_background;
9347 editor_bg_color.blend(accent_color.opacity(0.6))
9348 }
9349 fn get_prediction_provider_icon_name(
9350 provider: &Option<RegisteredEditPredictionProvider>,
9351 ) -> IconName {
9352 match provider {
9353 Some(provider) => match provider.provider.name() {
9354 "copilot" => IconName::Copilot,
9355 "supermaven" => IconName::Supermaven,
9356 _ => IconName::ZedPredict,
9357 },
9358 None => IconName::ZedPredict,
9359 }
9360 }
9361
9362 fn render_edit_prediction_cursor_popover(
9363 &self,
9364 min_width: Pixels,
9365 max_width: Pixels,
9366 cursor_point: Point,
9367 style: &EditorStyle,
9368 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9369 _window: &Window,
9370 cx: &mut Context<Editor>,
9371 ) -> Option<AnyElement> {
9372 let provider = self.edit_prediction_provider.as_ref()?;
9373 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9374
9375 let is_refreshing = provider.provider.is_refreshing(cx);
9376
9377 fn pending_completion_container(icon: IconName) -> Div {
9378 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9379 }
9380
9381 let completion = match &self.active_edit_prediction {
9382 Some(prediction) => {
9383 if !self.has_visible_completions_menu() {
9384 const RADIUS: Pixels = px(6.);
9385 const BORDER_WIDTH: Pixels = px(1.);
9386
9387 return Some(
9388 h_flex()
9389 .elevation_2(cx)
9390 .border(BORDER_WIDTH)
9391 .border_color(cx.theme().colors().border)
9392 .when(accept_keystroke.is_none(), |el| {
9393 el.border_color(cx.theme().status().error)
9394 })
9395 .rounded(RADIUS)
9396 .rounded_tl(px(0.))
9397 .overflow_hidden()
9398 .child(div().px_1p5().child(match &prediction.completion {
9399 EditPrediction::MoveWithin { target, snapshot } => {
9400 use text::ToPoint as _;
9401 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9402 {
9403 Icon::new(IconName::ZedPredictDown)
9404 } else {
9405 Icon::new(IconName::ZedPredictUp)
9406 }
9407 }
9408 EditPrediction::MoveOutside { .. } => {
9409 // TODO [zeta2] custom icon for external jump?
9410 Icon::new(provider_icon)
9411 }
9412 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9413 }))
9414 .child(
9415 h_flex()
9416 .gap_1()
9417 .py_1()
9418 .px_2()
9419 .rounded_r(RADIUS - BORDER_WIDTH)
9420 .border_l_1()
9421 .border_color(cx.theme().colors().border)
9422 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9423 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9424 el.child(
9425 Label::new("Hold")
9426 .size(LabelSize::Small)
9427 .when(accept_keystroke.is_none(), |el| {
9428 el.strikethrough()
9429 })
9430 .line_height_style(LineHeightStyle::UiLabel),
9431 )
9432 })
9433 .id("edit_prediction_cursor_popover_keybind")
9434 .when(accept_keystroke.is_none(), |el| {
9435 let status_colors = cx.theme().status();
9436
9437 el.bg(status_colors.error_background)
9438 .border_color(status_colors.error.opacity(0.6))
9439 .child(Icon::new(IconName::Info).color(Color::Error))
9440 .cursor_default()
9441 .hoverable_tooltip(move |_window, cx| {
9442 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9443 .into()
9444 })
9445 })
9446 .when_some(
9447 accept_keystroke.as_ref(),
9448 |el, accept_keystroke| {
9449 el.child(h_flex().children(ui::render_modifiers(
9450 accept_keystroke.modifiers(),
9451 PlatformStyle::platform(),
9452 Some(Color::Default),
9453 Some(IconSize::XSmall.rems().into()),
9454 false,
9455 )))
9456 },
9457 ),
9458 )
9459 .into_any(),
9460 );
9461 }
9462
9463 self.render_edit_prediction_cursor_popover_preview(
9464 prediction,
9465 cursor_point,
9466 style,
9467 cx,
9468 )?
9469 }
9470
9471 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9472 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9473 stale_completion,
9474 cursor_point,
9475 style,
9476 cx,
9477 )?,
9478
9479 None => pending_completion_container(provider_icon)
9480 .child(Label::new("...").size(LabelSize::Small)),
9481 },
9482
9483 None => pending_completion_container(provider_icon)
9484 .child(Label::new("...").size(LabelSize::Small)),
9485 };
9486
9487 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9488 completion
9489 .with_animation(
9490 "loading-completion",
9491 Animation::new(Duration::from_secs(2))
9492 .repeat()
9493 .with_easing(pulsating_between(0.4, 0.8)),
9494 |label, delta| label.opacity(delta),
9495 )
9496 .into_any_element()
9497 } else {
9498 completion.into_any_element()
9499 };
9500
9501 let has_completion = self.active_edit_prediction.is_some();
9502
9503 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9504 Some(
9505 h_flex()
9506 .min_w(min_width)
9507 .max_w(max_width)
9508 .flex_1()
9509 .elevation_2(cx)
9510 .border_color(cx.theme().colors().border)
9511 .child(
9512 div()
9513 .flex_1()
9514 .py_1()
9515 .px_2()
9516 .overflow_hidden()
9517 .child(completion),
9518 )
9519 .when_some(accept_keystroke, |el, accept_keystroke| {
9520 if !accept_keystroke.modifiers().modified() {
9521 return el;
9522 }
9523
9524 el.child(
9525 h_flex()
9526 .h_full()
9527 .border_l_1()
9528 .rounded_r_lg()
9529 .border_color(cx.theme().colors().border)
9530 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9531 .gap_1()
9532 .py_1()
9533 .px_2()
9534 .child(
9535 h_flex()
9536 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9537 .when(is_platform_style_mac, |parent| parent.gap_1())
9538 .child(h_flex().children(ui::render_modifiers(
9539 accept_keystroke.modifiers(),
9540 PlatformStyle::platform(),
9541 Some(if !has_completion {
9542 Color::Muted
9543 } else {
9544 Color::Default
9545 }),
9546 None,
9547 false,
9548 ))),
9549 )
9550 .child(Label::new("Preview").into_any_element())
9551 .opacity(if has_completion { 1.0 } else { 0.4 }),
9552 )
9553 })
9554 .into_any(),
9555 )
9556 }
9557
9558 fn render_edit_prediction_cursor_popover_preview(
9559 &self,
9560 completion: &EditPredictionState,
9561 cursor_point: Point,
9562 style: &EditorStyle,
9563 cx: &mut Context<Editor>,
9564 ) -> Option<Div> {
9565 use text::ToPoint as _;
9566
9567 fn render_relative_row_jump(
9568 prefix: impl Into<String>,
9569 current_row: u32,
9570 target_row: u32,
9571 ) -> Div {
9572 let (row_diff, arrow) = if target_row < current_row {
9573 (current_row - target_row, IconName::ArrowUp)
9574 } else {
9575 (target_row - current_row, IconName::ArrowDown)
9576 };
9577
9578 h_flex()
9579 .child(
9580 Label::new(format!("{}{}", prefix.into(), row_diff))
9581 .color(Color::Muted)
9582 .size(LabelSize::Small),
9583 )
9584 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9585 }
9586
9587 let supports_jump = self
9588 .edit_prediction_provider
9589 .as_ref()
9590 .map(|provider| provider.provider.supports_jump_to_edit())
9591 .unwrap_or(true);
9592
9593 match &completion.completion {
9594 EditPrediction::MoveWithin {
9595 target, snapshot, ..
9596 } => {
9597 if !supports_jump {
9598 return None;
9599 }
9600
9601 Some(
9602 h_flex()
9603 .px_2()
9604 .gap_2()
9605 .flex_1()
9606 .child(
9607 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9608 Icon::new(IconName::ZedPredictDown)
9609 } else {
9610 Icon::new(IconName::ZedPredictUp)
9611 },
9612 )
9613 .child(Label::new("Jump to Edit")),
9614 )
9615 }
9616 EditPrediction::MoveOutside { snapshot, .. } => {
9617 let file_name = snapshot
9618 .file()
9619 .map(|file| file.file_name(cx))
9620 .unwrap_or("untitled");
9621 Some(
9622 h_flex()
9623 .px_2()
9624 .gap_2()
9625 .flex_1()
9626 .child(Icon::new(IconName::ZedPredict))
9627 .child(Label::new(format!("Jump to {file_name}"))),
9628 )
9629 }
9630 EditPrediction::Edit {
9631 edits,
9632 edit_preview,
9633 snapshot,
9634 display_mode: _,
9635 } => {
9636 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9637
9638 let (highlighted_edits, has_more_lines) =
9639 if let Some(edit_preview) = edit_preview.as_ref() {
9640 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9641 .first_line_preview()
9642 } else {
9643 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9644 };
9645
9646 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9647 .with_default_highlights(&style.text, highlighted_edits.highlights);
9648
9649 let preview = h_flex()
9650 .gap_1()
9651 .min_w_16()
9652 .child(styled_text)
9653 .when(has_more_lines, |parent| parent.child("…"));
9654
9655 let left = if supports_jump && first_edit_row != cursor_point.row {
9656 render_relative_row_jump("", cursor_point.row, first_edit_row)
9657 .into_any_element()
9658 } else {
9659 let icon_name =
9660 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9661 Icon::new(icon_name).into_any_element()
9662 };
9663
9664 Some(
9665 h_flex()
9666 .h_full()
9667 .flex_1()
9668 .gap_2()
9669 .pr_1()
9670 .overflow_x_hidden()
9671 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9672 .child(left)
9673 .child(preview),
9674 )
9675 }
9676 }
9677 }
9678
9679 pub fn render_context_menu(
9680 &self,
9681 style: &EditorStyle,
9682 max_height_in_lines: u32,
9683 window: &mut Window,
9684 cx: &mut Context<Editor>,
9685 ) -> Option<AnyElement> {
9686 let menu = self.context_menu.borrow();
9687 let menu = menu.as_ref()?;
9688 if !menu.visible() {
9689 return None;
9690 };
9691 Some(menu.render(style, max_height_in_lines, window, cx))
9692 }
9693
9694 fn render_context_menu_aside(
9695 &mut self,
9696 max_size: Size<Pixels>,
9697 window: &mut Window,
9698 cx: &mut Context<Editor>,
9699 ) -> Option<AnyElement> {
9700 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9701 if menu.visible() {
9702 menu.render_aside(max_size, window, cx)
9703 } else {
9704 None
9705 }
9706 })
9707 }
9708
9709 fn hide_context_menu(
9710 &mut self,
9711 window: &mut Window,
9712 cx: &mut Context<Self>,
9713 ) -> Option<CodeContextMenu> {
9714 cx.notify();
9715 self.completion_tasks.clear();
9716 let context_menu = self.context_menu.borrow_mut().take();
9717 self.stale_edit_prediction_in_menu.take();
9718 self.update_visible_edit_prediction(window, cx);
9719 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9720 && let Some(completion_provider) = &self.completion_provider
9721 {
9722 completion_provider.selection_changed(None, window, cx);
9723 }
9724 context_menu
9725 }
9726
9727 fn show_snippet_choices(
9728 &mut self,
9729 choices: &Vec<String>,
9730 selection: Range<Anchor>,
9731 cx: &mut Context<Self>,
9732 ) {
9733 let Some((_, buffer, _)) = self
9734 .buffer()
9735 .read(cx)
9736 .excerpt_containing(selection.start, cx)
9737 else {
9738 return;
9739 };
9740 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9741 else {
9742 return;
9743 };
9744 if buffer != end_buffer {
9745 log::error!("expected anchor range to have matching buffer IDs");
9746 return;
9747 }
9748
9749 let id = post_inc(&mut self.next_completion_id);
9750 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9751 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9752 CompletionsMenu::new_snippet_choices(
9753 id,
9754 true,
9755 choices,
9756 selection,
9757 buffer,
9758 snippet_sort_order,
9759 ),
9760 ));
9761 }
9762
9763 pub fn insert_snippet(
9764 &mut self,
9765 insertion_ranges: &[Range<usize>],
9766 snippet: Snippet,
9767 window: &mut Window,
9768 cx: &mut Context<Self>,
9769 ) -> Result<()> {
9770 struct Tabstop<T> {
9771 is_end_tabstop: bool,
9772 ranges: Vec<Range<T>>,
9773 choices: Option<Vec<String>>,
9774 }
9775
9776 let tabstops = self.buffer.update(cx, |buffer, cx| {
9777 let snippet_text: Arc<str> = snippet.text.clone().into();
9778 let edits = insertion_ranges
9779 .iter()
9780 .cloned()
9781 .map(|range| (range, snippet_text.clone()));
9782 let autoindent_mode = AutoindentMode::Block {
9783 original_indent_columns: Vec::new(),
9784 };
9785 buffer.edit(edits, Some(autoindent_mode), cx);
9786
9787 let snapshot = &*buffer.read(cx);
9788 let snippet = &snippet;
9789 snippet
9790 .tabstops
9791 .iter()
9792 .map(|tabstop| {
9793 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9794 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9795 });
9796 let mut tabstop_ranges = tabstop
9797 .ranges
9798 .iter()
9799 .flat_map(|tabstop_range| {
9800 let mut delta = 0_isize;
9801 insertion_ranges.iter().map(move |insertion_range| {
9802 let insertion_start = insertion_range.start as isize + delta;
9803 delta +=
9804 snippet.text.len() as isize - insertion_range.len() as isize;
9805
9806 let start = ((insertion_start + tabstop_range.start) as usize)
9807 .min(snapshot.len());
9808 let end = ((insertion_start + tabstop_range.end) as usize)
9809 .min(snapshot.len());
9810 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9811 })
9812 })
9813 .collect::<Vec<_>>();
9814 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9815
9816 Tabstop {
9817 is_end_tabstop,
9818 ranges: tabstop_ranges,
9819 choices: tabstop.choices.clone(),
9820 }
9821 })
9822 .collect::<Vec<_>>()
9823 });
9824 if let Some(tabstop) = tabstops.first() {
9825 self.change_selections(Default::default(), window, cx, |s| {
9826 // Reverse order so that the first range is the newest created selection.
9827 // Completions will use it and autoscroll will prioritize it.
9828 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9829 });
9830
9831 if let Some(choices) = &tabstop.choices
9832 && let Some(selection) = tabstop.ranges.first()
9833 {
9834 self.show_snippet_choices(choices, selection.clone(), cx)
9835 }
9836
9837 // If we're already at the last tabstop and it's at the end of the snippet,
9838 // we're done, we don't need to keep the state around.
9839 if !tabstop.is_end_tabstop {
9840 let choices = tabstops
9841 .iter()
9842 .map(|tabstop| tabstop.choices.clone())
9843 .collect();
9844
9845 let ranges = tabstops
9846 .into_iter()
9847 .map(|tabstop| tabstop.ranges)
9848 .collect::<Vec<_>>();
9849
9850 self.snippet_stack.push(SnippetState {
9851 active_index: 0,
9852 ranges,
9853 choices,
9854 });
9855 }
9856
9857 // Check whether the just-entered snippet ends with an auto-closable bracket.
9858 if self.autoclose_regions.is_empty() {
9859 let snapshot = self.buffer.read(cx).snapshot(cx);
9860 let mut all_selections = self.selections.all::<Point>(cx);
9861 for selection in &mut all_selections {
9862 let selection_head = selection.head();
9863 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9864 continue;
9865 };
9866
9867 let mut bracket_pair = None;
9868 let max_lookup_length = scope
9869 .brackets()
9870 .map(|(pair, _)| {
9871 pair.start
9872 .as_str()
9873 .chars()
9874 .count()
9875 .max(pair.end.as_str().chars().count())
9876 })
9877 .max();
9878 if let Some(max_lookup_length) = max_lookup_length {
9879 let next_text = snapshot
9880 .chars_at(selection_head)
9881 .take(max_lookup_length)
9882 .collect::<String>();
9883 let prev_text = snapshot
9884 .reversed_chars_at(selection_head)
9885 .take(max_lookup_length)
9886 .collect::<String>();
9887
9888 for (pair, enabled) in scope.brackets() {
9889 if enabled
9890 && pair.close
9891 && prev_text.starts_with(pair.start.as_str())
9892 && next_text.starts_with(pair.end.as_str())
9893 {
9894 bracket_pair = Some(pair.clone());
9895 break;
9896 }
9897 }
9898 }
9899
9900 if let Some(pair) = bracket_pair {
9901 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9902 let autoclose_enabled =
9903 self.use_autoclose && snapshot_settings.use_autoclose;
9904 if autoclose_enabled {
9905 let start = snapshot.anchor_after(selection_head);
9906 let end = snapshot.anchor_after(selection_head);
9907 self.autoclose_regions.push(AutocloseRegion {
9908 selection_id: selection.id,
9909 range: start..end,
9910 pair,
9911 });
9912 }
9913 }
9914 }
9915 }
9916 }
9917 Ok(())
9918 }
9919
9920 pub fn move_to_next_snippet_tabstop(
9921 &mut self,
9922 window: &mut Window,
9923 cx: &mut Context<Self>,
9924 ) -> bool {
9925 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9926 }
9927
9928 pub fn move_to_prev_snippet_tabstop(
9929 &mut self,
9930 window: &mut Window,
9931 cx: &mut Context<Self>,
9932 ) -> bool {
9933 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9934 }
9935
9936 pub fn move_to_snippet_tabstop(
9937 &mut self,
9938 bias: Bias,
9939 window: &mut Window,
9940 cx: &mut Context<Self>,
9941 ) -> bool {
9942 if let Some(mut snippet) = self.snippet_stack.pop() {
9943 match bias {
9944 Bias::Left => {
9945 if snippet.active_index > 0 {
9946 snippet.active_index -= 1;
9947 } else {
9948 self.snippet_stack.push(snippet);
9949 return false;
9950 }
9951 }
9952 Bias::Right => {
9953 if snippet.active_index + 1 < snippet.ranges.len() {
9954 snippet.active_index += 1;
9955 } else {
9956 self.snippet_stack.push(snippet);
9957 return false;
9958 }
9959 }
9960 }
9961 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9962 self.change_selections(Default::default(), window, cx, |s| {
9963 // Reverse order so that the first range is the newest created selection.
9964 // Completions will use it and autoscroll will prioritize it.
9965 s.select_ranges(current_ranges.iter().rev().cloned())
9966 });
9967
9968 if let Some(choices) = &snippet.choices[snippet.active_index]
9969 && let Some(selection) = current_ranges.first()
9970 {
9971 self.show_snippet_choices(choices, selection.clone(), cx);
9972 }
9973
9974 // If snippet state is not at the last tabstop, push it back on the stack
9975 if snippet.active_index + 1 < snippet.ranges.len() {
9976 self.snippet_stack.push(snippet);
9977 }
9978 return true;
9979 }
9980 }
9981
9982 false
9983 }
9984
9985 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9986 self.transact(window, cx, |this, window, cx| {
9987 this.select_all(&SelectAll, window, cx);
9988 this.insert("", window, cx);
9989 });
9990 }
9991
9992 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9993 if self.read_only(cx) {
9994 return;
9995 }
9996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9997 self.transact(window, cx, |this, window, cx| {
9998 this.select_autoclose_pair(window, cx);
9999 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10000 if !this.linked_edit_ranges.is_empty() {
10001 let selections = this.selections.all::<MultiBufferPoint>(cx);
10002 let snapshot = this.buffer.read(cx).snapshot(cx);
10003
10004 for selection in selections.iter() {
10005 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10006 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10007 if selection_start.buffer_id != selection_end.buffer_id {
10008 continue;
10009 }
10010 if let Some(ranges) =
10011 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10012 {
10013 for (buffer, entries) in ranges {
10014 linked_ranges.entry(buffer).or_default().extend(entries);
10015 }
10016 }
10017 }
10018 }
10019
10020 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10021 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10022 for selection in &mut selections {
10023 if selection.is_empty() {
10024 let old_head = selection.head();
10025 let mut new_head =
10026 movement::left(&display_map, old_head.to_display_point(&display_map))
10027 .to_point(&display_map);
10028 if let Some((buffer, line_buffer_range)) = display_map
10029 .buffer_snapshot()
10030 .buffer_line_for_row(MultiBufferRow(old_head.row))
10031 {
10032 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10033 let indent_len = match indent_size.kind {
10034 IndentKind::Space => {
10035 buffer.settings_at(line_buffer_range.start, cx).tab_size
10036 }
10037 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10038 };
10039 if old_head.column <= indent_size.len && old_head.column > 0 {
10040 let indent_len = indent_len.get();
10041 new_head = cmp::min(
10042 new_head,
10043 MultiBufferPoint::new(
10044 old_head.row,
10045 ((old_head.column - 1) / indent_len) * indent_len,
10046 ),
10047 );
10048 }
10049 }
10050
10051 selection.set_head(new_head, SelectionGoal::None);
10052 }
10053 }
10054
10055 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10056 this.insert("", window, cx);
10057 let empty_str: Arc<str> = Arc::from("");
10058 for (buffer, edits) in linked_ranges {
10059 let snapshot = buffer.read(cx).snapshot();
10060 use text::ToPoint as TP;
10061
10062 let edits = edits
10063 .into_iter()
10064 .map(|range| {
10065 let end_point = TP::to_point(&range.end, &snapshot);
10066 let mut start_point = TP::to_point(&range.start, &snapshot);
10067
10068 if end_point == start_point {
10069 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10070 .saturating_sub(1);
10071 start_point =
10072 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10073 };
10074
10075 (start_point..end_point, empty_str.clone())
10076 })
10077 .sorted_by_key(|(range, _)| range.start)
10078 .collect::<Vec<_>>();
10079 buffer.update(cx, |this, cx| {
10080 this.edit(edits, None, cx);
10081 })
10082 }
10083 this.refresh_edit_prediction(true, false, window, cx);
10084 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10085 });
10086 }
10087
10088 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10089 if self.read_only(cx) {
10090 return;
10091 }
10092 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10093 self.transact(window, cx, |this, window, cx| {
10094 this.change_selections(Default::default(), window, cx, |s| {
10095 s.move_with(|map, selection| {
10096 if selection.is_empty() {
10097 let cursor = movement::right(map, selection.head());
10098 selection.end = cursor;
10099 selection.reversed = true;
10100 selection.goal = SelectionGoal::None;
10101 }
10102 })
10103 });
10104 this.insert("", window, cx);
10105 this.refresh_edit_prediction(true, false, window, cx);
10106 });
10107 }
10108
10109 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10110 if self.mode.is_single_line() {
10111 cx.propagate();
10112 return;
10113 }
10114
10115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10116 if self.move_to_prev_snippet_tabstop(window, cx) {
10117 return;
10118 }
10119 self.outdent(&Outdent, window, cx);
10120 }
10121
10122 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10123 if self.mode.is_single_line() {
10124 cx.propagate();
10125 return;
10126 }
10127
10128 if self.move_to_next_snippet_tabstop(window, cx) {
10129 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10130 return;
10131 }
10132 if self.read_only(cx) {
10133 return;
10134 }
10135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10136 let mut selections = self.selections.all_adjusted(cx);
10137 let buffer = self.buffer.read(cx);
10138 let snapshot = buffer.snapshot(cx);
10139 let rows_iter = selections.iter().map(|s| s.head().row);
10140 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10141
10142 let has_some_cursor_in_whitespace = selections
10143 .iter()
10144 .filter(|selection| selection.is_empty())
10145 .any(|selection| {
10146 let cursor = selection.head();
10147 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10148 cursor.column < current_indent.len
10149 });
10150
10151 let mut edits = Vec::new();
10152 let mut prev_edited_row = 0;
10153 let mut row_delta = 0;
10154 for selection in &mut selections {
10155 if selection.start.row != prev_edited_row {
10156 row_delta = 0;
10157 }
10158 prev_edited_row = selection.end.row;
10159
10160 // If the selection is non-empty, then increase the indentation of the selected lines.
10161 if !selection.is_empty() {
10162 row_delta =
10163 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10164 continue;
10165 }
10166
10167 let cursor = selection.head();
10168 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10169 if let Some(suggested_indent) =
10170 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10171 {
10172 // Don't do anything if already at suggested indent
10173 // and there is any other cursor which is not
10174 if has_some_cursor_in_whitespace
10175 && cursor.column == current_indent.len
10176 && current_indent.len == suggested_indent.len
10177 {
10178 continue;
10179 }
10180
10181 // Adjust line and move cursor to suggested indent
10182 // if cursor is not at suggested indent
10183 if cursor.column < suggested_indent.len
10184 && cursor.column <= current_indent.len
10185 && current_indent.len <= suggested_indent.len
10186 {
10187 selection.start = Point::new(cursor.row, suggested_indent.len);
10188 selection.end = selection.start;
10189 if row_delta == 0 {
10190 edits.extend(Buffer::edit_for_indent_size_adjustment(
10191 cursor.row,
10192 current_indent,
10193 suggested_indent,
10194 ));
10195 row_delta = suggested_indent.len - current_indent.len;
10196 }
10197 continue;
10198 }
10199
10200 // If current indent is more than suggested indent
10201 // only move cursor to current indent and skip indent
10202 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10203 selection.start = Point::new(cursor.row, current_indent.len);
10204 selection.end = selection.start;
10205 continue;
10206 }
10207 }
10208
10209 // Otherwise, insert a hard or soft tab.
10210 let settings = buffer.language_settings_at(cursor, cx);
10211 let tab_size = if settings.hard_tabs {
10212 IndentSize::tab()
10213 } else {
10214 let tab_size = settings.tab_size.get();
10215 let indent_remainder = snapshot
10216 .text_for_range(Point::new(cursor.row, 0)..cursor)
10217 .flat_map(str::chars)
10218 .fold(row_delta % tab_size, |counter: u32, c| {
10219 if c == '\t' {
10220 0
10221 } else {
10222 (counter + 1) % tab_size
10223 }
10224 });
10225
10226 let chars_to_next_tab_stop = tab_size - indent_remainder;
10227 IndentSize::spaces(chars_to_next_tab_stop)
10228 };
10229 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10230 selection.end = selection.start;
10231 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10232 row_delta += tab_size.len;
10233 }
10234
10235 self.transact(window, cx, |this, window, cx| {
10236 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10237 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10238 this.refresh_edit_prediction(true, false, window, cx);
10239 });
10240 }
10241
10242 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10243 if self.read_only(cx) {
10244 return;
10245 }
10246 if self.mode.is_single_line() {
10247 cx.propagate();
10248 return;
10249 }
10250
10251 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10252 let mut selections = self.selections.all::<Point>(cx);
10253 let mut prev_edited_row = 0;
10254 let mut row_delta = 0;
10255 let mut edits = Vec::new();
10256 let buffer = self.buffer.read(cx);
10257 let snapshot = buffer.snapshot(cx);
10258 for selection in &mut selections {
10259 if selection.start.row != prev_edited_row {
10260 row_delta = 0;
10261 }
10262 prev_edited_row = selection.end.row;
10263
10264 row_delta =
10265 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10266 }
10267
10268 self.transact(window, cx, |this, window, cx| {
10269 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10270 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10271 });
10272 }
10273
10274 fn indent_selection(
10275 buffer: &MultiBuffer,
10276 snapshot: &MultiBufferSnapshot,
10277 selection: &mut Selection<Point>,
10278 edits: &mut Vec<(Range<Point>, String)>,
10279 delta_for_start_row: u32,
10280 cx: &App,
10281 ) -> u32 {
10282 let settings = buffer.language_settings_at(selection.start, cx);
10283 let tab_size = settings.tab_size.get();
10284 let indent_kind = if settings.hard_tabs {
10285 IndentKind::Tab
10286 } else {
10287 IndentKind::Space
10288 };
10289 let mut start_row = selection.start.row;
10290 let mut end_row = selection.end.row + 1;
10291
10292 // If a selection ends at the beginning of a line, don't indent
10293 // that last line.
10294 if selection.end.column == 0 && selection.end.row > selection.start.row {
10295 end_row -= 1;
10296 }
10297
10298 // Avoid re-indenting a row that has already been indented by a
10299 // previous selection, but still update this selection's column
10300 // to reflect that indentation.
10301 if delta_for_start_row > 0 {
10302 start_row += 1;
10303 selection.start.column += delta_for_start_row;
10304 if selection.end.row == selection.start.row {
10305 selection.end.column += delta_for_start_row;
10306 }
10307 }
10308
10309 let mut delta_for_end_row = 0;
10310 let has_multiple_rows = start_row + 1 != end_row;
10311 for row in start_row..end_row {
10312 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10313 let indent_delta = match (current_indent.kind, indent_kind) {
10314 (IndentKind::Space, IndentKind::Space) => {
10315 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10316 IndentSize::spaces(columns_to_next_tab_stop)
10317 }
10318 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10319 (_, IndentKind::Tab) => IndentSize::tab(),
10320 };
10321
10322 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10323 0
10324 } else {
10325 selection.start.column
10326 };
10327 let row_start = Point::new(row, start);
10328 edits.push((
10329 row_start..row_start,
10330 indent_delta.chars().collect::<String>(),
10331 ));
10332
10333 // Update this selection's endpoints to reflect the indentation.
10334 if row == selection.start.row {
10335 selection.start.column += indent_delta.len;
10336 }
10337 if row == selection.end.row {
10338 selection.end.column += indent_delta.len;
10339 delta_for_end_row = indent_delta.len;
10340 }
10341 }
10342
10343 if selection.start.row == selection.end.row {
10344 delta_for_start_row + delta_for_end_row
10345 } else {
10346 delta_for_end_row
10347 }
10348 }
10349
10350 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10351 if self.read_only(cx) {
10352 return;
10353 }
10354 if self.mode.is_single_line() {
10355 cx.propagate();
10356 return;
10357 }
10358
10359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10360 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10361 let selections = self.selections.all::<Point>(cx);
10362 let mut deletion_ranges = Vec::new();
10363 let mut last_outdent = None;
10364 {
10365 let buffer = self.buffer.read(cx);
10366 let snapshot = buffer.snapshot(cx);
10367 for selection in &selections {
10368 let settings = buffer.language_settings_at(selection.start, cx);
10369 let tab_size = settings.tab_size.get();
10370 let mut rows = selection.spanned_rows(false, &display_map);
10371
10372 // Avoid re-outdenting a row that has already been outdented by a
10373 // previous selection.
10374 if let Some(last_row) = last_outdent
10375 && last_row == rows.start
10376 {
10377 rows.start = rows.start.next_row();
10378 }
10379 let has_multiple_rows = rows.len() > 1;
10380 for row in rows.iter_rows() {
10381 let indent_size = snapshot.indent_size_for_line(row);
10382 if indent_size.len > 0 {
10383 let deletion_len = match indent_size.kind {
10384 IndentKind::Space => {
10385 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10386 if columns_to_prev_tab_stop == 0 {
10387 tab_size
10388 } else {
10389 columns_to_prev_tab_stop
10390 }
10391 }
10392 IndentKind::Tab => 1,
10393 };
10394 let start = if has_multiple_rows
10395 || deletion_len > selection.start.column
10396 || indent_size.len < selection.start.column
10397 {
10398 0
10399 } else {
10400 selection.start.column - deletion_len
10401 };
10402 deletion_ranges.push(
10403 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10404 );
10405 last_outdent = Some(row);
10406 }
10407 }
10408 }
10409 }
10410
10411 self.transact(window, cx, |this, window, cx| {
10412 this.buffer.update(cx, |buffer, cx| {
10413 let empty_str: Arc<str> = Arc::default();
10414 buffer.edit(
10415 deletion_ranges
10416 .into_iter()
10417 .map(|range| (range, empty_str.clone())),
10418 None,
10419 cx,
10420 );
10421 });
10422 let selections = this.selections.all::<usize>(cx);
10423 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10424 });
10425 }
10426
10427 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10428 if self.read_only(cx) {
10429 return;
10430 }
10431 if self.mode.is_single_line() {
10432 cx.propagate();
10433 return;
10434 }
10435
10436 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10437 let selections = self
10438 .selections
10439 .all::<usize>(cx)
10440 .into_iter()
10441 .map(|s| s.range());
10442
10443 self.transact(window, cx, |this, window, cx| {
10444 this.buffer.update(cx, |buffer, cx| {
10445 buffer.autoindent_ranges(selections, cx);
10446 });
10447 let selections = this.selections.all::<usize>(cx);
10448 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10449 });
10450 }
10451
10452 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10453 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10454 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10455 let selections = self.selections.all::<Point>(cx);
10456
10457 let mut new_cursors = Vec::new();
10458 let mut edit_ranges = Vec::new();
10459 let mut selections = selections.iter().peekable();
10460 while let Some(selection) = selections.next() {
10461 let mut rows = selection.spanned_rows(false, &display_map);
10462
10463 // Accumulate contiguous regions of rows that we want to delete.
10464 while let Some(next_selection) = selections.peek() {
10465 let next_rows = next_selection.spanned_rows(false, &display_map);
10466 if next_rows.start <= rows.end {
10467 rows.end = next_rows.end;
10468 selections.next().unwrap();
10469 } else {
10470 break;
10471 }
10472 }
10473
10474 let buffer = display_map.buffer_snapshot();
10475 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10476 let edit_end = if buffer.max_point().row >= rows.end.0 {
10477 // If there's a line after the range, delete the \n from the end of the row range
10478 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer)
10479 } else {
10480 // If there isn't a line after the range, delete the \n from the line before the
10481 // start of the row range
10482 edit_start = edit_start.saturating_sub(1);
10483 buffer.len()
10484 };
10485
10486 let (cursor, goal) = movement::down_by_rows(
10487 &display_map,
10488 selection.head().to_display_point(&display_map),
10489 rows.len() as u32,
10490 selection.goal,
10491 false,
10492 &self.text_layout_details(window),
10493 );
10494
10495 new_cursors.push((
10496 selection.id,
10497 buffer.anchor_after(cursor.to_point(&display_map)),
10498 goal,
10499 ));
10500 edit_ranges.push(edit_start..edit_end);
10501 }
10502
10503 self.transact(window, cx, |this, window, cx| {
10504 let buffer = this.buffer.update(cx, |buffer, cx| {
10505 let empty_str: Arc<str> = Arc::default();
10506 buffer.edit(
10507 edit_ranges
10508 .into_iter()
10509 .map(|range| (range, empty_str.clone())),
10510 None,
10511 cx,
10512 );
10513 buffer.snapshot(cx)
10514 });
10515 let new_selections = new_cursors
10516 .into_iter()
10517 .map(|(id, cursor, goal)| {
10518 let cursor = cursor.to_point(&buffer);
10519 Selection {
10520 id,
10521 start: cursor,
10522 end: cursor,
10523 reversed: false,
10524 goal,
10525 }
10526 })
10527 .collect();
10528
10529 this.change_selections(Default::default(), window, cx, |s| {
10530 s.select(new_selections);
10531 });
10532 });
10533 }
10534
10535 pub fn join_lines_impl(
10536 &mut self,
10537 insert_whitespace: bool,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 if self.read_only(cx) {
10542 return;
10543 }
10544 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10545 for selection in self.selections.all::<Point>(cx) {
10546 let start = MultiBufferRow(selection.start.row);
10547 // Treat single line selections as if they include the next line. Otherwise this action
10548 // would do nothing for single line selections individual cursors.
10549 let end = if selection.start.row == selection.end.row {
10550 MultiBufferRow(selection.start.row + 1)
10551 } else {
10552 MultiBufferRow(selection.end.row)
10553 };
10554
10555 if let Some(last_row_range) = row_ranges.last_mut()
10556 && start <= last_row_range.end
10557 {
10558 last_row_range.end = end;
10559 continue;
10560 }
10561 row_ranges.push(start..end);
10562 }
10563
10564 let snapshot = self.buffer.read(cx).snapshot(cx);
10565 let mut cursor_positions = Vec::new();
10566 for row_range in &row_ranges {
10567 let anchor = snapshot.anchor_before(Point::new(
10568 row_range.end.previous_row().0,
10569 snapshot.line_len(row_range.end.previous_row()),
10570 ));
10571 cursor_positions.push(anchor..anchor);
10572 }
10573
10574 self.transact(window, cx, |this, window, cx| {
10575 for row_range in row_ranges.into_iter().rev() {
10576 for row in row_range.iter_rows().rev() {
10577 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10578 let next_line_row = row.next_row();
10579 let indent = snapshot.indent_size_for_line(next_line_row);
10580 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10581
10582 let replace =
10583 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10584 " "
10585 } else {
10586 ""
10587 };
10588
10589 this.buffer.update(cx, |buffer, cx| {
10590 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10591 });
10592 }
10593 }
10594
10595 this.change_selections(Default::default(), window, cx, |s| {
10596 s.select_anchor_ranges(cursor_positions)
10597 });
10598 });
10599 }
10600
10601 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10602 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10603 self.join_lines_impl(true, window, cx);
10604 }
10605
10606 pub fn sort_lines_case_sensitive(
10607 &mut self,
10608 _: &SortLinesCaseSensitive,
10609 window: &mut Window,
10610 cx: &mut Context<Self>,
10611 ) {
10612 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10613 }
10614
10615 pub fn sort_lines_by_length(
10616 &mut self,
10617 _: &SortLinesByLength,
10618 window: &mut Window,
10619 cx: &mut Context<Self>,
10620 ) {
10621 self.manipulate_immutable_lines(window, cx, |lines| {
10622 lines.sort_by_key(|&line| line.chars().count())
10623 })
10624 }
10625
10626 pub fn sort_lines_case_insensitive(
10627 &mut self,
10628 _: &SortLinesCaseInsensitive,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) {
10632 self.manipulate_immutable_lines(window, cx, |lines| {
10633 lines.sort_by_key(|line| line.to_lowercase())
10634 })
10635 }
10636
10637 pub fn unique_lines_case_insensitive(
10638 &mut self,
10639 _: &UniqueLinesCaseInsensitive,
10640 window: &mut Window,
10641 cx: &mut Context<Self>,
10642 ) {
10643 self.manipulate_immutable_lines(window, cx, |lines| {
10644 let mut seen = HashSet::default();
10645 lines.retain(|line| seen.insert(line.to_lowercase()));
10646 })
10647 }
10648
10649 pub fn unique_lines_case_sensitive(
10650 &mut self,
10651 _: &UniqueLinesCaseSensitive,
10652 window: &mut Window,
10653 cx: &mut Context<Self>,
10654 ) {
10655 self.manipulate_immutable_lines(window, cx, |lines| {
10656 let mut seen = HashSet::default();
10657 lines.retain(|line| seen.insert(*line));
10658 })
10659 }
10660
10661 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10662 let snapshot = self.buffer.read(cx).snapshot(cx);
10663 for selection in self.selections.disjoint_anchors_arc().iter() {
10664 if snapshot
10665 .language_at(selection.start)
10666 .and_then(|lang| lang.config().wrap_characters.as_ref())
10667 .is_some()
10668 {
10669 return true;
10670 }
10671 }
10672 false
10673 }
10674
10675 fn wrap_selections_in_tag(
10676 &mut self,
10677 _: &WrapSelectionsInTag,
10678 window: &mut Window,
10679 cx: &mut Context<Self>,
10680 ) {
10681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10682
10683 let snapshot = self.buffer.read(cx).snapshot(cx);
10684
10685 let mut edits = Vec::new();
10686 let mut boundaries = Vec::new();
10687
10688 for selection in self.selections.all::<Point>(cx).iter() {
10689 let Some(wrap_config) = snapshot
10690 .language_at(selection.start)
10691 .and_then(|lang| lang.config().wrap_characters.clone())
10692 else {
10693 continue;
10694 };
10695
10696 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10697 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10698
10699 let start_before = snapshot.anchor_before(selection.start);
10700 let end_after = snapshot.anchor_after(selection.end);
10701
10702 edits.push((start_before..start_before, open_tag));
10703 edits.push((end_after..end_after, close_tag));
10704
10705 boundaries.push((
10706 start_before,
10707 end_after,
10708 wrap_config.start_prefix.len(),
10709 wrap_config.end_suffix.len(),
10710 ));
10711 }
10712
10713 if edits.is_empty() {
10714 return;
10715 }
10716
10717 self.transact(window, cx, |this, window, cx| {
10718 let buffer = this.buffer.update(cx, |buffer, cx| {
10719 buffer.edit(edits, None, cx);
10720 buffer.snapshot(cx)
10721 });
10722
10723 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10724 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10725 boundaries.into_iter()
10726 {
10727 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10728 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10729 new_selections.push(open_offset..open_offset);
10730 new_selections.push(close_offset..close_offset);
10731 }
10732
10733 this.change_selections(Default::default(), window, cx, |s| {
10734 s.select_ranges(new_selections);
10735 });
10736
10737 this.request_autoscroll(Autoscroll::fit(), cx);
10738 });
10739 }
10740
10741 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10742 let Some(project) = self.project.clone() else {
10743 return;
10744 };
10745 self.reload(project, window, cx)
10746 .detach_and_notify_err(window, cx);
10747 }
10748
10749 pub fn restore_file(
10750 &mut self,
10751 _: &::git::RestoreFile,
10752 window: &mut Window,
10753 cx: &mut Context<Self>,
10754 ) {
10755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10756 let mut buffer_ids = HashSet::default();
10757 let snapshot = self.buffer().read(cx).snapshot(cx);
10758 for selection in self.selections.all::<usize>(cx) {
10759 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10760 }
10761
10762 let buffer = self.buffer().read(cx);
10763 let ranges = buffer_ids
10764 .into_iter()
10765 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10766 .collect::<Vec<_>>();
10767
10768 self.restore_hunks_in_ranges(ranges, window, cx);
10769 }
10770
10771 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10773 let selections = self
10774 .selections
10775 .all(cx)
10776 .into_iter()
10777 .map(|s| s.range())
10778 .collect();
10779 self.restore_hunks_in_ranges(selections, window, cx);
10780 }
10781
10782 pub fn restore_hunks_in_ranges(
10783 &mut self,
10784 ranges: Vec<Range<Point>>,
10785 window: &mut Window,
10786 cx: &mut Context<Editor>,
10787 ) {
10788 let mut revert_changes = HashMap::default();
10789 let chunk_by = self
10790 .snapshot(window, cx)
10791 .hunks_for_ranges(ranges)
10792 .into_iter()
10793 .chunk_by(|hunk| hunk.buffer_id);
10794 for (buffer_id, hunks) in &chunk_by {
10795 let hunks = hunks.collect::<Vec<_>>();
10796 for hunk in &hunks {
10797 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10798 }
10799 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10800 }
10801 drop(chunk_by);
10802 if !revert_changes.is_empty() {
10803 self.transact(window, cx, |editor, window, cx| {
10804 editor.restore(revert_changes, window, cx);
10805 });
10806 }
10807 }
10808
10809 pub fn open_active_item_in_terminal(
10810 &mut self,
10811 _: &OpenInTerminal,
10812 window: &mut Window,
10813 cx: &mut Context<Self>,
10814 ) {
10815 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10816 let project_path = buffer.read(cx).project_path(cx)?;
10817 let project = self.project()?.read(cx);
10818 let entry = project.entry_for_path(&project_path, cx)?;
10819 let parent = match &entry.canonical_path {
10820 Some(canonical_path) => canonical_path.to_path_buf(),
10821 None => project.absolute_path(&project_path, cx)?,
10822 }
10823 .parent()?
10824 .to_path_buf();
10825 Some(parent)
10826 }) {
10827 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10828 }
10829 }
10830
10831 fn set_breakpoint_context_menu(
10832 &mut self,
10833 display_row: DisplayRow,
10834 position: Option<Anchor>,
10835 clicked_point: gpui::Point<Pixels>,
10836 window: &mut Window,
10837 cx: &mut Context<Self>,
10838 ) {
10839 let source = self
10840 .buffer
10841 .read(cx)
10842 .snapshot(cx)
10843 .anchor_before(Point::new(display_row.0, 0u32));
10844
10845 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10846
10847 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10848 self,
10849 source,
10850 clicked_point,
10851 context_menu,
10852 window,
10853 cx,
10854 );
10855 }
10856
10857 fn add_edit_breakpoint_block(
10858 &mut self,
10859 anchor: Anchor,
10860 breakpoint: &Breakpoint,
10861 edit_action: BreakpointPromptEditAction,
10862 window: &mut Window,
10863 cx: &mut Context<Self>,
10864 ) {
10865 let weak_editor = cx.weak_entity();
10866 let bp_prompt = cx.new(|cx| {
10867 BreakpointPromptEditor::new(
10868 weak_editor,
10869 anchor,
10870 breakpoint.clone(),
10871 edit_action,
10872 window,
10873 cx,
10874 )
10875 });
10876
10877 let height = bp_prompt.update(cx, |this, cx| {
10878 this.prompt
10879 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10880 });
10881 let cloned_prompt = bp_prompt.clone();
10882 let blocks = vec![BlockProperties {
10883 style: BlockStyle::Sticky,
10884 placement: BlockPlacement::Above(anchor),
10885 height: Some(height),
10886 render: Arc::new(move |cx| {
10887 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10888 cloned_prompt.clone().into_any_element()
10889 }),
10890 priority: 0,
10891 }];
10892
10893 let focus_handle = bp_prompt.focus_handle(cx);
10894 window.focus(&focus_handle);
10895
10896 let block_ids = self.insert_blocks(blocks, None, cx);
10897 bp_prompt.update(cx, |prompt, _| {
10898 prompt.add_block_ids(block_ids);
10899 });
10900 }
10901
10902 pub(crate) fn breakpoint_at_row(
10903 &self,
10904 row: u32,
10905 window: &mut Window,
10906 cx: &mut Context<Self>,
10907 ) -> Option<(Anchor, Breakpoint)> {
10908 let snapshot = self.snapshot(window, cx);
10909 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10910
10911 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10912 }
10913
10914 pub(crate) fn breakpoint_at_anchor(
10915 &self,
10916 breakpoint_position: Anchor,
10917 snapshot: &EditorSnapshot,
10918 cx: &mut Context<Self>,
10919 ) -> Option<(Anchor, Breakpoint)> {
10920 let buffer = self
10921 .buffer
10922 .read(cx)
10923 .buffer_for_anchor(breakpoint_position, cx)?;
10924
10925 let enclosing_excerpt = breakpoint_position.excerpt_id;
10926 let buffer_snapshot = buffer.read(cx).snapshot();
10927
10928 let row = buffer_snapshot
10929 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10930 .row;
10931
10932 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10933 let anchor_end = snapshot
10934 .buffer_snapshot()
10935 .anchor_after(Point::new(row, line_len));
10936
10937 self.breakpoint_store
10938 .as_ref()?
10939 .read_with(cx, |breakpoint_store, cx| {
10940 breakpoint_store
10941 .breakpoints(
10942 &buffer,
10943 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10944 &buffer_snapshot,
10945 cx,
10946 )
10947 .next()
10948 .and_then(|(bp, _)| {
10949 let breakpoint_row = buffer_snapshot
10950 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10951 .row;
10952
10953 if breakpoint_row == row {
10954 snapshot
10955 .buffer_snapshot()
10956 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10957 .map(|position| (position, bp.bp.clone()))
10958 } else {
10959 None
10960 }
10961 })
10962 })
10963 }
10964
10965 pub fn edit_log_breakpoint(
10966 &mut self,
10967 _: &EditLogBreakpoint,
10968 window: &mut Window,
10969 cx: &mut Context<Self>,
10970 ) {
10971 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10972 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10973 message: None,
10974 state: BreakpointState::Enabled,
10975 condition: None,
10976 hit_condition: None,
10977 });
10978
10979 self.add_edit_breakpoint_block(
10980 anchor,
10981 &breakpoint,
10982 BreakpointPromptEditAction::Log,
10983 window,
10984 cx,
10985 );
10986 }
10987 }
10988
10989 fn breakpoints_at_cursors(
10990 &self,
10991 window: &mut Window,
10992 cx: &mut Context<Self>,
10993 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10994 let snapshot = self.snapshot(window, cx);
10995 let cursors = self
10996 .selections
10997 .disjoint_anchors_arc()
10998 .iter()
10999 .map(|selection| {
11000 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11001
11002 let breakpoint_position = self
11003 .breakpoint_at_row(cursor_position.row, window, cx)
11004 .map(|bp| bp.0)
11005 .unwrap_or_else(|| {
11006 snapshot
11007 .display_snapshot
11008 .buffer_snapshot()
11009 .anchor_after(Point::new(cursor_position.row, 0))
11010 });
11011
11012 let breakpoint = self
11013 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11014 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11015
11016 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11017 })
11018 // 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.
11019 .collect::<HashMap<Anchor, _>>();
11020
11021 cursors.into_iter().collect()
11022 }
11023
11024 pub fn enable_breakpoint(
11025 &mut self,
11026 _: &crate::actions::EnableBreakpoint,
11027 window: &mut Window,
11028 cx: &mut Context<Self>,
11029 ) {
11030 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11031 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11032 continue;
11033 };
11034 self.edit_breakpoint_at_anchor(
11035 anchor,
11036 breakpoint,
11037 BreakpointEditAction::InvertState,
11038 cx,
11039 );
11040 }
11041 }
11042
11043 pub fn disable_breakpoint(
11044 &mut self,
11045 _: &crate::actions::DisableBreakpoint,
11046 window: &mut Window,
11047 cx: &mut Context<Self>,
11048 ) {
11049 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11050 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11051 continue;
11052 };
11053 self.edit_breakpoint_at_anchor(
11054 anchor,
11055 breakpoint,
11056 BreakpointEditAction::InvertState,
11057 cx,
11058 );
11059 }
11060 }
11061
11062 pub fn toggle_breakpoint(
11063 &mut self,
11064 _: &crate::actions::ToggleBreakpoint,
11065 window: &mut Window,
11066 cx: &mut Context<Self>,
11067 ) {
11068 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11069 if let Some(breakpoint) = breakpoint {
11070 self.edit_breakpoint_at_anchor(
11071 anchor,
11072 breakpoint,
11073 BreakpointEditAction::Toggle,
11074 cx,
11075 );
11076 } else {
11077 self.edit_breakpoint_at_anchor(
11078 anchor,
11079 Breakpoint::new_standard(),
11080 BreakpointEditAction::Toggle,
11081 cx,
11082 );
11083 }
11084 }
11085 }
11086
11087 pub fn edit_breakpoint_at_anchor(
11088 &mut self,
11089 breakpoint_position: Anchor,
11090 breakpoint: Breakpoint,
11091 edit_action: BreakpointEditAction,
11092 cx: &mut Context<Self>,
11093 ) {
11094 let Some(breakpoint_store) = &self.breakpoint_store else {
11095 return;
11096 };
11097
11098 let Some(buffer) = self
11099 .buffer
11100 .read(cx)
11101 .buffer_for_anchor(breakpoint_position, cx)
11102 else {
11103 return;
11104 };
11105
11106 breakpoint_store.update(cx, |breakpoint_store, cx| {
11107 breakpoint_store.toggle_breakpoint(
11108 buffer,
11109 BreakpointWithPosition {
11110 position: breakpoint_position.text_anchor,
11111 bp: breakpoint,
11112 },
11113 edit_action,
11114 cx,
11115 );
11116 });
11117
11118 cx.notify();
11119 }
11120
11121 #[cfg(any(test, feature = "test-support"))]
11122 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11123 self.breakpoint_store.clone()
11124 }
11125
11126 pub fn prepare_restore_change(
11127 &self,
11128 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11129 hunk: &MultiBufferDiffHunk,
11130 cx: &mut App,
11131 ) -> Option<()> {
11132 if hunk.is_created_file() {
11133 return None;
11134 }
11135 let buffer = self.buffer.read(cx);
11136 let diff = buffer.diff_for(hunk.buffer_id)?;
11137 let buffer = buffer.buffer(hunk.buffer_id)?;
11138 let buffer = buffer.read(cx);
11139 let original_text = diff
11140 .read(cx)
11141 .base_text()
11142 .as_rope()
11143 .slice(hunk.diff_base_byte_range.clone());
11144 let buffer_snapshot = buffer.snapshot();
11145 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11146 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11147 probe
11148 .0
11149 .start
11150 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11151 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11152 }) {
11153 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11154 Some(())
11155 } else {
11156 None
11157 }
11158 }
11159
11160 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11161 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11162 }
11163
11164 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11165 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11166 }
11167
11168 fn manipulate_lines<M>(
11169 &mut self,
11170 window: &mut Window,
11171 cx: &mut Context<Self>,
11172 mut manipulate: M,
11173 ) where
11174 M: FnMut(&str) -> LineManipulationResult,
11175 {
11176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11177
11178 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11179 let buffer = self.buffer.read(cx).snapshot(cx);
11180
11181 let mut edits = Vec::new();
11182
11183 let selections = self.selections.all::<Point>(cx);
11184 let mut selections = selections.iter().peekable();
11185 let mut contiguous_row_selections = Vec::new();
11186 let mut new_selections = Vec::new();
11187 let mut added_lines = 0;
11188 let mut removed_lines = 0;
11189
11190 while let Some(selection) = selections.next() {
11191 let (start_row, end_row) = consume_contiguous_rows(
11192 &mut contiguous_row_selections,
11193 selection,
11194 &display_map,
11195 &mut selections,
11196 );
11197
11198 let start_point = Point::new(start_row.0, 0);
11199 let end_point = Point::new(
11200 end_row.previous_row().0,
11201 buffer.line_len(end_row.previous_row()),
11202 );
11203 let text = buffer
11204 .text_for_range(start_point..end_point)
11205 .collect::<String>();
11206
11207 let LineManipulationResult {
11208 new_text,
11209 line_count_before,
11210 line_count_after,
11211 } = manipulate(&text);
11212
11213 edits.push((start_point..end_point, new_text));
11214
11215 // Selections must change based on added and removed line count
11216 let start_row =
11217 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11218 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11219 new_selections.push(Selection {
11220 id: selection.id,
11221 start: start_row,
11222 end: end_row,
11223 goal: SelectionGoal::None,
11224 reversed: selection.reversed,
11225 });
11226
11227 if line_count_after > line_count_before {
11228 added_lines += line_count_after - line_count_before;
11229 } else if line_count_before > line_count_after {
11230 removed_lines += line_count_before - line_count_after;
11231 }
11232 }
11233
11234 self.transact(window, cx, |this, window, cx| {
11235 let buffer = this.buffer.update(cx, |buffer, cx| {
11236 buffer.edit(edits, None, cx);
11237 buffer.snapshot(cx)
11238 });
11239
11240 // Recalculate offsets on newly edited buffer
11241 let new_selections = new_selections
11242 .iter()
11243 .map(|s| {
11244 let start_point = Point::new(s.start.0, 0);
11245 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11246 Selection {
11247 id: s.id,
11248 start: buffer.point_to_offset(start_point),
11249 end: buffer.point_to_offset(end_point),
11250 goal: s.goal,
11251 reversed: s.reversed,
11252 }
11253 })
11254 .collect();
11255
11256 this.change_selections(Default::default(), window, cx, |s| {
11257 s.select(new_selections);
11258 });
11259
11260 this.request_autoscroll(Autoscroll::fit(), cx);
11261 });
11262 }
11263
11264 fn manipulate_immutable_lines<Fn>(
11265 &mut self,
11266 window: &mut Window,
11267 cx: &mut Context<Self>,
11268 mut callback: Fn,
11269 ) where
11270 Fn: FnMut(&mut Vec<&str>),
11271 {
11272 self.manipulate_lines(window, cx, |text| {
11273 let mut lines: Vec<&str> = text.split('\n').collect();
11274 let line_count_before = lines.len();
11275
11276 callback(&mut lines);
11277
11278 LineManipulationResult {
11279 new_text: lines.join("\n"),
11280 line_count_before,
11281 line_count_after: lines.len(),
11282 }
11283 });
11284 }
11285
11286 fn manipulate_mutable_lines<Fn>(
11287 &mut self,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 mut callback: Fn,
11291 ) where
11292 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11293 {
11294 self.manipulate_lines(window, cx, |text| {
11295 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11296 let line_count_before = lines.len();
11297
11298 callback(&mut lines);
11299
11300 LineManipulationResult {
11301 new_text: lines.join("\n"),
11302 line_count_before,
11303 line_count_after: lines.len(),
11304 }
11305 });
11306 }
11307
11308 pub fn convert_indentation_to_spaces(
11309 &mut self,
11310 _: &ConvertIndentationToSpaces,
11311 window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) {
11314 let settings = self.buffer.read(cx).language_settings(cx);
11315 let tab_size = settings.tab_size.get() as usize;
11316
11317 self.manipulate_mutable_lines(window, cx, |lines| {
11318 // Allocates a reasonably sized scratch buffer once for the whole loop
11319 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11320 // Avoids recomputing spaces that could be inserted many times
11321 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11322 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11323 .collect();
11324
11325 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11326 let mut chars = line.as_ref().chars();
11327 let mut col = 0;
11328 let mut changed = false;
11329
11330 for ch in chars.by_ref() {
11331 match ch {
11332 ' ' => {
11333 reindented_line.push(' ');
11334 col += 1;
11335 }
11336 '\t' => {
11337 // \t are converted to spaces depending on the current column
11338 let spaces_len = tab_size - (col % tab_size);
11339 reindented_line.extend(&space_cache[spaces_len - 1]);
11340 col += spaces_len;
11341 changed = true;
11342 }
11343 _ => {
11344 // If we dont append before break, the character is consumed
11345 reindented_line.push(ch);
11346 break;
11347 }
11348 }
11349 }
11350
11351 if !changed {
11352 reindented_line.clear();
11353 continue;
11354 }
11355 // Append the rest of the line and replace old reference with new one
11356 reindented_line.extend(chars);
11357 *line = Cow::Owned(reindented_line.clone());
11358 reindented_line.clear();
11359 }
11360 });
11361 }
11362
11363 pub fn convert_indentation_to_tabs(
11364 &mut self,
11365 _: &ConvertIndentationToTabs,
11366 window: &mut Window,
11367 cx: &mut Context<Self>,
11368 ) {
11369 let settings = self.buffer.read(cx).language_settings(cx);
11370 let tab_size = settings.tab_size.get() as usize;
11371
11372 self.manipulate_mutable_lines(window, cx, |lines| {
11373 // Allocates a reasonably sized buffer once for the whole loop
11374 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11375 // Avoids recomputing spaces that could be inserted many times
11376 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11377 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11378 .collect();
11379
11380 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11381 let mut chars = line.chars();
11382 let mut spaces_count = 0;
11383 let mut first_non_indent_char = None;
11384 let mut changed = false;
11385
11386 for ch in chars.by_ref() {
11387 match ch {
11388 ' ' => {
11389 // Keep track of spaces. Append \t when we reach tab_size
11390 spaces_count += 1;
11391 changed = true;
11392 if spaces_count == tab_size {
11393 reindented_line.push('\t');
11394 spaces_count = 0;
11395 }
11396 }
11397 '\t' => {
11398 reindented_line.push('\t');
11399 spaces_count = 0;
11400 }
11401 _ => {
11402 // Dont append it yet, we might have remaining spaces
11403 first_non_indent_char = Some(ch);
11404 break;
11405 }
11406 }
11407 }
11408
11409 if !changed {
11410 reindented_line.clear();
11411 continue;
11412 }
11413 // Remaining spaces that didn't make a full tab stop
11414 if spaces_count > 0 {
11415 reindented_line.extend(&space_cache[spaces_count - 1]);
11416 }
11417 // If we consume an extra character that was not indentation, add it back
11418 if let Some(extra_char) = first_non_indent_char {
11419 reindented_line.push(extra_char);
11420 }
11421 // Append the rest of the line and replace old reference with new one
11422 reindented_line.extend(chars);
11423 *line = Cow::Owned(reindented_line.clone());
11424 reindented_line.clear();
11425 }
11426 });
11427 }
11428
11429 pub fn convert_to_upper_case(
11430 &mut self,
11431 _: &ConvertToUpperCase,
11432 window: &mut Window,
11433 cx: &mut Context<Self>,
11434 ) {
11435 self.manipulate_text(window, cx, |text| text.to_uppercase())
11436 }
11437
11438 pub fn convert_to_lower_case(
11439 &mut self,
11440 _: &ConvertToLowerCase,
11441 window: &mut Window,
11442 cx: &mut Context<Self>,
11443 ) {
11444 self.manipulate_text(window, cx, |text| text.to_lowercase())
11445 }
11446
11447 pub fn convert_to_title_case(
11448 &mut self,
11449 _: &ConvertToTitleCase,
11450 window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 self.manipulate_text(window, cx, |text| {
11454 text.split('\n')
11455 .map(|line| line.to_case(Case::Title))
11456 .join("\n")
11457 })
11458 }
11459
11460 pub fn convert_to_snake_case(
11461 &mut self,
11462 _: &ConvertToSnakeCase,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11467 }
11468
11469 pub fn convert_to_kebab_case(
11470 &mut self,
11471 _: &ConvertToKebabCase,
11472 window: &mut Window,
11473 cx: &mut Context<Self>,
11474 ) {
11475 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11476 }
11477
11478 pub fn convert_to_upper_camel_case(
11479 &mut self,
11480 _: &ConvertToUpperCamelCase,
11481 window: &mut Window,
11482 cx: &mut Context<Self>,
11483 ) {
11484 self.manipulate_text(window, cx, |text| {
11485 text.split('\n')
11486 .map(|line| line.to_case(Case::UpperCamel))
11487 .join("\n")
11488 })
11489 }
11490
11491 pub fn convert_to_lower_camel_case(
11492 &mut self,
11493 _: &ConvertToLowerCamelCase,
11494 window: &mut Window,
11495 cx: &mut Context<Self>,
11496 ) {
11497 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11498 }
11499
11500 pub fn convert_to_opposite_case(
11501 &mut self,
11502 _: &ConvertToOppositeCase,
11503 window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 self.manipulate_text(window, cx, |text| {
11507 text.chars()
11508 .fold(String::with_capacity(text.len()), |mut t, c| {
11509 if c.is_uppercase() {
11510 t.extend(c.to_lowercase());
11511 } else {
11512 t.extend(c.to_uppercase());
11513 }
11514 t
11515 })
11516 })
11517 }
11518
11519 pub fn convert_to_sentence_case(
11520 &mut self,
11521 _: &ConvertToSentenceCase,
11522 window: &mut Window,
11523 cx: &mut Context<Self>,
11524 ) {
11525 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11526 }
11527
11528 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11529 self.manipulate_text(window, cx, |text| {
11530 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11531 if has_upper_case_characters {
11532 text.to_lowercase()
11533 } else {
11534 text.to_uppercase()
11535 }
11536 })
11537 }
11538
11539 pub fn convert_to_rot13(
11540 &mut self,
11541 _: &ConvertToRot13,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 ) {
11545 self.manipulate_text(window, cx, |text| {
11546 text.chars()
11547 .map(|c| match c {
11548 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11549 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11550 _ => c,
11551 })
11552 .collect()
11553 })
11554 }
11555
11556 pub fn convert_to_rot47(
11557 &mut self,
11558 _: &ConvertToRot47,
11559 window: &mut Window,
11560 cx: &mut Context<Self>,
11561 ) {
11562 self.manipulate_text(window, cx, |text| {
11563 text.chars()
11564 .map(|c| {
11565 let code_point = c as u32;
11566 if code_point >= 33 && code_point <= 126 {
11567 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11568 }
11569 c
11570 })
11571 .collect()
11572 })
11573 }
11574
11575 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11576 where
11577 Fn: FnMut(&str) -> String,
11578 {
11579 let buffer = self.buffer.read(cx).snapshot(cx);
11580
11581 let mut new_selections = Vec::new();
11582 let mut edits = Vec::new();
11583 let mut selection_adjustment = 0i32;
11584
11585 for selection in self.selections.all_adjusted(cx) {
11586 let selection_is_empty = selection.is_empty();
11587
11588 let (start, end) = if selection_is_empty {
11589 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11590 (word_range.start, word_range.end)
11591 } else {
11592 (
11593 buffer.point_to_offset(selection.start),
11594 buffer.point_to_offset(selection.end),
11595 )
11596 };
11597
11598 let text = buffer.text_for_range(start..end).collect::<String>();
11599 let old_length = text.len() as i32;
11600 let text = callback(&text);
11601
11602 new_selections.push(Selection {
11603 start: (start as i32 - selection_adjustment) as usize,
11604 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11605 goal: SelectionGoal::None,
11606 id: selection.id,
11607 reversed: selection.reversed,
11608 });
11609
11610 selection_adjustment += old_length - text.len() as i32;
11611
11612 edits.push((start..end, text));
11613 }
11614
11615 self.transact(window, cx, |this, window, cx| {
11616 this.buffer.update(cx, |buffer, cx| {
11617 buffer.edit(edits, None, cx);
11618 });
11619
11620 this.change_selections(Default::default(), window, cx, |s| {
11621 s.select(new_selections);
11622 });
11623
11624 this.request_autoscroll(Autoscroll::fit(), cx);
11625 });
11626 }
11627
11628 pub fn move_selection_on_drop(
11629 &mut self,
11630 selection: &Selection<Anchor>,
11631 target: DisplayPoint,
11632 is_cut: bool,
11633 window: &mut Window,
11634 cx: &mut Context<Self>,
11635 ) {
11636 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11637 let buffer = display_map.buffer_snapshot();
11638 let mut edits = Vec::new();
11639 let insert_point = display_map
11640 .clip_point(target, Bias::Left)
11641 .to_point(&display_map);
11642 let text = buffer
11643 .text_for_range(selection.start..selection.end)
11644 .collect::<String>();
11645 if is_cut {
11646 edits.push(((selection.start..selection.end), String::new()));
11647 }
11648 let insert_anchor = buffer.anchor_before(insert_point);
11649 edits.push(((insert_anchor..insert_anchor), text));
11650 let last_edit_start = insert_anchor.bias_left(buffer);
11651 let last_edit_end = insert_anchor.bias_right(buffer);
11652 self.transact(window, cx, |this, window, cx| {
11653 this.buffer.update(cx, |buffer, cx| {
11654 buffer.edit(edits, None, cx);
11655 });
11656 this.change_selections(Default::default(), window, cx, |s| {
11657 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11658 });
11659 });
11660 }
11661
11662 pub fn clear_selection_drag_state(&mut self) {
11663 self.selection_drag_state = SelectionDragState::None;
11664 }
11665
11666 pub fn duplicate(
11667 &mut self,
11668 upwards: bool,
11669 whole_lines: bool,
11670 window: &mut Window,
11671 cx: &mut Context<Self>,
11672 ) {
11673 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11674
11675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11676 let buffer = display_map.buffer_snapshot();
11677 let selections = self.selections.all::<Point>(cx);
11678
11679 let mut edits = Vec::new();
11680 let mut selections_iter = selections.iter().peekable();
11681 while let Some(selection) = selections_iter.next() {
11682 let mut rows = selection.spanned_rows(false, &display_map);
11683 // duplicate line-wise
11684 if whole_lines || selection.start == selection.end {
11685 // Avoid duplicating the same lines twice.
11686 while let Some(next_selection) = selections_iter.peek() {
11687 let next_rows = next_selection.spanned_rows(false, &display_map);
11688 if next_rows.start < rows.end {
11689 rows.end = next_rows.end;
11690 selections_iter.next().unwrap();
11691 } else {
11692 break;
11693 }
11694 }
11695
11696 // Copy the text from the selected row region and splice it either at the start
11697 // or end of the region.
11698 let start = Point::new(rows.start.0, 0);
11699 let end = Point::new(
11700 rows.end.previous_row().0,
11701 buffer.line_len(rows.end.previous_row()),
11702 );
11703
11704 let mut text = buffer.text_for_range(start..end).collect::<String>();
11705
11706 let insert_location = if upwards {
11707 // When duplicating upward, we need to insert before the current line.
11708 // If we're on the last line and it doesn't end with a newline,
11709 // we need to add a newline before the duplicated content.
11710 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11711 && buffer.max_point().column > 0
11712 && !text.ends_with('\n');
11713
11714 if needs_leading_newline {
11715 text.insert(0, '\n');
11716 end
11717 } else {
11718 text.push('\n');
11719 Point::new(rows.end.0, 0)
11720 }
11721 } else {
11722 text.push('\n');
11723 start
11724 };
11725 edits.push((insert_location..insert_location, text));
11726 } else {
11727 // duplicate character-wise
11728 let start = selection.start;
11729 let end = selection.end;
11730 let text = buffer.text_for_range(start..end).collect::<String>();
11731 edits.push((selection.end..selection.end, text));
11732 }
11733 }
11734
11735 self.transact(window, cx, |this, _, cx| {
11736 this.buffer.update(cx, |buffer, cx| {
11737 buffer.edit(edits, None, cx);
11738 });
11739
11740 this.request_autoscroll(Autoscroll::fit(), cx);
11741 });
11742 }
11743
11744 pub fn duplicate_line_up(
11745 &mut self,
11746 _: &DuplicateLineUp,
11747 window: &mut Window,
11748 cx: &mut Context<Self>,
11749 ) {
11750 self.duplicate(true, true, window, cx);
11751 }
11752
11753 pub fn duplicate_line_down(
11754 &mut self,
11755 _: &DuplicateLineDown,
11756 window: &mut Window,
11757 cx: &mut Context<Self>,
11758 ) {
11759 self.duplicate(false, true, window, cx);
11760 }
11761
11762 pub fn duplicate_selection(
11763 &mut self,
11764 _: &DuplicateSelection,
11765 window: &mut Window,
11766 cx: &mut Context<Self>,
11767 ) {
11768 self.duplicate(false, false, window, cx);
11769 }
11770
11771 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11773 if self.mode.is_single_line() {
11774 cx.propagate();
11775 return;
11776 }
11777
11778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11779 let buffer = self.buffer.read(cx).snapshot(cx);
11780
11781 let mut edits = Vec::new();
11782 let mut unfold_ranges = Vec::new();
11783 let mut refold_creases = Vec::new();
11784
11785 let selections = self.selections.all::<Point>(cx);
11786 let mut selections = selections.iter().peekable();
11787 let mut contiguous_row_selections = Vec::new();
11788 let mut new_selections = Vec::new();
11789
11790 while let Some(selection) = selections.next() {
11791 // Find all the selections that span a contiguous row range
11792 let (start_row, end_row) = consume_contiguous_rows(
11793 &mut contiguous_row_selections,
11794 selection,
11795 &display_map,
11796 &mut selections,
11797 );
11798
11799 // Move the text spanned by the row range to be before the line preceding the row range
11800 if start_row.0 > 0 {
11801 let range_to_move = Point::new(
11802 start_row.previous_row().0,
11803 buffer.line_len(start_row.previous_row()),
11804 )
11805 ..Point::new(
11806 end_row.previous_row().0,
11807 buffer.line_len(end_row.previous_row()),
11808 );
11809 let insertion_point = display_map
11810 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11811 .0;
11812
11813 // Don't move lines across excerpts
11814 if buffer
11815 .excerpt_containing(insertion_point..range_to_move.end)
11816 .is_some()
11817 {
11818 let text = buffer
11819 .text_for_range(range_to_move.clone())
11820 .flat_map(|s| s.chars())
11821 .skip(1)
11822 .chain(['\n'])
11823 .collect::<String>();
11824
11825 edits.push((
11826 buffer.anchor_after(range_to_move.start)
11827 ..buffer.anchor_before(range_to_move.end),
11828 String::new(),
11829 ));
11830 let insertion_anchor = buffer.anchor_after(insertion_point);
11831 edits.push((insertion_anchor..insertion_anchor, text));
11832
11833 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11834
11835 // Move selections up
11836 new_selections.extend(contiguous_row_selections.drain(..).map(
11837 |mut selection| {
11838 selection.start.row -= row_delta;
11839 selection.end.row -= row_delta;
11840 selection
11841 },
11842 ));
11843
11844 // Move folds up
11845 unfold_ranges.push(range_to_move.clone());
11846 for fold in display_map.folds_in_range(
11847 buffer.anchor_before(range_to_move.start)
11848 ..buffer.anchor_after(range_to_move.end),
11849 ) {
11850 let mut start = fold.range.start.to_point(&buffer);
11851 let mut end = fold.range.end.to_point(&buffer);
11852 start.row -= row_delta;
11853 end.row -= row_delta;
11854 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11855 }
11856 }
11857 }
11858
11859 // If we didn't move line(s), preserve the existing selections
11860 new_selections.append(&mut contiguous_row_selections);
11861 }
11862
11863 self.transact(window, cx, |this, window, cx| {
11864 this.unfold_ranges(&unfold_ranges, true, true, cx);
11865 this.buffer.update(cx, |buffer, cx| {
11866 for (range, text) in edits {
11867 buffer.edit([(range, text)], None, cx);
11868 }
11869 });
11870 this.fold_creases(refold_creases, true, window, cx);
11871 this.change_selections(Default::default(), window, cx, |s| {
11872 s.select(new_selections);
11873 })
11874 });
11875 }
11876
11877 pub fn move_line_down(
11878 &mut self,
11879 _: &MoveLineDown,
11880 window: &mut Window,
11881 cx: &mut Context<Self>,
11882 ) {
11883 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11884 if self.mode.is_single_line() {
11885 cx.propagate();
11886 return;
11887 }
11888
11889 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11890 let buffer = self.buffer.read(cx).snapshot(cx);
11891
11892 let mut edits = Vec::new();
11893 let mut unfold_ranges = Vec::new();
11894 let mut refold_creases = Vec::new();
11895
11896 let selections = self.selections.all::<Point>(cx);
11897 let mut selections = selections.iter().peekable();
11898 let mut contiguous_row_selections = Vec::new();
11899 let mut new_selections = Vec::new();
11900
11901 while let Some(selection) = selections.next() {
11902 // Find all the selections that span a contiguous row range
11903 let (start_row, end_row) = consume_contiguous_rows(
11904 &mut contiguous_row_selections,
11905 selection,
11906 &display_map,
11907 &mut selections,
11908 );
11909
11910 // Move the text spanned by the row range to be after the last line of the row range
11911 if end_row.0 <= buffer.max_point().row {
11912 let range_to_move =
11913 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11914 let insertion_point = display_map
11915 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11916 .0;
11917
11918 // Don't move lines across excerpt boundaries
11919 if buffer
11920 .excerpt_containing(range_to_move.start..insertion_point)
11921 .is_some()
11922 {
11923 let mut text = String::from("\n");
11924 text.extend(buffer.text_for_range(range_to_move.clone()));
11925 text.pop(); // Drop trailing newline
11926 edits.push((
11927 buffer.anchor_after(range_to_move.start)
11928 ..buffer.anchor_before(range_to_move.end),
11929 String::new(),
11930 ));
11931 let insertion_anchor = buffer.anchor_after(insertion_point);
11932 edits.push((insertion_anchor..insertion_anchor, text));
11933
11934 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11935
11936 // Move selections down
11937 new_selections.extend(contiguous_row_selections.drain(..).map(
11938 |mut selection| {
11939 selection.start.row += row_delta;
11940 selection.end.row += row_delta;
11941 selection
11942 },
11943 ));
11944
11945 // Move folds down
11946 unfold_ranges.push(range_to_move.clone());
11947 for fold in display_map.folds_in_range(
11948 buffer.anchor_before(range_to_move.start)
11949 ..buffer.anchor_after(range_to_move.end),
11950 ) {
11951 let mut start = fold.range.start.to_point(&buffer);
11952 let mut end = fold.range.end.to_point(&buffer);
11953 start.row += row_delta;
11954 end.row += row_delta;
11955 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11956 }
11957 }
11958 }
11959
11960 // If we didn't move line(s), preserve the existing selections
11961 new_selections.append(&mut contiguous_row_selections);
11962 }
11963
11964 self.transact(window, cx, |this, window, cx| {
11965 this.unfold_ranges(&unfold_ranges, true, true, cx);
11966 this.buffer.update(cx, |buffer, cx| {
11967 for (range, text) in edits {
11968 buffer.edit([(range, text)], None, cx);
11969 }
11970 });
11971 this.fold_creases(refold_creases, true, window, cx);
11972 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11973 });
11974 }
11975
11976 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11978 let text_layout_details = &self.text_layout_details(window);
11979 self.transact(window, cx, |this, window, cx| {
11980 let edits = this.change_selections(Default::default(), window, cx, |s| {
11981 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11982 s.move_with(|display_map, selection| {
11983 if !selection.is_empty() {
11984 return;
11985 }
11986
11987 let mut head = selection.head();
11988 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11989 if head.column() == display_map.line_len(head.row()) {
11990 transpose_offset = display_map
11991 .buffer_snapshot()
11992 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11993 }
11994
11995 if transpose_offset == 0 {
11996 return;
11997 }
11998
11999 *head.column_mut() += 1;
12000 head = display_map.clip_point(head, Bias::Right);
12001 let goal = SelectionGoal::HorizontalPosition(
12002 display_map
12003 .x_for_display_point(head, text_layout_details)
12004 .into(),
12005 );
12006 selection.collapse_to(head, goal);
12007
12008 let transpose_start = display_map
12009 .buffer_snapshot()
12010 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12011 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12012 let transpose_end = display_map
12013 .buffer_snapshot()
12014 .clip_offset(transpose_offset + 1, Bias::Right);
12015 if let Some(ch) = display_map
12016 .buffer_snapshot()
12017 .chars_at(transpose_start)
12018 .next()
12019 {
12020 edits.push((transpose_start..transpose_offset, String::new()));
12021 edits.push((transpose_end..transpose_end, ch.to_string()));
12022 }
12023 }
12024 });
12025 edits
12026 });
12027 this.buffer
12028 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12029 let selections = this.selections.all::<usize>(cx);
12030 this.change_selections(Default::default(), window, cx, |s| {
12031 s.select(selections);
12032 });
12033 });
12034 }
12035
12036 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12037 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12038 if self.mode.is_single_line() {
12039 cx.propagate();
12040 return;
12041 }
12042
12043 self.rewrap_impl(RewrapOptions::default(), cx)
12044 }
12045
12046 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12047 let buffer = self.buffer.read(cx).snapshot(cx);
12048 let selections = self.selections.all::<Point>(cx);
12049
12050 #[derive(Clone, Debug, PartialEq)]
12051 enum CommentFormat {
12052 /// single line comment, with prefix for line
12053 Line(String),
12054 /// single line within a block comment, with prefix for line
12055 BlockLine(String),
12056 /// a single line of a block comment that includes the initial delimiter
12057 BlockCommentWithStart(BlockCommentConfig),
12058 /// a single line of a block comment that includes the ending delimiter
12059 BlockCommentWithEnd(BlockCommentConfig),
12060 }
12061
12062 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12063 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12064 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12065 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12066 .peekable();
12067
12068 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12069 row
12070 } else {
12071 return Vec::new();
12072 };
12073
12074 let language_settings = buffer.language_settings_at(selection.head(), cx);
12075 let language_scope = buffer.language_scope_at(selection.head());
12076
12077 let indent_and_prefix_for_row =
12078 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12079 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12080 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12081 &language_scope
12082 {
12083 let indent_end = Point::new(row, indent.len);
12084 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12085 let line_text_after_indent = buffer
12086 .text_for_range(indent_end..line_end)
12087 .collect::<String>();
12088
12089 let is_within_comment_override = buffer
12090 .language_scope_at(indent_end)
12091 .is_some_and(|scope| scope.override_name() == Some("comment"));
12092 let comment_delimiters = if is_within_comment_override {
12093 // we are within a comment syntax node, but we don't
12094 // yet know what kind of comment: block, doc or line
12095 match (
12096 language_scope.documentation_comment(),
12097 language_scope.block_comment(),
12098 ) {
12099 (Some(config), _) | (_, Some(config))
12100 if buffer.contains_str_at(indent_end, &config.start) =>
12101 {
12102 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12103 }
12104 (Some(config), _) | (_, Some(config))
12105 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12106 {
12107 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12108 }
12109 (Some(config), _) | (_, Some(config))
12110 if buffer.contains_str_at(indent_end, &config.prefix) =>
12111 {
12112 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12113 }
12114 (_, _) => language_scope
12115 .line_comment_prefixes()
12116 .iter()
12117 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12118 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12119 }
12120 } else {
12121 // we not in an overridden comment node, but we may
12122 // be within a non-overridden line comment node
12123 language_scope
12124 .line_comment_prefixes()
12125 .iter()
12126 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12127 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12128 };
12129
12130 let rewrap_prefix = language_scope
12131 .rewrap_prefixes()
12132 .iter()
12133 .find_map(|prefix_regex| {
12134 prefix_regex.find(&line_text_after_indent).map(|mat| {
12135 if mat.start() == 0 {
12136 Some(mat.as_str().to_string())
12137 } else {
12138 None
12139 }
12140 })
12141 })
12142 .flatten();
12143 (comment_delimiters, rewrap_prefix)
12144 } else {
12145 (None, None)
12146 };
12147 (indent, comment_prefix, rewrap_prefix)
12148 };
12149
12150 let mut ranges = Vec::new();
12151 let from_empty_selection = selection.is_empty();
12152
12153 let mut current_range_start = first_row;
12154 let mut prev_row = first_row;
12155 let (
12156 mut current_range_indent,
12157 mut current_range_comment_delimiters,
12158 mut current_range_rewrap_prefix,
12159 ) = indent_and_prefix_for_row(first_row);
12160
12161 for row in non_blank_rows_iter.skip(1) {
12162 let has_paragraph_break = row > prev_row + 1;
12163
12164 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12165 indent_and_prefix_for_row(row);
12166
12167 let has_indent_change = row_indent != current_range_indent;
12168 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12169
12170 let has_boundary_change = has_comment_change
12171 || row_rewrap_prefix.is_some()
12172 || (has_indent_change && current_range_comment_delimiters.is_some());
12173
12174 if has_paragraph_break || has_boundary_change {
12175 ranges.push((
12176 language_settings.clone(),
12177 Point::new(current_range_start, 0)
12178 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12179 current_range_indent,
12180 current_range_comment_delimiters.clone(),
12181 current_range_rewrap_prefix.clone(),
12182 from_empty_selection,
12183 ));
12184 current_range_start = row;
12185 current_range_indent = row_indent;
12186 current_range_comment_delimiters = row_comment_delimiters;
12187 current_range_rewrap_prefix = row_rewrap_prefix;
12188 }
12189 prev_row = row;
12190 }
12191
12192 ranges.push((
12193 language_settings.clone(),
12194 Point::new(current_range_start, 0)
12195 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12196 current_range_indent,
12197 current_range_comment_delimiters,
12198 current_range_rewrap_prefix,
12199 from_empty_selection,
12200 ));
12201
12202 ranges
12203 });
12204
12205 let mut edits = Vec::new();
12206 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12207
12208 for (
12209 language_settings,
12210 wrap_range,
12211 mut indent_size,
12212 comment_prefix,
12213 rewrap_prefix,
12214 from_empty_selection,
12215 ) in wrap_ranges
12216 {
12217 let mut start_row = wrap_range.start.row;
12218 let mut end_row = wrap_range.end.row;
12219
12220 // Skip selections that overlap with a range that has already been rewrapped.
12221 let selection_range = start_row..end_row;
12222 if rewrapped_row_ranges
12223 .iter()
12224 .any(|range| range.overlaps(&selection_range))
12225 {
12226 continue;
12227 }
12228
12229 let tab_size = language_settings.tab_size;
12230
12231 let (line_prefix, inside_comment) = match &comment_prefix {
12232 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12233 (Some(prefix.as_str()), true)
12234 }
12235 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12236 (Some(prefix.as_ref()), true)
12237 }
12238 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12239 start: _,
12240 end: _,
12241 prefix,
12242 tab_size,
12243 })) => {
12244 indent_size.len += tab_size;
12245 (Some(prefix.as_ref()), true)
12246 }
12247 None => (None, false),
12248 };
12249 let indent_prefix = indent_size.chars().collect::<String>();
12250 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12251
12252 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12253 RewrapBehavior::InComments => inside_comment,
12254 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12255 RewrapBehavior::Anywhere => true,
12256 };
12257
12258 let should_rewrap = options.override_language_settings
12259 || allow_rewrap_based_on_language
12260 || self.hard_wrap.is_some();
12261 if !should_rewrap {
12262 continue;
12263 }
12264
12265 if from_empty_selection {
12266 'expand_upwards: while start_row > 0 {
12267 let prev_row = start_row - 1;
12268 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12269 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12270 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12271 {
12272 start_row = prev_row;
12273 } else {
12274 break 'expand_upwards;
12275 }
12276 }
12277
12278 'expand_downwards: while end_row < buffer.max_point().row {
12279 let next_row = end_row + 1;
12280 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12281 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12282 && !buffer.is_line_blank(MultiBufferRow(next_row))
12283 {
12284 end_row = next_row;
12285 } else {
12286 break 'expand_downwards;
12287 }
12288 }
12289 }
12290
12291 let start = Point::new(start_row, 0);
12292 let start_offset = ToOffset::to_offset(&start, &buffer);
12293 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12294 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12295 let mut first_line_delimiter = None;
12296 let mut last_line_delimiter = None;
12297 let Some(lines_without_prefixes) = selection_text
12298 .lines()
12299 .enumerate()
12300 .map(|(ix, line)| {
12301 let line_trimmed = line.trim_start();
12302 if rewrap_prefix.is_some() && ix > 0 {
12303 Ok(line_trimmed)
12304 } else if let Some(
12305 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12306 start,
12307 prefix,
12308 end,
12309 tab_size,
12310 })
12311 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12312 start,
12313 prefix,
12314 end,
12315 tab_size,
12316 }),
12317 ) = &comment_prefix
12318 {
12319 let line_trimmed = line_trimmed
12320 .strip_prefix(start.as_ref())
12321 .map(|s| {
12322 let mut indent_size = indent_size;
12323 indent_size.len -= tab_size;
12324 let indent_prefix: String = indent_size.chars().collect();
12325 first_line_delimiter = Some((indent_prefix, start));
12326 s.trim_start()
12327 })
12328 .unwrap_or(line_trimmed);
12329 let line_trimmed = line_trimmed
12330 .strip_suffix(end.as_ref())
12331 .map(|s| {
12332 last_line_delimiter = Some(end);
12333 s.trim_end()
12334 })
12335 .unwrap_or(line_trimmed);
12336 let line_trimmed = line_trimmed
12337 .strip_prefix(prefix.as_ref())
12338 .unwrap_or(line_trimmed);
12339 Ok(line_trimmed)
12340 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12341 line_trimmed.strip_prefix(prefix).with_context(|| {
12342 format!("line did not start with prefix {prefix:?}: {line:?}")
12343 })
12344 } else {
12345 line_trimmed
12346 .strip_prefix(&line_prefix.trim_start())
12347 .with_context(|| {
12348 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12349 })
12350 }
12351 })
12352 .collect::<Result<Vec<_>, _>>()
12353 .log_err()
12354 else {
12355 continue;
12356 };
12357
12358 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12359 buffer
12360 .language_settings_at(Point::new(start_row, 0), cx)
12361 .preferred_line_length as usize
12362 });
12363
12364 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12365 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12366 } else {
12367 line_prefix.clone()
12368 };
12369
12370 let wrapped_text = {
12371 let mut wrapped_text = wrap_with_prefix(
12372 line_prefix,
12373 subsequent_lines_prefix,
12374 lines_without_prefixes.join("\n"),
12375 wrap_column,
12376 tab_size,
12377 options.preserve_existing_whitespace,
12378 );
12379
12380 if let Some((indent, delimiter)) = first_line_delimiter {
12381 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12382 }
12383 if let Some(last_line) = last_line_delimiter {
12384 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12385 }
12386
12387 wrapped_text
12388 };
12389
12390 // TODO: should always use char-based diff while still supporting cursor behavior that
12391 // matches vim.
12392 let mut diff_options = DiffOptions::default();
12393 if options.override_language_settings {
12394 diff_options.max_word_diff_len = 0;
12395 diff_options.max_word_diff_line_count = 0;
12396 } else {
12397 diff_options.max_word_diff_len = usize::MAX;
12398 diff_options.max_word_diff_line_count = usize::MAX;
12399 }
12400
12401 for (old_range, new_text) in
12402 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12403 {
12404 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12405 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12406 edits.push((edit_start..edit_end, new_text));
12407 }
12408
12409 rewrapped_row_ranges.push(start_row..=end_row);
12410 }
12411
12412 self.buffer
12413 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12414 }
12415
12416 pub fn cut_common(
12417 &mut self,
12418 cut_no_selection_line: bool,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) -> ClipboardItem {
12422 let mut text = String::new();
12423 let buffer = self.buffer.read(cx).snapshot(cx);
12424 let mut selections = self.selections.all::<Point>(cx);
12425 let mut clipboard_selections = Vec::with_capacity(selections.len());
12426 {
12427 let max_point = buffer.max_point();
12428 let mut is_first = true;
12429 for selection in &mut selections {
12430 let is_entire_line =
12431 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12432 if is_entire_line {
12433 selection.start = Point::new(selection.start.row, 0);
12434 if !selection.is_empty() && selection.end.column == 0 {
12435 selection.end = cmp::min(max_point, selection.end);
12436 } else {
12437 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12438 }
12439 selection.goal = SelectionGoal::None;
12440 }
12441 if is_first {
12442 is_first = false;
12443 } else {
12444 text += "\n";
12445 }
12446 let mut len = 0;
12447 for chunk in buffer.text_for_range(selection.start..selection.end) {
12448 text.push_str(chunk);
12449 len += chunk.len();
12450 }
12451 clipboard_selections.push(ClipboardSelection {
12452 len,
12453 is_entire_line,
12454 first_line_indent: buffer
12455 .indent_size_for_line(MultiBufferRow(selection.start.row))
12456 .len,
12457 });
12458 }
12459 }
12460
12461 self.transact(window, cx, |this, window, cx| {
12462 this.change_selections(Default::default(), window, cx, |s| {
12463 s.select(selections);
12464 });
12465 this.insert("", window, cx);
12466 });
12467 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12468 }
12469
12470 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12472 let item = self.cut_common(true, window, cx);
12473 cx.write_to_clipboard(item);
12474 }
12475
12476 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12477 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12478 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12479 s.move_with(|snapshot, sel| {
12480 if sel.is_empty() {
12481 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12482 }
12483 if sel.is_empty() {
12484 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12485 }
12486 });
12487 });
12488 let item = self.cut_common(false, window, cx);
12489 cx.set_global(KillRing(item))
12490 }
12491
12492 pub fn kill_ring_yank(
12493 &mut self,
12494 _: &KillRingYank,
12495 window: &mut Window,
12496 cx: &mut Context<Self>,
12497 ) {
12498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12499 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12500 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12501 (kill_ring.text().to_string(), kill_ring.metadata_json())
12502 } else {
12503 return;
12504 }
12505 } else {
12506 return;
12507 };
12508 self.do_paste(&text, metadata, false, window, cx);
12509 }
12510
12511 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12512 self.do_copy(true, cx);
12513 }
12514
12515 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12516 self.do_copy(false, cx);
12517 }
12518
12519 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12520 let selections = self.selections.all::<Point>(cx);
12521 let buffer = self.buffer.read(cx).read(cx);
12522 let mut text = String::new();
12523
12524 let mut clipboard_selections = Vec::with_capacity(selections.len());
12525 {
12526 let max_point = buffer.max_point();
12527 let mut is_first = true;
12528 for selection in &selections {
12529 let mut start = selection.start;
12530 let mut end = selection.end;
12531 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12532 let mut add_trailing_newline = false;
12533 if is_entire_line {
12534 start = Point::new(start.row, 0);
12535 let next_line_start = Point::new(end.row + 1, 0);
12536 if next_line_start <= max_point {
12537 end = next_line_start;
12538 } else {
12539 // We're on the last line without a trailing newline.
12540 // Copy to the end of the line and add a newline afterwards.
12541 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12542 add_trailing_newline = true;
12543 }
12544 }
12545
12546 let mut trimmed_selections = Vec::new();
12547 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12548 let row = MultiBufferRow(start.row);
12549 let first_indent = buffer.indent_size_for_line(row);
12550 if first_indent.len == 0 || start.column > first_indent.len {
12551 trimmed_selections.push(start..end);
12552 } else {
12553 trimmed_selections.push(
12554 Point::new(row.0, first_indent.len)
12555 ..Point::new(row.0, buffer.line_len(row)),
12556 );
12557 for row in start.row + 1..=end.row {
12558 let mut line_len = buffer.line_len(MultiBufferRow(row));
12559 if row == end.row {
12560 line_len = end.column;
12561 }
12562 if line_len == 0 {
12563 trimmed_selections
12564 .push(Point::new(row, 0)..Point::new(row, line_len));
12565 continue;
12566 }
12567 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12568 if row_indent_size.len >= first_indent.len {
12569 trimmed_selections.push(
12570 Point::new(row, first_indent.len)..Point::new(row, line_len),
12571 );
12572 } else {
12573 trimmed_selections.clear();
12574 trimmed_selections.push(start..end);
12575 break;
12576 }
12577 }
12578 }
12579 } else {
12580 trimmed_selections.push(start..end);
12581 }
12582
12583 for trimmed_range in trimmed_selections {
12584 if is_first {
12585 is_first = false;
12586 } else {
12587 text += "\n";
12588 }
12589 let mut len = 0;
12590 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12591 text.push_str(chunk);
12592 len += chunk.len();
12593 }
12594 if add_trailing_newline {
12595 text.push('\n');
12596 len += 1;
12597 }
12598 clipboard_selections.push(ClipboardSelection {
12599 len,
12600 is_entire_line,
12601 first_line_indent: buffer
12602 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12603 .len,
12604 });
12605 }
12606 }
12607 }
12608
12609 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12610 text,
12611 clipboard_selections,
12612 ));
12613 }
12614
12615 pub fn do_paste(
12616 &mut self,
12617 text: &String,
12618 clipboard_selections: Option<Vec<ClipboardSelection>>,
12619 handle_entire_lines: bool,
12620 window: &mut Window,
12621 cx: &mut Context<Self>,
12622 ) {
12623 if self.read_only(cx) {
12624 return;
12625 }
12626
12627 let clipboard_text = Cow::Borrowed(text.as_str());
12628
12629 self.transact(window, cx, |this, window, cx| {
12630 let had_active_edit_prediction = this.has_active_edit_prediction();
12631 let old_selections = this.selections.all::<usize>(cx);
12632 let cursor_offset = this.selections.last::<usize>(cx).head();
12633
12634 if let Some(mut clipboard_selections) = clipboard_selections {
12635 let all_selections_were_entire_line =
12636 clipboard_selections.iter().all(|s| s.is_entire_line);
12637 let first_selection_indent_column =
12638 clipboard_selections.first().map(|s| s.first_line_indent);
12639 if clipboard_selections.len() != old_selections.len() {
12640 clipboard_selections.drain(..);
12641 }
12642 let mut auto_indent_on_paste = true;
12643
12644 this.buffer.update(cx, |buffer, cx| {
12645 let snapshot = buffer.read(cx);
12646 auto_indent_on_paste = snapshot
12647 .language_settings_at(cursor_offset, cx)
12648 .auto_indent_on_paste;
12649
12650 let mut start_offset = 0;
12651 let mut edits = Vec::new();
12652 let mut original_indent_columns = Vec::new();
12653 for (ix, selection) in old_selections.iter().enumerate() {
12654 let to_insert;
12655 let entire_line;
12656 let original_indent_column;
12657 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12658 let end_offset = start_offset + clipboard_selection.len;
12659 to_insert = &clipboard_text[start_offset..end_offset];
12660 entire_line = clipboard_selection.is_entire_line;
12661 start_offset = end_offset + 1;
12662 original_indent_column = Some(clipboard_selection.first_line_indent);
12663 } else {
12664 to_insert = &*clipboard_text;
12665 entire_line = all_selections_were_entire_line;
12666 original_indent_column = first_selection_indent_column
12667 }
12668
12669 let (range, to_insert) =
12670 if selection.is_empty() && handle_entire_lines && entire_line {
12671 // If the corresponding selection was empty when this slice of the
12672 // clipboard text was written, then the entire line containing the
12673 // selection was copied. If this selection is also currently empty,
12674 // then paste the line before the current line of the buffer.
12675 let column = selection.start.to_point(&snapshot).column as usize;
12676 let line_start = selection.start - column;
12677 (line_start..line_start, Cow::Borrowed(to_insert))
12678 } else {
12679 let language = snapshot.language_at(selection.head());
12680 let range = selection.range();
12681 if let Some(language) = language
12682 && language.name() == "Markdown".into()
12683 {
12684 edit_for_markdown_paste(
12685 &snapshot,
12686 range,
12687 to_insert,
12688 url::Url::parse(to_insert).ok(),
12689 )
12690 } else {
12691 (range, Cow::Borrowed(to_insert))
12692 }
12693 };
12694
12695 edits.push((range, to_insert));
12696 original_indent_columns.push(original_indent_column);
12697 }
12698 drop(snapshot);
12699
12700 buffer.edit(
12701 edits,
12702 if auto_indent_on_paste {
12703 Some(AutoindentMode::Block {
12704 original_indent_columns,
12705 })
12706 } else {
12707 None
12708 },
12709 cx,
12710 );
12711 });
12712
12713 let selections = this.selections.all::<usize>(cx);
12714 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12715 } else {
12716 let url = url::Url::parse(&clipboard_text).ok();
12717
12718 let auto_indent_mode = if !clipboard_text.is_empty() {
12719 Some(AutoindentMode::Block {
12720 original_indent_columns: Vec::new(),
12721 })
12722 } else {
12723 None
12724 };
12725
12726 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12727 let snapshot = buffer.snapshot(cx);
12728
12729 let anchors = old_selections
12730 .iter()
12731 .map(|s| {
12732 let anchor = snapshot.anchor_after(s.head());
12733 s.map(|_| anchor)
12734 })
12735 .collect::<Vec<_>>();
12736
12737 let mut edits = Vec::new();
12738
12739 for selection in old_selections.iter() {
12740 let language = snapshot.language_at(selection.head());
12741 let range = selection.range();
12742
12743 let (edit_range, edit_text) = if let Some(language) = language
12744 && language.name() == "Markdown".into()
12745 {
12746 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12747 } else {
12748 (range, clipboard_text.clone())
12749 };
12750
12751 edits.push((edit_range, edit_text));
12752 }
12753
12754 drop(snapshot);
12755 buffer.edit(edits, auto_indent_mode, cx);
12756
12757 anchors
12758 });
12759
12760 this.change_selections(Default::default(), window, cx, |s| {
12761 s.select_anchors(selection_anchors);
12762 });
12763 }
12764
12765 let trigger_in_words =
12766 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12767
12768 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12769 });
12770 }
12771
12772 pub fn diff_clipboard_with_selection(
12773 &mut self,
12774 _: &DiffClipboardWithSelection,
12775 window: &mut Window,
12776 cx: &mut Context<Self>,
12777 ) {
12778 let selections = self.selections.all::<usize>(cx);
12779
12780 if selections.is_empty() {
12781 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12782 return;
12783 };
12784
12785 let clipboard_text = match cx.read_from_clipboard() {
12786 Some(item) => match item.entries().first() {
12787 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12788 _ => None,
12789 },
12790 None => None,
12791 };
12792
12793 let Some(clipboard_text) = clipboard_text else {
12794 log::warn!("Clipboard doesn't contain text.");
12795 return;
12796 };
12797
12798 window.dispatch_action(
12799 Box::new(DiffClipboardWithSelectionData {
12800 clipboard_text,
12801 editor: cx.entity(),
12802 }),
12803 cx,
12804 );
12805 }
12806
12807 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12808 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12809 if let Some(item) = cx.read_from_clipboard() {
12810 let entries = item.entries();
12811
12812 match entries.first() {
12813 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12814 // of all the pasted entries.
12815 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12816 .do_paste(
12817 clipboard_string.text(),
12818 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12819 true,
12820 window,
12821 cx,
12822 ),
12823 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12824 }
12825 }
12826 }
12827
12828 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12829 if self.read_only(cx) {
12830 return;
12831 }
12832
12833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12834
12835 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12836 if let Some((selections, _)) =
12837 self.selection_history.transaction(transaction_id).cloned()
12838 {
12839 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12840 s.select_anchors(selections.to_vec());
12841 });
12842 } else {
12843 log::error!(
12844 "No entry in selection_history found for undo. \
12845 This may correspond to a bug where undo does not update the selection. \
12846 If this is occurring, please add details to \
12847 https://github.com/zed-industries/zed/issues/22692"
12848 );
12849 }
12850 self.request_autoscroll(Autoscroll::fit(), cx);
12851 self.unmark_text(window, cx);
12852 self.refresh_edit_prediction(true, false, window, cx);
12853 cx.emit(EditorEvent::Edited { transaction_id });
12854 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12855 }
12856 }
12857
12858 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12859 if self.read_only(cx) {
12860 return;
12861 }
12862
12863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12864
12865 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12866 if let Some((_, Some(selections))) =
12867 self.selection_history.transaction(transaction_id).cloned()
12868 {
12869 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12870 s.select_anchors(selections.to_vec());
12871 });
12872 } else {
12873 log::error!(
12874 "No entry in selection_history found for redo. \
12875 This may correspond to a bug where undo does not update the selection. \
12876 If this is occurring, please add details to \
12877 https://github.com/zed-industries/zed/issues/22692"
12878 );
12879 }
12880 self.request_autoscroll(Autoscroll::fit(), cx);
12881 self.unmark_text(window, cx);
12882 self.refresh_edit_prediction(true, false, window, cx);
12883 cx.emit(EditorEvent::Edited { transaction_id });
12884 }
12885 }
12886
12887 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12888 self.buffer
12889 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12890 }
12891
12892 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12893 self.buffer
12894 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12895 }
12896
12897 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12899 self.change_selections(Default::default(), window, cx, |s| {
12900 s.move_with(|map, selection| {
12901 let cursor = if selection.is_empty() {
12902 movement::left(map, selection.start)
12903 } else {
12904 selection.start
12905 };
12906 selection.collapse_to(cursor, SelectionGoal::None);
12907 });
12908 })
12909 }
12910
12911 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12913 self.change_selections(Default::default(), window, cx, |s| {
12914 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12915 })
12916 }
12917
12918 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12920 self.change_selections(Default::default(), window, cx, |s| {
12921 s.move_with(|map, selection| {
12922 let cursor = if selection.is_empty() {
12923 movement::right(map, selection.end)
12924 } else {
12925 selection.end
12926 };
12927 selection.collapse_to(cursor, SelectionGoal::None)
12928 });
12929 })
12930 }
12931
12932 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12933 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12934 self.change_selections(Default::default(), window, cx, |s| {
12935 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12936 });
12937 }
12938
12939 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12940 if self.take_rename(true, window, cx).is_some() {
12941 return;
12942 }
12943
12944 if self.mode.is_single_line() {
12945 cx.propagate();
12946 return;
12947 }
12948
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950
12951 let text_layout_details = &self.text_layout_details(window);
12952 let selection_count = self.selections.count();
12953 let first_selection = self.selections.first_anchor();
12954
12955 self.change_selections(Default::default(), window, cx, |s| {
12956 s.move_with(|map, selection| {
12957 if !selection.is_empty() {
12958 selection.goal = SelectionGoal::None;
12959 }
12960 let (cursor, goal) = movement::up(
12961 map,
12962 selection.start,
12963 selection.goal,
12964 false,
12965 text_layout_details,
12966 );
12967 selection.collapse_to(cursor, goal);
12968 });
12969 });
12970
12971 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12972 {
12973 cx.propagate();
12974 }
12975 }
12976
12977 pub fn move_up_by_lines(
12978 &mut self,
12979 action: &MoveUpByLines,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 if self.take_rename(true, window, cx).is_some() {
12984 return;
12985 }
12986
12987 if self.mode.is_single_line() {
12988 cx.propagate();
12989 return;
12990 }
12991
12992 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12993
12994 let text_layout_details = &self.text_layout_details(window);
12995
12996 self.change_selections(Default::default(), window, cx, |s| {
12997 s.move_with(|map, selection| {
12998 if !selection.is_empty() {
12999 selection.goal = SelectionGoal::None;
13000 }
13001 let (cursor, goal) = movement::up_by_rows(
13002 map,
13003 selection.start,
13004 action.lines,
13005 selection.goal,
13006 false,
13007 text_layout_details,
13008 );
13009 selection.collapse_to(cursor, goal);
13010 });
13011 })
13012 }
13013
13014 pub fn move_down_by_lines(
13015 &mut self,
13016 action: &MoveDownByLines,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 if self.take_rename(true, window, cx).is_some() {
13021 return;
13022 }
13023
13024 if self.mode.is_single_line() {
13025 cx.propagate();
13026 return;
13027 }
13028
13029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13030
13031 let text_layout_details = &self.text_layout_details(window);
13032
13033 self.change_selections(Default::default(), window, cx, |s| {
13034 s.move_with(|map, selection| {
13035 if !selection.is_empty() {
13036 selection.goal = SelectionGoal::None;
13037 }
13038 let (cursor, goal) = movement::down_by_rows(
13039 map,
13040 selection.start,
13041 action.lines,
13042 selection.goal,
13043 false,
13044 text_layout_details,
13045 );
13046 selection.collapse_to(cursor, goal);
13047 });
13048 })
13049 }
13050
13051 pub fn select_down_by_lines(
13052 &mut self,
13053 action: &SelectDownByLines,
13054 window: &mut Window,
13055 cx: &mut Context<Self>,
13056 ) {
13057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13058 let text_layout_details = &self.text_layout_details(window);
13059 self.change_selections(Default::default(), window, cx, |s| {
13060 s.move_heads_with(|map, head, goal| {
13061 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13062 })
13063 })
13064 }
13065
13066 pub fn select_up_by_lines(
13067 &mut self,
13068 action: &SelectUpByLines,
13069 window: &mut Window,
13070 cx: &mut Context<Self>,
13071 ) {
13072 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13073 let text_layout_details = &self.text_layout_details(window);
13074 self.change_selections(Default::default(), window, cx, |s| {
13075 s.move_heads_with(|map, head, goal| {
13076 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13077 })
13078 })
13079 }
13080
13081 pub fn select_page_up(
13082 &mut self,
13083 _: &SelectPageUp,
13084 window: &mut Window,
13085 cx: &mut Context<Self>,
13086 ) {
13087 let Some(row_count) = self.visible_row_count() else {
13088 return;
13089 };
13090
13091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13092
13093 let text_layout_details = &self.text_layout_details(window);
13094
13095 self.change_selections(Default::default(), window, cx, |s| {
13096 s.move_heads_with(|map, head, goal| {
13097 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13098 })
13099 })
13100 }
13101
13102 pub fn move_page_up(
13103 &mut self,
13104 action: &MovePageUp,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 if self.take_rename(true, window, cx).is_some() {
13109 return;
13110 }
13111
13112 if self
13113 .context_menu
13114 .borrow_mut()
13115 .as_mut()
13116 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13117 .unwrap_or(false)
13118 {
13119 return;
13120 }
13121
13122 if matches!(self.mode, EditorMode::SingleLine) {
13123 cx.propagate();
13124 return;
13125 }
13126
13127 let Some(row_count) = self.visible_row_count() else {
13128 return;
13129 };
13130
13131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13132
13133 let effects = if action.center_cursor {
13134 SelectionEffects::scroll(Autoscroll::center())
13135 } else {
13136 SelectionEffects::default()
13137 };
13138
13139 let text_layout_details = &self.text_layout_details(window);
13140
13141 self.change_selections(effects, window, cx, |s| {
13142 s.move_with(|map, selection| {
13143 if !selection.is_empty() {
13144 selection.goal = SelectionGoal::None;
13145 }
13146 let (cursor, goal) = movement::up_by_rows(
13147 map,
13148 selection.end,
13149 row_count,
13150 selection.goal,
13151 false,
13152 text_layout_details,
13153 );
13154 selection.collapse_to(cursor, goal);
13155 });
13156 });
13157 }
13158
13159 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13160 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13161 let text_layout_details = &self.text_layout_details(window);
13162 self.change_selections(Default::default(), window, cx, |s| {
13163 s.move_heads_with(|map, head, goal| {
13164 movement::up(map, head, goal, false, text_layout_details)
13165 })
13166 })
13167 }
13168
13169 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13170 self.take_rename(true, window, cx);
13171
13172 if self.mode.is_single_line() {
13173 cx.propagate();
13174 return;
13175 }
13176
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13178
13179 let text_layout_details = &self.text_layout_details(window);
13180 let selection_count = self.selections.count();
13181 let first_selection = self.selections.first_anchor();
13182
13183 self.change_selections(Default::default(), window, cx, |s| {
13184 s.move_with(|map, selection| {
13185 if !selection.is_empty() {
13186 selection.goal = SelectionGoal::None;
13187 }
13188 let (cursor, goal) = movement::down(
13189 map,
13190 selection.end,
13191 selection.goal,
13192 false,
13193 text_layout_details,
13194 );
13195 selection.collapse_to(cursor, goal);
13196 });
13197 });
13198
13199 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13200 {
13201 cx.propagate();
13202 }
13203 }
13204
13205 pub fn select_page_down(
13206 &mut self,
13207 _: &SelectPageDown,
13208 window: &mut Window,
13209 cx: &mut Context<Self>,
13210 ) {
13211 let Some(row_count) = self.visible_row_count() else {
13212 return;
13213 };
13214
13215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13216
13217 let text_layout_details = &self.text_layout_details(window);
13218
13219 self.change_selections(Default::default(), window, cx, |s| {
13220 s.move_heads_with(|map, head, goal| {
13221 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13222 })
13223 })
13224 }
13225
13226 pub fn move_page_down(
13227 &mut self,
13228 action: &MovePageDown,
13229 window: &mut Window,
13230 cx: &mut Context<Self>,
13231 ) {
13232 if self.take_rename(true, window, cx).is_some() {
13233 return;
13234 }
13235
13236 if self
13237 .context_menu
13238 .borrow_mut()
13239 .as_mut()
13240 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13241 .unwrap_or(false)
13242 {
13243 return;
13244 }
13245
13246 if matches!(self.mode, EditorMode::SingleLine) {
13247 cx.propagate();
13248 return;
13249 }
13250
13251 let Some(row_count) = self.visible_row_count() else {
13252 return;
13253 };
13254
13255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13256
13257 let effects = if action.center_cursor {
13258 SelectionEffects::scroll(Autoscroll::center())
13259 } else {
13260 SelectionEffects::default()
13261 };
13262
13263 let text_layout_details = &self.text_layout_details(window);
13264 self.change_selections(effects, window, cx, |s| {
13265 s.move_with(|map, selection| {
13266 if !selection.is_empty() {
13267 selection.goal = SelectionGoal::None;
13268 }
13269 let (cursor, goal) = movement::down_by_rows(
13270 map,
13271 selection.end,
13272 row_count,
13273 selection.goal,
13274 false,
13275 text_layout_details,
13276 );
13277 selection.collapse_to(cursor, goal);
13278 });
13279 });
13280 }
13281
13282 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13284 let text_layout_details = &self.text_layout_details(window);
13285 self.change_selections(Default::default(), window, cx, |s| {
13286 s.move_heads_with(|map, head, goal| {
13287 movement::down(map, head, goal, false, text_layout_details)
13288 })
13289 });
13290 }
13291
13292 pub fn context_menu_first(
13293 &mut self,
13294 _: &ContextMenuFirst,
13295 window: &mut Window,
13296 cx: &mut Context<Self>,
13297 ) {
13298 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13299 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13300 }
13301 }
13302
13303 pub fn context_menu_prev(
13304 &mut self,
13305 _: &ContextMenuPrevious,
13306 window: &mut Window,
13307 cx: &mut Context<Self>,
13308 ) {
13309 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13310 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13311 }
13312 }
13313
13314 pub fn context_menu_next(
13315 &mut self,
13316 _: &ContextMenuNext,
13317 window: &mut Window,
13318 cx: &mut Context<Self>,
13319 ) {
13320 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13321 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13322 }
13323 }
13324
13325 pub fn context_menu_last(
13326 &mut self,
13327 _: &ContextMenuLast,
13328 window: &mut Window,
13329 cx: &mut Context<Self>,
13330 ) {
13331 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13332 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13333 }
13334 }
13335
13336 pub fn signature_help_prev(
13337 &mut self,
13338 _: &SignatureHelpPrevious,
13339 _: &mut Window,
13340 cx: &mut Context<Self>,
13341 ) {
13342 if let Some(popover) = self.signature_help_state.popover_mut() {
13343 if popover.current_signature == 0 {
13344 popover.current_signature = popover.signatures.len() - 1;
13345 } else {
13346 popover.current_signature -= 1;
13347 }
13348 cx.notify();
13349 }
13350 }
13351
13352 pub fn signature_help_next(
13353 &mut self,
13354 _: &SignatureHelpNext,
13355 _: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 if let Some(popover) = self.signature_help_state.popover_mut() {
13359 if popover.current_signature + 1 == popover.signatures.len() {
13360 popover.current_signature = 0;
13361 } else {
13362 popover.current_signature += 1;
13363 }
13364 cx.notify();
13365 }
13366 }
13367
13368 pub fn move_to_previous_word_start(
13369 &mut self,
13370 _: &MoveToPreviousWordStart,
13371 window: &mut Window,
13372 cx: &mut Context<Self>,
13373 ) {
13374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13375 self.change_selections(Default::default(), window, cx, |s| {
13376 s.move_cursors_with(|map, head, _| {
13377 (
13378 movement::previous_word_start(map, head),
13379 SelectionGoal::None,
13380 )
13381 });
13382 })
13383 }
13384
13385 pub fn move_to_previous_subword_start(
13386 &mut self,
13387 _: &MoveToPreviousSubwordStart,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) {
13391 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13392 self.change_selections(Default::default(), window, cx, |s| {
13393 s.move_cursors_with(|map, head, _| {
13394 (
13395 movement::previous_subword_start(map, head),
13396 SelectionGoal::None,
13397 )
13398 });
13399 })
13400 }
13401
13402 pub fn select_to_previous_word_start(
13403 &mut self,
13404 _: &SelectToPreviousWordStart,
13405 window: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13409 self.change_selections(Default::default(), window, cx, |s| {
13410 s.move_heads_with(|map, head, _| {
13411 (
13412 movement::previous_word_start(map, head),
13413 SelectionGoal::None,
13414 )
13415 });
13416 })
13417 }
13418
13419 pub fn select_to_previous_subword_start(
13420 &mut self,
13421 _: &SelectToPreviousSubwordStart,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13426 self.change_selections(Default::default(), window, cx, |s| {
13427 s.move_heads_with(|map, head, _| {
13428 (
13429 movement::previous_subword_start(map, head),
13430 SelectionGoal::None,
13431 )
13432 });
13433 })
13434 }
13435
13436 pub fn delete_to_previous_word_start(
13437 &mut self,
13438 action: &DeleteToPreviousWordStart,
13439 window: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13443 self.transact(window, cx, |this, window, cx| {
13444 this.select_autoclose_pair(window, cx);
13445 this.change_selections(Default::default(), window, cx, |s| {
13446 s.move_with(|map, selection| {
13447 if selection.is_empty() {
13448 let mut cursor = if action.ignore_newlines {
13449 movement::previous_word_start(map, selection.head())
13450 } else {
13451 movement::previous_word_start_or_newline(map, selection.head())
13452 };
13453 cursor = movement::adjust_greedy_deletion(
13454 map,
13455 selection.head(),
13456 cursor,
13457 action.ignore_brackets,
13458 );
13459 selection.set_head(cursor, SelectionGoal::None);
13460 }
13461 });
13462 });
13463 this.insert("", window, cx);
13464 });
13465 }
13466
13467 pub fn delete_to_previous_subword_start(
13468 &mut self,
13469 _: &DeleteToPreviousSubwordStart,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) {
13473 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13474 self.transact(window, cx, |this, window, cx| {
13475 this.select_autoclose_pair(window, cx);
13476 this.change_selections(Default::default(), window, cx, |s| {
13477 s.move_with(|map, selection| {
13478 if selection.is_empty() {
13479 let mut cursor = movement::previous_subword_start(map, selection.head());
13480 cursor =
13481 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13482 selection.set_head(cursor, SelectionGoal::None);
13483 }
13484 });
13485 });
13486 this.insert("", window, cx);
13487 });
13488 }
13489
13490 pub fn move_to_next_word_end(
13491 &mut self,
13492 _: &MoveToNextWordEnd,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13497 self.change_selections(Default::default(), window, cx, |s| {
13498 s.move_cursors_with(|map, head, _| {
13499 (movement::next_word_end(map, head), SelectionGoal::None)
13500 });
13501 })
13502 }
13503
13504 pub fn move_to_next_subword_end(
13505 &mut self,
13506 _: &MoveToNextSubwordEnd,
13507 window: &mut Window,
13508 cx: &mut Context<Self>,
13509 ) {
13510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13511 self.change_selections(Default::default(), window, cx, |s| {
13512 s.move_cursors_with(|map, head, _| {
13513 (movement::next_subword_end(map, head), SelectionGoal::None)
13514 });
13515 })
13516 }
13517
13518 pub fn select_to_next_word_end(
13519 &mut self,
13520 _: &SelectToNextWordEnd,
13521 window: &mut Window,
13522 cx: &mut Context<Self>,
13523 ) {
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13525 self.change_selections(Default::default(), window, cx, |s| {
13526 s.move_heads_with(|map, head, _| {
13527 (movement::next_word_end(map, head), SelectionGoal::None)
13528 });
13529 })
13530 }
13531
13532 pub fn select_to_next_subword_end(
13533 &mut self,
13534 _: &SelectToNextSubwordEnd,
13535 window: &mut Window,
13536 cx: &mut Context<Self>,
13537 ) {
13538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13539 self.change_selections(Default::default(), window, cx, |s| {
13540 s.move_heads_with(|map, head, _| {
13541 (movement::next_subword_end(map, head), SelectionGoal::None)
13542 });
13543 })
13544 }
13545
13546 pub fn delete_to_next_word_end(
13547 &mut self,
13548 action: &DeleteToNextWordEnd,
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.change_selections(Default::default(), window, cx, |s| {
13555 s.move_with(|map, selection| {
13556 if selection.is_empty() {
13557 let mut cursor = if action.ignore_newlines {
13558 movement::next_word_end(map, selection.head())
13559 } else {
13560 movement::next_word_end_or_newline(map, selection.head())
13561 };
13562 cursor = movement::adjust_greedy_deletion(
13563 map,
13564 selection.head(),
13565 cursor,
13566 action.ignore_brackets,
13567 );
13568 selection.set_head(cursor, SelectionGoal::None);
13569 }
13570 });
13571 });
13572 this.insert("", window, cx);
13573 });
13574 }
13575
13576 pub fn delete_to_next_subword_end(
13577 &mut self,
13578 _: &DeleteToNextSubwordEnd,
13579 window: &mut Window,
13580 cx: &mut Context<Self>,
13581 ) {
13582 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13583 self.transact(window, cx, |this, window, cx| {
13584 this.change_selections(Default::default(), window, cx, |s| {
13585 s.move_with(|map, selection| {
13586 if selection.is_empty() {
13587 let mut cursor = movement::next_subword_end(map, selection.head());
13588 cursor =
13589 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13590 selection.set_head(cursor, SelectionGoal::None);
13591 }
13592 });
13593 });
13594 this.insert("", window, cx);
13595 });
13596 }
13597
13598 pub fn move_to_beginning_of_line(
13599 &mut self,
13600 action: &MoveToBeginningOfLine,
13601 window: &mut Window,
13602 cx: &mut Context<Self>,
13603 ) {
13604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13605 self.change_selections(Default::default(), window, cx, |s| {
13606 s.move_cursors_with(|map, head, _| {
13607 (
13608 movement::indented_line_beginning(
13609 map,
13610 head,
13611 action.stop_at_soft_wraps,
13612 action.stop_at_indent,
13613 ),
13614 SelectionGoal::None,
13615 )
13616 });
13617 })
13618 }
13619
13620 pub fn select_to_beginning_of_line(
13621 &mut self,
13622 action: &SelectToBeginningOfLine,
13623 window: &mut Window,
13624 cx: &mut Context<Self>,
13625 ) {
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13627 self.change_selections(Default::default(), window, cx, |s| {
13628 s.move_heads_with(|map, head, _| {
13629 (
13630 movement::indented_line_beginning(
13631 map,
13632 head,
13633 action.stop_at_soft_wraps,
13634 action.stop_at_indent,
13635 ),
13636 SelectionGoal::None,
13637 )
13638 });
13639 });
13640 }
13641
13642 pub fn delete_to_beginning_of_line(
13643 &mut self,
13644 action: &DeleteToBeginningOfLine,
13645 window: &mut Window,
13646 cx: &mut Context<Self>,
13647 ) {
13648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13649 self.transact(window, cx, |this, window, cx| {
13650 this.change_selections(Default::default(), window, cx, |s| {
13651 s.move_with(|_, selection| {
13652 selection.reversed = true;
13653 });
13654 });
13655
13656 this.select_to_beginning_of_line(
13657 &SelectToBeginningOfLine {
13658 stop_at_soft_wraps: false,
13659 stop_at_indent: action.stop_at_indent,
13660 },
13661 window,
13662 cx,
13663 );
13664 this.backspace(&Backspace, window, cx);
13665 });
13666 }
13667
13668 pub fn move_to_end_of_line(
13669 &mut self,
13670 action: &MoveToEndOfLine,
13671 window: &mut Window,
13672 cx: &mut Context<Self>,
13673 ) {
13674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13675 self.change_selections(Default::default(), window, cx, |s| {
13676 s.move_cursors_with(|map, head, _| {
13677 (
13678 movement::line_end(map, head, action.stop_at_soft_wraps),
13679 SelectionGoal::None,
13680 )
13681 });
13682 })
13683 }
13684
13685 pub fn select_to_end_of_line(
13686 &mut self,
13687 action: &SelectToEndOfLine,
13688 window: &mut Window,
13689 cx: &mut Context<Self>,
13690 ) {
13691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13692 self.change_selections(Default::default(), window, cx, |s| {
13693 s.move_heads_with(|map, head, _| {
13694 (
13695 movement::line_end(map, head, action.stop_at_soft_wraps),
13696 SelectionGoal::None,
13697 )
13698 });
13699 })
13700 }
13701
13702 pub fn delete_to_end_of_line(
13703 &mut self,
13704 _: &DeleteToEndOfLine,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13709 self.transact(window, cx, |this, window, cx| {
13710 this.select_to_end_of_line(
13711 &SelectToEndOfLine {
13712 stop_at_soft_wraps: false,
13713 },
13714 window,
13715 cx,
13716 );
13717 this.delete(&Delete, window, cx);
13718 });
13719 }
13720
13721 pub fn cut_to_end_of_line(
13722 &mut self,
13723 action: &CutToEndOfLine,
13724 window: &mut Window,
13725 cx: &mut Context<Self>,
13726 ) {
13727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13728 self.transact(window, cx, |this, window, cx| {
13729 this.select_to_end_of_line(
13730 &SelectToEndOfLine {
13731 stop_at_soft_wraps: false,
13732 },
13733 window,
13734 cx,
13735 );
13736 if !action.stop_at_newlines {
13737 this.change_selections(Default::default(), window, cx, |s| {
13738 s.move_with(|_, sel| {
13739 if sel.is_empty() {
13740 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13741 }
13742 });
13743 });
13744 }
13745 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13746 let item = this.cut_common(false, window, cx);
13747 cx.write_to_clipboard(item);
13748 });
13749 }
13750
13751 pub fn move_to_start_of_paragraph(
13752 &mut self,
13753 _: &MoveToStartOfParagraph,
13754 window: &mut Window,
13755 cx: &mut Context<Self>,
13756 ) {
13757 if matches!(self.mode, EditorMode::SingleLine) {
13758 cx.propagate();
13759 return;
13760 }
13761 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13762 self.change_selections(Default::default(), window, cx, |s| {
13763 s.move_with(|map, selection| {
13764 selection.collapse_to(
13765 movement::start_of_paragraph(map, selection.head(), 1),
13766 SelectionGoal::None,
13767 )
13768 });
13769 })
13770 }
13771
13772 pub fn move_to_end_of_paragraph(
13773 &mut self,
13774 _: &MoveToEndOfParagraph,
13775 window: &mut Window,
13776 cx: &mut Context<Self>,
13777 ) {
13778 if matches!(self.mode, EditorMode::SingleLine) {
13779 cx.propagate();
13780 return;
13781 }
13782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13783 self.change_selections(Default::default(), window, cx, |s| {
13784 s.move_with(|map, selection| {
13785 selection.collapse_to(
13786 movement::end_of_paragraph(map, selection.head(), 1),
13787 SelectionGoal::None,
13788 )
13789 });
13790 })
13791 }
13792
13793 pub fn select_to_start_of_paragraph(
13794 &mut self,
13795 _: &SelectToStartOfParagraph,
13796 window: &mut Window,
13797 cx: &mut Context<Self>,
13798 ) {
13799 if matches!(self.mode, EditorMode::SingleLine) {
13800 cx.propagate();
13801 return;
13802 }
13803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13804 self.change_selections(Default::default(), window, cx, |s| {
13805 s.move_heads_with(|map, head, _| {
13806 (
13807 movement::start_of_paragraph(map, head, 1),
13808 SelectionGoal::None,
13809 )
13810 });
13811 })
13812 }
13813
13814 pub fn select_to_end_of_paragraph(
13815 &mut self,
13816 _: &SelectToEndOfParagraph,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) {
13820 if matches!(self.mode, EditorMode::SingleLine) {
13821 cx.propagate();
13822 return;
13823 }
13824 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13825 self.change_selections(Default::default(), window, cx, |s| {
13826 s.move_heads_with(|map, head, _| {
13827 (
13828 movement::end_of_paragraph(map, head, 1),
13829 SelectionGoal::None,
13830 )
13831 });
13832 })
13833 }
13834
13835 pub fn move_to_start_of_excerpt(
13836 &mut self,
13837 _: &MoveToStartOfExcerpt,
13838 window: &mut Window,
13839 cx: &mut Context<Self>,
13840 ) {
13841 if matches!(self.mode, EditorMode::SingleLine) {
13842 cx.propagate();
13843 return;
13844 }
13845 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13846 self.change_selections(Default::default(), window, cx, |s| {
13847 s.move_with(|map, selection| {
13848 selection.collapse_to(
13849 movement::start_of_excerpt(
13850 map,
13851 selection.head(),
13852 workspace::searchable::Direction::Prev,
13853 ),
13854 SelectionGoal::None,
13855 )
13856 });
13857 })
13858 }
13859
13860 pub fn move_to_start_of_next_excerpt(
13861 &mut self,
13862 _: &MoveToStartOfNextExcerpt,
13863 window: &mut Window,
13864 cx: &mut Context<Self>,
13865 ) {
13866 if matches!(self.mode, EditorMode::SingleLine) {
13867 cx.propagate();
13868 return;
13869 }
13870
13871 self.change_selections(Default::default(), window, cx, |s| {
13872 s.move_with(|map, selection| {
13873 selection.collapse_to(
13874 movement::start_of_excerpt(
13875 map,
13876 selection.head(),
13877 workspace::searchable::Direction::Next,
13878 ),
13879 SelectionGoal::None,
13880 )
13881 });
13882 })
13883 }
13884
13885 pub fn move_to_end_of_excerpt(
13886 &mut self,
13887 _: &MoveToEndOfExcerpt,
13888 window: &mut Window,
13889 cx: &mut Context<Self>,
13890 ) {
13891 if matches!(self.mode, EditorMode::SingleLine) {
13892 cx.propagate();
13893 return;
13894 }
13895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13896 self.change_selections(Default::default(), window, cx, |s| {
13897 s.move_with(|map, selection| {
13898 selection.collapse_to(
13899 movement::end_of_excerpt(
13900 map,
13901 selection.head(),
13902 workspace::searchable::Direction::Next,
13903 ),
13904 SelectionGoal::None,
13905 )
13906 });
13907 })
13908 }
13909
13910 pub fn move_to_end_of_previous_excerpt(
13911 &mut self,
13912 _: &MoveToEndOfPreviousExcerpt,
13913 window: &mut Window,
13914 cx: &mut Context<Self>,
13915 ) {
13916 if matches!(self.mode, EditorMode::SingleLine) {
13917 cx.propagate();
13918 return;
13919 }
13920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13921 self.change_selections(Default::default(), window, cx, |s| {
13922 s.move_with(|map, selection| {
13923 selection.collapse_to(
13924 movement::end_of_excerpt(
13925 map,
13926 selection.head(),
13927 workspace::searchable::Direction::Prev,
13928 ),
13929 SelectionGoal::None,
13930 )
13931 });
13932 })
13933 }
13934
13935 pub fn select_to_start_of_excerpt(
13936 &mut self,
13937 _: &SelectToStartOfExcerpt,
13938 window: &mut Window,
13939 cx: &mut Context<Self>,
13940 ) {
13941 if matches!(self.mode, EditorMode::SingleLine) {
13942 cx.propagate();
13943 return;
13944 }
13945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13946 self.change_selections(Default::default(), window, cx, |s| {
13947 s.move_heads_with(|map, head, _| {
13948 (
13949 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13950 SelectionGoal::None,
13951 )
13952 });
13953 })
13954 }
13955
13956 pub fn select_to_start_of_next_excerpt(
13957 &mut self,
13958 _: &SelectToStartOfNextExcerpt,
13959 window: &mut Window,
13960 cx: &mut Context<Self>,
13961 ) {
13962 if matches!(self.mode, EditorMode::SingleLine) {
13963 cx.propagate();
13964 return;
13965 }
13966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13967 self.change_selections(Default::default(), window, cx, |s| {
13968 s.move_heads_with(|map, head, _| {
13969 (
13970 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13971 SelectionGoal::None,
13972 )
13973 });
13974 })
13975 }
13976
13977 pub fn select_to_end_of_excerpt(
13978 &mut self,
13979 _: &SelectToEndOfExcerpt,
13980 window: &mut Window,
13981 cx: &mut Context<Self>,
13982 ) {
13983 if matches!(self.mode, EditorMode::SingleLine) {
13984 cx.propagate();
13985 return;
13986 }
13987 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13988 self.change_selections(Default::default(), window, cx, |s| {
13989 s.move_heads_with(|map, head, _| {
13990 (
13991 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13992 SelectionGoal::None,
13993 )
13994 });
13995 })
13996 }
13997
13998 pub fn select_to_end_of_previous_excerpt(
13999 &mut self,
14000 _: &SelectToEndOfPreviousExcerpt,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) {
14004 if matches!(self.mode, EditorMode::SingleLine) {
14005 cx.propagate();
14006 return;
14007 }
14008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14009 self.change_selections(Default::default(), window, cx, |s| {
14010 s.move_heads_with(|map, head, _| {
14011 (
14012 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14013 SelectionGoal::None,
14014 )
14015 });
14016 })
14017 }
14018
14019 pub fn move_to_beginning(
14020 &mut self,
14021 _: &MoveToBeginning,
14022 window: &mut Window,
14023 cx: &mut Context<Self>,
14024 ) {
14025 if matches!(self.mode, EditorMode::SingleLine) {
14026 cx.propagate();
14027 return;
14028 }
14029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14030 self.change_selections(Default::default(), window, cx, |s| {
14031 s.select_ranges(vec![0..0]);
14032 });
14033 }
14034
14035 pub fn select_to_beginning(
14036 &mut self,
14037 _: &SelectToBeginning,
14038 window: &mut Window,
14039 cx: &mut Context<Self>,
14040 ) {
14041 let mut selection = self.selections.last::<Point>(cx);
14042 selection.set_head(Point::zero(), SelectionGoal::None);
14043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14044 self.change_selections(Default::default(), window, cx, |s| {
14045 s.select(vec![selection]);
14046 });
14047 }
14048
14049 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14050 if matches!(self.mode, EditorMode::SingleLine) {
14051 cx.propagate();
14052 return;
14053 }
14054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14055 let cursor = self.buffer.read(cx).read(cx).len();
14056 self.change_selections(Default::default(), window, cx, |s| {
14057 s.select_ranges(vec![cursor..cursor])
14058 });
14059 }
14060
14061 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14062 self.nav_history = nav_history;
14063 }
14064
14065 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14066 self.nav_history.as_ref()
14067 }
14068
14069 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14070 self.push_to_nav_history(
14071 self.selections.newest_anchor().head(),
14072 None,
14073 false,
14074 true,
14075 cx,
14076 );
14077 }
14078
14079 fn push_to_nav_history(
14080 &mut self,
14081 cursor_anchor: Anchor,
14082 new_position: Option<Point>,
14083 is_deactivate: bool,
14084 always: bool,
14085 cx: &mut Context<Self>,
14086 ) {
14087 if let Some(nav_history) = self.nav_history.as_mut() {
14088 let buffer = self.buffer.read(cx).read(cx);
14089 let cursor_position = cursor_anchor.to_point(&buffer);
14090 let scroll_state = self.scroll_manager.anchor();
14091 let scroll_top_row = scroll_state.top_row(&buffer);
14092 drop(buffer);
14093
14094 if let Some(new_position) = new_position {
14095 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14096 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14097 return;
14098 }
14099 }
14100
14101 nav_history.push(
14102 Some(NavigationData {
14103 cursor_anchor,
14104 cursor_position,
14105 scroll_anchor: scroll_state,
14106 scroll_top_row,
14107 }),
14108 cx,
14109 );
14110 cx.emit(EditorEvent::PushedToNavHistory {
14111 anchor: cursor_anchor,
14112 is_deactivate,
14113 })
14114 }
14115 }
14116
14117 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14119 let buffer = self.buffer.read(cx).snapshot(cx);
14120 let mut selection = self.selections.first::<usize>(cx);
14121 selection.set_head(buffer.len(), SelectionGoal::None);
14122 self.change_selections(Default::default(), window, cx, |s| {
14123 s.select(vec![selection]);
14124 });
14125 }
14126
14127 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14129 let end = self.buffer.read(cx).read(cx).len();
14130 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14131 s.select_ranges(vec![0..end]);
14132 });
14133 }
14134
14135 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14137 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14138 let mut selections = self.selections.all::<Point>(cx);
14139 let max_point = display_map.buffer_snapshot().max_point();
14140 for selection in &mut selections {
14141 let rows = selection.spanned_rows(true, &display_map);
14142 selection.start = Point::new(rows.start.0, 0);
14143 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14144 selection.reversed = false;
14145 }
14146 self.change_selections(Default::default(), window, cx, |s| {
14147 s.select(selections);
14148 });
14149 }
14150
14151 pub fn split_selection_into_lines(
14152 &mut self,
14153 action: &SplitSelectionIntoLines,
14154 window: &mut Window,
14155 cx: &mut Context<Self>,
14156 ) {
14157 let selections = self
14158 .selections
14159 .all::<Point>(cx)
14160 .into_iter()
14161 .map(|selection| selection.start..selection.end)
14162 .collect::<Vec<_>>();
14163 self.unfold_ranges(&selections, true, true, cx);
14164
14165 let mut new_selection_ranges = Vec::new();
14166 {
14167 let buffer = self.buffer.read(cx).read(cx);
14168 for selection in selections {
14169 for row in selection.start.row..selection.end.row {
14170 let line_start = Point::new(row, 0);
14171 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14172
14173 if action.keep_selections {
14174 // Keep the selection range for each line
14175 let selection_start = if row == selection.start.row {
14176 selection.start
14177 } else {
14178 line_start
14179 };
14180 new_selection_ranges.push(selection_start..line_end);
14181 } else {
14182 // Collapse to cursor at end of line
14183 new_selection_ranges.push(line_end..line_end);
14184 }
14185 }
14186
14187 let is_multiline_selection = selection.start.row != selection.end.row;
14188 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14189 // so this action feels more ergonomic when paired with other selection operations
14190 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14191 if !should_skip_last {
14192 if action.keep_selections {
14193 if is_multiline_selection {
14194 let line_start = Point::new(selection.end.row, 0);
14195 new_selection_ranges.push(line_start..selection.end);
14196 } else {
14197 new_selection_ranges.push(selection.start..selection.end);
14198 }
14199 } else {
14200 new_selection_ranges.push(selection.end..selection.end);
14201 }
14202 }
14203 }
14204 }
14205 self.change_selections(Default::default(), window, cx, |s| {
14206 s.select_ranges(new_selection_ranges);
14207 });
14208 }
14209
14210 pub fn add_selection_above(
14211 &mut self,
14212 _: &AddSelectionAbove,
14213 window: &mut Window,
14214 cx: &mut Context<Self>,
14215 ) {
14216 self.add_selection(true, window, cx);
14217 }
14218
14219 pub fn add_selection_below(
14220 &mut self,
14221 _: &AddSelectionBelow,
14222 window: &mut Window,
14223 cx: &mut Context<Self>,
14224 ) {
14225 self.add_selection(false, window, cx);
14226 }
14227
14228 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14230
14231 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14232 let all_selections = self.selections.all::<Point>(cx);
14233 let text_layout_details = self.text_layout_details(window);
14234
14235 let (mut columnar_selections, new_selections_to_columnarize) = {
14236 if let Some(state) = self.add_selections_state.as_ref() {
14237 let columnar_selection_ids: HashSet<_> = state
14238 .groups
14239 .iter()
14240 .flat_map(|group| group.stack.iter())
14241 .copied()
14242 .collect();
14243
14244 all_selections
14245 .into_iter()
14246 .partition(|s| columnar_selection_ids.contains(&s.id))
14247 } else {
14248 (Vec::new(), all_selections)
14249 }
14250 };
14251
14252 let mut state = self
14253 .add_selections_state
14254 .take()
14255 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14256
14257 for selection in new_selections_to_columnarize {
14258 let range = selection.display_range(&display_map).sorted();
14259 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14260 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14261 let positions = start_x.min(end_x)..start_x.max(end_x);
14262 let mut stack = Vec::new();
14263 for row in range.start.row().0..=range.end.row().0 {
14264 if let Some(selection) = self.selections.build_columnar_selection(
14265 &display_map,
14266 DisplayRow(row),
14267 &positions,
14268 selection.reversed,
14269 &text_layout_details,
14270 ) {
14271 stack.push(selection.id);
14272 columnar_selections.push(selection);
14273 }
14274 }
14275 if !stack.is_empty() {
14276 if above {
14277 stack.reverse();
14278 }
14279 state.groups.push(AddSelectionsGroup { above, stack });
14280 }
14281 }
14282
14283 let mut final_selections = Vec::new();
14284 let end_row = if above {
14285 DisplayRow(0)
14286 } else {
14287 display_map.max_point().row()
14288 };
14289
14290 let mut last_added_item_per_group = HashMap::default();
14291 for group in state.groups.iter_mut() {
14292 if let Some(last_id) = group.stack.last() {
14293 last_added_item_per_group.insert(*last_id, group);
14294 }
14295 }
14296
14297 for selection in columnar_selections {
14298 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14299 if above == group.above {
14300 let range = selection.display_range(&display_map).sorted();
14301 debug_assert_eq!(range.start.row(), range.end.row());
14302 let mut row = range.start.row();
14303 let positions =
14304 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14305 Pixels::from(start)..Pixels::from(end)
14306 } else {
14307 let start_x =
14308 display_map.x_for_display_point(range.start, &text_layout_details);
14309 let end_x =
14310 display_map.x_for_display_point(range.end, &text_layout_details);
14311 start_x.min(end_x)..start_x.max(end_x)
14312 };
14313
14314 let mut maybe_new_selection = None;
14315 while row != end_row {
14316 if above {
14317 row.0 -= 1;
14318 } else {
14319 row.0 += 1;
14320 }
14321 if let Some(new_selection) = self.selections.build_columnar_selection(
14322 &display_map,
14323 row,
14324 &positions,
14325 selection.reversed,
14326 &text_layout_details,
14327 ) {
14328 maybe_new_selection = Some(new_selection);
14329 break;
14330 }
14331 }
14332
14333 if let Some(new_selection) = maybe_new_selection {
14334 group.stack.push(new_selection.id);
14335 if above {
14336 final_selections.push(new_selection);
14337 final_selections.push(selection);
14338 } else {
14339 final_selections.push(selection);
14340 final_selections.push(new_selection);
14341 }
14342 } else {
14343 final_selections.push(selection);
14344 }
14345 } else {
14346 group.stack.pop();
14347 }
14348 } else {
14349 final_selections.push(selection);
14350 }
14351 }
14352
14353 self.change_selections(Default::default(), window, cx, |s| {
14354 s.select(final_selections);
14355 });
14356
14357 let final_selection_ids: HashSet<_> = self
14358 .selections
14359 .all::<Point>(cx)
14360 .iter()
14361 .map(|s| s.id)
14362 .collect();
14363 state.groups.retain_mut(|group| {
14364 // selections might get merged above so we remove invalid items from stacks
14365 group.stack.retain(|id| final_selection_ids.contains(id));
14366
14367 // single selection in stack can be treated as initial state
14368 group.stack.len() > 1
14369 });
14370
14371 if !state.groups.is_empty() {
14372 self.add_selections_state = Some(state);
14373 }
14374 }
14375
14376 fn select_match_ranges(
14377 &mut self,
14378 range: Range<usize>,
14379 reversed: bool,
14380 replace_newest: bool,
14381 auto_scroll: Option<Autoscroll>,
14382 window: &mut Window,
14383 cx: &mut Context<Editor>,
14384 ) {
14385 self.unfold_ranges(
14386 std::slice::from_ref(&range),
14387 false,
14388 auto_scroll.is_some(),
14389 cx,
14390 );
14391 let effects = if let Some(scroll) = auto_scroll {
14392 SelectionEffects::scroll(scroll)
14393 } else {
14394 SelectionEffects::no_scroll()
14395 };
14396 self.change_selections(effects, window, cx, |s| {
14397 if replace_newest {
14398 s.delete(s.newest_anchor().id);
14399 }
14400 if reversed {
14401 s.insert_range(range.end..range.start);
14402 } else {
14403 s.insert_range(range);
14404 }
14405 });
14406 }
14407
14408 pub fn select_next_match_internal(
14409 &mut self,
14410 display_map: &DisplaySnapshot,
14411 replace_newest: bool,
14412 autoscroll: Option<Autoscroll>,
14413 window: &mut Window,
14414 cx: &mut Context<Self>,
14415 ) -> Result<()> {
14416 let buffer = display_map.buffer_snapshot();
14417 let mut selections = self.selections.all::<usize>(cx);
14418 if let Some(mut select_next_state) = self.select_next_state.take() {
14419 let query = &select_next_state.query;
14420 if !select_next_state.done {
14421 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14422 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14423 let mut next_selected_range = None;
14424
14425 let bytes_after_last_selection =
14426 buffer.bytes_in_range(last_selection.end..buffer.len());
14427 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14428 let query_matches = query
14429 .stream_find_iter(bytes_after_last_selection)
14430 .map(|result| (last_selection.end, result))
14431 .chain(
14432 query
14433 .stream_find_iter(bytes_before_first_selection)
14434 .map(|result| (0, result)),
14435 );
14436
14437 for (start_offset, query_match) in query_matches {
14438 let query_match = query_match.unwrap(); // can only fail due to I/O
14439 let offset_range =
14440 start_offset + query_match.start()..start_offset + query_match.end();
14441
14442 if !select_next_state.wordwise
14443 || (!buffer.is_inside_word(offset_range.start, None)
14444 && !buffer.is_inside_word(offset_range.end, None))
14445 {
14446 // TODO: This is n^2, because we might check all the selections
14447 if !selections
14448 .iter()
14449 .any(|selection| selection.range().overlaps(&offset_range))
14450 {
14451 next_selected_range = Some(offset_range);
14452 break;
14453 }
14454 }
14455 }
14456
14457 if let Some(next_selected_range) = next_selected_range {
14458 self.select_match_ranges(
14459 next_selected_range,
14460 last_selection.reversed,
14461 replace_newest,
14462 autoscroll,
14463 window,
14464 cx,
14465 );
14466 } else {
14467 select_next_state.done = true;
14468 }
14469 }
14470
14471 self.select_next_state = Some(select_next_state);
14472 } else {
14473 let mut only_carets = true;
14474 let mut same_text_selected = true;
14475 let mut selected_text = None;
14476
14477 let mut selections_iter = selections.iter().peekable();
14478 while let Some(selection) = selections_iter.next() {
14479 if selection.start != selection.end {
14480 only_carets = false;
14481 }
14482
14483 if same_text_selected {
14484 if selected_text.is_none() {
14485 selected_text =
14486 Some(buffer.text_for_range(selection.range()).collect::<String>());
14487 }
14488
14489 if let Some(next_selection) = selections_iter.peek() {
14490 if next_selection.range().len() == selection.range().len() {
14491 let next_selected_text = buffer
14492 .text_for_range(next_selection.range())
14493 .collect::<String>();
14494 if Some(next_selected_text) != selected_text {
14495 same_text_selected = false;
14496 selected_text = None;
14497 }
14498 } else {
14499 same_text_selected = false;
14500 selected_text = None;
14501 }
14502 }
14503 }
14504 }
14505
14506 if only_carets {
14507 for selection in &mut selections {
14508 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14509 selection.start = word_range.start;
14510 selection.end = word_range.end;
14511 selection.goal = SelectionGoal::None;
14512 selection.reversed = false;
14513 self.select_match_ranges(
14514 selection.start..selection.end,
14515 selection.reversed,
14516 replace_newest,
14517 autoscroll,
14518 window,
14519 cx,
14520 );
14521 }
14522
14523 if selections.len() == 1 {
14524 let selection = selections
14525 .last()
14526 .expect("ensured that there's only one selection");
14527 let query = buffer
14528 .text_for_range(selection.start..selection.end)
14529 .collect::<String>();
14530 let is_empty = query.is_empty();
14531 let select_state = SelectNextState {
14532 query: AhoCorasick::new(&[query])?,
14533 wordwise: true,
14534 done: is_empty,
14535 };
14536 self.select_next_state = Some(select_state);
14537 } else {
14538 self.select_next_state = None;
14539 }
14540 } else if let Some(selected_text) = selected_text {
14541 self.select_next_state = Some(SelectNextState {
14542 query: AhoCorasick::new(&[selected_text])?,
14543 wordwise: false,
14544 done: false,
14545 });
14546 self.select_next_match_internal(
14547 display_map,
14548 replace_newest,
14549 autoscroll,
14550 window,
14551 cx,
14552 )?;
14553 }
14554 }
14555 Ok(())
14556 }
14557
14558 pub fn select_all_matches(
14559 &mut self,
14560 _action: &SelectAllMatches,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) -> Result<()> {
14564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14565
14566 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14567
14568 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14569 let Some(select_next_state) = self.select_next_state.as_mut() else {
14570 return Ok(());
14571 };
14572 if select_next_state.done {
14573 return Ok(());
14574 }
14575
14576 let mut new_selections = Vec::new();
14577
14578 let reversed = self.selections.oldest::<usize>(cx).reversed;
14579 let buffer = display_map.buffer_snapshot();
14580 let query_matches = select_next_state
14581 .query
14582 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14583
14584 for query_match in query_matches.into_iter() {
14585 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14586 let offset_range = if reversed {
14587 query_match.end()..query_match.start()
14588 } else {
14589 query_match.start()..query_match.end()
14590 };
14591
14592 if !select_next_state.wordwise
14593 || (!buffer.is_inside_word(offset_range.start, None)
14594 && !buffer.is_inside_word(offset_range.end, None))
14595 {
14596 new_selections.push(offset_range.start..offset_range.end);
14597 }
14598 }
14599
14600 select_next_state.done = true;
14601
14602 if new_selections.is_empty() {
14603 log::error!("bug: new_selections is empty in select_all_matches");
14604 return Ok(());
14605 }
14606
14607 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14608 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14609 selections.select_ranges(new_selections)
14610 });
14611
14612 Ok(())
14613 }
14614
14615 pub fn select_next(
14616 &mut self,
14617 action: &SelectNext,
14618 window: &mut Window,
14619 cx: &mut Context<Self>,
14620 ) -> Result<()> {
14621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14622 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14623 self.select_next_match_internal(
14624 &display_map,
14625 action.replace_newest,
14626 Some(Autoscroll::newest()),
14627 window,
14628 cx,
14629 )?;
14630 Ok(())
14631 }
14632
14633 pub fn select_previous(
14634 &mut self,
14635 action: &SelectPrevious,
14636 window: &mut Window,
14637 cx: &mut Context<Self>,
14638 ) -> Result<()> {
14639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14641 let buffer = display_map.buffer_snapshot();
14642 let mut selections = self.selections.all::<usize>(cx);
14643 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14644 let query = &select_prev_state.query;
14645 if !select_prev_state.done {
14646 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14647 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14648 let mut next_selected_range = None;
14649 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14650 let bytes_before_last_selection =
14651 buffer.reversed_bytes_in_range(0..last_selection.start);
14652 let bytes_after_first_selection =
14653 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14654 let query_matches = query
14655 .stream_find_iter(bytes_before_last_selection)
14656 .map(|result| (last_selection.start, result))
14657 .chain(
14658 query
14659 .stream_find_iter(bytes_after_first_selection)
14660 .map(|result| (buffer.len(), result)),
14661 );
14662 for (end_offset, query_match) in query_matches {
14663 let query_match = query_match.unwrap(); // can only fail due to I/O
14664 let offset_range =
14665 end_offset - query_match.end()..end_offset - query_match.start();
14666
14667 if !select_prev_state.wordwise
14668 || (!buffer.is_inside_word(offset_range.start, None)
14669 && !buffer.is_inside_word(offset_range.end, None))
14670 {
14671 next_selected_range = Some(offset_range);
14672 break;
14673 }
14674 }
14675
14676 if let Some(next_selected_range) = next_selected_range {
14677 self.select_match_ranges(
14678 next_selected_range,
14679 last_selection.reversed,
14680 action.replace_newest,
14681 Some(Autoscroll::newest()),
14682 window,
14683 cx,
14684 );
14685 } else {
14686 select_prev_state.done = true;
14687 }
14688 }
14689
14690 self.select_prev_state = Some(select_prev_state);
14691 } else {
14692 let mut only_carets = true;
14693 let mut same_text_selected = true;
14694 let mut selected_text = None;
14695
14696 let mut selections_iter = selections.iter().peekable();
14697 while let Some(selection) = selections_iter.next() {
14698 if selection.start != selection.end {
14699 only_carets = false;
14700 }
14701
14702 if same_text_selected {
14703 if selected_text.is_none() {
14704 selected_text =
14705 Some(buffer.text_for_range(selection.range()).collect::<String>());
14706 }
14707
14708 if let Some(next_selection) = selections_iter.peek() {
14709 if next_selection.range().len() == selection.range().len() {
14710 let next_selected_text = buffer
14711 .text_for_range(next_selection.range())
14712 .collect::<String>();
14713 if Some(next_selected_text) != selected_text {
14714 same_text_selected = false;
14715 selected_text = None;
14716 }
14717 } else {
14718 same_text_selected = false;
14719 selected_text = None;
14720 }
14721 }
14722 }
14723 }
14724
14725 if only_carets {
14726 for selection in &mut selections {
14727 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14728 selection.start = word_range.start;
14729 selection.end = word_range.end;
14730 selection.goal = SelectionGoal::None;
14731 selection.reversed = false;
14732 self.select_match_ranges(
14733 selection.start..selection.end,
14734 selection.reversed,
14735 action.replace_newest,
14736 Some(Autoscroll::newest()),
14737 window,
14738 cx,
14739 );
14740 }
14741 if selections.len() == 1 {
14742 let selection = selections
14743 .last()
14744 .expect("ensured that there's only one selection");
14745 let query = buffer
14746 .text_for_range(selection.start..selection.end)
14747 .collect::<String>();
14748 let is_empty = query.is_empty();
14749 let select_state = SelectNextState {
14750 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14751 wordwise: true,
14752 done: is_empty,
14753 };
14754 self.select_prev_state = Some(select_state);
14755 } else {
14756 self.select_prev_state = None;
14757 }
14758 } else if let Some(selected_text) = selected_text {
14759 self.select_prev_state = Some(SelectNextState {
14760 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14761 wordwise: false,
14762 done: false,
14763 });
14764 self.select_previous(action, window, cx)?;
14765 }
14766 }
14767 Ok(())
14768 }
14769
14770 pub fn find_next_match(
14771 &mut self,
14772 _: &FindNextMatch,
14773 window: &mut Window,
14774 cx: &mut Context<Self>,
14775 ) -> Result<()> {
14776 let selections = self.selections.disjoint_anchors_arc();
14777 match selections.first() {
14778 Some(first) if selections.len() >= 2 => {
14779 self.change_selections(Default::default(), window, cx, |s| {
14780 s.select_ranges([first.range()]);
14781 });
14782 }
14783 _ => self.select_next(
14784 &SelectNext {
14785 replace_newest: true,
14786 },
14787 window,
14788 cx,
14789 )?,
14790 }
14791 Ok(())
14792 }
14793
14794 pub fn find_previous_match(
14795 &mut self,
14796 _: &FindPreviousMatch,
14797 window: &mut Window,
14798 cx: &mut Context<Self>,
14799 ) -> Result<()> {
14800 let selections = self.selections.disjoint_anchors_arc();
14801 match selections.last() {
14802 Some(last) if selections.len() >= 2 => {
14803 self.change_selections(Default::default(), window, cx, |s| {
14804 s.select_ranges([last.range()]);
14805 });
14806 }
14807 _ => self.select_previous(
14808 &SelectPrevious {
14809 replace_newest: true,
14810 },
14811 window,
14812 cx,
14813 )?,
14814 }
14815 Ok(())
14816 }
14817
14818 pub fn toggle_comments(
14819 &mut self,
14820 action: &ToggleComments,
14821 window: &mut Window,
14822 cx: &mut Context<Self>,
14823 ) {
14824 if self.read_only(cx) {
14825 return;
14826 }
14827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14828 let text_layout_details = &self.text_layout_details(window);
14829 self.transact(window, cx, |this, window, cx| {
14830 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14831 let mut edits = Vec::new();
14832 let mut selection_edit_ranges = Vec::new();
14833 let mut last_toggled_row = None;
14834 let snapshot = this.buffer.read(cx).read(cx);
14835 let empty_str: Arc<str> = Arc::default();
14836 let mut suffixes_inserted = Vec::new();
14837 let ignore_indent = action.ignore_indent;
14838
14839 fn comment_prefix_range(
14840 snapshot: &MultiBufferSnapshot,
14841 row: MultiBufferRow,
14842 comment_prefix: &str,
14843 comment_prefix_whitespace: &str,
14844 ignore_indent: bool,
14845 ) -> Range<Point> {
14846 let indent_size = if ignore_indent {
14847 0
14848 } else {
14849 snapshot.indent_size_for_line(row).len
14850 };
14851
14852 let start = Point::new(row.0, indent_size);
14853
14854 let mut line_bytes = snapshot
14855 .bytes_in_range(start..snapshot.max_point())
14856 .flatten()
14857 .copied();
14858
14859 // If this line currently begins with the line comment prefix, then record
14860 // the range containing the prefix.
14861 if line_bytes
14862 .by_ref()
14863 .take(comment_prefix.len())
14864 .eq(comment_prefix.bytes())
14865 {
14866 // Include any whitespace that matches the comment prefix.
14867 let matching_whitespace_len = line_bytes
14868 .zip(comment_prefix_whitespace.bytes())
14869 .take_while(|(a, b)| a == b)
14870 .count() as u32;
14871 let end = Point::new(
14872 start.row,
14873 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14874 );
14875 start..end
14876 } else {
14877 start..start
14878 }
14879 }
14880
14881 fn comment_suffix_range(
14882 snapshot: &MultiBufferSnapshot,
14883 row: MultiBufferRow,
14884 comment_suffix: &str,
14885 comment_suffix_has_leading_space: bool,
14886 ) -> Range<Point> {
14887 let end = Point::new(row.0, snapshot.line_len(row));
14888 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14889
14890 let mut line_end_bytes = snapshot
14891 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14892 .flatten()
14893 .copied();
14894
14895 let leading_space_len = if suffix_start_column > 0
14896 && line_end_bytes.next() == Some(b' ')
14897 && comment_suffix_has_leading_space
14898 {
14899 1
14900 } else {
14901 0
14902 };
14903
14904 // If this line currently begins with the line comment prefix, then record
14905 // the range containing the prefix.
14906 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14907 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14908 start..end
14909 } else {
14910 end..end
14911 }
14912 }
14913
14914 // TODO: Handle selections that cross excerpts
14915 for selection in &mut selections {
14916 let start_column = snapshot
14917 .indent_size_for_line(MultiBufferRow(selection.start.row))
14918 .len;
14919 let language = if let Some(language) =
14920 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14921 {
14922 language
14923 } else {
14924 continue;
14925 };
14926
14927 selection_edit_ranges.clear();
14928
14929 // If multiple selections contain a given row, avoid processing that
14930 // row more than once.
14931 let mut start_row = MultiBufferRow(selection.start.row);
14932 if last_toggled_row == Some(start_row) {
14933 start_row = start_row.next_row();
14934 }
14935 let end_row =
14936 if selection.end.row > selection.start.row && selection.end.column == 0 {
14937 MultiBufferRow(selection.end.row - 1)
14938 } else {
14939 MultiBufferRow(selection.end.row)
14940 };
14941 last_toggled_row = Some(end_row);
14942
14943 if start_row > end_row {
14944 continue;
14945 }
14946
14947 // If the language has line comments, toggle those.
14948 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14949
14950 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14951 if ignore_indent {
14952 full_comment_prefixes = full_comment_prefixes
14953 .into_iter()
14954 .map(|s| Arc::from(s.trim_end()))
14955 .collect();
14956 }
14957
14958 if !full_comment_prefixes.is_empty() {
14959 let first_prefix = full_comment_prefixes
14960 .first()
14961 .expect("prefixes is non-empty");
14962 let prefix_trimmed_lengths = full_comment_prefixes
14963 .iter()
14964 .map(|p| p.trim_end_matches(' ').len())
14965 .collect::<SmallVec<[usize; 4]>>();
14966
14967 let mut all_selection_lines_are_comments = true;
14968
14969 for row in start_row.0..=end_row.0 {
14970 let row = MultiBufferRow(row);
14971 if start_row < end_row && snapshot.is_line_blank(row) {
14972 continue;
14973 }
14974
14975 let prefix_range = full_comment_prefixes
14976 .iter()
14977 .zip(prefix_trimmed_lengths.iter().copied())
14978 .map(|(prefix, trimmed_prefix_len)| {
14979 comment_prefix_range(
14980 snapshot.deref(),
14981 row,
14982 &prefix[..trimmed_prefix_len],
14983 &prefix[trimmed_prefix_len..],
14984 ignore_indent,
14985 )
14986 })
14987 .max_by_key(|range| range.end.column - range.start.column)
14988 .expect("prefixes is non-empty");
14989
14990 if prefix_range.is_empty() {
14991 all_selection_lines_are_comments = false;
14992 }
14993
14994 selection_edit_ranges.push(prefix_range);
14995 }
14996
14997 if all_selection_lines_are_comments {
14998 edits.extend(
14999 selection_edit_ranges
15000 .iter()
15001 .cloned()
15002 .map(|range| (range, empty_str.clone())),
15003 );
15004 } else {
15005 let min_column = selection_edit_ranges
15006 .iter()
15007 .map(|range| range.start.column)
15008 .min()
15009 .unwrap_or(0);
15010 edits.extend(selection_edit_ranges.iter().map(|range| {
15011 let position = Point::new(range.start.row, min_column);
15012 (position..position, first_prefix.clone())
15013 }));
15014 }
15015 } else if let Some(BlockCommentConfig {
15016 start: full_comment_prefix,
15017 end: comment_suffix,
15018 ..
15019 }) = language.block_comment()
15020 {
15021 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15022 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15023 let prefix_range = comment_prefix_range(
15024 snapshot.deref(),
15025 start_row,
15026 comment_prefix,
15027 comment_prefix_whitespace,
15028 ignore_indent,
15029 );
15030 let suffix_range = comment_suffix_range(
15031 snapshot.deref(),
15032 end_row,
15033 comment_suffix.trim_start_matches(' '),
15034 comment_suffix.starts_with(' '),
15035 );
15036
15037 if prefix_range.is_empty() || suffix_range.is_empty() {
15038 edits.push((
15039 prefix_range.start..prefix_range.start,
15040 full_comment_prefix.clone(),
15041 ));
15042 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15043 suffixes_inserted.push((end_row, comment_suffix.len()));
15044 } else {
15045 edits.push((prefix_range, empty_str.clone()));
15046 edits.push((suffix_range, empty_str.clone()));
15047 }
15048 } else {
15049 continue;
15050 }
15051 }
15052
15053 drop(snapshot);
15054 this.buffer.update(cx, |buffer, cx| {
15055 buffer.edit(edits, None, cx);
15056 });
15057
15058 // Adjust selections so that they end before any comment suffixes that
15059 // were inserted.
15060 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15061 let mut selections = this.selections.all::<Point>(cx);
15062 let snapshot = this.buffer.read(cx).read(cx);
15063 for selection in &mut selections {
15064 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15065 match row.cmp(&MultiBufferRow(selection.end.row)) {
15066 Ordering::Less => {
15067 suffixes_inserted.next();
15068 continue;
15069 }
15070 Ordering::Greater => break,
15071 Ordering::Equal => {
15072 if selection.end.column == snapshot.line_len(row) {
15073 if selection.is_empty() {
15074 selection.start.column -= suffix_len as u32;
15075 }
15076 selection.end.column -= suffix_len as u32;
15077 }
15078 break;
15079 }
15080 }
15081 }
15082 }
15083
15084 drop(snapshot);
15085 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15086
15087 let selections = this.selections.all::<Point>(cx);
15088 let selections_on_single_row = selections.windows(2).all(|selections| {
15089 selections[0].start.row == selections[1].start.row
15090 && selections[0].end.row == selections[1].end.row
15091 && selections[0].start.row == selections[0].end.row
15092 });
15093 let selections_selecting = selections
15094 .iter()
15095 .any(|selection| selection.start != selection.end);
15096 let advance_downwards = action.advance_downwards
15097 && selections_on_single_row
15098 && !selections_selecting
15099 && !matches!(this.mode, EditorMode::SingleLine);
15100
15101 if advance_downwards {
15102 let snapshot = this.buffer.read(cx).snapshot(cx);
15103
15104 this.change_selections(Default::default(), window, cx, |s| {
15105 s.move_cursors_with(|display_snapshot, display_point, _| {
15106 let mut point = display_point.to_point(display_snapshot);
15107 point.row += 1;
15108 point = snapshot.clip_point(point, Bias::Left);
15109 let display_point = point.to_display_point(display_snapshot);
15110 let goal = SelectionGoal::HorizontalPosition(
15111 display_snapshot
15112 .x_for_display_point(display_point, text_layout_details)
15113 .into(),
15114 );
15115 (display_point, goal)
15116 })
15117 });
15118 }
15119 });
15120 }
15121
15122 pub fn select_enclosing_symbol(
15123 &mut self,
15124 _: &SelectEnclosingSymbol,
15125 window: &mut Window,
15126 cx: &mut Context<Self>,
15127 ) {
15128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15129
15130 let buffer = self.buffer.read(cx).snapshot(cx);
15131 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15132
15133 fn update_selection(
15134 selection: &Selection<usize>,
15135 buffer_snap: &MultiBufferSnapshot,
15136 ) -> Option<Selection<usize>> {
15137 let cursor = selection.head();
15138 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15139 for symbol in symbols.iter().rev() {
15140 let start = symbol.range.start.to_offset(buffer_snap);
15141 let end = symbol.range.end.to_offset(buffer_snap);
15142 let new_range = start..end;
15143 if start < selection.start || end > selection.end {
15144 return Some(Selection {
15145 id: selection.id,
15146 start: new_range.start,
15147 end: new_range.end,
15148 goal: SelectionGoal::None,
15149 reversed: selection.reversed,
15150 });
15151 }
15152 }
15153 None
15154 }
15155
15156 let mut selected_larger_symbol = false;
15157 let new_selections = old_selections
15158 .iter()
15159 .map(|selection| match update_selection(selection, &buffer) {
15160 Some(new_selection) => {
15161 if new_selection.range() != selection.range() {
15162 selected_larger_symbol = true;
15163 }
15164 new_selection
15165 }
15166 None => selection.clone(),
15167 })
15168 .collect::<Vec<_>>();
15169
15170 if selected_larger_symbol {
15171 self.change_selections(Default::default(), window, cx, |s| {
15172 s.select(new_selections);
15173 });
15174 }
15175 }
15176
15177 pub fn select_larger_syntax_node(
15178 &mut self,
15179 _: &SelectLargerSyntaxNode,
15180 window: &mut Window,
15181 cx: &mut Context<Self>,
15182 ) {
15183 let Some(visible_row_count) = self.visible_row_count() else {
15184 return;
15185 };
15186 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15187 if old_selections.is_empty() {
15188 return;
15189 }
15190
15191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15192
15193 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15194 let buffer = self.buffer.read(cx).snapshot(cx);
15195
15196 let mut selected_larger_node = false;
15197 let mut new_selections = old_selections
15198 .iter()
15199 .map(|selection| {
15200 let old_range = selection.start..selection.end;
15201
15202 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15203 // manually select word at selection
15204 if ["string_content", "inline"].contains(&node.kind()) {
15205 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15206 // ignore if word is already selected
15207 if !word_range.is_empty() && old_range != word_range {
15208 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15209 // only select word if start and end point belongs to same word
15210 if word_range == last_word_range {
15211 selected_larger_node = true;
15212 return Selection {
15213 id: selection.id,
15214 start: word_range.start,
15215 end: word_range.end,
15216 goal: SelectionGoal::None,
15217 reversed: selection.reversed,
15218 };
15219 }
15220 }
15221 }
15222 }
15223
15224 let mut new_range = old_range.clone();
15225 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15226 new_range = range;
15227 if !node.is_named() {
15228 continue;
15229 }
15230 if !display_map.intersects_fold(new_range.start)
15231 && !display_map.intersects_fold(new_range.end)
15232 {
15233 break;
15234 }
15235 }
15236
15237 selected_larger_node |= new_range != old_range;
15238 Selection {
15239 id: selection.id,
15240 start: new_range.start,
15241 end: new_range.end,
15242 goal: SelectionGoal::None,
15243 reversed: selection.reversed,
15244 }
15245 })
15246 .collect::<Vec<_>>();
15247
15248 if !selected_larger_node {
15249 return; // don't put this call in the history
15250 }
15251
15252 // scroll based on transformation done to the last selection created by the user
15253 let (last_old, last_new) = old_selections
15254 .last()
15255 .zip(new_selections.last().cloned())
15256 .expect("old_selections isn't empty");
15257
15258 // revert selection
15259 let is_selection_reversed = {
15260 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15261 new_selections.last_mut().expect("checked above").reversed =
15262 should_newest_selection_be_reversed;
15263 should_newest_selection_be_reversed
15264 };
15265
15266 if selected_larger_node {
15267 self.select_syntax_node_history.disable_clearing = true;
15268 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15269 s.select(new_selections.clone());
15270 });
15271 self.select_syntax_node_history.disable_clearing = false;
15272 }
15273
15274 let start_row = last_new.start.to_display_point(&display_map).row().0;
15275 let end_row = last_new.end.to_display_point(&display_map).row().0;
15276 let selection_height = end_row - start_row + 1;
15277 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15278
15279 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15280 let scroll_behavior = if fits_on_the_screen {
15281 self.request_autoscroll(Autoscroll::fit(), cx);
15282 SelectSyntaxNodeScrollBehavior::FitSelection
15283 } else if is_selection_reversed {
15284 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15285 SelectSyntaxNodeScrollBehavior::CursorTop
15286 } else {
15287 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15288 SelectSyntaxNodeScrollBehavior::CursorBottom
15289 };
15290
15291 self.select_syntax_node_history.push((
15292 old_selections,
15293 scroll_behavior,
15294 is_selection_reversed,
15295 ));
15296 }
15297
15298 pub fn select_smaller_syntax_node(
15299 &mut self,
15300 _: &SelectSmallerSyntaxNode,
15301 window: &mut Window,
15302 cx: &mut Context<Self>,
15303 ) {
15304 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15305
15306 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15307 self.select_syntax_node_history.pop()
15308 {
15309 if let Some(selection) = selections.last_mut() {
15310 selection.reversed = is_selection_reversed;
15311 }
15312
15313 self.select_syntax_node_history.disable_clearing = true;
15314 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15315 s.select(selections.to_vec());
15316 });
15317 self.select_syntax_node_history.disable_clearing = false;
15318
15319 match scroll_behavior {
15320 SelectSyntaxNodeScrollBehavior::CursorTop => {
15321 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15322 }
15323 SelectSyntaxNodeScrollBehavior::FitSelection => {
15324 self.request_autoscroll(Autoscroll::fit(), cx);
15325 }
15326 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15327 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15328 }
15329 }
15330 }
15331 }
15332
15333 pub fn unwrap_syntax_node(
15334 &mut self,
15335 _: &UnwrapSyntaxNode,
15336 window: &mut Window,
15337 cx: &mut Context<Self>,
15338 ) {
15339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15340
15341 let buffer = self.buffer.read(cx).snapshot(cx);
15342 let selections = self
15343 .selections
15344 .all::<usize>(cx)
15345 .into_iter()
15346 // subtracting the offset requires sorting
15347 .sorted_by_key(|i| i.start);
15348
15349 let full_edits = selections
15350 .into_iter()
15351 .filter_map(|selection| {
15352 let child = if selection.is_empty()
15353 && let Some((_, ancestor_range)) =
15354 buffer.syntax_ancestor(selection.start..selection.end)
15355 {
15356 ancestor_range
15357 } else {
15358 selection.range()
15359 };
15360
15361 let mut parent = child.clone();
15362 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15363 parent = ancestor_range;
15364 if parent.start < child.start || parent.end > child.end {
15365 break;
15366 }
15367 }
15368
15369 if parent == child {
15370 return None;
15371 }
15372 let text = buffer.text_for_range(child).collect::<String>();
15373 Some((selection.id, parent, text))
15374 })
15375 .collect::<Vec<_>>();
15376 if full_edits.is_empty() {
15377 return;
15378 }
15379
15380 self.transact(window, cx, |this, window, cx| {
15381 this.buffer.update(cx, |buffer, cx| {
15382 buffer.edit(
15383 full_edits
15384 .iter()
15385 .map(|(_, p, t)| (p.clone(), t.clone()))
15386 .collect::<Vec<_>>(),
15387 None,
15388 cx,
15389 );
15390 });
15391 this.change_selections(Default::default(), window, cx, |s| {
15392 let mut offset = 0;
15393 let mut selections = vec![];
15394 for (id, parent, text) in full_edits {
15395 let start = parent.start - offset;
15396 offset += parent.len() - text.len();
15397 selections.push(Selection {
15398 id,
15399 start,
15400 end: start + text.len(),
15401 reversed: false,
15402 goal: Default::default(),
15403 });
15404 }
15405 s.select(selections);
15406 });
15407 });
15408 }
15409
15410 pub fn select_next_syntax_node(
15411 &mut self,
15412 _: &SelectNextSyntaxNode,
15413 window: &mut Window,
15414 cx: &mut Context<Self>,
15415 ) {
15416 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15417 if old_selections.is_empty() {
15418 return;
15419 }
15420
15421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15422
15423 let buffer = self.buffer.read(cx).snapshot(cx);
15424 let mut selected_sibling = false;
15425
15426 let new_selections = old_selections
15427 .iter()
15428 .map(|selection| {
15429 let old_range = selection.start..selection.end;
15430
15431 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15432 let new_range = node.byte_range();
15433 selected_sibling = true;
15434 Selection {
15435 id: selection.id,
15436 start: new_range.start,
15437 end: new_range.end,
15438 goal: SelectionGoal::None,
15439 reversed: selection.reversed,
15440 }
15441 } else {
15442 selection.clone()
15443 }
15444 })
15445 .collect::<Vec<_>>();
15446
15447 if selected_sibling {
15448 self.change_selections(
15449 SelectionEffects::scroll(Autoscroll::fit()),
15450 window,
15451 cx,
15452 |s| {
15453 s.select(new_selections);
15454 },
15455 );
15456 }
15457 }
15458
15459 pub fn select_prev_syntax_node(
15460 &mut self,
15461 _: &SelectPreviousSyntaxNode,
15462 window: &mut Window,
15463 cx: &mut Context<Self>,
15464 ) {
15465 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15466 if old_selections.is_empty() {
15467 return;
15468 }
15469
15470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15471
15472 let buffer = self.buffer.read(cx).snapshot(cx);
15473 let mut selected_sibling = false;
15474
15475 let new_selections = old_selections
15476 .iter()
15477 .map(|selection| {
15478 let old_range = selection.start..selection.end;
15479
15480 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15481 let new_range = node.byte_range();
15482 selected_sibling = true;
15483 Selection {
15484 id: selection.id,
15485 start: new_range.start,
15486 end: new_range.end,
15487 goal: SelectionGoal::None,
15488 reversed: selection.reversed,
15489 }
15490 } else {
15491 selection.clone()
15492 }
15493 })
15494 .collect::<Vec<_>>();
15495
15496 if selected_sibling {
15497 self.change_selections(
15498 SelectionEffects::scroll(Autoscroll::fit()),
15499 window,
15500 cx,
15501 |s| {
15502 s.select(new_selections);
15503 },
15504 );
15505 }
15506 }
15507
15508 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15509 if !EditorSettings::get_global(cx).gutter.runnables {
15510 self.clear_tasks();
15511 return Task::ready(());
15512 }
15513 let project = self.project().map(Entity::downgrade);
15514 let task_sources = self.lsp_task_sources(cx);
15515 let multi_buffer = self.buffer.downgrade();
15516 cx.spawn_in(window, async move |editor, cx| {
15517 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15518 let Some(project) = project.and_then(|p| p.upgrade()) else {
15519 return;
15520 };
15521 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15522 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15523 }) else {
15524 return;
15525 };
15526
15527 let hide_runnables = project
15528 .update(cx, |project, _| project.is_via_collab())
15529 .unwrap_or(true);
15530 if hide_runnables {
15531 return;
15532 }
15533 let new_rows =
15534 cx.background_spawn({
15535 let snapshot = display_snapshot.clone();
15536 async move {
15537 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15538 }
15539 })
15540 .await;
15541 let Ok(lsp_tasks) =
15542 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15543 else {
15544 return;
15545 };
15546 let lsp_tasks = lsp_tasks.await;
15547
15548 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15549 lsp_tasks
15550 .into_iter()
15551 .flat_map(|(kind, tasks)| {
15552 tasks.into_iter().filter_map(move |(location, task)| {
15553 Some((kind.clone(), location?, task))
15554 })
15555 })
15556 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15557 let buffer = location.target.buffer;
15558 let buffer_snapshot = buffer.read(cx).snapshot();
15559 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15560 |(excerpt_id, snapshot, _)| {
15561 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15562 display_snapshot
15563 .buffer_snapshot()
15564 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15565 } else {
15566 None
15567 }
15568 },
15569 );
15570 if let Some(offset) = offset {
15571 let task_buffer_range =
15572 location.target.range.to_point(&buffer_snapshot);
15573 let context_buffer_range =
15574 task_buffer_range.to_offset(&buffer_snapshot);
15575 let context_range = BufferOffset(context_buffer_range.start)
15576 ..BufferOffset(context_buffer_range.end);
15577
15578 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15579 .or_insert_with(|| RunnableTasks {
15580 templates: Vec::new(),
15581 offset,
15582 column: task_buffer_range.start.column,
15583 extra_variables: HashMap::default(),
15584 context_range,
15585 })
15586 .templates
15587 .push((kind, task.original_task().clone()));
15588 }
15589
15590 acc
15591 })
15592 }) else {
15593 return;
15594 };
15595
15596 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15597 buffer.language_settings(cx).tasks.prefer_lsp
15598 }) else {
15599 return;
15600 };
15601
15602 let rows = Self::runnable_rows(
15603 project,
15604 display_snapshot,
15605 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15606 new_rows,
15607 cx.clone(),
15608 )
15609 .await;
15610 editor
15611 .update(cx, |editor, _| {
15612 editor.clear_tasks();
15613 for (key, mut value) in rows {
15614 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15615 value.templates.extend(lsp_tasks.templates);
15616 }
15617
15618 editor.insert_tasks(key, value);
15619 }
15620 for (key, value) in lsp_tasks_by_rows {
15621 editor.insert_tasks(key, value);
15622 }
15623 })
15624 .ok();
15625 })
15626 }
15627 fn fetch_runnable_ranges(
15628 snapshot: &DisplaySnapshot,
15629 range: Range<Anchor>,
15630 ) -> Vec<language::RunnableRange> {
15631 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15632 }
15633
15634 fn runnable_rows(
15635 project: Entity<Project>,
15636 snapshot: DisplaySnapshot,
15637 prefer_lsp: bool,
15638 runnable_ranges: Vec<RunnableRange>,
15639 cx: AsyncWindowContext,
15640 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15641 cx.spawn(async move |cx| {
15642 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15643 for mut runnable in runnable_ranges {
15644 let Some(tasks) = cx
15645 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15646 .ok()
15647 else {
15648 continue;
15649 };
15650 let mut tasks = tasks.await;
15651
15652 if prefer_lsp {
15653 tasks.retain(|(task_kind, _)| {
15654 !matches!(task_kind, TaskSourceKind::Language { .. })
15655 });
15656 }
15657 if tasks.is_empty() {
15658 continue;
15659 }
15660
15661 let point = runnable
15662 .run_range
15663 .start
15664 .to_point(&snapshot.buffer_snapshot());
15665 let Some(row) = snapshot
15666 .buffer_snapshot()
15667 .buffer_line_for_row(MultiBufferRow(point.row))
15668 .map(|(_, range)| range.start.row)
15669 else {
15670 continue;
15671 };
15672
15673 let context_range =
15674 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15675 runnable_rows.push((
15676 (runnable.buffer_id, row),
15677 RunnableTasks {
15678 templates: tasks,
15679 offset: snapshot
15680 .buffer_snapshot()
15681 .anchor_before(runnable.run_range.start),
15682 context_range,
15683 column: point.column,
15684 extra_variables: runnable.extra_captures,
15685 },
15686 ));
15687 }
15688 runnable_rows
15689 })
15690 }
15691
15692 fn templates_with_tags(
15693 project: &Entity<Project>,
15694 runnable: &mut Runnable,
15695 cx: &mut App,
15696 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15697 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15698 let (worktree_id, file) = project
15699 .buffer_for_id(runnable.buffer, cx)
15700 .and_then(|buffer| buffer.read(cx).file())
15701 .map(|file| (file.worktree_id(cx), file.clone()))
15702 .unzip();
15703
15704 (
15705 project.task_store().read(cx).task_inventory().cloned(),
15706 worktree_id,
15707 file,
15708 )
15709 });
15710
15711 let tags = mem::take(&mut runnable.tags);
15712 let language = runnable.language.clone();
15713 cx.spawn(async move |cx| {
15714 let mut templates_with_tags = Vec::new();
15715 if let Some(inventory) = inventory {
15716 for RunnableTag(tag) in tags {
15717 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15718 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15719 }) else {
15720 return templates_with_tags;
15721 };
15722 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15723 move |(_, template)| {
15724 template.tags.iter().any(|source_tag| source_tag == &tag)
15725 },
15726 ));
15727 }
15728 }
15729 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15730
15731 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15732 // Strongest source wins; if we have worktree tag binding, prefer that to
15733 // global and language bindings;
15734 // if we have a global binding, prefer that to language binding.
15735 let first_mismatch = templates_with_tags
15736 .iter()
15737 .position(|(tag_source, _)| tag_source != leading_tag_source);
15738 if let Some(index) = first_mismatch {
15739 templates_with_tags.truncate(index);
15740 }
15741 }
15742
15743 templates_with_tags
15744 })
15745 }
15746
15747 pub fn move_to_enclosing_bracket(
15748 &mut self,
15749 _: &MoveToEnclosingBracket,
15750 window: &mut Window,
15751 cx: &mut Context<Self>,
15752 ) {
15753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15754 self.change_selections(Default::default(), window, cx, |s| {
15755 s.move_offsets_with(|snapshot, selection| {
15756 let Some(enclosing_bracket_ranges) =
15757 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15758 else {
15759 return;
15760 };
15761
15762 let mut best_length = usize::MAX;
15763 let mut best_inside = false;
15764 let mut best_in_bracket_range = false;
15765 let mut best_destination = None;
15766 for (open, close) in enclosing_bracket_ranges {
15767 let close = close.to_inclusive();
15768 let length = close.end() - open.start;
15769 let inside = selection.start >= open.end && selection.end <= *close.start();
15770 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15771 || close.contains(&selection.head());
15772
15773 // If best is next to a bracket and current isn't, skip
15774 if !in_bracket_range && best_in_bracket_range {
15775 continue;
15776 }
15777
15778 // Prefer smaller lengths unless best is inside and current isn't
15779 if length > best_length && (best_inside || !inside) {
15780 continue;
15781 }
15782
15783 best_length = length;
15784 best_inside = inside;
15785 best_in_bracket_range = in_bracket_range;
15786 best_destination = Some(
15787 if close.contains(&selection.start) && close.contains(&selection.end) {
15788 if inside { open.end } else { open.start }
15789 } else if inside {
15790 *close.start()
15791 } else {
15792 *close.end()
15793 },
15794 );
15795 }
15796
15797 if let Some(destination) = best_destination {
15798 selection.collapse_to(destination, SelectionGoal::None);
15799 }
15800 })
15801 });
15802 }
15803
15804 pub fn undo_selection(
15805 &mut self,
15806 _: &UndoSelection,
15807 window: &mut Window,
15808 cx: &mut Context<Self>,
15809 ) {
15810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15811 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15812 self.selection_history.mode = SelectionHistoryMode::Undoing;
15813 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15814 this.end_selection(window, cx);
15815 this.change_selections(
15816 SelectionEffects::scroll(Autoscroll::newest()),
15817 window,
15818 cx,
15819 |s| s.select_anchors(entry.selections.to_vec()),
15820 );
15821 });
15822 self.selection_history.mode = SelectionHistoryMode::Normal;
15823
15824 self.select_next_state = entry.select_next_state;
15825 self.select_prev_state = entry.select_prev_state;
15826 self.add_selections_state = entry.add_selections_state;
15827 }
15828 }
15829
15830 pub fn redo_selection(
15831 &mut self,
15832 _: &RedoSelection,
15833 window: &mut Window,
15834 cx: &mut Context<Self>,
15835 ) {
15836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15837 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15838 self.selection_history.mode = SelectionHistoryMode::Redoing;
15839 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15840 this.end_selection(window, cx);
15841 this.change_selections(
15842 SelectionEffects::scroll(Autoscroll::newest()),
15843 window,
15844 cx,
15845 |s| s.select_anchors(entry.selections.to_vec()),
15846 );
15847 });
15848 self.selection_history.mode = SelectionHistoryMode::Normal;
15849
15850 self.select_next_state = entry.select_next_state;
15851 self.select_prev_state = entry.select_prev_state;
15852 self.add_selections_state = entry.add_selections_state;
15853 }
15854 }
15855
15856 pub fn expand_excerpts(
15857 &mut self,
15858 action: &ExpandExcerpts,
15859 _: &mut Window,
15860 cx: &mut Context<Self>,
15861 ) {
15862 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15863 }
15864
15865 pub fn expand_excerpts_down(
15866 &mut self,
15867 action: &ExpandExcerptsDown,
15868 _: &mut Window,
15869 cx: &mut Context<Self>,
15870 ) {
15871 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15872 }
15873
15874 pub fn expand_excerpts_up(
15875 &mut self,
15876 action: &ExpandExcerptsUp,
15877 _: &mut Window,
15878 cx: &mut Context<Self>,
15879 ) {
15880 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15881 }
15882
15883 pub fn expand_excerpts_for_direction(
15884 &mut self,
15885 lines: u32,
15886 direction: ExpandExcerptDirection,
15887
15888 cx: &mut Context<Self>,
15889 ) {
15890 let selections = self.selections.disjoint_anchors_arc();
15891
15892 let lines = if lines == 0 {
15893 EditorSettings::get_global(cx).expand_excerpt_lines
15894 } else {
15895 lines
15896 };
15897
15898 self.buffer.update(cx, |buffer, cx| {
15899 let snapshot = buffer.snapshot(cx);
15900 let mut excerpt_ids = selections
15901 .iter()
15902 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15903 .collect::<Vec<_>>();
15904 excerpt_ids.sort();
15905 excerpt_ids.dedup();
15906 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15907 })
15908 }
15909
15910 pub fn expand_excerpt(
15911 &mut self,
15912 excerpt: ExcerptId,
15913 direction: ExpandExcerptDirection,
15914 window: &mut Window,
15915 cx: &mut Context<Self>,
15916 ) {
15917 let current_scroll_position = self.scroll_position(cx);
15918 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15919 let mut should_scroll_up = false;
15920
15921 if direction == ExpandExcerptDirection::Down {
15922 let multi_buffer = self.buffer.read(cx);
15923 let snapshot = multi_buffer.snapshot(cx);
15924 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15925 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15926 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15927 {
15928 let buffer_snapshot = buffer.read(cx).snapshot();
15929 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15930 let last_row = buffer_snapshot.max_point().row;
15931 let lines_below = last_row.saturating_sub(excerpt_end_row);
15932 should_scroll_up = lines_below >= lines_to_expand;
15933 }
15934 }
15935
15936 self.buffer.update(cx, |buffer, cx| {
15937 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15938 });
15939
15940 if should_scroll_up {
15941 let new_scroll_position =
15942 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15943 self.set_scroll_position(new_scroll_position, window, cx);
15944 }
15945 }
15946
15947 pub fn go_to_singleton_buffer_point(
15948 &mut self,
15949 point: Point,
15950 window: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 self.go_to_singleton_buffer_range(point..point, window, cx);
15954 }
15955
15956 pub fn go_to_singleton_buffer_range(
15957 &mut self,
15958 range: Range<Point>,
15959 window: &mut Window,
15960 cx: &mut Context<Self>,
15961 ) {
15962 let multibuffer = self.buffer().read(cx);
15963 let Some(buffer) = multibuffer.as_singleton() else {
15964 return;
15965 };
15966 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15967 return;
15968 };
15969 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15970 return;
15971 };
15972 self.change_selections(
15973 SelectionEffects::default().nav_history(true),
15974 window,
15975 cx,
15976 |s| s.select_anchor_ranges([start..end]),
15977 );
15978 }
15979
15980 pub fn go_to_diagnostic(
15981 &mut self,
15982 action: &GoToDiagnostic,
15983 window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) {
15986 if !self.diagnostics_enabled() {
15987 return;
15988 }
15989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15990 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15991 }
15992
15993 pub fn go_to_prev_diagnostic(
15994 &mut self,
15995 action: &GoToPreviousDiagnostic,
15996 window: &mut Window,
15997 cx: &mut Context<Self>,
15998 ) {
15999 if !self.diagnostics_enabled() {
16000 return;
16001 }
16002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16003 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16004 }
16005
16006 pub fn go_to_diagnostic_impl(
16007 &mut self,
16008 direction: Direction,
16009 severity: GoToDiagnosticSeverityFilter,
16010 window: &mut Window,
16011 cx: &mut Context<Self>,
16012 ) {
16013 let buffer = self.buffer.read(cx).snapshot(cx);
16014 let selection = self.selections.newest::<usize>(cx);
16015
16016 let mut active_group_id = None;
16017 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16018 && active_group.active_range.start.to_offset(&buffer) == selection.start
16019 {
16020 active_group_id = Some(active_group.group_id);
16021 }
16022
16023 fn filtered<'a>(
16024 snapshot: EditorSnapshot,
16025 severity: GoToDiagnosticSeverityFilter,
16026 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16027 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16028 diagnostics
16029 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16030 .filter(|entry| entry.range.start != entry.range.end)
16031 .filter(|entry| !entry.diagnostic.is_unnecessary)
16032 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16033 }
16034
16035 let snapshot = self.snapshot(window, cx);
16036 let before = filtered(
16037 snapshot.clone(),
16038 severity,
16039 buffer
16040 .diagnostics_in_range(0..selection.start)
16041 .filter(|entry| entry.range.start <= selection.start),
16042 );
16043 let after = filtered(
16044 snapshot,
16045 severity,
16046 buffer
16047 .diagnostics_in_range(selection.start..buffer.len())
16048 .filter(|entry| entry.range.start >= selection.start),
16049 );
16050
16051 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16052 if direction == Direction::Prev {
16053 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16054 {
16055 for diagnostic in prev_diagnostics.into_iter().rev() {
16056 if diagnostic.range.start != selection.start
16057 || active_group_id
16058 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16059 {
16060 found = Some(diagnostic);
16061 break 'outer;
16062 }
16063 }
16064 }
16065 } else {
16066 for diagnostic in after.chain(before) {
16067 if diagnostic.range.start != selection.start
16068 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16069 {
16070 found = Some(diagnostic);
16071 break;
16072 }
16073 }
16074 }
16075 let Some(next_diagnostic) = found else {
16076 return;
16077 };
16078
16079 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16080 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16081 return;
16082 };
16083 self.change_selections(Default::default(), window, cx, |s| {
16084 s.select_ranges(vec![
16085 next_diagnostic.range.start..next_diagnostic.range.start,
16086 ])
16087 });
16088 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16089 self.refresh_edit_prediction(false, true, window, cx);
16090 }
16091
16092 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16094 let snapshot = self.snapshot(window, cx);
16095 let selection = self.selections.newest::<Point>(cx);
16096 self.go_to_hunk_before_or_after_position(
16097 &snapshot,
16098 selection.head(),
16099 Direction::Next,
16100 window,
16101 cx,
16102 );
16103 }
16104
16105 pub fn go_to_hunk_before_or_after_position(
16106 &mut self,
16107 snapshot: &EditorSnapshot,
16108 position: Point,
16109 direction: Direction,
16110 window: &mut Window,
16111 cx: &mut Context<Editor>,
16112 ) {
16113 let row = if direction == Direction::Next {
16114 self.hunk_after_position(snapshot, position)
16115 .map(|hunk| hunk.row_range.start)
16116 } else {
16117 self.hunk_before_position(snapshot, position)
16118 };
16119
16120 if let Some(row) = row {
16121 let destination = Point::new(row.0, 0);
16122 let autoscroll = Autoscroll::center();
16123
16124 self.unfold_ranges(&[destination..destination], false, false, cx);
16125 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16126 s.select_ranges([destination..destination]);
16127 });
16128 }
16129 }
16130
16131 fn hunk_after_position(
16132 &mut self,
16133 snapshot: &EditorSnapshot,
16134 position: Point,
16135 ) -> Option<MultiBufferDiffHunk> {
16136 snapshot
16137 .buffer_snapshot()
16138 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16139 .find(|hunk| hunk.row_range.start.0 > position.row)
16140 .or_else(|| {
16141 snapshot
16142 .buffer_snapshot()
16143 .diff_hunks_in_range(Point::zero()..position)
16144 .find(|hunk| hunk.row_range.end.0 < position.row)
16145 })
16146 }
16147
16148 fn go_to_prev_hunk(
16149 &mut self,
16150 _: &GoToPreviousHunk,
16151 window: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) {
16154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16155 let snapshot = self.snapshot(window, cx);
16156 let selection = self.selections.newest::<Point>(cx);
16157 self.go_to_hunk_before_or_after_position(
16158 &snapshot,
16159 selection.head(),
16160 Direction::Prev,
16161 window,
16162 cx,
16163 );
16164 }
16165
16166 fn hunk_before_position(
16167 &mut self,
16168 snapshot: &EditorSnapshot,
16169 position: Point,
16170 ) -> Option<MultiBufferRow> {
16171 snapshot
16172 .buffer_snapshot()
16173 .diff_hunk_before(position)
16174 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16175 }
16176
16177 fn go_to_next_change(
16178 &mut self,
16179 _: &GoToNextChange,
16180 window: &mut Window,
16181 cx: &mut Context<Self>,
16182 ) {
16183 if let Some(selections) = self
16184 .change_list
16185 .next_change(1, Direction::Next)
16186 .map(|s| s.to_vec())
16187 {
16188 self.change_selections(Default::default(), window, cx, |s| {
16189 let map = s.display_map();
16190 s.select_display_ranges(selections.iter().map(|a| {
16191 let point = a.to_display_point(&map);
16192 point..point
16193 }))
16194 })
16195 }
16196 }
16197
16198 fn go_to_previous_change(
16199 &mut self,
16200 _: &GoToPreviousChange,
16201 window: &mut Window,
16202 cx: &mut Context<Self>,
16203 ) {
16204 if let Some(selections) = self
16205 .change_list
16206 .next_change(1, Direction::Prev)
16207 .map(|s| s.to_vec())
16208 {
16209 self.change_selections(Default::default(), window, cx, |s| {
16210 let map = s.display_map();
16211 s.select_display_ranges(selections.iter().map(|a| {
16212 let point = a.to_display_point(&map);
16213 point..point
16214 }))
16215 })
16216 }
16217 }
16218
16219 pub fn go_to_next_document_highlight(
16220 &mut self,
16221 _: &GoToNextDocumentHighlight,
16222 window: &mut Window,
16223 cx: &mut Context<Self>,
16224 ) {
16225 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16226 }
16227
16228 pub fn go_to_prev_document_highlight(
16229 &mut self,
16230 _: &GoToPreviousDocumentHighlight,
16231 window: &mut Window,
16232 cx: &mut Context<Self>,
16233 ) {
16234 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16235 }
16236
16237 pub fn go_to_document_highlight_before_or_after_position(
16238 &mut self,
16239 direction: Direction,
16240 window: &mut Window,
16241 cx: &mut Context<Editor>,
16242 ) {
16243 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16244 let snapshot = self.snapshot(window, cx);
16245 let buffer = &snapshot.buffer_snapshot();
16246 let position = self.selections.newest::<Point>(cx).head();
16247 let anchor_position = buffer.anchor_after(position);
16248
16249 // Get all document highlights (both read and write)
16250 let mut all_highlights = Vec::new();
16251
16252 if let Some((_, read_highlights)) = self
16253 .background_highlights
16254 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16255 {
16256 all_highlights.extend(read_highlights.iter());
16257 }
16258
16259 if let Some((_, write_highlights)) = self
16260 .background_highlights
16261 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16262 {
16263 all_highlights.extend(write_highlights.iter());
16264 }
16265
16266 if all_highlights.is_empty() {
16267 return;
16268 }
16269
16270 // Sort highlights by position
16271 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16272
16273 let target_highlight = match direction {
16274 Direction::Next => {
16275 // Find the first highlight after the current position
16276 all_highlights
16277 .iter()
16278 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16279 }
16280 Direction::Prev => {
16281 // Find the last highlight before the current position
16282 all_highlights
16283 .iter()
16284 .rev()
16285 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16286 }
16287 };
16288
16289 if let Some(highlight) = target_highlight {
16290 let destination = highlight.start.to_point(buffer);
16291 let autoscroll = Autoscroll::center();
16292
16293 self.unfold_ranges(&[destination..destination], false, false, cx);
16294 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16295 s.select_ranges([destination..destination]);
16296 });
16297 }
16298 }
16299
16300 fn go_to_line<T: 'static>(
16301 &mut self,
16302 position: Anchor,
16303 highlight_color: Option<Hsla>,
16304 window: &mut Window,
16305 cx: &mut Context<Self>,
16306 ) {
16307 let snapshot = self.snapshot(window, cx).display_snapshot;
16308 let position = position.to_point(&snapshot.buffer_snapshot());
16309 let start = snapshot
16310 .buffer_snapshot()
16311 .clip_point(Point::new(position.row, 0), Bias::Left);
16312 let end = start + Point::new(1, 0);
16313 let start = snapshot.buffer_snapshot().anchor_before(start);
16314 let end = snapshot.buffer_snapshot().anchor_before(end);
16315
16316 self.highlight_rows::<T>(
16317 start..end,
16318 highlight_color
16319 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16320 Default::default(),
16321 cx,
16322 );
16323
16324 if self.buffer.read(cx).is_singleton() {
16325 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16326 }
16327 }
16328
16329 pub fn go_to_definition(
16330 &mut self,
16331 _: &GoToDefinition,
16332 window: &mut Window,
16333 cx: &mut Context<Self>,
16334 ) -> Task<Result<Navigated>> {
16335 let definition =
16336 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16337 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16338 cx.spawn_in(window, async move |editor, cx| {
16339 if definition.await? == Navigated::Yes {
16340 return Ok(Navigated::Yes);
16341 }
16342 match fallback_strategy {
16343 GoToDefinitionFallback::None => Ok(Navigated::No),
16344 GoToDefinitionFallback::FindAllReferences => {
16345 match editor.update_in(cx, |editor, window, cx| {
16346 editor.find_all_references(&FindAllReferences, window, cx)
16347 })? {
16348 Some(references) => references.await,
16349 None => Ok(Navigated::No),
16350 }
16351 }
16352 }
16353 })
16354 }
16355
16356 pub fn go_to_declaration(
16357 &mut self,
16358 _: &GoToDeclaration,
16359 window: &mut Window,
16360 cx: &mut Context<Self>,
16361 ) -> Task<Result<Navigated>> {
16362 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16363 }
16364
16365 pub fn go_to_declaration_split(
16366 &mut self,
16367 _: &GoToDeclaration,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) -> Task<Result<Navigated>> {
16371 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16372 }
16373
16374 pub fn go_to_implementation(
16375 &mut self,
16376 _: &GoToImplementation,
16377 window: &mut Window,
16378 cx: &mut Context<Self>,
16379 ) -> Task<Result<Navigated>> {
16380 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16381 }
16382
16383 pub fn go_to_implementation_split(
16384 &mut self,
16385 _: &GoToImplementationSplit,
16386 window: &mut Window,
16387 cx: &mut Context<Self>,
16388 ) -> Task<Result<Navigated>> {
16389 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16390 }
16391
16392 pub fn go_to_type_definition(
16393 &mut self,
16394 _: &GoToTypeDefinition,
16395 window: &mut Window,
16396 cx: &mut Context<Self>,
16397 ) -> Task<Result<Navigated>> {
16398 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16399 }
16400
16401 pub fn go_to_definition_split(
16402 &mut self,
16403 _: &GoToDefinitionSplit,
16404 window: &mut Window,
16405 cx: &mut Context<Self>,
16406 ) -> Task<Result<Navigated>> {
16407 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16408 }
16409
16410 pub fn go_to_type_definition_split(
16411 &mut self,
16412 _: &GoToTypeDefinitionSplit,
16413 window: &mut Window,
16414 cx: &mut Context<Self>,
16415 ) -> Task<Result<Navigated>> {
16416 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16417 }
16418
16419 fn go_to_definition_of_kind(
16420 &mut self,
16421 kind: GotoDefinitionKind,
16422 split: bool,
16423 window: &mut Window,
16424 cx: &mut Context<Self>,
16425 ) -> Task<Result<Navigated>> {
16426 let Some(provider) = self.semantics_provider.clone() else {
16427 return Task::ready(Ok(Navigated::No));
16428 };
16429 let head = self.selections.newest::<usize>(cx).head();
16430 let buffer = self.buffer.read(cx);
16431 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16432 return Task::ready(Ok(Navigated::No));
16433 };
16434 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16435 return Task::ready(Ok(Navigated::No));
16436 };
16437
16438 cx.spawn_in(window, async move |editor, cx| {
16439 let Some(definitions) = definitions.await? else {
16440 return Ok(Navigated::No);
16441 };
16442 let navigated = editor
16443 .update_in(cx, |editor, window, cx| {
16444 editor.navigate_to_hover_links(
16445 Some(kind),
16446 definitions
16447 .into_iter()
16448 .filter(|location| {
16449 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16450 })
16451 .map(HoverLink::Text)
16452 .collect::<Vec<_>>(),
16453 split,
16454 window,
16455 cx,
16456 )
16457 })?
16458 .await?;
16459 anyhow::Ok(navigated)
16460 })
16461 }
16462
16463 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16464 let selection = self.selections.newest_anchor();
16465 let head = selection.head();
16466 let tail = selection.tail();
16467
16468 let Some((buffer, start_position)) =
16469 self.buffer.read(cx).text_anchor_for_position(head, cx)
16470 else {
16471 return;
16472 };
16473
16474 let end_position = if head != tail {
16475 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16476 return;
16477 };
16478 Some(pos)
16479 } else {
16480 None
16481 };
16482
16483 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16484 let url = if let Some(end_pos) = end_position {
16485 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16486 } else {
16487 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16488 };
16489
16490 if let Some(url) = url {
16491 cx.update(|window, cx| {
16492 if parse_zed_link(&url, cx).is_some() {
16493 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16494 } else {
16495 cx.open_url(&url);
16496 }
16497 })?;
16498 }
16499
16500 anyhow::Ok(())
16501 });
16502
16503 url_finder.detach();
16504 }
16505
16506 pub fn open_selected_filename(
16507 &mut self,
16508 _: &OpenSelectedFilename,
16509 window: &mut Window,
16510 cx: &mut Context<Self>,
16511 ) {
16512 let Some(workspace) = self.workspace() else {
16513 return;
16514 };
16515
16516 let position = self.selections.newest_anchor().head();
16517
16518 let Some((buffer, buffer_position)) =
16519 self.buffer.read(cx).text_anchor_for_position(position, cx)
16520 else {
16521 return;
16522 };
16523
16524 let project = self.project.clone();
16525
16526 cx.spawn_in(window, async move |_, cx| {
16527 let result = find_file(&buffer, project, buffer_position, cx).await;
16528
16529 if let Some((_, path)) = result {
16530 workspace
16531 .update_in(cx, |workspace, window, cx| {
16532 workspace.open_resolved_path(path, window, cx)
16533 })?
16534 .await?;
16535 }
16536 anyhow::Ok(())
16537 })
16538 .detach();
16539 }
16540
16541 pub(crate) fn navigate_to_hover_links(
16542 &mut self,
16543 kind: Option<GotoDefinitionKind>,
16544 definitions: Vec<HoverLink>,
16545 split: bool,
16546 window: &mut Window,
16547 cx: &mut Context<Editor>,
16548 ) -> Task<Result<Navigated>> {
16549 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16550 let mut first_url_or_file = None;
16551 let definitions: Vec<_> = definitions
16552 .into_iter()
16553 .filter_map(|def| match def {
16554 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16555 HoverLink::InlayHint(lsp_location, server_id) => {
16556 let computation =
16557 self.compute_target_location(lsp_location, server_id, window, cx);
16558 Some(cx.background_spawn(computation))
16559 }
16560 HoverLink::Url(url) => {
16561 first_url_or_file = Some(Either::Left(url));
16562 None
16563 }
16564 HoverLink::File(path) => {
16565 first_url_or_file = Some(Either::Right(path));
16566 None
16567 }
16568 })
16569 .collect();
16570
16571 let workspace = self.workspace();
16572
16573 cx.spawn_in(window, async move |editor, cx| {
16574 let locations: Vec<Location> = future::join_all(definitions)
16575 .await
16576 .into_iter()
16577 .filter_map(|location| location.transpose())
16578 .collect::<Result<_>>()
16579 .context("location tasks")?;
16580 let mut locations = cx.update(|_, cx| {
16581 locations
16582 .into_iter()
16583 .map(|location| {
16584 let buffer = location.buffer.read(cx);
16585 (location.buffer, location.range.to_point(buffer))
16586 })
16587 .into_group_map()
16588 })?;
16589 let mut num_locations = 0;
16590 for ranges in locations.values_mut() {
16591 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16592 ranges.dedup();
16593 num_locations += ranges.len();
16594 }
16595
16596 if num_locations > 1 {
16597 let Some(workspace) = workspace else {
16598 return Ok(Navigated::No);
16599 };
16600
16601 let tab_kind = match kind {
16602 Some(GotoDefinitionKind::Implementation) => "Implementations",
16603 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16604 Some(GotoDefinitionKind::Declaration) => "Declarations",
16605 Some(GotoDefinitionKind::Type) => "Types",
16606 };
16607 let title = editor
16608 .update_in(cx, |_, _, cx| {
16609 let target = locations
16610 .iter()
16611 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16612 .map(|(buffer, location)| {
16613 buffer
16614 .read(cx)
16615 .text_for_range(location.clone())
16616 .collect::<String>()
16617 })
16618 .filter(|text| !text.contains('\n'))
16619 .unique()
16620 .take(3)
16621 .join(", ");
16622 if target.is_empty() {
16623 tab_kind.to_owned()
16624 } else {
16625 format!("{tab_kind} for {target}")
16626 }
16627 })
16628 .context("buffer title")?;
16629
16630 let opened = workspace
16631 .update_in(cx, |workspace, window, cx| {
16632 Self::open_locations_in_multibuffer(
16633 workspace,
16634 locations,
16635 title,
16636 split,
16637 MultibufferSelectionMode::First,
16638 window,
16639 cx,
16640 )
16641 })
16642 .is_ok();
16643
16644 anyhow::Ok(Navigated::from_bool(opened))
16645 } else if num_locations == 0 {
16646 // If there is one url or file, open it directly
16647 match first_url_or_file {
16648 Some(Either::Left(url)) => {
16649 cx.update(|_, cx| cx.open_url(&url))?;
16650 Ok(Navigated::Yes)
16651 }
16652 Some(Either::Right(path)) => {
16653 let Some(workspace) = workspace else {
16654 return Ok(Navigated::No);
16655 };
16656
16657 workspace
16658 .update_in(cx, |workspace, window, cx| {
16659 workspace.open_resolved_path(path, window, cx)
16660 })?
16661 .await?;
16662 Ok(Navigated::Yes)
16663 }
16664 None => Ok(Navigated::No),
16665 }
16666 } else {
16667 let Some(workspace) = workspace else {
16668 return Ok(Navigated::No);
16669 };
16670
16671 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16672 let target_range = target_ranges.first().unwrap().clone();
16673
16674 editor.update_in(cx, |editor, window, cx| {
16675 let range = target_range.to_point(target_buffer.read(cx));
16676 let range = editor.range_for_match(&range);
16677 let range = collapse_multiline_range(range);
16678
16679 if !split
16680 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16681 {
16682 editor.go_to_singleton_buffer_range(range, window, cx);
16683 } else {
16684 let pane = workspace.read(cx).active_pane().clone();
16685 window.defer(cx, move |window, cx| {
16686 let target_editor: Entity<Self> =
16687 workspace.update(cx, |workspace, cx| {
16688 let pane = if split {
16689 workspace.adjacent_pane(window, cx)
16690 } else {
16691 workspace.active_pane().clone()
16692 };
16693
16694 workspace.open_project_item(
16695 pane,
16696 target_buffer.clone(),
16697 true,
16698 true,
16699 window,
16700 cx,
16701 )
16702 });
16703 target_editor.update(cx, |target_editor, cx| {
16704 // When selecting a definition in a different buffer, disable the nav history
16705 // to avoid creating a history entry at the previous cursor location.
16706 pane.update(cx, |pane, _| pane.disable_history());
16707 target_editor.go_to_singleton_buffer_range(range, window, cx);
16708 pane.update(cx, |pane, _| pane.enable_history());
16709 });
16710 });
16711 }
16712 Navigated::Yes
16713 })
16714 }
16715 })
16716 }
16717
16718 fn compute_target_location(
16719 &self,
16720 lsp_location: lsp::Location,
16721 server_id: LanguageServerId,
16722 window: &mut Window,
16723 cx: &mut Context<Self>,
16724 ) -> Task<anyhow::Result<Option<Location>>> {
16725 let Some(project) = self.project.clone() else {
16726 return Task::ready(Ok(None));
16727 };
16728
16729 cx.spawn_in(window, async move |editor, cx| {
16730 let location_task = editor.update(cx, |_, cx| {
16731 project.update(cx, |project, cx| {
16732 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16733 })
16734 })?;
16735 let location = Some({
16736 let target_buffer_handle = location_task.await.context("open local buffer")?;
16737 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16738 let target_start = target_buffer
16739 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16740 let target_end = target_buffer
16741 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16742 target_buffer.anchor_after(target_start)
16743 ..target_buffer.anchor_before(target_end)
16744 })?;
16745 Location {
16746 buffer: target_buffer_handle,
16747 range,
16748 }
16749 });
16750 Ok(location)
16751 })
16752 }
16753
16754 pub fn find_all_references(
16755 &mut self,
16756 _: &FindAllReferences,
16757 window: &mut Window,
16758 cx: &mut Context<Self>,
16759 ) -> Option<Task<Result<Navigated>>> {
16760 let selection = self.selections.newest::<usize>(cx);
16761 let multi_buffer = self.buffer.read(cx);
16762 let head = selection.head();
16763
16764 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16765 let head_anchor = multi_buffer_snapshot.anchor_at(
16766 head,
16767 if head < selection.tail() {
16768 Bias::Right
16769 } else {
16770 Bias::Left
16771 },
16772 );
16773
16774 match self
16775 .find_all_references_task_sources
16776 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16777 {
16778 Ok(_) => {
16779 log::info!(
16780 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16781 );
16782 return None;
16783 }
16784 Err(i) => {
16785 self.find_all_references_task_sources.insert(i, head_anchor);
16786 }
16787 }
16788
16789 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16790 let workspace = self.workspace()?;
16791 let project = workspace.read(cx).project().clone();
16792 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16793 Some(cx.spawn_in(window, async move |editor, cx| {
16794 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16795 if let Ok(i) = editor
16796 .find_all_references_task_sources
16797 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16798 {
16799 editor.find_all_references_task_sources.remove(i);
16800 }
16801 });
16802
16803 let Some(locations) = references.await? else {
16804 return anyhow::Ok(Navigated::No);
16805 };
16806 let mut locations = cx.update(|_, cx| {
16807 locations
16808 .into_iter()
16809 .map(|location| {
16810 let buffer = location.buffer.read(cx);
16811 (location.buffer, location.range.to_point(buffer))
16812 })
16813 .into_group_map()
16814 })?;
16815 if locations.is_empty() {
16816 return anyhow::Ok(Navigated::No);
16817 }
16818 for ranges in locations.values_mut() {
16819 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16820 ranges.dedup();
16821 }
16822
16823 workspace.update_in(cx, |workspace, window, cx| {
16824 let target = locations
16825 .iter()
16826 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16827 .map(|(buffer, location)| {
16828 buffer
16829 .read(cx)
16830 .text_for_range(location.clone())
16831 .collect::<String>()
16832 })
16833 .filter(|text| !text.contains('\n'))
16834 .unique()
16835 .take(3)
16836 .join(", ");
16837 let title = if target.is_empty() {
16838 "References".to_owned()
16839 } else {
16840 format!("References to {target}")
16841 };
16842 Self::open_locations_in_multibuffer(
16843 workspace,
16844 locations,
16845 title,
16846 false,
16847 MultibufferSelectionMode::First,
16848 window,
16849 cx,
16850 );
16851 Navigated::Yes
16852 })
16853 }))
16854 }
16855
16856 /// Opens a multibuffer with the given project locations in it
16857 pub fn open_locations_in_multibuffer(
16858 workspace: &mut Workspace,
16859 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16860 title: String,
16861 split: bool,
16862 multibuffer_selection_mode: MultibufferSelectionMode,
16863 window: &mut Window,
16864 cx: &mut Context<Workspace>,
16865 ) {
16866 if locations.is_empty() {
16867 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16868 return;
16869 }
16870
16871 let capability = workspace.project().read(cx).capability();
16872 let mut ranges = <Vec<Range<Anchor>>>::new();
16873
16874 // a key to find existing multibuffer editors with the same set of locations
16875 // to prevent us from opening more and more multibuffer tabs for searches and the like
16876 let mut key = (title.clone(), vec![]);
16877 let excerpt_buffer = cx.new(|cx| {
16878 let key = &mut key.1;
16879 let mut multibuffer = MultiBuffer::new(capability);
16880 for (buffer, mut ranges_for_buffer) in locations {
16881 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16882 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16883 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16884 PathKey::for_buffer(&buffer, cx),
16885 buffer.clone(),
16886 ranges_for_buffer,
16887 multibuffer_context_lines(cx),
16888 cx,
16889 );
16890 ranges.extend(new_ranges)
16891 }
16892
16893 multibuffer.with_title(title)
16894 });
16895 let existing = workspace.active_pane().update(cx, |pane, cx| {
16896 pane.items()
16897 .filter_map(|item| item.downcast::<Editor>())
16898 .find(|editor| {
16899 editor
16900 .read(cx)
16901 .lookup_key
16902 .as_ref()
16903 .and_then(|it| {
16904 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16905 })
16906 .is_some_and(|it| *it == key)
16907 })
16908 });
16909 let editor = existing.unwrap_or_else(|| {
16910 cx.new(|cx| {
16911 let mut editor = Editor::for_multibuffer(
16912 excerpt_buffer,
16913 Some(workspace.project().clone()),
16914 window,
16915 cx,
16916 );
16917 editor.lookup_key = Some(Box::new(key));
16918 editor
16919 })
16920 });
16921 editor.update(cx, |editor, cx| {
16922 match multibuffer_selection_mode {
16923 MultibufferSelectionMode::First => {
16924 if let Some(first_range) = ranges.first() {
16925 editor.change_selections(
16926 SelectionEffects::no_scroll(),
16927 window,
16928 cx,
16929 |selections| {
16930 selections.clear_disjoint();
16931 selections
16932 .select_anchor_ranges(std::iter::once(first_range.clone()));
16933 },
16934 );
16935 }
16936 editor.highlight_background::<Self>(
16937 &ranges,
16938 |theme| theme.colors().editor_highlighted_line_background,
16939 cx,
16940 );
16941 }
16942 MultibufferSelectionMode::All => {
16943 editor.change_selections(
16944 SelectionEffects::no_scroll(),
16945 window,
16946 cx,
16947 |selections| {
16948 selections.clear_disjoint();
16949 selections.select_anchor_ranges(ranges);
16950 },
16951 );
16952 }
16953 }
16954 editor.register_buffers_with_language_servers(cx);
16955 });
16956
16957 let item = Box::new(editor);
16958 let item_id = item.item_id();
16959
16960 if split {
16961 let pane = workspace.adjacent_pane(window, cx);
16962 workspace.add_item(pane, item, None, true, true, window, cx);
16963 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16964 let (preview_item_id, preview_item_idx) =
16965 workspace.active_pane().read_with(cx, |pane, _| {
16966 (pane.preview_item_id(), pane.preview_item_idx())
16967 });
16968
16969 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16970
16971 if let Some(preview_item_id) = preview_item_id {
16972 workspace.active_pane().update(cx, |pane, cx| {
16973 pane.remove_item(preview_item_id, false, false, window, cx);
16974 });
16975 }
16976 } else {
16977 workspace.add_item_to_active_pane(item, None, true, window, cx);
16978 }
16979 workspace.active_pane().update(cx, |pane, cx| {
16980 pane.set_preview_item_id(Some(item_id), cx);
16981 });
16982 }
16983
16984 pub fn rename(
16985 &mut self,
16986 _: &Rename,
16987 window: &mut Window,
16988 cx: &mut Context<Self>,
16989 ) -> Option<Task<Result<()>>> {
16990 use language::ToOffset as _;
16991
16992 let provider = self.semantics_provider.clone()?;
16993 let selection = self.selections.newest_anchor().clone();
16994 let (cursor_buffer, cursor_buffer_position) = self
16995 .buffer
16996 .read(cx)
16997 .text_anchor_for_position(selection.head(), cx)?;
16998 let (tail_buffer, cursor_buffer_position_end) = self
16999 .buffer
17000 .read(cx)
17001 .text_anchor_for_position(selection.tail(), cx)?;
17002 if tail_buffer != cursor_buffer {
17003 return None;
17004 }
17005
17006 let snapshot = cursor_buffer.read(cx).snapshot();
17007 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17008 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17009 let prepare_rename = provider
17010 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17011 .unwrap_or_else(|| Task::ready(Ok(None)));
17012 drop(snapshot);
17013
17014 Some(cx.spawn_in(window, async move |this, cx| {
17015 let rename_range = if let Some(range) = prepare_rename.await? {
17016 Some(range)
17017 } else {
17018 this.update(cx, |this, cx| {
17019 let buffer = this.buffer.read(cx).snapshot(cx);
17020 let mut buffer_highlights = this
17021 .document_highlights_for_position(selection.head(), &buffer)
17022 .filter(|highlight| {
17023 highlight.start.excerpt_id == selection.head().excerpt_id
17024 && highlight.end.excerpt_id == selection.head().excerpt_id
17025 });
17026 buffer_highlights
17027 .next()
17028 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17029 })?
17030 };
17031 if let Some(rename_range) = rename_range {
17032 this.update_in(cx, |this, window, cx| {
17033 let snapshot = cursor_buffer.read(cx).snapshot();
17034 let rename_buffer_range = rename_range.to_offset(&snapshot);
17035 let cursor_offset_in_rename_range =
17036 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17037 let cursor_offset_in_rename_range_end =
17038 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17039
17040 this.take_rename(false, window, cx);
17041 let buffer = this.buffer.read(cx).read(cx);
17042 let cursor_offset = selection.head().to_offset(&buffer);
17043 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17044 let rename_end = rename_start + rename_buffer_range.len();
17045 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17046 let mut old_highlight_id = None;
17047 let old_name: Arc<str> = buffer
17048 .chunks(rename_start..rename_end, true)
17049 .map(|chunk| {
17050 if old_highlight_id.is_none() {
17051 old_highlight_id = chunk.syntax_highlight_id;
17052 }
17053 chunk.text
17054 })
17055 .collect::<String>()
17056 .into();
17057
17058 drop(buffer);
17059
17060 // Position the selection in the rename editor so that it matches the current selection.
17061 this.show_local_selections = false;
17062 let rename_editor = cx.new(|cx| {
17063 let mut editor = Editor::single_line(window, cx);
17064 editor.buffer.update(cx, |buffer, cx| {
17065 buffer.edit([(0..0, old_name.clone())], None, cx)
17066 });
17067 let rename_selection_range = match cursor_offset_in_rename_range
17068 .cmp(&cursor_offset_in_rename_range_end)
17069 {
17070 Ordering::Equal => {
17071 editor.select_all(&SelectAll, window, cx);
17072 return editor;
17073 }
17074 Ordering::Less => {
17075 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17076 }
17077 Ordering::Greater => {
17078 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17079 }
17080 };
17081 if rename_selection_range.end > old_name.len() {
17082 editor.select_all(&SelectAll, window, cx);
17083 } else {
17084 editor.change_selections(Default::default(), window, cx, |s| {
17085 s.select_ranges([rename_selection_range]);
17086 });
17087 }
17088 editor
17089 });
17090 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17091 if e == &EditorEvent::Focused {
17092 cx.emit(EditorEvent::FocusedIn)
17093 }
17094 })
17095 .detach();
17096
17097 let write_highlights =
17098 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17099 let read_highlights =
17100 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17101 let ranges = write_highlights
17102 .iter()
17103 .flat_map(|(_, ranges)| ranges.iter())
17104 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17105 .cloned()
17106 .collect();
17107
17108 this.highlight_text::<Rename>(
17109 ranges,
17110 HighlightStyle {
17111 fade_out: Some(0.6),
17112 ..Default::default()
17113 },
17114 cx,
17115 );
17116 let rename_focus_handle = rename_editor.focus_handle(cx);
17117 window.focus(&rename_focus_handle);
17118 let block_id = this.insert_blocks(
17119 [BlockProperties {
17120 style: BlockStyle::Flex,
17121 placement: BlockPlacement::Below(range.start),
17122 height: Some(1),
17123 render: Arc::new({
17124 let rename_editor = rename_editor.clone();
17125 move |cx: &mut BlockContext| {
17126 let mut text_style = cx.editor_style.text.clone();
17127 if let Some(highlight_style) = old_highlight_id
17128 .and_then(|h| h.style(&cx.editor_style.syntax))
17129 {
17130 text_style = text_style.highlight(highlight_style);
17131 }
17132 div()
17133 .block_mouse_except_scroll()
17134 .pl(cx.anchor_x)
17135 .child(EditorElement::new(
17136 &rename_editor,
17137 EditorStyle {
17138 background: cx.theme().system().transparent,
17139 local_player: cx.editor_style.local_player,
17140 text: text_style,
17141 scrollbar_width: cx.editor_style.scrollbar_width,
17142 syntax: cx.editor_style.syntax.clone(),
17143 status: cx.editor_style.status.clone(),
17144 inlay_hints_style: HighlightStyle {
17145 font_weight: Some(FontWeight::BOLD),
17146 ..make_inlay_hints_style(cx.app)
17147 },
17148 edit_prediction_styles: make_suggestion_styles(
17149 cx.app,
17150 ),
17151 ..EditorStyle::default()
17152 },
17153 ))
17154 .into_any_element()
17155 }
17156 }),
17157 priority: 0,
17158 }],
17159 Some(Autoscroll::fit()),
17160 cx,
17161 )[0];
17162 this.pending_rename = Some(RenameState {
17163 range,
17164 old_name,
17165 editor: rename_editor,
17166 block_id,
17167 });
17168 })?;
17169 }
17170
17171 Ok(())
17172 }))
17173 }
17174
17175 pub fn confirm_rename(
17176 &mut self,
17177 _: &ConfirmRename,
17178 window: &mut Window,
17179 cx: &mut Context<Self>,
17180 ) -> Option<Task<Result<()>>> {
17181 let rename = self.take_rename(false, window, cx)?;
17182 let workspace = self.workspace()?.downgrade();
17183 let (buffer, start) = self
17184 .buffer
17185 .read(cx)
17186 .text_anchor_for_position(rename.range.start, cx)?;
17187 let (end_buffer, _) = self
17188 .buffer
17189 .read(cx)
17190 .text_anchor_for_position(rename.range.end, cx)?;
17191 if buffer != end_buffer {
17192 return None;
17193 }
17194
17195 let old_name = rename.old_name;
17196 let new_name = rename.editor.read(cx).text(cx);
17197
17198 let rename = self.semantics_provider.as_ref()?.perform_rename(
17199 &buffer,
17200 start,
17201 new_name.clone(),
17202 cx,
17203 )?;
17204
17205 Some(cx.spawn_in(window, async move |editor, cx| {
17206 let project_transaction = rename.await?;
17207 Self::open_project_transaction(
17208 &editor,
17209 workspace,
17210 project_transaction,
17211 format!("Rename: {} → {}", old_name, new_name),
17212 cx,
17213 )
17214 .await?;
17215
17216 editor.update(cx, |editor, cx| {
17217 editor.refresh_document_highlights(cx);
17218 })?;
17219 Ok(())
17220 }))
17221 }
17222
17223 fn take_rename(
17224 &mut self,
17225 moving_cursor: bool,
17226 window: &mut Window,
17227 cx: &mut Context<Self>,
17228 ) -> Option<RenameState> {
17229 let rename = self.pending_rename.take()?;
17230 if rename.editor.focus_handle(cx).is_focused(window) {
17231 window.focus(&self.focus_handle);
17232 }
17233
17234 self.remove_blocks(
17235 [rename.block_id].into_iter().collect(),
17236 Some(Autoscroll::fit()),
17237 cx,
17238 );
17239 self.clear_highlights::<Rename>(cx);
17240 self.show_local_selections = true;
17241
17242 if moving_cursor {
17243 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17244 editor.selections.newest::<usize>(cx).head()
17245 });
17246
17247 // Update the selection to match the position of the selection inside
17248 // the rename editor.
17249 let snapshot = self.buffer.read(cx).read(cx);
17250 let rename_range = rename.range.to_offset(&snapshot);
17251 let cursor_in_editor = snapshot
17252 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17253 .min(rename_range.end);
17254 drop(snapshot);
17255
17256 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17257 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17258 });
17259 } else {
17260 self.refresh_document_highlights(cx);
17261 }
17262
17263 Some(rename)
17264 }
17265
17266 pub fn pending_rename(&self) -> Option<&RenameState> {
17267 self.pending_rename.as_ref()
17268 }
17269
17270 fn format(
17271 &mut self,
17272 _: &Format,
17273 window: &mut Window,
17274 cx: &mut Context<Self>,
17275 ) -> Option<Task<Result<()>>> {
17276 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17277
17278 let project = match &self.project {
17279 Some(project) => project.clone(),
17280 None => return None,
17281 };
17282
17283 Some(self.perform_format(
17284 project,
17285 FormatTrigger::Manual,
17286 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17287 window,
17288 cx,
17289 ))
17290 }
17291
17292 fn format_selections(
17293 &mut self,
17294 _: &FormatSelections,
17295 window: &mut Window,
17296 cx: &mut Context<Self>,
17297 ) -> Option<Task<Result<()>>> {
17298 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17299
17300 let project = match &self.project {
17301 Some(project) => project.clone(),
17302 None => return None,
17303 };
17304
17305 let ranges = self
17306 .selections
17307 .all_adjusted(cx)
17308 .into_iter()
17309 .map(|selection| selection.range())
17310 .collect_vec();
17311
17312 Some(self.perform_format(
17313 project,
17314 FormatTrigger::Manual,
17315 FormatTarget::Ranges(ranges),
17316 window,
17317 cx,
17318 ))
17319 }
17320
17321 fn perform_format(
17322 &mut self,
17323 project: Entity<Project>,
17324 trigger: FormatTrigger,
17325 target: FormatTarget,
17326 window: &mut Window,
17327 cx: &mut Context<Self>,
17328 ) -> Task<Result<()>> {
17329 let buffer = self.buffer.clone();
17330 let (buffers, target) = match target {
17331 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17332 FormatTarget::Ranges(selection_ranges) => {
17333 let multi_buffer = buffer.read(cx);
17334 let snapshot = multi_buffer.read(cx);
17335 let mut buffers = HashSet::default();
17336 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17337 BTreeMap::new();
17338 for selection_range in selection_ranges {
17339 for (buffer, buffer_range, _) in
17340 snapshot.range_to_buffer_ranges(selection_range)
17341 {
17342 let buffer_id = buffer.remote_id();
17343 let start = buffer.anchor_before(buffer_range.start);
17344 let end = buffer.anchor_after(buffer_range.end);
17345 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17346 buffer_id_to_ranges
17347 .entry(buffer_id)
17348 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17349 .or_insert_with(|| vec![start..end]);
17350 }
17351 }
17352 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17353 }
17354 };
17355
17356 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17357 let selections_prev = transaction_id_prev
17358 .and_then(|transaction_id_prev| {
17359 // default to selections as they were after the last edit, if we have them,
17360 // instead of how they are now.
17361 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17362 // will take you back to where you made the last edit, instead of staying where you scrolled
17363 self.selection_history
17364 .transaction(transaction_id_prev)
17365 .map(|t| t.0.clone())
17366 })
17367 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17368
17369 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17370 let format = project.update(cx, |project, cx| {
17371 project.format(buffers, target, true, trigger, cx)
17372 });
17373
17374 cx.spawn_in(window, async move |editor, cx| {
17375 let transaction = futures::select_biased! {
17376 transaction = format.log_err().fuse() => transaction,
17377 () = timeout => {
17378 log::warn!("timed out waiting for formatting");
17379 None
17380 }
17381 };
17382
17383 buffer
17384 .update(cx, |buffer, cx| {
17385 if let Some(transaction) = transaction
17386 && !buffer.is_singleton()
17387 {
17388 buffer.push_transaction(&transaction.0, cx);
17389 }
17390 cx.notify();
17391 })
17392 .ok();
17393
17394 if let Some(transaction_id_now) =
17395 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17396 {
17397 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17398 if has_new_transaction {
17399 _ = editor.update(cx, |editor, _| {
17400 editor
17401 .selection_history
17402 .insert_transaction(transaction_id_now, selections_prev);
17403 });
17404 }
17405 }
17406
17407 Ok(())
17408 })
17409 }
17410
17411 fn organize_imports(
17412 &mut self,
17413 _: &OrganizeImports,
17414 window: &mut Window,
17415 cx: &mut Context<Self>,
17416 ) -> Option<Task<Result<()>>> {
17417 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17418 let project = match &self.project {
17419 Some(project) => project.clone(),
17420 None => return None,
17421 };
17422 Some(self.perform_code_action_kind(
17423 project,
17424 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17425 window,
17426 cx,
17427 ))
17428 }
17429
17430 fn perform_code_action_kind(
17431 &mut self,
17432 project: Entity<Project>,
17433 kind: CodeActionKind,
17434 window: &mut Window,
17435 cx: &mut Context<Self>,
17436 ) -> Task<Result<()>> {
17437 let buffer = self.buffer.clone();
17438 let buffers = buffer.read(cx).all_buffers();
17439 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17440 let apply_action = project.update(cx, |project, cx| {
17441 project.apply_code_action_kind(buffers, kind, true, cx)
17442 });
17443 cx.spawn_in(window, async move |_, cx| {
17444 let transaction = futures::select_biased! {
17445 () = timeout => {
17446 log::warn!("timed out waiting for executing code action");
17447 None
17448 }
17449 transaction = apply_action.log_err().fuse() => transaction,
17450 };
17451 buffer
17452 .update(cx, |buffer, cx| {
17453 // check if we need this
17454 if let Some(transaction) = transaction
17455 && !buffer.is_singleton()
17456 {
17457 buffer.push_transaction(&transaction.0, cx);
17458 }
17459 cx.notify();
17460 })
17461 .ok();
17462 Ok(())
17463 })
17464 }
17465
17466 pub fn restart_language_server(
17467 &mut self,
17468 _: &RestartLanguageServer,
17469 _: &mut Window,
17470 cx: &mut Context<Self>,
17471 ) {
17472 if let Some(project) = self.project.clone() {
17473 self.buffer.update(cx, |multi_buffer, cx| {
17474 project.update(cx, |project, cx| {
17475 project.restart_language_servers_for_buffers(
17476 multi_buffer.all_buffers().into_iter().collect(),
17477 HashSet::default(),
17478 cx,
17479 );
17480 });
17481 })
17482 }
17483 }
17484
17485 pub fn stop_language_server(
17486 &mut self,
17487 _: &StopLanguageServer,
17488 _: &mut Window,
17489 cx: &mut Context<Self>,
17490 ) {
17491 if let Some(project) = self.project.clone() {
17492 self.buffer.update(cx, |multi_buffer, cx| {
17493 project.update(cx, |project, cx| {
17494 project.stop_language_servers_for_buffers(
17495 multi_buffer.all_buffers().into_iter().collect(),
17496 HashSet::default(),
17497 cx,
17498 );
17499 cx.emit(project::Event::RefreshInlayHints);
17500 });
17501 });
17502 }
17503 }
17504
17505 fn cancel_language_server_work(
17506 workspace: &mut Workspace,
17507 _: &actions::CancelLanguageServerWork,
17508 _: &mut Window,
17509 cx: &mut Context<Workspace>,
17510 ) {
17511 let project = workspace.project();
17512 let buffers = workspace
17513 .active_item(cx)
17514 .and_then(|item| item.act_as::<Editor>(cx))
17515 .map_or(HashSet::default(), |editor| {
17516 editor.read(cx).buffer.read(cx).all_buffers()
17517 });
17518 project.update(cx, |project, cx| {
17519 project.cancel_language_server_work_for_buffers(buffers, cx);
17520 });
17521 }
17522
17523 fn show_character_palette(
17524 &mut self,
17525 _: &ShowCharacterPalette,
17526 window: &mut Window,
17527 _: &mut Context<Self>,
17528 ) {
17529 window.show_character_palette();
17530 }
17531
17532 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17533 if !self.diagnostics_enabled() {
17534 return;
17535 }
17536
17537 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17538 let buffer = self.buffer.read(cx).snapshot(cx);
17539 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17540 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17541 let is_valid = buffer
17542 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17543 .any(|entry| {
17544 entry.diagnostic.is_primary
17545 && !entry.range.is_empty()
17546 && entry.range.start == primary_range_start
17547 && entry.diagnostic.message == active_diagnostics.active_message
17548 });
17549
17550 if !is_valid {
17551 self.dismiss_diagnostics(cx);
17552 }
17553 }
17554 }
17555
17556 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17557 match &self.active_diagnostics {
17558 ActiveDiagnostic::Group(group) => Some(group),
17559 _ => None,
17560 }
17561 }
17562
17563 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17564 if !self.diagnostics_enabled() {
17565 return;
17566 }
17567 self.dismiss_diagnostics(cx);
17568 self.active_diagnostics = ActiveDiagnostic::All;
17569 }
17570
17571 fn activate_diagnostics(
17572 &mut self,
17573 buffer_id: BufferId,
17574 diagnostic: DiagnosticEntryRef<'_, usize>,
17575 window: &mut Window,
17576 cx: &mut Context<Self>,
17577 ) {
17578 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17579 return;
17580 }
17581 self.dismiss_diagnostics(cx);
17582 let snapshot = self.snapshot(window, cx);
17583 let buffer = self.buffer.read(cx).snapshot(cx);
17584 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17585 return;
17586 };
17587
17588 let diagnostic_group = buffer
17589 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17590 .collect::<Vec<_>>();
17591
17592 let blocks =
17593 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17594
17595 let blocks = self.display_map.update(cx, |display_map, cx| {
17596 display_map.insert_blocks(blocks, cx).into_iter().collect()
17597 });
17598 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17599 active_range: buffer.anchor_before(diagnostic.range.start)
17600 ..buffer.anchor_after(diagnostic.range.end),
17601 active_message: diagnostic.diagnostic.message.clone(),
17602 group_id: diagnostic.diagnostic.group_id,
17603 blocks,
17604 });
17605 cx.notify();
17606 }
17607
17608 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17609 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17610 return;
17611 };
17612
17613 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17614 if let ActiveDiagnostic::Group(group) = prev {
17615 self.display_map.update(cx, |display_map, cx| {
17616 display_map.remove_blocks(group.blocks, cx);
17617 });
17618 cx.notify();
17619 }
17620 }
17621
17622 /// Disable inline diagnostics rendering for this editor.
17623 pub fn disable_inline_diagnostics(&mut self) {
17624 self.inline_diagnostics_enabled = false;
17625 self.inline_diagnostics_update = Task::ready(());
17626 self.inline_diagnostics.clear();
17627 }
17628
17629 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17630 self.diagnostics_enabled = false;
17631 self.dismiss_diagnostics(cx);
17632 self.inline_diagnostics_update = Task::ready(());
17633 self.inline_diagnostics.clear();
17634 }
17635
17636 pub fn disable_word_completions(&mut self) {
17637 self.word_completions_enabled = false;
17638 }
17639
17640 pub fn diagnostics_enabled(&self) -> bool {
17641 self.diagnostics_enabled && self.mode.is_full()
17642 }
17643
17644 pub fn inline_diagnostics_enabled(&self) -> bool {
17645 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17646 }
17647
17648 pub fn show_inline_diagnostics(&self) -> bool {
17649 self.show_inline_diagnostics
17650 }
17651
17652 pub fn toggle_inline_diagnostics(
17653 &mut self,
17654 _: &ToggleInlineDiagnostics,
17655 window: &mut Window,
17656 cx: &mut Context<Editor>,
17657 ) {
17658 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17659 self.refresh_inline_diagnostics(false, window, cx);
17660 }
17661
17662 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17663 self.diagnostics_max_severity = severity;
17664 self.display_map.update(cx, |display_map, _| {
17665 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17666 });
17667 }
17668
17669 pub fn toggle_diagnostics(
17670 &mut self,
17671 _: &ToggleDiagnostics,
17672 window: &mut Window,
17673 cx: &mut Context<Editor>,
17674 ) {
17675 if !self.diagnostics_enabled() {
17676 return;
17677 }
17678
17679 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17680 EditorSettings::get_global(cx)
17681 .diagnostics_max_severity
17682 .filter(|severity| severity != &DiagnosticSeverity::Off)
17683 .unwrap_or(DiagnosticSeverity::Hint)
17684 } else {
17685 DiagnosticSeverity::Off
17686 };
17687 self.set_max_diagnostics_severity(new_severity, cx);
17688 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17689 self.active_diagnostics = ActiveDiagnostic::None;
17690 self.inline_diagnostics_update = Task::ready(());
17691 self.inline_diagnostics.clear();
17692 } else {
17693 self.refresh_inline_diagnostics(false, window, cx);
17694 }
17695
17696 cx.notify();
17697 }
17698
17699 pub fn toggle_minimap(
17700 &mut self,
17701 _: &ToggleMinimap,
17702 window: &mut Window,
17703 cx: &mut Context<Editor>,
17704 ) {
17705 if self.supports_minimap(cx) {
17706 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17707 }
17708 }
17709
17710 fn refresh_inline_diagnostics(
17711 &mut self,
17712 debounce: bool,
17713 window: &mut Window,
17714 cx: &mut Context<Self>,
17715 ) {
17716 let max_severity = ProjectSettings::get_global(cx)
17717 .diagnostics
17718 .inline
17719 .max_severity
17720 .unwrap_or(self.diagnostics_max_severity);
17721
17722 if !self.inline_diagnostics_enabled()
17723 || !self.show_inline_diagnostics
17724 || max_severity == DiagnosticSeverity::Off
17725 {
17726 self.inline_diagnostics_update = Task::ready(());
17727 self.inline_diagnostics.clear();
17728 return;
17729 }
17730
17731 let debounce_ms = ProjectSettings::get_global(cx)
17732 .diagnostics
17733 .inline
17734 .update_debounce_ms;
17735 let debounce = if debounce && debounce_ms > 0 {
17736 Some(Duration::from_millis(debounce_ms))
17737 } else {
17738 None
17739 };
17740 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17741 if let Some(debounce) = debounce {
17742 cx.background_executor().timer(debounce).await;
17743 }
17744 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17745 editor
17746 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17747 .ok()
17748 }) else {
17749 return;
17750 };
17751
17752 let new_inline_diagnostics = cx
17753 .background_spawn(async move {
17754 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17755 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17756 let message = diagnostic_entry
17757 .diagnostic
17758 .message
17759 .split_once('\n')
17760 .map(|(line, _)| line)
17761 .map(SharedString::new)
17762 .unwrap_or_else(|| {
17763 SharedString::new(&*diagnostic_entry.diagnostic.message)
17764 });
17765 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17766 let (Ok(i) | Err(i)) = inline_diagnostics
17767 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17768 inline_diagnostics.insert(
17769 i,
17770 (
17771 start_anchor,
17772 InlineDiagnostic {
17773 message,
17774 group_id: diagnostic_entry.diagnostic.group_id,
17775 start: diagnostic_entry.range.start.to_point(&snapshot),
17776 is_primary: diagnostic_entry.diagnostic.is_primary,
17777 severity: diagnostic_entry.diagnostic.severity,
17778 },
17779 ),
17780 );
17781 }
17782 inline_diagnostics
17783 })
17784 .await;
17785
17786 editor
17787 .update(cx, |editor, cx| {
17788 editor.inline_diagnostics = new_inline_diagnostics;
17789 cx.notify();
17790 })
17791 .ok();
17792 });
17793 }
17794
17795 fn pull_diagnostics(
17796 &mut self,
17797 buffer_id: Option<BufferId>,
17798 window: &Window,
17799 cx: &mut Context<Self>,
17800 ) -> Option<()> {
17801 if !self.mode().is_full() {
17802 return None;
17803 }
17804 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17805 .diagnostics
17806 .lsp_pull_diagnostics;
17807 if !pull_diagnostics_settings.enabled {
17808 return None;
17809 }
17810 let project = self.project()?.downgrade();
17811 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17812 let mut buffers = self.buffer.read(cx).all_buffers();
17813 if let Some(buffer_id) = buffer_id {
17814 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17815 }
17816
17817 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17818 cx.background_executor().timer(debounce).await;
17819
17820 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17821 buffers
17822 .into_iter()
17823 .filter_map(|buffer| {
17824 project
17825 .update(cx, |project, cx| {
17826 project.lsp_store().update(cx, |lsp_store, cx| {
17827 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17828 })
17829 })
17830 .ok()
17831 })
17832 .collect::<FuturesUnordered<_>>()
17833 }) else {
17834 return;
17835 };
17836
17837 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17838 match pull_task {
17839 Ok(()) => {
17840 if editor
17841 .update_in(cx, |editor, window, cx| {
17842 editor.update_diagnostics_state(window, cx);
17843 })
17844 .is_err()
17845 {
17846 return;
17847 }
17848 }
17849 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17850 }
17851 }
17852 });
17853
17854 Some(())
17855 }
17856
17857 pub fn set_selections_from_remote(
17858 &mut self,
17859 selections: Vec<Selection<Anchor>>,
17860 pending_selection: Option<Selection<Anchor>>,
17861 window: &mut Window,
17862 cx: &mut Context<Self>,
17863 ) {
17864 let old_cursor_position = self.selections.newest_anchor().head();
17865 self.selections.change_with(cx, |s| {
17866 s.select_anchors(selections);
17867 if let Some(pending_selection) = pending_selection {
17868 s.set_pending(pending_selection, SelectMode::Character);
17869 } else {
17870 s.clear_pending();
17871 }
17872 });
17873 self.selections_did_change(
17874 false,
17875 &old_cursor_position,
17876 SelectionEffects::default(),
17877 window,
17878 cx,
17879 );
17880 }
17881
17882 pub fn transact(
17883 &mut self,
17884 window: &mut Window,
17885 cx: &mut Context<Self>,
17886 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17887 ) -> Option<TransactionId> {
17888 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17889 this.start_transaction_at(Instant::now(), window, cx);
17890 update(this, window, cx);
17891 this.end_transaction_at(Instant::now(), cx)
17892 })
17893 }
17894
17895 pub fn start_transaction_at(
17896 &mut self,
17897 now: Instant,
17898 window: &mut Window,
17899 cx: &mut Context<Self>,
17900 ) -> Option<TransactionId> {
17901 self.end_selection(window, cx);
17902 if let Some(tx_id) = self
17903 .buffer
17904 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17905 {
17906 self.selection_history
17907 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17908 cx.emit(EditorEvent::TransactionBegun {
17909 transaction_id: tx_id,
17910 });
17911 Some(tx_id)
17912 } else {
17913 None
17914 }
17915 }
17916
17917 pub fn end_transaction_at(
17918 &mut self,
17919 now: Instant,
17920 cx: &mut Context<Self>,
17921 ) -> Option<TransactionId> {
17922 if let Some(transaction_id) = self
17923 .buffer
17924 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17925 {
17926 if let Some((_, end_selections)) =
17927 self.selection_history.transaction_mut(transaction_id)
17928 {
17929 *end_selections = Some(self.selections.disjoint_anchors_arc());
17930 } else {
17931 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17932 }
17933
17934 cx.emit(EditorEvent::Edited { transaction_id });
17935 Some(transaction_id)
17936 } else {
17937 None
17938 }
17939 }
17940
17941 pub fn modify_transaction_selection_history(
17942 &mut self,
17943 transaction_id: TransactionId,
17944 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17945 ) -> bool {
17946 self.selection_history
17947 .transaction_mut(transaction_id)
17948 .map(modify)
17949 .is_some()
17950 }
17951
17952 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17953 if self.selection_mark_mode {
17954 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17955 s.move_with(|_, sel| {
17956 sel.collapse_to(sel.head(), SelectionGoal::None);
17957 });
17958 })
17959 }
17960 self.selection_mark_mode = true;
17961 cx.notify();
17962 }
17963
17964 pub fn swap_selection_ends(
17965 &mut self,
17966 _: &actions::SwapSelectionEnds,
17967 window: &mut Window,
17968 cx: &mut Context<Self>,
17969 ) {
17970 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17971 s.move_with(|_, sel| {
17972 if sel.start != sel.end {
17973 sel.reversed = !sel.reversed
17974 }
17975 });
17976 });
17977 self.request_autoscroll(Autoscroll::newest(), cx);
17978 cx.notify();
17979 }
17980
17981 pub fn toggle_focus(
17982 workspace: &mut Workspace,
17983 _: &actions::ToggleFocus,
17984 window: &mut Window,
17985 cx: &mut Context<Workspace>,
17986 ) {
17987 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17988 return;
17989 };
17990 workspace.activate_item(&item, true, true, window, cx);
17991 }
17992
17993 pub fn toggle_fold(
17994 &mut self,
17995 _: &actions::ToggleFold,
17996 window: &mut Window,
17997 cx: &mut Context<Self>,
17998 ) {
17999 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18000 let selection = self.selections.newest::<Point>(cx);
18001
18002 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18003 let range = if selection.is_empty() {
18004 let point = selection.head().to_display_point(&display_map);
18005 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18006 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18007 .to_point(&display_map);
18008 start..end
18009 } else {
18010 selection.range()
18011 };
18012 if display_map.folds_in_range(range).next().is_some() {
18013 self.unfold_lines(&Default::default(), window, cx)
18014 } else {
18015 self.fold(&Default::default(), window, cx)
18016 }
18017 } else {
18018 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18019 let buffer_ids: HashSet<_> = self
18020 .selections
18021 .disjoint_anchor_ranges()
18022 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18023 .collect();
18024
18025 let should_unfold = buffer_ids
18026 .iter()
18027 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18028
18029 for buffer_id in buffer_ids {
18030 if should_unfold {
18031 self.unfold_buffer(buffer_id, cx);
18032 } else {
18033 self.fold_buffer(buffer_id, cx);
18034 }
18035 }
18036 }
18037 }
18038
18039 pub fn toggle_fold_recursive(
18040 &mut self,
18041 _: &actions::ToggleFoldRecursive,
18042 window: &mut Window,
18043 cx: &mut Context<Self>,
18044 ) {
18045 let selection = self.selections.newest::<Point>(cx);
18046
18047 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18048 let range = if selection.is_empty() {
18049 let point = selection.head().to_display_point(&display_map);
18050 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18051 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18052 .to_point(&display_map);
18053 start..end
18054 } else {
18055 selection.range()
18056 };
18057 if display_map.folds_in_range(range).next().is_some() {
18058 self.unfold_recursive(&Default::default(), window, cx)
18059 } else {
18060 self.fold_recursive(&Default::default(), window, cx)
18061 }
18062 }
18063
18064 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18065 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18066 let mut to_fold = Vec::new();
18067 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18068 let selections = self.selections.all_adjusted(cx);
18069
18070 for selection in selections {
18071 let range = selection.range().sorted();
18072 let buffer_start_row = range.start.row;
18073
18074 if range.start.row != range.end.row {
18075 let mut found = false;
18076 let mut row = range.start.row;
18077 while row <= range.end.row {
18078 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18079 {
18080 found = true;
18081 row = crease.range().end.row + 1;
18082 to_fold.push(crease);
18083 } else {
18084 row += 1
18085 }
18086 }
18087 if found {
18088 continue;
18089 }
18090 }
18091
18092 for row in (0..=range.start.row).rev() {
18093 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18094 && crease.range().end.row >= buffer_start_row
18095 {
18096 to_fold.push(crease);
18097 if row <= range.start.row {
18098 break;
18099 }
18100 }
18101 }
18102 }
18103
18104 self.fold_creases(to_fold, true, window, cx);
18105 } else {
18106 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18107 let buffer_ids = self
18108 .selections
18109 .disjoint_anchor_ranges()
18110 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18111 .collect::<HashSet<_>>();
18112 for buffer_id in buffer_ids {
18113 self.fold_buffer(buffer_id, cx);
18114 }
18115 }
18116 }
18117
18118 pub fn toggle_fold_all(
18119 &mut self,
18120 _: &actions::ToggleFoldAll,
18121 window: &mut Window,
18122 cx: &mut Context<Self>,
18123 ) {
18124 if self.buffer.read(cx).is_singleton() {
18125 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18126 let has_folds = display_map
18127 .folds_in_range(0..display_map.buffer_snapshot().len())
18128 .next()
18129 .is_some();
18130
18131 if has_folds {
18132 self.unfold_all(&actions::UnfoldAll, window, cx);
18133 } else {
18134 self.fold_all(&actions::FoldAll, window, cx);
18135 }
18136 } else {
18137 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18138 let should_unfold = buffer_ids
18139 .iter()
18140 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18141
18142 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18143 editor
18144 .update_in(cx, |editor, _, cx| {
18145 for buffer_id in buffer_ids {
18146 if should_unfold {
18147 editor.unfold_buffer(buffer_id, cx);
18148 } else {
18149 editor.fold_buffer(buffer_id, cx);
18150 }
18151 }
18152 })
18153 .ok();
18154 });
18155 }
18156 }
18157
18158 fn fold_at_level(
18159 &mut self,
18160 fold_at: &FoldAtLevel,
18161 window: &mut Window,
18162 cx: &mut Context<Self>,
18163 ) {
18164 if !self.buffer.read(cx).is_singleton() {
18165 return;
18166 }
18167
18168 let fold_at_level = fold_at.0;
18169 let snapshot = self.buffer.read(cx).snapshot(cx);
18170 let mut to_fold = Vec::new();
18171 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18172
18173 let row_ranges_to_keep: Vec<Range<u32>> = self
18174 .selections
18175 .all::<Point>(cx)
18176 .into_iter()
18177 .map(|sel| sel.start.row..sel.end.row)
18178 .collect();
18179
18180 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18181 while start_row < end_row {
18182 match self
18183 .snapshot(window, cx)
18184 .crease_for_buffer_row(MultiBufferRow(start_row))
18185 {
18186 Some(crease) => {
18187 let nested_start_row = crease.range().start.row + 1;
18188 let nested_end_row = crease.range().end.row;
18189
18190 if current_level < fold_at_level {
18191 stack.push((nested_start_row, nested_end_row, current_level + 1));
18192 } else if current_level == fold_at_level {
18193 // Fold iff there is no selection completely contained within the fold region
18194 if !row_ranges_to_keep.iter().any(|selection| {
18195 selection.end >= nested_start_row
18196 && selection.start <= nested_end_row
18197 }) {
18198 to_fold.push(crease);
18199 }
18200 }
18201
18202 start_row = nested_end_row + 1;
18203 }
18204 None => start_row += 1,
18205 }
18206 }
18207 }
18208
18209 self.fold_creases(to_fold, true, window, cx);
18210 }
18211
18212 pub fn fold_at_level_1(
18213 &mut self,
18214 _: &actions::FoldAtLevel1,
18215 window: &mut Window,
18216 cx: &mut Context<Self>,
18217 ) {
18218 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18219 }
18220
18221 pub fn fold_at_level_2(
18222 &mut self,
18223 _: &actions::FoldAtLevel2,
18224 window: &mut Window,
18225 cx: &mut Context<Self>,
18226 ) {
18227 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18228 }
18229
18230 pub fn fold_at_level_3(
18231 &mut self,
18232 _: &actions::FoldAtLevel3,
18233 window: &mut Window,
18234 cx: &mut Context<Self>,
18235 ) {
18236 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18237 }
18238
18239 pub fn fold_at_level_4(
18240 &mut self,
18241 _: &actions::FoldAtLevel4,
18242 window: &mut Window,
18243 cx: &mut Context<Self>,
18244 ) {
18245 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18246 }
18247
18248 pub fn fold_at_level_5(
18249 &mut self,
18250 _: &actions::FoldAtLevel5,
18251 window: &mut Window,
18252 cx: &mut Context<Self>,
18253 ) {
18254 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18255 }
18256
18257 pub fn fold_at_level_6(
18258 &mut self,
18259 _: &actions::FoldAtLevel6,
18260 window: &mut Window,
18261 cx: &mut Context<Self>,
18262 ) {
18263 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18264 }
18265
18266 pub fn fold_at_level_7(
18267 &mut self,
18268 _: &actions::FoldAtLevel7,
18269 window: &mut Window,
18270 cx: &mut Context<Self>,
18271 ) {
18272 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18273 }
18274
18275 pub fn fold_at_level_8(
18276 &mut self,
18277 _: &actions::FoldAtLevel8,
18278 window: &mut Window,
18279 cx: &mut Context<Self>,
18280 ) {
18281 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18282 }
18283
18284 pub fn fold_at_level_9(
18285 &mut self,
18286 _: &actions::FoldAtLevel9,
18287 window: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) {
18290 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18291 }
18292
18293 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18294 if self.buffer.read(cx).is_singleton() {
18295 let mut fold_ranges = Vec::new();
18296 let snapshot = self.buffer.read(cx).snapshot(cx);
18297
18298 for row in 0..snapshot.max_row().0 {
18299 if let Some(foldable_range) = self
18300 .snapshot(window, cx)
18301 .crease_for_buffer_row(MultiBufferRow(row))
18302 {
18303 fold_ranges.push(foldable_range);
18304 }
18305 }
18306
18307 self.fold_creases(fold_ranges, true, window, cx);
18308 } else {
18309 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18310 editor
18311 .update_in(cx, |editor, _, cx| {
18312 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18313 editor.fold_buffer(buffer_id, cx);
18314 }
18315 })
18316 .ok();
18317 });
18318 }
18319 }
18320
18321 pub fn fold_function_bodies(
18322 &mut self,
18323 _: &actions::FoldFunctionBodies,
18324 window: &mut Window,
18325 cx: &mut Context<Self>,
18326 ) {
18327 let snapshot = self.buffer.read(cx).snapshot(cx);
18328
18329 let ranges = snapshot
18330 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18331 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18332 .collect::<Vec<_>>();
18333
18334 let creases = ranges
18335 .into_iter()
18336 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18337 .collect();
18338
18339 self.fold_creases(creases, true, window, cx);
18340 }
18341
18342 pub fn fold_recursive(
18343 &mut self,
18344 _: &actions::FoldRecursive,
18345 window: &mut Window,
18346 cx: &mut Context<Self>,
18347 ) {
18348 let mut to_fold = Vec::new();
18349 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18350 let selections = self.selections.all_adjusted(cx);
18351
18352 for selection in selections {
18353 let range = selection.range().sorted();
18354 let buffer_start_row = range.start.row;
18355
18356 if range.start.row != range.end.row {
18357 let mut found = false;
18358 for row in range.start.row..=range.end.row {
18359 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18360 found = true;
18361 to_fold.push(crease);
18362 }
18363 }
18364 if found {
18365 continue;
18366 }
18367 }
18368
18369 for row in (0..=range.start.row).rev() {
18370 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18371 if crease.range().end.row >= buffer_start_row {
18372 to_fold.push(crease);
18373 } else {
18374 break;
18375 }
18376 }
18377 }
18378 }
18379
18380 self.fold_creases(to_fold, true, window, cx);
18381 }
18382
18383 pub fn fold_at(
18384 &mut self,
18385 buffer_row: MultiBufferRow,
18386 window: &mut Window,
18387 cx: &mut Context<Self>,
18388 ) {
18389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18390
18391 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18392 let autoscroll = self
18393 .selections
18394 .all::<Point>(cx)
18395 .iter()
18396 .any(|selection| crease.range().overlaps(&selection.range()));
18397
18398 self.fold_creases(vec![crease], autoscroll, window, cx);
18399 }
18400 }
18401
18402 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18403 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18404 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18405 let buffer = display_map.buffer_snapshot();
18406 let selections = self.selections.all::<Point>(cx);
18407 let ranges = selections
18408 .iter()
18409 .map(|s| {
18410 let range = s.display_range(&display_map).sorted();
18411 let mut start = range.start.to_point(&display_map);
18412 let mut end = range.end.to_point(&display_map);
18413 start.column = 0;
18414 end.column = buffer.line_len(MultiBufferRow(end.row));
18415 start..end
18416 })
18417 .collect::<Vec<_>>();
18418
18419 self.unfold_ranges(&ranges, true, true, cx);
18420 } else {
18421 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18422 let buffer_ids = self
18423 .selections
18424 .disjoint_anchor_ranges()
18425 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18426 .collect::<HashSet<_>>();
18427 for buffer_id in buffer_ids {
18428 self.unfold_buffer(buffer_id, cx);
18429 }
18430 }
18431 }
18432
18433 pub fn unfold_recursive(
18434 &mut self,
18435 _: &UnfoldRecursive,
18436 _window: &mut Window,
18437 cx: &mut Context<Self>,
18438 ) {
18439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18440 let selections = self.selections.all::<Point>(cx);
18441 let ranges = selections
18442 .iter()
18443 .map(|s| {
18444 let mut range = s.display_range(&display_map).sorted();
18445 *range.start.column_mut() = 0;
18446 *range.end.column_mut() = display_map.line_len(range.end.row());
18447 let start = range.start.to_point(&display_map);
18448 let end = range.end.to_point(&display_map);
18449 start..end
18450 })
18451 .collect::<Vec<_>>();
18452
18453 self.unfold_ranges(&ranges, true, true, cx);
18454 }
18455
18456 pub fn unfold_at(
18457 &mut self,
18458 buffer_row: MultiBufferRow,
18459 _window: &mut Window,
18460 cx: &mut Context<Self>,
18461 ) {
18462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18463
18464 let intersection_range = Point::new(buffer_row.0, 0)
18465 ..Point::new(
18466 buffer_row.0,
18467 display_map.buffer_snapshot().line_len(buffer_row),
18468 );
18469
18470 let autoscroll = self
18471 .selections
18472 .all::<Point>(cx)
18473 .iter()
18474 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18475
18476 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18477 }
18478
18479 pub fn unfold_all(
18480 &mut self,
18481 _: &actions::UnfoldAll,
18482 _window: &mut Window,
18483 cx: &mut Context<Self>,
18484 ) {
18485 if self.buffer.read(cx).is_singleton() {
18486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18487 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18488 } else {
18489 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18490 editor
18491 .update(cx, |editor, cx| {
18492 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18493 editor.unfold_buffer(buffer_id, cx);
18494 }
18495 })
18496 .ok();
18497 });
18498 }
18499 }
18500
18501 pub fn fold_selected_ranges(
18502 &mut self,
18503 _: &FoldSelectedRanges,
18504 window: &mut Window,
18505 cx: &mut Context<Self>,
18506 ) {
18507 let selections = self.selections.all_adjusted(cx);
18508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18509 let ranges = selections
18510 .into_iter()
18511 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18512 .collect::<Vec<_>>();
18513 self.fold_creases(ranges, true, window, cx);
18514 }
18515
18516 pub fn fold_ranges<T: ToOffset + Clone>(
18517 &mut self,
18518 ranges: Vec<Range<T>>,
18519 auto_scroll: bool,
18520 window: &mut Window,
18521 cx: &mut Context<Self>,
18522 ) {
18523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18524 let ranges = ranges
18525 .into_iter()
18526 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18527 .collect::<Vec<_>>();
18528 self.fold_creases(ranges, auto_scroll, window, cx);
18529 }
18530
18531 pub fn fold_creases<T: ToOffset + Clone>(
18532 &mut self,
18533 creases: Vec<Crease<T>>,
18534 auto_scroll: bool,
18535 _window: &mut Window,
18536 cx: &mut Context<Self>,
18537 ) {
18538 if creases.is_empty() {
18539 return;
18540 }
18541
18542 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18543
18544 if auto_scroll {
18545 self.request_autoscroll(Autoscroll::fit(), cx);
18546 }
18547
18548 cx.notify();
18549
18550 self.scrollbar_marker_state.dirty = true;
18551 self.folds_did_change(cx);
18552 }
18553
18554 /// Removes any folds whose ranges intersect any of the given ranges.
18555 pub fn unfold_ranges<T: ToOffset + Clone>(
18556 &mut self,
18557 ranges: &[Range<T>],
18558 inclusive: bool,
18559 auto_scroll: bool,
18560 cx: &mut Context<Self>,
18561 ) {
18562 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18563 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18564 });
18565 self.folds_did_change(cx);
18566 }
18567
18568 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18569 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18570 return;
18571 }
18572 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18573 self.display_map.update(cx, |display_map, cx| {
18574 display_map.fold_buffers([buffer_id], cx)
18575 });
18576 cx.emit(EditorEvent::BufferFoldToggled {
18577 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18578 folded: true,
18579 });
18580 cx.notify();
18581 }
18582
18583 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18584 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18585 return;
18586 }
18587 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18588 self.display_map.update(cx, |display_map, cx| {
18589 display_map.unfold_buffers([buffer_id], cx);
18590 });
18591 cx.emit(EditorEvent::BufferFoldToggled {
18592 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18593 folded: false,
18594 });
18595 cx.notify();
18596 }
18597
18598 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18599 self.display_map.read(cx).is_buffer_folded(buffer)
18600 }
18601
18602 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18603 self.display_map.read(cx).folded_buffers()
18604 }
18605
18606 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18607 self.display_map.update(cx, |display_map, cx| {
18608 display_map.disable_header_for_buffer(buffer_id, cx);
18609 });
18610 cx.notify();
18611 }
18612
18613 /// Removes any folds with the given ranges.
18614 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18615 &mut self,
18616 ranges: &[Range<T>],
18617 type_id: TypeId,
18618 auto_scroll: bool,
18619 cx: &mut Context<Self>,
18620 ) {
18621 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18622 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18623 });
18624 self.folds_did_change(cx);
18625 }
18626
18627 fn remove_folds_with<T: ToOffset + Clone>(
18628 &mut self,
18629 ranges: &[Range<T>],
18630 auto_scroll: bool,
18631 cx: &mut Context<Self>,
18632 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18633 ) {
18634 if ranges.is_empty() {
18635 return;
18636 }
18637
18638 let mut buffers_affected = HashSet::default();
18639 let multi_buffer = self.buffer().read(cx);
18640 for range in ranges {
18641 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18642 buffers_affected.insert(buffer.read(cx).remote_id());
18643 };
18644 }
18645
18646 self.display_map.update(cx, update);
18647
18648 if auto_scroll {
18649 self.request_autoscroll(Autoscroll::fit(), cx);
18650 }
18651
18652 cx.notify();
18653 self.scrollbar_marker_state.dirty = true;
18654 self.active_indent_guides_state.dirty = true;
18655 }
18656
18657 pub fn update_renderer_widths(
18658 &mut self,
18659 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18660 cx: &mut Context<Self>,
18661 ) -> bool {
18662 self.display_map
18663 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18664 }
18665
18666 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18667 self.display_map.read(cx).fold_placeholder.clone()
18668 }
18669
18670 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18671 self.buffer.update(cx, |buffer, cx| {
18672 buffer.set_all_diff_hunks_expanded(cx);
18673 });
18674 }
18675
18676 pub fn expand_all_diff_hunks(
18677 &mut self,
18678 _: &ExpandAllDiffHunks,
18679 _window: &mut Window,
18680 cx: &mut Context<Self>,
18681 ) {
18682 self.buffer.update(cx, |buffer, cx| {
18683 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18684 });
18685 }
18686
18687 pub fn toggle_selected_diff_hunks(
18688 &mut self,
18689 _: &ToggleSelectedDiffHunks,
18690 _window: &mut Window,
18691 cx: &mut Context<Self>,
18692 ) {
18693 let ranges: Vec<_> = self
18694 .selections
18695 .disjoint_anchors()
18696 .iter()
18697 .map(|s| s.range())
18698 .collect();
18699 self.toggle_diff_hunks_in_ranges(ranges, cx);
18700 }
18701
18702 pub fn diff_hunks_in_ranges<'a>(
18703 &'a self,
18704 ranges: &'a [Range<Anchor>],
18705 buffer: &'a MultiBufferSnapshot,
18706 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18707 ranges.iter().flat_map(move |range| {
18708 let end_excerpt_id = range.end.excerpt_id;
18709 let range = range.to_point(buffer);
18710 let mut peek_end = range.end;
18711 if range.end.row < buffer.max_row().0 {
18712 peek_end = Point::new(range.end.row + 1, 0);
18713 }
18714 buffer
18715 .diff_hunks_in_range(range.start..peek_end)
18716 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18717 })
18718 }
18719
18720 pub fn has_stageable_diff_hunks_in_ranges(
18721 &self,
18722 ranges: &[Range<Anchor>],
18723 snapshot: &MultiBufferSnapshot,
18724 ) -> bool {
18725 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18726 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18727 }
18728
18729 pub fn toggle_staged_selected_diff_hunks(
18730 &mut self,
18731 _: &::git::ToggleStaged,
18732 _: &mut Window,
18733 cx: &mut Context<Self>,
18734 ) {
18735 let snapshot = self.buffer.read(cx).snapshot(cx);
18736 let ranges: Vec<_> = self
18737 .selections
18738 .disjoint_anchors()
18739 .iter()
18740 .map(|s| s.range())
18741 .collect();
18742 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18743 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18744 }
18745
18746 pub fn set_render_diff_hunk_controls(
18747 &mut self,
18748 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18749 cx: &mut Context<Self>,
18750 ) {
18751 self.render_diff_hunk_controls = render_diff_hunk_controls;
18752 cx.notify();
18753 }
18754
18755 pub fn stage_and_next(
18756 &mut self,
18757 _: &::git::StageAndNext,
18758 window: &mut Window,
18759 cx: &mut Context<Self>,
18760 ) {
18761 self.do_stage_or_unstage_and_next(true, window, cx);
18762 }
18763
18764 pub fn unstage_and_next(
18765 &mut self,
18766 _: &::git::UnstageAndNext,
18767 window: &mut Window,
18768 cx: &mut Context<Self>,
18769 ) {
18770 self.do_stage_or_unstage_and_next(false, window, cx);
18771 }
18772
18773 pub fn stage_or_unstage_diff_hunks(
18774 &mut self,
18775 stage: bool,
18776 ranges: Vec<Range<Anchor>>,
18777 cx: &mut Context<Self>,
18778 ) {
18779 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18780 cx.spawn(async move |this, cx| {
18781 task.await?;
18782 this.update(cx, |this, cx| {
18783 let snapshot = this.buffer.read(cx).snapshot(cx);
18784 let chunk_by = this
18785 .diff_hunks_in_ranges(&ranges, &snapshot)
18786 .chunk_by(|hunk| hunk.buffer_id);
18787 for (buffer_id, hunks) in &chunk_by {
18788 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18789 }
18790 })
18791 })
18792 .detach_and_log_err(cx);
18793 }
18794
18795 fn save_buffers_for_ranges_if_needed(
18796 &mut self,
18797 ranges: &[Range<Anchor>],
18798 cx: &mut Context<Editor>,
18799 ) -> Task<Result<()>> {
18800 let multibuffer = self.buffer.read(cx);
18801 let snapshot = multibuffer.read(cx);
18802 let buffer_ids: HashSet<_> = ranges
18803 .iter()
18804 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18805 .collect();
18806 drop(snapshot);
18807
18808 let mut buffers = HashSet::default();
18809 for buffer_id in buffer_ids {
18810 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18811 let buffer = buffer_entity.read(cx);
18812 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18813 {
18814 buffers.insert(buffer_entity);
18815 }
18816 }
18817 }
18818
18819 if let Some(project) = &self.project {
18820 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18821 } else {
18822 Task::ready(Ok(()))
18823 }
18824 }
18825
18826 fn do_stage_or_unstage_and_next(
18827 &mut self,
18828 stage: bool,
18829 window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) {
18832 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18833
18834 if ranges.iter().any(|range| range.start != range.end) {
18835 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18836 return;
18837 }
18838
18839 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18840 let snapshot = self.snapshot(window, cx);
18841 let position = self.selections.newest::<Point>(cx).head();
18842 let mut row = snapshot
18843 .buffer_snapshot()
18844 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18845 .find(|hunk| hunk.row_range.start.0 > position.row)
18846 .map(|hunk| hunk.row_range.start);
18847
18848 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18849 // Outside of the project diff editor, wrap around to the beginning.
18850 if !all_diff_hunks_expanded {
18851 row = row.or_else(|| {
18852 snapshot
18853 .buffer_snapshot()
18854 .diff_hunks_in_range(Point::zero()..position)
18855 .find(|hunk| hunk.row_range.end.0 < position.row)
18856 .map(|hunk| hunk.row_range.start)
18857 });
18858 }
18859
18860 if let Some(row) = row {
18861 let destination = Point::new(row.0, 0);
18862 let autoscroll = Autoscroll::center();
18863
18864 self.unfold_ranges(&[destination..destination], false, false, cx);
18865 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18866 s.select_ranges([destination..destination]);
18867 });
18868 }
18869 }
18870
18871 fn do_stage_or_unstage(
18872 &self,
18873 stage: bool,
18874 buffer_id: BufferId,
18875 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18876 cx: &mut App,
18877 ) -> Option<()> {
18878 let project = self.project()?;
18879 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18880 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18881 let buffer_snapshot = buffer.read(cx).snapshot();
18882 let file_exists = buffer_snapshot
18883 .file()
18884 .is_some_and(|file| file.disk_state().exists());
18885 diff.update(cx, |diff, cx| {
18886 diff.stage_or_unstage_hunks(
18887 stage,
18888 &hunks
18889 .map(|hunk| buffer_diff::DiffHunk {
18890 buffer_range: hunk.buffer_range,
18891 diff_base_byte_range: hunk.diff_base_byte_range,
18892 secondary_status: hunk.secondary_status,
18893 range: Point::zero()..Point::zero(), // unused
18894 })
18895 .collect::<Vec<_>>(),
18896 &buffer_snapshot,
18897 file_exists,
18898 cx,
18899 )
18900 });
18901 None
18902 }
18903
18904 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18905 let ranges: Vec<_> = self
18906 .selections
18907 .disjoint_anchors()
18908 .iter()
18909 .map(|s| s.range())
18910 .collect();
18911 self.buffer
18912 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18913 }
18914
18915 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18916 self.buffer.update(cx, |buffer, cx| {
18917 let ranges = vec![Anchor::min()..Anchor::max()];
18918 if !buffer.all_diff_hunks_expanded()
18919 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18920 {
18921 buffer.collapse_diff_hunks(ranges, cx);
18922 true
18923 } else {
18924 false
18925 }
18926 })
18927 }
18928
18929 fn toggle_diff_hunks_in_ranges(
18930 &mut self,
18931 ranges: Vec<Range<Anchor>>,
18932 cx: &mut Context<Editor>,
18933 ) {
18934 self.buffer.update(cx, |buffer, cx| {
18935 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18936 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18937 })
18938 }
18939
18940 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18941 self.buffer.update(cx, |buffer, cx| {
18942 let snapshot = buffer.snapshot(cx);
18943 let excerpt_id = range.end.excerpt_id;
18944 let point_range = range.to_point(&snapshot);
18945 let expand = !buffer.single_hunk_is_expanded(range, cx);
18946 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18947 })
18948 }
18949
18950 pub(crate) fn apply_all_diff_hunks(
18951 &mut self,
18952 _: &ApplyAllDiffHunks,
18953 window: &mut Window,
18954 cx: &mut Context<Self>,
18955 ) {
18956 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18957
18958 let buffers = self.buffer.read(cx).all_buffers();
18959 for branch_buffer in buffers {
18960 branch_buffer.update(cx, |branch_buffer, cx| {
18961 branch_buffer.merge_into_base(Vec::new(), cx);
18962 });
18963 }
18964
18965 if let Some(project) = self.project.clone() {
18966 self.save(
18967 SaveOptions {
18968 format: true,
18969 autosave: false,
18970 },
18971 project,
18972 window,
18973 cx,
18974 )
18975 .detach_and_log_err(cx);
18976 }
18977 }
18978
18979 pub(crate) fn apply_selected_diff_hunks(
18980 &mut self,
18981 _: &ApplyDiffHunk,
18982 window: &mut Window,
18983 cx: &mut Context<Self>,
18984 ) {
18985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18986 let snapshot = self.snapshot(window, cx);
18987 let hunks = snapshot.hunks_for_ranges(
18988 self.selections
18989 .all(cx)
18990 .into_iter()
18991 .map(|selection| selection.range()),
18992 );
18993 let mut ranges_by_buffer = HashMap::default();
18994 self.transact(window, cx, |editor, _window, cx| {
18995 for hunk in hunks {
18996 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18997 ranges_by_buffer
18998 .entry(buffer.clone())
18999 .or_insert_with(Vec::new)
19000 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19001 }
19002 }
19003
19004 for (buffer, ranges) in ranges_by_buffer {
19005 buffer.update(cx, |buffer, cx| {
19006 buffer.merge_into_base(ranges, cx);
19007 });
19008 }
19009 });
19010
19011 if let Some(project) = self.project.clone() {
19012 self.save(
19013 SaveOptions {
19014 format: true,
19015 autosave: false,
19016 },
19017 project,
19018 window,
19019 cx,
19020 )
19021 .detach_and_log_err(cx);
19022 }
19023 }
19024
19025 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19026 if hovered != self.gutter_hovered {
19027 self.gutter_hovered = hovered;
19028 cx.notify();
19029 }
19030 }
19031
19032 pub fn insert_blocks(
19033 &mut self,
19034 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19035 autoscroll: Option<Autoscroll>,
19036 cx: &mut Context<Self>,
19037 ) -> Vec<CustomBlockId> {
19038 let blocks = self
19039 .display_map
19040 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19041 if let Some(autoscroll) = autoscroll {
19042 self.request_autoscroll(autoscroll, cx);
19043 }
19044 cx.notify();
19045 blocks
19046 }
19047
19048 pub fn resize_blocks(
19049 &mut self,
19050 heights: HashMap<CustomBlockId, u32>,
19051 autoscroll: Option<Autoscroll>,
19052 cx: &mut Context<Self>,
19053 ) {
19054 self.display_map
19055 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19056 if let Some(autoscroll) = autoscroll {
19057 self.request_autoscroll(autoscroll, cx);
19058 }
19059 cx.notify();
19060 }
19061
19062 pub fn replace_blocks(
19063 &mut self,
19064 renderers: HashMap<CustomBlockId, RenderBlock>,
19065 autoscroll: Option<Autoscroll>,
19066 cx: &mut Context<Self>,
19067 ) {
19068 self.display_map
19069 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19070 if let Some(autoscroll) = autoscroll {
19071 self.request_autoscroll(autoscroll, cx);
19072 }
19073 cx.notify();
19074 }
19075
19076 pub fn remove_blocks(
19077 &mut self,
19078 block_ids: HashSet<CustomBlockId>,
19079 autoscroll: Option<Autoscroll>,
19080 cx: &mut Context<Self>,
19081 ) {
19082 self.display_map.update(cx, |display_map, cx| {
19083 display_map.remove_blocks(block_ids, cx)
19084 });
19085 if let Some(autoscroll) = autoscroll {
19086 self.request_autoscroll(autoscroll, cx);
19087 }
19088 cx.notify();
19089 }
19090
19091 pub fn row_for_block(
19092 &self,
19093 block_id: CustomBlockId,
19094 cx: &mut Context<Self>,
19095 ) -> Option<DisplayRow> {
19096 self.display_map
19097 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19098 }
19099
19100 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19101 self.focused_block = Some(focused_block);
19102 }
19103
19104 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19105 self.focused_block.take()
19106 }
19107
19108 pub fn insert_creases(
19109 &mut self,
19110 creases: impl IntoIterator<Item = Crease<Anchor>>,
19111 cx: &mut Context<Self>,
19112 ) -> Vec<CreaseId> {
19113 self.display_map
19114 .update(cx, |map, cx| map.insert_creases(creases, cx))
19115 }
19116
19117 pub fn remove_creases(
19118 &mut self,
19119 ids: impl IntoIterator<Item = CreaseId>,
19120 cx: &mut Context<Self>,
19121 ) -> Vec<(CreaseId, Range<Anchor>)> {
19122 self.display_map
19123 .update(cx, |map, cx| map.remove_creases(ids, cx))
19124 }
19125
19126 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19127 self.display_map
19128 .update(cx, |map, cx| map.snapshot(cx))
19129 .longest_row()
19130 }
19131
19132 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19133 self.display_map
19134 .update(cx, |map, cx| map.snapshot(cx))
19135 .max_point()
19136 }
19137
19138 pub fn text(&self, cx: &App) -> String {
19139 self.buffer.read(cx).read(cx).text()
19140 }
19141
19142 pub fn is_empty(&self, cx: &App) -> bool {
19143 self.buffer.read(cx).read(cx).is_empty()
19144 }
19145
19146 pub fn text_option(&self, cx: &App) -> Option<String> {
19147 let text = self.text(cx);
19148 let text = text.trim();
19149
19150 if text.is_empty() {
19151 return None;
19152 }
19153
19154 Some(text.to_string())
19155 }
19156
19157 pub fn set_text(
19158 &mut self,
19159 text: impl Into<Arc<str>>,
19160 window: &mut Window,
19161 cx: &mut Context<Self>,
19162 ) {
19163 self.transact(window, cx, |this, _, cx| {
19164 this.buffer
19165 .read(cx)
19166 .as_singleton()
19167 .expect("you can only call set_text on editors for singleton buffers")
19168 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19169 });
19170 }
19171
19172 pub fn display_text(&self, cx: &mut App) -> String {
19173 self.display_map
19174 .update(cx, |map, cx| map.snapshot(cx))
19175 .text()
19176 }
19177
19178 fn create_minimap(
19179 &self,
19180 minimap_settings: MinimapSettings,
19181 window: &mut Window,
19182 cx: &mut Context<Self>,
19183 ) -> Option<Entity<Self>> {
19184 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19185 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19186 }
19187
19188 fn initialize_new_minimap(
19189 &self,
19190 minimap_settings: MinimapSettings,
19191 window: &mut Window,
19192 cx: &mut Context<Self>,
19193 ) -> Entity<Self> {
19194 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19195
19196 let mut minimap = Editor::new_internal(
19197 EditorMode::Minimap {
19198 parent: cx.weak_entity(),
19199 },
19200 self.buffer.clone(),
19201 None,
19202 Some(self.display_map.clone()),
19203 window,
19204 cx,
19205 );
19206 minimap.scroll_manager.clone_state(&self.scroll_manager);
19207 minimap.set_text_style_refinement(TextStyleRefinement {
19208 font_size: Some(MINIMAP_FONT_SIZE),
19209 font_weight: Some(MINIMAP_FONT_WEIGHT),
19210 ..Default::default()
19211 });
19212 minimap.update_minimap_configuration(minimap_settings, cx);
19213 cx.new(|_| minimap)
19214 }
19215
19216 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19217 let current_line_highlight = minimap_settings
19218 .current_line_highlight
19219 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19220 self.set_current_line_highlight(Some(current_line_highlight));
19221 }
19222
19223 pub fn minimap(&self) -> Option<&Entity<Self>> {
19224 self.minimap
19225 .as_ref()
19226 .filter(|_| self.minimap_visibility.visible())
19227 }
19228
19229 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19230 let mut wrap_guides = smallvec![];
19231
19232 if self.show_wrap_guides == Some(false) {
19233 return wrap_guides;
19234 }
19235
19236 let settings = self.buffer.read(cx).language_settings(cx);
19237 if settings.show_wrap_guides {
19238 match self.soft_wrap_mode(cx) {
19239 SoftWrap::Column(soft_wrap) => {
19240 wrap_guides.push((soft_wrap as usize, true));
19241 }
19242 SoftWrap::Bounded(soft_wrap) => {
19243 wrap_guides.push((soft_wrap as usize, true));
19244 }
19245 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19246 }
19247 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19248 }
19249
19250 wrap_guides
19251 }
19252
19253 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19254 let settings = self.buffer.read(cx).language_settings(cx);
19255 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19256 match mode {
19257 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19258 SoftWrap::None
19259 }
19260 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19261 language_settings::SoftWrap::PreferredLineLength => {
19262 SoftWrap::Column(settings.preferred_line_length)
19263 }
19264 language_settings::SoftWrap::Bounded => {
19265 SoftWrap::Bounded(settings.preferred_line_length)
19266 }
19267 }
19268 }
19269
19270 pub fn set_soft_wrap_mode(
19271 &mut self,
19272 mode: language_settings::SoftWrap,
19273
19274 cx: &mut Context<Self>,
19275 ) {
19276 self.soft_wrap_mode_override = Some(mode);
19277 cx.notify();
19278 }
19279
19280 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19281 self.hard_wrap = hard_wrap;
19282 cx.notify();
19283 }
19284
19285 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19286 self.text_style_refinement = Some(style);
19287 }
19288
19289 /// called by the Element so we know what style we were most recently rendered with.
19290 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19291 // We intentionally do not inform the display map about the minimap style
19292 // so that wrapping is not recalculated and stays consistent for the editor
19293 // and its linked minimap.
19294 if !self.mode.is_minimap() {
19295 let font = style.text.font();
19296 let font_size = style.text.font_size.to_pixels(window.rem_size());
19297 let display_map = self
19298 .placeholder_display_map
19299 .as_ref()
19300 .filter(|_| self.is_empty(cx))
19301 .unwrap_or(&self.display_map);
19302
19303 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19304 }
19305 self.style = Some(style);
19306 }
19307
19308 pub fn style(&self) -> Option<&EditorStyle> {
19309 self.style.as_ref()
19310 }
19311
19312 // Called by the element. This method is not designed to be called outside of the editor
19313 // element's layout code because it does not notify when rewrapping is computed synchronously.
19314 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19315 if self.is_empty(cx) {
19316 self.placeholder_display_map
19317 .as_ref()
19318 .map_or(false, |display_map| {
19319 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19320 })
19321 } else {
19322 self.display_map
19323 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19324 }
19325 }
19326
19327 pub fn set_soft_wrap(&mut self) {
19328 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19329 }
19330
19331 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19332 if self.soft_wrap_mode_override.is_some() {
19333 self.soft_wrap_mode_override.take();
19334 } else {
19335 let soft_wrap = match self.soft_wrap_mode(cx) {
19336 SoftWrap::GitDiff => return,
19337 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19338 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19339 language_settings::SoftWrap::None
19340 }
19341 };
19342 self.soft_wrap_mode_override = Some(soft_wrap);
19343 }
19344 cx.notify();
19345 }
19346
19347 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19348 let Some(workspace) = self.workspace() else {
19349 return;
19350 };
19351 let fs = workspace.read(cx).app_state().fs.clone();
19352 let current_show = TabBarSettings::get_global(cx).show;
19353 update_settings_file(fs, cx, move |setting, _| {
19354 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19355 });
19356 }
19357
19358 pub fn toggle_indent_guides(
19359 &mut self,
19360 _: &ToggleIndentGuides,
19361 _: &mut Window,
19362 cx: &mut Context<Self>,
19363 ) {
19364 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19365 self.buffer
19366 .read(cx)
19367 .language_settings(cx)
19368 .indent_guides
19369 .enabled
19370 });
19371 self.show_indent_guides = Some(!currently_enabled);
19372 cx.notify();
19373 }
19374
19375 fn should_show_indent_guides(&self) -> Option<bool> {
19376 self.show_indent_guides
19377 }
19378
19379 pub fn toggle_line_numbers(
19380 &mut self,
19381 _: &ToggleLineNumbers,
19382 _: &mut Window,
19383 cx: &mut Context<Self>,
19384 ) {
19385 let mut editor_settings = EditorSettings::get_global(cx).clone();
19386 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19387 EditorSettings::override_global(editor_settings, cx);
19388 }
19389
19390 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19391 if let Some(show_line_numbers) = self.show_line_numbers {
19392 return show_line_numbers;
19393 }
19394 EditorSettings::get_global(cx).gutter.line_numbers
19395 }
19396
19397 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19398 self.use_relative_line_numbers
19399 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19400 }
19401
19402 pub fn toggle_relative_line_numbers(
19403 &mut self,
19404 _: &ToggleRelativeLineNumbers,
19405 _: &mut Window,
19406 cx: &mut Context<Self>,
19407 ) {
19408 let is_relative = self.should_use_relative_line_numbers(cx);
19409 self.set_relative_line_number(Some(!is_relative), cx)
19410 }
19411
19412 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19413 self.use_relative_line_numbers = is_relative;
19414 cx.notify();
19415 }
19416
19417 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19418 self.show_gutter = show_gutter;
19419 cx.notify();
19420 }
19421
19422 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19423 self.show_scrollbars = ScrollbarAxes {
19424 horizontal: show,
19425 vertical: show,
19426 };
19427 cx.notify();
19428 }
19429
19430 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19431 self.show_scrollbars.vertical = show;
19432 cx.notify();
19433 }
19434
19435 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19436 self.show_scrollbars.horizontal = show;
19437 cx.notify();
19438 }
19439
19440 pub fn set_minimap_visibility(
19441 &mut self,
19442 minimap_visibility: MinimapVisibility,
19443 window: &mut Window,
19444 cx: &mut Context<Self>,
19445 ) {
19446 if self.minimap_visibility != minimap_visibility {
19447 if minimap_visibility.visible() && self.minimap.is_none() {
19448 let minimap_settings = EditorSettings::get_global(cx).minimap;
19449 self.minimap =
19450 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19451 }
19452 self.minimap_visibility = minimap_visibility;
19453 cx.notify();
19454 }
19455 }
19456
19457 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19458 self.set_show_scrollbars(false, cx);
19459 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19460 }
19461
19462 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19463 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19464 }
19465
19466 /// Normally the text in full mode and auto height editors is padded on the
19467 /// left side by roughly half a character width for improved hit testing.
19468 ///
19469 /// Use this method to disable this for cases where this is not wanted (e.g.
19470 /// if you want to align the editor text with some other text above or below)
19471 /// or if you want to add this padding to single-line editors.
19472 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19473 self.offset_content = offset_content;
19474 cx.notify();
19475 }
19476
19477 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19478 self.show_line_numbers = Some(show_line_numbers);
19479 cx.notify();
19480 }
19481
19482 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19483 self.disable_expand_excerpt_buttons = true;
19484 cx.notify();
19485 }
19486
19487 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19488 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19489 cx.notify();
19490 }
19491
19492 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19493 self.show_code_actions = Some(show_code_actions);
19494 cx.notify();
19495 }
19496
19497 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19498 self.show_runnables = Some(show_runnables);
19499 cx.notify();
19500 }
19501
19502 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19503 self.show_breakpoints = Some(show_breakpoints);
19504 cx.notify();
19505 }
19506
19507 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19508 if self.display_map.read(cx).masked != masked {
19509 self.display_map.update(cx, |map, _| map.masked = masked);
19510 }
19511 cx.notify()
19512 }
19513
19514 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19515 self.show_wrap_guides = Some(show_wrap_guides);
19516 cx.notify();
19517 }
19518
19519 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19520 self.show_indent_guides = Some(show_indent_guides);
19521 cx.notify();
19522 }
19523
19524 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19525 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19526 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19527 && let Some(dir) = file.abs_path(cx).parent()
19528 {
19529 return Some(dir.to_owned());
19530 }
19531 }
19532
19533 None
19534 }
19535
19536 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19537 self.active_excerpt(cx)?
19538 .1
19539 .read(cx)
19540 .file()
19541 .and_then(|f| f.as_local())
19542 }
19543
19544 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19545 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19546 let buffer = buffer.read(cx);
19547 if let Some(project_path) = buffer.project_path(cx) {
19548 let project = self.project()?.read(cx);
19549 project.absolute_path(&project_path, cx)
19550 } else {
19551 buffer
19552 .file()
19553 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19554 }
19555 })
19556 }
19557
19558 pub fn reveal_in_finder(
19559 &mut self,
19560 _: &RevealInFileManager,
19561 _window: &mut Window,
19562 cx: &mut Context<Self>,
19563 ) {
19564 if let Some(target) = self.target_file(cx) {
19565 cx.reveal_path(&target.abs_path(cx));
19566 }
19567 }
19568
19569 pub fn copy_path(
19570 &mut self,
19571 _: &zed_actions::workspace::CopyPath,
19572 _window: &mut Window,
19573 cx: &mut Context<Self>,
19574 ) {
19575 if let Some(path) = self.target_file_abs_path(cx)
19576 && let Some(path) = path.to_str()
19577 {
19578 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19579 } else {
19580 cx.propagate();
19581 }
19582 }
19583
19584 pub fn copy_relative_path(
19585 &mut self,
19586 _: &zed_actions::workspace::CopyRelativePath,
19587 _window: &mut Window,
19588 cx: &mut Context<Self>,
19589 ) {
19590 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19591 let project = self.project()?.read(cx);
19592 let path = buffer.read(cx).file()?.path();
19593 let path = path.display(project.path_style(cx));
19594 Some(path)
19595 }) {
19596 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19597 } else {
19598 cx.propagate();
19599 }
19600 }
19601
19602 /// Returns the project path for the editor's buffer, if any buffer is
19603 /// opened in the editor.
19604 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19605 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19606 buffer.read(cx).project_path(cx)
19607 } else {
19608 None
19609 }
19610 }
19611
19612 // Returns true if the editor handled a go-to-line request
19613 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19614 maybe!({
19615 let breakpoint_store = self.breakpoint_store.as_ref()?;
19616
19617 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19618 else {
19619 self.clear_row_highlights::<ActiveDebugLine>();
19620 return None;
19621 };
19622
19623 let position = active_stack_frame.position;
19624 let buffer_id = position.buffer_id?;
19625 let snapshot = self
19626 .project
19627 .as_ref()?
19628 .read(cx)
19629 .buffer_for_id(buffer_id, cx)?
19630 .read(cx)
19631 .snapshot();
19632
19633 let mut handled = false;
19634 for (id, ExcerptRange { context, .. }) in
19635 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19636 {
19637 if context.start.cmp(&position, &snapshot).is_ge()
19638 || context.end.cmp(&position, &snapshot).is_lt()
19639 {
19640 continue;
19641 }
19642 let snapshot = self.buffer.read(cx).snapshot(cx);
19643 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19644
19645 handled = true;
19646 self.clear_row_highlights::<ActiveDebugLine>();
19647
19648 self.go_to_line::<ActiveDebugLine>(
19649 multibuffer_anchor,
19650 Some(cx.theme().colors().editor_debugger_active_line_background),
19651 window,
19652 cx,
19653 );
19654
19655 cx.notify();
19656 }
19657
19658 handled.then_some(())
19659 })
19660 .is_some()
19661 }
19662
19663 pub fn copy_file_name_without_extension(
19664 &mut self,
19665 _: &CopyFileNameWithoutExtension,
19666 _: &mut Window,
19667 cx: &mut Context<Self>,
19668 ) {
19669 if let Some(file) = self.target_file(cx)
19670 && let Some(file_stem) = file.path().file_stem()
19671 {
19672 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19673 }
19674 }
19675
19676 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19677 if let Some(file) = self.target_file(cx)
19678 && let Some(name) = file.path().file_name()
19679 {
19680 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19681 }
19682 }
19683
19684 pub fn toggle_git_blame(
19685 &mut self,
19686 _: &::git::Blame,
19687 window: &mut Window,
19688 cx: &mut Context<Self>,
19689 ) {
19690 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19691
19692 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19693 self.start_git_blame(true, window, cx);
19694 }
19695
19696 cx.notify();
19697 }
19698
19699 pub fn toggle_git_blame_inline(
19700 &mut self,
19701 _: &ToggleGitBlameInline,
19702 window: &mut Window,
19703 cx: &mut Context<Self>,
19704 ) {
19705 self.toggle_git_blame_inline_internal(true, window, cx);
19706 cx.notify();
19707 }
19708
19709 pub fn open_git_blame_commit(
19710 &mut self,
19711 _: &OpenGitBlameCommit,
19712 window: &mut Window,
19713 cx: &mut Context<Self>,
19714 ) {
19715 self.open_git_blame_commit_internal(window, cx);
19716 }
19717
19718 fn open_git_blame_commit_internal(
19719 &mut self,
19720 window: &mut Window,
19721 cx: &mut Context<Self>,
19722 ) -> Option<()> {
19723 let blame = self.blame.as_ref()?;
19724 let snapshot = self.snapshot(window, cx);
19725 let cursor = self.selections.newest::<Point>(cx).head();
19726 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19727 let (_, blame_entry) = blame
19728 .update(cx, |blame, cx| {
19729 blame
19730 .blame_for_rows(
19731 &[RowInfo {
19732 buffer_id: Some(buffer.remote_id()),
19733 buffer_row: Some(point.row),
19734 ..Default::default()
19735 }],
19736 cx,
19737 )
19738 .next()
19739 })
19740 .flatten()?;
19741 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19742 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19743 let workspace = self.workspace()?.downgrade();
19744 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19745 None
19746 }
19747
19748 pub fn git_blame_inline_enabled(&self) -> bool {
19749 self.git_blame_inline_enabled
19750 }
19751
19752 pub fn toggle_selection_menu(
19753 &mut self,
19754 _: &ToggleSelectionMenu,
19755 _: &mut Window,
19756 cx: &mut Context<Self>,
19757 ) {
19758 self.show_selection_menu = self
19759 .show_selection_menu
19760 .map(|show_selections_menu| !show_selections_menu)
19761 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19762
19763 cx.notify();
19764 }
19765
19766 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19767 self.show_selection_menu
19768 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19769 }
19770
19771 fn start_git_blame(
19772 &mut self,
19773 user_triggered: bool,
19774 window: &mut Window,
19775 cx: &mut Context<Self>,
19776 ) {
19777 if let Some(project) = self.project() {
19778 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19779 && buffer.read(cx).file().is_none()
19780 {
19781 return;
19782 }
19783
19784 let focused = self.focus_handle(cx).contains_focused(window, cx);
19785
19786 let project = project.clone();
19787 let blame = cx
19788 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19789 self.blame_subscription =
19790 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19791 self.blame = Some(blame);
19792 }
19793 }
19794
19795 fn toggle_git_blame_inline_internal(
19796 &mut self,
19797 user_triggered: bool,
19798 window: &mut Window,
19799 cx: &mut Context<Self>,
19800 ) {
19801 if self.git_blame_inline_enabled {
19802 self.git_blame_inline_enabled = false;
19803 self.show_git_blame_inline = false;
19804 self.show_git_blame_inline_delay_task.take();
19805 } else {
19806 self.git_blame_inline_enabled = true;
19807 self.start_git_blame_inline(user_triggered, window, cx);
19808 }
19809
19810 cx.notify();
19811 }
19812
19813 fn start_git_blame_inline(
19814 &mut self,
19815 user_triggered: bool,
19816 window: &mut Window,
19817 cx: &mut Context<Self>,
19818 ) {
19819 self.start_git_blame(user_triggered, window, cx);
19820
19821 if ProjectSettings::get_global(cx)
19822 .git
19823 .inline_blame_delay()
19824 .is_some()
19825 {
19826 self.start_inline_blame_timer(window, cx);
19827 } else {
19828 self.show_git_blame_inline = true
19829 }
19830 }
19831
19832 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19833 self.blame.as_ref()
19834 }
19835
19836 pub fn show_git_blame_gutter(&self) -> bool {
19837 self.show_git_blame_gutter
19838 }
19839
19840 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19841 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19842 }
19843
19844 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19845 self.show_git_blame_inline
19846 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19847 && !self.newest_selection_head_on_empty_line(cx)
19848 && self.has_blame_entries(cx)
19849 }
19850
19851 fn has_blame_entries(&self, cx: &App) -> bool {
19852 self.blame()
19853 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19854 }
19855
19856 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19857 let cursor_anchor = self.selections.newest_anchor().head();
19858
19859 let snapshot = self.buffer.read(cx).snapshot(cx);
19860 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19861
19862 snapshot.line_len(buffer_row) == 0
19863 }
19864
19865 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19866 let buffer_and_selection = maybe!({
19867 let selection = self.selections.newest::<Point>(cx);
19868 let selection_range = selection.range();
19869
19870 let multi_buffer = self.buffer().read(cx);
19871 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19872 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19873
19874 let (buffer, range, _) = if selection.reversed {
19875 buffer_ranges.first()
19876 } else {
19877 buffer_ranges.last()
19878 }?;
19879
19880 let selection = text::ToPoint::to_point(&range.start, buffer).row
19881 ..text::ToPoint::to_point(&range.end, buffer).row;
19882 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19883 });
19884
19885 let Some((buffer, selection)) = buffer_and_selection else {
19886 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19887 };
19888
19889 let Some(project) = self.project() else {
19890 return Task::ready(Err(anyhow!("editor does not have project")));
19891 };
19892
19893 project.update(cx, |project, cx| {
19894 project.get_permalink_to_line(&buffer, selection, cx)
19895 })
19896 }
19897
19898 pub fn copy_permalink_to_line(
19899 &mut self,
19900 _: &CopyPermalinkToLine,
19901 window: &mut Window,
19902 cx: &mut Context<Self>,
19903 ) {
19904 let permalink_task = self.get_permalink_to_line(cx);
19905 let workspace = self.workspace();
19906
19907 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19908 Ok(permalink) => {
19909 cx.update(|_, cx| {
19910 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19911 })
19912 .ok();
19913 }
19914 Err(err) => {
19915 let message = format!("Failed to copy permalink: {err}");
19916
19917 anyhow::Result::<()>::Err(err).log_err();
19918
19919 if let Some(workspace) = workspace {
19920 workspace
19921 .update_in(cx, |workspace, _, cx| {
19922 struct CopyPermalinkToLine;
19923
19924 workspace.show_toast(
19925 Toast::new(
19926 NotificationId::unique::<CopyPermalinkToLine>(),
19927 message,
19928 ),
19929 cx,
19930 )
19931 })
19932 .ok();
19933 }
19934 }
19935 })
19936 .detach();
19937 }
19938
19939 pub fn copy_file_location(
19940 &mut self,
19941 _: &CopyFileLocation,
19942 _: &mut Window,
19943 cx: &mut Context<Self>,
19944 ) {
19945 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19946 if let Some(file) = self.target_file(cx) {
19947 let path = file.path().display(file.path_style(cx));
19948 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19949 }
19950 }
19951
19952 pub fn open_permalink_to_line(
19953 &mut self,
19954 _: &OpenPermalinkToLine,
19955 window: &mut Window,
19956 cx: &mut Context<Self>,
19957 ) {
19958 let permalink_task = self.get_permalink_to_line(cx);
19959 let workspace = self.workspace();
19960
19961 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19962 Ok(permalink) => {
19963 cx.update(|_, cx| {
19964 cx.open_url(permalink.as_ref());
19965 })
19966 .ok();
19967 }
19968 Err(err) => {
19969 let message = format!("Failed to open permalink: {err}");
19970
19971 anyhow::Result::<()>::Err(err).log_err();
19972
19973 if let Some(workspace) = workspace {
19974 workspace
19975 .update(cx, |workspace, cx| {
19976 struct OpenPermalinkToLine;
19977
19978 workspace.show_toast(
19979 Toast::new(
19980 NotificationId::unique::<OpenPermalinkToLine>(),
19981 message,
19982 ),
19983 cx,
19984 )
19985 })
19986 .ok();
19987 }
19988 }
19989 })
19990 .detach();
19991 }
19992
19993 pub fn insert_uuid_v4(
19994 &mut self,
19995 _: &InsertUuidV4,
19996 window: &mut Window,
19997 cx: &mut Context<Self>,
19998 ) {
19999 self.insert_uuid(UuidVersion::V4, window, cx);
20000 }
20001
20002 pub fn insert_uuid_v7(
20003 &mut self,
20004 _: &InsertUuidV7,
20005 window: &mut Window,
20006 cx: &mut Context<Self>,
20007 ) {
20008 self.insert_uuid(UuidVersion::V7, window, cx);
20009 }
20010
20011 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20013 self.transact(window, cx, |this, window, cx| {
20014 let edits = this
20015 .selections
20016 .all::<Point>(cx)
20017 .into_iter()
20018 .map(|selection| {
20019 let uuid = match version {
20020 UuidVersion::V4 => uuid::Uuid::new_v4(),
20021 UuidVersion::V7 => uuid::Uuid::now_v7(),
20022 };
20023
20024 (selection.range(), uuid.to_string())
20025 });
20026 this.edit(edits, cx);
20027 this.refresh_edit_prediction(true, false, window, cx);
20028 });
20029 }
20030
20031 pub fn open_selections_in_multibuffer(
20032 &mut self,
20033 _: &OpenSelectionsInMultibuffer,
20034 window: &mut Window,
20035 cx: &mut Context<Self>,
20036 ) {
20037 let multibuffer = self.buffer.read(cx);
20038
20039 let Some(buffer) = multibuffer.as_singleton() else {
20040 return;
20041 };
20042
20043 let Some(workspace) = self.workspace() else {
20044 return;
20045 };
20046
20047 let title = multibuffer.title(cx).to_string();
20048
20049 let locations = self
20050 .selections
20051 .all_anchors(cx)
20052 .iter()
20053 .map(|selection| {
20054 (
20055 buffer.clone(),
20056 (selection.start.text_anchor..selection.end.text_anchor)
20057 .to_point(buffer.read(cx)),
20058 )
20059 })
20060 .into_group_map();
20061
20062 cx.spawn_in(window, async move |_, cx| {
20063 workspace.update_in(cx, |workspace, window, cx| {
20064 Self::open_locations_in_multibuffer(
20065 workspace,
20066 locations,
20067 format!("Selections for '{title}'"),
20068 false,
20069 MultibufferSelectionMode::All,
20070 window,
20071 cx,
20072 );
20073 })
20074 })
20075 .detach();
20076 }
20077
20078 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20079 /// last highlight added will be used.
20080 ///
20081 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20082 pub fn highlight_rows<T: 'static>(
20083 &mut self,
20084 range: Range<Anchor>,
20085 color: Hsla,
20086 options: RowHighlightOptions,
20087 cx: &mut Context<Self>,
20088 ) {
20089 let snapshot = self.buffer().read(cx).snapshot(cx);
20090 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20091 let ix = row_highlights.binary_search_by(|highlight| {
20092 Ordering::Equal
20093 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20094 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20095 });
20096
20097 if let Err(mut ix) = ix {
20098 let index = post_inc(&mut self.highlight_order);
20099
20100 // If this range intersects with the preceding highlight, then merge it with
20101 // the preceding highlight. Otherwise insert a new highlight.
20102 let mut merged = false;
20103 if ix > 0 {
20104 let prev_highlight = &mut row_highlights[ix - 1];
20105 if prev_highlight
20106 .range
20107 .end
20108 .cmp(&range.start, &snapshot)
20109 .is_ge()
20110 {
20111 ix -= 1;
20112 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20113 prev_highlight.range.end = range.end;
20114 }
20115 merged = true;
20116 prev_highlight.index = index;
20117 prev_highlight.color = color;
20118 prev_highlight.options = options;
20119 }
20120 }
20121
20122 if !merged {
20123 row_highlights.insert(
20124 ix,
20125 RowHighlight {
20126 range,
20127 index,
20128 color,
20129 options,
20130 type_id: TypeId::of::<T>(),
20131 },
20132 );
20133 }
20134
20135 // If any of the following highlights intersect with this one, merge them.
20136 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20137 let highlight = &row_highlights[ix];
20138 if next_highlight
20139 .range
20140 .start
20141 .cmp(&highlight.range.end, &snapshot)
20142 .is_le()
20143 {
20144 if next_highlight
20145 .range
20146 .end
20147 .cmp(&highlight.range.end, &snapshot)
20148 .is_gt()
20149 {
20150 row_highlights[ix].range.end = next_highlight.range.end;
20151 }
20152 row_highlights.remove(ix + 1);
20153 } else {
20154 break;
20155 }
20156 }
20157 }
20158 }
20159
20160 /// Remove any highlighted row ranges of the given type that intersect the
20161 /// given ranges.
20162 pub fn remove_highlighted_rows<T: 'static>(
20163 &mut self,
20164 ranges_to_remove: Vec<Range<Anchor>>,
20165 cx: &mut Context<Self>,
20166 ) {
20167 let snapshot = self.buffer().read(cx).snapshot(cx);
20168 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20169 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20170 row_highlights.retain(|highlight| {
20171 while let Some(range_to_remove) = ranges_to_remove.peek() {
20172 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20173 Ordering::Less | Ordering::Equal => {
20174 ranges_to_remove.next();
20175 }
20176 Ordering::Greater => {
20177 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20178 Ordering::Less | Ordering::Equal => {
20179 return false;
20180 }
20181 Ordering::Greater => break,
20182 }
20183 }
20184 }
20185 }
20186
20187 true
20188 })
20189 }
20190
20191 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20192 pub fn clear_row_highlights<T: 'static>(&mut self) {
20193 self.highlighted_rows.remove(&TypeId::of::<T>());
20194 }
20195
20196 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20197 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20198 self.highlighted_rows
20199 .get(&TypeId::of::<T>())
20200 .map_or(&[] as &[_], |vec| vec.as_slice())
20201 .iter()
20202 .map(|highlight| (highlight.range.clone(), highlight.color))
20203 }
20204
20205 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20206 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20207 /// Allows to ignore certain kinds of highlights.
20208 pub fn highlighted_display_rows(
20209 &self,
20210 window: &mut Window,
20211 cx: &mut App,
20212 ) -> BTreeMap<DisplayRow, LineHighlight> {
20213 let snapshot = self.snapshot(window, cx);
20214 let mut used_highlight_orders = HashMap::default();
20215 self.highlighted_rows
20216 .iter()
20217 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20218 .fold(
20219 BTreeMap::<DisplayRow, LineHighlight>::new(),
20220 |mut unique_rows, highlight| {
20221 let start = highlight.range.start.to_display_point(&snapshot);
20222 let end = highlight.range.end.to_display_point(&snapshot);
20223 let start_row = start.row().0;
20224 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20225 && end.column() == 0
20226 {
20227 end.row().0.saturating_sub(1)
20228 } else {
20229 end.row().0
20230 };
20231 for row in start_row..=end_row {
20232 let used_index =
20233 used_highlight_orders.entry(row).or_insert(highlight.index);
20234 if highlight.index >= *used_index {
20235 *used_index = highlight.index;
20236 unique_rows.insert(
20237 DisplayRow(row),
20238 LineHighlight {
20239 include_gutter: highlight.options.include_gutter,
20240 border: None,
20241 background: highlight.color.into(),
20242 type_id: Some(highlight.type_id),
20243 },
20244 );
20245 }
20246 }
20247 unique_rows
20248 },
20249 )
20250 }
20251
20252 pub fn highlighted_display_row_for_autoscroll(
20253 &self,
20254 snapshot: &DisplaySnapshot,
20255 ) -> Option<DisplayRow> {
20256 self.highlighted_rows
20257 .values()
20258 .flat_map(|highlighted_rows| highlighted_rows.iter())
20259 .filter_map(|highlight| {
20260 if highlight.options.autoscroll {
20261 Some(highlight.range.start.to_display_point(snapshot).row())
20262 } else {
20263 None
20264 }
20265 })
20266 .min()
20267 }
20268
20269 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20270 self.highlight_background::<SearchWithinRange>(
20271 ranges,
20272 |colors| colors.colors().editor_document_highlight_read_background,
20273 cx,
20274 )
20275 }
20276
20277 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20278 self.breadcrumb_header = Some(new_header);
20279 }
20280
20281 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20282 self.clear_background_highlights::<SearchWithinRange>(cx);
20283 }
20284
20285 pub fn highlight_background<T: 'static>(
20286 &mut self,
20287 ranges: &[Range<Anchor>],
20288 color_fetcher: fn(&Theme) -> Hsla,
20289 cx: &mut Context<Self>,
20290 ) {
20291 self.background_highlights.insert(
20292 HighlightKey::Type(TypeId::of::<T>()),
20293 (color_fetcher, Arc::from(ranges)),
20294 );
20295 self.scrollbar_marker_state.dirty = true;
20296 cx.notify();
20297 }
20298
20299 pub fn highlight_background_key<T: 'static>(
20300 &mut self,
20301 key: usize,
20302 ranges: &[Range<Anchor>],
20303 color_fetcher: fn(&Theme) -> Hsla,
20304 cx: &mut Context<Self>,
20305 ) {
20306 self.background_highlights.insert(
20307 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20308 (color_fetcher, Arc::from(ranges)),
20309 );
20310 self.scrollbar_marker_state.dirty = true;
20311 cx.notify();
20312 }
20313
20314 pub fn clear_background_highlights<T: 'static>(
20315 &mut self,
20316 cx: &mut Context<Self>,
20317 ) -> Option<BackgroundHighlight> {
20318 let text_highlights = self
20319 .background_highlights
20320 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20321 if !text_highlights.1.is_empty() {
20322 self.scrollbar_marker_state.dirty = true;
20323 cx.notify();
20324 }
20325 Some(text_highlights)
20326 }
20327
20328 pub fn highlight_gutter<T: 'static>(
20329 &mut self,
20330 ranges: impl Into<Vec<Range<Anchor>>>,
20331 color_fetcher: fn(&App) -> Hsla,
20332 cx: &mut Context<Self>,
20333 ) {
20334 self.gutter_highlights
20335 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20336 cx.notify();
20337 }
20338
20339 pub fn clear_gutter_highlights<T: 'static>(
20340 &mut self,
20341 cx: &mut Context<Self>,
20342 ) -> Option<GutterHighlight> {
20343 cx.notify();
20344 self.gutter_highlights.remove(&TypeId::of::<T>())
20345 }
20346
20347 pub fn insert_gutter_highlight<T: 'static>(
20348 &mut self,
20349 range: Range<Anchor>,
20350 color_fetcher: fn(&App) -> Hsla,
20351 cx: &mut Context<Self>,
20352 ) {
20353 let snapshot = self.buffer().read(cx).snapshot(cx);
20354 let mut highlights = self
20355 .gutter_highlights
20356 .remove(&TypeId::of::<T>())
20357 .map(|(_, highlights)| highlights)
20358 .unwrap_or_default();
20359 let ix = highlights.binary_search_by(|highlight| {
20360 Ordering::Equal
20361 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20362 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20363 });
20364 if let Err(ix) = ix {
20365 highlights.insert(ix, range);
20366 }
20367 self.gutter_highlights
20368 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20369 }
20370
20371 pub fn remove_gutter_highlights<T: 'static>(
20372 &mut self,
20373 ranges_to_remove: Vec<Range<Anchor>>,
20374 cx: &mut Context<Self>,
20375 ) {
20376 let snapshot = self.buffer().read(cx).snapshot(cx);
20377 let Some((color_fetcher, mut gutter_highlights)) =
20378 self.gutter_highlights.remove(&TypeId::of::<T>())
20379 else {
20380 return;
20381 };
20382 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20383 gutter_highlights.retain(|highlight| {
20384 while let Some(range_to_remove) = ranges_to_remove.peek() {
20385 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20386 Ordering::Less | Ordering::Equal => {
20387 ranges_to_remove.next();
20388 }
20389 Ordering::Greater => {
20390 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20391 Ordering::Less | Ordering::Equal => {
20392 return false;
20393 }
20394 Ordering::Greater => break,
20395 }
20396 }
20397 }
20398 }
20399
20400 true
20401 });
20402 self.gutter_highlights
20403 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20404 }
20405
20406 #[cfg(feature = "test-support")]
20407 pub fn all_text_highlights(
20408 &self,
20409 window: &mut Window,
20410 cx: &mut Context<Self>,
20411 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20412 let snapshot = self.snapshot(window, cx);
20413 self.display_map.update(cx, |display_map, _| {
20414 display_map
20415 .all_text_highlights()
20416 .map(|highlight| {
20417 let (style, ranges) = highlight.as_ref();
20418 (
20419 *style,
20420 ranges
20421 .iter()
20422 .map(|range| range.clone().to_display_points(&snapshot))
20423 .collect(),
20424 )
20425 })
20426 .collect()
20427 })
20428 }
20429
20430 #[cfg(feature = "test-support")]
20431 pub fn all_text_background_highlights(
20432 &self,
20433 window: &mut Window,
20434 cx: &mut Context<Self>,
20435 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20436 let snapshot = self.snapshot(window, cx);
20437 let buffer = &snapshot.buffer_snapshot();
20438 let start = buffer.anchor_before(0);
20439 let end = buffer.anchor_after(buffer.len());
20440 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20441 }
20442
20443 #[cfg(any(test, feature = "test-support"))]
20444 pub fn sorted_background_highlights_in_range(
20445 &self,
20446 search_range: Range<Anchor>,
20447 display_snapshot: &DisplaySnapshot,
20448 theme: &Theme,
20449 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20450 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20451 res.sort_by(|a, b| {
20452 a.0.start
20453 .cmp(&b.0.start)
20454 .then_with(|| a.0.end.cmp(&b.0.end))
20455 .then_with(|| a.1.cmp(&b.1))
20456 });
20457 res
20458 }
20459
20460 #[cfg(feature = "test-support")]
20461 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20462 let snapshot = self.buffer().read(cx).snapshot(cx);
20463
20464 let highlights = self
20465 .background_highlights
20466 .get(&HighlightKey::Type(TypeId::of::<
20467 items::BufferSearchHighlights,
20468 >()));
20469
20470 if let Some((_color, ranges)) = highlights {
20471 ranges
20472 .iter()
20473 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20474 .collect_vec()
20475 } else {
20476 vec![]
20477 }
20478 }
20479
20480 fn document_highlights_for_position<'a>(
20481 &'a self,
20482 position: Anchor,
20483 buffer: &'a MultiBufferSnapshot,
20484 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20485 let read_highlights = self
20486 .background_highlights
20487 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20488 .map(|h| &h.1);
20489 let write_highlights = self
20490 .background_highlights
20491 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20492 .map(|h| &h.1);
20493 let left_position = position.bias_left(buffer);
20494 let right_position = position.bias_right(buffer);
20495 read_highlights
20496 .into_iter()
20497 .chain(write_highlights)
20498 .flat_map(move |ranges| {
20499 let start_ix = match ranges.binary_search_by(|probe| {
20500 let cmp = probe.end.cmp(&left_position, buffer);
20501 if cmp.is_ge() {
20502 Ordering::Greater
20503 } else {
20504 Ordering::Less
20505 }
20506 }) {
20507 Ok(i) | Err(i) => i,
20508 };
20509
20510 ranges[start_ix..]
20511 .iter()
20512 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20513 })
20514 }
20515
20516 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20517 self.background_highlights
20518 .get(&HighlightKey::Type(TypeId::of::<T>()))
20519 .is_some_and(|(_, highlights)| !highlights.is_empty())
20520 }
20521
20522 /// Returns all background highlights for a given range.
20523 ///
20524 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20525 pub fn background_highlights_in_range(
20526 &self,
20527 search_range: Range<Anchor>,
20528 display_snapshot: &DisplaySnapshot,
20529 theme: &Theme,
20530 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20531 let mut results = Vec::new();
20532 for (color_fetcher, ranges) in self.background_highlights.values() {
20533 let color = color_fetcher(theme);
20534 let start_ix = match ranges.binary_search_by(|probe| {
20535 let cmp = probe
20536 .end
20537 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20538 if cmp.is_gt() {
20539 Ordering::Greater
20540 } else {
20541 Ordering::Less
20542 }
20543 }) {
20544 Ok(i) | Err(i) => i,
20545 };
20546 for range in &ranges[start_ix..] {
20547 if range
20548 .start
20549 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20550 .is_ge()
20551 {
20552 break;
20553 }
20554
20555 let start = range.start.to_display_point(display_snapshot);
20556 let end = range.end.to_display_point(display_snapshot);
20557 results.push((start..end, color))
20558 }
20559 }
20560 results
20561 }
20562
20563 pub fn gutter_highlights_in_range(
20564 &self,
20565 search_range: Range<Anchor>,
20566 display_snapshot: &DisplaySnapshot,
20567 cx: &App,
20568 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20569 let mut results = Vec::new();
20570 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20571 let color = color_fetcher(cx);
20572 let start_ix = match ranges.binary_search_by(|probe| {
20573 let cmp = probe
20574 .end
20575 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20576 if cmp.is_gt() {
20577 Ordering::Greater
20578 } else {
20579 Ordering::Less
20580 }
20581 }) {
20582 Ok(i) | Err(i) => i,
20583 };
20584 for range in &ranges[start_ix..] {
20585 if range
20586 .start
20587 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20588 .is_ge()
20589 {
20590 break;
20591 }
20592
20593 let start = range.start.to_display_point(display_snapshot);
20594 let end = range.end.to_display_point(display_snapshot);
20595 results.push((start..end, color))
20596 }
20597 }
20598 results
20599 }
20600
20601 /// Get the text ranges corresponding to the redaction query
20602 pub fn redacted_ranges(
20603 &self,
20604 search_range: Range<Anchor>,
20605 display_snapshot: &DisplaySnapshot,
20606 cx: &App,
20607 ) -> Vec<Range<DisplayPoint>> {
20608 display_snapshot
20609 .buffer_snapshot()
20610 .redacted_ranges(search_range, |file| {
20611 if let Some(file) = file {
20612 file.is_private()
20613 && EditorSettings::get(
20614 Some(SettingsLocation {
20615 worktree_id: file.worktree_id(cx),
20616 path: file.path().as_ref(),
20617 }),
20618 cx,
20619 )
20620 .redact_private_values
20621 } else {
20622 false
20623 }
20624 })
20625 .map(|range| {
20626 range.start.to_display_point(display_snapshot)
20627 ..range.end.to_display_point(display_snapshot)
20628 })
20629 .collect()
20630 }
20631
20632 pub fn highlight_text_key<T: 'static>(
20633 &mut self,
20634 key: usize,
20635 ranges: Vec<Range<Anchor>>,
20636 style: HighlightStyle,
20637 cx: &mut Context<Self>,
20638 ) {
20639 self.display_map.update(cx, |map, _| {
20640 map.highlight_text(
20641 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20642 ranges,
20643 style,
20644 );
20645 });
20646 cx.notify();
20647 }
20648
20649 pub fn highlight_text<T: 'static>(
20650 &mut self,
20651 ranges: Vec<Range<Anchor>>,
20652 style: HighlightStyle,
20653 cx: &mut Context<Self>,
20654 ) {
20655 self.display_map.update(cx, |map, _| {
20656 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20657 });
20658 cx.notify();
20659 }
20660
20661 pub(crate) fn highlight_inlays<T: 'static>(
20662 &mut self,
20663 highlights: Vec<InlayHighlight>,
20664 style: HighlightStyle,
20665 cx: &mut Context<Self>,
20666 ) {
20667 self.display_map.update(cx, |map, _| {
20668 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20669 });
20670 cx.notify();
20671 }
20672
20673 pub fn text_highlights<'a, T: 'static>(
20674 &'a self,
20675 cx: &'a App,
20676 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20677 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20678 }
20679
20680 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20681 let cleared = self
20682 .display_map
20683 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20684 if cleared {
20685 cx.notify();
20686 }
20687 }
20688
20689 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20690 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20691 && self.focus_handle.is_focused(window)
20692 }
20693
20694 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20695 self.show_cursor_when_unfocused = is_enabled;
20696 cx.notify();
20697 }
20698
20699 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20700 cx.notify();
20701 }
20702
20703 fn on_debug_session_event(
20704 &mut self,
20705 _session: Entity<Session>,
20706 event: &SessionEvent,
20707 cx: &mut Context<Self>,
20708 ) {
20709 if let SessionEvent::InvalidateInlineValue = event {
20710 self.refresh_inline_values(cx);
20711 }
20712 }
20713
20714 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20715 let Some(project) = self.project.clone() else {
20716 return;
20717 };
20718
20719 if !self.inline_value_cache.enabled {
20720 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20721 self.splice_inlays(&inlays, Vec::new(), cx);
20722 return;
20723 }
20724
20725 let current_execution_position = self
20726 .highlighted_rows
20727 .get(&TypeId::of::<ActiveDebugLine>())
20728 .and_then(|lines| lines.last().map(|line| line.range.end));
20729
20730 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20731 let inline_values = editor
20732 .update(cx, |editor, cx| {
20733 let Some(current_execution_position) = current_execution_position else {
20734 return Some(Task::ready(Ok(Vec::new())));
20735 };
20736
20737 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20738 let snapshot = buffer.snapshot(cx);
20739
20740 let excerpt = snapshot.excerpt_containing(
20741 current_execution_position..current_execution_position,
20742 )?;
20743
20744 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20745 })?;
20746
20747 let range =
20748 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20749
20750 project.inline_values(buffer, range, cx)
20751 })
20752 .ok()
20753 .flatten()?
20754 .await
20755 .context("refreshing debugger inlays")
20756 .log_err()?;
20757
20758 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20759
20760 for (buffer_id, inline_value) in inline_values
20761 .into_iter()
20762 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20763 {
20764 buffer_inline_values
20765 .entry(buffer_id)
20766 .or_default()
20767 .push(inline_value);
20768 }
20769
20770 editor
20771 .update(cx, |editor, cx| {
20772 let snapshot = editor.buffer.read(cx).snapshot(cx);
20773 let mut new_inlays = Vec::default();
20774
20775 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20776 let buffer_id = buffer_snapshot.remote_id();
20777 buffer_inline_values
20778 .get(&buffer_id)
20779 .into_iter()
20780 .flatten()
20781 .for_each(|hint| {
20782 let inlay = Inlay::debugger(
20783 post_inc(&mut editor.next_inlay_id),
20784 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20785 hint.text(),
20786 );
20787 if !inlay.text().chars().contains(&'\n') {
20788 new_inlays.push(inlay);
20789 }
20790 });
20791 }
20792
20793 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20794 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20795
20796 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20797 })
20798 .ok()?;
20799 Some(())
20800 });
20801 }
20802
20803 fn on_buffer_event(
20804 &mut self,
20805 multibuffer: &Entity<MultiBuffer>,
20806 event: &multi_buffer::Event,
20807 window: &mut Window,
20808 cx: &mut Context<Self>,
20809 ) {
20810 match event {
20811 multi_buffer::Event::Edited {
20812 singleton_buffer_edited,
20813 edited_buffer,
20814 } => {
20815 self.scrollbar_marker_state.dirty = true;
20816 self.active_indent_guides_state.dirty = true;
20817 self.refresh_active_diagnostics(cx);
20818 self.refresh_code_actions(window, cx);
20819 self.refresh_selected_text_highlights(true, window, cx);
20820 self.refresh_single_line_folds(window, cx);
20821 refresh_matching_bracket_highlights(self, cx);
20822 if self.has_active_edit_prediction() {
20823 self.update_visible_edit_prediction(window, cx);
20824 }
20825 if let Some(project) = self.project.as_ref()
20826 && let Some(edited_buffer) = edited_buffer
20827 {
20828 project.update(cx, |project, cx| {
20829 self.registered_buffers
20830 .entry(edited_buffer.read(cx).remote_id())
20831 .or_insert_with(|| {
20832 project.register_buffer_with_language_servers(edited_buffer, cx)
20833 });
20834 });
20835 }
20836 cx.emit(EditorEvent::BufferEdited);
20837 cx.emit(SearchEvent::MatchesInvalidated);
20838
20839 if let Some(buffer) = edited_buffer {
20840 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20841 }
20842
20843 if *singleton_buffer_edited {
20844 if let Some(buffer) = edited_buffer
20845 && buffer.read(cx).file().is_none()
20846 {
20847 cx.emit(EditorEvent::TitleChanged);
20848 }
20849 if let Some(project) = &self.project {
20850 #[allow(clippy::mutable_key_type)]
20851 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20852 multibuffer
20853 .all_buffers()
20854 .into_iter()
20855 .filter_map(|buffer| {
20856 buffer.update(cx, |buffer, cx| {
20857 let language = buffer.language()?;
20858 let should_discard = project.update(cx, |project, cx| {
20859 project.is_local()
20860 && !project.has_language_servers_for(buffer, cx)
20861 });
20862 should_discard.not().then_some(language.clone())
20863 })
20864 })
20865 .collect::<HashSet<_>>()
20866 });
20867 if !languages_affected.is_empty() {
20868 self.refresh_inlay_hints(
20869 InlayHintRefreshReason::BufferEdited(languages_affected),
20870 cx,
20871 );
20872 }
20873 }
20874 }
20875
20876 let Some(project) = &self.project else { return };
20877 let (telemetry, is_via_ssh) = {
20878 let project = project.read(cx);
20879 let telemetry = project.client().telemetry().clone();
20880 let is_via_ssh = project.is_via_remote_server();
20881 (telemetry, is_via_ssh)
20882 };
20883 refresh_linked_ranges(self, window, cx);
20884 telemetry.log_edit_event("editor", is_via_ssh);
20885 }
20886 multi_buffer::Event::ExcerptsAdded {
20887 buffer,
20888 predecessor,
20889 excerpts,
20890 } => {
20891 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20892 let buffer_id = buffer.read(cx).remote_id();
20893 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20894 && let Some(project) = &self.project
20895 {
20896 update_uncommitted_diff_for_buffer(
20897 cx.entity(),
20898 project,
20899 [buffer.clone()],
20900 self.buffer.clone(),
20901 cx,
20902 )
20903 .detach();
20904 }
20905 if self.active_diagnostics != ActiveDiagnostic::All {
20906 self.update_lsp_data(false, Some(buffer_id), window, cx);
20907 }
20908 cx.emit(EditorEvent::ExcerptsAdded {
20909 buffer: buffer.clone(),
20910 predecessor: *predecessor,
20911 excerpts: excerpts.clone(),
20912 });
20913 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20914 }
20915 multi_buffer::Event::ExcerptsRemoved {
20916 ids,
20917 removed_buffer_ids,
20918 } => {
20919 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20920 let buffer = self.buffer.read(cx);
20921 self.registered_buffers
20922 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20923 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20924 cx.emit(EditorEvent::ExcerptsRemoved {
20925 ids: ids.clone(),
20926 removed_buffer_ids: removed_buffer_ids.clone(),
20927 });
20928 }
20929 multi_buffer::Event::ExcerptsEdited {
20930 excerpt_ids,
20931 buffer_ids,
20932 } => {
20933 self.display_map.update(cx, |map, cx| {
20934 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20935 });
20936 cx.emit(EditorEvent::ExcerptsEdited {
20937 ids: excerpt_ids.clone(),
20938 });
20939 }
20940 multi_buffer::Event::ExcerptsExpanded { ids } => {
20941 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20942 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20943 }
20944 multi_buffer::Event::Reparsed(buffer_id) => {
20945 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20946 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20947
20948 cx.emit(EditorEvent::Reparsed(*buffer_id));
20949 }
20950 multi_buffer::Event::DiffHunksToggled => {
20951 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20952 }
20953 multi_buffer::Event::LanguageChanged(buffer_id) => {
20954 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20955 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20956 cx.emit(EditorEvent::Reparsed(*buffer_id));
20957 cx.notify();
20958 }
20959 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20960 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20961 multi_buffer::Event::FileHandleChanged
20962 | multi_buffer::Event::Reloaded
20963 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20964 multi_buffer::Event::DiagnosticsUpdated => {
20965 self.update_diagnostics_state(window, cx);
20966 }
20967 _ => {}
20968 };
20969 }
20970
20971 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20972 if !self.diagnostics_enabled() {
20973 return;
20974 }
20975 self.refresh_active_diagnostics(cx);
20976 self.refresh_inline_diagnostics(true, window, cx);
20977 self.scrollbar_marker_state.dirty = true;
20978 cx.notify();
20979 }
20980
20981 pub fn start_temporary_diff_override(&mut self) {
20982 self.load_diff_task.take();
20983 self.temporary_diff_override = true;
20984 }
20985
20986 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20987 self.temporary_diff_override = false;
20988 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20989 self.buffer.update(cx, |buffer, cx| {
20990 buffer.set_all_diff_hunks_collapsed(cx);
20991 });
20992
20993 if let Some(project) = self.project.clone() {
20994 self.load_diff_task = Some(
20995 update_uncommitted_diff_for_buffer(
20996 cx.entity(),
20997 &project,
20998 self.buffer.read(cx).all_buffers(),
20999 self.buffer.clone(),
21000 cx,
21001 )
21002 .shared(),
21003 );
21004 }
21005 }
21006
21007 fn on_display_map_changed(
21008 &mut self,
21009 _: Entity<DisplayMap>,
21010 _: &mut Window,
21011 cx: &mut Context<Self>,
21012 ) {
21013 cx.notify();
21014 }
21015
21016 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21017 if self.diagnostics_enabled() {
21018 let new_severity = EditorSettings::get_global(cx)
21019 .diagnostics_max_severity
21020 .unwrap_or(DiagnosticSeverity::Hint);
21021 self.set_max_diagnostics_severity(new_severity, cx);
21022 }
21023 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21024 self.update_edit_prediction_settings(cx);
21025 self.refresh_edit_prediction(true, false, window, cx);
21026 self.refresh_inline_values(cx);
21027 self.refresh_inlay_hints(
21028 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21029 self.selections.newest_anchor().head(),
21030 &self.buffer.read(cx).snapshot(cx),
21031 cx,
21032 )),
21033 cx,
21034 );
21035
21036 let old_cursor_shape = self.cursor_shape;
21037 let old_show_breadcrumbs = self.show_breadcrumbs;
21038
21039 {
21040 let editor_settings = EditorSettings::get_global(cx);
21041 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21042 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21043 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21044 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21045 }
21046
21047 if old_cursor_shape != self.cursor_shape {
21048 cx.emit(EditorEvent::CursorShapeChanged);
21049 }
21050
21051 if old_show_breadcrumbs != self.show_breadcrumbs {
21052 cx.emit(EditorEvent::BreadcrumbsChanged);
21053 }
21054
21055 let project_settings = ProjectSettings::get_global(cx);
21056 self.serialize_dirty_buffers =
21057 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21058
21059 if self.mode.is_full() {
21060 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21061 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21062 if self.show_inline_diagnostics != show_inline_diagnostics {
21063 self.show_inline_diagnostics = show_inline_diagnostics;
21064 self.refresh_inline_diagnostics(false, window, cx);
21065 }
21066
21067 if self.git_blame_inline_enabled != inline_blame_enabled {
21068 self.toggle_git_blame_inline_internal(false, window, cx);
21069 }
21070
21071 let minimap_settings = EditorSettings::get_global(cx).minimap;
21072 if self.minimap_visibility != MinimapVisibility::Disabled {
21073 if self.minimap_visibility.settings_visibility()
21074 != minimap_settings.minimap_enabled()
21075 {
21076 self.set_minimap_visibility(
21077 MinimapVisibility::for_mode(self.mode(), cx),
21078 window,
21079 cx,
21080 );
21081 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21082 minimap_entity.update(cx, |minimap_editor, cx| {
21083 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21084 })
21085 }
21086 }
21087 }
21088
21089 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21090 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21091 }) {
21092 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21093 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21094 }
21095 self.refresh_colors(false, None, window, cx);
21096 }
21097
21098 cx.notify();
21099 }
21100
21101 pub fn set_searchable(&mut self, searchable: bool) {
21102 self.searchable = searchable;
21103 }
21104
21105 pub fn searchable(&self) -> bool {
21106 self.searchable
21107 }
21108
21109 fn open_proposed_changes_editor(
21110 &mut self,
21111 _: &OpenProposedChangesEditor,
21112 window: &mut Window,
21113 cx: &mut Context<Self>,
21114 ) {
21115 let Some(workspace) = self.workspace() else {
21116 cx.propagate();
21117 return;
21118 };
21119
21120 let selections = self.selections.all::<usize>(cx);
21121 let multi_buffer = self.buffer.read(cx);
21122 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21123 let mut new_selections_by_buffer = HashMap::default();
21124 for selection in selections {
21125 for (buffer, range, _) in
21126 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21127 {
21128 let mut range = range.to_point(buffer);
21129 range.start.column = 0;
21130 range.end.column = buffer.line_len(range.end.row);
21131 new_selections_by_buffer
21132 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21133 .or_insert(Vec::new())
21134 .push(range)
21135 }
21136 }
21137
21138 let proposed_changes_buffers = new_selections_by_buffer
21139 .into_iter()
21140 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21141 .collect::<Vec<_>>();
21142 let proposed_changes_editor = cx.new(|cx| {
21143 ProposedChangesEditor::new(
21144 "Proposed changes",
21145 proposed_changes_buffers,
21146 self.project.clone(),
21147 window,
21148 cx,
21149 )
21150 });
21151
21152 window.defer(cx, move |window, cx| {
21153 workspace.update(cx, |workspace, cx| {
21154 workspace.active_pane().update(cx, |pane, cx| {
21155 pane.add_item(
21156 Box::new(proposed_changes_editor),
21157 true,
21158 true,
21159 None,
21160 window,
21161 cx,
21162 );
21163 });
21164 });
21165 });
21166 }
21167
21168 pub fn open_excerpts_in_split(
21169 &mut self,
21170 _: &OpenExcerptsSplit,
21171 window: &mut Window,
21172 cx: &mut Context<Self>,
21173 ) {
21174 self.open_excerpts_common(None, true, window, cx)
21175 }
21176
21177 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21178 self.open_excerpts_common(None, false, window, cx)
21179 }
21180
21181 fn open_excerpts_common(
21182 &mut self,
21183 jump_data: Option<JumpData>,
21184 split: bool,
21185 window: &mut Window,
21186 cx: &mut Context<Self>,
21187 ) {
21188 let Some(workspace) = self.workspace() else {
21189 cx.propagate();
21190 return;
21191 };
21192
21193 if self.buffer.read(cx).is_singleton() {
21194 cx.propagate();
21195 return;
21196 }
21197
21198 let mut new_selections_by_buffer = HashMap::default();
21199 match &jump_data {
21200 Some(JumpData::MultiBufferPoint {
21201 excerpt_id,
21202 position,
21203 anchor,
21204 line_offset_from_top,
21205 }) => {
21206 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21207 if let Some(buffer) = multi_buffer_snapshot
21208 .buffer_id_for_excerpt(*excerpt_id)
21209 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21210 {
21211 let buffer_snapshot = buffer.read(cx).snapshot();
21212 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21213 language::ToPoint::to_point(anchor, &buffer_snapshot)
21214 } else {
21215 buffer_snapshot.clip_point(*position, Bias::Left)
21216 };
21217 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21218 new_selections_by_buffer.insert(
21219 buffer,
21220 (
21221 vec![jump_to_offset..jump_to_offset],
21222 Some(*line_offset_from_top),
21223 ),
21224 );
21225 }
21226 }
21227 Some(JumpData::MultiBufferRow {
21228 row,
21229 line_offset_from_top,
21230 }) => {
21231 let point = MultiBufferPoint::new(row.0, 0);
21232 if let Some((buffer, buffer_point, _)) =
21233 self.buffer.read(cx).point_to_buffer_point(point, cx)
21234 {
21235 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21236 new_selections_by_buffer
21237 .entry(buffer)
21238 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21239 .0
21240 .push(buffer_offset..buffer_offset)
21241 }
21242 }
21243 None => {
21244 let selections = self.selections.all::<usize>(cx);
21245 let multi_buffer = self.buffer.read(cx);
21246 for selection in selections {
21247 for (snapshot, range, _, anchor) in multi_buffer
21248 .snapshot(cx)
21249 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21250 {
21251 if let Some(anchor) = anchor {
21252 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21253 else {
21254 continue;
21255 };
21256 let offset = text::ToOffset::to_offset(
21257 &anchor.text_anchor,
21258 &buffer_handle.read(cx).snapshot(),
21259 );
21260 let range = offset..offset;
21261 new_selections_by_buffer
21262 .entry(buffer_handle)
21263 .or_insert((Vec::new(), None))
21264 .0
21265 .push(range)
21266 } else {
21267 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21268 else {
21269 continue;
21270 };
21271 new_selections_by_buffer
21272 .entry(buffer_handle)
21273 .or_insert((Vec::new(), None))
21274 .0
21275 .push(range)
21276 }
21277 }
21278 }
21279 }
21280 }
21281
21282 new_selections_by_buffer
21283 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21284
21285 if new_selections_by_buffer.is_empty() {
21286 return;
21287 }
21288
21289 // We defer the pane interaction because we ourselves are a workspace item
21290 // and activating a new item causes the pane to call a method on us reentrantly,
21291 // which panics if we're on the stack.
21292 window.defer(cx, move |window, cx| {
21293 workspace.update(cx, |workspace, cx| {
21294 let pane = if split {
21295 workspace.adjacent_pane(window, cx)
21296 } else {
21297 workspace.active_pane().clone()
21298 };
21299
21300 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21301 let editor = buffer
21302 .read(cx)
21303 .file()
21304 .is_none()
21305 .then(|| {
21306 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21307 // so `workspace.open_project_item` will never find them, always opening a new editor.
21308 // Instead, we try to activate the existing editor in the pane first.
21309 let (editor, pane_item_index) =
21310 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21311 let editor = item.downcast::<Editor>()?;
21312 let singleton_buffer =
21313 editor.read(cx).buffer().read(cx).as_singleton()?;
21314 if singleton_buffer == buffer {
21315 Some((editor, i))
21316 } else {
21317 None
21318 }
21319 })?;
21320 pane.update(cx, |pane, cx| {
21321 pane.activate_item(pane_item_index, true, true, window, cx)
21322 });
21323 Some(editor)
21324 })
21325 .flatten()
21326 .unwrap_or_else(|| {
21327 workspace.open_project_item::<Self>(
21328 pane.clone(),
21329 buffer,
21330 true,
21331 true,
21332 window,
21333 cx,
21334 )
21335 });
21336
21337 editor.update(cx, |editor, cx| {
21338 let autoscroll = match scroll_offset {
21339 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21340 None => Autoscroll::newest(),
21341 };
21342 let nav_history = editor.nav_history.take();
21343 editor.change_selections(
21344 SelectionEffects::scroll(autoscroll),
21345 window,
21346 cx,
21347 |s| {
21348 s.select_ranges(ranges);
21349 },
21350 );
21351 editor.nav_history = nav_history;
21352 });
21353 }
21354 })
21355 });
21356 }
21357
21358 // For now, don't allow opening excerpts in buffers that aren't backed by
21359 // regular project files.
21360 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21361 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21362 }
21363
21364 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21365 let snapshot = self.buffer.read(cx).read(cx);
21366 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21367 Some(
21368 ranges
21369 .iter()
21370 .map(move |range| {
21371 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21372 })
21373 .collect(),
21374 )
21375 }
21376
21377 fn selection_replacement_ranges(
21378 &self,
21379 range: Range<OffsetUtf16>,
21380 cx: &mut App,
21381 ) -> Vec<Range<OffsetUtf16>> {
21382 let selections = self.selections.all::<OffsetUtf16>(cx);
21383 let newest_selection = selections
21384 .iter()
21385 .max_by_key(|selection| selection.id)
21386 .unwrap();
21387 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21388 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21389 let snapshot = self.buffer.read(cx).read(cx);
21390 selections
21391 .into_iter()
21392 .map(|mut selection| {
21393 selection.start.0 =
21394 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21395 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21396 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21397 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21398 })
21399 .collect()
21400 }
21401
21402 fn report_editor_event(
21403 &self,
21404 reported_event: ReportEditorEvent,
21405 file_extension: Option<String>,
21406 cx: &App,
21407 ) {
21408 if cfg!(any(test, feature = "test-support")) {
21409 return;
21410 }
21411
21412 let Some(project) = &self.project else { return };
21413
21414 // If None, we are in a file without an extension
21415 let file = self
21416 .buffer
21417 .read(cx)
21418 .as_singleton()
21419 .and_then(|b| b.read(cx).file());
21420 let file_extension = file_extension.or(file
21421 .as_ref()
21422 .and_then(|file| Path::new(file.file_name(cx)).extension())
21423 .and_then(|e| e.to_str())
21424 .map(|a| a.to_string()));
21425
21426 let vim_mode = vim_enabled(cx);
21427
21428 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21429 let copilot_enabled = edit_predictions_provider
21430 == language::language_settings::EditPredictionProvider::Copilot;
21431 let copilot_enabled_for_language = self
21432 .buffer
21433 .read(cx)
21434 .language_settings(cx)
21435 .show_edit_predictions;
21436
21437 let project = project.read(cx);
21438 let event_type = reported_event.event_type();
21439
21440 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21441 telemetry::event!(
21442 event_type,
21443 type = if auto_saved {"autosave"} else {"manual"},
21444 file_extension,
21445 vim_mode,
21446 copilot_enabled,
21447 copilot_enabled_for_language,
21448 edit_predictions_provider,
21449 is_via_ssh = project.is_via_remote_server(),
21450 );
21451 } else {
21452 telemetry::event!(
21453 event_type,
21454 file_extension,
21455 vim_mode,
21456 copilot_enabled,
21457 copilot_enabled_for_language,
21458 edit_predictions_provider,
21459 is_via_ssh = project.is_via_remote_server(),
21460 );
21461 };
21462 }
21463
21464 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21465 /// with each line being an array of {text, highlight} objects.
21466 fn copy_highlight_json(
21467 &mut self,
21468 _: &CopyHighlightJson,
21469 window: &mut Window,
21470 cx: &mut Context<Self>,
21471 ) {
21472 #[derive(Serialize)]
21473 struct Chunk<'a> {
21474 text: String,
21475 highlight: Option<&'a str>,
21476 }
21477
21478 let snapshot = self.buffer.read(cx).snapshot(cx);
21479 let range = self
21480 .selected_text_range(false, window, cx)
21481 .and_then(|selection| {
21482 if selection.range.is_empty() {
21483 None
21484 } else {
21485 Some(selection.range)
21486 }
21487 })
21488 .unwrap_or_else(|| 0..snapshot.len());
21489
21490 let chunks = snapshot.chunks(range, true);
21491 let mut lines = Vec::new();
21492 let mut line: VecDeque<Chunk> = VecDeque::new();
21493
21494 let Some(style) = self.style.as_ref() else {
21495 return;
21496 };
21497
21498 for chunk in chunks {
21499 let highlight = chunk
21500 .syntax_highlight_id
21501 .and_then(|id| id.name(&style.syntax));
21502 let mut chunk_lines = chunk.text.split('\n').peekable();
21503 while let Some(text) = chunk_lines.next() {
21504 let mut merged_with_last_token = false;
21505 if let Some(last_token) = line.back_mut()
21506 && last_token.highlight == highlight
21507 {
21508 last_token.text.push_str(text);
21509 merged_with_last_token = true;
21510 }
21511
21512 if !merged_with_last_token {
21513 line.push_back(Chunk {
21514 text: text.into(),
21515 highlight,
21516 });
21517 }
21518
21519 if chunk_lines.peek().is_some() {
21520 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21521 line.pop_front();
21522 }
21523 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21524 line.pop_back();
21525 }
21526
21527 lines.push(mem::take(&mut line));
21528 }
21529 }
21530 }
21531
21532 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21533 return;
21534 };
21535 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21536 }
21537
21538 pub fn open_context_menu(
21539 &mut self,
21540 _: &OpenContextMenu,
21541 window: &mut Window,
21542 cx: &mut Context<Self>,
21543 ) {
21544 self.request_autoscroll(Autoscroll::newest(), cx);
21545 let position = self.selections.newest_display(cx).start;
21546 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21547 }
21548
21549 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21550 &self.inlay_hint_cache
21551 }
21552
21553 pub fn replay_insert_event(
21554 &mut self,
21555 text: &str,
21556 relative_utf16_range: Option<Range<isize>>,
21557 window: &mut Window,
21558 cx: &mut Context<Self>,
21559 ) {
21560 if !self.input_enabled {
21561 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21562 return;
21563 }
21564 if let Some(relative_utf16_range) = relative_utf16_range {
21565 let selections = self.selections.all::<OffsetUtf16>(cx);
21566 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21567 let new_ranges = selections.into_iter().map(|range| {
21568 let start = OffsetUtf16(
21569 range
21570 .head()
21571 .0
21572 .saturating_add_signed(relative_utf16_range.start),
21573 );
21574 let end = OffsetUtf16(
21575 range
21576 .head()
21577 .0
21578 .saturating_add_signed(relative_utf16_range.end),
21579 );
21580 start..end
21581 });
21582 s.select_ranges(new_ranges);
21583 });
21584 }
21585
21586 self.handle_input(text, window, cx);
21587 }
21588
21589 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21590 let Some(provider) = self.semantics_provider.as_ref() else {
21591 return false;
21592 };
21593
21594 let mut supports = false;
21595 self.buffer().update(cx, |this, cx| {
21596 this.for_each_buffer(|buffer| {
21597 supports |= provider.supports_inlay_hints(buffer, cx);
21598 });
21599 });
21600
21601 supports
21602 }
21603
21604 pub fn is_focused(&self, window: &Window) -> bool {
21605 self.focus_handle.is_focused(window)
21606 }
21607
21608 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21609 cx.emit(EditorEvent::Focused);
21610
21611 if let Some(descendant) = self
21612 .last_focused_descendant
21613 .take()
21614 .and_then(|descendant| descendant.upgrade())
21615 {
21616 window.focus(&descendant);
21617 } else {
21618 if let Some(blame) = self.blame.as_ref() {
21619 blame.update(cx, GitBlame::focus)
21620 }
21621
21622 self.blink_manager.update(cx, BlinkManager::enable);
21623 self.show_cursor_names(window, cx);
21624 self.buffer.update(cx, |buffer, cx| {
21625 buffer.finalize_last_transaction(cx);
21626 if self.leader_id.is_none() {
21627 buffer.set_active_selections(
21628 &self.selections.disjoint_anchors_arc(),
21629 self.selections.line_mode(),
21630 self.cursor_shape,
21631 cx,
21632 );
21633 }
21634 });
21635 }
21636 }
21637
21638 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21639 cx.emit(EditorEvent::FocusedIn)
21640 }
21641
21642 fn handle_focus_out(
21643 &mut self,
21644 event: FocusOutEvent,
21645 _window: &mut Window,
21646 cx: &mut Context<Self>,
21647 ) {
21648 if event.blurred != self.focus_handle {
21649 self.last_focused_descendant = Some(event.blurred);
21650 }
21651 self.selection_drag_state = SelectionDragState::None;
21652 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21653 }
21654
21655 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21656 self.blink_manager.update(cx, BlinkManager::disable);
21657 self.buffer
21658 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21659
21660 if let Some(blame) = self.blame.as_ref() {
21661 blame.update(cx, GitBlame::blur)
21662 }
21663 if !self.hover_state.focused(window, cx) {
21664 hide_hover(self, cx);
21665 }
21666 if !self
21667 .context_menu
21668 .borrow()
21669 .as_ref()
21670 .is_some_and(|context_menu| context_menu.focused(window, cx))
21671 {
21672 self.hide_context_menu(window, cx);
21673 }
21674 self.take_active_edit_prediction(cx);
21675 cx.emit(EditorEvent::Blurred);
21676 cx.notify();
21677 }
21678
21679 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21680 let mut pending: String = window
21681 .pending_input_keystrokes()
21682 .into_iter()
21683 .flatten()
21684 .filter_map(|keystroke| {
21685 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21686 keystroke.key_char.clone()
21687 } else {
21688 None
21689 }
21690 })
21691 .collect();
21692
21693 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21694 pending = "".to_string();
21695 }
21696
21697 let existing_pending = self
21698 .text_highlights::<PendingInput>(cx)
21699 .map(|(_, ranges)| ranges.to_vec());
21700 if existing_pending.is_none() && pending.is_empty() {
21701 return;
21702 }
21703 let transaction =
21704 self.transact(window, cx, |this, window, cx| {
21705 let selections = this.selections.all::<usize>(cx);
21706 let edits = selections
21707 .iter()
21708 .map(|selection| (selection.end..selection.end, pending.clone()));
21709 this.edit(edits, cx);
21710 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21711 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21712 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21713 }));
21714 });
21715 if let Some(existing_ranges) = existing_pending {
21716 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21717 this.edit(edits, cx);
21718 }
21719 });
21720
21721 let snapshot = self.snapshot(window, cx);
21722 let ranges = self
21723 .selections
21724 .all::<usize>(cx)
21725 .into_iter()
21726 .map(|selection| {
21727 snapshot.buffer_snapshot().anchor_after(selection.end)
21728 ..snapshot
21729 .buffer_snapshot()
21730 .anchor_before(selection.end + pending.len())
21731 })
21732 .collect();
21733
21734 if pending.is_empty() {
21735 self.clear_highlights::<PendingInput>(cx);
21736 } else {
21737 self.highlight_text::<PendingInput>(
21738 ranges,
21739 HighlightStyle {
21740 underline: Some(UnderlineStyle {
21741 thickness: px(1.),
21742 color: None,
21743 wavy: false,
21744 }),
21745 ..Default::default()
21746 },
21747 cx,
21748 );
21749 }
21750
21751 self.ime_transaction = self.ime_transaction.or(transaction);
21752 if let Some(transaction) = self.ime_transaction {
21753 self.buffer.update(cx, |buffer, cx| {
21754 buffer.group_until_transaction(transaction, cx);
21755 });
21756 }
21757
21758 if self.text_highlights::<PendingInput>(cx).is_none() {
21759 self.ime_transaction.take();
21760 }
21761 }
21762
21763 pub fn register_action_renderer(
21764 &mut self,
21765 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21766 ) -> Subscription {
21767 let id = self.next_editor_action_id.post_inc();
21768 self.editor_actions
21769 .borrow_mut()
21770 .insert(id, Box::new(listener));
21771
21772 let editor_actions = self.editor_actions.clone();
21773 Subscription::new(move || {
21774 editor_actions.borrow_mut().remove(&id);
21775 })
21776 }
21777
21778 pub fn register_action<A: Action>(
21779 &mut self,
21780 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21781 ) -> Subscription {
21782 let id = self.next_editor_action_id.post_inc();
21783 let listener = Arc::new(listener);
21784 self.editor_actions.borrow_mut().insert(
21785 id,
21786 Box::new(move |_, window, _| {
21787 let listener = listener.clone();
21788 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21789 let action = action.downcast_ref().unwrap();
21790 if phase == DispatchPhase::Bubble {
21791 listener(action, window, cx)
21792 }
21793 })
21794 }),
21795 );
21796
21797 let editor_actions = self.editor_actions.clone();
21798 Subscription::new(move || {
21799 editor_actions.borrow_mut().remove(&id);
21800 })
21801 }
21802
21803 pub fn file_header_size(&self) -> u32 {
21804 FILE_HEADER_HEIGHT
21805 }
21806
21807 pub fn restore(
21808 &mut self,
21809 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21810 window: &mut Window,
21811 cx: &mut Context<Self>,
21812 ) {
21813 let workspace = self.workspace();
21814 let project = self.project();
21815 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21816 let mut tasks = Vec::new();
21817 for (buffer_id, changes) in revert_changes {
21818 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21819 buffer.update(cx, |buffer, cx| {
21820 buffer.edit(
21821 changes
21822 .into_iter()
21823 .map(|(range, text)| (range, text.to_string())),
21824 None,
21825 cx,
21826 );
21827 });
21828
21829 if let Some(project) =
21830 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21831 {
21832 project.update(cx, |project, cx| {
21833 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21834 })
21835 }
21836 }
21837 }
21838 tasks
21839 });
21840 cx.spawn_in(window, async move |_, cx| {
21841 for (buffer, task) in save_tasks {
21842 let result = task.await;
21843 if result.is_err() {
21844 let Some(path) = buffer
21845 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21846 .ok()
21847 else {
21848 continue;
21849 };
21850 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21851 let Some(task) = cx
21852 .update_window_entity(workspace, |workspace, window, cx| {
21853 workspace
21854 .open_path_preview(path, None, false, false, false, window, cx)
21855 })
21856 .ok()
21857 else {
21858 continue;
21859 };
21860 task.await.log_err();
21861 }
21862 }
21863 }
21864 })
21865 .detach();
21866 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21867 selections.refresh()
21868 });
21869 }
21870
21871 pub fn to_pixel_point(
21872 &self,
21873 source: multi_buffer::Anchor,
21874 editor_snapshot: &EditorSnapshot,
21875 window: &mut Window,
21876 ) -> Option<gpui::Point<Pixels>> {
21877 let source_point = source.to_display_point(editor_snapshot);
21878 self.display_to_pixel_point(source_point, editor_snapshot, window)
21879 }
21880
21881 pub fn display_to_pixel_point(
21882 &self,
21883 source: DisplayPoint,
21884 editor_snapshot: &EditorSnapshot,
21885 window: &mut Window,
21886 ) -> Option<gpui::Point<Pixels>> {
21887 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21888 let text_layout_details = self.text_layout_details(window);
21889 let scroll_top = text_layout_details
21890 .scroll_anchor
21891 .scroll_position(editor_snapshot)
21892 .y;
21893
21894 if source.row().as_f64() < scroll_top.floor() {
21895 return None;
21896 }
21897 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21898 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21899 Some(gpui::Point::new(source_x, source_y))
21900 }
21901
21902 pub fn has_visible_completions_menu(&self) -> bool {
21903 !self.edit_prediction_preview_is_active()
21904 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21905 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21906 })
21907 }
21908
21909 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21910 if self.mode.is_minimap() {
21911 return;
21912 }
21913 self.addons
21914 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21915 }
21916
21917 pub fn unregister_addon<T: Addon>(&mut self) {
21918 self.addons.remove(&std::any::TypeId::of::<T>());
21919 }
21920
21921 pub fn addon<T: Addon>(&self) -> Option<&T> {
21922 let type_id = std::any::TypeId::of::<T>();
21923 self.addons
21924 .get(&type_id)
21925 .and_then(|item| item.to_any().downcast_ref::<T>())
21926 }
21927
21928 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21929 let type_id = std::any::TypeId::of::<T>();
21930 self.addons
21931 .get_mut(&type_id)
21932 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21933 }
21934
21935 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21936 let text_layout_details = self.text_layout_details(window);
21937 let style = &text_layout_details.editor_style;
21938 let font_id = window.text_system().resolve_font(&style.text.font());
21939 let font_size = style.text.font_size.to_pixels(window.rem_size());
21940 let line_height = style.text.line_height_in_pixels(window.rem_size());
21941 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21942 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21943
21944 CharacterDimensions {
21945 em_width,
21946 em_advance,
21947 line_height,
21948 }
21949 }
21950
21951 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21952 self.load_diff_task.clone()
21953 }
21954
21955 fn read_metadata_from_db(
21956 &mut self,
21957 item_id: u64,
21958 workspace_id: WorkspaceId,
21959 window: &mut Window,
21960 cx: &mut Context<Editor>,
21961 ) {
21962 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21963 && !self.mode.is_minimap()
21964 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21965 {
21966 let buffer_snapshot = OnceCell::new();
21967
21968 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21969 && !folds.is_empty()
21970 {
21971 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21972 self.fold_ranges(
21973 folds
21974 .into_iter()
21975 .map(|(start, end)| {
21976 snapshot.clip_offset(start, Bias::Left)
21977 ..snapshot.clip_offset(end, Bias::Right)
21978 })
21979 .collect(),
21980 false,
21981 window,
21982 cx,
21983 );
21984 }
21985
21986 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21987 && !selections.is_empty()
21988 {
21989 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21990 // skip adding the initial selection to selection history
21991 self.selection_history.mode = SelectionHistoryMode::Skipping;
21992 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21993 s.select_ranges(selections.into_iter().map(|(start, end)| {
21994 snapshot.clip_offset(start, Bias::Left)
21995 ..snapshot.clip_offset(end, Bias::Right)
21996 }));
21997 });
21998 self.selection_history.mode = SelectionHistoryMode::Normal;
21999 };
22000 }
22001
22002 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22003 }
22004
22005 fn update_lsp_data(
22006 &mut self,
22007 ignore_cache: bool,
22008 for_buffer: Option<BufferId>,
22009 window: &mut Window,
22010 cx: &mut Context<'_, Self>,
22011 ) {
22012 self.pull_diagnostics(for_buffer, window, cx);
22013 self.refresh_colors(ignore_cache, for_buffer, window, cx);
22014 }
22015}
22016
22017fn edit_for_markdown_paste<'a>(
22018 buffer: &MultiBufferSnapshot,
22019 range: Range<usize>,
22020 to_insert: &'a str,
22021 url: Option<url::Url>,
22022) -> (Range<usize>, Cow<'a, str>) {
22023 if url.is_none() {
22024 return (range, Cow::Borrowed(to_insert));
22025 };
22026
22027 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22028
22029 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22030 Cow::Borrowed(to_insert)
22031 } else {
22032 Cow::Owned(format!("[{old_text}]({to_insert})"))
22033 };
22034 (range, new_text)
22035}
22036
22037fn vim_enabled(cx: &App) -> bool {
22038 vim_mode_setting::VimModeSetting::try_get(cx)
22039 .map(|vim_mode| vim_mode.0)
22040 .unwrap_or(false)
22041}
22042
22043fn process_completion_for_edit(
22044 completion: &Completion,
22045 intent: CompletionIntent,
22046 buffer: &Entity<Buffer>,
22047 cursor_position: &text::Anchor,
22048 cx: &mut Context<Editor>,
22049) -> CompletionEdit {
22050 let buffer = buffer.read(cx);
22051 let buffer_snapshot = buffer.snapshot();
22052 let (snippet, new_text) = if completion.is_snippet() {
22053 let mut snippet_source = completion.new_text.clone();
22054 // Workaround for typescript language server issues so that methods don't expand within
22055 // strings and functions with type expressions. The previous point is used because the query
22056 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22057 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22058 let previous_point = if previous_point.column > 0 {
22059 cursor_position.to_previous_offset(&buffer_snapshot)
22060 } else {
22061 cursor_position.to_offset(&buffer_snapshot)
22062 };
22063 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22064 && scope.prefers_label_for_snippet_in_completion()
22065 && let Some(label) = completion.label()
22066 && matches!(
22067 completion.kind(),
22068 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22069 )
22070 {
22071 snippet_source = label;
22072 }
22073 match Snippet::parse(&snippet_source).log_err() {
22074 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22075 None => (None, completion.new_text.clone()),
22076 }
22077 } else {
22078 (None, completion.new_text.clone())
22079 };
22080
22081 let mut range_to_replace = {
22082 let replace_range = &completion.replace_range;
22083 if let CompletionSource::Lsp {
22084 insert_range: Some(insert_range),
22085 ..
22086 } = &completion.source
22087 {
22088 debug_assert_eq!(
22089 insert_range.start, replace_range.start,
22090 "insert_range and replace_range should start at the same position"
22091 );
22092 debug_assert!(
22093 insert_range
22094 .start
22095 .cmp(cursor_position, &buffer_snapshot)
22096 .is_le(),
22097 "insert_range should start before or at cursor position"
22098 );
22099 debug_assert!(
22100 replace_range
22101 .start
22102 .cmp(cursor_position, &buffer_snapshot)
22103 .is_le(),
22104 "replace_range should start before or at cursor position"
22105 );
22106
22107 let should_replace = match intent {
22108 CompletionIntent::CompleteWithInsert => false,
22109 CompletionIntent::CompleteWithReplace => true,
22110 CompletionIntent::Complete | CompletionIntent::Compose => {
22111 let insert_mode =
22112 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22113 .completions
22114 .lsp_insert_mode;
22115 match insert_mode {
22116 LspInsertMode::Insert => false,
22117 LspInsertMode::Replace => true,
22118 LspInsertMode::ReplaceSubsequence => {
22119 let mut text_to_replace = buffer.chars_for_range(
22120 buffer.anchor_before(replace_range.start)
22121 ..buffer.anchor_after(replace_range.end),
22122 );
22123 let mut current_needle = text_to_replace.next();
22124 for haystack_ch in completion.label.text.chars() {
22125 if let Some(needle_ch) = current_needle
22126 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22127 {
22128 current_needle = text_to_replace.next();
22129 }
22130 }
22131 current_needle.is_none()
22132 }
22133 LspInsertMode::ReplaceSuffix => {
22134 if replace_range
22135 .end
22136 .cmp(cursor_position, &buffer_snapshot)
22137 .is_gt()
22138 {
22139 let range_after_cursor = *cursor_position..replace_range.end;
22140 let text_after_cursor = buffer
22141 .text_for_range(
22142 buffer.anchor_before(range_after_cursor.start)
22143 ..buffer.anchor_after(range_after_cursor.end),
22144 )
22145 .collect::<String>()
22146 .to_ascii_lowercase();
22147 completion
22148 .label
22149 .text
22150 .to_ascii_lowercase()
22151 .ends_with(&text_after_cursor)
22152 } else {
22153 true
22154 }
22155 }
22156 }
22157 }
22158 };
22159
22160 if should_replace {
22161 replace_range.clone()
22162 } else {
22163 insert_range.clone()
22164 }
22165 } else {
22166 replace_range.clone()
22167 }
22168 };
22169
22170 if range_to_replace
22171 .end
22172 .cmp(cursor_position, &buffer_snapshot)
22173 .is_lt()
22174 {
22175 range_to_replace.end = *cursor_position;
22176 }
22177
22178 CompletionEdit {
22179 new_text,
22180 replace_range: range_to_replace.to_offset(buffer),
22181 snippet,
22182 }
22183}
22184
22185struct CompletionEdit {
22186 new_text: String,
22187 replace_range: Range<usize>,
22188 snippet: Option<Snippet>,
22189}
22190
22191fn insert_extra_newline_brackets(
22192 buffer: &MultiBufferSnapshot,
22193 range: Range<usize>,
22194 language: &language::LanguageScope,
22195) -> bool {
22196 let leading_whitespace_len = buffer
22197 .reversed_chars_at(range.start)
22198 .take_while(|c| c.is_whitespace() && *c != '\n')
22199 .map(|c| c.len_utf8())
22200 .sum::<usize>();
22201 let trailing_whitespace_len = buffer
22202 .chars_at(range.end)
22203 .take_while(|c| c.is_whitespace() && *c != '\n')
22204 .map(|c| c.len_utf8())
22205 .sum::<usize>();
22206 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22207
22208 language.brackets().any(|(pair, enabled)| {
22209 let pair_start = pair.start.trim_end();
22210 let pair_end = pair.end.trim_start();
22211
22212 enabled
22213 && pair.newline
22214 && buffer.contains_str_at(range.end, pair_end)
22215 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22216 })
22217}
22218
22219fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22220 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22221 [(buffer, range, _)] => (*buffer, range.clone()),
22222 _ => return false,
22223 };
22224 let pair = {
22225 let mut result: Option<BracketMatch> = None;
22226
22227 for pair in buffer
22228 .all_bracket_ranges(range.clone())
22229 .filter(move |pair| {
22230 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22231 })
22232 {
22233 let len = pair.close_range.end - pair.open_range.start;
22234
22235 if let Some(existing) = &result {
22236 let existing_len = existing.close_range.end - existing.open_range.start;
22237 if len > existing_len {
22238 continue;
22239 }
22240 }
22241
22242 result = Some(pair);
22243 }
22244
22245 result
22246 };
22247 let Some(pair) = pair else {
22248 return false;
22249 };
22250 pair.newline_only
22251 && buffer
22252 .chars_for_range(pair.open_range.end..range.start)
22253 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22254 .all(|c| c.is_whitespace() && c != '\n')
22255}
22256
22257fn update_uncommitted_diff_for_buffer(
22258 editor: Entity<Editor>,
22259 project: &Entity<Project>,
22260 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22261 buffer: Entity<MultiBuffer>,
22262 cx: &mut App,
22263) -> Task<()> {
22264 let mut tasks = Vec::new();
22265 project.update(cx, |project, cx| {
22266 for buffer in buffers {
22267 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22268 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22269 }
22270 }
22271 });
22272 cx.spawn(async move |cx| {
22273 let diffs = future::join_all(tasks).await;
22274 if editor
22275 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22276 .unwrap_or(false)
22277 {
22278 return;
22279 }
22280
22281 buffer
22282 .update(cx, |buffer, cx| {
22283 for diff in diffs.into_iter().flatten() {
22284 buffer.add_diff(diff, cx);
22285 }
22286 })
22287 .ok();
22288 })
22289}
22290
22291fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22292 let tab_size = tab_size.get() as usize;
22293 let mut width = offset;
22294
22295 for ch in text.chars() {
22296 width += if ch == '\t' {
22297 tab_size - (width % tab_size)
22298 } else {
22299 1
22300 };
22301 }
22302
22303 width - offset
22304}
22305
22306#[cfg(test)]
22307mod tests {
22308 use super::*;
22309
22310 #[test]
22311 fn test_string_size_with_expanded_tabs() {
22312 let nz = |val| NonZeroU32::new(val).unwrap();
22313 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22314 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22315 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22316 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22317 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22318 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22319 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22320 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22321 }
22322}
22323
22324/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22325struct WordBreakingTokenizer<'a> {
22326 input: &'a str,
22327}
22328
22329impl<'a> WordBreakingTokenizer<'a> {
22330 fn new(input: &'a str) -> Self {
22331 Self { input }
22332 }
22333}
22334
22335fn is_char_ideographic(ch: char) -> bool {
22336 use unicode_script::Script::*;
22337 use unicode_script::UnicodeScript;
22338 matches!(ch.script(), Han | Tangut | Yi)
22339}
22340
22341fn is_grapheme_ideographic(text: &str) -> bool {
22342 text.chars().any(is_char_ideographic)
22343}
22344
22345fn is_grapheme_whitespace(text: &str) -> bool {
22346 text.chars().any(|x| x.is_whitespace())
22347}
22348
22349fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22350 text.chars()
22351 .next()
22352 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22353}
22354
22355#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22356enum WordBreakToken<'a> {
22357 Word { token: &'a str, grapheme_len: usize },
22358 InlineWhitespace { token: &'a str, grapheme_len: usize },
22359 Newline,
22360}
22361
22362impl<'a> Iterator for WordBreakingTokenizer<'a> {
22363 /// Yields a span, the count of graphemes in the token, and whether it was
22364 /// whitespace. Note that it also breaks at word boundaries.
22365 type Item = WordBreakToken<'a>;
22366
22367 fn next(&mut self) -> Option<Self::Item> {
22368 use unicode_segmentation::UnicodeSegmentation;
22369 if self.input.is_empty() {
22370 return None;
22371 }
22372
22373 let mut iter = self.input.graphemes(true).peekable();
22374 let mut offset = 0;
22375 let mut grapheme_len = 0;
22376 if let Some(first_grapheme) = iter.next() {
22377 let is_newline = first_grapheme == "\n";
22378 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22379 offset += first_grapheme.len();
22380 grapheme_len += 1;
22381 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22382 if let Some(grapheme) = iter.peek().copied()
22383 && should_stay_with_preceding_ideograph(grapheme)
22384 {
22385 offset += grapheme.len();
22386 grapheme_len += 1;
22387 }
22388 } else {
22389 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22390 let mut next_word_bound = words.peek().copied();
22391 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22392 next_word_bound = words.next();
22393 }
22394 while let Some(grapheme) = iter.peek().copied() {
22395 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22396 break;
22397 };
22398 if is_grapheme_whitespace(grapheme) != is_whitespace
22399 || (grapheme == "\n") != is_newline
22400 {
22401 break;
22402 };
22403 offset += grapheme.len();
22404 grapheme_len += 1;
22405 iter.next();
22406 }
22407 }
22408 let token = &self.input[..offset];
22409 self.input = &self.input[offset..];
22410 if token == "\n" {
22411 Some(WordBreakToken::Newline)
22412 } else if is_whitespace {
22413 Some(WordBreakToken::InlineWhitespace {
22414 token,
22415 grapheme_len,
22416 })
22417 } else {
22418 Some(WordBreakToken::Word {
22419 token,
22420 grapheme_len,
22421 })
22422 }
22423 } else {
22424 None
22425 }
22426 }
22427}
22428
22429#[test]
22430fn test_word_breaking_tokenizer() {
22431 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22432 ("", &[]),
22433 (" ", &[whitespace(" ", 2)]),
22434 ("Ʒ", &[word("Ʒ", 1)]),
22435 ("Ǽ", &[word("Ǽ", 1)]),
22436 ("⋑", &[word("⋑", 1)]),
22437 ("⋑⋑", &[word("⋑⋑", 2)]),
22438 (
22439 "原理,进而",
22440 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22441 ),
22442 (
22443 "hello world",
22444 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22445 ),
22446 (
22447 "hello, world",
22448 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22449 ),
22450 (
22451 " hello world",
22452 &[
22453 whitespace(" ", 2),
22454 word("hello", 5),
22455 whitespace(" ", 1),
22456 word("world", 5),
22457 ],
22458 ),
22459 (
22460 "这是什么 \n 钢笔",
22461 &[
22462 word("这", 1),
22463 word("是", 1),
22464 word("什", 1),
22465 word("么", 1),
22466 whitespace(" ", 1),
22467 newline(),
22468 whitespace(" ", 1),
22469 word("钢", 1),
22470 word("笔", 1),
22471 ],
22472 ),
22473 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22474 ];
22475
22476 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22477 WordBreakToken::Word {
22478 token,
22479 grapheme_len,
22480 }
22481 }
22482
22483 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22484 WordBreakToken::InlineWhitespace {
22485 token,
22486 grapheme_len,
22487 }
22488 }
22489
22490 fn newline() -> WordBreakToken<'static> {
22491 WordBreakToken::Newline
22492 }
22493
22494 for (input, result) in tests {
22495 assert_eq!(
22496 WordBreakingTokenizer::new(input)
22497 .collect::<Vec<_>>()
22498 .as_slice(),
22499 *result,
22500 );
22501 }
22502}
22503
22504fn wrap_with_prefix(
22505 first_line_prefix: String,
22506 subsequent_lines_prefix: String,
22507 unwrapped_text: String,
22508 wrap_column: usize,
22509 tab_size: NonZeroU32,
22510 preserve_existing_whitespace: bool,
22511) -> String {
22512 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22513 let subsequent_lines_prefix_len =
22514 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22515 let mut wrapped_text = String::new();
22516 let mut current_line = first_line_prefix;
22517 let mut is_first_line = true;
22518
22519 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22520 let mut current_line_len = first_line_prefix_len;
22521 let mut in_whitespace = false;
22522 for token in tokenizer {
22523 let have_preceding_whitespace = in_whitespace;
22524 match token {
22525 WordBreakToken::Word {
22526 token,
22527 grapheme_len,
22528 } => {
22529 in_whitespace = false;
22530 let current_prefix_len = if is_first_line {
22531 first_line_prefix_len
22532 } else {
22533 subsequent_lines_prefix_len
22534 };
22535 if current_line_len + grapheme_len > wrap_column
22536 && current_line_len != current_prefix_len
22537 {
22538 wrapped_text.push_str(current_line.trim_end());
22539 wrapped_text.push('\n');
22540 is_first_line = false;
22541 current_line = subsequent_lines_prefix.clone();
22542 current_line_len = subsequent_lines_prefix_len;
22543 }
22544 current_line.push_str(token);
22545 current_line_len += grapheme_len;
22546 }
22547 WordBreakToken::InlineWhitespace {
22548 mut token,
22549 mut grapheme_len,
22550 } => {
22551 in_whitespace = true;
22552 if have_preceding_whitespace && !preserve_existing_whitespace {
22553 continue;
22554 }
22555 if !preserve_existing_whitespace {
22556 // Keep a single whitespace grapheme as-is
22557 if let Some(first) =
22558 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22559 {
22560 token = first;
22561 } else {
22562 token = " ";
22563 }
22564 grapheme_len = 1;
22565 }
22566 let current_prefix_len = if is_first_line {
22567 first_line_prefix_len
22568 } else {
22569 subsequent_lines_prefix_len
22570 };
22571 if current_line_len + grapheme_len > wrap_column {
22572 wrapped_text.push_str(current_line.trim_end());
22573 wrapped_text.push('\n');
22574 is_first_line = false;
22575 current_line = subsequent_lines_prefix.clone();
22576 current_line_len = subsequent_lines_prefix_len;
22577 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22578 current_line.push_str(token);
22579 current_line_len += grapheme_len;
22580 }
22581 }
22582 WordBreakToken::Newline => {
22583 in_whitespace = true;
22584 let current_prefix_len = if is_first_line {
22585 first_line_prefix_len
22586 } else {
22587 subsequent_lines_prefix_len
22588 };
22589 if preserve_existing_whitespace {
22590 wrapped_text.push_str(current_line.trim_end());
22591 wrapped_text.push('\n');
22592 is_first_line = false;
22593 current_line = subsequent_lines_prefix.clone();
22594 current_line_len = subsequent_lines_prefix_len;
22595 } else if have_preceding_whitespace {
22596 continue;
22597 } else if current_line_len + 1 > wrap_column
22598 && current_line_len != current_prefix_len
22599 {
22600 wrapped_text.push_str(current_line.trim_end());
22601 wrapped_text.push('\n');
22602 is_first_line = false;
22603 current_line = subsequent_lines_prefix.clone();
22604 current_line_len = subsequent_lines_prefix_len;
22605 } else if current_line_len != current_prefix_len {
22606 current_line.push(' ');
22607 current_line_len += 1;
22608 }
22609 }
22610 }
22611 }
22612
22613 if !current_line.is_empty() {
22614 wrapped_text.push_str(¤t_line);
22615 }
22616 wrapped_text
22617}
22618
22619#[test]
22620fn test_wrap_with_prefix() {
22621 assert_eq!(
22622 wrap_with_prefix(
22623 "# ".to_string(),
22624 "# ".to_string(),
22625 "abcdefg".to_string(),
22626 4,
22627 NonZeroU32::new(4).unwrap(),
22628 false,
22629 ),
22630 "# abcdefg"
22631 );
22632 assert_eq!(
22633 wrap_with_prefix(
22634 "".to_string(),
22635 "".to_string(),
22636 "\thello world".to_string(),
22637 8,
22638 NonZeroU32::new(4).unwrap(),
22639 false,
22640 ),
22641 "hello\nworld"
22642 );
22643 assert_eq!(
22644 wrap_with_prefix(
22645 "// ".to_string(),
22646 "// ".to_string(),
22647 "xx \nyy zz aa bb cc".to_string(),
22648 12,
22649 NonZeroU32::new(4).unwrap(),
22650 false,
22651 ),
22652 "// xx yy zz\n// aa bb cc"
22653 );
22654 assert_eq!(
22655 wrap_with_prefix(
22656 String::new(),
22657 String::new(),
22658 "这是什么 \n 钢笔".to_string(),
22659 3,
22660 NonZeroU32::new(4).unwrap(),
22661 false,
22662 ),
22663 "这是什\n么 钢\n笔"
22664 );
22665 assert_eq!(
22666 wrap_with_prefix(
22667 String::new(),
22668 String::new(),
22669 format!("foo{}bar", '\u{2009}'), // thin space
22670 80,
22671 NonZeroU32::new(4).unwrap(),
22672 false,
22673 ),
22674 format!("foo{}bar", '\u{2009}')
22675 );
22676}
22677
22678pub trait CollaborationHub {
22679 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22680 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22681 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22682}
22683
22684impl CollaborationHub for Entity<Project> {
22685 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22686 self.read(cx).collaborators()
22687 }
22688
22689 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22690 self.read(cx).user_store().read(cx).participant_indices()
22691 }
22692
22693 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22694 let this = self.read(cx);
22695 let user_ids = this.collaborators().values().map(|c| c.user_id);
22696 this.user_store().read(cx).participant_names(user_ids, cx)
22697 }
22698}
22699
22700pub trait SemanticsProvider {
22701 fn hover(
22702 &self,
22703 buffer: &Entity<Buffer>,
22704 position: text::Anchor,
22705 cx: &mut App,
22706 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22707
22708 fn inline_values(
22709 &self,
22710 buffer_handle: Entity<Buffer>,
22711 range: Range<text::Anchor>,
22712 cx: &mut App,
22713 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22714
22715 fn inlay_hints(
22716 &self,
22717 buffer_handle: Entity<Buffer>,
22718 range: Range<text::Anchor>,
22719 cx: &mut App,
22720 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22721
22722 fn resolve_inlay_hint(
22723 &self,
22724 hint: InlayHint,
22725 buffer_handle: Entity<Buffer>,
22726 server_id: LanguageServerId,
22727 cx: &mut App,
22728 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22729
22730 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22731
22732 fn document_highlights(
22733 &self,
22734 buffer: &Entity<Buffer>,
22735 position: text::Anchor,
22736 cx: &mut App,
22737 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22738
22739 fn definitions(
22740 &self,
22741 buffer: &Entity<Buffer>,
22742 position: text::Anchor,
22743 kind: GotoDefinitionKind,
22744 cx: &mut App,
22745 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22746
22747 fn range_for_rename(
22748 &self,
22749 buffer: &Entity<Buffer>,
22750 position: text::Anchor,
22751 cx: &mut App,
22752 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22753
22754 fn perform_rename(
22755 &self,
22756 buffer: &Entity<Buffer>,
22757 position: text::Anchor,
22758 new_name: String,
22759 cx: &mut App,
22760 ) -> Option<Task<Result<ProjectTransaction>>>;
22761}
22762
22763pub trait CompletionProvider {
22764 fn completions(
22765 &self,
22766 excerpt_id: ExcerptId,
22767 buffer: &Entity<Buffer>,
22768 buffer_position: text::Anchor,
22769 trigger: CompletionContext,
22770 window: &mut Window,
22771 cx: &mut Context<Editor>,
22772 ) -> Task<Result<Vec<CompletionResponse>>>;
22773
22774 fn resolve_completions(
22775 &self,
22776 _buffer: Entity<Buffer>,
22777 _completion_indices: Vec<usize>,
22778 _completions: Rc<RefCell<Box<[Completion]>>>,
22779 _cx: &mut Context<Editor>,
22780 ) -> Task<Result<bool>> {
22781 Task::ready(Ok(false))
22782 }
22783
22784 fn apply_additional_edits_for_completion(
22785 &self,
22786 _buffer: Entity<Buffer>,
22787 _completions: Rc<RefCell<Box<[Completion]>>>,
22788 _completion_index: usize,
22789 _push_to_history: bool,
22790 _cx: &mut Context<Editor>,
22791 ) -> Task<Result<Option<language::Transaction>>> {
22792 Task::ready(Ok(None))
22793 }
22794
22795 fn is_completion_trigger(
22796 &self,
22797 buffer: &Entity<Buffer>,
22798 position: language::Anchor,
22799 text: &str,
22800 trigger_in_words: bool,
22801 menu_is_open: bool,
22802 cx: &mut Context<Editor>,
22803 ) -> bool;
22804
22805 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22806
22807 fn sort_completions(&self) -> bool {
22808 true
22809 }
22810
22811 fn filter_completions(&self) -> bool {
22812 true
22813 }
22814}
22815
22816pub trait CodeActionProvider {
22817 fn id(&self) -> Arc<str>;
22818
22819 fn code_actions(
22820 &self,
22821 buffer: &Entity<Buffer>,
22822 range: Range<text::Anchor>,
22823 window: &mut Window,
22824 cx: &mut App,
22825 ) -> Task<Result<Vec<CodeAction>>>;
22826
22827 fn apply_code_action(
22828 &self,
22829 buffer_handle: Entity<Buffer>,
22830 action: CodeAction,
22831 excerpt_id: ExcerptId,
22832 push_to_history: bool,
22833 window: &mut Window,
22834 cx: &mut App,
22835 ) -> Task<Result<ProjectTransaction>>;
22836}
22837
22838impl CodeActionProvider for Entity<Project> {
22839 fn id(&self) -> Arc<str> {
22840 "project".into()
22841 }
22842
22843 fn code_actions(
22844 &self,
22845 buffer: &Entity<Buffer>,
22846 range: Range<text::Anchor>,
22847 _window: &mut Window,
22848 cx: &mut App,
22849 ) -> Task<Result<Vec<CodeAction>>> {
22850 self.update(cx, |project, cx| {
22851 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22852 let code_actions = project.code_actions(buffer, range, None, cx);
22853 cx.background_spawn(async move {
22854 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22855 Ok(code_lens_actions
22856 .context("code lens fetch")?
22857 .into_iter()
22858 .flatten()
22859 .chain(
22860 code_actions
22861 .context("code action fetch")?
22862 .into_iter()
22863 .flatten(),
22864 )
22865 .collect())
22866 })
22867 })
22868 }
22869
22870 fn apply_code_action(
22871 &self,
22872 buffer_handle: Entity<Buffer>,
22873 action: CodeAction,
22874 _excerpt_id: ExcerptId,
22875 push_to_history: bool,
22876 _window: &mut Window,
22877 cx: &mut App,
22878 ) -> Task<Result<ProjectTransaction>> {
22879 self.update(cx, |project, cx| {
22880 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22881 })
22882 }
22883}
22884
22885fn snippet_completions(
22886 project: &Project,
22887 buffer: &Entity<Buffer>,
22888 buffer_position: text::Anchor,
22889 cx: &mut App,
22890) -> Task<Result<CompletionResponse>> {
22891 let languages = buffer.read(cx).languages_at(buffer_position);
22892 let snippet_store = project.snippets().read(cx);
22893
22894 let scopes: Vec<_> = languages
22895 .iter()
22896 .filter_map(|language| {
22897 let language_name = language.lsp_id();
22898 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22899
22900 if snippets.is_empty() {
22901 None
22902 } else {
22903 Some((language.default_scope(), snippets))
22904 }
22905 })
22906 .collect();
22907
22908 if scopes.is_empty() {
22909 return Task::ready(Ok(CompletionResponse {
22910 completions: vec![],
22911 display_options: CompletionDisplayOptions::default(),
22912 is_incomplete: false,
22913 }));
22914 }
22915
22916 let snapshot = buffer.read(cx).text_snapshot();
22917 let executor = cx.background_executor().clone();
22918
22919 cx.background_spawn(async move {
22920 let mut is_incomplete = false;
22921 let mut completions: Vec<Completion> = Vec::new();
22922 for (scope, snippets) in scopes.into_iter() {
22923 let classifier =
22924 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22925
22926 const MAX_WORD_PREFIX_LEN: usize = 128;
22927 let last_word: String = snapshot
22928 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22929 .take(MAX_WORD_PREFIX_LEN)
22930 .take_while(|c| classifier.is_word(*c))
22931 .collect::<String>()
22932 .chars()
22933 .rev()
22934 .collect();
22935
22936 if last_word.is_empty() {
22937 return Ok(CompletionResponse {
22938 completions: vec![],
22939 display_options: CompletionDisplayOptions::default(),
22940 is_incomplete: true,
22941 });
22942 }
22943
22944 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22945 let to_lsp = |point: &text::Anchor| {
22946 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22947 point_to_lsp(end)
22948 };
22949 let lsp_end = to_lsp(&buffer_position);
22950
22951 let candidates = snippets
22952 .iter()
22953 .enumerate()
22954 .flat_map(|(ix, snippet)| {
22955 snippet
22956 .prefix
22957 .iter()
22958 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22959 })
22960 .collect::<Vec<StringMatchCandidate>>();
22961
22962 const MAX_RESULTS: usize = 100;
22963 let mut matches = fuzzy::match_strings(
22964 &candidates,
22965 &last_word,
22966 last_word.chars().any(|c| c.is_uppercase()),
22967 true,
22968 MAX_RESULTS,
22969 &Default::default(),
22970 executor.clone(),
22971 )
22972 .await;
22973
22974 if matches.len() >= MAX_RESULTS {
22975 is_incomplete = true;
22976 }
22977
22978 // Remove all candidates where the query's start does not match the start of any word in the candidate
22979 if let Some(query_start) = last_word.chars().next() {
22980 matches.retain(|string_match| {
22981 split_words(&string_match.string).any(|word| {
22982 // Check that the first codepoint of the word as lowercase matches the first
22983 // codepoint of the query as lowercase
22984 word.chars()
22985 .flat_map(|codepoint| codepoint.to_lowercase())
22986 .zip(query_start.to_lowercase())
22987 .all(|(word_cp, query_cp)| word_cp == query_cp)
22988 })
22989 });
22990 }
22991
22992 let matched_strings = matches
22993 .into_iter()
22994 .map(|m| m.string)
22995 .collect::<HashSet<_>>();
22996
22997 completions.extend(snippets.iter().filter_map(|snippet| {
22998 let matching_prefix = snippet
22999 .prefix
23000 .iter()
23001 .find(|prefix| matched_strings.contains(*prefix))?;
23002 let start = as_offset - last_word.len();
23003 let start = snapshot.anchor_before(start);
23004 let range = start..buffer_position;
23005 let lsp_start = to_lsp(&start);
23006 let lsp_range = lsp::Range {
23007 start: lsp_start,
23008 end: lsp_end,
23009 };
23010 Some(Completion {
23011 replace_range: range,
23012 new_text: snippet.body.clone(),
23013 source: CompletionSource::Lsp {
23014 insert_range: None,
23015 server_id: LanguageServerId(usize::MAX),
23016 resolved: true,
23017 lsp_completion: Box::new(lsp::CompletionItem {
23018 label: snippet.prefix.first().unwrap().clone(),
23019 kind: Some(CompletionItemKind::SNIPPET),
23020 label_details: snippet.description.as_ref().map(|description| {
23021 lsp::CompletionItemLabelDetails {
23022 detail: Some(description.clone()),
23023 description: None,
23024 }
23025 }),
23026 insert_text_format: Some(InsertTextFormat::SNIPPET),
23027 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23028 lsp::InsertReplaceEdit {
23029 new_text: snippet.body.clone(),
23030 insert: lsp_range,
23031 replace: lsp_range,
23032 },
23033 )),
23034 filter_text: Some(snippet.body.clone()),
23035 sort_text: Some(char::MAX.to_string()),
23036 ..lsp::CompletionItem::default()
23037 }),
23038 lsp_defaults: None,
23039 },
23040 label: CodeLabel {
23041 text: matching_prefix.clone(),
23042 runs: Vec::new(),
23043 filter_range: 0..matching_prefix.len(),
23044 },
23045 icon_path: None,
23046 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23047 single_line: snippet.name.clone().into(),
23048 plain_text: snippet
23049 .description
23050 .clone()
23051 .map(|description| description.into()),
23052 }),
23053 insert_text_mode: None,
23054 confirm: None,
23055 })
23056 }))
23057 }
23058
23059 Ok(CompletionResponse {
23060 completions,
23061 display_options: CompletionDisplayOptions::default(),
23062 is_incomplete,
23063 })
23064 })
23065}
23066
23067impl CompletionProvider for Entity<Project> {
23068 fn completions(
23069 &self,
23070 _excerpt_id: ExcerptId,
23071 buffer: &Entity<Buffer>,
23072 buffer_position: text::Anchor,
23073 options: CompletionContext,
23074 _window: &mut Window,
23075 cx: &mut Context<Editor>,
23076 ) -> Task<Result<Vec<CompletionResponse>>> {
23077 self.update(cx, |project, cx| {
23078 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23079 let project_completions = project.completions(buffer, buffer_position, options, cx);
23080 cx.background_spawn(async move {
23081 let mut responses = project_completions.await?;
23082 let snippets = snippets.await?;
23083 if !snippets.completions.is_empty() {
23084 responses.push(snippets);
23085 }
23086 Ok(responses)
23087 })
23088 })
23089 }
23090
23091 fn resolve_completions(
23092 &self,
23093 buffer: Entity<Buffer>,
23094 completion_indices: Vec<usize>,
23095 completions: Rc<RefCell<Box<[Completion]>>>,
23096 cx: &mut Context<Editor>,
23097 ) -> Task<Result<bool>> {
23098 self.update(cx, |project, cx| {
23099 project.lsp_store().update(cx, |lsp_store, cx| {
23100 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23101 })
23102 })
23103 }
23104
23105 fn apply_additional_edits_for_completion(
23106 &self,
23107 buffer: Entity<Buffer>,
23108 completions: Rc<RefCell<Box<[Completion]>>>,
23109 completion_index: usize,
23110 push_to_history: bool,
23111 cx: &mut Context<Editor>,
23112 ) -> Task<Result<Option<language::Transaction>>> {
23113 self.update(cx, |project, cx| {
23114 project.lsp_store().update(cx, |lsp_store, cx| {
23115 lsp_store.apply_additional_edits_for_completion(
23116 buffer,
23117 completions,
23118 completion_index,
23119 push_to_history,
23120 cx,
23121 )
23122 })
23123 })
23124 }
23125
23126 fn is_completion_trigger(
23127 &self,
23128 buffer: &Entity<Buffer>,
23129 position: language::Anchor,
23130 text: &str,
23131 trigger_in_words: bool,
23132 menu_is_open: bool,
23133 cx: &mut Context<Editor>,
23134 ) -> bool {
23135 let mut chars = text.chars();
23136 let char = if let Some(char) = chars.next() {
23137 char
23138 } else {
23139 return false;
23140 };
23141 if chars.next().is_some() {
23142 return false;
23143 }
23144
23145 let buffer = buffer.read(cx);
23146 let snapshot = buffer.snapshot();
23147 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23148 return false;
23149 }
23150 let classifier = snapshot
23151 .char_classifier_at(position)
23152 .scope_context(Some(CharScopeContext::Completion));
23153 if trigger_in_words && classifier.is_word(char) {
23154 return true;
23155 }
23156
23157 buffer.completion_triggers().contains(text)
23158 }
23159}
23160
23161impl SemanticsProvider for Entity<Project> {
23162 fn hover(
23163 &self,
23164 buffer: &Entity<Buffer>,
23165 position: text::Anchor,
23166 cx: &mut App,
23167 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23168 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23169 }
23170
23171 fn document_highlights(
23172 &self,
23173 buffer: &Entity<Buffer>,
23174 position: text::Anchor,
23175 cx: &mut App,
23176 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23177 Some(self.update(cx, |project, cx| {
23178 project.document_highlights(buffer, position, cx)
23179 }))
23180 }
23181
23182 fn definitions(
23183 &self,
23184 buffer: &Entity<Buffer>,
23185 position: text::Anchor,
23186 kind: GotoDefinitionKind,
23187 cx: &mut App,
23188 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23189 Some(self.update(cx, |project, cx| match kind {
23190 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23191 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23192 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23193 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23194 }))
23195 }
23196
23197 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23198 self.update(cx, |project, cx| {
23199 if project
23200 .active_debug_session(cx)
23201 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23202 {
23203 return true;
23204 }
23205
23206 buffer.update(cx, |buffer, cx| {
23207 project.any_language_server_supports_inlay_hints(buffer, cx)
23208 })
23209 })
23210 }
23211
23212 fn inline_values(
23213 &self,
23214 buffer_handle: Entity<Buffer>,
23215 range: Range<text::Anchor>,
23216 cx: &mut App,
23217 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23218 self.update(cx, |project, cx| {
23219 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23220
23221 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23222 })
23223 }
23224
23225 fn inlay_hints(
23226 &self,
23227 buffer_handle: Entity<Buffer>,
23228 range: Range<text::Anchor>,
23229 cx: &mut App,
23230 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23231 Some(self.update(cx, |project, cx| {
23232 project.inlay_hints(buffer_handle, range, cx)
23233 }))
23234 }
23235
23236 fn resolve_inlay_hint(
23237 &self,
23238 hint: InlayHint,
23239 buffer_handle: Entity<Buffer>,
23240 server_id: LanguageServerId,
23241 cx: &mut App,
23242 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23243 Some(self.update(cx, |project, cx| {
23244 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23245 }))
23246 }
23247
23248 fn range_for_rename(
23249 &self,
23250 buffer: &Entity<Buffer>,
23251 position: text::Anchor,
23252 cx: &mut App,
23253 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23254 Some(self.update(cx, |project, cx| {
23255 let buffer = buffer.clone();
23256 let task = project.prepare_rename(buffer.clone(), position, cx);
23257 cx.spawn(async move |_, cx| {
23258 Ok(match task.await? {
23259 PrepareRenameResponse::Success(range) => Some(range),
23260 PrepareRenameResponse::InvalidPosition => None,
23261 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23262 // Fallback on using TreeSitter info to determine identifier range
23263 buffer.read_with(cx, |buffer, _| {
23264 let snapshot = buffer.snapshot();
23265 let (range, kind) = snapshot.surrounding_word(position, None);
23266 if kind != Some(CharKind::Word) {
23267 return None;
23268 }
23269 Some(
23270 snapshot.anchor_before(range.start)
23271 ..snapshot.anchor_after(range.end),
23272 )
23273 })?
23274 }
23275 })
23276 })
23277 }))
23278 }
23279
23280 fn perform_rename(
23281 &self,
23282 buffer: &Entity<Buffer>,
23283 position: text::Anchor,
23284 new_name: String,
23285 cx: &mut App,
23286 ) -> Option<Task<Result<ProjectTransaction>>> {
23287 Some(self.update(cx, |project, cx| {
23288 project.perform_rename(buffer.clone(), position, new_name, cx)
23289 }))
23290 }
23291}
23292
23293fn inlay_hint_settings(
23294 location: Anchor,
23295 snapshot: &MultiBufferSnapshot,
23296 cx: &mut Context<Editor>,
23297) -> InlayHintSettings {
23298 let file = snapshot.file_at(location);
23299 let language = snapshot.language_at(location).map(|l| l.name());
23300 language_settings(language, file, cx).inlay_hints
23301}
23302
23303fn consume_contiguous_rows(
23304 contiguous_row_selections: &mut Vec<Selection<Point>>,
23305 selection: &Selection<Point>,
23306 display_map: &DisplaySnapshot,
23307 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23308) -> (MultiBufferRow, MultiBufferRow) {
23309 contiguous_row_selections.push(selection.clone());
23310 let start_row = starting_row(selection, display_map);
23311 let mut end_row = ending_row(selection, display_map);
23312
23313 while let Some(next_selection) = selections.peek() {
23314 if next_selection.start.row <= end_row.0 {
23315 end_row = ending_row(next_selection, display_map);
23316 contiguous_row_selections.push(selections.next().unwrap().clone());
23317 } else {
23318 break;
23319 }
23320 }
23321 (start_row, end_row)
23322}
23323
23324fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23325 if selection.start.column > 0 {
23326 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23327 } else {
23328 MultiBufferRow(selection.start.row)
23329 }
23330}
23331
23332fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23333 if next_selection.end.column > 0 || next_selection.is_empty() {
23334 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23335 } else {
23336 MultiBufferRow(next_selection.end.row)
23337 }
23338}
23339
23340impl EditorSnapshot {
23341 pub fn remote_selections_in_range<'a>(
23342 &'a self,
23343 range: &'a Range<Anchor>,
23344 collaboration_hub: &dyn CollaborationHub,
23345 cx: &'a App,
23346 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23347 let participant_names = collaboration_hub.user_names(cx);
23348 let participant_indices = collaboration_hub.user_participant_indices(cx);
23349 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23350 let collaborators_by_replica_id = collaborators_by_peer_id
23351 .values()
23352 .map(|collaborator| (collaborator.replica_id, collaborator))
23353 .collect::<HashMap<_, _>>();
23354 self.buffer_snapshot()
23355 .selections_in_range(range, false)
23356 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23357 if replica_id == AGENT_REPLICA_ID {
23358 Some(RemoteSelection {
23359 replica_id,
23360 selection,
23361 cursor_shape,
23362 line_mode,
23363 collaborator_id: CollaboratorId::Agent,
23364 user_name: Some("Agent".into()),
23365 color: cx.theme().players().agent(),
23366 })
23367 } else {
23368 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23369 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23370 let user_name = participant_names.get(&collaborator.user_id).cloned();
23371 Some(RemoteSelection {
23372 replica_id,
23373 selection,
23374 cursor_shape,
23375 line_mode,
23376 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23377 user_name,
23378 color: if let Some(index) = participant_index {
23379 cx.theme().players().color_for_participant(index.0)
23380 } else {
23381 cx.theme().players().absent()
23382 },
23383 })
23384 }
23385 })
23386 }
23387
23388 pub fn hunks_for_ranges(
23389 &self,
23390 ranges: impl IntoIterator<Item = Range<Point>>,
23391 ) -> Vec<MultiBufferDiffHunk> {
23392 let mut hunks = Vec::new();
23393 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23394 HashMap::default();
23395 for query_range in ranges {
23396 let query_rows =
23397 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23398 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23399 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23400 ) {
23401 // Include deleted hunks that are adjacent to the query range, because
23402 // otherwise they would be missed.
23403 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23404 if hunk.status().is_deleted() {
23405 intersects_range |= hunk.row_range.start == query_rows.end;
23406 intersects_range |= hunk.row_range.end == query_rows.start;
23407 }
23408 if intersects_range {
23409 if !processed_buffer_rows
23410 .entry(hunk.buffer_id)
23411 .or_default()
23412 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23413 {
23414 continue;
23415 }
23416 hunks.push(hunk);
23417 }
23418 }
23419 }
23420
23421 hunks
23422 }
23423
23424 fn display_diff_hunks_for_rows<'a>(
23425 &'a self,
23426 display_rows: Range<DisplayRow>,
23427 folded_buffers: &'a HashSet<BufferId>,
23428 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23429 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23430 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23431
23432 self.buffer_snapshot()
23433 .diff_hunks_in_range(buffer_start..buffer_end)
23434 .filter_map(|hunk| {
23435 if folded_buffers.contains(&hunk.buffer_id) {
23436 return None;
23437 }
23438
23439 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23440 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23441
23442 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23443 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23444
23445 let display_hunk = if hunk_display_start.column() != 0 {
23446 DisplayDiffHunk::Folded {
23447 display_row: hunk_display_start.row(),
23448 }
23449 } else {
23450 let mut end_row = hunk_display_end.row();
23451 if hunk_display_end.column() > 0 {
23452 end_row.0 += 1;
23453 }
23454 let is_created_file = hunk.is_created_file();
23455 DisplayDiffHunk::Unfolded {
23456 status: hunk.status(),
23457 diff_base_byte_range: hunk.diff_base_byte_range,
23458 display_row_range: hunk_display_start.row()..end_row,
23459 multi_buffer_range: Anchor::range_in_buffer(
23460 hunk.excerpt_id,
23461 hunk.buffer_id,
23462 hunk.buffer_range,
23463 ),
23464 is_created_file,
23465 }
23466 };
23467
23468 Some(display_hunk)
23469 })
23470 }
23471
23472 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23473 self.display_snapshot
23474 .buffer_snapshot()
23475 .language_at(position)
23476 }
23477
23478 pub fn is_focused(&self) -> bool {
23479 self.is_focused
23480 }
23481
23482 pub fn placeholder_text(&self) -> Option<String> {
23483 self.placeholder_display_snapshot
23484 .as_ref()
23485 .map(|display_map| display_map.text())
23486 }
23487
23488 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23489 self.scroll_anchor.scroll_position(&self.display_snapshot)
23490 }
23491
23492 fn gutter_dimensions(
23493 &self,
23494 font_id: FontId,
23495 font_size: Pixels,
23496 max_line_number_width: Pixels,
23497 cx: &App,
23498 ) -> Option<GutterDimensions> {
23499 if !self.show_gutter {
23500 return None;
23501 }
23502
23503 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23504 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23505
23506 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23507 matches!(
23508 ProjectSettings::get_global(cx).git.git_gutter,
23509 GitGutterSetting::TrackedFiles
23510 )
23511 });
23512 let gutter_settings = EditorSettings::get_global(cx).gutter;
23513 let show_line_numbers = self
23514 .show_line_numbers
23515 .unwrap_or(gutter_settings.line_numbers);
23516 let line_gutter_width = if show_line_numbers {
23517 // Avoid flicker-like gutter resizes when the line number gains another digit by
23518 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23519 let min_width_for_number_on_gutter =
23520 ch_advance * gutter_settings.min_line_number_digits as f32;
23521 max_line_number_width.max(min_width_for_number_on_gutter)
23522 } else {
23523 0.0.into()
23524 };
23525
23526 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23527 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23528
23529 let git_blame_entries_width =
23530 self.git_blame_gutter_max_author_length
23531 .map(|max_author_length| {
23532 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23533 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23534
23535 /// The number of characters to dedicate to gaps and margins.
23536 const SPACING_WIDTH: usize = 4;
23537
23538 let max_char_count = max_author_length.min(renderer.max_author_length())
23539 + ::git::SHORT_SHA_LENGTH
23540 + MAX_RELATIVE_TIMESTAMP.len()
23541 + SPACING_WIDTH;
23542
23543 ch_advance * max_char_count
23544 });
23545
23546 let is_singleton = self.buffer_snapshot().is_singleton();
23547
23548 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23549 left_padding += if !is_singleton {
23550 ch_width * 4.0
23551 } else if show_runnables || show_breakpoints {
23552 ch_width * 3.0
23553 } else if show_git_gutter && show_line_numbers {
23554 ch_width * 2.0
23555 } else if show_git_gutter || show_line_numbers {
23556 ch_width
23557 } else {
23558 px(0.)
23559 };
23560
23561 let shows_folds = is_singleton && gutter_settings.folds;
23562
23563 let right_padding = if shows_folds && show_line_numbers {
23564 ch_width * 4.0
23565 } else if shows_folds || (!is_singleton && show_line_numbers) {
23566 ch_width * 3.0
23567 } else if show_line_numbers {
23568 ch_width
23569 } else {
23570 px(0.)
23571 };
23572
23573 Some(GutterDimensions {
23574 left_padding,
23575 right_padding,
23576 width: line_gutter_width + left_padding + right_padding,
23577 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23578 git_blame_entries_width,
23579 })
23580 }
23581
23582 pub fn render_crease_toggle(
23583 &self,
23584 buffer_row: MultiBufferRow,
23585 row_contains_cursor: bool,
23586 editor: Entity<Editor>,
23587 window: &mut Window,
23588 cx: &mut App,
23589 ) -> Option<AnyElement> {
23590 let folded = self.is_line_folded(buffer_row);
23591 let mut is_foldable = false;
23592
23593 if let Some(crease) = self
23594 .crease_snapshot
23595 .query_row(buffer_row, self.buffer_snapshot())
23596 {
23597 is_foldable = true;
23598 match crease {
23599 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23600 if let Some(render_toggle) = render_toggle {
23601 let toggle_callback =
23602 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23603 if folded {
23604 editor.update(cx, |editor, cx| {
23605 editor.fold_at(buffer_row, window, cx)
23606 });
23607 } else {
23608 editor.update(cx, |editor, cx| {
23609 editor.unfold_at(buffer_row, window, cx)
23610 });
23611 }
23612 });
23613 return Some((render_toggle)(
23614 buffer_row,
23615 folded,
23616 toggle_callback,
23617 window,
23618 cx,
23619 ));
23620 }
23621 }
23622 }
23623 }
23624
23625 is_foldable |= self.starts_indent(buffer_row);
23626
23627 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23628 Some(
23629 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23630 .toggle_state(folded)
23631 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23632 if folded {
23633 this.unfold_at(buffer_row, window, cx);
23634 } else {
23635 this.fold_at(buffer_row, window, cx);
23636 }
23637 }))
23638 .into_any_element(),
23639 )
23640 } else {
23641 None
23642 }
23643 }
23644
23645 pub fn render_crease_trailer(
23646 &self,
23647 buffer_row: MultiBufferRow,
23648 window: &mut Window,
23649 cx: &mut App,
23650 ) -> Option<AnyElement> {
23651 let folded = self.is_line_folded(buffer_row);
23652 if let Crease::Inline { render_trailer, .. } = self
23653 .crease_snapshot
23654 .query_row(buffer_row, self.buffer_snapshot())?
23655 {
23656 let render_trailer = render_trailer.as_ref()?;
23657 Some(render_trailer(buffer_row, folded, window, cx))
23658 } else {
23659 None
23660 }
23661 }
23662}
23663
23664impl Deref for EditorSnapshot {
23665 type Target = DisplaySnapshot;
23666
23667 fn deref(&self) -> &Self::Target {
23668 &self.display_snapshot
23669 }
23670}
23671
23672#[derive(Clone, Debug, PartialEq, Eq)]
23673pub enum EditorEvent {
23674 InputIgnored {
23675 text: Arc<str>,
23676 },
23677 InputHandled {
23678 utf16_range_to_replace: Option<Range<isize>>,
23679 text: Arc<str>,
23680 },
23681 ExcerptsAdded {
23682 buffer: Entity<Buffer>,
23683 predecessor: ExcerptId,
23684 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23685 },
23686 ExcerptsRemoved {
23687 ids: Vec<ExcerptId>,
23688 removed_buffer_ids: Vec<BufferId>,
23689 },
23690 BufferFoldToggled {
23691 ids: Vec<ExcerptId>,
23692 folded: bool,
23693 },
23694 ExcerptsEdited {
23695 ids: Vec<ExcerptId>,
23696 },
23697 ExcerptsExpanded {
23698 ids: Vec<ExcerptId>,
23699 },
23700 BufferEdited,
23701 Edited {
23702 transaction_id: clock::Lamport,
23703 },
23704 Reparsed(BufferId),
23705 Focused,
23706 FocusedIn,
23707 Blurred,
23708 DirtyChanged,
23709 Saved,
23710 TitleChanged,
23711 SelectionsChanged {
23712 local: bool,
23713 },
23714 ScrollPositionChanged {
23715 local: bool,
23716 autoscroll: bool,
23717 },
23718 TransactionUndone {
23719 transaction_id: clock::Lamport,
23720 },
23721 TransactionBegun {
23722 transaction_id: clock::Lamport,
23723 },
23724 CursorShapeChanged,
23725 BreadcrumbsChanged,
23726 PushedToNavHistory {
23727 anchor: Anchor,
23728 is_deactivate: bool,
23729 },
23730}
23731
23732impl EventEmitter<EditorEvent> for Editor {}
23733
23734impl Focusable for Editor {
23735 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23736 self.focus_handle.clone()
23737 }
23738}
23739
23740impl Render for Editor {
23741 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23742 let settings = ThemeSettings::get_global(cx);
23743
23744 let mut text_style = match self.mode {
23745 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23746 color: cx.theme().colors().editor_foreground,
23747 font_family: settings.ui_font.family.clone(),
23748 font_features: settings.ui_font.features.clone(),
23749 font_fallbacks: settings.ui_font.fallbacks.clone(),
23750 font_size: rems(0.875).into(),
23751 font_weight: settings.ui_font.weight,
23752 line_height: relative(settings.buffer_line_height.value()),
23753 ..Default::default()
23754 },
23755 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23756 color: cx.theme().colors().editor_foreground,
23757 font_family: settings.buffer_font.family.clone(),
23758 font_features: settings.buffer_font.features.clone(),
23759 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23760 font_size: settings.buffer_font_size(cx).into(),
23761 font_weight: settings.buffer_font.weight,
23762 line_height: relative(settings.buffer_line_height.value()),
23763 ..Default::default()
23764 },
23765 };
23766 if let Some(text_style_refinement) = &self.text_style_refinement {
23767 text_style.refine(text_style_refinement)
23768 }
23769
23770 let background = match self.mode {
23771 EditorMode::SingleLine => cx.theme().system().transparent,
23772 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23773 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23774 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23775 };
23776
23777 EditorElement::new(
23778 &cx.entity(),
23779 EditorStyle {
23780 background,
23781 border: cx.theme().colors().border,
23782 local_player: cx.theme().players().local(),
23783 text: text_style,
23784 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23785 syntax: cx.theme().syntax().clone(),
23786 status: cx.theme().status().clone(),
23787 inlay_hints_style: make_inlay_hints_style(cx),
23788 edit_prediction_styles: make_suggestion_styles(cx),
23789 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23790 show_underlines: self.diagnostics_enabled(),
23791 },
23792 )
23793 }
23794}
23795
23796impl EntityInputHandler for Editor {
23797 fn text_for_range(
23798 &mut self,
23799 range_utf16: Range<usize>,
23800 adjusted_range: &mut Option<Range<usize>>,
23801 _: &mut Window,
23802 cx: &mut Context<Self>,
23803 ) -> Option<String> {
23804 let snapshot = self.buffer.read(cx).read(cx);
23805 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23806 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23807 if (start.0..end.0) != range_utf16 {
23808 adjusted_range.replace(start.0..end.0);
23809 }
23810 Some(snapshot.text_for_range(start..end).collect())
23811 }
23812
23813 fn selected_text_range(
23814 &mut self,
23815 ignore_disabled_input: bool,
23816 _: &mut Window,
23817 cx: &mut Context<Self>,
23818 ) -> Option<UTF16Selection> {
23819 // Prevent the IME menu from appearing when holding down an alphabetic key
23820 // while input is disabled.
23821 if !ignore_disabled_input && !self.input_enabled {
23822 return None;
23823 }
23824
23825 let selection = self.selections.newest::<OffsetUtf16>(cx);
23826 let range = selection.range();
23827
23828 Some(UTF16Selection {
23829 range: range.start.0..range.end.0,
23830 reversed: selection.reversed,
23831 })
23832 }
23833
23834 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23835 let snapshot = self.buffer.read(cx).read(cx);
23836 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23837 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23838 }
23839
23840 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23841 self.clear_highlights::<InputComposition>(cx);
23842 self.ime_transaction.take();
23843 }
23844
23845 fn replace_text_in_range(
23846 &mut self,
23847 range_utf16: Option<Range<usize>>,
23848 text: &str,
23849 window: &mut Window,
23850 cx: &mut Context<Self>,
23851 ) {
23852 if !self.input_enabled {
23853 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23854 return;
23855 }
23856
23857 self.transact(window, cx, |this, window, cx| {
23858 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23859 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23860 Some(this.selection_replacement_ranges(range_utf16, cx))
23861 } else {
23862 this.marked_text_ranges(cx)
23863 };
23864
23865 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23866 let newest_selection_id = this.selections.newest_anchor().id;
23867 this.selections
23868 .all::<OffsetUtf16>(cx)
23869 .iter()
23870 .zip(ranges_to_replace.iter())
23871 .find_map(|(selection, range)| {
23872 if selection.id == newest_selection_id {
23873 Some(
23874 (range.start.0 as isize - selection.head().0 as isize)
23875 ..(range.end.0 as isize - selection.head().0 as isize),
23876 )
23877 } else {
23878 None
23879 }
23880 })
23881 });
23882
23883 cx.emit(EditorEvent::InputHandled {
23884 utf16_range_to_replace: range_to_replace,
23885 text: text.into(),
23886 });
23887
23888 if let Some(new_selected_ranges) = new_selected_ranges {
23889 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23890 selections.select_ranges(new_selected_ranges)
23891 });
23892 this.backspace(&Default::default(), window, cx);
23893 }
23894
23895 this.handle_input(text, window, cx);
23896 });
23897
23898 if let Some(transaction) = self.ime_transaction {
23899 self.buffer.update(cx, |buffer, cx| {
23900 buffer.group_until_transaction(transaction, cx);
23901 });
23902 }
23903
23904 self.unmark_text(window, cx);
23905 }
23906
23907 fn replace_and_mark_text_in_range(
23908 &mut self,
23909 range_utf16: Option<Range<usize>>,
23910 text: &str,
23911 new_selected_range_utf16: Option<Range<usize>>,
23912 window: &mut Window,
23913 cx: &mut Context<Self>,
23914 ) {
23915 if !self.input_enabled {
23916 return;
23917 }
23918
23919 let transaction = self.transact(window, cx, |this, window, cx| {
23920 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23921 let snapshot = this.buffer.read(cx).read(cx);
23922 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23923 for marked_range in &mut marked_ranges {
23924 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23925 marked_range.start.0 += relative_range_utf16.start;
23926 marked_range.start =
23927 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23928 marked_range.end =
23929 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23930 }
23931 }
23932 Some(marked_ranges)
23933 } else if let Some(range_utf16) = range_utf16 {
23934 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23935 Some(this.selection_replacement_ranges(range_utf16, cx))
23936 } else {
23937 None
23938 };
23939
23940 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23941 let newest_selection_id = this.selections.newest_anchor().id;
23942 this.selections
23943 .all::<OffsetUtf16>(cx)
23944 .iter()
23945 .zip(ranges_to_replace.iter())
23946 .find_map(|(selection, range)| {
23947 if selection.id == newest_selection_id {
23948 Some(
23949 (range.start.0 as isize - selection.head().0 as isize)
23950 ..(range.end.0 as isize - selection.head().0 as isize),
23951 )
23952 } else {
23953 None
23954 }
23955 })
23956 });
23957
23958 cx.emit(EditorEvent::InputHandled {
23959 utf16_range_to_replace: range_to_replace,
23960 text: text.into(),
23961 });
23962
23963 if let Some(ranges) = ranges_to_replace {
23964 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23965 s.select_ranges(ranges)
23966 });
23967 }
23968
23969 let marked_ranges = {
23970 let snapshot = this.buffer.read(cx).read(cx);
23971 this.selections
23972 .disjoint_anchors_arc()
23973 .iter()
23974 .map(|selection| {
23975 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23976 })
23977 .collect::<Vec<_>>()
23978 };
23979
23980 if text.is_empty() {
23981 this.unmark_text(window, cx);
23982 } else {
23983 this.highlight_text::<InputComposition>(
23984 marked_ranges.clone(),
23985 HighlightStyle {
23986 underline: Some(UnderlineStyle {
23987 thickness: px(1.),
23988 color: None,
23989 wavy: false,
23990 }),
23991 ..Default::default()
23992 },
23993 cx,
23994 );
23995 }
23996
23997 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23998 let use_autoclose = this.use_autoclose;
23999 let use_auto_surround = this.use_auto_surround;
24000 this.set_use_autoclose(false);
24001 this.set_use_auto_surround(false);
24002 this.handle_input(text, window, cx);
24003 this.set_use_autoclose(use_autoclose);
24004 this.set_use_auto_surround(use_auto_surround);
24005
24006 if let Some(new_selected_range) = new_selected_range_utf16 {
24007 let snapshot = this.buffer.read(cx).read(cx);
24008 let new_selected_ranges = marked_ranges
24009 .into_iter()
24010 .map(|marked_range| {
24011 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24012 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24013 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24014 snapshot.clip_offset_utf16(new_start, Bias::Left)
24015 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24016 })
24017 .collect::<Vec<_>>();
24018
24019 drop(snapshot);
24020 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24021 selections.select_ranges(new_selected_ranges)
24022 });
24023 }
24024 });
24025
24026 self.ime_transaction = self.ime_transaction.or(transaction);
24027 if let Some(transaction) = self.ime_transaction {
24028 self.buffer.update(cx, |buffer, cx| {
24029 buffer.group_until_transaction(transaction, cx);
24030 });
24031 }
24032
24033 if self.text_highlights::<InputComposition>(cx).is_none() {
24034 self.ime_transaction.take();
24035 }
24036 }
24037
24038 fn bounds_for_range(
24039 &mut self,
24040 range_utf16: Range<usize>,
24041 element_bounds: gpui::Bounds<Pixels>,
24042 window: &mut Window,
24043 cx: &mut Context<Self>,
24044 ) -> Option<gpui::Bounds<Pixels>> {
24045 let text_layout_details = self.text_layout_details(window);
24046 let CharacterDimensions {
24047 em_width,
24048 em_advance,
24049 line_height,
24050 } = self.character_dimensions(window);
24051
24052 let snapshot = self.snapshot(window, cx);
24053 let scroll_position = snapshot.scroll_position();
24054 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24055
24056 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24057 let x = Pixels::from(
24058 ScrollOffset::from(
24059 snapshot.x_for_display_point(start, &text_layout_details)
24060 + self.gutter_dimensions.full_width(),
24061 ) - scroll_left,
24062 );
24063 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24064
24065 Some(Bounds {
24066 origin: element_bounds.origin + point(x, y),
24067 size: size(em_width, line_height),
24068 })
24069 }
24070
24071 fn character_index_for_point(
24072 &mut self,
24073 point: gpui::Point<Pixels>,
24074 _window: &mut Window,
24075 _cx: &mut Context<Self>,
24076 ) -> Option<usize> {
24077 let position_map = self.last_position_map.as_ref()?;
24078 if !position_map.text_hitbox.contains(&point) {
24079 return None;
24080 }
24081 let display_point = position_map.point_for_position(point).previous_valid;
24082 let anchor = position_map
24083 .snapshot
24084 .display_point_to_anchor(display_point, Bias::Left);
24085 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24086 Some(utf16_offset.0)
24087 }
24088}
24089
24090trait SelectionExt {
24091 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24092 fn spanned_rows(
24093 &self,
24094 include_end_if_at_line_start: bool,
24095 map: &DisplaySnapshot,
24096 ) -> Range<MultiBufferRow>;
24097}
24098
24099impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24100 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24101 let start = self
24102 .start
24103 .to_point(map.buffer_snapshot())
24104 .to_display_point(map);
24105 let end = self
24106 .end
24107 .to_point(map.buffer_snapshot())
24108 .to_display_point(map);
24109 if self.reversed {
24110 end..start
24111 } else {
24112 start..end
24113 }
24114 }
24115
24116 fn spanned_rows(
24117 &self,
24118 include_end_if_at_line_start: bool,
24119 map: &DisplaySnapshot,
24120 ) -> Range<MultiBufferRow> {
24121 let start = self.start.to_point(map.buffer_snapshot());
24122 let mut end = self.end.to_point(map.buffer_snapshot());
24123 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24124 end.row -= 1;
24125 }
24126
24127 let buffer_start = map.prev_line_boundary(start).0;
24128 let buffer_end = map.next_line_boundary(end).0;
24129 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24130 }
24131}
24132
24133impl<T: InvalidationRegion> InvalidationStack<T> {
24134 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24135 where
24136 S: Clone + ToOffset,
24137 {
24138 while let Some(region) = self.last() {
24139 let all_selections_inside_invalidation_ranges =
24140 if selections.len() == region.ranges().len() {
24141 selections
24142 .iter()
24143 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24144 .all(|(selection, invalidation_range)| {
24145 let head = selection.head().to_offset(buffer);
24146 invalidation_range.start <= head && invalidation_range.end >= head
24147 })
24148 } else {
24149 false
24150 };
24151
24152 if all_selections_inside_invalidation_ranges {
24153 break;
24154 } else {
24155 self.pop();
24156 }
24157 }
24158 }
24159}
24160
24161impl<T> Default for InvalidationStack<T> {
24162 fn default() -> Self {
24163 Self(Default::default())
24164 }
24165}
24166
24167impl<T> Deref for InvalidationStack<T> {
24168 type Target = Vec<T>;
24169
24170 fn deref(&self) -> &Self::Target {
24171 &self.0
24172 }
24173}
24174
24175impl<T> DerefMut for InvalidationStack<T> {
24176 fn deref_mut(&mut self) -> &mut Self::Target {
24177 &mut self.0
24178 }
24179}
24180
24181impl InvalidationRegion for SnippetState {
24182 fn ranges(&self) -> &[Range<Anchor>] {
24183 &self.ranges[self.active_index]
24184 }
24185}
24186
24187fn edit_prediction_edit_text(
24188 current_snapshot: &BufferSnapshot,
24189 edits: &[(Range<Anchor>, String)],
24190 edit_preview: &EditPreview,
24191 include_deletions: bool,
24192 cx: &App,
24193) -> HighlightedText {
24194 let edits = edits
24195 .iter()
24196 .map(|(anchor, text)| {
24197 (
24198 anchor.start.text_anchor..anchor.end.text_anchor,
24199 text.clone(),
24200 )
24201 })
24202 .collect::<Vec<_>>();
24203
24204 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24205}
24206
24207fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24208 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24209 // Just show the raw edit text with basic styling
24210 let mut text = String::new();
24211 let mut highlights = Vec::new();
24212
24213 let insertion_highlight_style = HighlightStyle {
24214 color: Some(cx.theme().colors().text),
24215 ..Default::default()
24216 };
24217
24218 for (_, edit_text) in edits {
24219 let start_offset = text.len();
24220 text.push_str(edit_text);
24221 let end_offset = text.len();
24222
24223 if start_offset < end_offset {
24224 highlights.push((start_offset..end_offset, insertion_highlight_style));
24225 }
24226 }
24227
24228 HighlightedText {
24229 text: text.into(),
24230 highlights,
24231 }
24232}
24233
24234pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24235 match severity {
24236 lsp::DiagnosticSeverity::ERROR => colors.error,
24237 lsp::DiagnosticSeverity::WARNING => colors.warning,
24238 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24239 lsp::DiagnosticSeverity::HINT => colors.info,
24240 _ => colors.ignored,
24241 }
24242}
24243
24244pub fn styled_runs_for_code_label<'a>(
24245 label: &'a CodeLabel,
24246 syntax_theme: &'a theme::SyntaxTheme,
24247) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24248 let fade_out = HighlightStyle {
24249 fade_out: Some(0.35),
24250 ..Default::default()
24251 };
24252
24253 let mut prev_end = label.filter_range.end;
24254 label
24255 .runs
24256 .iter()
24257 .enumerate()
24258 .flat_map(move |(ix, (range, highlight_id))| {
24259 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24260 style
24261 } else {
24262 return Default::default();
24263 };
24264 let muted_style = style.highlight(fade_out);
24265
24266 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24267 if range.start >= label.filter_range.end {
24268 if range.start > prev_end {
24269 runs.push((prev_end..range.start, fade_out));
24270 }
24271 runs.push((range.clone(), muted_style));
24272 } else if range.end <= label.filter_range.end {
24273 runs.push((range.clone(), style));
24274 } else {
24275 runs.push((range.start..label.filter_range.end, style));
24276 runs.push((label.filter_range.end..range.end, muted_style));
24277 }
24278 prev_end = cmp::max(prev_end, range.end);
24279
24280 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24281 runs.push((prev_end..label.text.len(), fade_out));
24282 }
24283
24284 runs
24285 })
24286}
24287
24288pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24289 let mut prev_index = 0;
24290 let mut prev_codepoint: Option<char> = None;
24291 text.char_indices()
24292 .chain([(text.len(), '\0')])
24293 .filter_map(move |(index, codepoint)| {
24294 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24295 let is_boundary = index == text.len()
24296 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24297 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24298 if is_boundary {
24299 let chunk = &text[prev_index..index];
24300 prev_index = index;
24301 Some(chunk)
24302 } else {
24303 None
24304 }
24305 })
24306}
24307
24308pub trait RangeToAnchorExt: Sized {
24309 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24310
24311 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24312 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24313 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24314 }
24315}
24316
24317impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24318 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24319 let start_offset = self.start.to_offset(snapshot);
24320 let end_offset = self.end.to_offset(snapshot);
24321 if start_offset == end_offset {
24322 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24323 } else {
24324 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24325 }
24326 }
24327}
24328
24329pub trait RowExt {
24330 fn as_f64(&self) -> f64;
24331
24332 fn next_row(&self) -> Self;
24333
24334 fn previous_row(&self) -> Self;
24335
24336 fn minus(&self, other: Self) -> u32;
24337}
24338
24339impl RowExt for DisplayRow {
24340 fn as_f64(&self) -> f64 {
24341 self.0 as _
24342 }
24343
24344 fn next_row(&self) -> Self {
24345 Self(self.0 + 1)
24346 }
24347
24348 fn previous_row(&self) -> Self {
24349 Self(self.0.saturating_sub(1))
24350 }
24351
24352 fn minus(&self, other: Self) -> u32 {
24353 self.0 - other.0
24354 }
24355}
24356
24357impl RowExt for MultiBufferRow {
24358 fn as_f64(&self) -> f64 {
24359 self.0 as _
24360 }
24361
24362 fn next_row(&self) -> Self {
24363 Self(self.0 + 1)
24364 }
24365
24366 fn previous_row(&self) -> Self {
24367 Self(self.0.saturating_sub(1))
24368 }
24369
24370 fn minus(&self, other: Self) -> u32 {
24371 self.0 - other.0
24372 }
24373}
24374
24375trait RowRangeExt {
24376 type Row;
24377
24378 fn len(&self) -> usize;
24379
24380 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24381}
24382
24383impl RowRangeExt for Range<MultiBufferRow> {
24384 type Row = MultiBufferRow;
24385
24386 fn len(&self) -> usize {
24387 (self.end.0 - self.start.0) as usize
24388 }
24389
24390 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24391 (self.start.0..self.end.0).map(MultiBufferRow)
24392 }
24393}
24394
24395impl RowRangeExt for Range<DisplayRow> {
24396 type Row = DisplayRow;
24397
24398 fn len(&self) -> usize {
24399 (self.end.0 - self.start.0) as usize
24400 }
24401
24402 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24403 (self.start.0..self.end.0).map(DisplayRow)
24404 }
24405}
24406
24407/// If select range has more than one line, we
24408/// just point the cursor to range.start.
24409fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24410 if range.start.row == range.end.row {
24411 range
24412 } else {
24413 range.start..range.start
24414 }
24415}
24416pub struct KillRing(ClipboardItem);
24417impl Global for KillRing {}
24418
24419const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24420
24421enum BreakpointPromptEditAction {
24422 Log,
24423 Condition,
24424 HitCondition,
24425}
24426
24427struct BreakpointPromptEditor {
24428 pub(crate) prompt: Entity<Editor>,
24429 editor: WeakEntity<Editor>,
24430 breakpoint_anchor: Anchor,
24431 breakpoint: Breakpoint,
24432 edit_action: BreakpointPromptEditAction,
24433 block_ids: HashSet<CustomBlockId>,
24434 editor_margins: Arc<Mutex<EditorMargins>>,
24435 _subscriptions: Vec<Subscription>,
24436}
24437
24438impl BreakpointPromptEditor {
24439 const MAX_LINES: u8 = 4;
24440
24441 fn new(
24442 editor: WeakEntity<Editor>,
24443 breakpoint_anchor: Anchor,
24444 breakpoint: Breakpoint,
24445 edit_action: BreakpointPromptEditAction,
24446 window: &mut Window,
24447 cx: &mut Context<Self>,
24448 ) -> Self {
24449 let base_text = match edit_action {
24450 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24451 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24452 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24453 }
24454 .map(|msg| msg.to_string())
24455 .unwrap_or_default();
24456
24457 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24458 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24459
24460 let prompt = cx.new(|cx| {
24461 let mut prompt = Editor::new(
24462 EditorMode::AutoHeight {
24463 min_lines: 1,
24464 max_lines: Some(Self::MAX_LINES as usize),
24465 },
24466 buffer,
24467 None,
24468 window,
24469 cx,
24470 );
24471 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24472 prompt.set_show_cursor_when_unfocused(false, cx);
24473 prompt.set_placeholder_text(
24474 match edit_action {
24475 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24476 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24477 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24478 },
24479 window,
24480 cx,
24481 );
24482
24483 prompt
24484 });
24485
24486 Self {
24487 prompt,
24488 editor,
24489 breakpoint_anchor,
24490 breakpoint,
24491 edit_action,
24492 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24493 block_ids: Default::default(),
24494 _subscriptions: vec![],
24495 }
24496 }
24497
24498 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24499 self.block_ids.extend(block_ids)
24500 }
24501
24502 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24503 if let Some(editor) = self.editor.upgrade() {
24504 let message = self
24505 .prompt
24506 .read(cx)
24507 .buffer
24508 .read(cx)
24509 .as_singleton()
24510 .expect("A multi buffer in breakpoint prompt isn't possible")
24511 .read(cx)
24512 .as_rope()
24513 .to_string();
24514
24515 editor.update(cx, |editor, cx| {
24516 editor.edit_breakpoint_at_anchor(
24517 self.breakpoint_anchor,
24518 self.breakpoint.clone(),
24519 match self.edit_action {
24520 BreakpointPromptEditAction::Log => {
24521 BreakpointEditAction::EditLogMessage(message.into())
24522 }
24523 BreakpointPromptEditAction::Condition => {
24524 BreakpointEditAction::EditCondition(message.into())
24525 }
24526 BreakpointPromptEditAction::HitCondition => {
24527 BreakpointEditAction::EditHitCondition(message.into())
24528 }
24529 },
24530 cx,
24531 );
24532
24533 editor.remove_blocks(self.block_ids.clone(), None, cx);
24534 cx.focus_self(window);
24535 });
24536 }
24537 }
24538
24539 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24540 self.editor
24541 .update(cx, |editor, cx| {
24542 editor.remove_blocks(self.block_ids.clone(), None, cx);
24543 window.focus(&editor.focus_handle);
24544 })
24545 .log_err();
24546 }
24547
24548 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24549 let settings = ThemeSettings::get_global(cx);
24550 let text_style = TextStyle {
24551 color: if self.prompt.read(cx).read_only(cx) {
24552 cx.theme().colors().text_disabled
24553 } else {
24554 cx.theme().colors().text
24555 },
24556 font_family: settings.buffer_font.family.clone(),
24557 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24558 font_size: settings.buffer_font_size(cx).into(),
24559 font_weight: settings.buffer_font.weight,
24560 line_height: relative(settings.buffer_line_height.value()),
24561 ..Default::default()
24562 };
24563 EditorElement::new(
24564 &self.prompt,
24565 EditorStyle {
24566 background: cx.theme().colors().editor_background,
24567 local_player: cx.theme().players().local(),
24568 text: text_style,
24569 ..Default::default()
24570 },
24571 )
24572 }
24573}
24574
24575impl Render for BreakpointPromptEditor {
24576 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24577 let editor_margins = *self.editor_margins.lock();
24578 let gutter_dimensions = editor_margins.gutter;
24579 h_flex()
24580 .key_context("Editor")
24581 .bg(cx.theme().colors().editor_background)
24582 .border_y_1()
24583 .border_color(cx.theme().status().info_border)
24584 .size_full()
24585 .py(window.line_height() / 2.5)
24586 .on_action(cx.listener(Self::confirm))
24587 .on_action(cx.listener(Self::cancel))
24588 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24589 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24590 }
24591}
24592
24593impl Focusable for BreakpointPromptEditor {
24594 fn focus_handle(&self, cx: &App) -> FocusHandle {
24595 self.prompt.focus_handle(cx)
24596 }
24597}
24598
24599fn all_edits_insertions_or_deletions(
24600 edits: &Vec<(Range<Anchor>, String)>,
24601 snapshot: &MultiBufferSnapshot,
24602) -> bool {
24603 let mut all_insertions = true;
24604 let mut all_deletions = true;
24605
24606 for (range, new_text) in edits.iter() {
24607 let range_is_empty = range.to_offset(snapshot).is_empty();
24608 let text_is_empty = new_text.is_empty();
24609
24610 if range_is_empty != text_is_empty {
24611 if range_is_empty {
24612 all_deletions = false;
24613 } else {
24614 all_insertions = false;
24615 }
24616 } else {
24617 return false;
24618 }
24619
24620 if !all_insertions && !all_deletions {
24621 return false;
24622 }
24623 }
24624 all_insertions || all_deletions
24625}
24626
24627struct MissingEditPredictionKeybindingTooltip;
24628
24629impl Render for MissingEditPredictionKeybindingTooltip {
24630 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24631 ui::tooltip_container(cx, |container, cx| {
24632 container
24633 .flex_shrink_0()
24634 .max_w_80()
24635 .min_h(rems_from_px(124.))
24636 .justify_between()
24637 .child(
24638 v_flex()
24639 .flex_1()
24640 .text_ui_sm(cx)
24641 .child(Label::new("Conflict with Accept Keybinding"))
24642 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24643 )
24644 .child(
24645 h_flex()
24646 .pb_1()
24647 .gap_1()
24648 .items_end()
24649 .w_full()
24650 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24651 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24652 }))
24653 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24654 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24655 })),
24656 )
24657 })
24658 }
24659}
24660
24661#[derive(Debug, Clone, Copy, PartialEq)]
24662pub struct LineHighlight {
24663 pub background: Background,
24664 pub border: Option<gpui::Hsla>,
24665 pub include_gutter: bool,
24666 pub type_id: Option<TypeId>,
24667}
24668
24669struct LineManipulationResult {
24670 pub new_text: String,
24671 pub line_count_before: usize,
24672 pub line_count_after: usize,
24673}
24674
24675fn render_diff_hunk_controls(
24676 row: u32,
24677 status: &DiffHunkStatus,
24678 hunk_range: Range<Anchor>,
24679 is_created_file: bool,
24680 line_height: Pixels,
24681 editor: &Entity<Editor>,
24682 _window: &mut Window,
24683 cx: &mut App,
24684) -> AnyElement {
24685 h_flex()
24686 .h(line_height)
24687 .mr_1()
24688 .gap_1()
24689 .px_0p5()
24690 .pb_1()
24691 .border_x_1()
24692 .border_b_1()
24693 .border_color(cx.theme().colors().border_variant)
24694 .rounded_b_lg()
24695 .bg(cx.theme().colors().editor_background)
24696 .gap_1()
24697 .block_mouse_except_scroll()
24698 .shadow_md()
24699 .child(if status.has_secondary_hunk() {
24700 Button::new(("stage", row as u64), "Stage")
24701 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24702 .tooltip({
24703 let focus_handle = editor.focus_handle(cx);
24704 move |window, cx| {
24705 Tooltip::for_action_in(
24706 "Stage Hunk",
24707 &::git::ToggleStaged,
24708 &focus_handle,
24709 window,
24710 cx,
24711 )
24712 }
24713 })
24714 .on_click({
24715 let editor = editor.clone();
24716 move |_event, _window, cx| {
24717 editor.update(cx, |editor, cx| {
24718 editor.stage_or_unstage_diff_hunks(
24719 true,
24720 vec![hunk_range.start..hunk_range.start],
24721 cx,
24722 );
24723 });
24724 }
24725 })
24726 } else {
24727 Button::new(("unstage", row as u64), "Unstage")
24728 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24729 .tooltip({
24730 let focus_handle = editor.focus_handle(cx);
24731 move |window, cx| {
24732 Tooltip::for_action_in(
24733 "Unstage Hunk",
24734 &::git::ToggleStaged,
24735 &focus_handle,
24736 window,
24737 cx,
24738 )
24739 }
24740 })
24741 .on_click({
24742 let editor = editor.clone();
24743 move |_event, _window, cx| {
24744 editor.update(cx, |editor, cx| {
24745 editor.stage_or_unstage_diff_hunks(
24746 false,
24747 vec![hunk_range.start..hunk_range.start],
24748 cx,
24749 );
24750 });
24751 }
24752 })
24753 })
24754 .child(
24755 Button::new(("restore", row as u64), "Restore")
24756 .tooltip({
24757 let focus_handle = editor.focus_handle(cx);
24758 move |window, cx| {
24759 Tooltip::for_action_in(
24760 "Restore Hunk",
24761 &::git::Restore,
24762 &focus_handle,
24763 window,
24764 cx,
24765 )
24766 }
24767 })
24768 .on_click({
24769 let editor = editor.clone();
24770 move |_event, window, cx| {
24771 editor.update(cx, |editor, cx| {
24772 let snapshot = editor.snapshot(window, cx);
24773 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24774 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24775 });
24776 }
24777 })
24778 .disabled(is_created_file),
24779 )
24780 .when(
24781 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24782 |el| {
24783 el.child(
24784 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24785 .shape(IconButtonShape::Square)
24786 .icon_size(IconSize::Small)
24787 // .disabled(!has_multiple_hunks)
24788 .tooltip({
24789 let focus_handle = editor.focus_handle(cx);
24790 move |window, cx| {
24791 Tooltip::for_action_in(
24792 "Next Hunk",
24793 &GoToHunk,
24794 &focus_handle,
24795 window,
24796 cx,
24797 )
24798 }
24799 })
24800 .on_click({
24801 let editor = editor.clone();
24802 move |_event, window, cx| {
24803 editor.update(cx, |editor, cx| {
24804 let snapshot = editor.snapshot(window, cx);
24805 let position =
24806 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24807 editor.go_to_hunk_before_or_after_position(
24808 &snapshot,
24809 position,
24810 Direction::Next,
24811 window,
24812 cx,
24813 );
24814 editor.expand_selected_diff_hunks(cx);
24815 });
24816 }
24817 }),
24818 )
24819 .child(
24820 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24821 .shape(IconButtonShape::Square)
24822 .icon_size(IconSize::Small)
24823 // .disabled(!has_multiple_hunks)
24824 .tooltip({
24825 let focus_handle = editor.focus_handle(cx);
24826 move |window, cx| {
24827 Tooltip::for_action_in(
24828 "Previous Hunk",
24829 &GoToPreviousHunk,
24830 &focus_handle,
24831 window,
24832 cx,
24833 )
24834 }
24835 })
24836 .on_click({
24837 let editor = editor.clone();
24838 move |_event, window, cx| {
24839 editor.update(cx, |editor, cx| {
24840 let snapshot = editor.snapshot(window, cx);
24841 let point =
24842 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24843 editor.go_to_hunk_before_or_after_position(
24844 &snapshot,
24845 point,
24846 Direction::Prev,
24847 window,
24848 cx,
24849 );
24850 editor.expand_selected_diff_hunks(cx);
24851 });
24852 }
24853 }),
24854 )
24855 },
24856 )
24857 .into_any_element()
24858}
24859
24860pub fn multibuffer_context_lines(cx: &App) -> u32 {
24861 EditorSettings::try_get(cx)
24862 .map(|settings| settings.excerpt_context_lines)
24863 .unwrap_or(2)
24864 .min(32)
24865}