1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex, parse_zed_link};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
125 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
126 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
127 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{
169 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
170};
171use serde::{Deserialize, Serialize};
172use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
173use smallvec::{SmallVec, smallvec};
174use snippet::Snippet;
175use std::{
176 any::{Any, TypeId},
177 borrow::Cow,
178 cell::{OnceCell, RefCell},
179 cmp::{self, Ordering, Reverse},
180 iter::{self, Peekable},
181 mem,
182 num::NonZeroU32,
183 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
184 path::{Path, PathBuf},
185 rc::Rc,
186 sync::Arc,
187 time::{Duration, Instant},
188};
189use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
190use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
191use theme::{
192 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
193 observe_buffer_font_size_adjustment,
194};
195use ui::{
196 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
197 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
198};
199use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
200use workspace::{
201 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
202 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
203 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
204 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
205 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
206 searchable::SearchEvent,
207};
208
209use crate::{
210 code_context_menus::CompletionsMenuSource,
211 editor_settings::MultiCursorModifier,
212 hover_links::{find_url, find_url_from_range},
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
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 folding_newlines: Task<()>,
1194 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1195}
1196
1197#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1198enum NextScrollCursorCenterTopBottom {
1199 #[default]
1200 Center,
1201 Top,
1202 Bottom,
1203}
1204
1205impl NextScrollCursorCenterTopBottom {
1206 fn next(&self) -> Self {
1207 match self {
1208 Self::Center => Self::Top,
1209 Self::Top => Self::Bottom,
1210 Self::Bottom => Self::Center,
1211 }
1212 }
1213}
1214
1215#[derive(Clone)]
1216pub struct EditorSnapshot {
1217 pub mode: EditorMode,
1218 show_gutter: bool,
1219 show_line_numbers: Option<bool>,
1220 show_git_diff_gutter: Option<bool>,
1221 show_code_actions: Option<bool>,
1222 show_runnables: Option<bool>,
1223 show_breakpoints: Option<bool>,
1224 git_blame_gutter_max_author_length: Option<usize>,
1225 pub display_snapshot: DisplaySnapshot,
1226 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1227 is_focused: bool,
1228 scroll_anchor: ScrollAnchor,
1229 ongoing_scroll: OngoingScroll,
1230 current_line_highlight: CurrentLineHighlight,
1231 gutter_hovered: bool,
1232}
1233
1234#[derive(Default, Debug, Clone, Copy)]
1235pub struct GutterDimensions {
1236 pub left_padding: Pixels,
1237 pub right_padding: Pixels,
1238 pub width: Pixels,
1239 pub margin: Pixels,
1240 pub git_blame_entries_width: Option<Pixels>,
1241}
1242
1243impl GutterDimensions {
1244 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1245 Self {
1246 margin: Self::default_gutter_margin(font_id, font_size, cx),
1247 ..Default::default()
1248 }
1249 }
1250
1251 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1252 -cx.text_system().descent(font_id, font_size)
1253 }
1254 /// The full width of the space taken up by the gutter.
1255 pub fn full_width(&self) -> Pixels {
1256 self.margin + self.width
1257 }
1258
1259 /// The width of the space reserved for the fold indicators,
1260 /// use alongside 'justify_end' and `gutter_width` to
1261 /// right align content with the line numbers
1262 pub fn fold_area_width(&self) -> Pixels {
1263 self.margin + self.right_padding
1264 }
1265}
1266
1267struct CharacterDimensions {
1268 em_width: Pixels,
1269 em_advance: Pixels,
1270 line_height: Pixels,
1271}
1272
1273#[derive(Debug)]
1274pub struct RemoteSelection {
1275 pub replica_id: ReplicaId,
1276 pub selection: Selection<Anchor>,
1277 pub cursor_shape: CursorShape,
1278 pub collaborator_id: CollaboratorId,
1279 pub line_mode: bool,
1280 pub user_name: Option<SharedString>,
1281 pub color: PlayerColor,
1282}
1283
1284#[derive(Clone, Debug)]
1285struct SelectionHistoryEntry {
1286 selections: Arc<[Selection<Anchor>]>,
1287 select_next_state: Option<SelectNextState>,
1288 select_prev_state: Option<SelectNextState>,
1289 add_selections_state: Option<AddSelectionsState>,
1290}
1291
1292#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1293enum SelectionHistoryMode {
1294 Normal,
1295 Undoing,
1296 Redoing,
1297 Skipping,
1298}
1299
1300#[derive(Clone, PartialEq, Eq, Hash)]
1301struct HoveredCursor {
1302 replica_id: u16,
1303 selection_id: usize,
1304}
1305
1306impl Default for SelectionHistoryMode {
1307 fn default() -> Self {
1308 Self::Normal
1309 }
1310}
1311
1312#[derive(Debug)]
1313/// SelectionEffects controls the side-effects of updating the selection.
1314///
1315/// The default behaviour does "what you mostly want":
1316/// - it pushes to the nav history if the cursor moved by >10 lines
1317/// - it re-triggers completion requests
1318/// - it scrolls to fit
1319///
1320/// You might want to modify these behaviours. For example when doing a "jump"
1321/// like go to definition, we always want to add to nav history; but when scrolling
1322/// in vim mode we never do.
1323///
1324/// Similarly, you might want to disable scrolling if you don't want the viewport to
1325/// move.
1326#[derive(Clone)]
1327pub struct SelectionEffects {
1328 nav_history: Option<bool>,
1329 completions: bool,
1330 scroll: Option<Autoscroll>,
1331}
1332
1333impl Default for SelectionEffects {
1334 fn default() -> Self {
1335 Self {
1336 nav_history: None,
1337 completions: true,
1338 scroll: Some(Autoscroll::fit()),
1339 }
1340 }
1341}
1342impl SelectionEffects {
1343 pub fn scroll(scroll: Autoscroll) -> Self {
1344 Self {
1345 scroll: Some(scroll),
1346 ..Default::default()
1347 }
1348 }
1349
1350 pub fn no_scroll() -> Self {
1351 Self {
1352 scroll: None,
1353 ..Default::default()
1354 }
1355 }
1356
1357 pub fn completions(self, completions: bool) -> Self {
1358 Self {
1359 completions,
1360 ..self
1361 }
1362 }
1363
1364 pub fn nav_history(self, nav_history: bool) -> Self {
1365 Self {
1366 nav_history: Some(nav_history),
1367 ..self
1368 }
1369 }
1370}
1371
1372struct DeferredSelectionEffectsState {
1373 changed: bool,
1374 effects: SelectionEffects,
1375 old_cursor_position: Anchor,
1376 history_entry: SelectionHistoryEntry,
1377}
1378
1379#[derive(Default)]
1380struct SelectionHistory {
1381 #[allow(clippy::type_complexity)]
1382 selections_by_transaction:
1383 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1384 mode: SelectionHistoryMode,
1385 undo_stack: VecDeque<SelectionHistoryEntry>,
1386 redo_stack: VecDeque<SelectionHistoryEntry>,
1387}
1388
1389impl SelectionHistory {
1390 #[track_caller]
1391 fn insert_transaction(
1392 &mut self,
1393 transaction_id: TransactionId,
1394 selections: Arc<[Selection<Anchor>]>,
1395 ) {
1396 if selections.is_empty() {
1397 log::error!(
1398 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1399 std::panic::Location::caller()
1400 );
1401 return;
1402 }
1403 self.selections_by_transaction
1404 .insert(transaction_id, (selections, None));
1405 }
1406
1407 #[allow(clippy::type_complexity)]
1408 fn transaction(
1409 &self,
1410 transaction_id: TransactionId,
1411 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1412 self.selections_by_transaction.get(&transaction_id)
1413 }
1414
1415 #[allow(clippy::type_complexity)]
1416 fn transaction_mut(
1417 &mut self,
1418 transaction_id: TransactionId,
1419 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1420 self.selections_by_transaction.get_mut(&transaction_id)
1421 }
1422
1423 fn push(&mut self, entry: SelectionHistoryEntry) {
1424 if !entry.selections.is_empty() {
1425 match self.mode {
1426 SelectionHistoryMode::Normal => {
1427 self.push_undo(entry);
1428 self.redo_stack.clear();
1429 }
1430 SelectionHistoryMode::Undoing => self.push_redo(entry),
1431 SelectionHistoryMode::Redoing => self.push_undo(entry),
1432 SelectionHistoryMode::Skipping => {}
1433 }
1434 }
1435 }
1436
1437 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1438 if self
1439 .undo_stack
1440 .back()
1441 .is_none_or(|e| e.selections != entry.selections)
1442 {
1443 self.undo_stack.push_back(entry);
1444 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1445 self.undo_stack.pop_front();
1446 }
1447 }
1448 }
1449
1450 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1451 if self
1452 .redo_stack
1453 .back()
1454 .is_none_or(|e| e.selections != entry.selections)
1455 {
1456 self.redo_stack.push_back(entry);
1457 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1458 self.redo_stack.pop_front();
1459 }
1460 }
1461 }
1462}
1463
1464#[derive(Clone, Copy)]
1465pub struct RowHighlightOptions {
1466 pub autoscroll: bool,
1467 pub include_gutter: bool,
1468}
1469
1470impl Default for RowHighlightOptions {
1471 fn default() -> Self {
1472 Self {
1473 autoscroll: Default::default(),
1474 include_gutter: true,
1475 }
1476 }
1477}
1478
1479struct RowHighlight {
1480 index: usize,
1481 range: Range<Anchor>,
1482 color: Hsla,
1483 options: RowHighlightOptions,
1484 type_id: TypeId,
1485}
1486
1487#[derive(Clone, Debug)]
1488struct AddSelectionsState {
1489 groups: Vec<AddSelectionsGroup>,
1490}
1491
1492#[derive(Clone, Debug)]
1493struct AddSelectionsGroup {
1494 above: bool,
1495 stack: Vec<usize>,
1496}
1497
1498#[derive(Clone)]
1499struct SelectNextState {
1500 query: AhoCorasick,
1501 wordwise: bool,
1502 done: bool,
1503}
1504
1505impl std::fmt::Debug for SelectNextState {
1506 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1507 f.debug_struct(std::any::type_name::<Self>())
1508 .field("wordwise", &self.wordwise)
1509 .field("done", &self.done)
1510 .finish()
1511 }
1512}
1513
1514#[derive(Debug)]
1515struct AutocloseRegion {
1516 selection_id: usize,
1517 range: Range<Anchor>,
1518 pair: BracketPair,
1519}
1520
1521#[derive(Debug)]
1522struct SnippetState {
1523 ranges: Vec<Vec<Range<Anchor>>>,
1524 active_index: usize,
1525 choices: Vec<Option<Vec<String>>>,
1526}
1527
1528#[doc(hidden)]
1529pub struct RenameState {
1530 pub range: Range<Anchor>,
1531 pub old_name: Arc<str>,
1532 pub editor: Entity<Editor>,
1533 block_id: CustomBlockId,
1534}
1535
1536struct InvalidationStack<T>(Vec<T>);
1537
1538struct RegisteredEditPredictionProvider {
1539 provider: Arc<dyn EditPredictionProviderHandle>,
1540 _subscription: Subscription,
1541}
1542
1543#[derive(Debug, PartialEq, Eq)]
1544pub struct ActiveDiagnosticGroup {
1545 pub active_range: Range<Anchor>,
1546 pub active_message: String,
1547 pub group_id: usize,
1548 pub blocks: HashSet<CustomBlockId>,
1549}
1550
1551#[derive(Debug, PartialEq, Eq)]
1552
1553pub(crate) enum ActiveDiagnostic {
1554 None,
1555 All,
1556 Group(ActiveDiagnosticGroup),
1557}
1558
1559#[derive(Serialize, Deserialize, Clone, Debug)]
1560pub struct ClipboardSelection {
1561 /// The number of bytes in this selection.
1562 pub len: usize,
1563 /// Whether this was a full-line selection.
1564 pub is_entire_line: bool,
1565 /// The indentation of the first line when this content was originally copied.
1566 pub first_line_indent: u32,
1567}
1568
1569// selections, scroll behavior, was newest selection reversed
1570type SelectSyntaxNodeHistoryState = (
1571 Box<[Selection<usize>]>,
1572 SelectSyntaxNodeScrollBehavior,
1573 bool,
1574);
1575
1576#[derive(Default)]
1577struct SelectSyntaxNodeHistory {
1578 stack: Vec<SelectSyntaxNodeHistoryState>,
1579 // disable temporarily to allow changing selections without losing the stack
1580 pub disable_clearing: bool,
1581}
1582
1583impl SelectSyntaxNodeHistory {
1584 pub fn try_clear(&mut self) {
1585 if !self.disable_clearing {
1586 self.stack.clear();
1587 }
1588 }
1589
1590 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1591 self.stack.push(selection);
1592 }
1593
1594 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1595 self.stack.pop()
1596 }
1597}
1598
1599enum SelectSyntaxNodeScrollBehavior {
1600 CursorTop,
1601 FitSelection,
1602 CursorBottom,
1603}
1604
1605#[derive(Debug)]
1606pub(crate) struct NavigationData {
1607 cursor_anchor: Anchor,
1608 cursor_position: Point,
1609 scroll_anchor: ScrollAnchor,
1610 scroll_top_row: u32,
1611}
1612
1613#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1614pub enum GotoDefinitionKind {
1615 Symbol,
1616 Declaration,
1617 Type,
1618 Implementation,
1619}
1620
1621#[derive(Debug, Clone)]
1622enum InlayHintRefreshReason {
1623 ModifiersChanged(bool),
1624 Toggle(bool),
1625 SettingsChange(InlayHintSettings),
1626 NewLinesShown,
1627 BufferEdited(HashSet<Arc<Language>>),
1628 RefreshRequested,
1629 ExcerptsRemoved(Vec<ExcerptId>),
1630}
1631
1632impl InlayHintRefreshReason {
1633 fn description(&self) -> &'static str {
1634 match self {
1635 Self::ModifiersChanged(_) => "modifiers changed",
1636 Self::Toggle(_) => "toggle",
1637 Self::SettingsChange(_) => "settings change",
1638 Self::NewLinesShown => "new lines shown",
1639 Self::BufferEdited(_) => "buffer edited",
1640 Self::RefreshRequested => "refresh requested",
1641 Self::ExcerptsRemoved(_) => "excerpts removed",
1642 }
1643 }
1644}
1645
1646pub enum FormatTarget {
1647 Buffers(HashSet<Entity<Buffer>>),
1648 Ranges(Vec<Range<MultiBufferPoint>>),
1649}
1650
1651pub(crate) struct FocusedBlock {
1652 id: BlockId,
1653 focus_handle: WeakFocusHandle,
1654}
1655
1656#[derive(Clone)]
1657enum JumpData {
1658 MultiBufferRow {
1659 row: MultiBufferRow,
1660 line_offset_from_top: u32,
1661 },
1662 MultiBufferPoint {
1663 excerpt_id: ExcerptId,
1664 position: Point,
1665 anchor: text::Anchor,
1666 line_offset_from_top: u32,
1667 },
1668}
1669
1670pub enum MultibufferSelectionMode {
1671 First,
1672 All,
1673}
1674
1675#[derive(Clone, Copy, Debug, Default)]
1676pub struct RewrapOptions {
1677 pub override_language_settings: bool,
1678 pub preserve_existing_whitespace: bool,
1679}
1680
1681impl Editor {
1682 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1683 let buffer = cx.new(|cx| Buffer::local("", cx));
1684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1685 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1686 }
1687
1688 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1689 let buffer = cx.new(|cx| Buffer::local("", cx));
1690 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1691 Self::new(EditorMode::full(), buffer, None, window, cx)
1692 }
1693
1694 pub fn auto_height(
1695 min_lines: usize,
1696 max_lines: usize,
1697 window: &mut Window,
1698 cx: &mut Context<Self>,
1699 ) -> Self {
1700 let buffer = cx.new(|cx| Buffer::local("", cx));
1701 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1702 Self::new(
1703 EditorMode::AutoHeight {
1704 min_lines,
1705 max_lines: Some(max_lines),
1706 },
1707 buffer,
1708 None,
1709 window,
1710 cx,
1711 )
1712 }
1713
1714 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1715 /// The editor grows as tall as needed to fit its content.
1716 pub fn auto_height_unbounded(
1717 min_lines: usize,
1718 window: &mut Window,
1719 cx: &mut Context<Self>,
1720 ) -> Self {
1721 let buffer = cx.new(|cx| Buffer::local("", cx));
1722 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1723 Self::new(
1724 EditorMode::AutoHeight {
1725 min_lines,
1726 max_lines: None,
1727 },
1728 buffer,
1729 None,
1730 window,
1731 cx,
1732 )
1733 }
1734
1735 pub fn for_buffer(
1736 buffer: Entity<Buffer>,
1737 project: Option<Entity<Project>>,
1738 window: &mut Window,
1739 cx: &mut Context<Self>,
1740 ) -> Self {
1741 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1742 Self::new(EditorMode::full(), buffer, project, window, cx)
1743 }
1744
1745 pub fn for_multibuffer(
1746 buffer: Entity<MultiBuffer>,
1747 project: Option<Entity<Project>>,
1748 window: &mut Window,
1749 cx: &mut Context<Self>,
1750 ) -> Self {
1751 Self::new(EditorMode::full(), buffer, project, window, cx)
1752 }
1753
1754 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1755 let mut clone = Self::new(
1756 self.mode.clone(),
1757 self.buffer.clone(),
1758 self.project.clone(),
1759 window,
1760 cx,
1761 );
1762 self.display_map.update(cx, |display_map, cx| {
1763 let snapshot = display_map.snapshot(cx);
1764 clone.display_map.update(cx, |display_map, cx| {
1765 display_map.set_state(&snapshot, cx);
1766 });
1767 });
1768 clone.folds_did_change(cx);
1769 clone.selections.clone_state(&self.selections);
1770 clone.scroll_manager.clone_state(&self.scroll_manager);
1771 clone.searchable = self.searchable;
1772 clone.read_only = self.read_only;
1773 clone
1774 }
1775
1776 pub fn new(
1777 mode: EditorMode,
1778 buffer: Entity<MultiBuffer>,
1779 project: Option<Entity<Project>>,
1780 window: &mut Window,
1781 cx: &mut Context<Self>,
1782 ) -> Self {
1783 Editor::new_internal(mode, buffer, project, None, window, cx)
1784 }
1785
1786 fn new_internal(
1787 mode: EditorMode,
1788 buffer: Entity<MultiBuffer>,
1789 project: Option<Entity<Project>>,
1790 display_map: Option<Entity<DisplayMap>>,
1791 window: &mut Window,
1792 cx: &mut Context<Self>,
1793 ) -> Self {
1794 debug_assert!(
1795 display_map.is_none() || mode.is_minimap(),
1796 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1797 );
1798
1799 let full_mode = mode.is_full();
1800 let is_minimap = mode.is_minimap();
1801 let diagnostics_max_severity = if full_mode {
1802 EditorSettings::get_global(cx)
1803 .diagnostics_max_severity
1804 .unwrap_or(DiagnosticSeverity::Hint)
1805 } else {
1806 DiagnosticSeverity::Off
1807 };
1808 let style = window.text_style();
1809 let font_size = style.font_size.to_pixels(window.rem_size());
1810 let editor = cx.entity().downgrade();
1811 let fold_placeholder = FoldPlaceholder {
1812 constrain_width: false,
1813 render: Arc::new(move |fold_id, fold_range, cx| {
1814 let editor = editor.clone();
1815 div()
1816 .id(fold_id)
1817 .bg(cx.theme().colors().ghost_element_background)
1818 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1819 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1820 .rounded_xs()
1821 .size_full()
1822 .cursor_pointer()
1823 .child("⋯")
1824 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1825 .on_click(move |_, _window, cx| {
1826 editor
1827 .update(cx, |editor, cx| {
1828 editor.unfold_ranges(
1829 &[fold_range.start..fold_range.end],
1830 true,
1831 false,
1832 cx,
1833 );
1834 cx.stop_propagation();
1835 })
1836 .ok();
1837 })
1838 .into_any()
1839 }),
1840 merge_adjacent: true,
1841 ..FoldPlaceholder::default()
1842 };
1843 let display_map = display_map.unwrap_or_else(|| {
1844 cx.new(|cx| {
1845 DisplayMap::new(
1846 buffer.clone(),
1847 style.font(),
1848 font_size,
1849 None,
1850 FILE_HEADER_HEIGHT,
1851 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1852 fold_placeholder,
1853 diagnostics_max_severity,
1854 cx,
1855 )
1856 })
1857 });
1858
1859 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1860
1861 let blink_manager = cx.new(|cx| {
1862 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1863 if is_minimap {
1864 blink_manager.disable(cx);
1865 }
1866 blink_manager
1867 });
1868
1869 let soft_wrap_mode_override =
1870 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1871
1872 let mut project_subscriptions = Vec::new();
1873 if full_mode && let Some(project) = project.as_ref() {
1874 project_subscriptions.push(cx.subscribe_in(
1875 project,
1876 window,
1877 |editor, _, event, window, cx| match event {
1878 project::Event::RefreshCodeLens => {
1879 // we always query lens with actions, without storing them, always refreshing them
1880 }
1881 project::Event::RefreshInlayHints => {
1882 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1883 }
1884 project::Event::LanguageServerAdded(..)
1885 | project::Event::LanguageServerRemoved(..) => {
1886 if editor.tasks_update_task.is_none() {
1887 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1888 }
1889 }
1890 project::Event::SnippetEdit(id, snippet_edits) => {
1891 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1892 let focus_handle = editor.focus_handle(cx);
1893 if focus_handle.is_focused(window) {
1894 let snapshot = buffer.read(cx).snapshot();
1895 for (range, snippet) in snippet_edits {
1896 let editor_range =
1897 language::range_from_lsp(*range).to_offset(&snapshot);
1898 editor
1899 .insert_snippet(
1900 &[editor_range],
1901 snippet.clone(),
1902 window,
1903 cx,
1904 )
1905 .ok();
1906 }
1907 }
1908 }
1909 }
1910 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1911 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1912 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1913 }
1914 }
1915
1916 project::Event::EntryRenamed(transaction) => {
1917 let Some(workspace) = editor.workspace() else {
1918 return;
1919 };
1920 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1921 else {
1922 return;
1923 };
1924 if active_editor.entity_id() == cx.entity_id() {
1925 let edited_buffers_already_open = {
1926 let other_editors: Vec<Entity<Editor>> = workspace
1927 .read(cx)
1928 .panes()
1929 .iter()
1930 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1931 .filter(|editor| editor.entity_id() != cx.entity_id())
1932 .collect();
1933
1934 transaction.0.keys().all(|buffer| {
1935 other_editors.iter().any(|editor| {
1936 let multi_buffer = editor.read(cx).buffer();
1937 multi_buffer.read(cx).is_singleton()
1938 && multi_buffer.read(cx).as_singleton().map_or(
1939 false,
1940 |singleton| {
1941 singleton.entity_id() == buffer.entity_id()
1942 },
1943 )
1944 })
1945 })
1946 };
1947
1948 if !edited_buffers_already_open {
1949 let workspace = workspace.downgrade();
1950 let transaction = transaction.clone();
1951 cx.defer_in(window, move |_, window, cx| {
1952 cx.spawn_in(window, async move |editor, cx| {
1953 Self::open_project_transaction(
1954 &editor,
1955 workspace,
1956 transaction,
1957 "Rename".to_string(),
1958 cx,
1959 )
1960 .await
1961 .ok()
1962 })
1963 .detach();
1964 });
1965 }
1966 }
1967 }
1968
1969 _ => {}
1970 },
1971 ));
1972 if let Some(task_inventory) = project
1973 .read(cx)
1974 .task_store()
1975 .read(cx)
1976 .task_inventory()
1977 .cloned()
1978 {
1979 project_subscriptions.push(cx.observe_in(
1980 &task_inventory,
1981 window,
1982 |editor, _, window, cx| {
1983 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1984 },
1985 ));
1986 };
1987
1988 project_subscriptions.push(cx.subscribe_in(
1989 &project.read(cx).breakpoint_store(),
1990 window,
1991 |editor, _, event, window, cx| match event {
1992 BreakpointStoreEvent::ClearDebugLines => {
1993 editor.clear_row_highlights::<ActiveDebugLine>();
1994 editor.refresh_inline_values(cx);
1995 }
1996 BreakpointStoreEvent::SetDebugLine => {
1997 if editor.go_to_active_debug_line(window, cx) {
1998 cx.stop_propagation();
1999 }
2000
2001 editor.refresh_inline_values(cx);
2002 }
2003 _ => {}
2004 },
2005 ));
2006 let git_store = project.read(cx).git_store().clone();
2007 let project = project.clone();
2008 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2009 if let GitStoreEvent::RepositoryUpdated(
2010 _,
2011 RepositoryEvent::Updated {
2012 new_instance: true, ..
2013 },
2014 _,
2015 ) = event
2016 {
2017 this.load_diff_task = Some(
2018 update_uncommitted_diff_for_buffer(
2019 cx.entity(),
2020 &project,
2021 this.buffer.read(cx).all_buffers(),
2022 this.buffer.clone(),
2023 cx,
2024 )
2025 .shared(),
2026 );
2027 }
2028 }));
2029 }
2030
2031 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2032
2033 let inlay_hint_settings =
2034 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2035 let focus_handle = cx.focus_handle();
2036 if !is_minimap {
2037 cx.on_focus(&focus_handle, window, Self::handle_focus)
2038 .detach();
2039 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2040 .detach();
2041 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2042 .detach();
2043 cx.on_blur(&focus_handle, window, Self::handle_blur)
2044 .detach();
2045 cx.observe_pending_input(window, Self::observe_pending_input)
2046 .detach();
2047 }
2048
2049 let show_indent_guides =
2050 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2051 Some(false)
2052 } else {
2053 None
2054 };
2055
2056 let breakpoint_store = match (&mode, project.as_ref()) {
2057 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2058 _ => None,
2059 };
2060
2061 let mut code_action_providers = Vec::new();
2062 let mut load_uncommitted_diff = None;
2063 if let Some(project) = project.clone() {
2064 load_uncommitted_diff = Some(
2065 update_uncommitted_diff_for_buffer(
2066 cx.entity(),
2067 &project,
2068 buffer.read(cx).all_buffers(),
2069 buffer.clone(),
2070 cx,
2071 )
2072 .shared(),
2073 );
2074 code_action_providers.push(Rc::new(project) as Rc<_>);
2075 }
2076
2077 let mut editor = Self {
2078 focus_handle,
2079 show_cursor_when_unfocused: false,
2080 last_focused_descendant: None,
2081 buffer: buffer.clone(),
2082 display_map: display_map.clone(),
2083 placeholder_display_map: None,
2084 selections,
2085 scroll_manager: ScrollManager::new(cx),
2086 columnar_selection_state: None,
2087 add_selections_state: None,
2088 select_next_state: None,
2089 select_prev_state: None,
2090 selection_history: SelectionHistory::default(),
2091 defer_selection_effects: false,
2092 deferred_selection_effects_state: None,
2093 autoclose_regions: Vec::new(),
2094 snippet_stack: InvalidationStack::default(),
2095 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2096 ime_transaction: None,
2097 active_diagnostics: ActiveDiagnostic::None,
2098 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2099 inline_diagnostics_update: Task::ready(()),
2100 inline_diagnostics: Vec::new(),
2101 soft_wrap_mode_override,
2102 diagnostics_max_severity,
2103 hard_wrap: None,
2104 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2105 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2106 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2107 project,
2108 blink_manager: blink_manager.clone(),
2109 show_local_selections: true,
2110 show_scrollbars: ScrollbarAxes {
2111 horizontal: full_mode,
2112 vertical: full_mode,
2113 },
2114 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2115 offset_content: !matches!(mode, EditorMode::SingleLine),
2116 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2117 show_gutter: full_mode,
2118 show_line_numbers: (!full_mode).then_some(false),
2119 use_relative_line_numbers: None,
2120 disable_expand_excerpt_buttons: !full_mode,
2121 show_git_diff_gutter: None,
2122 show_code_actions: None,
2123 show_runnables: None,
2124 show_breakpoints: None,
2125 show_wrap_guides: None,
2126 show_indent_guides,
2127 highlight_order: 0,
2128 highlighted_rows: HashMap::default(),
2129 background_highlights: HashMap::default(),
2130 gutter_highlights: HashMap::default(),
2131 scrollbar_marker_state: ScrollbarMarkerState::default(),
2132 active_indent_guides_state: ActiveIndentGuidesState::default(),
2133 nav_history: None,
2134 context_menu: RefCell::new(None),
2135 context_menu_options: None,
2136 mouse_context_menu: None,
2137 completion_tasks: Vec::new(),
2138 inline_blame_popover: None,
2139 inline_blame_popover_show_task: None,
2140 signature_help_state: SignatureHelpState::default(),
2141 auto_signature_help: None,
2142 find_all_references_task_sources: Vec::new(),
2143 next_completion_id: 0,
2144 next_inlay_id: 0,
2145 code_action_providers,
2146 available_code_actions: None,
2147 code_actions_task: None,
2148 quick_selection_highlight_task: None,
2149 debounced_selection_highlight_task: None,
2150 document_highlights_task: None,
2151 linked_editing_range_task: None,
2152 pending_rename: None,
2153 searchable: !is_minimap,
2154 cursor_shape: EditorSettings::get_global(cx)
2155 .cursor_shape
2156 .unwrap_or_default(),
2157 current_line_highlight: None,
2158 autoindent_mode: Some(AutoindentMode::EachLine),
2159 collapse_matches: false,
2160 workspace: None,
2161 input_enabled: !is_minimap,
2162 use_modal_editing: full_mode,
2163 read_only: is_minimap,
2164 use_autoclose: true,
2165 use_auto_surround: true,
2166 auto_replace_emoji_shortcode: false,
2167 jsx_tag_auto_close_enabled_in_any_buffer: false,
2168 leader_id: None,
2169 remote_id: None,
2170 hover_state: HoverState::default(),
2171 pending_mouse_down: None,
2172 hovered_link_state: None,
2173 edit_prediction_provider: None,
2174 active_edit_prediction: None,
2175 stale_edit_prediction_in_menu: None,
2176 edit_prediction_preview: EditPredictionPreview::Inactive {
2177 released_too_fast: false,
2178 },
2179 inline_diagnostics_enabled: full_mode,
2180 diagnostics_enabled: full_mode,
2181 word_completions_enabled: full_mode,
2182 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2183 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2184 gutter_hovered: false,
2185 pixel_position_of_newest_cursor: None,
2186 last_bounds: None,
2187 last_position_map: None,
2188 expect_bounds_change: None,
2189 gutter_dimensions: GutterDimensions::default(),
2190 style: None,
2191 show_cursor_names: false,
2192 hovered_cursors: HashMap::default(),
2193 next_editor_action_id: EditorActionId::default(),
2194 editor_actions: Rc::default(),
2195 edit_predictions_hidden_for_vim_mode: false,
2196 show_edit_predictions_override: None,
2197 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2198 edit_prediction_settings: EditPredictionSettings::Disabled,
2199 edit_prediction_indent_conflict: false,
2200 edit_prediction_requires_modifier_in_indent_conflict: true,
2201 custom_context_menu: None,
2202 show_git_blame_gutter: false,
2203 show_git_blame_inline: false,
2204 show_selection_menu: None,
2205 show_git_blame_inline_delay_task: None,
2206 git_blame_inline_enabled: full_mode
2207 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2208 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2209 serialize_dirty_buffers: !is_minimap
2210 && ProjectSettings::get_global(cx)
2211 .session
2212 .restore_unsaved_buffers,
2213 blame: None,
2214 blame_subscription: None,
2215 tasks: BTreeMap::default(),
2216
2217 breakpoint_store,
2218 gutter_breakpoint_indicator: (None, None),
2219 hovered_diff_hunk_row: None,
2220 _subscriptions: (!is_minimap)
2221 .then(|| {
2222 vec![
2223 cx.observe(&buffer, Self::on_buffer_changed),
2224 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2225 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2226 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2227 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2228 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2229 cx.observe_window_activation(window, |editor, window, cx| {
2230 let active = window.is_window_active();
2231 editor.blink_manager.update(cx, |blink_manager, cx| {
2232 if active {
2233 blink_manager.enable(cx);
2234 } else {
2235 blink_manager.disable(cx);
2236 }
2237 });
2238 if active {
2239 editor.show_mouse_cursor(cx);
2240 }
2241 }),
2242 ]
2243 })
2244 .unwrap_or_default(),
2245 tasks_update_task: None,
2246 pull_diagnostics_task: Task::ready(()),
2247 colors: None,
2248 next_color_inlay_id: 0,
2249 linked_edit_ranges: Default::default(),
2250 in_project_search: false,
2251 previous_search_ranges: None,
2252 breadcrumb_header: None,
2253 focused_block: None,
2254 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2255 addons: HashMap::default(),
2256 registered_buffers: HashMap::default(),
2257 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2258 selection_mark_mode: false,
2259 toggle_fold_multiple_buffers: Task::ready(()),
2260 serialize_selections: Task::ready(()),
2261 serialize_folds: Task::ready(()),
2262 text_style_refinement: None,
2263 load_diff_task: load_uncommitted_diff,
2264 temporary_diff_override: false,
2265 mouse_cursor_hidden: false,
2266 minimap: None,
2267 hide_mouse_mode: EditorSettings::get_global(cx)
2268 .hide_mouse
2269 .unwrap_or_default(),
2270 change_list: ChangeList::new(),
2271 mode,
2272 selection_drag_state: SelectionDragState::None,
2273 folding_newlines: Task::ready(()),
2274 lookup_key: None,
2275 };
2276
2277 if is_minimap {
2278 return editor;
2279 }
2280
2281 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2282 editor
2283 ._subscriptions
2284 .push(cx.observe(breakpoints, |_, _, cx| {
2285 cx.notify();
2286 }));
2287 }
2288 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2289 editor._subscriptions.extend(project_subscriptions);
2290
2291 editor._subscriptions.push(cx.subscribe_in(
2292 &cx.entity(),
2293 window,
2294 |editor, _, e: &EditorEvent, window, cx| match e {
2295 EditorEvent::ScrollPositionChanged { local, .. } => {
2296 if *local {
2297 let new_anchor = editor.scroll_manager.anchor();
2298 let snapshot = editor.snapshot(window, cx);
2299 editor.update_restoration_data(cx, move |data| {
2300 data.scroll_position = (
2301 new_anchor.top_row(&snapshot.buffer_snapshot),
2302 new_anchor.offset,
2303 );
2304 });
2305 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2306 editor.inline_blame_popover.take();
2307 }
2308 }
2309 EditorEvent::Edited { .. } => {
2310 if !vim_enabled(cx) {
2311 let (map, selections) = editor.selections.all_adjusted_display(cx);
2312 let pop_state = editor
2313 .change_list
2314 .last()
2315 .map(|previous| {
2316 previous.len() == selections.len()
2317 && previous.iter().enumerate().all(|(ix, p)| {
2318 p.to_display_point(&map).row()
2319 == selections[ix].head().row()
2320 })
2321 })
2322 .unwrap_or(false);
2323 let new_positions = selections
2324 .into_iter()
2325 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2326 .collect();
2327 editor
2328 .change_list
2329 .push_to_change_list(pop_state, new_positions);
2330 }
2331 }
2332 _ => (),
2333 },
2334 ));
2335
2336 if let Some(dap_store) = editor
2337 .project
2338 .as_ref()
2339 .map(|project| project.read(cx).dap_store())
2340 {
2341 let weak_editor = cx.weak_entity();
2342
2343 editor
2344 ._subscriptions
2345 .push(
2346 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2347 let session_entity = cx.entity();
2348 weak_editor
2349 .update(cx, |editor, cx| {
2350 editor._subscriptions.push(
2351 cx.subscribe(&session_entity, Self::on_debug_session_event),
2352 );
2353 })
2354 .ok();
2355 }),
2356 );
2357
2358 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2359 editor
2360 ._subscriptions
2361 .push(cx.subscribe(&session, Self::on_debug_session_event));
2362 }
2363 }
2364
2365 // skip adding the initial selection to selection history
2366 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2367 editor.end_selection(window, cx);
2368 editor.selection_history.mode = SelectionHistoryMode::Normal;
2369
2370 editor.scroll_manager.show_scrollbars(window, cx);
2371 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2372
2373 if full_mode {
2374 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2375 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2376
2377 if editor.git_blame_inline_enabled {
2378 editor.start_git_blame_inline(false, window, cx);
2379 }
2380
2381 editor.go_to_active_debug_line(window, cx);
2382
2383 if let Some(buffer) = buffer.read(cx).as_singleton()
2384 && let Some(project) = editor.project()
2385 {
2386 let handle = project.update(cx, |project, cx| {
2387 project.register_buffer_with_language_servers(&buffer, cx)
2388 });
2389 editor
2390 .registered_buffers
2391 .insert(buffer.read(cx).remote_id(), handle);
2392 }
2393
2394 editor.minimap =
2395 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2396 editor.colors = Some(LspColorData::new(cx));
2397 editor.update_lsp_data(false, None, window, cx);
2398 }
2399
2400 if editor.mode.is_full() {
2401 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2402 }
2403
2404 editor
2405 }
2406
2407 pub fn deploy_mouse_context_menu(
2408 &mut self,
2409 position: gpui::Point<Pixels>,
2410 context_menu: Entity<ContextMenu>,
2411 window: &mut Window,
2412 cx: &mut Context<Self>,
2413 ) {
2414 self.mouse_context_menu = Some(MouseContextMenu::new(
2415 self,
2416 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2417 context_menu,
2418 window,
2419 cx,
2420 ));
2421 }
2422
2423 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2424 self.mouse_context_menu
2425 .as_ref()
2426 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2427 }
2428
2429 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2430 if self
2431 .selections
2432 .pending_anchor()
2433 .is_some_and(|pending_selection| {
2434 let snapshot = self.buffer().read(cx).snapshot(cx);
2435 pending_selection.range().includes(range, &snapshot)
2436 })
2437 {
2438 return true;
2439 }
2440
2441 self.selections
2442 .disjoint_in_range::<usize>(range.clone(), cx)
2443 .into_iter()
2444 .any(|selection| {
2445 // This is needed to cover a corner case, if we just check for an existing
2446 // selection in the fold range, having a cursor at the start of the fold
2447 // marks it as selected. Non-empty selections don't cause this.
2448 let length = selection.end - selection.start;
2449 length > 0
2450 })
2451 }
2452
2453 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2454 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2455 }
2456
2457 fn key_context_internal(
2458 &self,
2459 has_active_edit_prediction: bool,
2460 window: &Window,
2461 cx: &App,
2462 ) -> KeyContext {
2463 let mut key_context = KeyContext::new_with_defaults();
2464 key_context.add("Editor");
2465 let mode = match self.mode {
2466 EditorMode::SingleLine => "single_line",
2467 EditorMode::AutoHeight { .. } => "auto_height",
2468 EditorMode::Minimap { .. } => "minimap",
2469 EditorMode::Full { .. } => "full",
2470 };
2471
2472 if EditorSettings::jupyter_enabled(cx) {
2473 key_context.add("jupyter");
2474 }
2475
2476 key_context.set("mode", mode);
2477 if self.pending_rename.is_some() {
2478 key_context.add("renaming");
2479 }
2480
2481 match self.context_menu.borrow().as_ref() {
2482 Some(CodeContextMenu::Completions(menu)) => {
2483 if menu.visible() {
2484 key_context.add("menu");
2485 key_context.add("showing_completions");
2486 }
2487 }
2488 Some(CodeContextMenu::CodeActions(menu)) => {
2489 if menu.visible() {
2490 key_context.add("menu");
2491 key_context.add("showing_code_actions")
2492 }
2493 }
2494 None => {}
2495 }
2496
2497 if self.signature_help_state.has_multiple_signatures() {
2498 key_context.add("showing_signature_help");
2499 }
2500
2501 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2502 if !self.focus_handle(cx).contains_focused(window, cx)
2503 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2504 {
2505 for addon in self.addons.values() {
2506 addon.extend_key_context(&mut key_context, cx)
2507 }
2508 }
2509
2510 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2511 if let Some(extension) = singleton_buffer
2512 .read(cx)
2513 .file()
2514 .and_then(|file| file.path().extension())
2515 {
2516 key_context.set("extension", extension.to_string());
2517 }
2518 } else {
2519 key_context.add("multibuffer");
2520 }
2521
2522 if has_active_edit_prediction {
2523 if self.edit_prediction_in_conflict() {
2524 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2525 } else {
2526 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2527 key_context.add("copilot_suggestion");
2528 }
2529 }
2530
2531 if self.selection_mark_mode {
2532 key_context.add("selection_mode");
2533 }
2534
2535 key_context
2536 }
2537
2538 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2539 self.last_bounds.as_ref()
2540 }
2541
2542 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2543 if self.mouse_cursor_hidden {
2544 self.mouse_cursor_hidden = false;
2545 cx.notify();
2546 }
2547 }
2548
2549 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2550 let hide_mouse_cursor = match origin {
2551 HideMouseCursorOrigin::TypingAction => {
2552 matches!(
2553 self.hide_mouse_mode,
2554 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2555 )
2556 }
2557 HideMouseCursorOrigin::MovementAction => {
2558 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2559 }
2560 };
2561 if self.mouse_cursor_hidden != hide_mouse_cursor {
2562 self.mouse_cursor_hidden = hide_mouse_cursor;
2563 cx.notify();
2564 }
2565 }
2566
2567 pub fn edit_prediction_in_conflict(&self) -> bool {
2568 if !self.show_edit_predictions_in_menu() {
2569 return false;
2570 }
2571
2572 let showing_completions = self
2573 .context_menu
2574 .borrow()
2575 .as_ref()
2576 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2577
2578 showing_completions
2579 || self.edit_prediction_requires_modifier()
2580 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2581 // bindings to insert tab characters.
2582 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2583 }
2584
2585 pub fn accept_edit_prediction_keybind(
2586 &self,
2587 accept_partial: bool,
2588 window: &Window,
2589 cx: &App,
2590 ) -> AcceptEditPredictionBinding {
2591 let key_context = self.key_context_internal(true, window, cx);
2592 let in_conflict = self.edit_prediction_in_conflict();
2593
2594 let bindings = if accept_partial {
2595 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2596 } else {
2597 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2598 };
2599
2600 // TODO: if the binding contains multiple keystrokes, display all of them, not
2601 // just the first one.
2602 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2603 !in_conflict
2604 || binding
2605 .keystrokes()
2606 .first()
2607 .is_some_and(|keystroke| keystroke.modifiers().modified())
2608 }))
2609 }
2610
2611 pub fn new_file(
2612 workspace: &mut Workspace,
2613 _: &workspace::NewFile,
2614 window: &mut Window,
2615 cx: &mut Context<Workspace>,
2616 ) {
2617 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2618 "Failed to create buffer",
2619 window,
2620 cx,
2621 |e, _, _| match e.error_code() {
2622 ErrorCode::RemoteUpgradeRequired => Some(format!(
2623 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2624 e.error_tag("required").unwrap_or("the latest version")
2625 )),
2626 _ => None,
2627 },
2628 );
2629 }
2630
2631 pub fn new_in_workspace(
2632 workspace: &mut Workspace,
2633 window: &mut Window,
2634 cx: &mut Context<Workspace>,
2635 ) -> Task<Result<Entity<Editor>>> {
2636 let project = workspace.project().clone();
2637 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2638
2639 cx.spawn_in(window, async move |workspace, cx| {
2640 let buffer = create.await?;
2641 workspace.update_in(cx, |workspace, window, cx| {
2642 let editor =
2643 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2644 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2645 editor
2646 })
2647 })
2648 }
2649
2650 fn new_file_vertical(
2651 workspace: &mut Workspace,
2652 _: &workspace::NewFileSplitVertical,
2653 window: &mut Window,
2654 cx: &mut Context<Workspace>,
2655 ) {
2656 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2657 }
2658
2659 fn new_file_horizontal(
2660 workspace: &mut Workspace,
2661 _: &workspace::NewFileSplitHorizontal,
2662 window: &mut Window,
2663 cx: &mut Context<Workspace>,
2664 ) {
2665 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2666 }
2667
2668 fn new_file_in_direction(
2669 workspace: &mut Workspace,
2670 direction: SplitDirection,
2671 window: &mut Window,
2672 cx: &mut Context<Workspace>,
2673 ) {
2674 let project = workspace.project().clone();
2675 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2676
2677 cx.spawn_in(window, async move |workspace, cx| {
2678 let buffer = create.await?;
2679 workspace.update_in(cx, move |workspace, window, cx| {
2680 workspace.split_item(
2681 direction,
2682 Box::new(
2683 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2684 ),
2685 window,
2686 cx,
2687 )
2688 })?;
2689 anyhow::Ok(())
2690 })
2691 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2692 match e.error_code() {
2693 ErrorCode::RemoteUpgradeRequired => Some(format!(
2694 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2695 e.error_tag("required").unwrap_or("the latest version")
2696 )),
2697 _ => None,
2698 }
2699 });
2700 }
2701
2702 pub fn leader_id(&self) -> Option<CollaboratorId> {
2703 self.leader_id
2704 }
2705
2706 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2707 &self.buffer
2708 }
2709
2710 pub fn project(&self) -> Option<&Entity<Project>> {
2711 self.project.as_ref()
2712 }
2713
2714 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2715 self.workspace.as_ref()?.0.upgrade()
2716 }
2717
2718 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2719 self.buffer().read(cx).title(cx)
2720 }
2721
2722 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2723 let git_blame_gutter_max_author_length = self
2724 .render_git_blame_gutter(cx)
2725 .then(|| {
2726 if let Some(blame) = self.blame.as_ref() {
2727 let max_author_length =
2728 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2729 Some(max_author_length)
2730 } else {
2731 None
2732 }
2733 })
2734 .flatten();
2735
2736 EditorSnapshot {
2737 mode: self.mode.clone(),
2738 show_gutter: self.show_gutter,
2739 show_line_numbers: self.show_line_numbers,
2740 show_git_diff_gutter: self.show_git_diff_gutter,
2741 show_code_actions: self.show_code_actions,
2742 show_runnables: self.show_runnables,
2743 show_breakpoints: self.show_breakpoints,
2744 git_blame_gutter_max_author_length,
2745 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2746 placeholder_display_snapshot: self
2747 .placeholder_display_map
2748 .as_ref()
2749 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2750 scroll_anchor: self.scroll_manager.anchor(),
2751 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2752 is_focused: self.focus_handle.is_focused(window),
2753 current_line_highlight: self
2754 .current_line_highlight
2755 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2756 gutter_hovered: self.gutter_hovered,
2757 }
2758 }
2759
2760 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2761 self.buffer.read(cx).language_at(point, cx)
2762 }
2763
2764 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2765 self.buffer.read(cx).read(cx).file_at(point).cloned()
2766 }
2767
2768 pub fn active_excerpt(
2769 &self,
2770 cx: &App,
2771 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2772 self.buffer
2773 .read(cx)
2774 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2775 }
2776
2777 pub fn mode(&self) -> &EditorMode {
2778 &self.mode
2779 }
2780
2781 pub fn set_mode(&mut self, mode: EditorMode) {
2782 self.mode = mode;
2783 }
2784
2785 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2786 self.collaboration_hub.as_deref()
2787 }
2788
2789 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2790 self.collaboration_hub = Some(hub);
2791 }
2792
2793 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2794 self.in_project_search = in_project_search;
2795 }
2796
2797 pub fn set_custom_context_menu(
2798 &mut self,
2799 f: impl 'static
2800 + Fn(
2801 &mut Self,
2802 DisplayPoint,
2803 &mut Window,
2804 &mut Context<Self>,
2805 ) -> Option<Entity<ui::ContextMenu>>,
2806 ) {
2807 self.custom_context_menu = Some(Box::new(f))
2808 }
2809
2810 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2811 self.completion_provider = provider;
2812 }
2813
2814 #[cfg(any(test, feature = "test-support"))]
2815 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2816 self.completion_provider.clone()
2817 }
2818
2819 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2820 self.semantics_provider.clone()
2821 }
2822
2823 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2824 self.semantics_provider = provider;
2825 }
2826
2827 pub fn set_edit_prediction_provider<T>(
2828 &mut self,
2829 provider: Option<Entity<T>>,
2830 window: &mut Window,
2831 cx: &mut Context<Self>,
2832 ) where
2833 T: EditPredictionProvider,
2834 {
2835 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2836 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2837 if this.focus_handle.is_focused(window) {
2838 this.update_visible_edit_prediction(window, cx);
2839 }
2840 }),
2841 provider: Arc::new(provider),
2842 });
2843 self.update_edit_prediction_settings(cx);
2844 self.refresh_edit_prediction(false, false, window, cx);
2845 }
2846
2847 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2848 self.placeholder_display_map
2849 .as_ref()
2850 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2851 }
2852
2853 pub fn set_placeholder_text(
2854 &mut self,
2855 placeholder_text: &str,
2856 window: &mut Window,
2857 cx: &mut Context<Self>,
2858 ) {
2859 let multibuffer = cx
2860 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2861
2862 let style = window.text_style();
2863
2864 self.placeholder_display_map = Some(cx.new(|cx| {
2865 DisplayMap::new(
2866 multibuffer,
2867 style.font(),
2868 style.font_size.to_pixels(window.rem_size()),
2869 None,
2870 FILE_HEADER_HEIGHT,
2871 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2872 Default::default(),
2873 DiagnosticSeverity::Off,
2874 cx,
2875 )
2876 }));
2877 cx.notify();
2878 }
2879
2880 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2881 self.cursor_shape = cursor_shape;
2882
2883 // Disrupt blink for immediate user feedback that the cursor shape has changed
2884 self.blink_manager.update(cx, BlinkManager::show_cursor);
2885
2886 cx.notify();
2887 }
2888
2889 pub fn set_current_line_highlight(
2890 &mut self,
2891 current_line_highlight: Option<CurrentLineHighlight>,
2892 ) {
2893 self.current_line_highlight = current_line_highlight;
2894 }
2895
2896 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2897 self.collapse_matches = collapse_matches;
2898 }
2899
2900 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2901 let buffers = self.buffer.read(cx).all_buffers();
2902 let Some(project) = self.project.as_ref() else {
2903 return;
2904 };
2905 project.update(cx, |project, cx| {
2906 for buffer in buffers {
2907 self.registered_buffers
2908 .entry(buffer.read(cx).remote_id())
2909 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2910 }
2911 })
2912 }
2913
2914 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2915 if self.collapse_matches {
2916 return range.start..range.start;
2917 }
2918 range.clone()
2919 }
2920
2921 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2922 if self.display_map.read(cx).clip_at_line_ends != clip {
2923 self.display_map
2924 .update(cx, |map, _| map.clip_at_line_ends = clip);
2925 }
2926 }
2927
2928 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2929 self.input_enabled = input_enabled;
2930 }
2931
2932 pub fn set_edit_predictions_hidden_for_vim_mode(
2933 &mut self,
2934 hidden: bool,
2935 window: &mut Window,
2936 cx: &mut Context<Self>,
2937 ) {
2938 if hidden != self.edit_predictions_hidden_for_vim_mode {
2939 self.edit_predictions_hidden_for_vim_mode = hidden;
2940 if hidden {
2941 self.update_visible_edit_prediction(window, cx);
2942 } else {
2943 self.refresh_edit_prediction(true, false, window, cx);
2944 }
2945 }
2946 }
2947
2948 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2949 self.menu_edit_predictions_policy = value;
2950 }
2951
2952 pub fn set_autoindent(&mut self, autoindent: bool) {
2953 if autoindent {
2954 self.autoindent_mode = Some(AutoindentMode::EachLine);
2955 } else {
2956 self.autoindent_mode = None;
2957 }
2958 }
2959
2960 pub fn read_only(&self, cx: &App) -> bool {
2961 self.read_only || self.buffer.read(cx).read_only()
2962 }
2963
2964 pub fn set_read_only(&mut self, read_only: bool) {
2965 self.read_only = read_only;
2966 }
2967
2968 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2969 self.use_autoclose = autoclose;
2970 }
2971
2972 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2973 self.use_auto_surround = auto_surround;
2974 }
2975
2976 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2977 self.auto_replace_emoji_shortcode = auto_replace;
2978 }
2979
2980 pub fn toggle_edit_predictions(
2981 &mut self,
2982 _: &ToggleEditPrediction,
2983 window: &mut Window,
2984 cx: &mut Context<Self>,
2985 ) {
2986 if self.show_edit_predictions_override.is_some() {
2987 self.set_show_edit_predictions(None, window, cx);
2988 } else {
2989 let show_edit_predictions = !self.edit_predictions_enabled();
2990 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2991 }
2992 }
2993
2994 pub fn set_show_edit_predictions(
2995 &mut self,
2996 show_edit_predictions: Option<bool>,
2997 window: &mut Window,
2998 cx: &mut Context<Self>,
2999 ) {
3000 self.show_edit_predictions_override = show_edit_predictions;
3001 self.update_edit_prediction_settings(cx);
3002
3003 if let Some(false) = show_edit_predictions {
3004 self.discard_edit_prediction(false, cx);
3005 } else {
3006 self.refresh_edit_prediction(false, true, window, cx);
3007 }
3008 }
3009
3010 fn edit_predictions_disabled_in_scope(
3011 &self,
3012 buffer: &Entity<Buffer>,
3013 buffer_position: language::Anchor,
3014 cx: &App,
3015 ) -> bool {
3016 let snapshot = buffer.read(cx).snapshot();
3017 let settings = snapshot.settings_at(buffer_position, cx);
3018
3019 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3020 return false;
3021 };
3022
3023 scope.override_name().is_some_and(|scope_name| {
3024 settings
3025 .edit_predictions_disabled_in
3026 .iter()
3027 .any(|s| s == scope_name)
3028 })
3029 }
3030
3031 pub fn set_use_modal_editing(&mut self, to: bool) {
3032 self.use_modal_editing = to;
3033 }
3034
3035 pub fn use_modal_editing(&self) -> bool {
3036 self.use_modal_editing
3037 }
3038
3039 fn selections_did_change(
3040 &mut self,
3041 local: bool,
3042 old_cursor_position: &Anchor,
3043 effects: SelectionEffects,
3044 window: &mut Window,
3045 cx: &mut Context<Self>,
3046 ) {
3047 window.invalidate_character_coordinates();
3048
3049 // Copy selections to primary selection buffer
3050 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3051 if local {
3052 let selections = self.selections.all::<usize>(cx);
3053 let buffer_handle = self.buffer.read(cx).read(cx);
3054
3055 let mut text = String::new();
3056 for (index, selection) in selections.iter().enumerate() {
3057 let text_for_selection = buffer_handle
3058 .text_for_range(selection.start..selection.end)
3059 .collect::<String>();
3060
3061 text.push_str(&text_for_selection);
3062 if index != selections.len() - 1 {
3063 text.push('\n');
3064 }
3065 }
3066
3067 if !text.is_empty() {
3068 cx.write_to_primary(ClipboardItem::new_string(text));
3069 }
3070 }
3071
3072 let selection_anchors = self.selections.disjoint_anchors_arc();
3073
3074 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3075 self.buffer.update(cx, |buffer, cx| {
3076 buffer.set_active_selections(
3077 &selection_anchors,
3078 self.selections.line_mode(),
3079 self.cursor_shape,
3080 cx,
3081 )
3082 });
3083 }
3084 let display_map = self
3085 .display_map
3086 .update(cx, |display_map, cx| display_map.snapshot(cx));
3087 let buffer = &display_map.buffer_snapshot;
3088 if self.selections.count() == 1 {
3089 self.add_selections_state = None;
3090 }
3091 self.select_next_state = None;
3092 self.select_prev_state = None;
3093 self.select_syntax_node_history.try_clear();
3094 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3095 self.snippet_stack.invalidate(&selection_anchors, buffer);
3096 self.take_rename(false, window, cx);
3097
3098 let newest_selection = self.selections.newest_anchor();
3099 let new_cursor_position = newest_selection.head();
3100 let selection_start = newest_selection.start;
3101
3102 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3103 self.push_to_nav_history(
3104 *old_cursor_position,
3105 Some(new_cursor_position.to_point(buffer)),
3106 false,
3107 effects.nav_history == Some(true),
3108 cx,
3109 );
3110 }
3111
3112 if local {
3113 if let Some(buffer_id) = new_cursor_position.buffer_id
3114 && !self.registered_buffers.contains_key(&buffer_id)
3115 && let Some(project) = self.project.as_ref()
3116 {
3117 project.update(cx, |project, cx| {
3118 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3119 return;
3120 };
3121 self.registered_buffers.insert(
3122 buffer_id,
3123 project.register_buffer_with_language_servers(&buffer, cx),
3124 );
3125 })
3126 }
3127
3128 let mut context_menu = self.context_menu.borrow_mut();
3129 let completion_menu = match context_menu.as_ref() {
3130 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3131 Some(CodeContextMenu::CodeActions(_)) => {
3132 *context_menu = None;
3133 None
3134 }
3135 None => None,
3136 };
3137 let completion_position = completion_menu.map(|menu| menu.initial_position);
3138 drop(context_menu);
3139
3140 if effects.completions
3141 && let Some(completion_position) = completion_position
3142 {
3143 let start_offset = selection_start.to_offset(buffer);
3144 let position_matches = start_offset == completion_position.to_offset(buffer);
3145 let continue_showing = if position_matches {
3146 if self.snippet_stack.is_empty() {
3147 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3148 == Some(CharKind::Word)
3149 } else {
3150 // Snippet choices can be shown even when the cursor is in whitespace.
3151 // Dismissing the menu with actions like backspace is handled by
3152 // invalidation regions.
3153 true
3154 }
3155 } else {
3156 false
3157 };
3158
3159 if continue_showing {
3160 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3161 } else {
3162 self.hide_context_menu(window, cx);
3163 }
3164 }
3165
3166 hide_hover(self, cx);
3167
3168 if old_cursor_position.to_display_point(&display_map).row()
3169 != new_cursor_position.to_display_point(&display_map).row()
3170 {
3171 self.available_code_actions.take();
3172 }
3173 self.refresh_code_actions(window, cx);
3174 self.refresh_document_highlights(cx);
3175 self.refresh_selected_text_highlights(false, window, cx);
3176 refresh_matching_bracket_highlights(self, window, cx);
3177 self.update_visible_edit_prediction(window, cx);
3178 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3179 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3180 self.inline_blame_popover.take();
3181 if self.git_blame_inline_enabled {
3182 self.start_inline_blame_timer(window, cx);
3183 }
3184 }
3185
3186 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3187 cx.emit(EditorEvent::SelectionsChanged { local });
3188
3189 let selections = &self.selections.disjoint_anchors_arc();
3190 if selections.len() == 1 {
3191 cx.emit(SearchEvent::ActiveMatchChanged)
3192 }
3193 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3194 let inmemory_selections = selections
3195 .iter()
3196 .map(|s| {
3197 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3198 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3199 })
3200 .collect();
3201 self.update_restoration_data(cx, |data| {
3202 data.selections = inmemory_selections;
3203 });
3204
3205 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3206 && let Some(workspace_id) =
3207 self.workspace.as_ref().and_then(|workspace| workspace.1)
3208 {
3209 let snapshot = self.buffer().read(cx).snapshot(cx);
3210 let selections = selections.clone();
3211 let background_executor = cx.background_executor().clone();
3212 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3213 self.serialize_selections = cx.background_spawn(async move {
3214 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3215 let db_selections = selections
3216 .iter()
3217 .map(|selection| {
3218 (
3219 selection.start.to_offset(&snapshot),
3220 selection.end.to_offset(&snapshot),
3221 )
3222 })
3223 .collect();
3224
3225 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3226 .await
3227 .with_context(|| {
3228 format!(
3229 "persisting editor selections for editor {editor_id}, \
3230 workspace {workspace_id:?}"
3231 )
3232 })
3233 .log_err();
3234 });
3235 }
3236 }
3237
3238 cx.notify();
3239 }
3240
3241 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3242 use text::ToOffset as _;
3243 use text::ToPoint as _;
3244
3245 if self.mode.is_minimap()
3246 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3247 {
3248 return;
3249 }
3250
3251 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3252 return;
3253 };
3254
3255 let snapshot = singleton.read(cx).snapshot();
3256 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3257 let display_snapshot = display_map.snapshot(cx);
3258
3259 display_snapshot
3260 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3261 .map(|fold| {
3262 fold.range.start.text_anchor.to_point(&snapshot)
3263 ..fold.range.end.text_anchor.to_point(&snapshot)
3264 })
3265 .collect()
3266 });
3267 self.update_restoration_data(cx, |data| {
3268 data.folds = inmemory_folds;
3269 });
3270
3271 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3272 return;
3273 };
3274 let background_executor = cx.background_executor().clone();
3275 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3276 let db_folds = self.display_map.update(cx, |display_map, cx| {
3277 display_map
3278 .snapshot(cx)
3279 .folds_in_range(0..snapshot.len())
3280 .map(|fold| {
3281 (
3282 fold.range.start.text_anchor.to_offset(&snapshot),
3283 fold.range.end.text_anchor.to_offset(&snapshot),
3284 )
3285 })
3286 .collect()
3287 });
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.range.start.cmp(&start, &map.buffer_snapshot) {
4348 Ordering::Less => i += 1,
4349 Ordering::Greater => break,
4350 Ordering::Equal => {
4351 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4352 Ordering::Less => i += 1,
4353 Ordering::Equal => break,
4354 Ordering::Greater => break,
4355 }
4356 }
4357 }
4358 }
4359 this.autoclose_regions.insert(
4360 i,
4361 AutocloseRegion {
4362 selection_id,
4363 range: start..end,
4364 pair,
4365 },
4366 );
4367 }
4368
4369 let had_active_edit_prediction = this.has_active_edit_prediction();
4370 this.change_selections(
4371 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4372 window,
4373 cx,
4374 |s| s.select(new_selections),
4375 );
4376
4377 if !bracket_inserted
4378 && let Some(on_type_format_task) =
4379 this.trigger_on_type_formatting(text.to_string(), window, cx)
4380 {
4381 on_type_format_task.detach_and_log_err(cx);
4382 }
4383
4384 let editor_settings = EditorSettings::get_global(cx);
4385 if bracket_inserted
4386 && (editor_settings.auto_signature_help
4387 || editor_settings.show_signature_help_after_edits)
4388 {
4389 this.show_signature_help(&ShowSignatureHelp, window, cx);
4390 }
4391
4392 let trigger_in_words =
4393 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4394 if this.hard_wrap.is_some() {
4395 let latest: Range<Point> = this.selections.newest(cx).range();
4396 if latest.is_empty()
4397 && this
4398 .buffer()
4399 .read(cx)
4400 .snapshot(cx)
4401 .line_len(MultiBufferRow(latest.start.row))
4402 == latest.start.column
4403 {
4404 this.rewrap_impl(
4405 RewrapOptions {
4406 override_language_settings: true,
4407 preserve_existing_whitespace: true,
4408 },
4409 cx,
4410 )
4411 }
4412 }
4413 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4414 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4415 this.refresh_edit_prediction(true, false, window, cx);
4416 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4417 });
4418 }
4419
4420 fn find_possible_emoji_shortcode_at_position(
4421 snapshot: &MultiBufferSnapshot,
4422 position: Point,
4423 ) -> Option<String> {
4424 let mut chars = Vec::new();
4425 let mut found_colon = false;
4426 for char in snapshot.reversed_chars_at(position).take(100) {
4427 // Found a possible emoji shortcode in the middle of the buffer
4428 if found_colon {
4429 if char.is_whitespace() {
4430 chars.reverse();
4431 return Some(chars.iter().collect());
4432 }
4433 // If the previous character is not a whitespace, we are in the middle of a word
4434 // and we only want to complete the shortcode if the word is made up of other emojis
4435 let mut containing_word = String::new();
4436 for ch in snapshot
4437 .reversed_chars_at(position)
4438 .skip(chars.len() + 1)
4439 .take(100)
4440 {
4441 if ch.is_whitespace() {
4442 break;
4443 }
4444 containing_word.push(ch);
4445 }
4446 let containing_word = containing_word.chars().rev().collect::<String>();
4447 if util::word_consists_of_emojis(containing_word.as_str()) {
4448 chars.reverse();
4449 return Some(chars.iter().collect());
4450 }
4451 }
4452
4453 if char.is_whitespace() || !char.is_ascii() {
4454 return None;
4455 }
4456 if char == ':' {
4457 found_colon = true;
4458 } else {
4459 chars.push(char);
4460 }
4461 }
4462 // Found a possible emoji shortcode at the beginning of the buffer
4463 chars.reverse();
4464 Some(chars.iter().collect())
4465 }
4466
4467 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4469 self.transact(window, cx, |this, window, cx| {
4470 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4471 let selections = this.selections.all::<usize>(cx);
4472 let multi_buffer = this.buffer.read(cx);
4473 let buffer = multi_buffer.snapshot(cx);
4474 selections
4475 .iter()
4476 .map(|selection| {
4477 let start_point = selection.start.to_point(&buffer);
4478 let mut existing_indent =
4479 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4480 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4481 let start = selection.start;
4482 let end = selection.end;
4483 let selection_is_empty = start == end;
4484 let language_scope = buffer.language_scope_at(start);
4485 let (
4486 comment_delimiter,
4487 doc_delimiter,
4488 insert_extra_newline,
4489 indent_on_newline,
4490 indent_on_extra_newline,
4491 ) = if let Some(language) = &language_scope {
4492 let mut insert_extra_newline =
4493 insert_extra_newline_brackets(&buffer, start..end, language)
4494 || insert_extra_newline_tree_sitter(&buffer, start..end);
4495
4496 // Comment extension on newline is allowed only for cursor selections
4497 let comment_delimiter = maybe!({
4498 if !selection_is_empty {
4499 return None;
4500 }
4501
4502 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4503 return None;
4504 }
4505
4506 let delimiters = language.line_comment_prefixes();
4507 let max_len_of_delimiter =
4508 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4509 let (snapshot, range) =
4510 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4511
4512 let num_of_whitespaces = snapshot
4513 .chars_for_range(range.clone())
4514 .take_while(|c| c.is_whitespace())
4515 .count();
4516 let comment_candidate = snapshot
4517 .chars_for_range(range.clone())
4518 .skip(num_of_whitespaces)
4519 .take(max_len_of_delimiter)
4520 .collect::<String>();
4521 let (delimiter, trimmed_len) = delimiters
4522 .iter()
4523 .filter_map(|delimiter| {
4524 let prefix = delimiter.trim_end();
4525 if comment_candidate.starts_with(prefix) {
4526 Some((delimiter, prefix.len()))
4527 } else {
4528 None
4529 }
4530 })
4531 .max_by_key(|(_, len)| *len)?;
4532
4533 if let Some(BlockCommentConfig {
4534 start: block_start, ..
4535 }) = language.block_comment()
4536 {
4537 let block_start_trimmed = block_start.trim_end();
4538 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4539 let line_content = snapshot
4540 .chars_for_range(range)
4541 .skip(num_of_whitespaces)
4542 .take(block_start_trimmed.len())
4543 .collect::<String>();
4544
4545 if line_content.starts_with(block_start_trimmed) {
4546 return None;
4547 }
4548 }
4549 }
4550
4551 let cursor_is_placed_after_comment_marker =
4552 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4553 if cursor_is_placed_after_comment_marker {
4554 Some(delimiter.clone())
4555 } else {
4556 None
4557 }
4558 });
4559
4560 let mut indent_on_newline = IndentSize::spaces(0);
4561 let mut indent_on_extra_newline = IndentSize::spaces(0);
4562
4563 let doc_delimiter = maybe!({
4564 if !selection_is_empty {
4565 return None;
4566 }
4567
4568 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4569 return None;
4570 }
4571
4572 let BlockCommentConfig {
4573 start: start_tag,
4574 end: end_tag,
4575 prefix: delimiter,
4576 tab_size: len,
4577 } = language.documentation_comment()?;
4578 let is_within_block_comment = buffer
4579 .language_scope_at(start_point)
4580 .is_some_and(|scope| scope.override_name() == Some("comment"));
4581 if !is_within_block_comment {
4582 return None;
4583 }
4584
4585 let (snapshot, range) =
4586 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4587
4588 let num_of_whitespaces = snapshot
4589 .chars_for_range(range.clone())
4590 .take_while(|c| c.is_whitespace())
4591 .count();
4592
4593 // 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.
4594 let column = start_point.column;
4595 let cursor_is_after_start_tag = {
4596 let start_tag_len = start_tag.len();
4597 let start_tag_line = snapshot
4598 .chars_for_range(range.clone())
4599 .skip(num_of_whitespaces)
4600 .take(start_tag_len)
4601 .collect::<String>();
4602 if start_tag_line.starts_with(start_tag.as_ref()) {
4603 num_of_whitespaces + start_tag_len <= column as usize
4604 } else {
4605 false
4606 }
4607 };
4608
4609 let cursor_is_after_delimiter = {
4610 let delimiter_trim = delimiter.trim_end();
4611 let delimiter_line = snapshot
4612 .chars_for_range(range.clone())
4613 .skip(num_of_whitespaces)
4614 .take(delimiter_trim.len())
4615 .collect::<String>();
4616 if delimiter_line.starts_with(delimiter_trim) {
4617 num_of_whitespaces + delimiter_trim.len() <= column as usize
4618 } else {
4619 false
4620 }
4621 };
4622
4623 let cursor_is_before_end_tag_if_exists = {
4624 let mut char_position = 0u32;
4625 let mut end_tag_offset = None;
4626
4627 'outer: for chunk in snapshot.text_for_range(range) {
4628 if let Some(byte_pos) = chunk.find(&**end_tag) {
4629 let chars_before_match =
4630 chunk[..byte_pos].chars().count() as u32;
4631 end_tag_offset =
4632 Some(char_position + chars_before_match);
4633 break 'outer;
4634 }
4635 char_position += chunk.chars().count() as u32;
4636 }
4637
4638 if let Some(end_tag_offset) = end_tag_offset {
4639 let cursor_is_before_end_tag = column <= end_tag_offset;
4640 if cursor_is_after_start_tag {
4641 if cursor_is_before_end_tag {
4642 insert_extra_newline = true;
4643 }
4644 let cursor_is_at_start_of_end_tag =
4645 column == end_tag_offset;
4646 if cursor_is_at_start_of_end_tag {
4647 indent_on_extra_newline.len = *len;
4648 }
4649 }
4650 cursor_is_before_end_tag
4651 } else {
4652 true
4653 }
4654 };
4655
4656 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4657 && cursor_is_before_end_tag_if_exists
4658 {
4659 if cursor_is_after_start_tag {
4660 indent_on_newline.len = *len;
4661 }
4662 Some(delimiter.clone())
4663 } else {
4664 None
4665 }
4666 });
4667
4668 (
4669 comment_delimiter,
4670 doc_delimiter,
4671 insert_extra_newline,
4672 indent_on_newline,
4673 indent_on_extra_newline,
4674 )
4675 } else {
4676 (
4677 None,
4678 None,
4679 false,
4680 IndentSize::default(),
4681 IndentSize::default(),
4682 )
4683 };
4684
4685 let prevent_auto_indent = doc_delimiter.is_some();
4686 let delimiter = comment_delimiter.or(doc_delimiter);
4687
4688 let capacity_for_delimiter =
4689 delimiter.as_deref().map(str::len).unwrap_or_default();
4690 let mut new_text = String::with_capacity(
4691 1 + capacity_for_delimiter
4692 + existing_indent.len as usize
4693 + indent_on_newline.len as usize
4694 + indent_on_extra_newline.len as usize,
4695 );
4696 new_text.push('\n');
4697 new_text.extend(existing_indent.chars());
4698 new_text.extend(indent_on_newline.chars());
4699
4700 if let Some(delimiter) = &delimiter {
4701 new_text.push_str(delimiter);
4702 }
4703
4704 if insert_extra_newline {
4705 new_text.push('\n');
4706 new_text.extend(existing_indent.chars());
4707 new_text.extend(indent_on_extra_newline.chars());
4708 }
4709
4710 let anchor = buffer.anchor_after(end);
4711 let new_selection = selection.map(|_| anchor);
4712 (
4713 ((start..end, new_text), prevent_auto_indent),
4714 (insert_extra_newline, new_selection),
4715 )
4716 })
4717 .unzip()
4718 };
4719
4720 let mut auto_indent_edits = Vec::new();
4721 let mut edits = Vec::new();
4722 for (edit, prevent_auto_indent) in edits_with_flags {
4723 if prevent_auto_indent {
4724 edits.push(edit);
4725 } else {
4726 auto_indent_edits.push(edit);
4727 }
4728 }
4729 if !edits.is_empty() {
4730 this.edit(edits, cx);
4731 }
4732 if !auto_indent_edits.is_empty() {
4733 this.edit_with_autoindent(auto_indent_edits, cx);
4734 }
4735
4736 let buffer = this.buffer.read(cx).snapshot(cx);
4737 let new_selections = selection_info
4738 .into_iter()
4739 .map(|(extra_newline_inserted, new_selection)| {
4740 let mut cursor = new_selection.end.to_point(&buffer);
4741 if extra_newline_inserted {
4742 cursor.row -= 1;
4743 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4744 }
4745 new_selection.map(|_| cursor)
4746 })
4747 .collect();
4748
4749 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4750 this.refresh_edit_prediction(true, false, window, cx);
4751 });
4752 }
4753
4754 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4756
4757 let buffer = self.buffer.read(cx);
4758 let snapshot = buffer.snapshot(cx);
4759
4760 let mut edits = Vec::new();
4761 let mut rows = Vec::new();
4762
4763 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4764 let cursor = selection.head();
4765 let row = cursor.row;
4766
4767 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4768
4769 let newline = "\n".to_string();
4770 edits.push((start_of_line..start_of_line, newline));
4771
4772 rows.push(row + rows_inserted as u32);
4773 }
4774
4775 self.transact(window, cx, |editor, window, cx| {
4776 editor.edit(edits, cx);
4777
4778 editor.change_selections(Default::default(), window, cx, |s| {
4779 let mut index = 0;
4780 s.move_cursors_with(|map, _, _| {
4781 let row = rows[index];
4782 index += 1;
4783
4784 let point = Point::new(row, 0);
4785 let boundary = map.next_line_boundary(point).1;
4786 let clipped = map.clip_point(boundary, Bias::Left);
4787
4788 (clipped, SelectionGoal::None)
4789 });
4790 });
4791
4792 let mut indent_edits = Vec::new();
4793 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4794 for row in rows {
4795 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4796 for (row, indent) in indents {
4797 if indent.len == 0 {
4798 continue;
4799 }
4800
4801 let text = match indent.kind {
4802 IndentKind::Space => " ".repeat(indent.len as usize),
4803 IndentKind::Tab => "\t".repeat(indent.len as usize),
4804 };
4805 let point = Point::new(row.0, 0);
4806 indent_edits.push((point..point, text));
4807 }
4808 }
4809 editor.edit(indent_edits, cx);
4810 });
4811 }
4812
4813 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4815
4816 let buffer = self.buffer.read(cx);
4817 let snapshot = buffer.snapshot(cx);
4818
4819 let mut edits = Vec::new();
4820 let mut rows = Vec::new();
4821 let mut rows_inserted = 0;
4822
4823 for selection in self.selections.all_adjusted(cx) {
4824 let cursor = selection.head();
4825 let row = cursor.row;
4826
4827 let point = Point::new(row + 1, 0);
4828 let start_of_line = snapshot.clip_point(point, Bias::Left);
4829
4830 let newline = "\n".to_string();
4831 edits.push((start_of_line..start_of_line, newline));
4832
4833 rows_inserted += 1;
4834 rows.push(row + rows_inserted);
4835 }
4836
4837 self.transact(window, cx, |editor, window, cx| {
4838 editor.edit(edits, cx);
4839
4840 editor.change_selections(Default::default(), window, cx, |s| {
4841 let mut index = 0;
4842 s.move_cursors_with(|map, _, _| {
4843 let row = rows[index];
4844 index += 1;
4845
4846 let point = Point::new(row, 0);
4847 let boundary = map.next_line_boundary(point).1;
4848 let clipped = map.clip_point(boundary, Bias::Left);
4849
4850 (clipped, SelectionGoal::None)
4851 });
4852 });
4853
4854 let mut indent_edits = Vec::new();
4855 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4856 for row in rows {
4857 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4858 for (row, indent) in indents {
4859 if indent.len == 0 {
4860 continue;
4861 }
4862
4863 let text = match indent.kind {
4864 IndentKind::Space => " ".repeat(indent.len as usize),
4865 IndentKind::Tab => "\t".repeat(indent.len as usize),
4866 };
4867 let point = Point::new(row.0, 0);
4868 indent_edits.push((point..point, text));
4869 }
4870 }
4871 editor.edit(indent_edits, cx);
4872 });
4873 }
4874
4875 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4876 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4877 original_indent_columns: Vec::new(),
4878 });
4879 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4880 }
4881
4882 fn insert_with_autoindent_mode(
4883 &mut self,
4884 text: &str,
4885 autoindent_mode: Option<AutoindentMode>,
4886 window: &mut Window,
4887 cx: &mut Context<Self>,
4888 ) {
4889 if self.read_only(cx) {
4890 return;
4891 }
4892
4893 let text: Arc<str> = text.into();
4894 self.transact(window, cx, |this, window, cx| {
4895 let old_selections = this.selections.all_adjusted(cx);
4896 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4897 let anchors = {
4898 let snapshot = buffer.read(cx);
4899 old_selections
4900 .iter()
4901 .map(|s| {
4902 let anchor = snapshot.anchor_after(s.head());
4903 s.map(|_| anchor)
4904 })
4905 .collect::<Vec<_>>()
4906 };
4907 buffer.edit(
4908 old_selections
4909 .iter()
4910 .map(|s| (s.start..s.end, text.clone())),
4911 autoindent_mode,
4912 cx,
4913 );
4914 anchors
4915 });
4916
4917 this.change_selections(Default::default(), window, cx, |s| {
4918 s.select_anchors(selection_anchors);
4919 });
4920
4921 cx.notify();
4922 });
4923 }
4924
4925 fn trigger_completion_on_input(
4926 &mut self,
4927 text: &str,
4928 trigger_in_words: bool,
4929 window: &mut Window,
4930 cx: &mut Context<Self>,
4931 ) {
4932 let completions_source = self
4933 .context_menu
4934 .borrow()
4935 .as_ref()
4936 .and_then(|menu| match menu {
4937 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4938 CodeContextMenu::CodeActions(_) => None,
4939 });
4940
4941 match completions_source {
4942 Some(CompletionsMenuSource::Words { .. }) => {
4943 self.open_or_update_completions_menu(
4944 Some(CompletionsMenuSource::Words {
4945 ignore_threshold: false,
4946 }),
4947 None,
4948 window,
4949 cx,
4950 );
4951 }
4952 Some(CompletionsMenuSource::Normal)
4953 | Some(CompletionsMenuSource::SnippetChoices)
4954 | None
4955 if self.is_completion_trigger(
4956 text,
4957 trigger_in_words,
4958 completions_source.is_some(),
4959 cx,
4960 ) =>
4961 {
4962 self.show_completions(
4963 &ShowCompletions {
4964 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4965 },
4966 window,
4967 cx,
4968 )
4969 }
4970 _ => {
4971 self.hide_context_menu(window, cx);
4972 }
4973 }
4974 }
4975
4976 fn is_completion_trigger(
4977 &self,
4978 text: &str,
4979 trigger_in_words: bool,
4980 menu_is_open: bool,
4981 cx: &mut Context<Self>,
4982 ) -> bool {
4983 let position = self.selections.newest_anchor().head();
4984 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4985 return false;
4986 };
4987
4988 if let Some(completion_provider) = &self.completion_provider {
4989 completion_provider.is_completion_trigger(
4990 &buffer,
4991 position.text_anchor,
4992 text,
4993 trigger_in_words,
4994 menu_is_open,
4995 cx,
4996 )
4997 } else {
4998 false
4999 }
5000 }
5001
5002 /// If any empty selections is touching the start of its innermost containing autoclose
5003 /// region, expand it to select the brackets.
5004 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5005 let selections = self.selections.all::<usize>(cx);
5006 let buffer = self.buffer.read(cx).read(cx);
5007 let new_selections = self
5008 .selections_with_autoclose_regions(selections, &buffer)
5009 .map(|(mut selection, region)| {
5010 if !selection.is_empty() {
5011 return selection;
5012 }
5013
5014 if let Some(region) = region {
5015 let mut range = region.range.to_offset(&buffer);
5016 if selection.start == range.start && range.start >= region.pair.start.len() {
5017 range.start -= region.pair.start.len();
5018 if buffer.contains_str_at(range.start, ®ion.pair.start)
5019 && buffer.contains_str_at(range.end, ®ion.pair.end)
5020 {
5021 range.end += region.pair.end.len();
5022 selection.start = range.start;
5023 selection.end = range.end;
5024
5025 return selection;
5026 }
5027 }
5028 }
5029
5030 let always_treat_brackets_as_autoclosed = buffer
5031 .language_settings_at(selection.start, cx)
5032 .always_treat_brackets_as_autoclosed;
5033
5034 if !always_treat_brackets_as_autoclosed {
5035 return selection;
5036 }
5037
5038 if let Some(scope) = buffer.language_scope_at(selection.start) {
5039 for (pair, enabled) in scope.brackets() {
5040 if !enabled || !pair.close {
5041 continue;
5042 }
5043
5044 if buffer.contains_str_at(selection.start, &pair.end) {
5045 let pair_start_len = pair.start.len();
5046 if buffer.contains_str_at(
5047 selection.start.saturating_sub(pair_start_len),
5048 &pair.start,
5049 ) {
5050 selection.start -= pair_start_len;
5051 selection.end += pair.end.len();
5052
5053 return selection;
5054 }
5055 }
5056 }
5057 }
5058
5059 selection
5060 })
5061 .collect();
5062
5063 drop(buffer);
5064 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5065 selections.select(new_selections)
5066 });
5067 }
5068
5069 /// Iterate the given selections, and for each one, find the smallest surrounding
5070 /// autoclose region. This uses the ordering of the selections and the autoclose
5071 /// regions to avoid repeated comparisons.
5072 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5073 &'a self,
5074 selections: impl IntoIterator<Item = Selection<D>>,
5075 buffer: &'a MultiBufferSnapshot,
5076 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5077 let mut i = 0;
5078 let mut regions = self.autoclose_regions.as_slice();
5079 selections.into_iter().map(move |selection| {
5080 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5081
5082 let mut enclosing = None;
5083 while let Some(pair_state) = regions.get(i) {
5084 if pair_state.range.end.to_offset(buffer) < range.start {
5085 regions = ®ions[i + 1..];
5086 i = 0;
5087 } else if pair_state.range.start.to_offset(buffer) > range.end {
5088 break;
5089 } else {
5090 if pair_state.selection_id == selection.id {
5091 enclosing = Some(pair_state);
5092 }
5093 i += 1;
5094 }
5095 }
5096
5097 (selection, enclosing)
5098 })
5099 }
5100
5101 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5102 fn invalidate_autoclose_regions(
5103 &mut self,
5104 mut selections: &[Selection<Anchor>],
5105 buffer: &MultiBufferSnapshot,
5106 ) {
5107 self.autoclose_regions.retain(|state| {
5108 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5109 return false;
5110 }
5111
5112 let mut i = 0;
5113 while let Some(selection) = selections.get(i) {
5114 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5115 selections = &selections[1..];
5116 continue;
5117 }
5118 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5119 break;
5120 }
5121 if selection.id == state.selection_id {
5122 return true;
5123 } else {
5124 i += 1;
5125 }
5126 }
5127 false
5128 });
5129 }
5130
5131 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5132 let offset = position.to_offset(buffer);
5133 let (word_range, kind) =
5134 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5135 if offset > word_range.start && kind == Some(CharKind::Word) {
5136 Some(
5137 buffer
5138 .text_for_range(word_range.start..offset)
5139 .collect::<String>(),
5140 )
5141 } else {
5142 None
5143 }
5144 }
5145
5146 pub fn toggle_inline_values(
5147 &mut self,
5148 _: &ToggleInlineValues,
5149 _: &mut Window,
5150 cx: &mut Context<Self>,
5151 ) {
5152 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5153
5154 self.refresh_inline_values(cx);
5155 }
5156
5157 pub fn toggle_inlay_hints(
5158 &mut self,
5159 _: &ToggleInlayHints,
5160 _: &mut Window,
5161 cx: &mut Context<Self>,
5162 ) {
5163 self.refresh_inlay_hints(
5164 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5165 cx,
5166 );
5167 }
5168
5169 pub fn inlay_hints_enabled(&self) -> bool {
5170 self.inlay_hint_cache.enabled
5171 }
5172
5173 pub fn inline_values_enabled(&self) -> bool {
5174 self.inline_value_cache.enabled
5175 }
5176
5177 #[cfg(any(test, feature = "test-support"))]
5178 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5179 self.display_map
5180 .read(cx)
5181 .current_inlays()
5182 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5183 .cloned()
5184 .collect()
5185 }
5186
5187 #[cfg(any(test, feature = "test-support"))]
5188 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5189 self.display_map
5190 .read(cx)
5191 .current_inlays()
5192 .cloned()
5193 .collect()
5194 }
5195
5196 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5197 if self.semantics_provider.is_none() || !self.mode.is_full() {
5198 return;
5199 }
5200
5201 let reason_description = reason.description();
5202 let ignore_debounce = matches!(
5203 reason,
5204 InlayHintRefreshReason::SettingsChange(_)
5205 | InlayHintRefreshReason::Toggle(_)
5206 | InlayHintRefreshReason::ExcerptsRemoved(_)
5207 | InlayHintRefreshReason::ModifiersChanged(_)
5208 );
5209 let (invalidate_cache, required_languages) = match reason {
5210 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5211 match self.inlay_hint_cache.modifiers_override(enabled) {
5212 Some(enabled) => {
5213 if enabled {
5214 (InvalidationStrategy::RefreshRequested, None)
5215 } else {
5216 self.splice_inlays(
5217 &self
5218 .visible_inlay_hints(cx)
5219 .iter()
5220 .map(|inlay| inlay.id)
5221 .collect::<Vec<InlayId>>(),
5222 Vec::new(),
5223 cx,
5224 );
5225 return;
5226 }
5227 }
5228 None => return,
5229 }
5230 }
5231 InlayHintRefreshReason::Toggle(enabled) => {
5232 if self.inlay_hint_cache.toggle(enabled) {
5233 if enabled {
5234 (InvalidationStrategy::RefreshRequested, None)
5235 } else {
5236 self.splice_inlays(
5237 &self
5238 .visible_inlay_hints(cx)
5239 .iter()
5240 .map(|inlay| inlay.id)
5241 .collect::<Vec<InlayId>>(),
5242 Vec::new(),
5243 cx,
5244 );
5245 return;
5246 }
5247 } else {
5248 return;
5249 }
5250 }
5251 InlayHintRefreshReason::SettingsChange(new_settings) => {
5252 match self.inlay_hint_cache.update_settings(
5253 &self.buffer,
5254 new_settings,
5255 self.visible_inlay_hints(cx),
5256 cx,
5257 ) {
5258 ControlFlow::Break(Some(InlaySplice {
5259 to_remove,
5260 to_insert,
5261 })) => {
5262 self.splice_inlays(&to_remove, to_insert, cx);
5263 return;
5264 }
5265 ControlFlow::Break(None) => return,
5266 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5267 }
5268 }
5269 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5270 if let Some(InlaySplice {
5271 to_remove,
5272 to_insert,
5273 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5274 {
5275 self.splice_inlays(&to_remove, to_insert, cx);
5276 }
5277 self.display_map.update(cx, |display_map, _| {
5278 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5279 });
5280 return;
5281 }
5282 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5283 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5284 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5285 }
5286 InlayHintRefreshReason::RefreshRequested => {
5287 (InvalidationStrategy::RefreshRequested, None)
5288 }
5289 };
5290
5291 if let Some(InlaySplice {
5292 to_remove,
5293 to_insert,
5294 }) = self.inlay_hint_cache.spawn_hint_refresh(
5295 reason_description,
5296 self.visible_excerpts(required_languages.as_ref(), cx),
5297 invalidate_cache,
5298 ignore_debounce,
5299 cx,
5300 ) {
5301 self.splice_inlays(&to_remove, to_insert, cx);
5302 }
5303 }
5304
5305 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5306 self.display_map
5307 .read(cx)
5308 .current_inlays()
5309 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5310 .cloned()
5311 .collect()
5312 }
5313
5314 pub fn visible_excerpts(
5315 &self,
5316 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5317 cx: &mut Context<Editor>,
5318 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5319 let Some(project) = self.project() else {
5320 return HashMap::default();
5321 };
5322 let project = project.read(cx);
5323 let multi_buffer = self.buffer().read(cx);
5324 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5325 let multi_buffer_visible_start = self
5326 .scroll_manager
5327 .anchor()
5328 .anchor
5329 .to_point(&multi_buffer_snapshot);
5330 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5331 multi_buffer_visible_start
5332 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5333 Bias::Left,
5334 );
5335 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5336 multi_buffer_snapshot
5337 .range_to_buffer_ranges(multi_buffer_visible_range)
5338 .into_iter()
5339 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5340 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5341 let buffer_file = project::File::from_dyn(buffer.file())?;
5342 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5343 let worktree_entry = buffer_worktree
5344 .read(cx)
5345 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5346 if worktree_entry.is_ignored {
5347 return None;
5348 }
5349
5350 let language = buffer.language()?;
5351 if let Some(restrict_to_languages) = restrict_to_languages
5352 && !restrict_to_languages.contains(language)
5353 {
5354 return None;
5355 }
5356 Some((
5357 excerpt_id,
5358 (
5359 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5360 buffer.version().clone(),
5361 excerpt_visible_range,
5362 ),
5363 ))
5364 })
5365 .collect()
5366 }
5367
5368 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5369 TextLayoutDetails {
5370 text_system: window.text_system().clone(),
5371 editor_style: self.style.clone().unwrap(),
5372 rem_size: window.rem_size(),
5373 scroll_anchor: self.scroll_manager.anchor(),
5374 visible_rows: self.visible_line_count(),
5375 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5376 }
5377 }
5378
5379 pub fn splice_inlays(
5380 &self,
5381 to_remove: &[InlayId],
5382 to_insert: Vec<Inlay>,
5383 cx: &mut Context<Self>,
5384 ) {
5385 self.display_map.update(cx, |display_map, cx| {
5386 display_map.splice_inlays(to_remove, to_insert, cx)
5387 });
5388 cx.notify();
5389 }
5390
5391 fn trigger_on_type_formatting(
5392 &self,
5393 input: String,
5394 window: &mut Window,
5395 cx: &mut Context<Self>,
5396 ) -> Option<Task<Result<()>>> {
5397 if input.len() != 1 {
5398 return None;
5399 }
5400
5401 let project = self.project()?;
5402 let position = self.selections.newest_anchor().head();
5403 let (buffer, buffer_position) = self
5404 .buffer
5405 .read(cx)
5406 .text_anchor_for_position(position, cx)?;
5407
5408 let settings = language_settings::language_settings(
5409 buffer
5410 .read(cx)
5411 .language_at(buffer_position)
5412 .map(|l| l.name()),
5413 buffer.read(cx).file(),
5414 cx,
5415 );
5416 if !settings.use_on_type_format {
5417 return None;
5418 }
5419
5420 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5421 // hence we do LSP request & edit on host side only — add formats to host's history.
5422 let push_to_lsp_host_history = true;
5423 // If this is not the host, append its history with new edits.
5424 let push_to_client_history = project.read(cx).is_via_collab();
5425
5426 let on_type_formatting = project.update(cx, |project, cx| {
5427 project.on_type_format(
5428 buffer.clone(),
5429 buffer_position,
5430 input,
5431 push_to_lsp_host_history,
5432 cx,
5433 )
5434 });
5435 Some(cx.spawn_in(window, async move |editor, cx| {
5436 if let Some(transaction) = on_type_formatting.await? {
5437 if push_to_client_history {
5438 buffer
5439 .update(cx, |buffer, _| {
5440 buffer.push_transaction(transaction, Instant::now());
5441 buffer.finalize_last_transaction();
5442 })
5443 .ok();
5444 }
5445 editor.update(cx, |editor, cx| {
5446 editor.refresh_document_highlights(cx);
5447 })?;
5448 }
5449 Ok(())
5450 }))
5451 }
5452
5453 pub fn show_word_completions(
5454 &mut self,
5455 _: &ShowWordCompletions,
5456 window: &mut Window,
5457 cx: &mut Context<Self>,
5458 ) {
5459 self.open_or_update_completions_menu(
5460 Some(CompletionsMenuSource::Words {
5461 ignore_threshold: true,
5462 }),
5463 None,
5464 window,
5465 cx,
5466 );
5467 }
5468
5469 pub fn show_completions(
5470 &mut self,
5471 options: &ShowCompletions,
5472 window: &mut Window,
5473 cx: &mut Context<Self>,
5474 ) {
5475 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5476 }
5477
5478 fn open_or_update_completions_menu(
5479 &mut self,
5480 requested_source: Option<CompletionsMenuSource>,
5481 trigger: Option<&str>,
5482 window: &mut Window,
5483 cx: &mut Context<Self>,
5484 ) {
5485 if self.pending_rename.is_some() {
5486 return;
5487 }
5488
5489 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5490
5491 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5492 // inserted and selected. To handle that case, the start of the selection is used so that
5493 // the menu starts with all choices.
5494 let position = self
5495 .selections
5496 .newest_anchor()
5497 .start
5498 .bias_right(&multibuffer_snapshot);
5499 if position.diff_base_anchor.is_some() {
5500 return;
5501 }
5502 let buffer_position = multibuffer_snapshot.anchor_before(position);
5503 let Some(buffer) = buffer_position
5504 .buffer_id
5505 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5506 else {
5507 return;
5508 };
5509 let buffer_snapshot = buffer.read(cx).snapshot();
5510
5511 let query: Option<Arc<String>> =
5512 Self::completion_query(&multibuffer_snapshot, buffer_position)
5513 .map(|query| query.into());
5514
5515 drop(multibuffer_snapshot);
5516
5517 // Hide the current completions menu when query is empty. Without this, cached
5518 // completions from before the trigger char may be reused (#32774).
5519 if query.is_none() {
5520 let menu_is_open = matches!(
5521 self.context_menu.borrow().as_ref(),
5522 Some(CodeContextMenu::Completions(_))
5523 );
5524 if menu_is_open {
5525 self.hide_context_menu(window, cx);
5526 }
5527 }
5528
5529 let mut ignore_word_threshold = false;
5530 let provider = match requested_source {
5531 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5532 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5533 ignore_word_threshold = ignore_threshold;
5534 None
5535 }
5536 Some(CompletionsMenuSource::SnippetChoices) => {
5537 log::error!("bug: SnippetChoices requested_source is not handled");
5538 None
5539 }
5540 };
5541
5542 let sort_completions = provider
5543 .as_ref()
5544 .is_some_and(|provider| provider.sort_completions());
5545
5546 let filter_completions = provider
5547 .as_ref()
5548 .is_none_or(|provider| provider.filter_completions());
5549
5550 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5551 if filter_completions {
5552 menu.filter(query.clone(), provider.clone(), window, cx);
5553 }
5554 // When `is_incomplete` is false, no need to re-query completions when the current query
5555 // is a suffix of the initial query.
5556 if !menu.is_incomplete {
5557 // If the new query is a suffix of the old query (typing more characters) and
5558 // the previous result was complete, the existing completions can be filtered.
5559 //
5560 // Note that this is always true for snippet completions.
5561 let query_matches = match (&menu.initial_query, &query) {
5562 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5563 (None, _) => true,
5564 _ => false,
5565 };
5566 if query_matches {
5567 let position_matches = if menu.initial_position == position {
5568 true
5569 } else {
5570 let snapshot = self.buffer.read(cx).read(cx);
5571 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5572 };
5573 if position_matches {
5574 return;
5575 }
5576 }
5577 }
5578 };
5579
5580 let trigger_kind = match trigger {
5581 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5582 CompletionTriggerKind::TRIGGER_CHARACTER
5583 }
5584 _ => CompletionTriggerKind::INVOKED,
5585 };
5586 let completion_context = CompletionContext {
5587 trigger_character: trigger.and_then(|trigger| {
5588 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5589 Some(String::from(trigger))
5590 } else {
5591 None
5592 }
5593 }),
5594 trigger_kind,
5595 };
5596
5597 let Anchor {
5598 excerpt_id: buffer_excerpt_id,
5599 text_anchor: buffer_position,
5600 ..
5601 } = buffer_position;
5602
5603 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5604 buffer_snapshot.surrounding_word(buffer_position, None)
5605 {
5606 let word_to_exclude = buffer_snapshot
5607 .text_for_range(word_range.clone())
5608 .collect::<String>();
5609 (
5610 buffer_snapshot.anchor_before(word_range.start)
5611 ..buffer_snapshot.anchor_after(buffer_position),
5612 Some(word_to_exclude),
5613 )
5614 } else {
5615 (buffer_position..buffer_position, None)
5616 };
5617
5618 let language = buffer_snapshot
5619 .language_at(buffer_position)
5620 .map(|language| language.name());
5621
5622 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5623 .completions
5624 .clone();
5625
5626 let show_completion_documentation = buffer_snapshot
5627 .settings_at(buffer_position, cx)
5628 .show_completion_documentation;
5629
5630 // The document can be large, so stay in reasonable bounds when searching for words,
5631 // otherwise completion pop-up might be slow to appear.
5632 const WORD_LOOKUP_ROWS: u32 = 5_000;
5633 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5634 let min_word_search = buffer_snapshot.clip_point(
5635 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5636 Bias::Left,
5637 );
5638 let max_word_search = buffer_snapshot.clip_point(
5639 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5640 Bias::Right,
5641 );
5642 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5643 ..buffer_snapshot.point_to_offset(max_word_search);
5644
5645 let skip_digits = query
5646 .as_ref()
5647 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5648
5649 let omit_word_completions = !self.word_completions_enabled
5650 || (!ignore_word_threshold
5651 && match &query {
5652 Some(query) => query.chars().count() < completion_settings.words_min_length,
5653 None => completion_settings.words_min_length != 0,
5654 });
5655
5656 let (mut words, provider_responses) = match &provider {
5657 Some(provider) => {
5658 let provider_responses = provider.completions(
5659 buffer_excerpt_id,
5660 &buffer,
5661 buffer_position,
5662 completion_context,
5663 window,
5664 cx,
5665 );
5666
5667 let words = match (omit_word_completions, completion_settings.words) {
5668 (true, _) | (_, WordsCompletionMode::Disabled) => {
5669 Task::ready(BTreeMap::default())
5670 }
5671 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5672 .background_spawn(async move {
5673 buffer_snapshot.words_in_range(WordsQuery {
5674 fuzzy_contents: None,
5675 range: word_search_range,
5676 skip_digits,
5677 })
5678 }),
5679 };
5680
5681 (words, provider_responses)
5682 }
5683 None => {
5684 let words = if omit_word_completions {
5685 Task::ready(BTreeMap::default())
5686 } else {
5687 cx.background_spawn(async move {
5688 buffer_snapshot.words_in_range(WordsQuery {
5689 fuzzy_contents: None,
5690 range: word_search_range,
5691 skip_digits,
5692 })
5693 })
5694 };
5695 (words, Task::ready(Ok(Vec::new())))
5696 }
5697 };
5698
5699 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5700
5701 let id = post_inc(&mut self.next_completion_id);
5702 let task = cx.spawn_in(window, async move |editor, cx| {
5703 let Ok(()) = editor.update(cx, |this, _| {
5704 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5705 }) else {
5706 return;
5707 };
5708
5709 // TODO: Ideally completions from different sources would be selectively re-queried, so
5710 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5711 let mut completions = Vec::new();
5712 let mut is_incomplete = false;
5713 let mut display_options: Option<CompletionDisplayOptions> = None;
5714 if let Some(provider_responses) = provider_responses.await.log_err()
5715 && !provider_responses.is_empty()
5716 {
5717 for response in provider_responses {
5718 completions.extend(response.completions);
5719 is_incomplete = is_incomplete || response.is_incomplete;
5720 match display_options.as_mut() {
5721 None => {
5722 display_options = Some(response.display_options);
5723 }
5724 Some(options) => options.merge(&response.display_options),
5725 }
5726 }
5727 if completion_settings.words == WordsCompletionMode::Fallback {
5728 words = Task::ready(BTreeMap::default());
5729 }
5730 }
5731 let display_options = display_options.unwrap_or_default();
5732
5733 let mut words = words.await;
5734 if let Some(word_to_exclude) = &word_to_exclude {
5735 words.remove(word_to_exclude);
5736 }
5737 for lsp_completion in &completions {
5738 words.remove(&lsp_completion.new_text);
5739 }
5740 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5741 replace_range: word_replace_range.clone(),
5742 new_text: word.clone(),
5743 label: CodeLabel::plain(word, None),
5744 icon_path: None,
5745 documentation: None,
5746 source: CompletionSource::BufferWord {
5747 word_range,
5748 resolved: false,
5749 },
5750 insert_text_mode: Some(InsertTextMode::AS_IS),
5751 confirm: None,
5752 }));
5753
5754 let menu = if completions.is_empty() {
5755 None
5756 } else {
5757 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5758 let languages = editor
5759 .workspace
5760 .as_ref()
5761 .and_then(|(workspace, _)| workspace.upgrade())
5762 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5763 let menu = CompletionsMenu::new(
5764 id,
5765 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5766 sort_completions,
5767 show_completion_documentation,
5768 position,
5769 query.clone(),
5770 is_incomplete,
5771 buffer.clone(),
5772 completions.into(),
5773 display_options,
5774 snippet_sort_order,
5775 languages,
5776 language,
5777 cx,
5778 );
5779
5780 let query = if filter_completions { query } else { None };
5781 let matches_task = if let Some(query) = query {
5782 menu.do_async_filtering(query, cx)
5783 } else {
5784 Task::ready(menu.unfiltered_matches())
5785 };
5786 (menu, matches_task)
5787 }) else {
5788 return;
5789 };
5790
5791 let matches = matches_task.await;
5792
5793 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5794 // Newer menu already set, so exit.
5795 if let Some(CodeContextMenu::Completions(prev_menu)) =
5796 editor.context_menu.borrow().as_ref()
5797 && prev_menu.id > id
5798 {
5799 return;
5800 };
5801
5802 // Only valid to take prev_menu because it the new menu is immediately set
5803 // below, or the menu is hidden.
5804 if let Some(CodeContextMenu::Completions(prev_menu)) =
5805 editor.context_menu.borrow_mut().take()
5806 {
5807 let position_matches =
5808 if prev_menu.initial_position == menu.initial_position {
5809 true
5810 } else {
5811 let snapshot = editor.buffer.read(cx).read(cx);
5812 prev_menu.initial_position.to_offset(&snapshot)
5813 == menu.initial_position.to_offset(&snapshot)
5814 };
5815 if position_matches {
5816 // Preserve markdown cache before `set_filter_results` because it will
5817 // try to populate the documentation cache.
5818 menu.preserve_markdown_cache(prev_menu);
5819 }
5820 };
5821
5822 menu.set_filter_results(matches, provider, window, cx);
5823 }) else {
5824 return;
5825 };
5826
5827 menu.visible().then_some(menu)
5828 };
5829
5830 editor
5831 .update_in(cx, |editor, window, cx| {
5832 if editor.focus_handle.is_focused(window)
5833 && let Some(menu) = menu
5834 {
5835 *editor.context_menu.borrow_mut() =
5836 Some(CodeContextMenu::Completions(menu));
5837
5838 crate::hover_popover::hide_hover(editor, cx);
5839 if editor.show_edit_predictions_in_menu() {
5840 editor.update_visible_edit_prediction(window, cx);
5841 } else {
5842 editor.discard_edit_prediction(false, cx);
5843 }
5844
5845 cx.notify();
5846 return;
5847 }
5848
5849 if editor.completion_tasks.len() <= 1 {
5850 // If there are no more completion tasks and the last menu was empty, we should hide it.
5851 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5852 // If it was already hidden and we don't show edit predictions in the menu,
5853 // we should also show the edit prediction when available.
5854 if was_hidden && editor.show_edit_predictions_in_menu() {
5855 editor.update_visible_edit_prediction(window, cx);
5856 }
5857 }
5858 })
5859 .ok();
5860 });
5861
5862 self.completion_tasks.push((id, task));
5863 }
5864
5865 #[cfg(feature = "test-support")]
5866 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5867 let menu = self.context_menu.borrow();
5868 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5869 let completions = menu.completions.borrow();
5870 Some(completions.to_vec())
5871 } else {
5872 None
5873 }
5874 }
5875
5876 pub fn with_completions_menu_matching_id<R>(
5877 &self,
5878 id: CompletionId,
5879 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5880 ) -> R {
5881 let mut context_menu = self.context_menu.borrow_mut();
5882 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5883 return f(None);
5884 };
5885 if completions_menu.id != id {
5886 return f(None);
5887 }
5888 f(Some(completions_menu))
5889 }
5890
5891 pub fn confirm_completion(
5892 &mut self,
5893 action: &ConfirmCompletion,
5894 window: &mut Window,
5895 cx: &mut Context<Self>,
5896 ) -> Option<Task<Result<()>>> {
5897 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5898 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5899 }
5900
5901 pub fn confirm_completion_insert(
5902 &mut self,
5903 _: &ConfirmCompletionInsert,
5904 window: &mut Window,
5905 cx: &mut Context<Self>,
5906 ) -> Option<Task<Result<()>>> {
5907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5908 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5909 }
5910
5911 pub fn confirm_completion_replace(
5912 &mut self,
5913 _: &ConfirmCompletionReplace,
5914 window: &mut Window,
5915 cx: &mut Context<Self>,
5916 ) -> Option<Task<Result<()>>> {
5917 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5918 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5919 }
5920
5921 pub fn compose_completion(
5922 &mut self,
5923 action: &ComposeCompletion,
5924 window: &mut Window,
5925 cx: &mut Context<Self>,
5926 ) -> Option<Task<Result<()>>> {
5927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5928 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5929 }
5930
5931 fn do_completion(
5932 &mut self,
5933 item_ix: Option<usize>,
5934 intent: CompletionIntent,
5935 window: &mut Window,
5936 cx: &mut Context<Editor>,
5937 ) -> Option<Task<Result<()>>> {
5938 use language::ToOffset as _;
5939
5940 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5941 else {
5942 return None;
5943 };
5944
5945 let candidate_id = {
5946 let entries = completions_menu.entries.borrow();
5947 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5948 if self.show_edit_predictions_in_menu() {
5949 self.discard_edit_prediction(true, cx);
5950 }
5951 mat.candidate_id
5952 };
5953
5954 let completion = completions_menu
5955 .completions
5956 .borrow()
5957 .get(candidate_id)?
5958 .clone();
5959 cx.stop_propagation();
5960
5961 let buffer_handle = completions_menu.buffer.clone();
5962
5963 let CompletionEdit {
5964 new_text,
5965 snippet,
5966 replace_range,
5967 } = process_completion_for_edit(
5968 &completion,
5969 intent,
5970 &buffer_handle,
5971 &completions_menu.initial_position.text_anchor,
5972 cx,
5973 );
5974
5975 let buffer = buffer_handle.read(cx);
5976 let snapshot = self.buffer.read(cx).snapshot(cx);
5977 let newest_anchor = self.selections.newest_anchor();
5978 let replace_range_multibuffer = {
5979 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5980 let multibuffer_anchor = snapshot
5981 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5982 .unwrap()
5983 ..snapshot
5984 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5985 .unwrap();
5986 multibuffer_anchor.start.to_offset(&snapshot)
5987 ..multibuffer_anchor.end.to_offset(&snapshot)
5988 };
5989 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5990 return None;
5991 }
5992
5993 let old_text = buffer
5994 .text_for_range(replace_range.clone())
5995 .collect::<String>();
5996 let lookbehind = newest_anchor
5997 .start
5998 .text_anchor
5999 .to_offset(buffer)
6000 .saturating_sub(replace_range.start);
6001 let lookahead = replace_range
6002 .end
6003 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6004 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6005 let suffix = &old_text[lookbehind.min(old_text.len())..];
6006
6007 let selections = self.selections.all::<usize>(cx);
6008 let mut ranges = Vec::new();
6009 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6010
6011 for selection in &selections {
6012 let range = if selection.id == newest_anchor.id {
6013 replace_range_multibuffer.clone()
6014 } else {
6015 let mut range = selection.range();
6016
6017 // if prefix is present, don't duplicate it
6018 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6019 range.start = range.start.saturating_sub(lookbehind);
6020
6021 // if suffix is also present, mimic the newest cursor and replace it
6022 if selection.id != newest_anchor.id
6023 && snapshot.contains_str_at(range.end, suffix)
6024 {
6025 range.end += lookahead;
6026 }
6027 }
6028 range
6029 };
6030
6031 ranges.push(range.clone());
6032
6033 if !self.linked_edit_ranges.is_empty() {
6034 let start_anchor = snapshot.anchor_before(range.start);
6035 let end_anchor = snapshot.anchor_after(range.end);
6036 if let Some(ranges) = self
6037 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6038 {
6039 for (buffer, edits) in ranges {
6040 linked_edits
6041 .entry(buffer.clone())
6042 .or_default()
6043 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6044 }
6045 }
6046 }
6047 }
6048
6049 let common_prefix_len = old_text
6050 .chars()
6051 .zip(new_text.chars())
6052 .take_while(|(a, b)| a == b)
6053 .map(|(a, _)| a.len_utf8())
6054 .sum::<usize>();
6055
6056 cx.emit(EditorEvent::InputHandled {
6057 utf16_range_to_replace: None,
6058 text: new_text[common_prefix_len..].into(),
6059 });
6060
6061 self.transact(window, cx, |editor, window, cx| {
6062 if let Some(mut snippet) = snippet {
6063 snippet.text = new_text.to_string();
6064 editor
6065 .insert_snippet(&ranges, snippet, window, cx)
6066 .log_err();
6067 } else {
6068 editor.buffer.update(cx, |multi_buffer, cx| {
6069 let auto_indent = match completion.insert_text_mode {
6070 Some(InsertTextMode::AS_IS) => None,
6071 _ => editor.autoindent_mode.clone(),
6072 };
6073 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6074 multi_buffer.edit(edits, auto_indent, cx);
6075 });
6076 }
6077 for (buffer, edits) in linked_edits {
6078 buffer.update(cx, |buffer, cx| {
6079 let snapshot = buffer.snapshot();
6080 let edits = edits
6081 .into_iter()
6082 .map(|(range, text)| {
6083 use text::ToPoint as TP;
6084 let end_point = TP::to_point(&range.end, &snapshot);
6085 let start_point = TP::to_point(&range.start, &snapshot);
6086 (start_point..end_point, text)
6087 })
6088 .sorted_by_key(|(range, _)| range.start);
6089 buffer.edit(edits, None, cx);
6090 })
6091 }
6092
6093 editor.refresh_edit_prediction(true, false, window, cx);
6094 });
6095 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6096
6097 let show_new_completions_on_confirm = completion
6098 .confirm
6099 .as_ref()
6100 .is_some_and(|confirm| confirm(intent, window, cx));
6101 if show_new_completions_on_confirm {
6102 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6103 }
6104
6105 let provider = self.completion_provider.as_ref()?;
6106 drop(completion);
6107 let apply_edits = provider.apply_additional_edits_for_completion(
6108 buffer_handle,
6109 completions_menu.completions.clone(),
6110 candidate_id,
6111 true,
6112 cx,
6113 );
6114
6115 let editor_settings = EditorSettings::get_global(cx);
6116 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6117 // After the code completion is finished, users often want to know what signatures are needed.
6118 // so we should automatically call signature_help
6119 self.show_signature_help(&ShowSignatureHelp, window, cx);
6120 }
6121
6122 Some(cx.foreground_executor().spawn(async move {
6123 apply_edits.await?;
6124 Ok(())
6125 }))
6126 }
6127
6128 pub fn toggle_code_actions(
6129 &mut self,
6130 action: &ToggleCodeActions,
6131 window: &mut Window,
6132 cx: &mut Context<Self>,
6133 ) {
6134 let quick_launch = action.quick_launch;
6135 let mut context_menu = self.context_menu.borrow_mut();
6136 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6137 if code_actions.deployed_from == action.deployed_from {
6138 // Toggle if we're selecting the same one
6139 *context_menu = None;
6140 cx.notify();
6141 return;
6142 } else {
6143 // Otherwise, clear it and start a new one
6144 *context_menu = None;
6145 cx.notify();
6146 }
6147 }
6148 drop(context_menu);
6149 let snapshot = self.snapshot(window, cx);
6150 let deployed_from = action.deployed_from.clone();
6151 let action = action.clone();
6152 self.completion_tasks.clear();
6153 self.discard_edit_prediction(false, cx);
6154
6155 let multibuffer_point = match &action.deployed_from {
6156 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6157 DisplayPoint::new(*row, 0).to_point(&snapshot)
6158 }
6159 _ => self.selections.newest::<Point>(cx).head(),
6160 };
6161 let Some((buffer, buffer_row)) = snapshot
6162 .buffer_snapshot
6163 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6164 .and_then(|(buffer_snapshot, range)| {
6165 self.buffer()
6166 .read(cx)
6167 .buffer(buffer_snapshot.remote_id())
6168 .map(|buffer| (buffer, range.start.row))
6169 })
6170 else {
6171 return;
6172 };
6173 let buffer_id = buffer.read(cx).remote_id();
6174 let tasks = self
6175 .tasks
6176 .get(&(buffer_id, buffer_row))
6177 .map(|t| Arc::new(t.to_owned()));
6178
6179 if !self.focus_handle.is_focused(window) {
6180 return;
6181 }
6182 let project = self.project.clone();
6183
6184 let code_actions_task = match deployed_from {
6185 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6186 _ => self.code_actions(buffer_row, window, cx),
6187 };
6188
6189 let runnable_task = match deployed_from {
6190 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6191 _ => {
6192 let mut task_context_task = Task::ready(None);
6193 if let Some(tasks) = &tasks
6194 && let Some(project) = project
6195 {
6196 task_context_task =
6197 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6198 }
6199
6200 cx.spawn_in(window, {
6201 let buffer = buffer.clone();
6202 async move |editor, cx| {
6203 let task_context = task_context_task.await;
6204
6205 let resolved_tasks =
6206 tasks
6207 .zip(task_context.clone())
6208 .map(|(tasks, task_context)| ResolvedTasks {
6209 templates: tasks.resolve(&task_context).collect(),
6210 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6211 multibuffer_point.row,
6212 tasks.column,
6213 )),
6214 });
6215 let debug_scenarios = editor
6216 .update(cx, |editor, cx| {
6217 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6218 })?
6219 .await;
6220 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6221 }
6222 })
6223 }
6224 };
6225
6226 cx.spawn_in(window, async move |editor, cx| {
6227 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6228 let code_actions = code_actions_task.await;
6229 let spawn_straight_away = quick_launch
6230 && resolved_tasks
6231 .as_ref()
6232 .is_some_and(|tasks| tasks.templates.len() == 1)
6233 && code_actions
6234 .as_ref()
6235 .is_none_or(|actions| actions.is_empty())
6236 && debug_scenarios.is_empty();
6237
6238 editor.update_in(cx, |editor, window, cx| {
6239 crate::hover_popover::hide_hover(editor, cx);
6240 let actions = CodeActionContents::new(
6241 resolved_tasks,
6242 code_actions,
6243 debug_scenarios,
6244 task_context.unwrap_or_default(),
6245 );
6246
6247 // Don't show the menu if there are no actions available
6248 if actions.is_empty() {
6249 cx.notify();
6250 return Task::ready(Ok(()));
6251 }
6252
6253 *editor.context_menu.borrow_mut() =
6254 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6255 buffer,
6256 actions,
6257 selected_item: Default::default(),
6258 scroll_handle: UniformListScrollHandle::default(),
6259 deployed_from,
6260 }));
6261 cx.notify();
6262 if spawn_straight_away
6263 && let Some(task) = editor.confirm_code_action(
6264 &ConfirmCodeAction { item_ix: Some(0) },
6265 window,
6266 cx,
6267 )
6268 {
6269 return task;
6270 }
6271
6272 Task::ready(Ok(()))
6273 })
6274 })
6275 .detach_and_log_err(cx);
6276 }
6277
6278 fn debug_scenarios(
6279 &mut self,
6280 resolved_tasks: &Option<ResolvedTasks>,
6281 buffer: &Entity<Buffer>,
6282 cx: &mut App,
6283 ) -> Task<Vec<task::DebugScenario>> {
6284 maybe!({
6285 let project = self.project()?;
6286 let dap_store = project.read(cx).dap_store();
6287 let mut scenarios = vec![];
6288 let resolved_tasks = resolved_tasks.as_ref()?;
6289 let buffer = buffer.read(cx);
6290 let language = buffer.language()?;
6291 let file = buffer.file();
6292 let debug_adapter = language_settings(language.name().into(), file, cx)
6293 .debuggers
6294 .first()
6295 .map(SharedString::from)
6296 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6297
6298 dap_store.update(cx, |dap_store, cx| {
6299 for (_, task) in &resolved_tasks.templates {
6300 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6301 task.original_task().clone(),
6302 debug_adapter.clone().into(),
6303 task.display_label().to_owned().into(),
6304 cx,
6305 );
6306 scenarios.push(maybe_scenario);
6307 }
6308 });
6309 Some(cx.background_spawn(async move {
6310 futures::future::join_all(scenarios)
6311 .await
6312 .into_iter()
6313 .flatten()
6314 .collect::<Vec<_>>()
6315 }))
6316 })
6317 .unwrap_or_else(|| Task::ready(vec![]))
6318 }
6319
6320 fn code_actions(
6321 &mut self,
6322 buffer_row: u32,
6323 window: &mut Window,
6324 cx: &mut Context<Self>,
6325 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6326 let mut task = self.code_actions_task.take();
6327 cx.spawn_in(window, async move |editor, cx| {
6328 while let Some(prev_task) = task {
6329 prev_task.await.log_err();
6330 task = editor
6331 .update(cx, |this, _| this.code_actions_task.take())
6332 .ok()?;
6333 }
6334
6335 editor
6336 .update(cx, |editor, cx| {
6337 editor
6338 .available_code_actions
6339 .clone()
6340 .and_then(|(location, code_actions)| {
6341 let snapshot = location.buffer.read(cx).snapshot();
6342 let point_range = location.range.to_point(&snapshot);
6343 let point_range = point_range.start.row..=point_range.end.row;
6344 if point_range.contains(&buffer_row) {
6345 Some(code_actions)
6346 } else {
6347 None
6348 }
6349 })
6350 })
6351 .ok()
6352 .flatten()
6353 })
6354 }
6355
6356 pub fn confirm_code_action(
6357 &mut self,
6358 action: &ConfirmCodeAction,
6359 window: &mut Window,
6360 cx: &mut Context<Self>,
6361 ) -> Option<Task<Result<()>>> {
6362 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6363
6364 let actions_menu =
6365 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6366 menu
6367 } else {
6368 return None;
6369 };
6370
6371 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6372 let action = actions_menu.actions.get(action_ix)?;
6373 let title = action.label();
6374 let buffer = actions_menu.buffer;
6375 let workspace = self.workspace()?;
6376
6377 match action {
6378 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6379 workspace.update(cx, |workspace, cx| {
6380 workspace.schedule_resolved_task(
6381 task_source_kind,
6382 resolved_task,
6383 false,
6384 window,
6385 cx,
6386 );
6387
6388 Some(Task::ready(Ok(())))
6389 })
6390 }
6391 CodeActionsItem::CodeAction {
6392 excerpt_id,
6393 action,
6394 provider,
6395 } => {
6396 let apply_code_action =
6397 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6398 let workspace = workspace.downgrade();
6399 Some(cx.spawn_in(window, async move |editor, cx| {
6400 let project_transaction = apply_code_action.await?;
6401 Self::open_project_transaction(
6402 &editor,
6403 workspace,
6404 project_transaction,
6405 title,
6406 cx,
6407 )
6408 .await
6409 }))
6410 }
6411 CodeActionsItem::DebugScenario(scenario) => {
6412 let context = actions_menu.actions.context;
6413
6414 workspace.update(cx, |workspace, cx| {
6415 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6416 workspace.start_debug_session(
6417 scenario,
6418 context,
6419 Some(buffer),
6420 None,
6421 window,
6422 cx,
6423 );
6424 });
6425 Some(Task::ready(Ok(())))
6426 }
6427 }
6428 }
6429
6430 pub async fn open_project_transaction(
6431 editor: &WeakEntity<Editor>,
6432 workspace: WeakEntity<Workspace>,
6433 transaction: ProjectTransaction,
6434 title: String,
6435 cx: &mut AsyncWindowContext,
6436 ) -> Result<()> {
6437 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6438 cx.update(|_, cx| {
6439 entries.sort_unstable_by_key(|(buffer, _)| {
6440 buffer.read(cx).file().map(|f| f.path().clone())
6441 });
6442 })?;
6443 if entries.is_empty() {
6444 return Ok(());
6445 }
6446
6447 // If the project transaction's edits are all contained within this editor, then
6448 // avoid opening a new editor to display them.
6449
6450 if let [(buffer, transaction)] = &*entries {
6451 let excerpt = editor.update(cx, |editor, cx| {
6452 editor
6453 .buffer()
6454 .read(cx)
6455 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6456 })?;
6457 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6458 && excerpted_buffer == *buffer
6459 {
6460 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6461 let excerpt_range = excerpt_range.to_offset(buffer);
6462 buffer
6463 .edited_ranges_for_transaction::<usize>(transaction)
6464 .all(|range| {
6465 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6466 })
6467 })?;
6468
6469 if all_edits_within_excerpt {
6470 return Ok(());
6471 }
6472 }
6473 }
6474
6475 let mut ranges_to_highlight = Vec::new();
6476 let excerpt_buffer = cx.new(|cx| {
6477 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6478 for (buffer_handle, transaction) in &entries {
6479 let edited_ranges = buffer_handle
6480 .read(cx)
6481 .edited_ranges_for_transaction::<Point>(transaction)
6482 .collect::<Vec<_>>();
6483 let (ranges, _) = multibuffer.set_excerpts_for_path(
6484 PathKey::for_buffer(buffer_handle, cx),
6485 buffer_handle.clone(),
6486 edited_ranges,
6487 multibuffer_context_lines(cx),
6488 cx,
6489 );
6490
6491 ranges_to_highlight.extend(ranges);
6492 }
6493 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6494 multibuffer
6495 })?;
6496
6497 workspace.update_in(cx, |workspace, window, cx| {
6498 let project = workspace.project().clone();
6499 let editor =
6500 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6501 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6502 editor.update(cx, |editor, cx| {
6503 editor.highlight_background::<Self>(
6504 &ranges_to_highlight,
6505 |theme| theme.colors().editor_highlighted_line_background,
6506 cx,
6507 );
6508 });
6509 })?;
6510
6511 Ok(())
6512 }
6513
6514 pub fn clear_code_action_providers(&mut self) {
6515 self.code_action_providers.clear();
6516 self.available_code_actions.take();
6517 }
6518
6519 pub fn add_code_action_provider(
6520 &mut self,
6521 provider: Rc<dyn CodeActionProvider>,
6522 window: &mut Window,
6523 cx: &mut Context<Self>,
6524 ) {
6525 if self
6526 .code_action_providers
6527 .iter()
6528 .any(|existing_provider| existing_provider.id() == provider.id())
6529 {
6530 return;
6531 }
6532
6533 self.code_action_providers.push(provider);
6534 self.refresh_code_actions(window, cx);
6535 }
6536
6537 pub fn remove_code_action_provider(
6538 &mut self,
6539 id: Arc<str>,
6540 window: &mut Window,
6541 cx: &mut Context<Self>,
6542 ) {
6543 self.code_action_providers
6544 .retain(|provider| provider.id() != id);
6545 self.refresh_code_actions(window, cx);
6546 }
6547
6548 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6549 !self.code_action_providers.is_empty()
6550 && EditorSettings::get_global(cx).toolbar.code_actions
6551 }
6552
6553 pub fn has_available_code_actions(&self) -> bool {
6554 self.available_code_actions
6555 .as_ref()
6556 .is_some_and(|(_, actions)| !actions.is_empty())
6557 }
6558
6559 fn render_inline_code_actions(
6560 &self,
6561 icon_size: ui::IconSize,
6562 display_row: DisplayRow,
6563 is_active: bool,
6564 cx: &mut Context<Self>,
6565 ) -> AnyElement {
6566 let show_tooltip = !self.context_menu_visible();
6567 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6568 .icon_size(icon_size)
6569 .shape(ui::IconButtonShape::Square)
6570 .icon_color(ui::Color::Hidden)
6571 .toggle_state(is_active)
6572 .when(show_tooltip, |this| {
6573 this.tooltip({
6574 let focus_handle = self.focus_handle.clone();
6575 move |window, cx| {
6576 Tooltip::for_action_in(
6577 "Toggle Code Actions",
6578 &ToggleCodeActions {
6579 deployed_from: None,
6580 quick_launch: false,
6581 },
6582 &focus_handle,
6583 window,
6584 cx,
6585 )
6586 }
6587 })
6588 })
6589 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6590 window.focus(&editor.focus_handle(cx));
6591 editor.toggle_code_actions(
6592 &crate::actions::ToggleCodeActions {
6593 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6594 display_row,
6595 )),
6596 quick_launch: false,
6597 },
6598 window,
6599 cx,
6600 );
6601 }))
6602 .into_any_element()
6603 }
6604
6605 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6606 &self.context_menu
6607 }
6608
6609 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6610 let newest_selection = self.selections.newest_anchor().clone();
6611 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6612 let buffer = self.buffer.read(cx);
6613 if newest_selection.head().diff_base_anchor.is_some() {
6614 return None;
6615 }
6616 let (start_buffer, start) =
6617 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6618 let (end_buffer, end) =
6619 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6620 if start_buffer != end_buffer {
6621 return None;
6622 }
6623
6624 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6625 cx.background_executor()
6626 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6627 .await;
6628
6629 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6630 let providers = this.code_action_providers.clone();
6631 let tasks = this
6632 .code_action_providers
6633 .iter()
6634 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6635 .collect::<Vec<_>>();
6636 (providers, tasks)
6637 })?;
6638
6639 let mut actions = Vec::new();
6640 for (provider, provider_actions) in
6641 providers.into_iter().zip(future::join_all(tasks).await)
6642 {
6643 if let Some(provider_actions) = provider_actions.log_err() {
6644 actions.extend(provider_actions.into_iter().map(|action| {
6645 AvailableCodeAction {
6646 excerpt_id: newest_selection.start.excerpt_id,
6647 action,
6648 provider: provider.clone(),
6649 }
6650 }));
6651 }
6652 }
6653
6654 this.update(cx, |this, cx| {
6655 this.available_code_actions = if actions.is_empty() {
6656 None
6657 } else {
6658 Some((
6659 Location {
6660 buffer: start_buffer,
6661 range: start..end,
6662 },
6663 actions.into(),
6664 ))
6665 };
6666 cx.notify();
6667 })
6668 }));
6669 None
6670 }
6671
6672 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6673 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6674 self.show_git_blame_inline = false;
6675
6676 self.show_git_blame_inline_delay_task =
6677 Some(cx.spawn_in(window, async move |this, cx| {
6678 cx.background_executor().timer(delay).await;
6679
6680 this.update(cx, |this, cx| {
6681 this.show_git_blame_inline = true;
6682 cx.notify();
6683 })
6684 .log_err();
6685 }));
6686 }
6687 }
6688
6689 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6690 let snapshot = self.snapshot(window, cx);
6691 let cursor = self.selections.newest::<Point>(cx).head();
6692 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6693 else {
6694 return;
6695 };
6696
6697 let Some(blame) = self.blame.as_ref() else {
6698 return;
6699 };
6700
6701 let row_info = RowInfo {
6702 buffer_id: Some(buffer.remote_id()),
6703 buffer_row: Some(point.row),
6704 ..Default::default()
6705 };
6706 let Some((buffer, blame_entry)) = blame
6707 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6708 .flatten()
6709 else {
6710 return;
6711 };
6712
6713 let anchor = self.selections.newest_anchor().head();
6714 let position = self.to_pixel_point(anchor, &snapshot, window);
6715 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6716 self.show_blame_popover(
6717 buffer,
6718 &blame_entry,
6719 position + last_bounds.origin,
6720 true,
6721 cx,
6722 );
6723 };
6724 }
6725
6726 fn show_blame_popover(
6727 &mut self,
6728 buffer: BufferId,
6729 blame_entry: &BlameEntry,
6730 position: gpui::Point<Pixels>,
6731 ignore_timeout: bool,
6732 cx: &mut Context<Self>,
6733 ) {
6734 if let Some(state) = &mut self.inline_blame_popover {
6735 state.hide_task.take();
6736 } else {
6737 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6738 let blame_entry = blame_entry.clone();
6739 let show_task = cx.spawn(async move |editor, cx| {
6740 if !ignore_timeout {
6741 cx.background_executor()
6742 .timer(std::time::Duration::from_millis(blame_popover_delay))
6743 .await;
6744 }
6745 editor
6746 .update(cx, |editor, cx| {
6747 editor.inline_blame_popover_show_task.take();
6748 let Some(blame) = editor.blame.as_ref() else {
6749 return;
6750 };
6751 let blame = blame.read(cx);
6752 let details = blame.details_for_entry(buffer, &blame_entry);
6753 let markdown = cx.new(|cx| {
6754 Markdown::new(
6755 details
6756 .as_ref()
6757 .map(|message| message.message.clone())
6758 .unwrap_or_default(),
6759 None,
6760 None,
6761 cx,
6762 )
6763 });
6764 editor.inline_blame_popover = Some(InlineBlamePopover {
6765 position,
6766 hide_task: None,
6767 popover_bounds: None,
6768 popover_state: InlineBlamePopoverState {
6769 scroll_handle: ScrollHandle::new(),
6770 commit_message: details,
6771 markdown,
6772 },
6773 keyboard_grace: ignore_timeout,
6774 });
6775 cx.notify();
6776 })
6777 .ok();
6778 });
6779 self.inline_blame_popover_show_task = Some(show_task);
6780 }
6781 }
6782
6783 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6784 self.inline_blame_popover_show_task.take();
6785 if let Some(state) = &mut self.inline_blame_popover {
6786 let hide_task = cx.spawn(async move |editor, cx| {
6787 cx.background_executor()
6788 .timer(std::time::Duration::from_millis(100))
6789 .await;
6790 editor
6791 .update(cx, |editor, cx| {
6792 editor.inline_blame_popover.take();
6793 cx.notify();
6794 })
6795 .ok();
6796 });
6797 state.hide_task = Some(hide_task);
6798 }
6799 }
6800
6801 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6802 if self.pending_rename.is_some() {
6803 return None;
6804 }
6805
6806 let provider = self.semantics_provider.clone()?;
6807 let buffer = self.buffer.read(cx);
6808 let newest_selection = self.selections.newest_anchor().clone();
6809 let cursor_position = newest_selection.head();
6810 let (cursor_buffer, cursor_buffer_position) =
6811 buffer.text_anchor_for_position(cursor_position, cx)?;
6812 let (tail_buffer, tail_buffer_position) =
6813 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6814 if cursor_buffer != tail_buffer {
6815 return None;
6816 }
6817
6818 let snapshot = cursor_buffer.read(cx).snapshot();
6819 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6820 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6821 if start_word_range != end_word_range {
6822 self.document_highlights_task.take();
6823 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6824 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6825 return None;
6826 }
6827
6828 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6829 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6830 cx.background_executor()
6831 .timer(Duration::from_millis(debounce))
6832 .await;
6833
6834 let highlights = if let Some(highlights) = cx
6835 .update(|cx| {
6836 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6837 })
6838 .ok()
6839 .flatten()
6840 {
6841 highlights.await.log_err()
6842 } else {
6843 None
6844 };
6845
6846 if let Some(highlights) = highlights {
6847 this.update(cx, |this, cx| {
6848 if this.pending_rename.is_some() {
6849 return;
6850 }
6851
6852 let buffer = this.buffer.read(cx);
6853 if buffer
6854 .text_anchor_for_position(cursor_position, cx)
6855 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6856 {
6857 return;
6858 }
6859
6860 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6861 let mut write_ranges = Vec::new();
6862 let mut read_ranges = Vec::new();
6863 for highlight in highlights {
6864 let buffer_id = cursor_buffer.read(cx).remote_id();
6865 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6866 {
6867 let start = highlight
6868 .range
6869 .start
6870 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6871 let end = highlight
6872 .range
6873 .end
6874 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6875 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6876 continue;
6877 }
6878
6879 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6880 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6881 write_ranges.push(range);
6882 } else {
6883 read_ranges.push(range);
6884 }
6885 }
6886 }
6887
6888 this.highlight_background::<DocumentHighlightRead>(
6889 &read_ranges,
6890 |theme| theme.colors().editor_document_highlight_read_background,
6891 cx,
6892 );
6893 this.highlight_background::<DocumentHighlightWrite>(
6894 &write_ranges,
6895 |theme| theme.colors().editor_document_highlight_write_background,
6896 cx,
6897 );
6898 cx.notify();
6899 })
6900 .log_err();
6901 }
6902 }));
6903 None
6904 }
6905
6906 fn prepare_highlight_query_from_selection(
6907 &mut self,
6908 cx: &mut Context<Editor>,
6909 ) -> Option<(String, Range<Anchor>)> {
6910 if matches!(self.mode, EditorMode::SingleLine) {
6911 return None;
6912 }
6913 if !EditorSettings::get_global(cx).selection_highlight {
6914 return None;
6915 }
6916 if self.selections.count() != 1 || self.selections.line_mode() {
6917 return None;
6918 }
6919 let selection = self.selections.newest::<Point>(cx);
6920 if selection.is_empty() || selection.start.row != selection.end.row {
6921 return None;
6922 }
6923 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6924 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6925 let query = multi_buffer_snapshot
6926 .text_for_range(selection_anchor_range.clone())
6927 .collect::<String>();
6928 if query.trim().is_empty() {
6929 return None;
6930 }
6931 Some((query, selection_anchor_range))
6932 }
6933
6934 fn update_selection_occurrence_highlights(
6935 &mut self,
6936 query_text: String,
6937 query_range: Range<Anchor>,
6938 multi_buffer_range_to_query: Range<Point>,
6939 use_debounce: bool,
6940 window: &mut Window,
6941 cx: &mut Context<Editor>,
6942 ) -> Task<()> {
6943 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6944 cx.spawn_in(window, async move |editor, cx| {
6945 if use_debounce {
6946 cx.background_executor()
6947 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6948 .await;
6949 }
6950 let match_task = cx.background_spawn(async move {
6951 let buffer_ranges = multi_buffer_snapshot
6952 .range_to_buffer_ranges(multi_buffer_range_to_query)
6953 .into_iter()
6954 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6955 let mut match_ranges = Vec::new();
6956 let Ok(regex) = project::search::SearchQuery::text(
6957 query_text.clone(),
6958 false,
6959 false,
6960 false,
6961 Default::default(),
6962 Default::default(),
6963 false,
6964 None,
6965 ) else {
6966 return Vec::default();
6967 };
6968 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6969 match_ranges.extend(
6970 regex
6971 .search(buffer_snapshot, Some(search_range.clone()))
6972 .await
6973 .into_iter()
6974 .filter_map(|match_range| {
6975 let match_start = buffer_snapshot
6976 .anchor_after(search_range.start + match_range.start);
6977 let match_end = buffer_snapshot
6978 .anchor_before(search_range.start + match_range.end);
6979 let match_anchor_range = Anchor::range_in_buffer(
6980 excerpt_id,
6981 buffer_snapshot.remote_id(),
6982 match_start..match_end,
6983 );
6984 (match_anchor_range != query_range).then_some(match_anchor_range)
6985 }),
6986 );
6987 }
6988 match_ranges
6989 });
6990 let match_ranges = match_task.await;
6991 editor
6992 .update_in(cx, |editor, _, cx| {
6993 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6994 if !match_ranges.is_empty() {
6995 editor.highlight_background::<SelectedTextHighlight>(
6996 &match_ranges,
6997 |theme| theme.colors().editor_document_highlight_bracket_background,
6998 cx,
6999 )
7000 }
7001 })
7002 .log_err();
7003 })
7004 }
7005
7006 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7007 struct NewlineFold;
7008 let type_id = std::any::TypeId::of::<NewlineFold>();
7009 if !self.mode.is_single_line() {
7010 return;
7011 }
7012 let snapshot = self.snapshot(window, cx);
7013 if snapshot.buffer_snapshot.max_point().row == 0 {
7014 return;
7015 }
7016 let task = cx.background_spawn(async move {
7017 let new_newlines = snapshot
7018 .buffer_chars_at(0)
7019 .filter_map(|(c, i)| {
7020 if c == '\n' {
7021 Some(
7022 snapshot.buffer_snapshot.anchor_after(i)
7023 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7024 )
7025 } else {
7026 None
7027 }
7028 })
7029 .collect::<Vec<_>>();
7030 let existing_newlines = snapshot
7031 .folds_in_range(0..snapshot.buffer_snapshot.len())
7032 .filter_map(|fold| {
7033 if fold.placeholder.type_tag == Some(type_id) {
7034 Some(fold.range.start..fold.range.end)
7035 } else {
7036 None
7037 }
7038 })
7039 .collect::<Vec<_>>();
7040
7041 (new_newlines, existing_newlines)
7042 });
7043 self.folding_newlines = cx.spawn(async move |this, cx| {
7044 let (new_newlines, existing_newlines) = task.await;
7045 if new_newlines == existing_newlines {
7046 return;
7047 }
7048 let placeholder = FoldPlaceholder {
7049 render: Arc::new(move |_, _, cx| {
7050 div()
7051 .bg(cx.theme().status().hint_background)
7052 .border_b_1()
7053 .size_full()
7054 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7055 .border_color(cx.theme().status().hint)
7056 .child("\\n")
7057 .into_any()
7058 }),
7059 constrain_width: false,
7060 merge_adjacent: false,
7061 type_tag: Some(type_id),
7062 };
7063 let creases = new_newlines
7064 .into_iter()
7065 .map(|range| Crease::simple(range, placeholder.clone()))
7066 .collect();
7067 this.update(cx, |this, cx| {
7068 this.display_map.update(cx, |display_map, cx| {
7069 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7070 display_map.fold(creases, cx);
7071 });
7072 })
7073 .ok();
7074 });
7075 }
7076
7077 fn refresh_selected_text_highlights(
7078 &mut self,
7079 on_buffer_edit: bool,
7080 window: &mut Window,
7081 cx: &mut Context<Editor>,
7082 ) {
7083 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7084 else {
7085 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7086 self.quick_selection_highlight_task.take();
7087 self.debounced_selection_highlight_task.take();
7088 return;
7089 };
7090 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7091 if on_buffer_edit
7092 || self
7093 .quick_selection_highlight_task
7094 .as_ref()
7095 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7096 {
7097 let multi_buffer_visible_start = self
7098 .scroll_manager
7099 .anchor()
7100 .anchor
7101 .to_point(&multi_buffer_snapshot);
7102 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7103 multi_buffer_visible_start
7104 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7105 Bias::Left,
7106 );
7107 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7108 self.quick_selection_highlight_task = Some((
7109 query_range.clone(),
7110 self.update_selection_occurrence_highlights(
7111 query_text.clone(),
7112 query_range.clone(),
7113 multi_buffer_visible_range,
7114 false,
7115 window,
7116 cx,
7117 ),
7118 ));
7119 }
7120 if on_buffer_edit
7121 || self
7122 .debounced_selection_highlight_task
7123 .as_ref()
7124 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7125 {
7126 let multi_buffer_start = multi_buffer_snapshot
7127 .anchor_before(0)
7128 .to_point(&multi_buffer_snapshot);
7129 let multi_buffer_end = multi_buffer_snapshot
7130 .anchor_after(multi_buffer_snapshot.len())
7131 .to_point(&multi_buffer_snapshot);
7132 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7133 self.debounced_selection_highlight_task = Some((
7134 query_range.clone(),
7135 self.update_selection_occurrence_highlights(
7136 query_text,
7137 query_range,
7138 multi_buffer_full_range,
7139 true,
7140 window,
7141 cx,
7142 ),
7143 ));
7144 }
7145 }
7146
7147 pub fn refresh_edit_prediction(
7148 &mut self,
7149 debounce: bool,
7150 user_requested: bool,
7151 window: &mut Window,
7152 cx: &mut Context<Self>,
7153 ) -> Option<()> {
7154 if DisableAiSettings::get_global(cx).disable_ai {
7155 return None;
7156 }
7157
7158 let provider = self.edit_prediction_provider()?;
7159 let cursor = self.selections.newest_anchor().head();
7160 let (buffer, cursor_buffer_position) =
7161 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7162
7163 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7164 self.discard_edit_prediction(false, cx);
7165 return None;
7166 }
7167
7168 self.update_visible_edit_prediction(window, cx);
7169
7170 if !user_requested
7171 && (!self.should_show_edit_predictions()
7172 || !self.is_focused(window)
7173 || buffer.read(cx).is_empty())
7174 {
7175 self.discard_edit_prediction(false, cx);
7176 return None;
7177 }
7178
7179 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7180 Some(())
7181 }
7182
7183 fn show_edit_predictions_in_menu(&self) -> bool {
7184 match self.edit_prediction_settings {
7185 EditPredictionSettings::Disabled => false,
7186 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7187 }
7188 }
7189
7190 pub fn edit_predictions_enabled(&self) -> bool {
7191 match self.edit_prediction_settings {
7192 EditPredictionSettings::Disabled => false,
7193 EditPredictionSettings::Enabled { .. } => true,
7194 }
7195 }
7196
7197 fn edit_prediction_requires_modifier(&self) -> bool {
7198 match self.edit_prediction_settings {
7199 EditPredictionSettings::Disabled => false,
7200 EditPredictionSettings::Enabled {
7201 preview_requires_modifier,
7202 ..
7203 } => preview_requires_modifier,
7204 }
7205 }
7206
7207 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7208 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7209 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7210 self.discard_edit_prediction(false, cx);
7211 } else {
7212 let selection = self.selections.newest_anchor();
7213 let cursor = selection.head();
7214
7215 if let Some((buffer, cursor_buffer_position)) =
7216 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7217 {
7218 self.edit_prediction_settings =
7219 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7220 }
7221 }
7222 }
7223
7224 fn edit_prediction_settings_at_position(
7225 &self,
7226 buffer: &Entity<Buffer>,
7227 buffer_position: language::Anchor,
7228 cx: &App,
7229 ) -> EditPredictionSettings {
7230 if !self.mode.is_full()
7231 || !self.show_edit_predictions_override.unwrap_or(true)
7232 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7233 {
7234 return EditPredictionSettings::Disabled;
7235 }
7236
7237 let buffer = buffer.read(cx);
7238
7239 let file = buffer.file();
7240
7241 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7242 return EditPredictionSettings::Disabled;
7243 };
7244
7245 let by_provider = matches!(
7246 self.menu_edit_predictions_policy,
7247 MenuEditPredictionsPolicy::ByProvider
7248 );
7249
7250 let show_in_menu = by_provider
7251 && self
7252 .edit_prediction_provider
7253 .as_ref()
7254 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7255
7256 let preview_requires_modifier =
7257 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7258
7259 EditPredictionSettings::Enabled {
7260 show_in_menu,
7261 preview_requires_modifier,
7262 }
7263 }
7264
7265 fn should_show_edit_predictions(&self) -> bool {
7266 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7267 }
7268
7269 pub fn edit_prediction_preview_is_active(&self) -> bool {
7270 matches!(
7271 self.edit_prediction_preview,
7272 EditPredictionPreview::Active { .. }
7273 )
7274 }
7275
7276 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7277 let cursor = self.selections.newest_anchor().head();
7278 if let Some((buffer, cursor_position)) =
7279 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7280 {
7281 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7282 } else {
7283 false
7284 }
7285 }
7286
7287 pub fn supports_minimap(&self, cx: &App) -> bool {
7288 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7289 }
7290
7291 fn edit_predictions_enabled_in_buffer(
7292 &self,
7293 buffer: &Entity<Buffer>,
7294 buffer_position: language::Anchor,
7295 cx: &App,
7296 ) -> bool {
7297 maybe!({
7298 if self.read_only(cx) {
7299 return Some(false);
7300 }
7301 let provider = self.edit_prediction_provider()?;
7302 if !provider.is_enabled(buffer, buffer_position, cx) {
7303 return Some(false);
7304 }
7305 let buffer = buffer.read(cx);
7306 let Some(file) = buffer.file() else {
7307 return Some(true);
7308 };
7309 let settings = all_language_settings(Some(file), cx);
7310 Some(settings.edit_predictions_enabled_for_file(file, cx))
7311 })
7312 .unwrap_or(false)
7313 }
7314
7315 fn cycle_edit_prediction(
7316 &mut self,
7317 direction: Direction,
7318 window: &mut Window,
7319 cx: &mut Context<Self>,
7320 ) -> Option<()> {
7321 let provider = self.edit_prediction_provider()?;
7322 let cursor = self.selections.newest_anchor().head();
7323 let (buffer, cursor_buffer_position) =
7324 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7325 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7326 return None;
7327 }
7328
7329 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7330 self.update_visible_edit_prediction(window, cx);
7331
7332 Some(())
7333 }
7334
7335 pub fn show_edit_prediction(
7336 &mut self,
7337 _: &ShowEditPrediction,
7338 window: &mut Window,
7339 cx: &mut Context<Self>,
7340 ) {
7341 if !self.has_active_edit_prediction() {
7342 self.refresh_edit_prediction(false, true, window, cx);
7343 return;
7344 }
7345
7346 self.update_visible_edit_prediction(window, cx);
7347 }
7348
7349 pub fn display_cursor_names(
7350 &mut self,
7351 _: &DisplayCursorNames,
7352 window: &mut Window,
7353 cx: &mut Context<Self>,
7354 ) {
7355 self.show_cursor_names(window, cx);
7356 }
7357
7358 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7359 self.show_cursor_names = true;
7360 cx.notify();
7361 cx.spawn_in(window, async move |this, cx| {
7362 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7363 this.update(cx, |this, cx| {
7364 this.show_cursor_names = false;
7365 cx.notify()
7366 })
7367 .ok()
7368 })
7369 .detach();
7370 }
7371
7372 pub fn next_edit_prediction(
7373 &mut self,
7374 _: &NextEditPrediction,
7375 window: &mut Window,
7376 cx: &mut Context<Self>,
7377 ) {
7378 if self.has_active_edit_prediction() {
7379 self.cycle_edit_prediction(Direction::Next, window, cx);
7380 } else {
7381 let is_copilot_disabled = self
7382 .refresh_edit_prediction(false, true, window, cx)
7383 .is_none();
7384 if is_copilot_disabled {
7385 cx.propagate();
7386 }
7387 }
7388 }
7389
7390 pub fn previous_edit_prediction(
7391 &mut self,
7392 _: &PreviousEditPrediction,
7393 window: &mut Window,
7394 cx: &mut Context<Self>,
7395 ) {
7396 if self.has_active_edit_prediction() {
7397 self.cycle_edit_prediction(Direction::Prev, window, cx);
7398 } else {
7399 let is_copilot_disabled = self
7400 .refresh_edit_prediction(false, true, window, cx)
7401 .is_none();
7402 if is_copilot_disabled {
7403 cx.propagate();
7404 }
7405 }
7406 }
7407
7408 pub fn accept_edit_prediction(
7409 &mut self,
7410 _: &AcceptEditPrediction,
7411 window: &mut Window,
7412 cx: &mut Context<Self>,
7413 ) {
7414 if self.show_edit_predictions_in_menu() {
7415 self.hide_context_menu(window, cx);
7416 }
7417
7418 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7419 return;
7420 };
7421
7422 match &active_edit_prediction.completion {
7423 EditPrediction::MoveWithin { target, .. } => {
7424 let target = *target;
7425
7426 if let Some(position_map) = &self.last_position_map {
7427 if position_map
7428 .visible_row_range
7429 .contains(&target.to_display_point(&position_map.snapshot).row())
7430 || !self.edit_prediction_requires_modifier()
7431 {
7432 self.unfold_ranges(&[target..target], true, false, cx);
7433 // Note that this is also done in vim's handler of the Tab action.
7434 self.change_selections(
7435 SelectionEffects::scroll(Autoscroll::newest()),
7436 window,
7437 cx,
7438 |selections| {
7439 selections.select_anchor_ranges([target..target]);
7440 },
7441 );
7442 self.clear_row_highlights::<EditPredictionPreview>();
7443
7444 self.edit_prediction_preview
7445 .set_previous_scroll_position(None);
7446 } else {
7447 self.edit_prediction_preview
7448 .set_previous_scroll_position(Some(
7449 position_map.snapshot.scroll_anchor,
7450 ));
7451
7452 self.highlight_rows::<EditPredictionPreview>(
7453 target..target,
7454 cx.theme().colors().editor_highlighted_line_background,
7455 RowHighlightOptions {
7456 autoscroll: true,
7457 ..Default::default()
7458 },
7459 cx,
7460 );
7461 self.request_autoscroll(Autoscroll::fit(), cx);
7462 }
7463 }
7464 }
7465 EditPrediction::MoveOutside { snapshot, target } => {
7466 if let Some(workspace) = self.workspace() {
7467 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7468 .detach_and_log_err(cx);
7469 }
7470 }
7471 EditPrediction::Edit { edits, .. } => {
7472 self.report_edit_prediction_event(
7473 active_edit_prediction.completion_id.clone(),
7474 true,
7475 cx,
7476 );
7477
7478 if let Some(provider) = self.edit_prediction_provider() {
7479 provider.accept(cx);
7480 }
7481
7482 // Store the transaction ID and selections before applying the edit
7483 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7484
7485 let snapshot = self.buffer.read(cx).snapshot(cx);
7486 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7487
7488 self.buffer.update(cx, |buffer, cx| {
7489 buffer.edit(edits.iter().cloned(), None, cx)
7490 });
7491
7492 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7493 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7494 });
7495
7496 let selections = self.selections.disjoint_anchors_arc();
7497 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7498 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7499 if has_new_transaction {
7500 self.selection_history
7501 .insert_transaction(transaction_id_now, selections);
7502 }
7503 }
7504
7505 self.update_visible_edit_prediction(window, cx);
7506 if self.active_edit_prediction.is_none() {
7507 self.refresh_edit_prediction(true, true, window, cx);
7508 }
7509
7510 cx.notify();
7511 }
7512 }
7513
7514 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7515 }
7516
7517 pub fn accept_partial_edit_prediction(
7518 &mut self,
7519 _: &AcceptPartialEditPrediction,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) {
7523 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7524 return;
7525 };
7526 if self.selections.count() != 1 {
7527 return;
7528 }
7529
7530 match &active_edit_prediction.completion {
7531 EditPrediction::MoveWithin { target, .. } => {
7532 let target = *target;
7533 self.change_selections(
7534 SelectionEffects::scroll(Autoscroll::newest()),
7535 window,
7536 cx,
7537 |selections| {
7538 selections.select_anchor_ranges([target..target]);
7539 },
7540 );
7541 }
7542 EditPrediction::MoveOutside { snapshot, target } => {
7543 if let Some(workspace) = self.workspace() {
7544 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7545 .detach_and_log_err(cx);
7546 }
7547 }
7548 EditPrediction::Edit { edits, .. } => {
7549 self.report_edit_prediction_event(
7550 active_edit_prediction.completion_id.clone(),
7551 true,
7552 cx,
7553 );
7554
7555 // Find an insertion that starts at the cursor position.
7556 let snapshot = self.buffer.read(cx).snapshot(cx);
7557 let cursor_offset = self.selections.newest::<usize>(cx).head();
7558 let insertion = edits.iter().find_map(|(range, text)| {
7559 let range = range.to_offset(&snapshot);
7560 if range.is_empty() && range.start == cursor_offset {
7561 Some(text)
7562 } else {
7563 None
7564 }
7565 });
7566
7567 if let Some(text) = insertion {
7568 let mut partial_completion = text
7569 .chars()
7570 .by_ref()
7571 .take_while(|c| c.is_alphabetic())
7572 .collect::<String>();
7573 if partial_completion.is_empty() {
7574 partial_completion = text
7575 .chars()
7576 .by_ref()
7577 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7578 .collect::<String>();
7579 }
7580
7581 cx.emit(EditorEvent::InputHandled {
7582 utf16_range_to_replace: None,
7583 text: partial_completion.clone().into(),
7584 });
7585
7586 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7587
7588 self.refresh_edit_prediction(true, true, window, cx);
7589 cx.notify();
7590 } else {
7591 self.accept_edit_prediction(&Default::default(), window, cx);
7592 }
7593 }
7594 }
7595 }
7596
7597 fn discard_edit_prediction(
7598 &mut self,
7599 should_report_edit_prediction_event: bool,
7600 cx: &mut Context<Self>,
7601 ) -> bool {
7602 if should_report_edit_prediction_event {
7603 let completion_id = self
7604 .active_edit_prediction
7605 .as_ref()
7606 .and_then(|active_completion| active_completion.completion_id.clone());
7607
7608 self.report_edit_prediction_event(completion_id, false, cx);
7609 }
7610
7611 if let Some(provider) = self.edit_prediction_provider() {
7612 provider.discard(cx);
7613 }
7614
7615 self.take_active_edit_prediction(cx)
7616 }
7617
7618 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7619 let Some(provider) = self.edit_prediction_provider() else {
7620 return;
7621 };
7622
7623 let Some((_, buffer, _)) = self
7624 .buffer
7625 .read(cx)
7626 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7627 else {
7628 return;
7629 };
7630
7631 let extension = buffer
7632 .read(cx)
7633 .file()
7634 .and_then(|file| Some(file.path().extension()?.to_string()));
7635
7636 let event_type = match accepted {
7637 true => "Edit Prediction Accepted",
7638 false => "Edit Prediction Discarded",
7639 };
7640 telemetry::event!(
7641 event_type,
7642 provider = provider.name(),
7643 prediction_id = id,
7644 suggestion_accepted = accepted,
7645 file_extension = extension,
7646 );
7647 }
7648
7649 fn open_editor_at_anchor(
7650 snapshot: &language::BufferSnapshot,
7651 target: language::Anchor,
7652 workspace: &Entity<Workspace>,
7653 window: &mut Window,
7654 cx: &mut App,
7655 ) -> Task<Result<()>> {
7656 workspace.update(cx, |workspace, cx| {
7657 let path = snapshot.file().map(|file| file.full_path(cx));
7658 let Some(path) =
7659 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7660 else {
7661 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7662 };
7663 let target = text::ToPoint::to_point(&target, snapshot);
7664 let item = workspace.open_path(path, None, true, window, cx);
7665 window.spawn(cx, async move |cx| {
7666 let Some(editor) = item.await?.downcast::<Editor>() else {
7667 return Ok(());
7668 };
7669 editor
7670 .update_in(cx, |editor, window, cx| {
7671 editor.go_to_singleton_buffer_point(target, window, cx);
7672 })
7673 .ok();
7674 anyhow::Ok(())
7675 })
7676 })
7677 }
7678
7679 pub fn has_active_edit_prediction(&self) -> bool {
7680 self.active_edit_prediction.is_some()
7681 }
7682
7683 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7684 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7685 return false;
7686 };
7687
7688 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7689 self.clear_highlights::<EditPredictionHighlight>(cx);
7690 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7691 true
7692 }
7693
7694 /// Returns true when we're displaying the edit prediction popover below the cursor
7695 /// like we are not previewing and the LSP autocomplete menu is visible
7696 /// or we are in `when_holding_modifier` mode.
7697 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7698 if self.edit_prediction_preview_is_active()
7699 || !self.show_edit_predictions_in_menu()
7700 || !self.edit_predictions_enabled()
7701 {
7702 return false;
7703 }
7704
7705 if self.has_visible_completions_menu() {
7706 return true;
7707 }
7708
7709 has_completion && self.edit_prediction_requires_modifier()
7710 }
7711
7712 fn handle_modifiers_changed(
7713 &mut self,
7714 modifiers: Modifiers,
7715 position_map: &PositionMap,
7716 window: &mut Window,
7717 cx: &mut Context<Self>,
7718 ) {
7719 if self.show_edit_predictions_in_menu() {
7720 self.update_edit_prediction_preview(&modifiers, window, cx);
7721 }
7722
7723 self.update_selection_mode(&modifiers, position_map, window, cx);
7724
7725 let mouse_position = window.mouse_position();
7726 if !position_map.text_hitbox.is_hovered(window) {
7727 return;
7728 }
7729
7730 self.update_hovered_link(
7731 position_map.point_for_position(mouse_position),
7732 &position_map.snapshot,
7733 modifiers,
7734 window,
7735 cx,
7736 )
7737 }
7738
7739 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7740 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7741 if invert {
7742 match multi_cursor_setting {
7743 MultiCursorModifier::Alt => modifiers.alt,
7744 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7745 }
7746 } else {
7747 match multi_cursor_setting {
7748 MultiCursorModifier::Alt => modifiers.secondary(),
7749 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7750 }
7751 }
7752 }
7753
7754 fn columnar_selection_mode(
7755 modifiers: &Modifiers,
7756 cx: &mut Context<Self>,
7757 ) -> Option<ColumnarMode> {
7758 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7759 if Self::multi_cursor_modifier(false, modifiers, cx) {
7760 Some(ColumnarMode::FromMouse)
7761 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7762 Some(ColumnarMode::FromSelection)
7763 } else {
7764 None
7765 }
7766 } else {
7767 None
7768 }
7769 }
7770
7771 fn update_selection_mode(
7772 &mut self,
7773 modifiers: &Modifiers,
7774 position_map: &PositionMap,
7775 window: &mut Window,
7776 cx: &mut Context<Self>,
7777 ) {
7778 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7779 return;
7780 };
7781 if self.selections.pending_anchor().is_none() {
7782 return;
7783 }
7784
7785 let mouse_position = window.mouse_position();
7786 let point_for_position = position_map.point_for_position(mouse_position);
7787 let position = point_for_position.previous_valid;
7788
7789 self.select(
7790 SelectPhase::BeginColumnar {
7791 position,
7792 reset: false,
7793 mode,
7794 goal_column: point_for_position.exact_unclipped.column(),
7795 },
7796 window,
7797 cx,
7798 );
7799 }
7800
7801 fn update_edit_prediction_preview(
7802 &mut self,
7803 modifiers: &Modifiers,
7804 window: &mut Window,
7805 cx: &mut Context<Self>,
7806 ) {
7807 let mut modifiers_held = false;
7808 if let Some(accept_keystroke) = self
7809 .accept_edit_prediction_keybind(false, window, cx)
7810 .keystroke()
7811 {
7812 modifiers_held = modifiers_held
7813 || (accept_keystroke.modifiers() == modifiers
7814 && accept_keystroke.modifiers().modified());
7815 };
7816 if let Some(accept_partial_keystroke) = self
7817 .accept_edit_prediction_keybind(true, window, cx)
7818 .keystroke()
7819 {
7820 modifiers_held = modifiers_held
7821 || (accept_partial_keystroke.modifiers() == modifiers
7822 && accept_partial_keystroke.modifiers().modified());
7823 }
7824
7825 if modifiers_held {
7826 if matches!(
7827 self.edit_prediction_preview,
7828 EditPredictionPreview::Inactive { .. }
7829 ) {
7830 self.edit_prediction_preview = EditPredictionPreview::Active {
7831 previous_scroll_position: None,
7832 since: Instant::now(),
7833 };
7834
7835 self.update_visible_edit_prediction(window, cx);
7836 cx.notify();
7837 }
7838 } else if let EditPredictionPreview::Active {
7839 previous_scroll_position,
7840 since,
7841 } = self.edit_prediction_preview
7842 {
7843 if let (Some(previous_scroll_position), Some(position_map)) =
7844 (previous_scroll_position, self.last_position_map.as_ref())
7845 {
7846 self.set_scroll_position(
7847 previous_scroll_position
7848 .scroll_position(&position_map.snapshot.display_snapshot),
7849 window,
7850 cx,
7851 );
7852 }
7853
7854 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7855 released_too_fast: since.elapsed() < Duration::from_millis(200),
7856 };
7857 self.clear_row_highlights::<EditPredictionPreview>();
7858 self.update_visible_edit_prediction(window, cx);
7859 cx.notify();
7860 }
7861 }
7862
7863 fn update_visible_edit_prediction(
7864 &mut self,
7865 _window: &mut Window,
7866 cx: &mut Context<Self>,
7867 ) -> Option<()> {
7868 if DisableAiSettings::get_global(cx).disable_ai {
7869 return None;
7870 }
7871
7872 if self.ime_transaction.is_some() {
7873 self.discard_edit_prediction(false, cx);
7874 return None;
7875 }
7876
7877 let selection = self.selections.newest_anchor();
7878 let cursor = selection.head();
7879 let multibuffer = self.buffer.read(cx).snapshot(cx);
7880 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7881 let excerpt_id = cursor.excerpt_id;
7882
7883 let show_in_menu = self.show_edit_predictions_in_menu();
7884 let completions_menu_has_precedence = !show_in_menu
7885 && (self.context_menu.borrow().is_some()
7886 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7887
7888 if completions_menu_has_precedence
7889 || !offset_selection.is_empty()
7890 || self
7891 .active_edit_prediction
7892 .as_ref()
7893 .is_some_and(|completion| {
7894 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7895 return false;
7896 };
7897 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7898 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7899 !invalidation_range.contains(&offset_selection.head())
7900 })
7901 {
7902 self.discard_edit_prediction(false, cx);
7903 return None;
7904 }
7905
7906 self.take_active_edit_prediction(cx);
7907 let Some(provider) = self.edit_prediction_provider() else {
7908 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7909 return None;
7910 };
7911
7912 let (buffer, cursor_buffer_position) =
7913 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7914
7915 self.edit_prediction_settings =
7916 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7917
7918 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7919
7920 if self.edit_prediction_indent_conflict {
7921 let cursor_point = cursor.to_point(&multibuffer);
7922
7923 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7924
7925 if let Some((_, indent)) = indents.iter().next()
7926 && indent.len == cursor_point.column
7927 {
7928 self.edit_prediction_indent_conflict = false;
7929 }
7930 }
7931
7932 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7933
7934 let (completion_id, edits, edit_preview) = match edit_prediction {
7935 edit_prediction::EditPrediction::Local {
7936 id,
7937 edits,
7938 edit_preview,
7939 } => (id, edits, edit_preview),
7940 edit_prediction::EditPrediction::Jump {
7941 id,
7942 snapshot,
7943 target,
7944 } => {
7945 self.stale_edit_prediction_in_menu = None;
7946 self.active_edit_prediction = Some(EditPredictionState {
7947 inlay_ids: vec![],
7948 completion: EditPrediction::MoveOutside { snapshot, target },
7949 completion_id: id,
7950 invalidation_range: None,
7951 });
7952 cx.notify();
7953 return Some(());
7954 }
7955 };
7956
7957 let edits = edits
7958 .into_iter()
7959 .flat_map(|(range, new_text)| {
7960 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7961 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7962 Some((start..end, new_text))
7963 })
7964 .collect::<Vec<_>>();
7965 if edits.is_empty() {
7966 return None;
7967 }
7968
7969 let first_edit_start = edits.first().unwrap().0.start;
7970 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7971 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7972
7973 let last_edit_end = edits.last().unwrap().0.end;
7974 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7975 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7976
7977 let cursor_row = cursor.to_point(&multibuffer).row;
7978
7979 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7980
7981 let mut inlay_ids = Vec::new();
7982 let invalidation_row_range;
7983 let move_invalidation_row_range = if cursor_row < edit_start_row {
7984 Some(cursor_row..edit_end_row)
7985 } else if cursor_row > edit_end_row {
7986 Some(edit_start_row..cursor_row)
7987 } else {
7988 None
7989 };
7990 let supports_jump = self
7991 .edit_prediction_provider
7992 .as_ref()
7993 .map(|provider| provider.provider.supports_jump_to_edit())
7994 .unwrap_or(true);
7995
7996 let is_move = supports_jump
7997 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7998 let completion = if is_move {
7999 invalidation_row_range =
8000 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8001 let target = first_edit_start;
8002 EditPrediction::MoveWithin { target, snapshot }
8003 } else {
8004 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8005 && !self.edit_predictions_hidden_for_vim_mode;
8006
8007 if show_completions_in_buffer {
8008 if edits
8009 .iter()
8010 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8011 {
8012 let mut inlays = Vec::new();
8013 for (range, new_text) in &edits {
8014 let inlay = Inlay::edit_prediction(
8015 post_inc(&mut self.next_inlay_id),
8016 range.start,
8017 new_text.as_str(),
8018 );
8019 inlay_ids.push(inlay.id);
8020 inlays.push(inlay);
8021 }
8022
8023 self.splice_inlays(&[], inlays, cx);
8024 } else {
8025 let background_color = cx.theme().status().deleted_background;
8026 self.highlight_text::<EditPredictionHighlight>(
8027 edits.iter().map(|(range, _)| range.clone()).collect(),
8028 HighlightStyle {
8029 background_color: Some(background_color),
8030 ..Default::default()
8031 },
8032 cx,
8033 );
8034 }
8035 }
8036
8037 invalidation_row_range = edit_start_row..edit_end_row;
8038
8039 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8040 if provider.show_tab_accept_marker() {
8041 EditDisplayMode::TabAccept
8042 } else {
8043 EditDisplayMode::Inline
8044 }
8045 } else {
8046 EditDisplayMode::DiffPopover
8047 };
8048
8049 EditPrediction::Edit {
8050 edits,
8051 edit_preview,
8052 display_mode,
8053 snapshot,
8054 }
8055 };
8056
8057 let invalidation_range = multibuffer
8058 .anchor_before(Point::new(invalidation_row_range.start, 0))
8059 ..multibuffer.anchor_after(Point::new(
8060 invalidation_row_range.end,
8061 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8062 ));
8063
8064 self.stale_edit_prediction_in_menu = None;
8065 self.active_edit_prediction = Some(EditPredictionState {
8066 inlay_ids,
8067 completion,
8068 completion_id,
8069 invalidation_range: Some(invalidation_range),
8070 });
8071
8072 cx.notify();
8073
8074 Some(())
8075 }
8076
8077 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8078 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8079 }
8080
8081 fn clear_tasks(&mut self) {
8082 self.tasks.clear()
8083 }
8084
8085 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8086 if self.tasks.insert(key, value).is_some() {
8087 // This case should hopefully be rare, but just in case...
8088 log::error!(
8089 "multiple different run targets found on a single line, only the last target will be rendered"
8090 )
8091 }
8092 }
8093
8094 /// Get all display points of breakpoints that will be rendered within editor
8095 ///
8096 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8097 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8098 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8099 fn active_breakpoints(
8100 &self,
8101 range: Range<DisplayRow>,
8102 window: &mut Window,
8103 cx: &mut Context<Self>,
8104 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8105 let mut breakpoint_display_points = HashMap::default();
8106
8107 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8108 return breakpoint_display_points;
8109 };
8110
8111 let snapshot = self.snapshot(window, cx);
8112
8113 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8114 let Some(project) = self.project() else {
8115 return breakpoint_display_points;
8116 };
8117
8118 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8119 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8120
8121 for (buffer_snapshot, range, excerpt_id) in
8122 multi_buffer_snapshot.range_to_buffer_ranges(range)
8123 {
8124 let Some(buffer) = project
8125 .read(cx)
8126 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8127 else {
8128 continue;
8129 };
8130 let breakpoints = breakpoint_store.read(cx).breakpoints(
8131 &buffer,
8132 Some(
8133 buffer_snapshot.anchor_before(range.start)
8134 ..buffer_snapshot.anchor_after(range.end),
8135 ),
8136 buffer_snapshot,
8137 cx,
8138 );
8139 for (breakpoint, state) in breakpoints {
8140 let multi_buffer_anchor =
8141 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8142 let position = multi_buffer_anchor
8143 .to_point(multi_buffer_snapshot)
8144 .to_display_point(&snapshot);
8145
8146 breakpoint_display_points.insert(
8147 position.row(),
8148 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8149 );
8150 }
8151 }
8152
8153 breakpoint_display_points
8154 }
8155
8156 fn breakpoint_context_menu(
8157 &self,
8158 anchor: Anchor,
8159 window: &mut Window,
8160 cx: &mut Context<Self>,
8161 ) -> Entity<ui::ContextMenu> {
8162 let weak_editor = cx.weak_entity();
8163 let focus_handle = self.focus_handle(cx);
8164
8165 let row = self
8166 .buffer
8167 .read(cx)
8168 .snapshot(cx)
8169 .summary_for_anchor::<Point>(&anchor)
8170 .row;
8171
8172 let breakpoint = self
8173 .breakpoint_at_row(row, window, cx)
8174 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8175
8176 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8177 "Edit Log Breakpoint"
8178 } else {
8179 "Set Log Breakpoint"
8180 };
8181
8182 let condition_breakpoint_msg = if breakpoint
8183 .as_ref()
8184 .is_some_and(|bp| bp.1.condition.is_some())
8185 {
8186 "Edit Condition Breakpoint"
8187 } else {
8188 "Set Condition Breakpoint"
8189 };
8190
8191 let hit_condition_breakpoint_msg = if breakpoint
8192 .as_ref()
8193 .is_some_and(|bp| bp.1.hit_condition.is_some())
8194 {
8195 "Edit Hit Condition Breakpoint"
8196 } else {
8197 "Set Hit Condition Breakpoint"
8198 };
8199
8200 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8201 "Unset Breakpoint"
8202 } else {
8203 "Set Breakpoint"
8204 };
8205
8206 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8207
8208 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8209 BreakpointState::Enabled => Some("Disable"),
8210 BreakpointState::Disabled => Some("Enable"),
8211 });
8212
8213 let (anchor, breakpoint) =
8214 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8215
8216 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8217 menu.on_blur_subscription(Subscription::new(|| {}))
8218 .context(focus_handle)
8219 .when(run_to_cursor, |this| {
8220 let weak_editor = weak_editor.clone();
8221 this.entry("Run to cursor", None, move |window, cx| {
8222 weak_editor
8223 .update(cx, |editor, cx| {
8224 editor.change_selections(
8225 SelectionEffects::no_scroll(),
8226 window,
8227 cx,
8228 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8229 );
8230 })
8231 .ok();
8232
8233 window.dispatch_action(Box::new(RunToCursor), cx);
8234 })
8235 .separator()
8236 })
8237 .when_some(toggle_state_msg, |this, msg| {
8238 this.entry(msg, None, {
8239 let weak_editor = weak_editor.clone();
8240 let breakpoint = breakpoint.clone();
8241 move |_window, cx| {
8242 weak_editor
8243 .update(cx, |this, cx| {
8244 this.edit_breakpoint_at_anchor(
8245 anchor,
8246 breakpoint.as_ref().clone(),
8247 BreakpointEditAction::InvertState,
8248 cx,
8249 );
8250 })
8251 .log_err();
8252 }
8253 })
8254 })
8255 .entry(set_breakpoint_msg, None, {
8256 let weak_editor = weak_editor.clone();
8257 let breakpoint = breakpoint.clone();
8258 move |_window, cx| {
8259 weak_editor
8260 .update(cx, |this, cx| {
8261 this.edit_breakpoint_at_anchor(
8262 anchor,
8263 breakpoint.as_ref().clone(),
8264 BreakpointEditAction::Toggle,
8265 cx,
8266 );
8267 })
8268 .log_err();
8269 }
8270 })
8271 .entry(log_breakpoint_msg, None, {
8272 let breakpoint = breakpoint.clone();
8273 let weak_editor = weak_editor.clone();
8274 move |window, cx| {
8275 weak_editor
8276 .update(cx, |this, cx| {
8277 this.add_edit_breakpoint_block(
8278 anchor,
8279 breakpoint.as_ref(),
8280 BreakpointPromptEditAction::Log,
8281 window,
8282 cx,
8283 );
8284 })
8285 .log_err();
8286 }
8287 })
8288 .entry(condition_breakpoint_msg, None, {
8289 let breakpoint = breakpoint.clone();
8290 let weak_editor = weak_editor.clone();
8291 move |window, cx| {
8292 weak_editor
8293 .update(cx, |this, cx| {
8294 this.add_edit_breakpoint_block(
8295 anchor,
8296 breakpoint.as_ref(),
8297 BreakpointPromptEditAction::Condition,
8298 window,
8299 cx,
8300 );
8301 })
8302 .log_err();
8303 }
8304 })
8305 .entry(hit_condition_breakpoint_msg, None, 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::HitCondition,
8312 window,
8313 cx,
8314 );
8315 })
8316 .log_err();
8317 })
8318 })
8319 }
8320
8321 fn render_breakpoint(
8322 &self,
8323 position: Anchor,
8324 row: DisplayRow,
8325 breakpoint: &Breakpoint,
8326 state: Option<BreakpointSessionState>,
8327 cx: &mut Context<Self>,
8328 ) -> IconButton {
8329 let is_rejected = state.is_some_and(|s| !s.verified);
8330 // Is it a breakpoint that shows up when hovering over gutter?
8331 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8332 (false, false),
8333 |PhantomBreakpointIndicator {
8334 is_active,
8335 display_row,
8336 collides_with_existing_breakpoint,
8337 }| {
8338 (
8339 is_active && display_row == row,
8340 collides_with_existing_breakpoint,
8341 )
8342 },
8343 );
8344
8345 let (color, icon) = {
8346 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8347 (false, false) => ui::IconName::DebugBreakpoint,
8348 (true, false) => ui::IconName::DebugLogBreakpoint,
8349 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8350 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8351 };
8352
8353 let color = if is_phantom {
8354 Color::Hint
8355 } else if is_rejected {
8356 Color::Disabled
8357 } else {
8358 Color::Debugger
8359 };
8360
8361 (color, icon)
8362 };
8363
8364 let breakpoint = Arc::from(breakpoint.clone());
8365
8366 let alt_as_text = gpui::Keystroke {
8367 modifiers: Modifiers::secondary_key(),
8368 ..Default::default()
8369 };
8370 let primary_action_text = if breakpoint.is_disabled() {
8371 "Enable breakpoint"
8372 } else if is_phantom && !collides_with_existing {
8373 "Set breakpoint"
8374 } else {
8375 "Unset breakpoint"
8376 };
8377 let focus_handle = self.focus_handle.clone();
8378
8379 let meta = if is_rejected {
8380 SharedString::from("No executable code is associated with this line.")
8381 } else if collides_with_existing && !breakpoint.is_disabled() {
8382 SharedString::from(format!(
8383 "{alt_as_text}-click to disable,\nright-click for more options."
8384 ))
8385 } else {
8386 SharedString::from("Right-click for more options.")
8387 };
8388 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8389 .icon_size(IconSize::XSmall)
8390 .size(ui::ButtonSize::None)
8391 .when(is_rejected, |this| {
8392 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8393 })
8394 .icon_color(color)
8395 .style(ButtonStyle::Transparent)
8396 .on_click(cx.listener({
8397 move |editor, event: &ClickEvent, window, cx| {
8398 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8399 BreakpointEditAction::InvertState
8400 } else {
8401 BreakpointEditAction::Toggle
8402 };
8403
8404 window.focus(&editor.focus_handle(cx));
8405 editor.edit_breakpoint_at_anchor(
8406 position,
8407 breakpoint.as_ref().clone(),
8408 edit_action,
8409 cx,
8410 );
8411 }
8412 }))
8413 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8414 editor.set_breakpoint_context_menu(
8415 row,
8416 Some(position),
8417 event.position(),
8418 window,
8419 cx,
8420 );
8421 }))
8422 .tooltip(move |window, cx| {
8423 Tooltip::with_meta_in(
8424 primary_action_text,
8425 Some(&ToggleBreakpoint),
8426 meta.clone(),
8427 &focus_handle,
8428 window,
8429 cx,
8430 )
8431 })
8432 }
8433
8434 fn build_tasks_context(
8435 project: &Entity<Project>,
8436 buffer: &Entity<Buffer>,
8437 buffer_row: u32,
8438 tasks: &Arc<RunnableTasks>,
8439 cx: &mut Context<Self>,
8440 ) -> Task<Option<task::TaskContext>> {
8441 let position = Point::new(buffer_row, tasks.column);
8442 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8443 let location = Location {
8444 buffer: buffer.clone(),
8445 range: range_start..range_start,
8446 };
8447 // Fill in the environmental variables from the tree-sitter captures
8448 let mut captured_task_variables = TaskVariables::default();
8449 for (capture_name, value) in tasks.extra_variables.clone() {
8450 captured_task_variables.insert(
8451 task::VariableName::Custom(capture_name.into()),
8452 value.clone(),
8453 );
8454 }
8455 project.update(cx, |project, cx| {
8456 project.task_store().update(cx, |task_store, cx| {
8457 task_store.task_context_for_location(captured_task_variables, location, cx)
8458 })
8459 })
8460 }
8461
8462 pub fn spawn_nearest_task(
8463 &mut self,
8464 action: &SpawnNearestTask,
8465 window: &mut Window,
8466 cx: &mut Context<Self>,
8467 ) {
8468 let Some((workspace, _)) = self.workspace.clone() else {
8469 return;
8470 };
8471 let Some(project) = self.project.clone() else {
8472 return;
8473 };
8474
8475 // Try to find a closest, enclosing node using tree-sitter that has a task
8476 let Some((buffer, buffer_row, tasks)) = self
8477 .find_enclosing_node_task(cx)
8478 // Or find the task that's closest in row-distance.
8479 .or_else(|| self.find_closest_task(cx))
8480 else {
8481 return;
8482 };
8483
8484 let reveal_strategy = action.reveal;
8485 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8486 cx.spawn_in(window, async move |_, cx| {
8487 let context = task_context.await?;
8488 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8489
8490 let resolved = &mut resolved_task.resolved;
8491 resolved.reveal = reveal_strategy;
8492
8493 workspace
8494 .update_in(cx, |workspace, window, cx| {
8495 workspace.schedule_resolved_task(
8496 task_source_kind,
8497 resolved_task,
8498 false,
8499 window,
8500 cx,
8501 );
8502 })
8503 .ok()
8504 })
8505 .detach();
8506 }
8507
8508 fn find_closest_task(
8509 &mut self,
8510 cx: &mut Context<Self>,
8511 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8512 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8513
8514 let ((buffer_id, row), tasks) = self
8515 .tasks
8516 .iter()
8517 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8518
8519 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8520 let tasks = Arc::new(tasks.to_owned());
8521 Some((buffer, *row, tasks))
8522 }
8523
8524 fn find_enclosing_node_task(
8525 &mut self,
8526 cx: &mut Context<Self>,
8527 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8528 let snapshot = self.buffer.read(cx).snapshot(cx);
8529 let offset = self.selections.newest::<usize>(cx).head();
8530 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8531 let buffer_id = excerpt.buffer().remote_id();
8532
8533 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8534 let mut cursor = layer.node().walk();
8535
8536 while cursor.goto_first_child_for_byte(offset).is_some() {
8537 if cursor.node().end_byte() == offset {
8538 cursor.goto_next_sibling();
8539 }
8540 }
8541
8542 // Ascend to the smallest ancestor that contains the range and has a task.
8543 loop {
8544 let node = cursor.node();
8545 let node_range = node.byte_range();
8546 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8547
8548 // Check if this node contains our offset
8549 if node_range.start <= offset && node_range.end >= offset {
8550 // If it contains offset, check for task
8551 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8552 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8553 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8554 }
8555 }
8556
8557 if !cursor.goto_parent() {
8558 break;
8559 }
8560 }
8561 None
8562 }
8563
8564 fn render_run_indicator(
8565 &self,
8566 _style: &EditorStyle,
8567 is_active: bool,
8568 row: DisplayRow,
8569 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8570 cx: &mut Context<Self>,
8571 ) -> IconButton {
8572 let color = Color::Muted;
8573 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8574
8575 IconButton::new(
8576 ("run_indicator", row.0 as usize),
8577 ui::IconName::PlayOutlined,
8578 )
8579 .shape(ui::IconButtonShape::Square)
8580 .icon_size(IconSize::XSmall)
8581 .icon_color(color)
8582 .toggle_state(is_active)
8583 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8584 let quick_launch = match e {
8585 ClickEvent::Keyboard(_) => true,
8586 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8587 };
8588
8589 window.focus(&editor.focus_handle(cx));
8590 editor.toggle_code_actions(
8591 &ToggleCodeActions {
8592 deployed_from: Some(CodeActionSource::RunMenu(row)),
8593 quick_launch,
8594 },
8595 window,
8596 cx,
8597 );
8598 }))
8599 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8600 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8601 }))
8602 }
8603
8604 pub fn context_menu_visible(&self) -> bool {
8605 !self.edit_prediction_preview_is_active()
8606 && self
8607 .context_menu
8608 .borrow()
8609 .as_ref()
8610 .is_some_and(|menu| menu.visible())
8611 }
8612
8613 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8614 self.context_menu
8615 .borrow()
8616 .as_ref()
8617 .map(|menu| menu.origin())
8618 }
8619
8620 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8621 self.context_menu_options = Some(options);
8622 }
8623
8624 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8625 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8626
8627 fn render_edit_prediction_popover(
8628 &mut self,
8629 text_bounds: &Bounds<Pixels>,
8630 content_origin: gpui::Point<Pixels>,
8631 right_margin: Pixels,
8632 editor_snapshot: &EditorSnapshot,
8633 visible_row_range: Range<DisplayRow>,
8634 scroll_top: f32,
8635 scroll_bottom: f32,
8636 line_layouts: &[LineWithInvisibles],
8637 line_height: Pixels,
8638 scroll_pixel_position: gpui::Point<Pixels>,
8639 newest_selection_head: Option<DisplayPoint>,
8640 editor_width: Pixels,
8641 style: &EditorStyle,
8642 window: &mut Window,
8643 cx: &mut App,
8644 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8645 if self.mode().is_minimap() {
8646 return None;
8647 }
8648 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8649
8650 if self.edit_prediction_visible_in_cursor_popover(true) {
8651 return None;
8652 }
8653
8654 match &active_edit_prediction.completion {
8655 EditPrediction::MoveWithin { target, .. } => {
8656 let target_display_point = target.to_display_point(editor_snapshot);
8657
8658 if self.edit_prediction_requires_modifier() {
8659 if !self.edit_prediction_preview_is_active() {
8660 return None;
8661 }
8662
8663 self.render_edit_prediction_modifier_jump_popover(
8664 text_bounds,
8665 content_origin,
8666 visible_row_range,
8667 line_layouts,
8668 line_height,
8669 scroll_pixel_position,
8670 newest_selection_head,
8671 target_display_point,
8672 window,
8673 cx,
8674 )
8675 } else {
8676 self.render_edit_prediction_eager_jump_popover(
8677 text_bounds,
8678 content_origin,
8679 editor_snapshot,
8680 visible_row_range,
8681 scroll_top,
8682 scroll_bottom,
8683 line_height,
8684 scroll_pixel_position,
8685 target_display_point,
8686 editor_width,
8687 window,
8688 cx,
8689 )
8690 }
8691 }
8692 EditPrediction::Edit {
8693 display_mode: EditDisplayMode::Inline,
8694 ..
8695 } => None,
8696 EditPrediction::Edit {
8697 display_mode: EditDisplayMode::TabAccept,
8698 edits,
8699 ..
8700 } => {
8701 let range = &edits.first()?.0;
8702 let target_display_point = range.end.to_display_point(editor_snapshot);
8703
8704 self.render_edit_prediction_end_of_line_popover(
8705 "Accept",
8706 editor_snapshot,
8707 visible_row_range,
8708 target_display_point,
8709 line_height,
8710 scroll_pixel_position,
8711 content_origin,
8712 editor_width,
8713 window,
8714 cx,
8715 )
8716 }
8717 EditPrediction::Edit {
8718 edits,
8719 edit_preview,
8720 display_mode: EditDisplayMode::DiffPopover,
8721 snapshot,
8722 } => self.render_edit_prediction_diff_popover(
8723 text_bounds,
8724 content_origin,
8725 right_margin,
8726 editor_snapshot,
8727 visible_row_range,
8728 line_layouts,
8729 line_height,
8730 scroll_pixel_position,
8731 newest_selection_head,
8732 editor_width,
8733 style,
8734 edits,
8735 edit_preview,
8736 snapshot,
8737 window,
8738 cx,
8739 ),
8740 EditPrediction::MoveOutside { snapshot, .. } => {
8741 let file_name = snapshot
8742 .file()
8743 .map(|file| file.file_name(cx))
8744 .unwrap_or("untitled");
8745 let mut element = self
8746 .render_edit_prediction_line_popover(
8747 format!("Jump to {file_name}"),
8748 Some(IconName::ZedPredict),
8749 window,
8750 cx,
8751 )
8752 .into_any();
8753
8754 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8755 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8756 let origin_y = text_bounds.size.height - size.height - px(30.);
8757 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8758 element.prepaint_at(origin, window, cx);
8759
8760 Some((element, origin))
8761 }
8762 }
8763 }
8764
8765 fn render_edit_prediction_modifier_jump_popover(
8766 &mut self,
8767 text_bounds: &Bounds<Pixels>,
8768 content_origin: gpui::Point<Pixels>,
8769 visible_row_range: Range<DisplayRow>,
8770 line_layouts: &[LineWithInvisibles],
8771 line_height: Pixels,
8772 scroll_pixel_position: gpui::Point<Pixels>,
8773 newest_selection_head: Option<DisplayPoint>,
8774 target_display_point: DisplayPoint,
8775 window: &mut Window,
8776 cx: &mut App,
8777 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8778 let scrolled_content_origin =
8779 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8780
8781 const SCROLL_PADDING_Y: Pixels = px(12.);
8782
8783 if target_display_point.row() < visible_row_range.start {
8784 return self.render_edit_prediction_scroll_popover(
8785 |_| SCROLL_PADDING_Y,
8786 IconName::ArrowUp,
8787 visible_row_range,
8788 line_layouts,
8789 newest_selection_head,
8790 scrolled_content_origin,
8791 window,
8792 cx,
8793 );
8794 } else if target_display_point.row() >= visible_row_range.end {
8795 return self.render_edit_prediction_scroll_popover(
8796 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8797 IconName::ArrowDown,
8798 visible_row_range,
8799 line_layouts,
8800 newest_selection_head,
8801 scrolled_content_origin,
8802 window,
8803 cx,
8804 );
8805 }
8806
8807 const POLE_WIDTH: Pixels = px(2.);
8808
8809 let line_layout =
8810 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8811 let target_column = target_display_point.column() as usize;
8812
8813 let target_x = line_layout.x_for_index(target_column);
8814 let target_y =
8815 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8816
8817 let flag_on_right = target_x < text_bounds.size.width / 2.;
8818
8819 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8820 border_color.l += 0.001;
8821
8822 let mut element = v_flex()
8823 .items_end()
8824 .when(flag_on_right, |el| el.items_start())
8825 .child(if flag_on_right {
8826 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8827 .rounded_bl(px(0.))
8828 .rounded_tl(px(0.))
8829 .border_l_2()
8830 .border_color(border_color)
8831 } else {
8832 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8833 .rounded_br(px(0.))
8834 .rounded_tr(px(0.))
8835 .border_r_2()
8836 .border_color(border_color)
8837 })
8838 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8839 .into_any();
8840
8841 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8842
8843 let mut origin = scrolled_content_origin + point(target_x, target_y)
8844 - point(
8845 if flag_on_right {
8846 POLE_WIDTH
8847 } else {
8848 size.width - POLE_WIDTH
8849 },
8850 size.height - line_height,
8851 );
8852
8853 origin.x = origin.x.max(content_origin.x);
8854
8855 element.prepaint_at(origin, window, cx);
8856
8857 Some((element, origin))
8858 }
8859
8860 fn render_edit_prediction_scroll_popover(
8861 &mut self,
8862 to_y: impl Fn(Size<Pixels>) -> Pixels,
8863 scroll_icon: IconName,
8864 visible_row_range: Range<DisplayRow>,
8865 line_layouts: &[LineWithInvisibles],
8866 newest_selection_head: Option<DisplayPoint>,
8867 scrolled_content_origin: gpui::Point<Pixels>,
8868 window: &mut Window,
8869 cx: &mut App,
8870 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8871 let mut element = self
8872 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8873 .into_any();
8874
8875 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8876
8877 let cursor = newest_selection_head?;
8878 let cursor_row_layout =
8879 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8880 let cursor_column = cursor.column() as usize;
8881
8882 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8883
8884 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8885
8886 element.prepaint_at(origin, window, cx);
8887 Some((element, origin))
8888 }
8889
8890 fn render_edit_prediction_eager_jump_popover(
8891 &mut self,
8892 text_bounds: &Bounds<Pixels>,
8893 content_origin: gpui::Point<Pixels>,
8894 editor_snapshot: &EditorSnapshot,
8895 visible_row_range: Range<DisplayRow>,
8896 scroll_top: f32,
8897 scroll_bottom: f32,
8898 line_height: Pixels,
8899 scroll_pixel_position: gpui::Point<Pixels>,
8900 target_display_point: DisplayPoint,
8901 editor_width: Pixels,
8902 window: &mut Window,
8903 cx: &mut App,
8904 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8905 if target_display_point.row().as_f32() < scroll_top {
8906 let mut element = self
8907 .render_edit_prediction_line_popover(
8908 "Jump to Edit",
8909 Some(IconName::ArrowUp),
8910 window,
8911 cx,
8912 )
8913 .into_any();
8914
8915 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8916 let offset = point(
8917 (text_bounds.size.width - size.width) / 2.,
8918 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8919 );
8920
8921 let origin = text_bounds.origin + offset;
8922 element.prepaint_at(origin, window, cx);
8923 Some((element, origin))
8924 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8925 let mut element = self
8926 .render_edit_prediction_line_popover(
8927 "Jump to Edit",
8928 Some(IconName::ArrowDown),
8929 window,
8930 cx,
8931 )
8932 .into_any();
8933
8934 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8935 let offset = point(
8936 (text_bounds.size.width - size.width) / 2.,
8937 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8938 );
8939
8940 let origin = text_bounds.origin + offset;
8941 element.prepaint_at(origin, window, cx);
8942 Some((element, origin))
8943 } else {
8944 self.render_edit_prediction_end_of_line_popover(
8945 "Jump to Edit",
8946 editor_snapshot,
8947 visible_row_range,
8948 target_display_point,
8949 line_height,
8950 scroll_pixel_position,
8951 content_origin,
8952 editor_width,
8953 window,
8954 cx,
8955 )
8956 }
8957 }
8958
8959 fn render_edit_prediction_end_of_line_popover(
8960 self: &mut Editor,
8961 label: &'static str,
8962 editor_snapshot: &EditorSnapshot,
8963 visible_row_range: Range<DisplayRow>,
8964 target_display_point: DisplayPoint,
8965 line_height: Pixels,
8966 scroll_pixel_position: gpui::Point<Pixels>,
8967 content_origin: gpui::Point<Pixels>,
8968 editor_width: Pixels,
8969 window: &mut Window,
8970 cx: &mut App,
8971 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8972 let target_line_end = DisplayPoint::new(
8973 target_display_point.row(),
8974 editor_snapshot.line_len(target_display_point.row()),
8975 );
8976
8977 let mut element = self
8978 .render_edit_prediction_line_popover(label, None, window, cx)
8979 .into_any();
8980
8981 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8982
8983 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8984
8985 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8986 let mut origin = start_point
8987 + line_origin
8988 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8989 origin.x = origin.x.max(content_origin.x);
8990
8991 let max_x = content_origin.x + editor_width - size.width;
8992
8993 if origin.x > max_x {
8994 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8995
8996 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8997 origin.y += offset;
8998 IconName::ArrowUp
8999 } else {
9000 origin.y -= offset;
9001 IconName::ArrowDown
9002 };
9003
9004 element = self
9005 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9006 .into_any();
9007
9008 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9009
9010 origin.x = content_origin.x + editor_width - size.width - px(2.);
9011 }
9012
9013 element.prepaint_at(origin, window, cx);
9014 Some((element, origin))
9015 }
9016
9017 fn render_edit_prediction_diff_popover(
9018 self: &Editor,
9019 text_bounds: &Bounds<Pixels>,
9020 content_origin: gpui::Point<Pixels>,
9021 right_margin: Pixels,
9022 editor_snapshot: &EditorSnapshot,
9023 visible_row_range: Range<DisplayRow>,
9024 line_layouts: &[LineWithInvisibles],
9025 line_height: Pixels,
9026 scroll_pixel_position: gpui::Point<Pixels>,
9027 newest_selection_head: Option<DisplayPoint>,
9028 editor_width: Pixels,
9029 style: &EditorStyle,
9030 edits: &Vec<(Range<Anchor>, String)>,
9031 edit_preview: &Option<language::EditPreview>,
9032 snapshot: &language::BufferSnapshot,
9033 window: &mut Window,
9034 cx: &mut App,
9035 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9036 let edit_start = edits
9037 .first()
9038 .unwrap()
9039 .0
9040 .start
9041 .to_display_point(editor_snapshot);
9042 let edit_end = edits
9043 .last()
9044 .unwrap()
9045 .0
9046 .end
9047 .to_display_point(editor_snapshot);
9048
9049 let is_visible = visible_row_range.contains(&edit_start.row())
9050 || visible_row_range.contains(&edit_end.row());
9051 if !is_visible {
9052 return None;
9053 }
9054
9055 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9056 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9057 } else {
9058 // Fallback for providers without edit_preview
9059 crate::edit_prediction_fallback_text(edits, cx)
9060 };
9061
9062 let styled_text = highlighted_edits.to_styled_text(&style.text);
9063 let line_count = highlighted_edits.text.lines().count();
9064
9065 const BORDER_WIDTH: Pixels = px(1.);
9066
9067 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9068 let has_keybind = keybind.is_some();
9069
9070 let mut element = h_flex()
9071 .items_start()
9072 .child(
9073 h_flex()
9074 .bg(cx.theme().colors().editor_background)
9075 .border(BORDER_WIDTH)
9076 .shadow_xs()
9077 .border_color(cx.theme().colors().border)
9078 .rounded_l_lg()
9079 .when(line_count > 1, |el| el.rounded_br_lg())
9080 .pr_1()
9081 .child(styled_text),
9082 )
9083 .child(
9084 h_flex()
9085 .h(line_height + BORDER_WIDTH * 2.)
9086 .px_1p5()
9087 .gap_1()
9088 // Workaround: For some reason, there's a gap if we don't do this
9089 .ml(-BORDER_WIDTH)
9090 .shadow(vec![gpui::BoxShadow {
9091 color: gpui::black().opacity(0.05),
9092 offset: point(px(1.), px(1.)),
9093 blur_radius: px(2.),
9094 spread_radius: px(0.),
9095 }])
9096 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9097 .border(BORDER_WIDTH)
9098 .border_color(cx.theme().colors().border)
9099 .rounded_r_lg()
9100 .id("edit_prediction_diff_popover_keybind")
9101 .when(!has_keybind, |el| {
9102 let status_colors = cx.theme().status();
9103
9104 el.bg(status_colors.error_background)
9105 .border_color(status_colors.error.opacity(0.6))
9106 .child(Icon::new(IconName::Info).color(Color::Error))
9107 .cursor_default()
9108 .hoverable_tooltip(move |_window, cx| {
9109 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9110 })
9111 })
9112 .children(keybind),
9113 )
9114 .into_any();
9115
9116 let longest_row =
9117 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9118 let longest_line_width = if visible_row_range.contains(&longest_row) {
9119 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9120 } else {
9121 layout_line(
9122 longest_row,
9123 editor_snapshot,
9124 style,
9125 editor_width,
9126 |_| false,
9127 window,
9128 cx,
9129 )
9130 .width
9131 };
9132
9133 let viewport_bounds =
9134 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9135 right: -right_margin,
9136 ..Default::default()
9137 });
9138
9139 let x_after_longest =
9140 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9141 - scroll_pixel_position.x;
9142
9143 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9144
9145 // Fully visible if it can be displayed within the window (allow overlapping other
9146 // panes). However, this is only allowed if the popover starts within text_bounds.
9147 let can_position_to_the_right = x_after_longest < text_bounds.right()
9148 && x_after_longest + element_bounds.width < viewport_bounds.right();
9149
9150 let mut origin = if can_position_to_the_right {
9151 point(
9152 x_after_longest,
9153 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9154 - scroll_pixel_position.y,
9155 )
9156 } else {
9157 let cursor_row = newest_selection_head.map(|head| head.row());
9158 let above_edit = edit_start
9159 .row()
9160 .0
9161 .checked_sub(line_count as u32)
9162 .map(DisplayRow);
9163 let below_edit = Some(edit_end.row() + 1);
9164 let above_cursor =
9165 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9166 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9167
9168 // Place the edit popover adjacent to the edit if there is a location
9169 // available that is onscreen and does not obscure the cursor. Otherwise,
9170 // place it adjacent to the cursor.
9171 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9172 .into_iter()
9173 .flatten()
9174 .find(|&start_row| {
9175 let end_row = start_row + line_count as u32;
9176 visible_row_range.contains(&start_row)
9177 && visible_row_range.contains(&end_row)
9178 && cursor_row
9179 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9180 })?;
9181
9182 content_origin
9183 + point(
9184 -scroll_pixel_position.x,
9185 row_target.as_f32() * line_height - scroll_pixel_position.y,
9186 )
9187 };
9188
9189 origin.x -= BORDER_WIDTH;
9190
9191 window.defer_draw(element, origin, 1);
9192
9193 // Do not return an element, since it will already be drawn due to defer_draw.
9194 None
9195 }
9196
9197 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9198 px(30.)
9199 }
9200
9201 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9202 if self.read_only(cx) {
9203 cx.theme().players().read_only()
9204 } else {
9205 self.style.as_ref().unwrap().local_player
9206 }
9207 }
9208
9209 fn render_edit_prediction_accept_keybind(
9210 &self,
9211 window: &mut Window,
9212 cx: &App,
9213 ) -> Option<AnyElement> {
9214 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9215 let accept_keystroke = accept_binding.keystroke()?;
9216
9217 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9218
9219 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9220 Color::Accent
9221 } else {
9222 Color::Muted
9223 };
9224
9225 h_flex()
9226 .px_0p5()
9227 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9228 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9229 .text_size(TextSize::XSmall.rems(cx))
9230 .child(h_flex().children(ui::render_modifiers(
9231 accept_keystroke.modifiers(),
9232 PlatformStyle::platform(),
9233 Some(modifiers_color),
9234 Some(IconSize::XSmall.rems().into()),
9235 true,
9236 )))
9237 .when(is_platform_style_mac, |parent| {
9238 parent.child(accept_keystroke.key().to_string())
9239 })
9240 .when(!is_platform_style_mac, |parent| {
9241 parent.child(
9242 Key::new(
9243 util::capitalize(accept_keystroke.key()),
9244 Some(Color::Default),
9245 )
9246 .size(Some(IconSize::XSmall.rems().into())),
9247 )
9248 })
9249 .into_any()
9250 .into()
9251 }
9252
9253 fn render_edit_prediction_line_popover(
9254 &self,
9255 label: impl Into<SharedString>,
9256 icon: Option<IconName>,
9257 window: &mut Window,
9258 cx: &App,
9259 ) -> Stateful<Div> {
9260 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9261
9262 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9263 let has_keybind = keybind.is_some();
9264
9265 h_flex()
9266 .id("ep-line-popover")
9267 .py_0p5()
9268 .pl_1()
9269 .pr(padding_right)
9270 .gap_1()
9271 .rounded_md()
9272 .border_1()
9273 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9274 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9275 .shadow_xs()
9276 .when(!has_keybind, |el| {
9277 let status_colors = cx.theme().status();
9278
9279 el.bg(status_colors.error_background)
9280 .border_color(status_colors.error.opacity(0.6))
9281 .pl_2()
9282 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9283 .cursor_default()
9284 .hoverable_tooltip(move |_window, cx| {
9285 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9286 })
9287 })
9288 .children(keybind)
9289 .child(
9290 Label::new(label)
9291 .size(LabelSize::Small)
9292 .when(!has_keybind, |el| {
9293 el.color(cx.theme().status().error.into()).strikethrough()
9294 }),
9295 )
9296 .when(!has_keybind, |el| {
9297 el.child(
9298 h_flex().ml_1().child(
9299 Icon::new(IconName::Info)
9300 .size(IconSize::Small)
9301 .color(cx.theme().status().error.into()),
9302 ),
9303 )
9304 })
9305 .when_some(icon, |element, icon| {
9306 element.child(
9307 div()
9308 .mt(px(1.5))
9309 .child(Icon::new(icon).size(IconSize::Small)),
9310 )
9311 })
9312 }
9313
9314 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9315 let accent_color = cx.theme().colors().text_accent;
9316 let editor_bg_color = cx.theme().colors().editor_background;
9317 editor_bg_color.blend(accent_color.opacity(0.1))
9318 }
9319
9320 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9321 let accent_color = cx.theme().colors().text_accent;
9322 let editor_bg_color = cx.theme().colors().editor_background;
9323 editor_bg_color.blend(accent_color.opacity(0.6))
9324 }
9325 fn get_prediction_provider_icon_name(
9326 provider: &Option<RegisteredEditPredictionProvider>,
9327 ) -> IconName {
9328 match provider {
9329 Some(provider) => match provider.provider.name() {
9330 "copilot" => IconName::Copilot,
9331 "supermaven" => IconName::Supermaven,
9332 _ => IconName::ZedPredict,
9333 },
9334 None => IconName::ZedPredict,
9335 }
9336 }
9337
9338 fn render_edit_prediction_cursor_popover(
9339 &self,
9340 min_width: Pixels,
9341 max_width: Pixels,
9342 cursor_point: Point,
9343 style: &EditorStyle,
9344 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9345 _window: &Window,
9346 cx: &mut Context<Editor>,
9347 ) -> Option<AnyElement> {
9348 let provider = self.edit_prediction_provider.as_ref()?;
9349 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9350
9351 let is_refreshing = provider.provider.is_refreshing(cx);
9352
9353 fn pending_completion_container(icon: IconName) -> Div {
9354 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9355 }
9356
9357 let completion = match &self.active_edit_prediction {
9358 Some(prediction) => {
9359 if !self.has_visible_completions_menu() {
9360 const RADIUS: Pixels = px(6.);
9361 const BORDER_WIDTH: Pixels = px(1.);
9362
9363 return Some(
9364 h_flex()
9365 .elevation_2(cx)
9366 .border(BORDER_WIDTH)
9367 .border_color(cx.theme().colors().border)
9368 .when(accept_keystroke.is_none(), |el| {
9369 el.border_color(cx.theme().status().error)
9370 })
9371 .rounded(RADIUS)
9372 .rounded_tl(px(0.))
9373 .overflow_hidden()
9374 .child(div().px_1p5().child(match &prediction.completion {
9375 EditPrediction::MoveWithin { target, snapshot } => {
9376 use text::ToPoint as _;
9377 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9378 {
9379 Icon::new(IconName::ZedPredictDown)
9380 } else {
9381 Icon::new(IconName::ZedPredictUp)
9382 }
9383 }
9384 EditPrediction::MoveOutside { .. } => {
9385 // TODO [zeta2] custom icon for external jump?
9386 Icon::new(provider_icon)
9387 }
9388 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9389 }))
9390 .child(
9391 h_flex()
9392 .gap_1()
9393 .py_1()
9394 .px_2()
9395 .rounded_r(RADIUS - BORDER_WIDTH)
9396 .border_l_1()
9397 .border_color(cx.theme().colors().border)
9398 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9399 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9400 el.child(
9401 Label::new("Hold")
9402 .size(LabelSize::Small)
9403 .when(accept_keystroke.is_none(), |el| {
9404 el.strikethrough()
9405 })
9406 .line_height_style(LineHeightStyle::UiLabel),
9407 )
9408 })
9409 .id("edit_prediction_cursor_popover_keybind")
9410 .when(accept_keystroke.is_none(), |el| {
9411 let status_colors = cx.theme().status();
9412
9413 el.bg(status_colors.error_background)
9414 .border_color(status_colors.error.opacity(0.6))
9415 .child(Icon::new(IconName::Info).color(Color::Error))
9416 .cursor_default()
9417 .hoverable_tooltip(move |_window, cx| {
9418 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9419 .into()
9420 })
9421 })
9422 .when_some(
9423 accept_keystroke.as_ref(),
9424 |el, accept_keystroke| {
9425 el.child(h_flex().children(ui::render_modifiers(
9426 accept_keystroke.modifiers(),
9427 PlatformStyle::platform(),
9428 Some(Color::Default),
9429 Some(IconSize::XSmall.rems().into()),
9430 false,
9431 )))
9432 },
9433 ),
9434 )
9435 .into_any(),
9436 );
9437 }
9438
9439 self.render_edit_prediction_cursor_popover_preview(
9440 prediction,
9441 cursor_point,
9442 style,
9443 cx,
9444 )?
9445 }
9446
9447 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9448 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9449 stale_completion,
9450 cursor_point,
9451 style,
9452 cx,
9453 )?,
9454
9455 None => pending_completion_container(provider_icon)
9456 .child(Label::new("...").size(LabelSize::Small)),
9457 },
9458
9459 None => pending_completion_container(provider_icon)
9460 .child(Label::new("...").size(LabelSize::Small)),
9461 };
9462
9463 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9464 completion
9465 .with_animation(
9466 "loading-completion",
9467 Animation::new(Duration::from_secs(2))
9468 .repeat()
9469 .with_easing(pulsating_between(0.4, 0.8)),
9470 |label, delta| label.opacity(delta),
9471 )
9472 .into_any_element()
9473 } else {
9474 completion.into_any_element()
9475 };
9476
9477 let has_completion = self.active_edit_prediction.is_some();
9478
9479 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9480 Some(
9481 h_flex()
9482 .min_w(min_width)
9483 .max_w(max_width)
9484 .flex_1()
9485 .elevation_2(cx)
9486 .border_color(cx.theme().colors().border)
9487 .child(
9488 div()
9489 .flex_1()
9490 .py_1()
9491 .px_2()
9492 .overflow_hidden()
9493 .child(completion),
9494 )
9495 .when_some(accept_keystroke, |el, accept_keystroke| {
9496 if !accept_keystroke.modifiers().modified() {
9497 return el;
9498 }
9499
9500 el.child(
9501 h_flex()
9502 .h_full()
9503 .border_l_1()
9504 .rounded_r_lg()
9505 .border_color(cx.theme().colors().border)
9506 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9507 .gap_1()
9508 .py_1()
9509 .px_2()
9510 .child(
9511 h_flex()
9512 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9513 .when(is_platform_style_mac, |parent| parent.gap_1())
9514 .child(h_flex().children(ui::render_modifiers(
9515 accept_keystroke.modifiers(),
9516 PlatformStyle::platform(),
9517 Some(if !has_completion {
9518 Color::Muted
9519 } else {
9520 Color::Default
9521 }),
9522 None,
9523 false,
9524 ))),
9525 )
9526 .child(Label::new("Preview").into_any_element())
9527 .opacity(if has_completion { 1.0 } else { 0.4 }),
9528 )
9529 })
9530 .into_any(),
9531 )
9532 }
9533
9534 fn render_edit_prediction_cursor_popover_preview(
9535 &self,
9536 completion: &EditPredictionState,
9537 cursor_point: Point,
9538 style: &EditorStyle,
9539 cx: &mut Context<Editor>,
9540 ) -> Option<Div> {
9541 use text::ToPoint as _;
9542
9543 fn render_relative_row_jump(
9544 prefix: impl Into<String>,
9545 current_row: u32,
9546 target_row: u32,
9547 ) -> Div {
9548 let (row_diff, arrow) = if target_row < current_row {
9549 (current_row - target_row, IconName::ArrowUp)
9550 } else {
9551 (target_row - current_row, IconName::ArrowDown)
9552 };
9553
9554 h_flex()
9555 .child(
9556 Label::new(format!("{}{}", prefix.into(), row_diff))
9557 .color(Color::Muted)
9558 .size(LabelSize::Small),
9559 )
9560 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9561 }
9562
9563 let supports_jump = self
9564 .edit_prediction_provider
9565 .as_ref()
9566 .map(|provider| provider.provider.supports_jump_to_edit())
9567 .unwrap_or(true);
9568
9569 match &completion.completion {
9570 EditPrediction::MoveWithin {
9571 target, snapshot, ..
9572 } => {
9573 if !supports_jump {
9574 return None;
9575 }
9576
9577 Some(
9578 h_flex()
9579 .px_2()
9580 .gap_2()
9581 .flex_1()
9582 .child(
9583 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9584 Icon::new(IconName::ZedPredictDown)
9585 } else {
9586 Icon::new(IconName::ZedPredictUp)
9587 },
9588 )
9589 .child(Label::new("Jump to Edit")),
9590 )
9591 }
9592 EditPrediction::MoveOutside { snapshot, .. } => {
9593 let file_name = snapshot
9594 .file()
9595 .map(|file| file.file_name(cx))
9596 .unwrap_or("untitled");
9597 Some(
9598 h_flex()
9599 .px_2()
9600 .gap_2()
9601 .flex_1()
9602 .child(Icon::new(IconName::ZedPredict))
9603 .child(Label::new(format!("Jump to {file_name}"))),
9604 )
9605 }
9606 EditPrediction::Edit {
9607 edits,
9608 edit_preview,
9609 snapshot,
9610 display_mode: _,
9611 } => {
9612 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9613
9614 let (highlighted_edits, has_more_lines) =
9615 if let Some(edit_preview) = edit_preview.as_ref() {
9616 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9617 .first_line_preview()
9618 } else {
9619 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9620 };
9621
9622 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9623 .with_default_highlights(&style.text, highlighted_edits.highlights);
9624
9625 let preview = h_flex()
9626 .gap_1()
9627 .min_w_16()
9628 .child(styled_text)
9629 .when(has_more_lines, |parent| parent.child("…"));
9630
9631 let left = if supports_jump && first_edit_row != cursor_point.row {
9632 render_relative_row_jump("", cursor_point.row, first_edit_row)
9633 .into_any_element()
9634 } else {
9635 let icon_name =
9636 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9637 Icon::new(icon_name).into_any_element()
9638 };
9639
9640 Some(
9641 h_flex()
9642 .h_full()
9643 .flex_1()
9644 .gap_2()
9645 .pr_1()
9646 .overflow_x_hidden()
9647 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9648 .child(left)
9649 .child(preview),
9650 )
9651 }
9652 }
9653 }
9654
9655 pub fn render_context_menu(
9656 &self,
9657 style: &EditorStyle,
9658 max_height_in_lines: u32,
9659 window: &mut Window,
9660 cx: &mut Context<Editor>,
9661 ) -> Option<AnyElement> {
9662 let menu = self.context_menu.borrow();
9663 let menu = menu.as_ref()?;
9664 if !menu.visible() {
9665 return None;
9666 };
9667 Some(menu.render(style, max_height_in_lines, window, cx))
9668 }
9669
9670 fn render_context_menu_aside(
9671 &mut self,
9672 max_size: Size<Pixels>,
9673 window: &mut Window,
9674 cx: &mut Context<Editor>,
9675 ) -> Option<AnyElement> {
9676 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9677 if menu.visible() {
9678 menu.render_aside(max_size, window, cx)
9679 } else {
9680 None
9681 }
9682 })
9683 }
9684
9685 fn hide_context_menu(
9686 &mut self,
9687 window: &mut Window,
9688 cx: &mut Context<Self>,
9689 ) -> Option<CodeContextMenu> {
9690 cx.notify();
9691 self.completion_tasks.clear();
9692 let context_menu = self.context_menu.borrow_mut().take();
9693 self.stale_edit_prediction_in_menu.take();
9694 self.update_visible_edit_prediction(window, cx);
9695 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9696 && let Some(completion_provider) = &self.completion_provider
9697 {
9698 completion_provider.selection_changed(None, window, cx);
9699 }
9700 context_menu
9701 }
9702
9703 fn show_snippet_choices(
9704 &mut self,
9705 choices: &Vec<String>,
9706 selection: Range<Anchor>,
9707 cx: &mut Context<Self>,
9708 ) {
9709 let Some((_, buffer, _)) = self
9710 .buffer()
9711 .read(cx)
9712 .excerpt_containing(selection.start, cx)
9713 else {
9714 return;
9715 };
9716 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9717 else {
9718 return;
9719 };
9720 if buffer != end_buffer {
9721 log::error!("expected anchor range to have matching buffer IDs");
9722 return;
9723 }
9724
9725 let id = post_inc(&mut self.next_completion_id);
9726 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9727 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9728 CompletionsMenu::new_snippet_choices(
9729 id,
9730 true,
9731 choices,
9732 selection,
9733 buffer,
9734 snippet_sort_order,
9735 ),
9736 ));
9737 }
9738
9739 pub fn insert_snippet(
9740 &mut self,
9741 insertion_ranges: &[Range<usize>],
9742 snippet: Snippet,
9743 window: &mut Window,
9744 cx: &mut Context<Self>,
9745 ) -> Result<()> {
9746 struct Tabstop<T> {
9747 is_end_tabstop: bool,
9748 ranges: Vec<Range<T>>,
9749 choices: Option<Vec<String>>,
9750 }
9751
9752 let tabstops = self.buffer.update(cx, |buffer, cx| {
9753 let snippet_text: Arc<str> = snippet.text.clone().into();
9754 let edits = insertion_ranges
9755 .iter()
9756 .cloned()
9757 .map(|range| (range, snippet_text.clone()));
9758 let autoindent_mode = AutoindentMode::Block {
9759 original_indent_columns: Vec::new(),
9760 };
9761 buffer.edit(edits, Some(autoindent_mode), cx);
9762
9763 let snapshot = &*buffer.read(cx);
9764 let snippet = &snippet;
9765 snippet
9766 .tabstops
9767 .iter()
9768 .map(|tabstop| {
9769 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9770 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9771 });
9772 let mut tabstop_ranges = tabstop
9773 .ranges
9774 .iter()
9775 .flat_map(|tabstop_range| {
9776 let mut delta = 0_isize;
9777 insertion_ranges.iter().map(move |insertion_range| {
9778 let insertion_start = insertion_range.start as isize + delta;
9779 delta +=
9780 snippet.text.len() as isize - insertion_range.len() as isize;
9781
9782 let start = ((insertion_start + tabstop_range.start) as usize)
9783 .min(snapshot.len());
9784 let end = ((insertion_start + tabstop_range.end) as usize)
9785 .min(snapshot.len());
9786 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9787 })
9788 })
9789 .collect::<Vec<_>>();
9790 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9791
9792 Tabstop {
9793 is_end_tabstop,
9794 ranges: tabstop_ranges,
9795 choices: tabstop.choices.clone(),
9796 }
9797 })
9798 .collect::<Vec<_>>()
9799 });
9800 if let Some(tabstop) = tabstops.first() {
9801 self.change_selections(Default::default(), window, cx, |s| {
9802 // Reverse order so that the first range is the newest created selection.
9803 // Completions will use it and autoscroll will prioritize it.
9804 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9805 });
9806
9807 if let Some(choices) = &tabstop.choices
9808 && let Some(selection) = tabstop.ranges.first()
9809 {
9810 self.show_snippet_choices(choices, selection.clone(), cx)
9811 }
9812
9813 // If we're already at the last tabstop and it's at the end of the snippet,
9814 // we're done, we don't need to keep the state around.
9815 if !tabstop.is_end_tabstop {
9816 let choices = tabstops
9817 .iter()
9818 .map(|tabstop| tabstop.choices.clone())
9819 .collect();
9820
9821 let ranges = tabstops
9822 .into_iter()
9823 .map(|tabstop| tabstop.ranges)
9824 .collect::<Vec<_>>();
9825
9826 self.snippet_stack.push(SnippetState {
9827 active_index: 0,
9828 ranges,
9829 choices,
9830 });
9831 }
9832
9833 // Check whether the just-entered snippet ends with an auto-closable bracket.
9834 if self.autoclose_regions.is_empty() {
9835 let snapshot = self.buffer.read(cx).snapshot(cx);
9836 let mut all_selections = self.selections.all::<Point>(cx);
9837 for selection in &mut all_selections {
9838 let selection_head = selection.head();
9839 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9840 continue;
9841 };
9842
9843 let mut bracket_pair = None;
9844 let max_lookup_length = scope
9845 .brackets()
9846 .map(|(pair, _)| {
9847 pair.start
9848 .as_str()
9849 .chars()
9850 .count()
9851 .max(pair.end.as_str().chars().count())
9852 })
9853 .max();
9854 if let Some(max_lookup_length) = max_lookup_length {
9855 let next_text = snapshot
9856 .chars_at(selection_head)
9857 .take(max_lookup_length)
9858 .collect::<String>();
9859 let prev_text = snapshot
9860 .reversed_chars_at(selection_head)
9861 .take(max_lookup_length)
9862 .collect::<String>();
9863
9864 for (pair, enabled) in scope.brackets() {
9865 if enabled
9866 && pair.close
9867 && prev_text.starts_with(pair.start.as_str())
9868 && next_text.starts_with(pair.end.as_str())
9869 {
9870 bracket_pair = Some(pair.clone());
9871 break;
9872 }
9873 }
9874 }
9875
9876 if let Some(pair) = bracket_pair {
9877 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9878 let autoclose_enabled =
9879 self.use_autoclose && snapshot_settings.use_autoclose;
9880 if autoclose_enabled {
9881 let start = snapshot.anchor_after(selection_head);
9882 let end = snapshot.anchor_after(selection_head);
9883 self.autoclose_regions.push(AutocloseRegion {
9884 selection_id: selection.id,
9885 range: start..end,
9886 pair,
9887 });
9888 }
9889 }
9890 }
9891 }
9892 }
9893 Ok(())
9894 }
9895
9896 pub fn move_to_next_snippet_tabstop(
9897 &mut self,
9898 window: &mut Window,
9899 cx: &mut Context<Self>,
9900 ) -> bool {
9901 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9902 }
9903
9904 pub fn move_to_prev_snippet_tabstop(
9905 &mut self,
9906 window: &mut Window,
9907 cx: &mut Context<Self>,
9908 ) -> bool {
9909 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9910 }
9911
9912 pub fn move_to_snippet_tabstop(
9913 &mut self,
9914 bias: Bias,
9915 window: &mut Window,
9916 cx: &mut Context<Self>,
9917 ) -> bool {
9918 if let Some(mut snippet) = self.snippet_stack.pop() {
9919 match bias {
9920 Bias::Left => {
9921 if snippet.active_index > 0 {
9922 snippet.active_index -= 1;
9923 } else {
9924 self.snippet_stack.push(snippet);
9925 return false;
9926 }
9927 }
9928 Bias::Right => {
9929 if snippet.active_index + 1 < snippet.ranges.len() {
9930 snippet.active_index += 1;
9931 } else {
9932 self.snippet_stack.push(snippet);
9933 return false;
9934 }
9935 }
9936 }
9937 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9938 self.change_selections(Default::default(), window, cx, |s| {
9939 // Reverse order so that the first range is the newest created selection.
9940 // Completions will use it and autoscroll will prioritize it.
9941 s.select_ranges(current_ranges.iter().rev().cloned())
9942 });
9943
9944 if let Some(choices) = &snippet.choices[snippet.active_index]
9945 && let Some(selection) = current_ranges.first()
9946 {
9947 self.show_snippet_choices(choices, selection.clone(), cx);
9948 }
9949
9950 // If snippet state is not at the last tabstop, push it back on the stack
9951 if snippet.active_index + 1 < snippet.ranges.len() {
9952 self.snippet_stack.push(snippet);
9953 }
9954 return true;
9955 }
9956 }
9957
9958 false
9959 }
9960
9961 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9962 self.transact(window, cx, |this, window, cx| {
9963 this.select_all(&SelectAll, window, cx);
9964 this.insert("", window, cx);
9965 });
9966 }
9967
9968 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9969 if self.read_only(cx) {
9970 return;
9971 }
9972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9973 self.transact(window, cx, |this, window, cx| {
9974 this.select_autoclose_pair(window, cx);
9975 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9976 if !this.linked_edit_ranges.is_empty() {
9977 let selections = this.selections.all::<MultiBufferPoint>(cx);
9978 let snapshot = this.buffer.read(cx).snapshot(cx);
9979
9980 for selection in selections.iter() {
9981 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9982 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9983 if selection_start.buffer_id != selection_end.buffer_id {
9984 continue;
9985 }
9986 if let Some(ranges) =
9987 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9988 {
9989 for (buffer, entries) in ranges {
9990 linked_ranges.entry(buffer).or_default().extend(entries);
9991 }
9992 }
9993 }
9994 }
9995
9996 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9997 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9998 for selection in &mut selections {
9999 if selection.is_empty() {
10000 let old_head = selection.head();
10001 let mut new_head =
10002 movement::left(&display_map, old_head.to_display_point(&display_map))
10003 .to_point(&display_map);
10004 if let Some((buffer, line_buffer_range)) = display_map
10005 .buffer_snapshot
10006 .buffer_line_for_row(MultiBufferRow(old_head.row))
10007 {
10008 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10009 let indent_len = match indent_size.kind {
10010 IndentKind::Space => {
10011 buffer.settings_at(line_buffer_range.start, cx).tab_size
10012 }
10013 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10014 };
10015 if old_head.column <= indent_size.len && old_head.column > 0 {
10016 let indent_len = indent_len.get();
10017 new_head = cmp::min(
10018 new_head,
10019 MultiBufferPoint::new(
10020 old_head.row,
10021 ((old_head.column - 1) / indent_len) * indent_len,
10022 ),
10023 );
10024 }
10025 }
10026
10027 selection.set_head(new_head, SelectionGoal::None);
10028 }
10029 }
10030
10031 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10032 this.insert("", window, cx);
10033 let empty_str: Arc<str> = Arc::from("");
10034 for (buffer, edits) in linked_ranges {
10035 let snapshot = buffer.read(cx).snapshot();
10036 use text::ToPoint as TP;
10037
10038 let edits = edits
10039 .into_iter()
10040 .map(|range| {
10041 let end_point = TP::to_point(&range.end, &snapshot);
10042 let mut start_point = TP::to_point(&range.start, &snapshot);
10043
10044 if end_point == start_point {
10045 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10046 .saturating_sub(1);
10047 start_point =
10048 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10049 };
10050
10051 (start_point..end_point, empty_str.clone())
10052 })
10053 .sorted_by_key(|(range, _)| range.start)
10054 .collect::<Vec<_>>();
10055 buffer.update(cx, |this, cx| {
10056 this.edit(edits, None, cx);
10057 })
10058 }
10059 this.refresh_edit_prediction(true, false, window, cx);
10060 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10061 });
10062 }
10063
10064 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10065 if self.read_only(cx) {
10066 return;
10067 }
10068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10069 self.transact(window, cx, |this, window, cx| {
10070 this.change_selections(Default::default(), window, cx, |s| {
10071 s.move_with(|map, selection| {
10072 if selection.is_empty() {
10073 let cursor = movement::right(map, selection.head());
10074 selection.end = cursor;
10075 selection.reversed = true;
10076 selection.goal = SelectionGoal::None;
10077 }
10078 })
10079 });
10080 this.insert("", window, cx);
10081 this.refresh_edit_prediction(true, false, window, cx);
10082 });
10083 }
10084
10085 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10086 if self.mode.is_single_line() {
10087 cx.propagate();
10088 return;
10089 }
10090
10091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10092 if self.move_to_prev_snippet_tabstop(window, cx) {
10093 return;
10094 }
10095 self.outdent(&Outdent, window, cx);
10096 }
10097
10098 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10099 if self.mode.is_single_line() {
10100 cx.propagate();
10101 return;
10102 }
10103
10104 if self.move_to_next_snippet_tabstop(window, cx) {
10105 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10106 return;
10107 }
10108 if self.read_only(cx) {
10109 return;
10110 }
10111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10112 let mut selections = self.selections.all_adjusted(cx);
10113 let buffer = self.buffer.read(cx);
10114 let snapshot = buffer.snapshot(cx);
10115 let rows_iter = selections.iter().map(|s| s.head().row);
10116 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10117
10118 let has_some_cursor_in_whitespace = selections
10119 .iter()
10120 .filter(|selection| selection.is_empty())
10121 .any(|selection| {
10122 let cursor = selection.head();
10123 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10124 cursor.column < current_indent.len
10125 });
10126
10127 let mut edits = Vec::new();
10128 let mut prev_edited_row = 0;
10129 let mut row_delta = 0;
10130 for selection in &mut selections {
10131 if selection.start.row != prev_edited_row {
10132 row_delta = 0;
10133 }
10134 prev_edited_row = selection.end.row;
10135
10136 // If the selection is non-empty, then increase the indentation of the selected lines.
10137 if !selection.is_empty() {
10138 row_delta =
10139 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10140 continue;
10141 }
10142
10143 let cursor = selection.head();
10144 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10145 if let Some(suggested_indent) =
10146 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10147 {
10148 // Don't do anything if already at suggested indent
10149 // and there is any other cursor which is not
10150 if has_some_cursor_in_whitespace
10151 && cursor.column == current_indent.len
10152 && current_indent.len == suggested_indent.len
10153 {
10154 continue;
10155 }
10156
10157 // Adjust line and move cursor to suggested indent
10158 // if cursor is not at suggested indent
10159 if cursor.column < suggested_indent.len
10160 && cursor.column <= current_indent.len
10161 && current_indent.len <= suggested_indent.len
10162 {
10163 selection.start = Point::new(cursor.row, suggested_indent.len);
10164 selection.end = selection.start;
10165 if row_delta == 0 {
10166 edits.extend(Buffer::edit_for_indent_size_adjustment(
10167 cursor.row,
10168 current_indent,
10169 suggested_indent,
10170 ));
10171 row_delta = suggested_indent.len - current_indent.len;
10172 }
10173 continue;
10174 }
10175
10176 // If current indent is more than suggested indent
10177 // only move cursor to current indent and skip indent
10178 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10179 selection.start = Point::new(cursor.row, current_indent.len);
10180 selection.end = selection.start;
10181 continue;
10182 }
10183 }
10184
10185 // Otherwise, insert a hard or soft tab.
10186 let settings = buffer.language_settings_at(cursor, cx);
10187 let tab_size = if settings.hard_tabs {
10188 IndentSize::tab()
10189 } else {
10190 let tab_size = settings.tab_size.get();
10191 let indent_remainder = snapshot
10192 .text_for_range(Point::new(cursor.row, 0)..cursor)
10193 .flat_map(str::chars)
10194 .fold(row_delta % tab_size, |counter: u32, c| {
10195 if c == '\t' {
10196 0
10197 } else {
10198 (counter + 1) % tab_size
10199 }
10200 });
10201
10202 let chars_to_next_tab_stop = tab_size - indent_remainder;
10203 IndentSize::spaces(chars_to_next_tab_stop)
10204 };
10205 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10206 selection.end = selection.start;
10207 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10208 row_delta += tab_size.len;
10209 }
10210
10211 self.transact(window, cx, |this, window, cx| {
10212 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10213 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10214 this.refresh_edit_prediction(true, false, window, cx);
10215 });
10216 }
10217
10218 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10219 if self.read_only(cx) {
10220 return;
10221 }
10222 if self.mode.is_single_line() {
10223 cx.propagate();
10224 return;
10225 }
10226
10227 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10228 let mut selections = self.selections.all::<Point>(cx);
10229 let mut prev_edited_row = 0;
10230 let mut row_delta = 0;
10231 let mut edits = Vec::new();
10232 let buffer = self.buffer.read(cx);
10233 let snapshot = buffer.snapshot(cx);
10234 for selection in &mut selections {
10235 if selection.start.row != prev_edited_row {
10236 row_delta = 0;
10237 }
10238 prev_edited_row = selection.end.row;
10239
10240 row_delta =
10241 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10242 }
10243
10244 self.transact(window, cx, |this, window, cx| {
10245 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10246 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10247 });
10248 }
10249
10250 fn indent_selection(
10251 buffer: &MultiBuffer,
10252 snapshot: &MultiBufferSnapshot,
10253 selection: &mut Selection<Point>,
10254 edits: &mut Vec<(Range<Point>, String)>,
10255 delta_for_start_row: u32,
10256 cx: &App,
10257 ) -> u32 {
10258 let settings = buffer.language_settings_at(selection.start, cx);
10259 let tab_size = settings.tab_size.get();
10260 let indent_kind = if settings.hard_tabs {
10261 IndentKind::Tab
10262 } else {
10263 IndentKind::Space
10264 };
10265 let mut start_row = selection.start.row;
10266 let mut end_row = selection.end.row + 1;
10267
10268 // If a selection ends at the beginning of a line, don't indent
10269 // that last line.
10270 if selection.end.column == 0 && selection.end.row > selection.start.row {
10271 end_row -= 1;
10272 }
10273
10274 // Avoid re-indenting a row that has already been indented by a
10275 // previous selection, but still update this selection's column
10276 // to reflect that indentation.
10277 if delta_for_start_row > 0 {
10278 start_row += 1;
10279 selection.start.column += delta_for_start_row;
10280 if selection.end.row == selection.start.row {
10281 selection.end.column += delta_for_start_row;
10282 }
10283 }
10284
10285 let mut delta_for_end_row = 0;
10286 let has_multiple_rows = start_row + 1 != end_row;
10287 for row in start_row..end_row {
10288 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10289 let indent_delta = match (current_indent.kind, indent_kind) {
10290 (IndentKind::Space, IndentKind::Space) => {
10291 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10292 IndentSize::spaces(columns_to_next_tab_stop)
10293 }
10294 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10295 (_, IndentKind::Tab) => IndentSize::tab(),
10296 };
10297
10298 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10299 0
10300 } else {
10301 selection.start.column
10302 };
10303 let row_start = Point::new(row, start);
10304 edits.push((
10305 row_start..row_start,
10306 indent_delta.chars().collect::<String>(),
10307 ));
10308
10309 // Update this selection's endpoints to reflect the indentation.
10310 if row == selection.start.row {
10311 selection.start.column += indent_delta.len;
10312 }
10313 if row == selection.end.row {
10314 selection.end.column += indent_delta.len;
10315 delta_for_end_row = indent_delta.len;
10316 }
10317 }
10318
10319 if selection.start.row == selection.end.row {
10320 delta_for_start_row + delta_for_end_row
10321 } else {
10322 delta_for_end_row
10323 }
10324 }
10325
10326 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10327 if self.read_only(cx) {
10328 return;
10329 }
10330 if self.mode.is_single_line() {
10331 cx.propagate();
10332 return;
10333 }
10334
10335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10336 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10337 let selections = self.selections.all::<Point>(cx);
10338 let mut deletion_ranges = Vec::new();
10339 let mut last_outdent = None;
10340 {
10341 let buffer = self.buffer.read(cx);
10342 let snapshot = buffer.snapshot(cx);
10343 for selection in &selections {
10344 let settings = buffer.language_settings_at(selection.start, cx);
10345 let tab_size = settings.tab_size.get();
10346 let mut rows = selection.spanned_rows(false, &display_map);
10347
10348 // Avoid re-outdenting a row that has already been outdented by a
10349 // previous selection.
10350 if let Some(last_row) = last_outdent
10351 && last_row == rows.start
10352 {
10353 rows.start = rows.start.next_row();
10354 }
10355 let has_multiple_rows = rows.len() > 1;
10356 for row in rows.iter_rows() {
10357 let indent_size = snapshot.indent_size_for_line(row);
10358 if indent_size.len > 0 {
10359 let deletion_len = match indent_size.kind {
10360 IndentKind::Space => {
10361 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10362 if columns_to_prev_tab_stop == 0 {
10363 tab_size
10364 } else {
10365 columns_to_prev_tab_stop
10366 }
10367 }
10368 IndentKind::Tab => 1,
10369 };
10370 let start = if has_multiple_rows
10371 || deletion_len > selection.start.column
10372 || indent_size.len < selection.start.column
10373 {
10374 0
10375 } else {
10376 selection.start.column - deletion_len
10377 };
10378 deletion_ranges.push(
10379 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10380 );
10381 last_outdent = Some(row);
10382 }
10383 }
10384 }
10385 }
10386
10387 self.transact(window, cx, |this, window, cx| {
10388 this.buffer.update(cx, |buffer, cx| {
10389 let empty_str: Arc<str> = Arc::default();
10390 buffer.edit(
10391 deletion_ranges
10392 .into_iter()
10393 .map(|range| (range, empty_str.clone())),
10394 None,
10395 cx,
10396 );
10397 });
10398 let selections = this.selections.all::<usize>(cx);
10399 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10400 });
10401 }
10402
10403 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10404 if self.read_only(cx) {
10405 return;
10406 }
10407 if self.mode.is_single_line() {
10408 cx.propagate();
10409 return;
10410 }
10411
10412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10413 let selections = self
10414 .selections
10415 .all::<usize>(cx)
10416 .into_iter()
10417 .map(|s| s.range());
10418
10419 self.transact(window, cx, |this, window, cx| {
10420 this.buffer.update(cx, |buffer, cx| {
10421 buffer.autoindent_ranges(selections, cx);
10422 });
10423 let selections = this.selections.all::<usize>(cx);
10424 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10425 });
10426 }
10427
10428 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10429 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10431 let selections = self.selections.all::<Point>(cx);
10432
10433 let mut new_cursors = Vec::new();
10434 let mut edit_ranges = Vec::new();
10435 let mut selections = selections.iter().peekable();
10436 while let Some(selection) = selections.next() {
10437 let mut rows = selection.spanned_rows(false, &display_map);
10438
10439 // Accumulate contiguous regions of rows that we want to delete.
10440 while let Some(next_selection) = selections.peek() {
10441 let next_rows = next_selection.spanned_rows(false, &display_map);
10442 if next_rows.start <= rows.end {
10443 rows.end = next_rows.end;
10444 selections.next().unwrap();
10445 } else {
10446 break;
10447 }
10448 }
10449
10450 let buffer = &display_map.buffer_snapshot;
10451 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10452 let edit_end = if buffer.max_point().row >= rows.end.0 {
10453 // If there's a line after the range, delete the \n from the end of the row range
10454 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer)
10455 } else {
10456 // If there isn't a line after the range, delete the \n from the line before the
10457 // start of the row range
10458 edit_start = edit_start.saturating_sub(1);
10459 buffer.len()
10460 };
10461
10462 let (cursor, goal) = movement::down_by_rows(
10463 &display_map,
10464 selection.head().to_display_point(&display_map),
10465 rows.len() as u32,
10466 selection.goal,
10467 false,
10468 &self.text_layout_details(window),
10469 );
10470
10471 new_cursors.push((
10472 selection.id,
10473 buffer.anchor_after(cursor.to_point(&display_map)),
10474 goal,
10475 ));
10476 edit_ranges.push(edit_start..edit_end);
10477 }
10478
10479 self.transact(window, cx, |this, window, cx| {
10480 let buffer = this.buffer.update(cx, |buffer, cx| {
10481 let empty_str: Arc<str> = Arc::default();
10482 buffer.edit(
10483 edit_ranges
10484 .into_iter()
10485 .map(|range| (range, empty_str.clone())),
10486 None,
10487 cx,
10488 );
10489 buffer.snapshot(cx)
10490 });
10491 let new_selections = new_cursors
10492 .into_iter()
10493 .map(|(id, cursor, goal)| {
10494 let cursor = cursor.to_point(&buffer);
10495 Selection {
10496 id,
10497 start: cursor,
10498 end: cursor,
10499 reversed: false,
10500 goal,
10501 }
10502 })
10503 .collect();
10504
10505 this.change_selections(Default::default(), window, cx, |s| {
10506 s.select(new_selections);
10507 });
10508 });
10509 }
10510
10511 pub fn join_lines_impl(
10512 &mut self,
10513 insert_whitespace: bool,
10514 window: &mut Window,
10515 cx: &mut Context<Self>,
10516 ) {
10517 if self.read_only(cx) {
10518 return;
10519 }
10520 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10521 for selection in self.selections.all::<Point>(cx) {
10522 let start = MultiBufferRow(selection.start.row);
10523 // Treat single line selections as if they include the next line. Otherwise this action
10524 // would do nothing for single line selections individual cursors.
10525 let end = if selection.start.row == selection.end.row {
10526 MultiBufferRow(selection.start.row + 1)
10527 } else {
10528 MultiBufferRow(selection.end.row)
10529 };
10530
10531 if let Some(last_row_range) = row_ranges.last_mut()
10532 && start <= last_row_range.end
10533 {
10534 last_row_range.end = end;
10535 continue;
10536 }
10537 row_ranges.push(start..end);
10538 }
10539
10540 let snapshot = self.buffer.read(cx).snapshot(cx);
10541 let mut cursor_positions = Vec::new();
10542 for row_range in &row_ranges {
10543 let anchor = snapshot.anchor_before(Point::new(
10544 row_range.end.previous_row().0,
10545 snapshot.line_len(row_range.end.previous_row()),
10546 ));
10547 cursor_positions.push(anchor..anchor);
10548 }
10549
10550 self.transact(window, cx, |this, window, cx| {
10551 for row_range in row_ranges.into_iter().rev() {
10552 for row in row_range.iter_rows().rev() {
10553 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10554 let next_line_row = row.next_row();
10555 let indent = snapshot.indent_size_for_line(next_line_row);
10556 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10557
10558 let replace =
10559 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10560 " "
10561 } else {
10562 ""
10563 };
10564
10565 this.buffer.update(cx, |buffer, cx| {
10566 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10567 });
10568 }
10569 }
10570
10571 this.change_selections(Default::default(), window, cx, |s| {
10572 s.select_anchor_ranges(cursor_positions)
10573 });
10574 });
10575 }
10576
10577 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10578 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10579 self.join_lines_impl(true, window, cx);
10580 }
10581
10582 pub fn sort_lines_case_sensitive(
10583 &mut self,
10584 _: &SortLinesCaseSensitive,
10585 window: &mut Window,
10586 cx: &mut Context<Self>,
10587 ) {
10588 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10589 }
10590
10591 pub fn sort_lines_by_length(
10592 &mut self,
10593 _: &SortLinesByLength,
10594 window: &mut Window,
10595 cx: &mut Context<Self>,
10596 ) {
10597 self.manipulate_immutable_lines(window, cx, |lines| {
10598 lines.sort_by_key(|&line| line.chars().count())
10599 })
10600 }
10601
10602 pub fn sort_lines_case_insensitive(
10603 &mut self,
10604 _: &SortLinesCaseInsensitive,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.manipulate_immutable_lines(window, cx, |lines| {
10609 lines.sort_by_key(|line| line.to_lowercase())
10610 })
10611 }
10612
10613 pub fn unique_lines_case_insensitive(
10614 &mut self,
10615 _: &UniqueLinesCaseInsensitive,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) {
10619 self.manipulate_immutable_lines(window, cx, |lines| {
10620 let mut seen = HashSet::default();
10621 lines.retain(|line| seen.insert(line.to_lowercase()));
10622 })
10623 }
10624
10625 pub fn unique_lines_case_sensitive(
10626 &mut self,
10627 _: &UniqueLinesCaseSensitive,
10628 window: &mut Window,
10629 cx: &mut Context<Self>,
10630 ) {
10631 self.manipulate_immutable_lines(window, cx, |lines| {
10632 let mut seen = HashSet::default();
10633 lines.retain(|line| seen.insert(*line));
10634 })
10635 }
10636
10637 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10638 let snapshot = self.buffer.read(cx).snapshot(cx);
10639 for selection in self.selections.disjoint_anchors_arc().iter() {
10640 if snapshot
10641 .language_at(selection.start)
10642 .and_then(|lang| lang.config().wrap_characters.as_ref())
10643 .is_some()
10644 {
10645 return true;
10646 }
10647 }
10648 false
10649 }
10650
10651 fn wrap_selections_in_tag(
10652 &mut self,
10653 _: &WrapSelectionsInTag,
10654 window: &mut Window,
10655 cx: &mut Context<Self>,
10656 ) {
10657 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10658
10659 let snapshot = self.buffer.read(cx).snapshot(cx);
10660
10661 let mut edits = Vec::new();
10662 let mut boundaries = Vec::new();
10663
10664 for selection in self.selections.all::<Point>(cx).iter() {
10665 let Some(wrap_config) = snapshot
10666 .language_at(selection.start)
10667 .and_then(|lang| lang.config().wrap_characters.clone())
10668 else {
10669 continue;
10670 };
10671
10672 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10673 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10674
10675 let start_before = snapshot.anchor_before(selection.start);
10676 let end_after = snapshot.anchor_after(selection.end);
10677
10678 edits.push((start_before..start_before, open_tag));
10679 edits.push((end_after..end_after, close_tag));
10680
10681 boundaries.push((
10682 start_before,
10683 end_after,
10684 wrap_config.start_prefix.len(),
10685 wrap_config.end_suffix.len(),
10686 ));
10687 }
10688
10689 if edits.is_empty() {
10690 return;
10691 }
10692
10693 self.transact(window, cx, |this, window, cx| {
10694 let buffer = this.buffer.update(cx, |buffer, cx| {
10695 buffer.edit(edits, None, cx);
10696 buffer.snapshot(cx)
10697 });
10698
10699 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10700 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10701 boundaries.into_iter()
10702 {
10703 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10704 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10705 new_selections.push(open_offset..open_offset);
10706 new_selections.push(close_offset..close_offset);
10707 }
10708
10709 this.change_selections(Default::default(), window, cx, |s| {
10710 s.select_ranges(new_selections);
10711 });
10712
10713 this.request_autoscroll(Autoscroll::fit(), cx);
10714 });
10715 }
10716
10717 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10718 let Some(project) = self.project.clone() else {
10719 return;
10720 };
10721 self.reload(project, window, cx)
10722 .detach_and_notify_err(window, cx);
10723 }
10724
10725 pub fn restore_file(
10726 &mut self,
10727 _: &::git::RestoreFile,
10728 window: &mut Window,
10729 cx: &mut Context<Self>,
10730 ) {
10731 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10732 let mut buffer_ids = HashSet::default();
10733 let snapshot = self.buffer().read(cx).snapshot(cx);
10734 for selection in self.selections.all::<usize>(cx) {
10735 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10736 }
10737
10738 let buffer = self.buffer().read(cx);
10739 let ranges = buffer_ids
10740 .into_iter()
10741 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10742 .collect::<Vec<_>>();
10743
10744 self.restore_hunks_in_ranges(ranges, window, cx);
10745 }
10746
10747 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10749 let selections = self
10750 .selections
10751 .all(cx)
10752 .into_iter()
10753 .map(|s| s.range())
10754 .collect();
10755 self.restore_hunks_in_ranges(selections, window, cx);
10756 }
10757
10758 pub fn restore_hunks_in_ranges(
10759 &mut self,
10760 ranges: Vec<Range<Point>>,
10761 window: &mut Window,
10762 cx: &mut Context<Editor>,
10763 ) {
10764 let mut revert_changes = HashMap::default();
10765 let chunk_by = self
10766 .snapshot(window, cx)
10767 .hunks_for_ranges(ranges)
10768 .into_iter()
10769 .chunk_by(|hunk| hunk.buffer_id);
10770 for (buffer_id, hunks) in &chunk_by {
10771 let hunks = hunks.collect::<Vec<_>>();
10772 for hunk in &hunks {
10773 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10774 }
10775 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10776 }
10777 drop(chunk_by);
10778 if !revert_changes.is_empty() {
10779 self.transact(window, cx, |editor, window, cx| {
10780 editor.restore(revert_changes, window, cx);
10781 });
10782 }
10783 }
10784
10785 pub fn open_active_item_in_terminal(
10786 &mut self,
10787 _: &OpenInTerminal,
10788 window: &mut Window,
10789 cx: &mut Context<Self>,
10790 ) {
10791 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10792 let project_path = buffer.read(cx).project_path(cx)?;
10793 let project = self.project()?.read(cx);
10794 let entry = project.entry_for_path(&project_path, cx)?;
10795 let parent = match &entry.canonical_path {
10796 Some(canonical_path) => canonical_path.to_path_buf(),
10797 None => project.absolute_path(&project_path, cx)?,
10798 }
10799 .parent()?
10800 .to_path_buf();
10801 Some(parent)
10802 }) {
10803 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10804 }
10805 }
10806
10807 fn set_breakpoint_context_menu(
10808 &mut self,
10809 display_row: DisplayRow,
10810 position: Option<Anchor>,
10811 clicked_point: gpui::Point<Pixels>,
10812 window: &mut Window,
10813 cx: &mut Context<Self>,
10814 ) {
10815 let source = self
10816 .buffer
10817 .read(cx)
10818 .snapshot(cx)
10819 .anchor_before(Point::new(display_row.0, 0u32));
10820
10821 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10822
10823 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10824 self,
10825 source,
10826 clicked_point,
10827 context_menu,
10828 window,
10829 cx,
10830 );
10831 }
10832
10833 fn add_edit_breakpoint_block(
10834 &mut self,
10835 anchor: Anchor,
10836 breakpoint: &Breakpoint,
10837 edit_action: BreakpointPromptEditAction,
10838 window: &mut Window,
10839 cx: &mut Context<Self>,
10840 ) {
10841 let weak_editor = cx.weak_entity();
10842 let bp_prompt = cx.new(|cx| {
10843 BreakpointPromptEditor::new(
10844 weak_editor,
10845 anchor,
10846 breakpoint.clone(),
10847 edit_action,
10848 window,
10849 cx,
10850 )
10851 });
10852
10853 let height = bp_prompt.update(cx, |this, cx| {
10854 this.prompt
10855 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10856 });
10857 let cloned_prompt = bp_prompt.clone();
10858 let blocks = vec![BlockProperties {
10859 style: BlockStyle::Sticky,
10860 placement: BlockPlacement::Above(anchor),
10861 height: Some(height),
10862 render: Arc::new(move |cx| {
10863 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10864 cloned_prompt.clone().into_any_element()
10865 }),
10866 priority: 0,
10867 }];
10868
10869 let focus_handle = bp_prompt.focus_handle(cx);
10870 window.focus(&focus_handle);
10871
10872 let block_ids = self.insert_blocks(blocks, None, cx);
10873 bp_prompt.update(cx, |prompt, _| {
10874 prompt.add_block_ids(block_ids);
10875 });
10876 }
10877
10878 pub(crate) fn breakpoint_at_row(
10879 &self,
10880 row: u32,
10881 window: &mut Window,
10882 cx: &mut Context<Self>,
10883 ) -> Option<(Anchor, Breakpoint)> {
10884 let snapshot = self.snapshot(window, cx);
10885 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10886
10887 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10888 }
10889
10890 pub(crate) fn breakpoint_at_anchor(
10891 &self,
10892 breakpoint_position: Anchor,
10893 snapshot: &EditorSnapshot,
10894 cx: &mut Context<Self>,
10895 ) -> Option<(Anchor, Breakpoint)> {
10896 let buffer = self
10897 .buffer
10898 .read(cx)
10899 .buffer_for_anchor(breakpoint_position, cx)?;
10900
10901 let enclosing_excerpt = breakpoint_position.excerpt_id;
10902 let buffer_snapshot = buffer.read(cx).snapshot();
10903
10904 let row = buffer_snapshot
10905 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10906 .row;
10907
10908 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10909 let anchor_end = snapshot
10910 .buffer_snapshot
10911 .anchor_after(Point::new(row, line_len));
10912
10913 self.breakpoint_store
10914 .as_ref()?
10915 .read_with(cx, |breakpoint_store, cx| {
10916 breakpoint_store
10917 .breakpoints(
10918 &buffer,
10919 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10920 &buffer_snapshot,
10921 cx,
10922 )
10923 .next()
10924 .and_then(|(bp, _)| {
10925 let breakpoint_row = buffer_snapshot
10926 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10927 .row;
10928
10929 if breakpoint_row == row {
10930 snapshot
10931 .buffer_snapshot
10932 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10933 .map(|position| (position, bp.bp.clone()))
10934 } else {
10935 None
10936 }
10937 })
10938 })
10939 }
10940
10941 pub fn edit_log_breakpoint(
10942 &mut self,
10943 _: &EditLogBreakpoint,
10944 window: &mut Window,
10945 cx: &mut Context<Self>,
10946 ) {
10947 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10948 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10949 message: None,
10950 state: BreakpointState::Enabled,
10951 condition: None,
10952 hit_condition: None,
10953 });
10954
10955 self.add_edit_breakpoint_block(
10956 anchor,
10957 &breakpoint,
10958 BreakpointPromptEditAction::Log,
10959 window,
10960 cx,
10961 );
10962 }
10963 }
10964
10965 fn breakpoints_at_cursors(
10966 &self,
10967 window: &mut Window,
10968 cx: &mut Context<Self>,
10969 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10970 let snapshot = self.snapshot(window, cx);
10971 let cursors = self
10972 .selections
10973 .disjoint_anchors_arc()
10974 .iter()
10975 .map(|selection| {
10976 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10977
10978 let breakpoint_position = self
10979 .breakpoint_at_row(cursor_position.row, window, cx)
10980 .map(|bp| bp.0)
10981 .unwrap_or_else(|| {
10982 snapshot
10983 .display_snapshot
10984 .buffer_snapshot
10985 .anchor_after(Point::new(cursor_position.row, 0))
10986 });
10987
10988 let breakpoint = self
10989 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10990 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10991
10992 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10993 })
10994 // 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.
10995 .collect::<HashMap<Anchor, _>>();
10996
10997 cursors.into_iter().collect()
10998 }
10999
11000 pub fn enable_breakpoint(
11001 &mut self,
11002 _: &crate::actions::EnableBreakpoint,
11003 window: &mut Window,
11004 cx: &mut Context<Self>,
11005 ) {
11006 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11007 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11008 continue;
11009 };
11010 self.edit_breakpoint_at_anchor(
11011 anchor,
11012 breakpoint,
11013 BreakpointEditAction::InvertState,
11014 cx,
11015 );
11016 }
11017 }
11018
11019 pub fn disable_breakpoint(
11020 &mut self,
11021 _: &crate::actions::DisableBreakpoint,
11022 window: &mut Window,
11023 cx: &mut Context<Self>,
11024 ) {
11025 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11026 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11027 continue;
11028 };
11029 self.edit_breakpoint_at_anchor(
11030 anchor,
11031 breakpoint,
11032 BreakpointEditAction::InvertState,
11033 cx,
11034 );
11035 }
11036 }
11037
11038 pub fn toggle_breakpoint(
11039 &mut self,
11040 _: &crate::actions::ToggleBreakpoint,
11041 window: &mut Window,
11042 cx: &mut Context<Self>,
11043 ) {
11044 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11045 if let Some(breakpoint) = breakpoint {
11046 self.edit_breakpoint_at_anchor(
11047 anchor,
11048 breakpoint,
11049 BreakpointEditAction::Toggle,
11050 cx,
11051 );
11052 } else {
11053 self.edit_breakpoint_at_anchor(
11054 anchor,
11055 Breakpoint::new_standard(),
11056 BreakpointEditAction::Toggle,
11057 cx,
11058 );
11059 }
11060 }
11061 }
11062
11063 pub fn edit_breakpoint_at_anchor(
11064 &mut self,
11065 breakpoint_position: Anchor,
11066 breakpoint: Breakpoint,
11067 edit_action: BreakpointEditAction,
11068 cx: &mut Context<Self>,
11069 ) {
11070 let Some(breakpoint_store) = &self.breakpoint_store else {
11071 return;
11072 };
11073
11074 let Some(buffer) = self
11075 .buffer
11076 .read(cx)
11077 .buffer_for_anchor(breakpoint_position, cx)
11078 else {
11079 return;
11080 };
11081
11082 breakpoint_store.update(cx, |breakpoint_store, cx| {
11083 breakpoint_store.toggle_breakpoint(
11084 buffer,
11085 BreakpointWithPosition {
11086 position: breakpoint_position.text_anchor,
11087 bp: breakpoint,
11088 },
11089 edit_action,
11090 cx,
11091 );
11092 });
11093
11094 cx.notify();
11095 }
11096
11097 #[cfg(any(test, feature = "test-support"))]
11098 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11099 self.breakpoint_store.clone()
11100 }
11101
11102 pub fn prepare_restore_change(
11103 &self,
11104 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11105 hunk: &MultiBufferDiffHunk,
11106 cx: &mut App,
11107 ) -> Option<()> {
11108 if hunk.is_created_file() {
11109 return None;
11110 }
11111 let buffer = self.buffer.read(cx);
11112 let diff = buffer.diff_for(hunk.buffer_id)?;
11113 let buffer = buffer.buffer(hunk.buffer_id)?;
11114 let buffer = buffer.read(cx);
11115 let original_text = diff
11116 .read(cx)
11117 .base_text()
11118 .as_rope()
11119 .slice(hunk.diff_base_byte_range.clone());
11120 let buffer_snapshot = buffer.snapshot();
11121 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11122 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11123 probe
11124 .0
11125 .start
11126 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11127 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11128 }) {
11129 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11130 Some(())
11131 } else {
11132 None
11133 }
11134 }
11135
11136 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11137 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11138 }
11139
11140 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11141 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11142 }
11143
11144 fn manipulate_lines<M>(
11145 &mut self,
11146 window: &mut Window,
11147 cx: &mut Context<Self>,
11148 mut manipulate: M,
11149 ) where
11150 M: FnMut(&str) -> LineManipulationResult,
11151 {
11152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11153
11154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11155 let buffer = self.buffer.read(cx).snapshot(cx);
11156
11157 let mut edits = Vec::new();
11158
11159 let selections = self.selections.all::<Point>(cx);
11160 let mut selections = selections.iter().peekable();
11161 let mut contiguous_row_selections = Vec::new();
11162 let mut new_selections = Vec::new();
11163 let mut added_lines = 0;
11164 let mut removed_lines = 0;
11165
11166 while let Some(selection) = selections.next() {
11167 let (start_row, end_row) = consume_contiguous_rows(
11168 &mut contiguous_row_selections,
11169 selection,
11170 &display_map,
11171 &mut selections,
11172 );
11173
11174 let start_point = Point::new(start_row.0, 0);
11175 let end_point = Point::new(
11176 end_row.previous_row().0,
11177 buffer.line_len(end_row.previous_row()),
11178 );
11179 let text = buffer
11180 .text_for_range(start_point..end_point)
11181 .collect::<String>();
11182
11183 let LineManipulationResult {
11184 new_text,
11185 line_count_before,
11186 line_count_after,
11187 } = manipulate(&text);
11188
11189 edits.push((start_point..end_point, new_text));
11190
11191 // Selections must change based on added and removed line count
11192 let start_row =
11193 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11194 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11195 new_selections.push(Selection {
11196 id: selection.id,
11197 start: start_row,
11198 end: end_row,
11199 goal: SelectionGoal::None,
11200 reversed: selection.reversed,
11201 });
11202
11203 if line_count_after > line_count_before {
11204 added_lines += line_count_after - line_count_before;
11205 } else if line_count_before > line_count_after {
11206 removed_lines += line_count_before - line_count_after;
11207 }
11208 }
11209
11210 self.transact(window, cx, |this, window, cx| {
11211 let buffer = this.buffer.update(cx, |buffer, cx| {
11212 buffer.edit(edits, None, cx);
11213 buffer.snapshot(cx)
11214 });
11215
11216 // Recalculate offsets on newly edited buffer
11217 let new_selections = new_selections
11218 .iter()
11219 .map(|s| {
11220 let start_point = Point::new(s.start.0, 0);
11221 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11222 Selection {
11223 id: s.id,
11224 start: buffer.point_to_offset(start_point),
11225 end: buffer.point_to_offset(end_point),
11226 goal: s.goal,
11227 reversed: s.reversed,
11228 }
11229 })
11230 .collect();
11231
11232 this.change_selections(Default::default(), window, cx, |s| {
11233 s.select(new_selections);
11234 });
11235
11236 this.request_autoscroll(Autoscroll::fit(), cx);
11237 });
11238 }
11239
11240 fn manipulate_immutable_lines<Fn>(
11241 &mut self,
11242 window: &mut Window,
11243 cx: &mut Context<Self>,
11244 mut callback: Fn,
11245 ) where
11246 Fn: FnMut(&mut Vec<&str>),
11247 {
11248 self.manipulate_lines(window, cx, |text| {
11249 let mut lines: Vec<&str> = text.split('\n').collect();
11250 let line_count_before = lines.len();
11251
11252 callback(&mut lines);
11253
11254 LineManipulationResult {
11255 new_text: lines.join("\n"),
11256 line_count_before,
11257 line_count_after: lines.len(),
11258 }
11259 });
11260 }
11261
11262 fn manipulate_mutable_lines<Fn>(
11263 &mut self,
11264 window: &mut Window,
11265 cx: &mut Context<Self>,
11266 mut callback: Fn,
11267 ) where
11268 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11269 {
11270 self.manipulate_lines(window, cx, |text| {
11271 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11272 let line_count_before = lines.len();
11273
11274 callback(&mut lines);
11275
11276 LineManipulationResult {
11277 new_text: lines.join("\n"),
11278 line_count_before,
11279 line_count_after: lines.len(),
11280 }
11281 });
11282 }
11283
11284 pub fn convert_indentation_to_spaces(
11285 &mut self,
11286 _: &ConvertIndentationToSpaces,
11287 window: &mut Window,
11288 cx: &mut Context<Self>,
11289 ) {
11290 let settings = self.buffer.read(cx).language_settings(cx);
11291 let tab_size = settings.tab_size.get() as usize;
11292
11293 self.manipulate_mutable_lines(window, cx, |lines| {
11294 // Allocates a reasonably sized scratch buffer once for the whole loop
11295 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11296 // Avoids recomputing spaces that could be inserted many times
11297 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11298 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11299 .collect();
11300
11301 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11302 let mut chars = line.as_ref().chars();
11303 let mut col = 0;
11304 let mut changed = false;
11305
11306 for ch in chars.by_ref() {
11307 match ch {
11308 ' ' => {
11309 reindented_line.push(' ');
11310 col += 1;
11311 }
11312 '\t' => {
11313 // \t are converted to spaces depending on the current column
11314 let spaces_len = tab_size - (col % tab_size);
11315 reindented_line.extend(&space_cache[spaces_len - 1]);
11316 col += spaces_len;
11317 changed = true;
11318 }
11319 _ => {
11320 // If we dont append before break, the character is consumed
11321 reindented_line.push(ch);
11322 break;
11323 }
11324 }
11325 }
11326
11327 if !changed {
11328 reindented_line.clear();
11329 continue;
11330 }
11331 // Append the rest of the line and replace old reference with new one
11332 reindented_line.extend(chars);
11333 *line = Cow::Owned(reindented_line.clone());
11334 reindented_line.clear();
11335 }
11336 });
11337 }
11338
11339 pub fn convert_indentation_to_tabs(
11340 &mut self,
11341 _: &ConvertIndentationToTabs,
11342 window: &mut Window,
11343 cx: &mut Context<Self>,
11344 ) {
11345 let settings = self.buffer.read(cx).language_settings(cx);
11346 let tab_size = settings.tab_size.get() as usize;
11347
11348 self.manipulate_mutable_lines(window, cx, |lines| {
11349 // Allocates a reasonably sized buffer once for the whole loop
11350 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11351 // Avoids recomputing spaces that could be inserted many times
11352 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11353 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11354 .collect();
11355
11356 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11357 let mut chars = line.chars();
11358 let mut spaces_count = 0;
11359 let mut first_non_indent_char = None;
11360 let mut changed = false;
11361
11362 for ch in chars.by_ref() {
11363 match ch {
11364 ' ' => {
11365 // Keep track of spaces. Append \t when we reach tab_size
11366 spaces_count += 1;
11367 changed = true;
11368 if spaces_count == tab_size {
11369 reindented_line.push('\t');
11370 spaces_count = 0;
11371 }
11372 }
11373 '\t' => {
11374 reindented_line.push('\t');
11375 spaces_count = 0;
11376 }
11377 _ => {
11378 // Dont append it yet, we might have remaining spaces
11379 first_non_indent_char = Some(ch);
11380 break;
11381 }
11382 }
11383 }
11384
11385 if !changed {
11386 reindented_line.clear();
11387 continue;
11388 }
11389 // Remaining spaces that didn't make a full tab stop
11390 if spaces_count > 0 {
11391 reindented_line.extend(&space_cache[spaces_count - 1]);
11392 }
11393 // If we consume an extra character that was not indentation, add it back
11394 if let Some(extra_char) = first_non_indent_char {
11395 reindented_line.push(extra_char);
11396 }
11397 // Append the rest of the line and replace old reference with new one
11398 reindented_line.extend(chars);
11399 *line = Cow::Owned(reindented_line.clone());
11400 reindented_line.clear();
11401 }
11402 });
11403 }
11404
11405 pub fn convert_to_upper_case(
11406 &mut self,
11407 _: &ConvertToUpperCase,
11408 window: &mut Window,
11409 cx: &mut Context<Self>,
11410 ) {
11411 self.manipulate_text(window, cx, |text| text.to_uppercase())
11412 }
11413
11414 pub fn convert_to_lower_case(
11415 &mut self,
11416 _: &ConvertToLowerCase,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) {
11420 self.manipulate_text(window, cx, |text| text.to_lowercase())
11421 }
11422
11423 pub fn convert_to_title_case(
11424 &mut self,
11425 _: &ConvertToTitleCase,
11426 window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 self.manipulate_text(window, cx, |text| {
11430 text.split('\n')
11431 .map(|line| line.to_case(Case::Title))
11432 .join("\n")
11433 })
11434 }
11435
11436 pub fn convert_to_snake_case(
11437 &mut self,
11438 _: &ConvertToSnakeCase,
11439 window: &mut Window,
11440 cx: &mut Context<Self>,
11441 ) {
11442 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11443 }
11444
11445 pub fn convert_to_kebab_case(
11446 &mut self,
11447 _: &ConvertToKebabCase,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11452 }
11453
11454 pub fn convert_to_upper_camel_case(
11455 &mut self,
11456 _: &ConvertToUpperCamelCase,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.manipulate_text(window, cx, |text| {
11461 text.split('\n')
11462 .map(|line| line.to_case(Case::UpperCamel))
11463 .join("\n")
11464 })
11465 }
11466
11467 pub fn convert_to_lower_camel_case(
11468 &mut self,
11469 _: &ConvertToLowerCamelCase,
11470 window: &mut Window,
11471 cx: &mut Context<Self>,
11472 ) {
11473 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11474 }
11475
11476 pub fn convert_to_opposite_case(
11477 &mut self,
11478 _: &ConvertToOppositeCase,
11479 window: &mut Window,
11480 cx: &mut Context<Self>,
11481 ) {
11482 self.manipulate_text(window, cx, |text| {
11483 text.chars()
11484 .fold(String::with_capacity(text.len()), |mut t, c| {
11485 if c.is_uppercase() {
11486 t.extend(c.to_lowercase());
11487 } else {
11488 t.extend(c.to_uppercase());
11489 }
11490 t
11491 })
11492 })
11493 }
11494
11495 pub fn convert_to_sentence_case(
11496 &mut self,
11497 _: &ConvertToSentenceCase,
11498 window: &mut Window,
11499 cx: &mut Context<Self>,
11500 ) {
11501 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11502 }
11503
11504 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11505 self.manipulate_text(window, cx, |text| {
11506 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11507 if has_upper_case_characters {
11508 text.to_lowercase()
11509 } else {
11510 text.to_uppercase()
11511 }
11512 })
11513 }
11514
11515 pub fn convert_to_rot13(
11516 &mut self,
11517 _: &ConvertToRot13,
11518 window: &mut Window,
11519 cx: &mut Context<Self>,
11520 ) {
11521 self.manipulate_text(window, cx, |text| {
11522 text.chars()
11523 .map(|c| match c {
11524 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11525 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11526 _ => c,
11527 })
11528 .collect()
11529 })
11530 }
11531
11532 pub fn convert_to_rot47(
11533 &mut self,
11534 _: &ConvertToRot47,
11535 window: &mut Window,
11536 cx: &mut Context<Self>,
11537 ) {
11538 self.manipulate_text(window, cx, |text| {
11539 text.chars()
11540 .map(|c| {
11541 let code_point = c as u32;
11542 if code_point >= 33 && code_point <= 126 {
11543 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11544 }
11545 c
11546 })
11547 .collect()
11548 })
11549 }
11550
11551 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11552 where
11553 Fn: FnMut(&str) -> String,
11554 {
11555 let buffer = self.buffer.read(cx).snapshot(cx);
11556
11557 let mut new_selections = Vec::new();
11558 let mut edits = Vec::new();
11559 let mut selection_adjustment = 0i32;
11560
11561 for selection in self.selections.all_adjusted(cx) {
11562 let selection_is_empty = selection.is_empty();
11563
11564 let (start, end) = if selection_is_empty {
11565 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11566 (word_range.start, word_range.end)
11567 } else {
11568 (
11569 buffer.point_to_offset(selection.start),
11570 buffer.point_to_offset(selection.end),
11571 )
11572 };
11573
11574 let text = buffer.text_for_range(start..end).collect::<String>();
11575 let old_length = text.len() as i32;
11576 let text = callback(&text);
11577
11578 new_selections.push(Selection {
11579 start: (start as i32 - selection_adjustment) as usize,
11580 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11581 goal: SelectionGoal::None,
11582 id: selection.id,
11583 reversed: selection.reversed,
11584 });
11585
11586 selection_adjustment += old_length - text.len() as i32;
11587
11588 edits.push((start..end, text));
11589 }
11590
11591 self.transact(window, cx, |this, window, cx| {
11592 this.buffer.update(cx, |buffer, cx| {
11593 buffer.edit(edits, None, cx);
11594 });
11595
11596 this.change_selections(Default::default(), window, cx, |s| {
11597 s.select(new_selections);
11598 });
11599
11600 this.request_autoscroll(Autoscroll::fit(), cx);
11601 });
11602 }
11603
11604 pub fn move_selection_on_drop(
11605 &mut self,
11606 selection: &Selection<Anchor>,
11607 target: DisplayPoint,
11608 is_cut: bool,
11609 window: &mut Window,
11610 cx: &mut Context<Self>,
11611 ) {
11612 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11613 let buffer = &display_map.buffer_snapshot;
11614 let mut edits = Vec::new();
11615 let insert_point = display_map
11616 .clip_point(target, Bias::Left)
11617 .to_point(&display_map);
11618 let text = buffer
11619 .text_for_range(selection.start..selection.end)
11620 .collect::<String>();
11621 if is_cut {
11622 edits.push(((selection.start..selection.end), String::new()));
11623 }
11624 let insert_anchor = buffer.anchor_before(insert_point);
11625 edits.push(((insert_anchor..insert_anchor), text));
11626 let last_edit_start = insert_anchor.bias_left(buffer);
11627 let last_edit_end = insert_anchor.bias_right(buffer);
11628 self.transact(window, cx, |this, window, cx| {
11629 this.buffer.update(cx, |buffer, cx| {
11630 buffer.edit(edits, None, cx);
11631 });
11632 this.change_selections(Default::default(), window, cx, |s| {
11633 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11634 });
11635 });
11636 }
11637
11638 pub fn clear_selection_drag_state(&mut self) {
11639 self.selection_drag_state = SelectionDragState::None;
11640 }
11641
11642 pub fn duplicate(
11643 &mut self,
11644 upwards: bool,
11645 whole_lines: bool,
11646 window: &mut Window,
11647 cx: &mut Context<Self>,
11648 ) {
11649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11650
11651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11652 let buffer = &display_map.buffer_snapshot;
11653 let selections = self.selections.all::<Point>(cx);
11654
11655 let mut edits = Vec::new();
11656 let mut selections_iter = selections.iter().peekable();
11657 while let Some(selection) = selections_iter.next() {
11658 let mut rows = selection.spanned_rows(false, &display_map);
11659 // duplicate line-wise
11660 if whole_lines || selection.start == selection.end {
11661 // Avoid duplicating the same lines twice.
11662 while let Some(next_selection) = selections_iter.peek() {
11663 let next_rows = next_selection.spanned_rows(false, &display_map);
11664 if next_rows.start < rows.end {
11665 rows.end = next_rows.end;
11666 selections_iter.next().unwrap();
11667 } else {
11668 break;
11669 }
11670 }
11671
11672 // Copy the text from the selected row region and splice it either at the start
11673 // or end of the region.
11674 let start = Point::new(rows.start.0, 0);
11675 let end = Point::new(
11676 rows.end.previous_row().0,
11677 buffer.line_len(rows.end.previous_row()),
11678 );
11679 let text = buffer
11680 .text_for_range(start..end)
11681 .chain(Some("\n"))
11682 .collect::<String>();
11683 let insert_location = if upwards {
11684 Point::new(rows.end.0, 0)
11685 } else {
11686 start
11687 };
11688 edits.push((insert_location..insert_location, text));
11689 } else {
11690 // duplicate character-wise
11691 let start = selection.start;
11692 let end = selection.end;
11693 let text = buffer.text_for_range(start..end).collect::<String>();
11694 edits.push((selection.end..selection.end, text));
11695 }
11696 }
11697
11698 self.transact(window, cx, |this, _, cx| {
11699 this.buffer.update(cx, |buffer, cx| {
11700 buffer.edit(edits, None, cx);
11701 });
11702
11703 this.request_autoscroll(Autoscroll::fit(), cx);
11704 });
11705 }
11706
11707 pub fn duplicate_line_up(
11708 &mut self,
11709 _: &DuplicateLineUp,
11710 window: &mut Window,
11711 cx: &mut Context<Self>,
11712 ) {
11713 self.duplicate(true, true, window, cx);
11714 }
11715
11716 pub fn duplicate_line_down(
11717 &mut self,
11718 _: &DuplicateLineDown,
11719 window: &mut Window,
11720 cx: &mut Context<Self>,
11721 ) {
11722 self.duplicate(false, true, window, cx);
11723 }
11724
11725 pub fn duplicate_selection(
11726 &mut self,
11727 _: &DuplicateSelection,
11728 window: &mut Window,
11729 cx: &mut Context<Self>,
11730 ) {
11731 self.duplicate(false, false, window, cx);
11732 }
11733
11734 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11735 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11736 if self.mode.is_single_line() {
11737 cx.propagate();
11738 return;
11739 }
11740
11741 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11742 let buffer = self.buffer.read(cx).snapshot(cx);
11743
11744 let mut edits = Vec::new();
11745 let mut unfold_ranges = Vec::new();
11746 let mut refold_creases = Vec::new();
11747
11748 let selections = self.selections.all::<Point>(cx);
11749 let mut selections = selections.iter().peekable();
11750 let mut contiguous_row_selections = Vec::new();
11751 let mut new_selections = Vec::new();
11752
11753 while let Some(selection) = selections.next() {
11754 // Find all the selections that span a contiguous row range
11755 let (start_row, end_row) = consume_contiguous_rows(
11756 &mut contiguous_row_selections,
11757 selection,
11758 &display_map,
11759 &mut selections,
11760 );
11761
11762 // Move the text spanned by the row range to be before the line preceding the row range
11763 if start_row.0 > 0 {
11764 let range_to_move = Point::new(
11765 start_row.previous_row().0,
11766 buffer.line_len(start_row.previous_row()),
11767 )
11768 ..Point::new(
11769 end_row.previous_row().0,
11770 buffer.line_len(end_row.previous_row()),
11771 );
11772 let insertion_point = display_map
11773 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11774 .0;
11775
11776 // Don't move lines across excerpts
11777 if buffer
11778 .excerpt_containing(insertion_point..range_to_move.end)
11779 .is_some()
11780 {
11781 let text = buffer
11782 .text_for_range(range_to_move.clone())
11783 .flat_map(|s| s.chars())
11784 .skip(1)
11785 .chain(['\n'])
11786 .collect::<String>();
11787
11788 edits.push((
11789 buffer.anchor_after(range_to_move.start)
11790 ..buffer.anchor_before(range_to_move.end),
11791 String::new(),
11792 ));
11793 let insertion_anchor = buffer.anchor_after(insertion_point);
11794 edits.push((insertion_anchor..insertion_anchor, text));
11795
11796 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11797
11798 // Move selections up
11799 new_selections.extend(contiguous_row_selections.drain(..).map(
11800 |mut selection| {
11801 selection.start.row -= row_delta;
11802 selection.end.row -= row_delta;
11803 selection
11804 },
11805 ));
11806
11807 // Move folds up
11808 unfold_ranges.push(range_to_move.clone());
11809 for fold in display_map.folds_in_range(
11810 buffer.anchor_before(range_to_move.start)
11811 ..buffer.anchor_after(range_to_move.end),
11812 ) {
11813 let mut start = fold.range.start.to_point(&buffer);
11814 let mut end = fold.range.end.to_point(&buffer);
11815 start.row -= row_delta;
11816 end.row -= row_delta;
11817 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11818 }
11819 }
11820 }
11821
11822 // If we didn't move line(s), preserve the existing selections
11823 new_selections.append(&mut contiguous_row_selections);
11824 }
11825
11826 self.transact(window, cx, |this, window, cx| {
11827 this.unfold_ranges(&unfold_ranges, true, true, cx);
11828 this.buffer.update(cx, |buffer, cx| {
11829 for (range, text) in edits {
11830 buffer.edit([(range, text)], None, cx);
11831 }
11832 });
11833 this.fold_creases(refold_creases, true, window, cx);
11834 this.change_selections(Default::default(), window, cx, |s| {
11835 s.select(new_selections);
11836 })
11837 });
11838 }
11839
11840 pub fn move_line_down(
11841 &mut self,
11842 _: &MoveLineDown,
11843 window: &mut Window,
11844 cx: &mut Context<Self>,
11845 ) {
11846 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11847 if self.mode.is_single_line() {
11848 cx.propagate();
11849 return;
11850 }
11851
11852 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11853 let buffer = self.buffer.read(cx).snapshot(cx);
11854
11855 let mut edits = Vec::new();
11856 let mut unfold_ranges = Vec::new();
11857 let mut refold_creases = Vec::new();
11858
11859 let selections = self.selections.all::<Point>(cx);
11860 let mut selections = selections.iter().peekable();
11861 let mut contiguous_row_selections = Vec::new();
11862 let mut new_selections = Vec::new();
11863
11864 while let Some(selection) = selections.next() {
11865 // Find all the selections that span a contiguous row range
11866 let (start_row, end_row) = consume_contiguous_rows(
11867 &mut contiguous_row_selections,
11868 selection,
11869 &display_map,
11870 &mut selections,
11871 );
11872
11873 // Move the text spanned by the row range to be after the last line of the row range
11874 if end_row.0 <= buffer.max_point().row {
11875 let range_to_move =
11876 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11877 let insertion_point = display_map
11878 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11879 .0;
11880
11881 // Don't move lines across excerpt boundaries
11882 if buffer
11883 .excerpt_containing(range_to_move.start..insertion_point)
11884 .is_some()
11885 {
11886 let mut text = String::from("\n");
11887 text.extend(buffer.text_for_range(range_to_move.clone()));
11888 text.pop(); // Drop trailing newline
11889 edits.push((
11890 buffer.anchor_after(range_to_move.start)
11891 ..buffer.anchor_before(range_to_move.end),
11892 String::new(),
11893 ));
11894 let insertion_anchor = buffer.anchor_after(insertion_point);
11895 edits.push((insertion_anchor..insertion_anchor, text));
11896
11897 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11898
11899 // Move selections down
11900 new_selections.extend(contiguous_row_selections.drain(..).map(
11901 |mut selection| {
11902 selection.start.row += row_delta;
11903 selection.end.row += row_delta;
11904 selection
11905 },
11906 ));
11907
11908 // Move folds down
11909 unfold_ranges.push(range_to_move.clone());
11910 for fold in display_map.folds_in_range(
11911 buffer.anchor_before(range_to_move.start)
11912 ..buffer.anchor_after(range_to_move.end),
11913 ) {
11914 let mut start = fold.range.start.to_point(&buffer);
11915 let mut end = fold.range.end.to_point(&buffer);
11916 start.row += row_delta;
11917 end.row += row_delta;
11918 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11919 }
11920 }
11921 }
11922
11923 // If we didn't move line(s), preserve the existing selections
11924 new_selections.append(&mut contiguous_row_selections);
11925 }
11926
11927 self.transact(window, cx, |this, window, cx| {
11928 this.unfold_ranges(&unfold_ranges, true, true, cx);
11929 this.buffer.update(cx, |buffer, cx| {
11930 for (range, text) in edits {
11931 buffer.edit([(range, text)], None, cx);
11932 }
11933 });
11934 this.fold_creases(refold_creases, true, window, cx);
11935 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11936 });
11937 }
11938
11939 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11941 let text_layout_details = &self.text_layout_details(window);
11942 self.transact(window, cx, |this, window, cx| {
11943 let edits = this.change_selections(Default::default(), window, cx, |s| {
11944 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11945 s.move_with(|display_map, selection| {
11946 if !selection.is_empty() {
11947 return;
11948 }
11949
11950 let mut head = selection.head();
11951 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11952 if head.column() == display_map.line_len(head.row()) {
11953 transpose_offset = display_map
11954 .buffer_snapshot
11955 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11956 }
11957
11958 if transpose_offset == 0 {
11959 return;
11960 }
11961
11962 *head.column_mut() += 1;
11963 head = display_map.clip_point(head, Bias::Right);
11964 let goal = SelectionGoal::HorizontalPosition(
11965 display_map
11966 .x_for_display_point(head, text_layout_details)
11967 .into(),
11968 );
11969 selection.collapse_to(head, goal);
11970
11971 let transpose_start = display_map
11972 .buffer_snapshot
11973 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11974 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11975 let transpose_end = display_map
11976 .buffer_snapshot
11977 .clip_offset(transpose_offset + 1, Bias::Right);
11978 if let Some(ch) =
11979 display_map.buffer_snapshot.chars_at(transpose_start).next()
11980 {
11981 edits.push((transpose_start..transpose_offset, String::new()));
11982 edits.push((transpose_end..transpose_end, ch.to_string()));
11983 }
11984 }
11985 });
11986 edits
11987 });
11988 this.buffer
11989 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11990 let selections = this.selections.all::<usize>(cx);
11991 this.change_selections(Default::default(), window, cx, |s| {
11992 s.select(selections);
11993 });
11994 });
11995 }
11996
11997 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11998 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11999 if self.mode.is_single_line() {
12000 cx.propagate();
12001 return;
12002 }
12003
12004 self.rewrap_impl(RewrapOptions::default(), cx)
12005 }
12006
12007 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12008 let buffer = self.buffer.read(cx).snapshot(cx);
12009 let selections = self.selections.all::<Point>(cx);
12010
12011 #[derive(Clone, Debug, PartialEq)]
12012 enum CommentFormat {
12013 /// single line comment, with prefix for line
12014 Line(String),
12015 /// single line within a block comment, with prefix for line
12016 BlockLine(String),
12017 /// a single line of a block comment that includes the initial delimiter
12018 BlockCommentWithStart(BlockCommentConfig),
12019 /// a single line of a block comment that includes the ending delimiter
12020 BlockCommentWithEnd(BlockCommentConfig),
12021 }
12022
12023 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12024 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12025 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12026 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12027 .peekable();
12028
12029 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12030 row
12031 } else {
12032 return Vec::new();
12033 };
12034
12035 let language_settings = buffer.language_settings_at(selection.head(), cx);
12036 let language_scope = buffer.language_scope_at(selection.head());
12037
12038 let indent_and_prefix_for_row =
12039 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12040 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12041 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12042 &language_scope
12043 {
12044 let indent_end = Point::new(row, indent.len);
12045 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12046 let line_text_after_indent = buffer
12047 .text_for_range(indent_end..line_end)
12048 .collect::<String>();
12049
12050 let is_within_comment_override = buffer
12051 .language_scope_at(indent_end)
12052 .is_some_and(|scope| scope.override_name() == Some("comment"));
12053 let comment_delimiters = if is_within_comment_override {
12054 // we are within a comment syntax node, but we don't
12055 // yet know what kind of comment: block, doc or line
12056 match (
12057 language_scope.documentation_comment(),
12058 language_scope.block_comment(),
12059 ) {
12060 (Some(config), _) | (_, Some(config))
12061 if buffer.contains_str_at(indent_end, &config.start) =>
12062 {
12063 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12064 }
12065 (Some(config), _) | (_, Some(config))
12066 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12067 {
12068 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12069 }
12070 (Some(config), _) | (_, Some(config))
12071 if buffer.contains_str_at(indent_end, &config.prefix) =>
12072 {
12073 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12074 }
12075 (_, _) => language_scope
12076 .line_comment_prefixes()
12077 .iter()
12078 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12079 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12080 }
12081 } else {
12082 // we not in an overridden comment node, but we may
12083 // be within a non-overridden line comment node
12084 language_scope
12085 .line_comment_prefixes()
12086 .iter()
12087 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12088 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12089 };
12090
12091 let rewrap_prefix = language_scope
12092 .rewrap_prefixes()
12093 .iter()
12094 .find_map(|prefix_regex| {
12095 prefix_regex.find(&line_text_after_indent).map(|mat| {
12096 if mat.start() == 0 {
12097 Some(mat.as_str().to_string())
12098 } else {
12099 None
12100 }
12101 })
12102 })
12103 .flatten();
12104 (comment_delimiters, rewrap_prefix)
12105 } else {
12106 (None, None)
12107 };
12108 (indent, comment_prefix, rewrap_prefix)
12109 };
12110
12111 let mut ranges = Vec::new();
12112 let from_empty_selection = selection.is_empty();
12113
12114 let mut current_range_start = first_row;
12115 let mut prev_row = first_row;
12116 let (
12117 mut current_range_indent,
12118 mut current_range_comment_delimiters,
12119 mut current_range_rewrap_prefix,
12120 ) = indent_and_prefix_for_row(first_row);
12121
12122 for row in non_blank_rows_iter.skip(1) {
12123 let has_paragraph_break = row > prev_row + 1;
12124
12125 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12126 indent_and_prefix_for_row(row);
12127
12128 let has_indent_change = row_indent != current_range_indent;
12129 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12130
12131 let has_boundary_change = has_comment_change
12132 || row_rewrap_prefix.is_some()
12133 || (has_indent_change && current_range_comment_delimiters.is_some());
12134
12135 if has_paragraph_break || has_boundary_change {
12136 ranges.push((
12137 language_settings.clone(),
12138 Point::new(current_range_start, 0)
12139 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12140 current_range_indent,
12141 current_range_comment_delimiters.clone(),
12142 current_range_rewrap_prefix.clone(),
12143 from_empty_selection,
12144 ));
12145 current_range_start = row;
12146 current_range_indent = row_indent;
12147 current_range_comment_delimiters = row_comment_delimiters;
12148 current_range_rewrap_prefix = row_rewrap_prefix;
12149 }
12150 prev_row = row;
12151 }
12152
12153 ranges.push((
12154 language_settings.clone(),
12155 Point::new(current_range_start, 0)
12156 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12157 current_range_indent,
12158 current_range_comment_delimiters,
12159 current_range_rewrap_prefix,
12160 from_empty_selection,
12161 ));
12162
12163 ranges
12164 });
12165
12166 let mut edits = Vec::new();
12167 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12168
12169 for (
12170 language_settings,
12171 wrap_range,
12172 mut indent_size,
12173 comment_prefix,
12174 rewrap_prefix,
12175 from_empty_selection,
12176 ) in wrap_ranges
12177 {
12178 let mut start_row = wrap_range.start.row;
12179 let mut end_row = wrap_range.end.row;
12180
12181 // Skip selections that overlap with a range that has already been rewrapped.
12182 let selection_range = start_row..end_row;
12183 if rewrapped_row_ranges
12184 .iter()
12185 .any(|range| range.overlaps(&selection_range))
12186 {
12187 continue;
12188 }
12189
12190 let tab_size = language_settings.tab_size;
12191
12192 let (line_prefix, inside_comment) = match &comment_prefix {
12193 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12194 (Some(prefix.as_str()), true)
12195 }
12196 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12197 (Some(prefix.as_ref()), true)
12198 }
12199 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12200 start: _,
12201 end: _,
12202 prefix,
12203 tab_size,
12204 })) => {
12205 indent_size.len += tab_size;
12206 (Some(prefix.as_ref()), true)
12207 }
12208 None => (None, false),
12209 };
12210 let indent_prefix = indent_size.chars().collect::<String>();
12211 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12212
12213 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12214 RewrapBehavior::InComments => inside_comment,
12215 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12216 RewrapBehavior::Anywhere => true,
12217 };
12218
12219 let should_rewrap = options.override_language_settings
12220 || allow_rewrap_based_on_language
12221 || self.hard_wrap.is_some();
12222 if !should_rewrap {
12223 continue;
12224 }
12225
12226 if from_empty_selection {
12227 'expand_upwards: while start_row > 0 {
12228 let prev_row = start_row - 1;
12229 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12230 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12231 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12232 {
12233 start_row = prev_row;
12234 } else {
12235 break 'expand_upwards;
12236 }
12237 }
12238
12239 'expand_downwards: while end_row < buffer.max_point().row {
12240 let next_row = end_row + 1;
12241 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12242 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12243 && !buffer.is_line_blank(MultiBufferRow(next_row))
12244 {
12245 end_row = next_row;
12246 } else {
12247 break 'expand_downwards;
12248 }
12249 }
12250 }
12251
12252 let start = Point::new(start_row, 0);
12253 let start_offset = ToOffset::to_offset(&start, &buffer);
12254 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12255 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12256 let mut first_line_delimiter = None;
12257 let mut last_line_delimiter = None;
12258 let Some(lines_without_prefixes) = selection_text
12259 .lines()
12260 .enumerate()
12261 .map(|(ix, line)| {
12262 let line_trimmed = line.trim_start();
12263 if rewrap_prefix.is_some() && ix > 0 {
12264 Ok(line_trimmed)
12265 } else if let Some(
12266 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12267 start,
12268 prefix,
12269 end,
12270 tab_size,
12271 })
12272 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12273 start,
12274 prefix,
12275 end,
12276 tab_size,
12277 }),
12278 ) = &comment_prefix
12279 {
12280 let line_trimmed = line_trimmed
12281 .strip_prefix(start.as_ref())
12282 .map(|s| {
12283 let mut indent_size = indent_size;
12284 indent_size.len -= tab_size;
12285 let indent_prefix: String = indent_size.chars().collect();
12286 first_line_delimiter = Some((indent_prefix, start));
12287 s.trim_start()
12288 })
12289 .unwrap_or(line_trimmed);
12290 let line_trimmed = line_trimmed
12291 .strip_suffix(end.as_ref())
12292 .map(|s| {
12293 last_line_delimiter = Some(end);
12294 s.trim_end()
12295 })
12296 .unwrap_or(line_trimmed);
12297 let line_trimmed = line_trimmed
12298 .strip_prefix(prefix.as_ref())
12299 .unwrap_or(line_trimmed);
12300 Ok(line_trimmed)
12301 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12302 line_trimmed.strip_prefix(prefix).with_context(|| {
12303 format!("line did not start with prefix {prefix:?}: {line:?}")
12304 })
12305 } else {
12306 line_trimmed
12307 .strip_prefix(&line_prefix.trim_start())
12308 .with_context(|| {
12309 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12310 })
12311 }
12312 })
12313 .collect::<Result<Vec<_>, _>>()
12314 .log_err()
12315 else {
12316 continue;
12317 };
12318
12319 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12320 buffer
12321 .language_settings_at(Point::new(start_row, 0), cx)
12322 .preferred_line_length as usize
12323 });
12324
12325 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12326 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12327 } else {
12328 line_prefix.clone()
12329 };
12330
12331 let wrapped_text = {
12332 let mut wrapped_text = wrap_with_prefix(
12333 line_prefix,
12334 subsequent_lines_prefix,
12335 lines_without_prefixes.join("\n"),
12336 wrap_column,
12337 tab_size,
12338 options.preserve_existing_whitespace,
12339 );
12340
12341 if let Some((indent, delimiter)) = first_line_delimiter {
12342 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12343 }
12344 if let Some(last_line) = last_line_delimiter {
12345 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12346 }
12347
12348 wrapped_text
12349 };
12350
12351 // TODO: should always use char-based diff while still supporting cursor behavior that
12352 // matches vim.
12353 let mut diff_options = DiffOptions::default();
12354 if options.override_language_settings {
12355 diff_options.max_word_diff_len = 0;
12356 diff_options.max_word_diff_line_count = 0;
12357 } else {
12358 diff_options.max_word_diff_len = usize::MAX;
12359 diff_options.max_word_diff_line_count = usize::MAX;
12360 }
12361
12362 for (old_range, new_text) in
12363 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12364 {
12365 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12366 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12367 edits.push((edit_start..edit_end, new_text));
12368 }
12369
12370 rewrapped_row_ranges.push(start_row..=end_row);
12371 }
12372
12373 self.buffer
12374 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12375 }
12376
12377 pub fn cut_common(
12378 &mut self,
12379 cut_no_selection_line: bool,
12380 window: &mut Window,
12381 cx: &mut Context<Self>,
12382 ) -> ClipboardItem {
12383 let mut text = String::new();
12384 let buffer = self.buffer.read(cx).snapshot(cx);
12385 let mut selections = self.selections.all::<Point>(cx);
12386 let mut clipboard_selections = Vec::with_capacity(selections.len());
12387 {
12388 let max_point = buffer.max_point();
12389 let mut is_first = true;
12390 for selection in &mut selections {
12391 let is_entire_line =
12392 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12393 if is_entire_line {
12394 selection.start = Point::new(selection.start.row, 0);
12395 if !selection.is_empty() && selection.end.column == 0 {
12396 selection.end = cmp::min(max_point, selection.end);
12397 } else {
12398 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12399 }
12400 selection.goal = SelectionGoal::None;
12401 }
12402 if is_first {
12403 is_first = false;
12404 } else {
12405 text += "\n";
12406 }
12407 let mut len = 0;
12408 for chunk in buffer.text_for_range(selection.start..selection.end) {
12409 text.push_str(chunk);
12410 len += chunk.len();
12411 }
12412 clipboard_selections.push(ClipboardSelection {
12413 len,
12414 is_entire_line,
12415 first_line_indent: buffer
12416 .indent_size_for_line(MultiBufferRow(selection.start.row))
12417 .len,
12418 });
12419 }
12420 }
12421
12422 self.transact(window, cx, |this, window, cx| {
12423 this.change_selections(Default::default(), window, cx, |s| {
12424 s.select(selections);
12425 });
12426 this.insert("", window, cx);
12427 });
12428 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12429 }
12430
12431 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12433 let item = self.cut_common(true, window, cx);
12434 cx.write_to_clipboard(item);
12435 }
12436
12437 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12439 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12440 s.move_with(|snapshot, sel| {
12441 if sel.is_empty() {
12442 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12443 }
12444 if sel.is_empty() {
12445 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12446 }
12447 });
12448 });
12449 let item = self.cut_common(false, window, cx);
12450 cx.set_global(KillRing(item))
12451 }
12452
12453 pub fn kill_ring_yank(
12454 &mut self,
12455 _: &KillRingYank,
12456 window: &mut Window,
12457 cx: &mut Context<Self>,
12458 ) {
12459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12460 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12461 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12462 (kill_ring.text().to_string(), kill_ring.metadata_json())
12463 } else {
12464 return;
12465 }
12466 } else {
12467 return;
12468 };
12469 self.do_paste(&text, metadata, false, window, cx);
12470 }
12471
12472 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12473 self.do_copy(true, cx);
12474 }
12475
12476 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12477 self.do_copy(false, cx);
12478 }
12479
12480 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12481 let selections = self.selections.all::<Point>(cx);
12482 let buffer = self.buffer.read(cx).read(cx);
12483 let mut text = String::new();
12484
12485 let mut clipboard_selections = Vec::with_capacity(selections.len());
12486 {
12487 let max_point = buffer.max_point();
12488 let mut is_first = true;
12489 for selection in &selections {
12490 let mut start = selection.start;
12491 let mut end = selection.end;
12492 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12493 if is_entire_line {
12494 start = Point::new(start.row, 0);
12495 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12496 }
12497
12498 let mut trimmed_selections = Vec::new();
12499 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12500 let row = MultiBufferRow(start.row);
12501 let first_indent = buffer.indent_size_for_line(row);
12502 if first_indent.len == 0 || start.column > first_indent.len {
12503 trimmed_selections.push(start..end);
12504 } else {
12505 trimmed_selections.push(
12506 Point::new(row.0, first_indent.len)
12507 ..Point::new(row.0, buffer.line_len(row)),
12508 );
12509 for row in start.row + 1..=end.row {
12510 let mut line_len = buffer.line_len(MultiBufferRow(row));
12511 if row == end.row {
12512 line_len = end.column;
12513 }
12514 if line_len == 0 {
12515 trimmed_selections
12516 .push(Point::new(row, 0)..Point::new(row, line_len));
12517 continue;
12518 }
12519 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12520 if row_indent_size.len >= first_indent.len {
12521 trimmed_selections.push(
12522 Point::new(row, first_indent.len)..Point::new(row, line_len),
12523 );
12524 } else {
12525 trimmed_selections.clear();
12526 trimmed_selections.push(start..end);
12527 break;
12528 }
12529 }
12530 }
12531 } else {
12532 trimmed_selections.push(start..end);
12533 }
12534
12535 for trimmed_range in trimmed_selections {
12536 if is_first {
12537 is_first = false;
12538 } else {
12539 text += "\n";
12540 }
12541 let mut len = 0;
12542 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12543 text.push_str(chunk);
12544 len += chunk.len();
12545 }
12546 clipboard_selections.push(ClipboardSelection {
12547 len,
12548 is_entire_line,
12549 first_line_indent: buffer
12550 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12551 .len,
12552 });
12553 }
12554 }
12555 }
12556
12557 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12558 text,
12559 clipboard_selections,
12560 ));
12561 }
12562
12563 pub fn do_paste(
12564 &mut self,
12565 text: &String,
12566 clipboard_selections: Option<Vec<ClipboardSelection>>,
12567 handle_entire_lines: bool,
12568 window: &mut Window,
12569 cx: &mut Context<Self>,
12570 ) {
12571 if self.read_only(cx) {
12572 return;
12573 }
12574
12575 let clipboard_text = Cow::Borrowed(text.as_str());
12576
12577 self.transact(window, cx, |this, window, cx| {
12578 let had_active_edit_prediction = this.has_active_edit_prediction();
12579 let old_selections = this.selections.all::<usize>(cx);
12580 let cursor_offset = this.selections.last::<usize>(cx).head();
12581
12582 if let Some(mut clipboard_selections) = clipboard_selections {
12583 let all_selections_were_entire_line =
12584 clipboard_selections.iter().all(|s| s.is_entire_line);
12585 let first_selection_indent_column =
12586 clipboard_selections.first().map(|s| s.first_line_indent);
12587 if clipboard_selections.len() != old_selections.len() {
12588 clipboard_selections.drain(..);
12589 }
12590 let mut auto_indent_on_paste = true;
12591
12592 this.buffer.update(cx, |buffer, cx| {
12593 let snapshot = buffer.read(cx);
12594 auto_indent_on_paste = snapshot
12595 .language_settings_at(cursor_offset, cx)
12596 .auto_indent_on_paste;
12597
12598 let mut start_offset = 0;
12599 let mut edits = Vec::new();
12600 let mut original_indent_columns = Vec::new();
12601 for (ix, selection) in old_selections.iter().enumerate() {
12602 let to_insert;
12603 let entire_line;
12604 let original_indent_column;
12605 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12606 let end_offset = start_offset + clipboard_selection.len;
12607 to_insert = &clipboard_text[start_offset..end_offset];
12608 entire_line = clipboard_selection.is_entire_line;
12609 start_offset = end_offset + 1;
12610 original_indent_column = Some(clipboard_selection.first_line_indent);
12611 } else {
12612 to_insert = &*clipboard_text;
12613 entire_line = all_selections_were_entire_line;
12614 original_indent_column = first_selection_indent_column
12615 }
12616
12617 let (range, to_insert) =
12618 if selection.is_empty() && handle_entire_lines && entire_line {
12619 // If the corresponding selection was empty when this slice of the
12620 // clipboard text was written, then the entire line containing the
12621 // selection was copied. If this selection is also currently empty,
12622 // then paste the line before the current line of the buffer.
12623 let column = selection.start.to_point(&snapshot).column as usize;
12624 let line_start = selection.start - column;
12625 (line_start..line_start, Cow::Borrowed(to_insert))
12626 } else {
12627 let language = snapshot.language_at(selection.head());
12628 let range = selection.range();
12629 if let Some(language) = language
12630 && language.name() == "Markdown".into()
12631 {
12632 edit_for_markdown_paste(
12633 &snapshot,
12634 range,
12635 to_insert,
12636 url::Url::parse(to_insert).ok(),
12637 )
12638 } else {
12639 (range, Cow::Borrowed(to_insert))
12640 }
12641 };
12642
12643 edits.push((range, to_insert));
12644 original_indent_columns.push(original_indent_column);
12645 }
12646 drop(snapshot);
12647
12648 buffer.edit(
12649 edits,
12650 if auto_indent_on_paste {
12651 Some(AutoindentMode::Block {
12652 original_indent_columns,
12653 })
12654 } else {
12655 None
12656 },
12657 cx,
12658 );
12659 });
12660
12661 let selections = this.selections.all::<usize>(cx);
12662 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12663 } else {
12664 let url = url::Url::parse(&clipboard_text).ok();
12665
12666 let auto_indent_mode = if !clipboard_text.is_empty() {
12667 Some(AutoindentMode::Block {
12668 original_indent_columns: Vec::new(),
12669 })
12670 } else {
12671 None
12672 };
12673
12674 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12675 let snapshot = buffer.snapshot(cx);
12676
12677 let anchors = old_selections
12678 .iter()
12679 .map(|s| {
12680 let anchor = snapshot.anchor_after(s.head());
12681 s.map(|_| anchor)
12682 })
12683 .collect::<Vec<_>>();
12684
12685 let mut edits = Vec::new();
12686
12687 for selection in old_selections.iter() {
12688 let language = snapshot.language_at(selection.head());
12689 let range = selection.range();
12690
12691 let (edit_range, edit_text) = if let Some(language) = language
12692 && language.name() == "Markdown".into()
12693 {
12694 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12695 } else {
12696 (range, clipboard_text.clone())
12697 };
12698
12699 edits.push((edit_range, edit_text));
12700 }
12701
12702 drop(snapshot);
12703 buffer.edit(edits, auto_indent_mode, cx);
12704
12705 anchors
12706 });
12707
12708 this.change_selections(Default::default(), window, cx, |s| {
12709 s.select_anchors(selection_anchors);
12710 });
12711 }
12712
12713 let trigger_in_words =
12714 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12715
12716 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12717 });
12718 }
12719
12720 pub fn diff_clipboard_with_selection(
12721 &mut self,
12722 _: &DiffClipboardWithSelection,
12723 window: &mut Window,
12724 cx: &mut Context<Self>,
12725 ) {
12726 let selections = self.selections.all::<usize>(cx);
12727
12728 if selections.is_empty() {
12729 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12730 return;
12731 };
12732
12733 let clipboard_text = match cx.read_from_clipboard() {
12734 Some(item) => match item.entries().first() {
12735 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12736 _ => None,
12737 },
12738 None => None,
12739 };
12740
12741 let Some(clipboard_text) = clipboard_text else {
12742 log::warn!("Clipboard doesn't contain text.");
12743 return;
12744 };
12745
12746 window.dispatch_action(
12747 Box::new(DiffClipboardWithSelectionData {
12748 clipboard_text,
12749 editor: cx.entity(),
12750 }),
12751 cx,
12752 );
12753 }
12754
12755 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12757 if let Some(item) = cx.read_from_clipboard() {
12758 let entries = item.entries();
12759
12760 match entries.first() {
12761 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12762 // of all the pasted entries.
12763 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12764 .do_paste(
12765 clipboard_string.text(),
12766 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12767 true,
12768 window,
12769 cx,
12770 ),
12771 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12772 }
12773 }
12774 }
12775
12776 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12777 if self.read_only(cx) {
12778 return;
12779 }
12780
12781 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12782
12783 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12784 if let Some((selections, _)) =
12785 self.selection_history.transaction(transaction_id).cloned()
12786 {
12787 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12788 s.select_anchors(selections.to_vec());
12789 });
12790 } else {
12791 log::error!(
12792 "No entry in selection_history found for undo. \
12793 This may correspond to a bug where undo does not update the selection. \
12794 If this is occurring, please add details to \
12795 https://github.com/zed-industries/zed/issues/22692"
12796 );
12797 }
12798 self.request_autoscroll(Autoscroll::fit(), cx);
12799 self.unmark_text(window, cx);
12800 self.refresh_edit_prediction(true, false, window, cx);
12801 cx.emit(EditorEvent::Edited { transaction_id });
12802 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12803 }
12804 }
12805
12806 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12807 if self.read_only(cx) {
12808 return;
12809 }
12810
12811 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12812
12813 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12814 if let Some((_, Some(selections))) =
12815 self.selection_history.transaction(transaction_id).cloned()
12816 {
12817 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12818 s.select_anchors(selections.to_vec());
12819 });
12820 } else {
12821 log::error!(
12822 "No entry in selection_history found for redo. \
12823 This may correspond to a bug where undo does not update the selection. \
12824 If this is occurring, please add details to \
12825 https://github.com/zed-industries/zed/issues/22692"
12826 );
12827 }
12828 self.request_autoscroll(Autoscroll::fit(), cx);
12829 self.unmark_text(window, cx);
12830 self.refresh_edit_prediction(true, false, window, cx);
12831 cx.emit(EditorEvent::Edited { transaction_id });
12832 }
12833 }
12834
12835 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12836 self.buffer
12837 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12838 }
12839
12840 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12841 self.buffer
12842 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12843 }
12844
12845 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12847 self.change_selections(Default::default(), window, cx, |s| {
12848 s.move_with(|map, selection| {
12849 let cursor = if selection.is_empty() {
12850 movement::left(map, selection.start)
12851 } else {
12852 selection.start
12853 };
12854 selection.collapse_to(cursor, SelectionGoal::None);
12855 });
12856 })
12857 }
12858
12859 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12860 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12861 self.change_selections(Default::default(), window, cx, |s| {
12862 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12863 })
12864 }
12865
12866 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12868 self.change_selections(Default::default(), window, cx, |s| {
12869 s.move_with(|map, selection| {
12870 let cursor = if selection.is_empty() {
12871 movement::right(map, selection.end)
12872 } else {
12873 selection.end
12874 };
12875 selection.collapse_to(cursor, SelectionGoal::None)
12876 });
12877 })
12878 }
12879
12880 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12882 self.change_selections(Default::default(), window, cx, |s| {
12883 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12884 });
12885 }
12886
12887 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12888 if self.take_rename(true, window, cx).is_some() {
12889 return;
12890 }
12891
12892 if self.mode.is_single_line() {
12893 cx.propagate();
12894 return;
12895 }
12896
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12898
12899 let text_layout_details = &self.text_layout_details(window);
12900 let selection_count = self.selections.count();
12901 let first_selection = self.selections.first_anchor();
12902
12903 self.change_selections(Default::default(), window, cx, |s| {
12904 s.move_with(|map, selection| {
12905 if !selection.is_empty() {
12906 selection.goal = SelectionGoal::None;
12907 }
12908 let (cursor, goal) = movement::up(
12909 map,
12910 selection.start,
12911 selection.goal,
12912 false,
12913 text_layout_details,
12914 );
12915 selection.collapse_to(cursor, goal);
12916 });
12917 });
12918
12919 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12920 {
12921 cx.propagate();
12922 }
12923 }
12924
12925 pub fn move_up_by_lines(
12926 &mut self,
12927 action: &MoveUpByLines,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 if self.take_rename(true, window, cx).is_some() {
12932 return;
12933 }
12934
12935 if self.mode.is_single_line() {
12936 cx.propagate();
12937 return;
12938 }
12939
12940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12941
12942 let text_layout_details = &self.text_layout_details(window);
12943
12944 self.change_selections(Default::default(), window, cx, |s| {
12945 s.move_with(|map, selection| {
12946 if !selection.is_empty() {
12947 selection.goal = SelectionGoal::None;
12948 }
12949 let (cursor, goal) = movement::up_by_rows(
12950 map,
12951 selection.start,
12952 action.lines,
12953 selection.goal,
12954 false,
12955 text_layout_details,
12956 );
12957 selection.collapse_to(cursor, goal);
12958 });
12959 })
12960 }
12961
12962 pub fn move_down_by_lines(
12963 &mut self,
12964 action: &MoveDownByLines,
12965 window: &mut Window,
12966 cx: &mut Context<Self>,
12967 ) {
12968 if self.take_rename(true, window, cx).is_some() {
12969 return;
12970 }
12971
12972 if self.mode.is_single_line() {
12973 cx.propagate();
12974 return;
12975 }
12976
12977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12978
12979 let text_layout_details = &self.text_layout_details(window);
12980
12981 self.change_selections(Default::default(), window, cx, |s| {
12982 s.move_with(|map, selection| {
12983 if !selection.is_empty() {
12984 selection.goal = SelectionGoal::None;
12985 }
12986 let (cursor, goal) = movement::down_by_rows(
12987 map,
12988 selection.start,
12989 action.lines,
12990 selection.goal,
12991 false,
12992 text_layout_details,
12993 );
12994 selection.collapse_to(cursor, goal);
12995 });
12996 })
12997 }
12998
12999 pub fn select_down_by_lines(
13000 &mut self,
13001 action: &SelectDownByLines,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13006 let text_layout_details = &self.text_layout_details(window);
13007 self.change_selections(Default::default(), window, cx, |s| {
13008 s.move_heads_with(|map, head, goal| {
13009 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13010 })
13011 })
13012 }
13013
13014 pub fn select_up_by_lines(
13015 &mut self,
13016 action: &SelectUpByLines,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13021 let text_layout_details = &self.text_layout_details(window);
13022 self.change_selections(Default::default(), window, cx, |s| {
13023 s.move_heads_with(|map, head, goal| {
13024 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13025 })
13026 })
13027 }
13028
13029 pub fn select_page_up(
13030 &mut self,
13031 _: &SelectPageUp,
13032 window: &mut Window,
13033 cx: &mut Context<Self>,
13034 ) {
13035 let Some(row_count) = self.visible_row_count() else {
13036 return;
13037 };
13038
13039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13040
13041 let text_layout_details = &self.text_layout_details(window);
13042
13043 self.change_selections(Default::default(), window, cx, |s| {
13044 s.move_heads_with(|map, head, goal| {
13045 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13046 })
13047 })
13048 }
13049
13050 pub fn move_page_up(
13051 &mut self,
13052 action: &MovePageUp,
13053 window: &mut Window,
13054 cx: &mut Context<Self>,
13055 ) {
13056 if self.take_rename(true, window, cx).is_some() {
13057 return;
13058 }
13059
13060 if self
13061 .context_menu
13062 .borrow_mut()
13063 .as_mut()
13064 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13065 .unwrap_or(false)
13066 {
13067 return;
13068 }
13069
13070 if matches!(self.mode, EditorMode::SingleLine) {
13071 cx.propagate();
13072 return;
13073 }
13074
13075 let Some(row_count) = self.visible_row_count() else {
13076 return;
13077 };
13078
13079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13080
13081 let effects = if action.center_cursor {
13082 SelectionEffects::scroll(Autoscroll::center())
13083 } else {
13084 SelectionEffects::default()
13085 };
13086
13087 let text_layout_details = &self.text_layout_details(window);
13088
13089 self.change_selections(effects, window, cx, |s| {
13090 s.move_with(|map, selection| {
13091 if !selection.is_empty() {
13092 selection.goal = SelectionGoal::None;
13093 }
13094 let (cursor, goal) = movement::up_by_rows(
13095 map,
13096 selection.end,
13097 row_count,
13098 selection.goal,
13099 false,
13100 text_layout_details,
13101 );
13102 selection.collapse_to(cursor, goal);
13103 });
13104 });
13105 }
13106
13107 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13109 let text_layout_details = &self.text_layout_details(window);
13110 self.change_selections(Default::default(), window, cx, |s| {
13111 s.move_heads_with(|map, head, goal| {
13112 movement::up(map, head, goal, false, text_layout_details)
13113 })
13114 })
13115 }
13116
13117 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13118 self.take_rename(true, window, cx);
13119
13120 if self.mode.is_single_line() {
13121 cx.propagate();
13122 return;
13123 }
13124
13125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13126
13127 let text_layout_details = &self.text_layout_details(window);
13128 let selection_count = self.selections.count();
13129 let first_selection = self.selections.first_anchor();
13130
13131 self.change_selections(Default::default(), window, cx, |s| {
13132 s.move_with(|map, selection| {
13133 if !selection.is_empty() {
13134 selection.goal = SelectionGoal::None;
13135 }
13136 let (cursor, goal) = movement::down(
13137 map,
13138 selection.end,
13139 selection.goal,
13140 false,
13141 text_layout_details,
13142 );
13143 selection.collapse_to(cursor, goal);
13144 });
13145 });
13146
13147 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13148 {
13149 cx.propagate();
13150 }
13151 }
13152
13153 pub fn select_page_down(
13154 &mut self,
13155 _: &SelectPageDown,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 let Some(row_count) = self.visible_row_count() else {
13160 return;
13161 };
13162
13163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13164
13165 let text_layout_details = &self.text_layout_details(window);
13166
13167 self.change_selections(Default::default(), window, cx, |s| {
13168 s.move_heads_with(|map, head, goal| {
13169 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13170 })
13171 })
13172 }
13173
13174 pub fn move_page_down(
13175 &mut self,
13176 action: &MovePageDown,
13177 window: &mut Window,
13178 cx: &mut Context<Self>,
13179 ) {
13180 if self.take_rename(true, window, cx).is_some() {
13181 return;
13182 }
13183
13184 if self
13185 .context_menu
13186 .borrow_mut()
13187 .as_mut()
13188 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13189 .unwrap_or(false)
13190 {
13191 return;
13192 }
13193
13194 if matches!(self.mode, EditorMode::SingleLine) {
13195 cx.propagate();
13196 return;
13197 }
13198
13199 let Some(row_count) = self.visible_row_count() else {
13200 return;
13201 };
13202
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13204
13205 let effects = if action.center_cursor {
13206 SelectionEffects::scroll(Autoscroll::center())
13207 } else {
13208 SelectionEffects::default()
13209 };
13210
13211 let text_layout_details = &self.text_layout_details(window);
13212 self.change_selections(effects, window, cx, |s| {
13213 s.move_with(|map, selection| {
13214 if !selection.is_empty() {
13215 selection.goal = SelectionGoal::None;
13216 }
13217 let (cursor, goal) = movement::down_by_rows(
13218 map,
13219 selection.end,
13220 row_count,
13221 selection.goal,
13222 false,
13223 text_layout_details,
13224 );
13225 selection.collapse_to(cursor, goal);
13226 });
13227 });
13228 }
13229
13230 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13232 let text_layout_details = &self.text_layout_details(window);
13233 self.change_selections(Default::default(), window, cx, |s| {
13234 s.move_heads_with(|map, head, goal| {
13235 movement::down(map, head, goal, false, text_layout_details)
13236 })
13237 });
13238 }
13239
13240 pub fn context_menu_first(
13241 &mut self,
13242 _: &ContextMenuFirst,
13243 window: &mut Window,
13244 cx: &mut Context<Self>,
13245 ) {
13246 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13247 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13248 }
13249 }
13250
13251 pub fn context_menu_prev(
13252 &mut self,
13253 _: &ContextMenuPrevious,
13254 window: &mut Window,
13255 cx: &mut Context<Self>,
13256 ) {
13257 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13258 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13259 }
13260 }
13261
13262 pub fn context_menu_next(
13263 &mut self,
13264 _: &ContextMenuNext,
13265 window: &mut Window,
13266 cx: &mut Context<Self>,
13267 ) {
13268 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13269 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13270 }
13271 }
13272
13273 pub fn context_menu_last(
13274 &mut self,
13275 _: &ContextMenuLast,
13276 window: &mut Window,
13277 cx: &mut Context<Self>,
13278 ) {
13279 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13280 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13281 }
13282 }
13283
13284 pub fn signature_help_prev(
13285 &mut self,
13286 _: &SignatureHelpPrevious,
13287 _: &mut Window,
13288 cx: &mut Context<Self>,
13289 ) {
13290 if let Some(popover) = self.signature_help_state.popover_mut() {
13291 if popover.current_signature == 0 {
13292 popover.current_signature = popover.signatures.len() - 1;
13293 } else {
13294 popover.current_signature -= 1;
13295 }
13296 cx.notify();
13297 }
13298 }
13299
13300 pub fn signature_help_next(
13301 &mut self,
13302 _: &SignatureHelpNext,
13303 _: &mut Window,
13304 cx: &mut Context<Self>,
13305 ) {
13306 if let Some(popover) = self.signature_help_state.popover_mut() {
13307 if popover.current_signature + 1 == popover.signatures.len() {
13308 popover.current_signature = 0;
13309 } else {
13310 popover.current_signature += 1;
13311 }
13312 cx.notify();
13313 }
13314 }
13315
13316 pub fn move_to_previous_word_start(
13317 &mut self,
13318 _: &MoveToPreviousWordStart,
13319 window: &mut Window,
13320 cx: &mut Context<Self>,
13321 ) {
13322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13323 self.change_selections(Default::default(), window, cx, |s| {
13324 s.move_cursors_with(|map, head, _| {
13325 (
13326 movement::previous_word_start(map, head),
13327 SelectionGoal::None,
13328 )
13329 });
13330 })
13331 }
13332
13333 pub fn move_to_previous_subword_start(
13334 &mut self,
13335 _: &MoveToPreviousSubwordStart,
13336 window: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) {
13339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13340 self.change_selections(Default::default(), window, cx, |s| {
13341 s.move_cursors_with(|map, head, _| {
13342 (
13343 movement::previous_subword_start(map, head),
13344 SelectionGoal::None,
13345 )
13346 });
13347 })
13348 }
13349
13350 pub fn select_to_previous_word_start(
13351 &mut self,
13352 _: &SelectToPreviousWordStart,
13353 window: &mut Window,
13354 cx: &mut Context<Self>,
13355 ) {
13356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13357 self.change_selections(Default::default(), window, cx, |s| {
13358 s.move_heads_with(|map, head, _| {
13359 (
13360 movement::previous_word_start(map, head),
13361 SelectionGoal::None,
13362 )
13363 });
13364 })
13365 }
13366
13367 pub fn select_to_previous_subword_start(
13368 &mut self,
13369 _: &SelectToPreviousSubwordStart,
13370 window: &mut Window,
13371 cx: &mut Context<Self>,
13372 ) {
13373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13374 self.change_selections(Default::default(), window, cx, |s| {
13375 s.move_heads_with(|map, head, _| {
13376 (
13377 movement::previous_subword_start(map, head),
13378 SelectionGoal::None,
13379 )
13380 });
13381 })
13382 }
13383
13384 pub fn delete_to_previous_word_start(
13385 &mut self,
13386 action: &DeleteToPreviousWordStart,
13387 window: &mut Window,
13388 cx: &mut Context<Self>,
13389 ) {
13390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13391 self.transact(window, cx, |this, window, cx| {
13392 this.select_autoclose_pair(window, cx);
13393 this.change_selections(Default::default(), window, cx, |s| {
13394 s.move_with(|map, selection| {
13395 if selection.is_empty() {
13396 let mut cursor = if action.ignore_newlines {
13397 movement::previous_word_start(map, selection.head())
13398 } else {
13399 movement::previous_word_start_or_newline(map, selection.head())
13400 };
13401 cursor = movement::adjust_greedy_deletion(
13402 map,
13403 selection.head(),
13404 cursor,
13405 action.ignore_brackets,
13406 );
13407 selection.set_head(cursor, SelectionGoal::None);
13408 }
13409 });
13410 });
13411 this.insert("", window, cx);
13412 });
13413 }
13414
13415 pub fn delete_to_previous_subword_start(
13416 &mut self,
13417 _: &DeleteToPreviousSubwordStart,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13422 self.transact(window, cx, |this, window, cx| {
13423 this.select_autoclose_pair(window, cx);
13424 this.change_selections(Default::default(), window, cx, |s| {
13425 s.move_with(|map, selection| {
13426 if selection.is_empty() {
13427 let mut cursor = movement::previous_subword_start(map, selection.head());
13428 cursor =
13429 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13430 selection.set_head(cursor, SelectionGoal::None);
13431 }
13432 });
13433 });
13434 this.insert("", window, cx);
13435 });
13436 }
13437
13438 pub fn move_to_next_word_end(
13439 &mut self,
13440 _: &MoveToNextWordEnd,
13441 window: &mut Window,
13442 cx: &mut Context<Self>,
13443 ) {
13444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13445 self.change_selections(Default::default(), window, cx, |s| {
13446 s.move_cursors_with(|map, head, _| {
13447 (movement::next_word_end(map, head), SelectionGoal::None)
13448 });
13449 })
13450 }
13451
13452 pub fn move_to_next_subword_end(
13453 &mut self,
13454 _: &MoveToNextSubwordEnd,
13455 window: &mut Window,
13456 cx: &mut Context<Self>,
13457 ) {
13458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13459 self.change_selections(Default::default(), window, cx, |s| {
13460 s.move_cursors_with(|map, head, _| {
13461 (movement::next_subword_end(map, head), SelectionGoal::None)
13462 });
13463 })
13464 }
13465
13466 pub fn select_to_next_word_end(
13467 &mut self,
13468 _: &SelectToNextWordEnd,
13469 window: &mut Window,
13470 cx: &mut Context<Self>,
13471 ) {
13472 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13473 self.change_selections(Default::default(), window, cx, |s| {
13474 s.move_heads_with(|map, head, _| {
13475 (movement::next_word_end(map, head), SelectionGoal::None)
13476 });
13477 })
13478 }
13479
13480 pub fn select_to_next_subword_end(
13481 &mut self,
13482 _: &SelectToNextSubwordEnd,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13487 self.change_selections(Default::default(), window, cx, |s| {
13488 s.move_heads_with(|map, head, _| {
13489 (movement::next_subword_end(map, head), SelectionGoal::None)
13490 });
13491 })
13492 }
13493
13494 pub fn delete_to_next_word_end(
13495 &mut self,
13496 action: &DeleteToNextWordEnd,
13497 window: &mut Window,
13498 cx: &mut Context<Self>,
13499 ) {
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13501 self.transact(window, cx, |this, window, cx| {
13502 this.change_selections(Default::default(), window, cx, |s| {
13503 s.move_with(|map, selection| {
13504 if selection.is_empty() {
13505 let mut cursor = if action.ignore_newlines {
13506 movement::next_word_end(map, selection.head())
13507 } else {
13508 movement::next_word_end_or_newline(map, selection.head())
13509 };
13510 cursor = movement::adjust_greedy_deletion(
13511 map,
13512 selection.head(),
13513 cursor,
13514 action.ignore_brackets,
13515 );
13516 selection.set_head(cursor, SelectionGoal::None);
13517 }
13518 });
13519 });
13520 this.insert("", window, cx);
13521 });
13522 }
13523
13524 pub fn delete_to_next_subword_end(
13525 &mut self,
13526 _: &DeleteToNextSubwordEnd,
13527 window: &mut Window,
13528 cx: &mut Context<Self>,
13529 ) {
13530 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13531 self.transact(window, cx, |this, window, cx| {
13532 this.change_selections(Default::default(), window, cx, |s| {
13533 s.move_with(|map, selection| {
13534 if selection.is_empty() {
13535 let mut cursor = movement::next_subword_end(map, selection.head());
13536 cursor =
13537 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13538 selection.set_head(cursor, SelectionGoal::None);
13539 }
13540 });
13541 });
13542 this.insert("", window, cx);
13543 });
13544 }
13545
13546 pub fn move_to_beginning_of_line(
13547 &mut self,
13548 action: &MoveToBeginningOfLine,
13549 window: &mut Window,
13550 cx: &mut Context<Self>,
13551 ) {
13552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13553 self.change_selections(Default::default(), window, cx, |s| {
13554 s.move_cursors_with(|map, head, _| {
13555 (
13556 movement::indented_line_beginning(
13557 map,
13558 head,
13559 action.stop_at_soft_wraps,
13560 action.stop_at_indent,
13561 ),
13562 SelectionGoal::None,
13563 )
13564 });
13565 })
13566 }
13567
13568 pub fn select_to_beginning_of_line(
13569 &mut self,
13570 action: &SelectToBeginningOfLine,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) {
13574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13575 self.change_selections(Default::default(), window, cx, |s| {
13576 s.move_heads_with(|map, head, _| {
13577 (
13578 movement::indented_line_beginning(
13579 map,
13580 head,
13581 action.stop_at_soft_wraps,
13582 action.stop_at_indent,
13583 ),
13584 SelectionGoal::None,
13585 )
13586 });
13587 });
13588 }
13589
13590 pub fn delete_to_beginning_of_line(
13591 &mut self,
13592 action: &DeleteToBeginningOfLine,
13593 window: &mut Window,
13594 cx: &mut Context<Self>,
13595 ) {
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13597 self.transact(window, cx, |this, window, cx| {
13598 this.change_selections(Default::default(), window, cx, |s| {
13599 s.move_with(|_, selection| {
13600 selection.reversed = true;
13601 });
13602 });
13603
13604 this.select_to_beginning_of_line(
13605 &SelectToBeginningOfLine {
13606 stop_at_soft_wraps: false,
13607 stop_at_indent: action.stop_at_indent,
13608 },
13609 window,
13610 cx,
13611 );
13612 this.backspace(&Backspace, window, cx);
13613 });
13614 }
13615
13616 pub fn move_to_end_of_line(
13617 &mut self,
13618 action: &MoveToEndOfLine,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13623 self.change_selections(Default::default(), window, cx, |s| {
13624 s.move_cursors_with(|map, head, _| {
13625 (
13626 movement::line_end(map, head, action.stop_at_soft_wraps),
13627 SelectionGoal::None,
13628 )
13629 });
13630 })
13631 }
13632
13633 pub fn select_to_end_of_line(
13634 &mut self,
13635 action: &SelectToEndOfLine,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13640 self.change_selections(Default::default(), window, cx, |s| {
13641 s.move_heads_with(|map, head, _| {
13642 (
13643 movement::line_end(map, head, action.stop_at_soft_wraps),
13644 SelectionGoal::None,
13645 )
13646 });
13647 })
13648 }
13649
13650 pub fn delete_to_end_of_line(
13651 &mut self,
13652 _: &DeleteToEndOfLine,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13657 self.transact(window, cx, |this, window, cx| {
13658 this.select_to_end_of_line(
13659 &SelectToEndOfLine {
13660 stop_at_soft_wraps: false,
13661 },
13662 window,
13663 cx,
13664 );
13665 this.delete(&Delete, window, cx);
13666 });
13667 }
13668
13669 pub fn cut_to_end_of_line(
13670 &mut self,
13671 action: &CutToEndOfLine,
13672 window: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13676 self.transact(window, cx, |this, window, cx| {
13677 this.select_to_end_of_line(
13678 &SelectToEndOfLine {
13679 stop_at_soft_wraps: false,
13680 },
13681 window,
13682 cx,
13683 );
13684 if !action.stop_at_newlines {
13685 this.change_selections(Default::default(), window, cx, |s| {
13686 s.move_with(|_, sel| {
13687 if sel.is_empty() {
13688 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13689 }
13690 });
13691 });
13692 }
13693 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13694 let item = this.cut_common(false, window, cx);
13695 cx.write_to_clipboard(item);
13696 });
13697 }
13698
13699 pub fn move_to_start_of_paragraph(
13700 &mut self,
13701 _: &MoveToStartOfParagraph,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 if matches!(self.mode, EditorMode::SingleLine) {
13706 cx.propagate();
13707 return;
13708 }
13709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13710 self.change_selections(Default::default(), window, cx, |s| {
13711 s.move_with(|map, selection| {
13712 selection.collapse_to(
13713 movement::start_of_paragraph(map, selection.head(), 1),
13714 SelectionGoal::None,
13715 )
13716 });
13717 })
13718 }
13719
13720 pub fn move_to_end_of_paragraph(
13721 &mut self,
13722 _: &MoveToEndOfParagraph,
13723 window: &mut Window,
13724 cx: &mut Context<Self>,
13725 ) {
13726 if matches!(self.mode, EditorMode::SingleLine) {
13727 cx.propagate();
13728 return;
13729 }
13730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13731 self.change_selections(Default::default(), window, cx, |s| {
13732 s.move_with(|map, selection| {
13733 selection.collapse_to(
13734 movement::end_of_paragraph(map, selection.head(), 1),
13735 SelectionGoal::None,
13736 )
13737 });
13738 })
13739 }
13740
13741 pub fn select_to_start_of_paragraph(
13742 &mut self,
13743 _: &SelectToStartOfParagraph,
13744 window: &mut Window,
13745 cx: &mut Context<Self>,
13746 ) {
13747 if matches!(self.mode, EditorMode::SingleLine) {
13748 cx.propagate();
13749 return;
13750 }
13751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13752 self.change_selections(Default::default(), window, cx, |s| {
13753 s.move_heads_with(|map, head, _| {
13754 (
13755 movement::start_of_paragraph(map, head, 1),
13756 SelectionGoal::None,
13757 )
13758 });
13759 })
13760 }
13761
13762 pub fn select_to_end_of_paragraph(
13763 &mut self,
13764 _: &SelectToEndOfParagraph,
13765 window: &mut Window,
13766 cx: &mut Context<Self>,
13767 ) {
13768 if matches!(self.mode, EditorMode::SingleLine) {
13769 cx.propagate();
13770 return;
13771 }
13772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13773 self.change_selections(Default::default(), window, cx, |s| {
13774 s.move_heads_with(|map, head, _| {
13775 (
13776 movement::end_of_paragraph(map, head, 1),
13777 SelectionGoal::None,
13778 )
13779 });
13780 })
13781 }
13782
13783 pub fn move_to_start_of_excerpt(
13784 &mut self,
13785 _: &MoveToStartOfExcerpt,
13786 window: &mut Window,
13787 cx: &mut Context<Self>,
13788 ) {
13789 if matches!(self.mode, EditorMode::SingleLine) {
13790 cx.propagate();
13791 return;
13792 }
13793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13794 self.change_selections(Default::default(), window, cx, |s| {
13795 s.move_with(|map, selection| {
13796 selection.collapse_to(
13797 movement::start_of_excerpt(
13798 map,
13799 selection.head(),
13800 workspace::searchable::Direction::Prev,
13801 ),
13802 SelectionGoal::None,
13803 )
13804 });
13805 })
13806 }
13807
13808 pub fn move_to_start_of_next_excerpt(
13809 &mut self,
13810 _: &MoveToStartOfNextExcerpt,
13811 window: &mut Window,
13812 cx: &mut Context<Self>,
13813 ) {
13814 if matches!(self.mode, EditorMode::SingleLine) {
13815 cx.propagate();
13816 return;
13817 }
13818
13819 self.change_selections(Default::default(), window, cx, |s| {
13820 s.move_with(|map, selection| {
13821 selection.collapse_to(
13822 movement::start_of_excerpt(
13823 map,
13824 selection.head(),
13825 workspace::searchable::Direction::Next,
13826 ),
13827 SelectionGoal::None,
13828 )
13829 });
13830 })
13831 }
13832
13833 pub fn move_to_end_of_excerpt(
13834 &mut self,
13835 _: &MoveToEndOfExcerpt,
13836 window: &mut Window,
13837 cx: &mut Context<Self>,
13838 ) {
13839 if matches!(self.mode, EditorMode::SingleLine) {
13840 cx.propagate();
13841 return;
13842 }
13843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13844 self.change_selections(Default::default(), window, cx, |s| {
13845 s.move_with(|map, selection| {
13846 selection.collapse_to(
13847 movement::end_of_excerpt(
13848 map,
13849 selection.head(),
13850 workspace::searchable::Direction::Next,
13851 ),
13852 SelectionGoal::None,
13853 )
13854 });
13855 })
13856 }
13857
13858 pub fn move_to_end_of_previous_excerpt(
13859 &mut self,
13860 _: &MoveToEndOfPreviousExcerpt,
13861 window: &mut Window,
13862 cx: &mut Context<Self>,
13863 ) {
13864 if matches!(self.mode, EditorMode::SingleLine) {
13865 cx.propagate();
13866 return;
13867 }
13868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.move_with(|map, selection| {
13871 selection.collapse_to(
13872 movement::end_of_excerpt(
13873 map,
13874 selection.head(),
13875 workspace::searchable::Direction::Prev,
13876 ),
13877 SelectionGoal::None,
13878 )
13879 });
13880 })
13881 }
13882
13883 pub fn select_to_start_of_excerpt(
13884 &mut self,
13885 _: &SelectToStartOfExcerpt,
13886 window: &mut Window,
13887 cx: &mut Context<Self>,
13888 ) {
13889 if matches!(self.mode, EditorMode::SingleLine) {
13890 cx.propagate();
13891 return;
13892 }
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 self.change_selections(Default::default(), window, cx, |s| {
13895 s.move_heads_with(|map, head, _| {
13896 (
13897 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13898 SelectionGoal::None,
13899 )
13900 });
13901 })
13902 }
13903
13904 pub fn select_to_start_of_next_excerpt(
13905 &mut self,
13906 _: &SelectToStartOfNextExcerpt,
13907 window: &mut Window,
13908 cx: &mut Context<Self>,
13909 ) {
13910 if matches!(self.mode, EditorMode::SingleLine) {
13911 cx.propagate();
13912 return;
13913 }
13914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13915 self.change_selections(Default::default(), window, cx, |s| {
13916 s.move_heads_with(|map, head, _| {
13917 (
13918 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13919 SelectionGoal::None,
13920 )
13921 });
13922 })
13923 }
13924
13925 pub fn select_to_end_of_excerpt(
13926 &mut self,
13927 _: &SelectToEndOfExcerpt,
13928 window: &mut Window,
13929 cx: &mut Context<Self>,
13930 ) {
13931 if matches!(self.mode, EditorMode::SingleLine) {
13932 cx.propagate();
13933 return;
13934 }
13935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13936 self.change_selections(Default::default(), window, cx, |s| {
13937 s.move_heads_with(|map, head, _| {
13938 (
13939 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13940 SelectionGoal::None,
13941 )
13942 });
13943 })
13944 }
13945
13946 pub fn select_to_end_of_previous_excerpt(
13947 &mut self,
13948 _: &SelectToEndOfPreviousExcerpt,
13949 window: &mut Window,
13950 cx: &mut Context<Self>,
13951 ) {
13952 if matches!(self.mode, EditorMode::SingleLine) {
13953 cx.propagate();
13954 return;
13955 }
13956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13957 self.change_selections(Default::default(), window, cx, |s| {
13958 s.move_heads_with(|map, head, _| {
13959 (
13960 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13961 SelectionGoal::None,
13962 )
13963 });
13964 })
13965 }
13966
13967 pub fn move_to_beginning(
13968 &mut self,
13969 _: &MoveToBeginning,
13970 window: &mut Window,
13971 cx: &mut Context<Self>,
13972 ) {
13973 if matches!(self.mode, EditorMode::SingleLine) {
13974 cx.propagate();
13975 return;
13976 }
13977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13978 self.change_selections(Default::default(), window, cx, |s| {
13979 s.select_ranges(vec![0..0]);
13980 });
13981 }
13982
13983 pub fn select_to_beginning(
13984 &mut self,
13985 _: &SelectToBeginning,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 let mut selection = self.selections.last::<Point>(cx);
13990 selection.set_head(Point::zero(), SelectionGoal::None);
13991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13992 self.change_selections(Default::default(), window, cx, |s| {
13993 s.select(vec![selection]);
13994 });
13995 }
13996
13997 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13998 if matches!(self.mode, EditorMode::SingleLine) {
13999 cx.propagate();
14000 return;
14001 }
14002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14003 let cursor = self.buffer.read(cx).read(cx).len();
14004 self.change_selections(Default::default(), window, cx, |s| {
14005 s.select_ranges(vec![cursor..cursor])
14006 });
14007 }
14008
14009 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14010 self.nav_history = nav_history;
14011 }
14012
14013 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14014 self.nav_history.as_ref()
14015 }
14016
14017 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14018 self.push_to_nav_history(
14019 self.selections.newest_anchor().head(),
14020 None,
14021 false,
14022 true,
14023 cx,
14024 );
14025 }
14026
14027 fn push_to_nav_history(
14028 &mut self,
14029 cursor_anchor: Anchor,
14030 new_position: Option<Point>,
14031 is_deactivate: bool,
14032 always: bool,
14033 cx: &mut Context<Self>,
14034 ) {
14035 if let Some(nav_history) = self.nav_history.as_mut() {
14036 let buffer = self.buffer.read(cx).read(cx);
14037 let cursor_position = cursor_anchor.to_point(&buffer);
14038 let scroll_state = self.scroll_manager.anchor();
14039 let scroll_top_row = scroll_state.top_row(&buffer);
14040 drop(buffer);
14041
14042 if let Some(new_position) = new_position {
14043 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14044 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14045 return;
14046 }
14047 }
14048
14049 nav_history.push(
14050 Some(NavigationData {
14051 cursor_anchor,
14052 cursor_position,
14053 scroll_anchor: scroll_state,
14054 scroll_top_row,
14055 }),
14056 cx,
14057 );
14058 cx.emit(EditorEvent::PushedToNavHistory {
14059 anchor: cursor_anchor,
14060 is_deactivate,
14061 })
14062 }
14063 }
14064
14065 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14067 let buffer = self.buffer.read(cx).snapshot(cx);
14068 let mut selection = self.selections.first::<usize>(cx);
14069 selection.set_head(buffer.len(), SelectionGoal::None);
14070 self.change_selections(Default::default(), window, cx, |s| {
14071 s.select(vec![selection]);
14072 });
14073 }
14074
14075 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14077 let end = self.buffer.read(cx).read(cx).len();
14078 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14079 s.select_ranges(vec![0..end]);
14080 });
14081 }
14082
14083 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14085 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14086 let mut selections = self.selections.all::<Point>(cx);
14087 let max_point = display_map.buffer_snapshot.max_point();
14088 for selection in &mut selections {
14089 let rows = selection.spanned_rows(true, &display_map);
14090 selection.start = Point::new(rows.start.0, 0);
14091 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14092 selection.reversed = false;
14093 }
14094 self.change_selections(Default::default(), window, cx, |s| {
14095 s.select(selections);
14096 });
14097 }
14098
14099 pub fn split_selection_into_lines(
14100 &mut self,
14101 action: &SplitSelectionIntoLines,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) {
14105 let selections = self
14106 .selections
14107 .all::<Point>(cx)
14108 .into_iter()
14109 .map(|selection| selection.start..selection.end)
14110 .collect::<Vec<_>>();
14111 self.unfold_ranges(&selections, true, true, cx);
14112
14113 let mut new_selection_ranges = Vec::new();
14114 {
14115 let buffer = self.buffer.read(cx).read(cx);
14116 for selection in selections {
14117 for row in selection.start.row..selection.end.row {
14118 let line_start = Point::new(row, 0);
14119 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14120
14121 if action.keep_selections {
14122 // Keep the selection range for each line
14123 let selection_start = if row == selection.start.row {
14124 selection.start
14125 } else {
14126 line_start
14127 };
14128 new_selection_ranges.push(selection_start..line_end);
14129 } else {
14130 // Collapse to cursor at end of line
14131 new_selection_ranges.push(line_end..line_end);
14132 }
14133 }
14134
14135 let is_multiline_selection = selection.start.row != selection.end.row;
14136 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14137 // so this action feels more ergonomic when paired with other selection operations
14138 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14139 if !should_skip_last {
14140 if action.keep_selections {
14141 if is_multiline_selection {
14142 let line_start = Point::new(selection.end.row, 0);
14143 new_selection_ranges.push(line_start..selection.end);
14144 } else {
14145 new_selection_ranges.push(selection.start..selection.end);
14146 }
14147 } else {
14148 new_selection_ranges.push(selection.end..selection.end);
14149 }
14150 }
14151 }
14152 }
14153 self.change_selections(Default::default(), window, cx, |s| {
14154 s.select_ranges(new_selection_ranges);
14155 });
14156 }
14157
14158 pub fn add_selection_above(
14159 &mut self,
14160 _: &AddSelectionAbove,
14161 window: &mut Window,
14162 cx: &mut Context<Self>,
14163 ) {
14164 self.add_selection(true, window, cx);
14165 }
14166
14167 pub fn add_selection_below(
14168 &mut self,
14169 _: &AddSelectionBelow,
14170 window: &mut Window,
14171 cx: &mut Context<Self>,
14172 ) {
14173 self.add_selection(false, window, cx);
14174 }
14175
14176 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14178
14179 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14180 let all_selections = self.selections.all::<Point>(cx);
14181 let text_layout_details = self.text_layout_details(window);
14182
14183 let (mut columnar_selections, new_selections_to_columnarize) = {
14184 if let Some(state) = self.add_selections_state.as_ref() {
14185 let columnar_selection_ids: HashSet<_> = state
14186 .groups
14187 .iter()
14188 .flat_map(|group| group.stack.iter())
14189 .copied()
14190 .collect();
14191
14192 all_selections
14193 .into_iter()
14194 .partition(|s| columnar_selection_ids.contains(&s.id))
14195 } else {
14196 (Vec::new(), all_selections)
14197 }
14198 };
14199
14200 let mut state = self
14201 .add_selections_state
14202 .take()
14203 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14204
14205 for selection in new_selections_to_columnarize {
14206 let range = selection.display_range(&display_map).sorted();
14207 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14208 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14209 let positions = start_x.min(end_x)..start_x.max(end_x);
14210 let mut stack = Vec::new();
14211 for row in range.start.row().0..=range.end.row().0 {
14212 if let Some(selection) = self.selections.build_columnar_selection(
14213 &display_map,
14214 DisplayRow(row),
14215 &positions,
14216 selection.reversed,
14217 &text_layout_details,
14218 ) {
14219 stack.push(selection.id);
14220 columnar_selections.push(selection);
14221 }
14222 }
14223 if !stack.is_empty() {
14224 if above {
14225 stack.reverse();
14226 }
14227 state.groups.push(AddSelectionsGroup { above, stack });
14228 }
14229 }
14230
14231 let mut final_selections = Vec::new();
14232 let end_row = if above {
14233 DisplayRow(0)
14234 } else {
14235 display_map.max_point().row()
14236 };
14237
14238 let mut last_added_item_per_group = HashMap::default();
14239 for group in state.groups.iter_mut() {
14240 if let Some(last_id) = group.stack.last() {
14241 last_added_item_per_group.insert(*last_id, group);
14242 }
14243 }
14244
14245 for selection in columnar_selections {
14246 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14247 if above == group.above {
14248 let range = selection.display_range(&display_map).sorted();
14249 debug_assert_eq!(range.start.row(), range.end.row());
14250 let mut row = range.start.row();
14251 let positions =
14252 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14253 px(start)..px(end)
14254 } else {
14255 let start_x =
14256 display_map.x_for_display_point(range.start, &text_layout_details);
14257 let end_x =
14258 display_map.x_for_display_point(range.end, &text_layout_details);
14259 start_x.min(end_x)..start_x.max(end_x)
14260 };
14261
14262 let mut maybe_new_selection = None;
14263 while row != end_row {
14264 if above {
14265 row.0 -= 1;
14266 } else {
14267 row.0 += 1;
14268 }
14269 if let Some(new_selection) = self.selections.build_columnar_selection(
14270 &display_map,
14271 row,
14272 &positions,
14273 selection.reversed,
14274 &text_layout_details,
14275 ) {
14276 maybe_new_selection = Some(new_selection);
14277 break;
14278 }
14279 }
14280
14281 if let Some(new_selection) = maybe_new_selection {
14282 group.stack.push(new_selection.id);
14283 if above {
14284 final_selections.push(new_selection);
14285 final_selections.push(selection);
14286 } else {
14287 final_selections.push(selection);
14288 final_selections.push(new_selection);
14289 }
14290 } else {
14291 final_selections.push(selection);
14292 }
14293 } else {
14294 group.stack.pop();
14295 }
14296 } else {
14297 final_selections.push(selection);
14298 }
14299 }
14300
14301 self.change_selections(Default::default(), window, cx, |s| {
14302 s.select(final_selections);
14303 });
14304
14305 let final_selection_ids: HashSet<_> = self
14306 .selections
14307 .all::<Point>(cx)
14308 .iter()
14309 .map(|s| s.id)
14310 .collect();
14311 state.groups.retain_mut(|group| {
14312 // selections might get merged above so we remove invalid items from stacks
14313 group.stack.retain(|id| final_selection_ids.contains(id));
14314
14315 // single selection in stack can be treated as initial state
14316 group.stack.len() > 1
14317 });
14318
14319 if !state.groups.is_empty() {
14320 self.add_selections_state = Some(state);
14321 }
14322 }
14323
14324 fn select_match_ranges(
14325 &mut self,
14326 range: Range<usize>,
14327 reversed: bool,
14328 replace_newest: bool,
14329 auto_scroll: Option<Autoscroll>,
14330 window: &mut Window,
14331 cx: &mut Context<Editor>,
14332 ) {
14333 self.unfold_ranges(
14334 std::slice::from_ref(&range),
14335 false,
14336 auto_scroll.is_some(),
14337 cx,
14338 );
14339 let effects = if let Some(scroll) = auto_scroll {
14340 SelectionEffects::scroll(scroll)
14341 } else {
14342 SelectionEffects::no_scroll()
14343 };
14344 self.change_selections(effects, window, cx, |s| {
14345 if replace_newest {
14346 s.delete(s.newest_anchor().id);
14347 }
14348 if reversed {
14349 s.insert_range(range.end..range.start);
14350 } else {
14351 s.insert_range(range);
14352 }
14353 });
14354 }
14355
14356 pub fn select_next_match_internal(
14357 &mut self,
14358 display_map: &DisplaySnapshot,
14359 replace_newest: bool,
14360 autoscroll: Option<Autoscroll>,
14361 window: &mut Window,
14362 cx: &mut Context<Self>,
14363 ) -> Result<()> {
14364 let buffer = &display_map.buffer_snapshot;
14365 let mut selections = self.selections.all::<usize>(cx);
14366 if let Some(mut select_next_state) = self.select_next_state.take() {
14367 let query = &select_next_state.query;
14368 if !select_next_state.done {
14369 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14370 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14371 let mut next_selected_range = None;
14372
14373 let bytes_after_last_selection =
14374 buffer.bytes_in_range(last_selection.end..buffer.len());
14375 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14376 let query_matches = query
14377 .stream_find_iter(bytes_after_last_selection)
14378 .map(|result| (last_selection.end, result))
14379 .chain(
14380 query
14381 .stream_find_iter(bytes_before_first_selection)
14382 .map(|result| (0, result)),
14383 );
14384
14385 for (start_offset, query_match) in query_matches {
14386 let query_match = query_match.unwrap(); // can only fail due to I/O
14387 let offset_range =
14388 start_offset + query_match.start()..start_offset + query_match.end();
14389
14390 if !select_next_state.wordwise
14391 || (!buffer.is_inside_word(offset_range.start, None)
14392 && !buffer.is_inside_word(offset_range.end, None))
14393 {
14394 // TODO: This is n^2, because we might check all the selections
14395 if !selections
14396 .iter()
14397 .any(|selection| selection.range().overlaps(&offset_range))
14398 {
14399 next_selected_range = Some(offset_range);
14400 break;
14401 }
14402 }
14403 }
14404
14405 if let Some(next_selected_range) = next_selected_range {
14406 self.select_match_ranges(
14407 next_selected_range,
14408 last_selection.reversed,
14409 replace_newest,
14410 autoscroll,
14411 window,
14412 cx,
14413 );
14414 } else {
14415 select_next_state.done = true;
14416 }
14417 }
14418
14419 self.select_next_state = Some(select_next_state);
14420 } else {
14421 let mut only_carets = true;
14422 let mut same_text_selected = true;
14423 let mut selected_text = None;
14424
14425 let mut selections_iter = selections.iter().peekable();
14426 while let Some(selection) = selections_iter.next() {
14427 if selection.start != selection.end {
14428 only_carets = false;
14429 }
14430
14431 if same_text_selected {
14432 if selected_text.is_none() {
14433 selected_text =
14434 Some(buffer.text_for_range(selection.range()).collect::<String>());
14435 }
14436
14437 if let Some(next_selection) = selections_iter.peek() {
14438 if next_selection.range().len() == selection.range().len() {
14439 let next_selected_text = buffer
14440 .text_for_range(next_selection.range())
14441 .collect::<String>();
14442 if Some(next_selected_text) != selected_text {
14443 same_text_selected = false;
14444 selected_text = None;
14445 }
14446 } else {
14447 same_text_selected = false;
14448 selected_text = None;
14449 }
14450 }
14451 }
14452 }
14453
14454 if only_carets {
14455 for selection in &mut selections {
14456 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14457 selection.start = word_range.start;
14458 selection.end = word_range.end;
14459 selection.goal = SelectionGoal::None;
14460 selection.reversed = false;
14461 self.select_match_ranges(
14462 selection.start..selection.end,
14463 selection.reversed,
14464 replace_newest,
14465 autoscroll,
14466 window,
14467 cx,
14468 );
14469 }
14470
14471 if selections.len() == 1 {
14472 let selection = selections
14473 .last()
14474 .expect("ensured that there's only one selection");
14475 let query = buffer
14476 .text_for_range(selection.start..selection.end)
14477 .collect::<String>();
14478 let is_empty = query.is_empty();
14479 let select_state = SelectNextState {
14480 query: AhoCorasick::new(&[query])?,
14481 wordwise: true,
14482 done: is_empty,
14483 };
14484 self.select_next_state = Some(select_state);
14485 } else {
14486 self.select_next_state = None;
14487 }
14488 } else if let Some(selected_text) = selected_text {
14489 self.select_next_state = Some(SelectNextState {
14490 query: AhoCorasick::new(&[selected_text])?,
14491 wordwise: false,
14492 done: false,
14493 });
14494 self.select_next_match_internal(
14495 display_map,
14496 replace_newest,
14497 autoscroll,
14498 window,
14499 cx,
14500 )?;
14501 }
14502 }
14503 Ok(())
14504 }
14505
14506 pub fn select_all_matches(
14507 &mut self,
14508 _action: &SelectAllMatches,
14509 window: &mut Window,
14510 cx: &mut Context<Self>,
14511 ) -> Result<()> {
14512 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14513
14514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14515
14516 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14517 let Some(select_next_state) = self.select_next_state.as_mut() else {
14518 return Ok(());
14519 };
14520 if select_next_state.done {
14521 return Ok(());
14522 }
14523
14524 let mut new_selections = Vec::new();
14525
14526 let reversed = self.selections.oldest::<usize>(cx).reversed;
14527 let buffer = &display_map.buffer_snapshot;
14528 let query_matches = select_next_state
14529 .query
14530 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14531
14532 for query_match in query_matches.into_iter() {
14533 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14534 let offset_range = if reversed {
14535 query_match.end()..query_match.start()
14536 } else {
14537 query_match.start()..query_match.end()
14538 };
14539
14540 if !select_next_state.wordwise
14541 || (!buffer.is_inside_word(offset_range.start, None)
14542 && !buffer.is_inside_word(offset_range.end, None))
14543 {
14544 new_selections.push(offset_range.start..offset_range.end);
14545 }
14546 }
14547
14548 select_next_state.done = true;
14549
14550 if new_selections.is_empty() {
14551 log::error!("bug: new_selections is empty in select_all_matches");
14552 return Ok(());
14553 }
14554
14555 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14556 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14557 selections.select_ranges(new_selections)
14558 });
14559
14560 Ok(())
14561 }
14562
14563 pub fn select_next(
14564 &mut self,
14565 action: &SelectNext,
14566 window: &mut Window,
14567 cx: &mut Context<Self>,
14568 ) -> Result<()> {
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14571 self.select_next_match_internal(
14572 &display_map,
14573 action.replace_newest,
14574 Some(Autoscroll::newest()),
14575 window,
14576 cx,
14577 )?;
14578 Ok(())
14579 }
14580
14581 pub fn select_previous(
14582 &mut self,
14583 action: &SelectPrevious,
14584 window: &mut Window,
14585 cx: &mut Context<Self>,
14586 ) -> Result<()> {
14587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14589 let buffer = &display_map.buffer_snapshot;
14590 let mut selections = self.selections.all::<usize>(cx);
14591 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14592 let query = &select_prev_state.query;
14593 if !select_prev_state.done {
14594 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14595 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14596 let mut next_selected_range = None;
14597 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14598 let bytes_before_last_selection =
14599 buffer.reversed_bytes_in_range(0..last_selection.start);
14600 let bytes_after_first_selection =
14601 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14602 let query_matches = query
14603 .stream_find_iter(bytes_before_last_selection)
14604 .map(|result| (last_selection.start, result))
14605 .chain(
14606 query
14607 .stream_find_iter(bytes_after_first_selection)
14608 .map(|result| (buffer.len(), result)),
14609 );
14610 for (end_offset, query_match) in query_matches {
14611 let query_match = query_match.unwrap(); // can only fail due to I/O
14612 let offset_range =
14613 end_offset - query_match.end()..end_offset - query_match.start();
14614
14615 if !select_prev_state.wordwise
14616 || (!buffer.is_inside_word(offset_range.start, None)
14617 && !buffer.is_inside_word(offset_range.end, None))
14618 {
14619 next_selected_range = Some(offset_range);
14620 break;
14621 }
14622 }
14623
14624 if let Some(next_selected_range) = next_selected_range {
14625 self.select_match_ranges(
14626 next_selected_range,
14627 last_selection.reversed,
14628 action.replace_newest,
14629 Some(Autoscroll::newest()),
14630 window,
14631 cx,
14632 );
14633 } else {
14634 select_prev_state.done = true;
14635 }
14636 }
14637
14638 self.select_prev_state = Some(select_prev_state);
14639 } else {
14640 let mut only_carets = true;
14641 let mut same_text_selected = true;
14642 let mut selected_text = None;
14643
14644 let mut selections_iter = selections.iter().peekable();
14645 while let Some(selection) = selections_iter.next() {
14646 if selection.start != selection.end {
14647 only_carets = false;
14648 }
14649
14650 if same_text_selected {
14651 if selected_text.is_none() {
14652 selected_text =
14653 Some(buffer.text_for_range(selection.range()).collect::<String>());
14654 }
14655
14656 if let Some(next_selection) = selections_iter.peek() {
14657 if next_selection.range().len() == selection.range().len() {
14658 let next_selected_text = buffer
14659 .text_for_range(next_selection.range())
14660 .collect::<String>();
14661 if Some(next_selected_text) != selected_text {
14662 same_text_selected = false;
14663 selected_text = None;
14664 }
14665 } else {
14666 same_text_selected = false;
14667 selected_text = None;
14668 }
14669 }
14670 }
14671 }
14672
14673 if only_carets {
14674 for selection in &mut selections {
14675 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14676 selection.start = word_range.start;
14677 selection.end = word_range.end;
14678 selection.goal = SelectionGoal::None;
14679 selection.reversed = false;
14680 self.select_match_ranges(
14681 selection.start..selection.end,
14682 selection.reversed,
14683 action.replace_newest,
14684 Some(Autoscroll::newest()),
14685 window,
14686 cx,
14687 );
14688 }
14689 if selections.len() == 1 {
14690 let selection = selections
14691 .last()
14692 .expect("ensured that there's only one selection");
14693 let query = buffer
14694 .text_for_range(selection.start..selection.end)
14695 .collect::<String>();
14696 let is_empty = query.is_empty();
14697 let select_state = SelectNextState {
14698 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14699 wordwise: true,
14700 done: is_empty,
14701 };
14702 self.select_prev_state = Some(select_state);
14703 } else {
14704 self.select_prev_state = None;
14705 }
14706 } else if let Some(selected_text) = selected_text {
14707 self.select_prev_state = Some(SelectNextState {
14708 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14709 wordwise: false,
14710 done: false,
14711 });
14712 self.select_previous(action, window, cx)?;
14713 }
14714 }
14715 Ok(())
14716 }
14717
14718 pub fn find_next_match(
14719 &mut self,
14720 _: &FindNextMatch,
14721 window: &mut Window,
14722 cx: &mut Context<Self>,
14723 ) -> Result<()> {
14724 let selections = self.selections.disjoint_anchors_arc();
14725 match selections.first() {
14726 Some(first) if selections.len() >= 2 => {
14727 self.change_selections(Default::default(), window, cx, |s| {
14728 s.select_ranges([first.range()]);
14729 });
14730 }
14731 _ => self.select_next(
14732 &SelectNext {
14733 replace_newest: true,
14734 },
14735 window,
14736 cx,
14737 )?,
14738 }
14739 Ok(())
14740 }
14741
14742 pub fn find_previous_match(
14743 &mut self,
14744 _: &FindPreviousMatch,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) -> Result<()> {
14748 let selections = self.selections.disjoint_anchors_arc();
14749 match selections.last() {
14750 Some(last) if selections.len() >= 2 => {
14751 self.change_selections(Default::default(), window, cx, |s| {
14752 s.select_ranges([last.range()]);
14753 });
14754 }
14755 _ => self.select_previous(
14756 &SelectPrevious {
14757 replace_newest: true,
14758 },
14759 window,
14760 cx,
14761 )?,
14762 }
14763 Ok(())
14764 }
14765
14766 pub fn toggle_comments(
14767 &mut self,
14768 action: &ToggleComments,
14769 window: &mut Window,
14770 cx: &mut Context<Self>,
14771 ) {
14772 if self.read_only(cx) {
14773 return;
14774 }
14775 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14776 let text_layout_details = &self.text_layout_details(window);
14777 self.transact(window, cx, |this, window, cx| {
14778 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14779 let mut edits = Vec::new();
14780 let mut selection_edit_ranges = Vec::new();
14781 let mut last_toggled_row = None;
14782 let snapshot = this.buffer.read(cx).read(cx);
14783 let empty_str: Arc<str> = Arc::default();
14784 let mut suffixes_inserted = Vec::new();
14785 let ignore_indent = action.ignore_indent;
14786
14787 fn comment_prefix_range(
14788 snapshot: &MultiBufferSnapshot,
14789 row: MultiBufferRow,
14790 comment_prefix: &str,
14791 comment_prefix_whitespace: &str,
14792 ignore_indent: bool,
14793 ) -> Range<Point> {
14794 let indent_size = if ignore_indent {
14795 0
14796 } else {
14797 snapshot.indent_size_for_line(row).len
14798 };
14799
14800 let start = Point::new(row.0, indent_size);
14801
14802 let mut line_bytes = snapshot
14803 .bytes_in_range(start..snapshot.max_point())
14804 .flatten()
14805 .copied();
14806
14807 // If this line currently begins with the line comment prefix, then record
14808 // the range containing the prefix.
14809 if line_bytes
14810 .by_ref()
14811 .take(comment_prefix.len())
14812 .eq(comment_prefix.bytes())
14813 {
14814 // Include any whitespace that matches the comment prefix.
14815 let matching_whitespace_len = line_bytes
14816 .zip(comment_prefix_whitespace.bytes())
14817 .take_while(|(a, b)| a == b)
14818 .count() as u32;
14819 let end = Point::new(
14820 start.row,
14821 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14822 );
14823 start..end
14824 } else {
14825 start..start
14826 }
14827 }
14828
14829 fn comment_suffix_range(
14830 snapshot: &MultiBufferSnapshot,
14831 row: MultiBufferRow,
14832 comment_suffix: &str,
14833 comment_suffix_has_leading_space: bool,
14834 ) -> Range<Point> {
14835 let end = Point::new(row.0, snapshot.line_len(row));
14836 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14837
14838 let mut line_end_bytes = snapshot
14839 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14840 .flatten()
14841 .copied();
14842
14843 let leading_space_len = if suffix_start_column > 0
14844 && line_end_bytes.next() == Some(b' ')
14845 && comment_suffix_has_leading_space
14846 {
14847 1
14848 } else {
14849 0
14850 };
14851
14852 // If this line currently begins with the line comment prefix, then record
14853 // the range containing the prefix.
14854 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14855 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14856 start..end
14857 } else {
14858 end..end
14859 }
14860 }
14861
14862 // TODO: Handle selections that cross excerpts
14863 for selection in &mut selections {
14864 let start_column = snapshot
14865 .indent_size_for_line(MultiBufferRow(selection.start.row))
14866 .len;
14867 let language = if let Some(language) =
14868 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14869 {
14870 language
14871 } else {
14872 continue;
14873 };
14874
14875 selection_edit_ranges.clear();
14876
14877 // If multiple selections contain a given row, avoid processing that
14878 // row more than once.
14879 let mut start_row = MultiBufferRow(selection.start.row);
14880 if last_toggled_row == Some(start_row) {
14881 start_row = start_row.next_row();
14882 }
14883 let end_row =
14884 if selection.end.row > selection.start.row && selection.end.column == 0 {
14885 MultiBufferRow(selection.end.row - 1)
14886 } else {
14887 MultiBufferRow(selection.end.row)
14888 };
14889 last_toggled_row = Some(end_row);
14890
14891 if start_row > end_row {
14892 continue;
14893 }
14894
14895 // If the language has line comments, toggle those.
14896 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14897
14898 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14899 if ignore_indent {
14900 full_comment_prefixes = full_comment_prefixes
14901 .into_iter()
14902 .map(|s| Arc::from(s.trim_end()))
14903 .collect();
14904 }
14905
14906 if !full_comment_prefixes.is_empty() {
14907 let first_prefix = full_comment_prefixes
14908 .first()
14909 .expect("prefixes is non-empty");
14910 let prefix_trimmed_lengths = full_comment_prefixes
14911 .iter()
14912 .map(|p| p.trim_end_matches(' ').len())
14913 .collect::<SmallVec<[usize; 4]>>();
14914
14915 let mut all_selection_lines_are_comments = true;
14916
14917 for row in start_row.0..=end_row.0 {
14918 let row = MultiBufferRow(row);
14919 if start_row < end_row && snapshot.is_line_blank(row) {
14920 continue;
14921 }
14922
14923 let prefix_range = full_comment_prefixes
14924 .iter()
14925 .zip(prefix_trimmed_lengths.iter().copied())
14926 .map(|(prefix, trimmed_prefix_len)| {
14927 comment_prefix_range(
14928 snapshot.deref(),
14929 row,
14930 &prefix[..trimmed_prefix_len],
14931 &prefix[trimmed_prefix_len..],
14932 ignore_indent,
14933 )
14934 })
14935 .max_by_key(|range| range.end.column - range.start.column)
14936 .expect("prefixes is non-empty");
14937
14938 if prefix_range.is_empty() {
14939 all_selection_lines_are_comments = false;
14940 }
14941
14942 selection_edit_ranges.push(prefix_range);
14943 }
14944
14945 if all_selection_lines_are_comments {
14946 edits.extend(
14947 selection_edit_ranges
14948 .iter()
14949 .cloned()
14950 .map(|range| (range, empty_str.clone())),
14951 );
14952 } else {
14953 let min_column = selection_edit_ranges
14954 .iter()
14955 .map(|range| range.start.column)
14956 .min()
14957 .unwrap_or(0);
14958 edits.extend(selection_edit_ranges.iter().map(|range| {
14959 let position = Point::new(range.start.row, min_column);
14960 (position..position, first_prefix.clone())
14961 }));
14962 }
14963 } else if let Some(BlockCommentConfig {
14964 start: full_comment_prefix,
14965 end: comment_suffix,
14966 ..
14967 }) = language.block_comment()
14968 {
14969 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14970 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14971 let prefix_range = comment_prefix_range(
14972 snapshot.deref(),
14973 start_row,
14974 comment_prefix,
14975 comment_prefix_whitespace,
14976 ignore_indent,
14977 );
14978 let suffix_range = comment_suffix_range(
14979 snapshot.deref(),
14980 end_row,
14981 comment_suffix.trim_start_matches(' '),
14982 comment_suffix.starts_with(' '),
14983 );
14984
14985 if prefix_range.is_empty() || suffix_range.is_empty() {
14986 edits.push((
14987 prefix_range.start..prefix_range.start,
14988 full_comment_prefix.clone(),
14989 ));
14990 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14991 suffixes_inserted.push((end_row, comment_suffix.len()));
14992 } else {
14993 edits.push((prefix_range, empty_str.clone()));
14994 edits.push((suffix_range, empty_str.clone()));
14995 }
14996 } else {
14997 continue;
14998 }
14999 }
15000
15001 drop(snapshot);
15002 this.buffer.update(cx, |buffer, cx| {
15003 buffer.edit(edits, None, cx);
15004 });
15005
15006 // Adjust selections so that they end before any comment suffixes that
15007 // were inserted.
15008 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15009 let mut selections = this.selections.all::<Point>(cx);
15010 let snapshot = this.buffer.read(cx).read(cx);
15011 for selection in &mut selections {
15012 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15013 match row.cmp(&MultiBufferRow(selection.end.row)) {
15014 Ordering::Less => {
15015 suffixes_inserted.next();
15016 continue;
15017 }
15018 Ordering::Greater => break,
15019 Ordering::Equal => {
15020 if selection.end.column == snapshot.line_len(row) {
15021 if selection.is_empty() {
15022 selection.start.column -= suffix_len as u32;
15023 }
15024 selection.end.column -= suffix_len as u32;
15025 }
15026 break;
15027 }
15028 }
15029 }
15030 }
15031
15032 drop(snapshot);
15033 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15034
15035 let selections = this.selections.all::<Point>(cx);
15036 let selections_on_single_row = selections.windows(2).all(|selections| {
15037 selections[0].start.row == selections[1].start.row
15038 && selections[0].end.row == selections[1].end.row
15039 && selections[0].start.row == selections[0].end.row
15040 });
15041 let selections_selecting = selections
15042 .iter()
15043 .any(|selection| selection.start != selection.end);
15044 let advance_downwards = action.advance_downwards
15045 && selections_on_single_row
15046 && !selections_selecting
15047 && !matches!(this.mode, EditorMode::SingleLine);
15048
15049 if advance_downwards {
15050 let snapshot = this.buffer.read(cx).snapshot(cx);
15051
15052 this.change_selections(Default::default(), window, cx, |s| {
15053 s.move_cursors_with(|display_snapshot, display_point, _| {
15054 let mut point = display_point.to_point(display_snapshot);
15055 point.row += 1;
15056 point = snapshot.clip_point(point, Bias::Left);
15057 let display_point = point.to_display_point(display_snapshot);
15058 let goal = SelectionGoal::HorizontalPosition(
15059 display_snapshot
15060 .x_for_display_point(display_point, text_layout_details)
15061 .into(),
15062 );
15063 (display_point, goal)
15064 })
15065 });
15066 }
15067 });
15068 }
15069
15070 pub fn select_enclosing_symbol(
15071 &mut self,
15072 _: &SelectEnclosingSymbol,
15073 window: &mut Window,
15074 cx: &mut Context<Self>,
15075 ) {
15076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15077
15078 let buffer = self.buffer.read(cx).snapshot(cx);
15079 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15080
15081 fn update_selection(
15082 selection: &Selection<usize>,
15083 buffer_snap: &MultiBufferSnapshot,
15084 ) -> Option<Selection<usize>> {
15085 let cursor = selection.head();
15086 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15087 for symbol in symbols.iter().rev() {
15088 let start = symbol.range.start.to_offset(buffer_snap);
15089 let end = symbol.range.end.to_offset(buffer_snap);
15090 let new_range = start..end;
15091 if start < selection.start || end > selection.end {
15092 return Some(Selection {
15093 id: selection.id,
15094 start: new_range.start,
15095 end: new_range.end,
15096 goal: SelectionGoal::None,
15097 reversed: selection.reversed,
15098 });
15099 }
15100 }
15101 None
15102 }
15103
15104 let mut selected_larger_symbol = false;
15105 let new_selections = old_selections
15106 .iter()
15107 .map(|selection| match update_selection(selection, &buffer) {
15108 Some(new_selection) => {
15109 if new_selection.range() != selection.range() {
15110 selected_larger_symbol = true;
15111 }
15112 new_selection
15113 }
15114 None => selection.clone(),
15115 })
15116 .collect::<Vec<_>>();
15117
15118 if selected_larger_symbol {
15119 self.change_selections(Default::default(), window, cx, |s| {
15120 s.select(new_selections);
15121 });
15122 }
15123 }
15124
15125 pub fn select_larger_syntax_node(
15126 &mut self,
15127 _: &SelectLargerSyntaxNode,
15128 window: &mut Window,
15129 cx: &mut Context<Self>,
15130 ) {
15131 let Some(visible_row_count) = self.visible_row_count() else {
15132 return;
15133 };
15134 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15135 if old_selections.is_empty() {
15136 return;
15137 }
15138
15139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15140
15141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15142 let buffer = self.buffer.read(cx).snapshot(cx);
15143
15144 let mut selected_larger_node = false;
15145 let mut new_selections = old_selections
15146 .iter()
15147 .map(|selection| {
15148 let old_range = selection.start..selection.end;
15149
15150 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15151 // manually select word at selection
15152 if ["string_content", "inline"].contains(&node.kind()) {
15153 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15154 // ignore if word is already selected
15155 if !word_range.is_empty() && old_range != word_range {
15156 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15157 // only select word if start and end point belongs to same word
15158 if word_range == last_word_range {
15159 selected_larger_node = true;
15160 return Selection {
15161 id: selection.id,
15162 start: word_range.start,
15163 end: word_range.end,
15164 goal: SelectionGoal::None,
15165 reversed: selection.reversed,
15166 };
15167 }
15168 }
15169 }
15170 }
15171
15172 let mut new_range = old_range.clone();
15173 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15174 new_range = range;
15175 if !node.is_named() {
15176 continue;
15177 }
15178 if !display_map.intersects_fold(new_range.start)
15179 && !display_map.intersects_fold(new_range.end)
15180 {
15181 break;
15182 }
15183 }
15184
15185 selected_larger_node |= new_range != old_range;
15186 Selection {
15187 id: selection.id,
15188 start: new_range.start,
15189 end: new_range.end,
15190 goal: SelectionGoal::None,
15191 reversed: selection.reversed,
15192 }
15193 })
15194 .collect::<Vec<_>>();
15195
15196 if !selected_larger_node {
15197 return; // don't put this call in the history
15198 }
15199
15200 // scroll based on transformation done to the last selection created by the user
15201 let (last_old, last_new) = old_selections
15202 .last()
15203 .zip(new_selections.last().cloned())
15204 .expect("old_selections isn't empty");
15205
15206 // revert selection
15207 let is_selection_reversed = {
15208 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15209 new_selections.last_mut().expect("checked above").reversed =
15210 should_newest_selection_be_reversed;
15211 should_newest_selection_be_reversed
15212 };
15213
15214 if selected_larger_node {
15215 self.select_syntax_node_history.disable_clearing = true;
15216 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15217 s.select(new_selections.clone());
15218 });
15219 self.select_syntax_node_history.disable_clearing = false;
15220 }
15221
15222 let start_row = last_new.start.to_display_point(&display_map).row().0;
15223 let end_row = last_new.end.to_display_point(&display_map).row().0;
15224 let selection_height = end_row - start_row + 1;
15225 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15226
15227 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15228 let scroll_behavior = if fits_on_the_screen {
15229 self.request_autoscroll(Autoscroll::fit(), cx);
15230 SelectSyntaxNodeScrollBehavior::FitSelection
15231 } else if is_selection_reversed {
15232 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15233 SelectSyntaxNodeScrollBehavior::CursorTop
15234 } else {
15235 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15236 SelectSyntaxNodeScrollBehavior::CursorBottom
15237 };
15238
15239 self.select_syntax_node_history.push((
15240 old_selections,
15241 scroll_behavior,
15242 is_selection_reversed,
15243 ));
15244 }
15245
15246 pub fn select_smaller_syntax_node(
15247 &mut self,
15248 _: &SelectSmallerSyntaxNode,
15249 window: &mut Window,
15250 cx: &mut Context<Self>,
15251 ) {
15252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15253
15254 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15255 self.select_syntax_node_history.pop()
15256 {
15257 if let Some(selection) = selections.last_mut() {
15258 selection.reversed = is_selection_reversed;
15259 }
15260
15261 self.select_syntax_node_history.disable_clearing = true;
15262 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15263 s.select(selections.to_vec());
15264 });
15265 self.select_syntax_node_history.disable_clearing = false;
15266
15267 match scroll_behavior {
15268 SelectSyntaxNodeScrollBehavior::CursorTop => {
15269 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15270 }
15271 SelectSyntaxNodeScrollBehavior::FitSelection => {
15272 self.request_autoscroll(Autoscroll::fit(), cx);
15273 }
15274 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15275 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15276 }
15277 }
15278 }
15279 }
15280
15281 pub fn unwrap_syntax_node(
15282 &mut self,
15283 _: &UnwrapSyntaxNode,
15284 window: &mut Window,
15285 cx: &mut Context<Self>,
15286 ) {
15287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15288
15289 let buffer = self.buffer.read(cx).snapshot(cx);
15290 let selections = self
15291 .selections
15292 .all::<usize>(cx)
15293 .into_iter()
15294 // subtracting the offset requires sorting
15295 .sorted_by_key(|i| i.start);
15296
15297 let full_edits = selections
15298 .into_iter()
15299 .filter_map(|selection| {
15300 let child = if selection.is_empty()
15301 && let Some((_, ancestor_range)) =
15302 buffer.syntax_ancestor(selection.start..selection.end)
15303 {
15304 ancestor_range
15305 } else {
15306 selection.range()
15307 };
15308
15309 let mut parent = child.clone();
15310 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15311 parent = ancestor_range;
15312 if parent.start < child.start || parent.end > child.end {
15313 break;
15314 }
15315 }
15316
15317 if parent == child {
15318 return None;
15319 }
15320 let text = buffer.text_for_range(child).collect::<String>();
15321 Some((selection.id, parent, text))
15322 })
15323 .collect::<Vec<_>>();
15324 if full_edits.is_empty() {
15325 return;
15326 }
15327
15328 self.transact(window, cx, |this, window, cx| {
15329 this.buffer.update(cx, |buffer, cx| {
15330 buffer.edit(
15331 full_edits
15332 .iter()
15333 .map(|(_, p, t)| (p.clone(), t.clone()))
15334 .collect::<Vec<_>>(),
15335 None,
15336 cx,
15337 );
15338 });
15339 this.change_selections(Default::default(), window, cx, |s| {
15340 let mut offset = 0;
15341 let mut selections = vec![];
15342 for (id, parent, text) in full_edits {
15343 let start = parent.start - offset;
15344 offset += parent.len() - text.len();
15345 selections.push(Selection {
15346 id,
15347 start,
15348 end: start + text.len(),
15349 reversed: false,
15350 goal: Default::default(),
15351 });
15352 }
15353 s.select(selections);
15354 });
15355 });
15356 }
15357
15358 pub fn select_next_syntax_node(
15359 &mut self,
15360 _: &SelectNextSyntaxNode,
15361 window: &mut Window,
15362 cx: &mut Context<Self>,
15363 ) {
15364 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15365 if old_selections.is_empty() {
15366 return;
15367 }
15368
15369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15370
15371 let buffer = self.buffer.read(cx).snapshot(cx);
15372 let mut selected_sibling = false;
15373
15374 let new_selections = old_selections
15375 .iter()
15376 .map(|selection| {
15377 let old_range = selection.start..selection.end;
15378
15379 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15380 let new_range = node.byte_range();
15381 selected_sibling = true;
15382 Selection {
15383 id: selection.id,
15384 start: new_range.start,
15385 end: new_range.end,
15386 goal: SelectionGoal::None,
15387 reversed: selection.reversed,
15388 }
15389 } else {
15390 selection.clone()
15391 }
15392 })
15393 .collect::<Vec<_>>();
15394
15395 if selected_sibling {
15396 self.change_selections(
15397 SelectionEffects::scroll(Autoscroll::fit()),
15398 window,
15399 cx,
15400 |s| {
15401 s.select(new_selections);
15402 },
15403 );
15404 }
15405 }
15406
15407 pub fn select_prev_syntax_node(
15408 &mut self,
15409 _: &SelectPreviousSyntaxNode,
15410 window: &mut Window,
15411 cx: &mut Context<Self>,
15412 ) {
15413 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15414 if old_selections.is_empty() {
15415 return;
15416 }
15417
15418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15419
15420 let buffer = self.buffer.read(cx).snapshot(cx);
15421 let mut selected_sibling = false;
15422
15423 let new_selections = old_selections
15424 .iter()
15425 .map(|selection| {
15426 let old_range = selection.start..selection.end;
15427
15428 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15429 let new_range = node.byte_range();
15430 selected_sibling = true;
15431 Selection {
15432 id: selection.id,
15433 start: new_range.start,
15434 end: new_range.end,
15435 goal: SelectionGoal::None,
15436 reversed: selection.reversed,
15437 }
15438 } else {
15439 selection.clone()
15440 }
15441 })
15442 .collect::<Vec<_>>();
15443
15444 if selected_sibling {
15445 self.change_selections(
15446 SelectionEffects::scroll(Autoscroll::fit()),
15447 window,
15448 cx,
15449 |s| {
15450 s.select(new_selections);
15451 },
15452 );
15453 }
15454 }
15455
15456 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15457 if !EditorSettings::get_global(cx).gutter.runnables {
15458 self.clear_tasks();
15459 return Task::ready(());
15460 }
15461 let project = self.project().map(Entity::downgrade);
15462 let task_sources = self.lsp_task_sources(cx);
15463 let multi_buffer = self.buffer.downgrade();
15464 cx.spawn_in(window, async move |editor, cx| {
15465 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15466 let Some(project) = project.and_then(|p| p.upgrade()) else {
15467 return;
15468 };
15469 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15470 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15471 }) else {
15472 return;
15473 };
15474
15475 let hide_runnables = project
15476 .update(cx, |project, _| project.is_via_collab())
15477 .unwrap_or(true);
15478 if hide_runnables {
15479 return;
15480 }
15481 let new_rows =
15482 cx.background_spawn({
15483 let snapshot = display_snapshot.clone();
15484 async move {
15485 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15486 }
15487 })
15488 .await;
15489 let Ok(lsp_tasks) =
15490 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15491 else {
15492 return;
15493 };
15494 let lsp_tasks = lsp_tasks.await;
15495
15496 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15497 lsp_tasks
15498 .into_iter()
15499 .flat_map(|(kind, tasks)| {
15500 tasks.into_iter().filter_map(move |(location, task)| {
15501 Some((kind.clone(), location?, task))
15502 })
15503 })
15504 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15505 let buffer = location.target.buffer;
15506 let buffer_snapshot = buffer.read(cx).snapshot();
15507 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15508 |(excerpt_id, snapshot, _)| {
15509 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15510 display_snapshot
15511 .buffer_snapshot
15512 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15513 } else {
15514 None
15515 }
15516 },
15517 );
15518 if let Some(offset) = offset {
15519 let task_buffer_range =
15520 location.target.range.to_point(&buffer_snapshot);
15521 let context_buffer_range =
15522 task_buffer_range.to_offset(&buffer_snapshot);
15523 let context_range = BufferOffset(context_buffer_range.start)
15524 ..BufferOffset(context_buffer_range.end);
15525
15526 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15527 .or_insert_with(|| RunnableTasks {
15528 templates: Vec::new(),
15529 offset,
15530 column: task_buffer_range.start.column,
15531 extra_variables: HashMap::default(),
15532 context_range,
15533 })
15534 .templates
15535 .push((kind, task.original_task().clone()));
15536 }
15537
15538 acc
15539 })
15540 }) else {
15541 return;
15542 };
15543
15544 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15545 buffer.language_settings(cx).tasks.prefer_lsp
15546 }) else {
15547 return;
15548 };
15549
15550 let rows = Self::runnable_rows(
15551 project,
15552 display_snapshot,
15553 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15554 new_rows,
15555 cx.clone(),
15556 )
15557 .await;
15558 editor
15559 .update(cx, |editor, _| {
15560 editor.clear_tasks();
15561 for (key, mut value) in rows {
15562 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15563 value.templates.extend(lsp_tasks.templates);
15564 }
15565
15566 editor.insert_tasks(key, value);
15567 }
15568 for (key, value) in lsp_tasks_by_rows {
15569 editor.insert_tasks(key, value);
15570 }
15571 })
15572 .ok();
15573 })
15574 }
15575 fn fetch_runnable_ranges(
15576 snapshot: &DisplaySnapshot,
15577 range: Range<Anchor>,
15578 ) -> Vec<language::RunnableRange> {
15579 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15580 }
15581
15582 fn runnable_rows(
15583 project: Entity<Project>,
15584 snapshot: DisplaySnapshot,
15585 prefer_lsp: bool,
15586 runnable_ranges: Vec<RunnableRange>,
15587 cx: AsyncWindowContext,
15588 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15589 cx.spawn(async move |cx| {
15590 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15591 for mut runnable in runnable_ranges {
15592 let Some(tasks) = cx
15593 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15594 .ok()
15595 else {
15596 continue;
15597 };
15598 let mut tasks = tasks.await;
15599
15600 if prefer_lsp {
15601 tasks.retain(|(task_kind, _)| {
15602 !matches!(task_kind, TaskSourceKind::Language { .. })
15603 });
15604 }
15605 if tasks.is_empty() {
15606 continue;
15607 }
15608
15609 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15610 let Some(row) = snapshot
15611 .buffer_snapshot
15612 .buffer_line_for_row(MultiBufferRow(point.row))
15613 .map(|(_, range)| range.start.row)
15614 else {
15615 continue;
15616 };
15617
15618 let context_range =
15619 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15620 runnable_rows.push((
15621 (runnable.buffer_id, row),
15622 RunnableTasks {
15623 templates: tasks,
15624 offset: snapshot
15625 .buffer_snapshot
15626 .anchor_before(runnable.run_range.start),
15627 context_range,
15628 column: point.column,
15629 extra_variables: runnable.extra_captures,
15630 },
15631 ));
15632 }
15633 runnable_rows
15634 })
15635 }
15636
15637 fn templates_with_tags(
15638 project: &Entity<Project>,
15639 runnable: &mut Runnable,
15640 cx: &mut App,
15641 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15642 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15643 let (worktree_id, file) = project
15644 .buffer_for_id(runnable.buffer, cx)
15645 .and_then(|buffer| buffer.read(cx).file())
15646 .map(|file| (file.worktree_id(cx), file.clone()))
15647 .unzip();
15648
15649 (
15650 project.task_store().read(cx).task_inventory().cloned(),
15651 worktree_id,
15652 file,
15653 )
15654 });
15655
15656 let tags = mem::take(&mut runnable.tags);
15657 let language = runnable.language.clone();
15658 cx.spawn(async move |cx| {
15659 let mut templates_with_tags = Vec::new();
15660 if let Some(inventory) = inventory {
15661 for RunnableTag(tag) in tags {
15662 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15663 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15664 }) else {
15665 return templates_with_tags;
15666 };
15667 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15668 move |(_, template)| {
15669 template.tags.iter().any(|source_tag| source_tag == &tag)
15670 },
15671 ));
15672 }
15673 }
15674 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15675
15676 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15677 // Strongest source wins; if we have worktree tag binding, prefer that to
15678 // global and language bindings;
15679 // if we have a global binding, prefer that to language binding.
15680 let first_mismatch = templates_with_tags
15681 .iter()
15682 .position(|(tag_source, _)| tag_source != leading_tag_source);
15683 if let Some(index) = first_mismatch {
15684 templates_with_tags.truncate(index);
15685 }
15686 }
15687
15688 templates_with_tags
15689 })
15690 }
15691
15692 pub fn move_to_enclosing_bracket(
15693 &mut self,
15694 _: &MoveToEnclosingBracket,
15695 window: &mut Window,
15696 cx: &mut Context<Self>,
15697 ) {
15698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15699 self.change_selections(Default::default(), window, cx, |s| {
15700 s.move_offsets_with(|snapshot, selection| {
15701 let Some(enclosing_bracket_ranges) =
15702 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15703 else {
15704 return;
15705 };
15706
15707 let mut best_length = usize::MAX;
15708 let mut best_inside = false;
15709 let mut best_in_bracket_range = false;
15710 let mut best_destination = None;
15711 for (open, close) in enclosing_bracket_ranges {
15712 let close = close.to_inclusive();
15713 let length = close.end() - open.start;
15714 let inside = selection.start >= open.end && selection.end <= *close.start();
15715 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15716 || close.contains(&selection.head());
15717
15718 // If best is next to a bracket and current isn't, skip
15719 if !in_bracket_range && best_in_bracket_range {
15720 continue;
15721 }
15722
15723 // Prefer smaller lengths unless best is inside and current isn't
15724 if length > best_length && (best_inside || !inside) {
15725 continue;
15726 }
15727
15728 best_length = length;
15729 best_inside = inside;
15730 best_in_bracket_range = in_bracket_range;
15731 best_destination = Some(
15732 if close.contains(&selection.start) && close.contains(&selection.end) {
15733 if inside { open.end } else { open.start }
15734 } else if inside {
15735 *close.start()
15736 } else {
15737 *close.end()
15738 },
15739 );
15740 }
15741
15742 if let Some(destination) = best_destination {
15743 selection.collapse_to(destination, SelectionGoal::None);
15744 }
15745 })
15746 });
15747 }
15748
15749 pub fn undo_selection(
15750 &mut self,
15751 _: &UndoSelection,
15752 window: &mut Window,
15753 cx: &mut Context<Self>,
15754 ) {
15755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15756 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15757 self.selection_history.mode = SelectionHistoryMode::Undoing;
15758 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15759 this.end_selection(window, cx);
15760 this.change_selections(
15761 SelectionEffects::scroll(Autoscroll::newest()),
15762 window,
15763 cx,
15764 |s| s.select_anchors(entry.selections.to_vec()),
15765 );
15766 });
15767 self.selection_history.mode = SelectionHistoryMode::Normal;
15768
15769 self.select_next_state = entry.select_next_state;
15770 self.select_prev_state = entry.select_prev_state;
15771 self.add_selections_state = entry.add_selections_state;
15772 }
15773 }
15774
15775 pub fn redo_selection(
15776 &mut self,
15777 _: &RedoSelection,
15778 window: &mut Window,
15779 cx: &mut Context<Self>,
15780 ) {
15781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15782 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15783 self.selection_history.mode = SelectionHistoryMode::Redoing;
15784 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15785 this.end_selection(window, cx);
15786 this.change_selections(
15787 SelectionEffects::scroll(Autoscroll::newest()),
15788 window,
15789 cx,
15790 |s| s.select_anchors(entry.selections.to_vec()),
15791 );
15792 });
15793 self.selection_history.mode = SelectionHistoryMode::Normal;
15794
15795 self.select_next_state = entry.select_next_state;
15796 self.select_prev_state = entry.select_prev_state;
15797 self.add_selections_state = entry.add_selections_state;
15798 }
15799 }
15800
15801 pub fn expand_excerpts(
15802 &mut self,
15803 action: &ExpandExcerpts,
15804 _: &mut Window,
15805 cx: &mut Context<Self>,
15806 ) {
15807 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15808 }
15809
15810 pub fn expand_excerpts_down(
15811 &mut self,
15812 action: &ExpandExcerptsDown,
15813 _: &mut Window,
15814 cx: &mut Context<Self>,
15815 ) {
15816 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15817 }
15818
15819 pub fn expand_excerpts_up(
15820 &mut self,
15821 action: &ExpandExcerptsUp,
15822 _: &mut Window,
15823 cx: &mut Context<Self>,
15824 ) {
15825 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15826 }
15827
15828 pub fn expand_excerpts_for_direction(
15829 &mut self,
15830 lines: u32,
15831 direction: ExpandExcerptDirection,
15832
15833 cx: &mut Context<Self>,
15834 ) {
15835 let selections = self.selections.disjoint_anchors_arc();
15836
15837 let lines = if lines == 0 {
15838 EditorSettings::get_global(cx).expand_excerpt_lines
15839 } else {
15840 lines
15841 };
15842
15843 self.buffer.update(cx, |buffer, cx| {
15844 let snapshot = buffer.snapshot(cx);
15845 let mut excerpt_ids = selections
15846 .iter()
15847 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15848 .collect::<Vec<_>>();
15849 excerpt_ids.sort();
15850 excerpt_ids.dedup();
15851 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15852 })
15853 }
15854
15855 pub fn expand_excerpt(
15856 &mut self,
15857 excerpt: ExcerptId,
15858 direction: ExpandExcerptDirection,
15859 window: &mut Window,
15860 cx: &mut Context<Self>,
15861 ) {
15862 let current_scroll_position = self.scroll_position(cx);
15863 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15864 let mut should_scroll_up = false;
15865
15866 if direction == ExpandExcerptDirection::Down {
15867 let multi_buffer = self.buffer.read(cx);
15868 let snapshot = multi_buffer.snapshot(cx);
15869 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15870 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15871 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15872 {
15873 let buffer_snapshot = buffer.read(cx).snapshot();
15874 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15875 let last_row = buffer_snapshot.max_point().row;
15876 let lines_below = last_row.saturating_sub(excerpt_end_row);
15877 should_scroll_up = lines_below >= lines_to_expand;
15878 }
15879 }
15880
15881 self.buffer.update(cx, |buffer, cx| {
15882 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15883 });
15884
15885 if should_scroll_up {
15886 let new_scroll_position =
15887 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15888 self.set_scroll_position(new_scroll_position, window, cx);
15889 }
15890 }
15891
15892 pub fn go_to_singleton_buffer_point(
15893 &mut self,
15894 point: Point,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) {
15898 self.go_to_singleton_buffer_range(point..point, window, cx);
15899 }
15900
15901 pub fn go_to_singleton_buffer_range(
15902 &mut self,
15903 range: Range<Point>,
15904 window: &mut Window,
15905 cx: &mut Context<Self>,
15906 ) {
15907 let multibuffer = self.buffer().read(cx);
15908 let Some(buffer) = multibuffer.as_singleton() else {
15909 return;
15910 };
15911 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15912 return;
15913 };
15914 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15915 return;
15916 };
15917 self.change_selections(
15918 SelectionEffects::default().nav_history(true),
15919 window,
15920 cx,
15921 |s| s.select_anchor_ranges([start..end]),
15922 );
15923 }
15924
15925 pub fn go_to_diagnostic(
15926 &mut self,
15927 action: &GoToDiagnostic,
15928 window: &mut Window,
15929 cx: &mut Context<Self>,
15930 ) {
15931 if !self.diagnostics_enabled() {
15932 return;
15933 }
15934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15935 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15936 }
15937
15938 pub fn go_to_prev_diagnostic(
15939 &mut self,
15940 action: &GoToPreviousDiagnostic,
15941 window: &mut Window,
15942 cx: &mut Context<Self>,
15943 ) {
15944 if !self.diagnostics_enabled() {
15945 return;
15946 }
15947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15948 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15949 }
15950
15951 pub fn go_to_diagnostic_impl(
15952 &mut self,
15953 direction: Direction,
15954 severity: GoToDiagnosticSeverityFilter,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) {
15958 let buffer = self.buffer.read(cx).snapshot(cx);
15959 let selection = self.selections.newest::<usize>(cx);
15960
15961 let mut active_group_id = None;
15962 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15963 && active_group.active_range.start.to_offset(&buffer) == selection.start
15964 {
15965 active_group_id = Some(active_group.group_id);
15966 }
15967
15968 fn filtered<'a>(
15969 snapshot: EditorSnapshot,
15970 severity: GoToDiagnosticSeverityFilter,
15971 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15972 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15973 diagnostics
15974 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15975 .filter(|entry| entry.range.start != entry.range.end)
15976 .filter(|entry| !entry.diagnostic.is_unnecessary)
15977 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15978 }
15979
15980 let snapshot = self.snapshot(window, cx);
15981 let before = filtered(
15982 snapshot.clone(),
15983 severity,
15984 buffer
15985 .diagnostics_in_range(0..selection.start)
15986 .filter(|entry| entry.range.start <= selection.start),
15987 );
15988 let after = filtered(
15989 snapshot,
15990 severity,
15991 buffer
15992 .diagnostics_in_range(selection.start..buffer.len())
15993 .filter(|entry| entry.range.start >= selection.start),
15994 );
15995
15996 let mut found: Option<DiagnosticEntryRef<usize>> = None;
15997 if direction == Direction::Prev {
15998 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15999 {
16000 for diagnostic in prev_diagnostics.into_iter().rev() {
16001 if diagnostic.range.start != selection.start
16002 || active_group_id
16003 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16004 {
16005 found = Some(diagnostic);
16006 break 'outer;
16007 }
16008 }
16009 }
16010 } else {
16011 for diagnostic in after.chain(before) {
16012 if diagnostic.range.start != selection.start
16013 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16014 {
16015 found = Some(diagnostic);
16016 break;
16017 }
16018 }
16019 }
16020 let Some(next_diagnostic) = found else {
16021 return;
16022 };
16023
16024 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16025 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16026 return;
16027 };
16028 self.change_selections(Default::default(), window, cx, |s| {
16029 s.select_ranges(vec![
16030 next_diagnostic.range.start..next_diagnostic.range.start,
16031 ])
16032 });
16033 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16034 self.refresh_edit_prediction(false, true, window, cx);
16035 }
16036
16037 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16039 let snapshot = self.snapshot(window, cx);
16040 let selection = self.selections.newest::<Point>(cx);
16041 self.go_to_hunk_before_or_after_position(
16042 &snapshot,
16043 selection.head(),
16044 Direction::Next,
16045 window,
16046 cx,
16047 );
16048 }
16049
16050 pub fn go_to_hunk_before_or_after_position(
16051 &mut self,
16052 snapshot: &EditorSnapshot,
16053 position: Point,
16054 direction: Direction,
16055 window: &mut Window,
16056 cx: &mut Context<Editor>,
16057 ) {
16058 let row = if direction == Direction::Next {
16059 self.hunk_after_position(snapshot, position)
16060 .map(|hunk| hunk.row_range.start)
16061 } else {
16062 self.hunk_before_position(snapshot, position)
16063 };
16064
16065 if let Some(row) = row {
16066 let destination = Point::new(row.0, 0);
16067 let autoscroll = Autoscroll::center();
16068
16069 self.unfold_ranges(&[destination..destination], false, false, cx);
16070 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16071 s.select_ranges([destination..destination]);
16072 });
16073 }
16074 }
16075
16076 fn hunk_after_position(
16077 &mut self,
16078 snapshot: &EditorSnapshot,
16079 position: Point,
16080 ) -> Option<MultiBufferDiffHunk> {
16081 snapshot
16082 .buffer_snapshot
16083 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16084 .find(|hunk| hunk.row_range.start.0 > position.row)
16085 .or_else(|| {
16086 snapshot
16087 .buffer_snapshot
16088 .diff_hunks_in_range(Point::zero()..position)
16089 .find(|hunk| hunk.row_range.end.0 < position.row)
16090 })
16091 }
16092
16093 fn go_to_prev_hunk(
16094 &mut self,
16095 _: &GoToPreviousHunk,
16096 window: &mut Window,
16097 cx: &mut Context<Self>,
16098 ) {
16099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16100 let snapshot = self.snapshot(window, cx);
16101 let selection = self.selections.newest::<Point>(cx);
16102 self.go_to_hunk_before_or_after_position(
16103 &snapshot,
16104 selection.head(),
16105 Direction::Prev,
16106 window,
16107 cx,
16108 );
16109 }
16110
16111 fn hunk_before_position(
16112 &mut self,
16113 snapshot: &EditorSnapshot,
16114 position: Point,
16115 ) -> Option<MultiBufferRow> {
16116 snapshot
16117 .buffer_snapshot
16118 .diff_hunk_before(position)
16119 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16120 }
16121
16122 fn go_to_next_change(
16123 &mut self,
16124 _: &GoToNextChange,
16125 window: &mut Window,
16126 cx: &mut Context<Self>,
16127 ) {
16128 if let Some(selections) = self
16129 .change_list
16130 .next_change(1, Direction::Next)
16131 .map(|s| s.to_vec())
16132 {
16133 self.change_selections(Default::default(), window, cx, |s| {
16134 let map = s.display_map();
16135 s.select_display_ranges(selections.iter().map(|a| {
16136 let point = a.to_display_point(&map);
16137 point..point
16138 }))
16139 })
16140 }
16141 }
16142
16143 fn go_to_previous_change(
16144 &mut self,
16145 _: &GoToPreviousChange,
16146 window: &mut Window,
16147 cx: &mut Context<Self>,
16148 ) {
16149 if let Some(selections) = self
16150 .change_list
16151 .next_change(1, Direction::Prev)
16152 .map(|s| s.to_vec())
16153 {
16154 self.change_selections(Default::default(), window, cx, |s| {
16155 let map = s.display_map();
16156 s.select_display_ranges(selections.iter().map(|a| {
16157 let point = a.to_display_point(&map);
16158 point..point
16159 }))
16160 })
16161 }
16162 }
16163
16164 pub fn go_to_next_document_highlight(
16165 &mut self,
16166 _: &GoToNextDocumentHighlight,
16167 window: &mut Window,
16168 cx: &mut Context<Self>,
16169 ) {
16170 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16171 }
16172
16173 pub fn go_to_prev_document_highlight(
16174 &mut self,
16175 _: &GoToPreviousDocumentHighlight,
16176 window: &mut Window,
16177 cx: &mut Context<Self>,
16178 ) {
16179 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16180 }
16181
16182 pub fn go_to_document_highlight_before_or_after_position(
16183 &mut self,
16184 direction: Direction,
16185 window: &mut Window,
16186 cx: &mut Context<Editor>,
16187 ) {
16188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16189 let snapshot = self.snapshot(window, cx);
16190 let buffer = &snapshot.buffer_snapshot;
16191 let position = self.selections.newest::<Point>(cx).head();
16192 let anchor_position = buffer.anchor_after(position);
16193
16194 // Get all document highlights (both read and write)
16195 let mut all_highlights = Vec::new();
16196
16197 if let Some((_, read_highlights)) = self
16198 .background_highlights
16199 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16200 {
16201 all_highlights.extend(read_highlights.iter());
16202 }
16203
16204 if let Some((_, write_highlights)) = self
16205 .background_highlights
16206 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16207 {
16208 all_highlights.extend(write_highlights.iter());
16209 }
16210
16211 if all_highlights.is_empty() {
16212 return;
16213 }
16214
16215 // Sort highlights by position
16216 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16217
16218 let target_highlight = match direction {
16219 Direction::Next => {
16220 // Find the first highlight after the current position
16221 all_highlights
16222 .iter()
16223 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16224 }
16225 Direction::Prev => {
16226 // Find the last highlight before the current position
16227 all_highlights
16228 .iter()
16229 .rev()
16230 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16231 }
16232 };
16233
16234 if let Some(highlight) = target_highlight {
16235 let destination = highlight.start.to_point(buffer);
16236 let autoscroll = Autoscroll::center();
16237
16238 self.unfold_ranges(&[destination..destination], false, false, cx);
16239 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16240 s.select_ranges([destination..destination]);
16241 });
16242 }
16243 }
16244
16245 fn go_to_line<T: 'static>(
16246 &mut self,
16247 position: Anchor,
16248 highlight_color: Option<Hsla>,
16249 window: &mut Window,
16250 cx: &mut Context<Self>,
16251 ) {
16252 let snapshot = self.snapshot(window, cx).display_snapshot;
16253 let position = position.to_point(&snapshot.buffer_snapshot);
16254 let start = snapshot
16255 .buffer_snapshot
16256 .clip_point(Point::new(position.row, 0), Bias::Left);
16257 let end = start + Point::new(1, 0);
16258 let start = snapshot.buffer_snapshot.anchor_before(start);
16259 let end = snapshot.buffer_snapshot.anchor_before(end);
16260
16261 self.highlight_rows::<T>(
16262 start..end,
16263 highlight_color
16264 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16265 Default::default(),
16266 cx,
16267 );
16268
16269 if self.buffer.read(cx).is_singleton() {
16270 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16271 }
16272 }
16273
16274 pub fn go_to_definition(
16275 &mut self,
16276 _: &GoToDefinition,
16277 window: &mut Window,
16278 cx: &mut Context<Self>,
16279 ) -> Task<Result<Navigated>> {
16280 let definition =
16281 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16282 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16283 cx.spawn_in(window, async move |editor, cx| {
16284 if definition.await? == Navigated::Yes {
16285 return Ok(Navigated::Yes);
16286 }
16287 match fallback_strategy {
16288 GoToDefinitionFallback::None => Ok(Navigated::No),
16289 GoToDefinitionFallback::FindAllReferences => {
16290 match editor.update_in(cx, |editor, window, cx| {
16291 editor.find_all_references(&FindAllReferences, window, cx)
16292 })? {
16293 Some(references) => references.await,
16294 None => Ok(Navigated::No),
16295 }
16296 }
16297 }
16298 })
16299 }
16300
16301 pub fn go_to_declaration(
16302 &mut self,
16303 _: &GoToDeclaration,
16304 window: &mut Window,
16305 cx: &mut Context<Self>,
16306 ) -> Task<Result<Navigated>> {
16307 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16308 }
16309
16310 pub fn go_to_declaration_split(
16311 &mut self,
16312 _: &GoToDeclaration,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) -> Task<Result<Navigated>> {
16316 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16317 }
16318
16319 pub fn go_to_implementation(
16320 &mut self,
16321 _: &GoToImplementation,
16322 window: &mut Window,
16323 cx: &mut Context<Self>,
16324 ) -> Task<Result<Navigated>> {
16325 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16326 }
16327
16328 pub fn go_to_implementation_split(
16329 &mut self,
16330 _: &GoToImplementationSplit,
16331 window: &mut Window,
16332 cx: &mut Context<Self>,
16333 ) -> Task<Result<Navigated>> {
16334 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16335 }
16336
16337 pub fn go_to_type_definition(
16338 &mut self,
16339 _: &GoToTypeDefinition,
16340 window: &mut Window,
16341 cx: &mut Context<Self>,
16342 ) -> Task<Result<Navigated>> {
16343 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16344 }
16345
16346 pub fn go_to_definition_split(
16347 &mut self,
16348 _: &GoToDefinitionSplit,
16349 window: &mut Window,
16350 cx: &mut Context<Self>,
16351 ) -> Task<Result<Navigated>> {
16352 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16353 }
16354
16355 pub fn go_to_type_definition_split(
16356 &mut self,
16357 _: &GoToTypeDefinitionSplit,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) -> Task<Result<Navigated>> {
16361 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16362 }
16363
16364 fn go_to_definition_of_kind(
16365 &mut self,
16366 kind: GotoDefinitionKind,
16367 split: bool,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) -> Task<Result<Navigated>> {
16371 let Some(provider) = self.semantics_provider.clone() else {
16372 return Task::ready(Ok(Navigated::No));
16373 };
16374 let head = self.selections.newest::<usize>(cx).head();
16375 let buffer = self.buffer.read(cx);
16376 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16377 return Task::ready(Ok(Navigated::No));
16378 };
16379 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16380 return Task::ready(Ok(Navigated::No));
16381 };
16382
16383 cx.spawn_in(window, async move |editor, cx| {
16384 let Some(definitions) = definitions.await? else {
16385 return Ok(Navigated::No);
16386 };
16387 let navigated = editor
16388 .update_in(cx, |editor, window, cx| {
16389 editor.navigate_to_hover_links(
16390 Some(kind),
16391 definitions
16392 .into_iter()
16393 .filter(|location| {
16394 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16395 })
16396 .map(HoverLink::Text)
16397 .collect::<Vec<_>>(),
16398 split,
16399 window,
16400 cx,
16401 )
16402 })?
16403 .await?;
16404 anyhow::Ok(navigated)
16405 })
16406 }
16407
16408 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16409 let selection = self.selections.newest_anchor();
16410 let head = selection.head();
16411 let tail = selection.tail();
16412
16413 let Some((buffer, start_position)) =
16414 self.buffer.read(cx).text_anchor_for_position(head, cx)
16415 else {
16416 return;
16417 };
16418
16419 let end_position = if head != tail {
16420 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16421 return;
16422 };
16423 Some(pos)
16424 } else {
16425 None
16426 };
16427
16428 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16429 let url = if let Some(end_pos) = end_position {
16430 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16431 } else {
16432 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16433 };
16434
16435 if let Some(url) = url {
16436 cx.update(|window, cx| {
16437 if parse_zed_link(&url, cx).is_some() {
16438 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16439 } else {
16440 cx.open_url(&url);
16441 }
16442 })?;
16443 }
16444
16445 anyhow::Ok(())
16446 });
16447
16448 url_finder.detach();
16449 }
16450
16451 pub fn open_selected_filename(
16452 &mut self,
16453 _: &OpenSelectedFilename,
16454 window: &mut Window,
16455 cx: &mut Context<Self>,
16456 ) {
16457 let Some(workspace) = self.workspace() else {
16458 return;
16459 };
16460
16461 let position = self.selections.newest_anchor().head();
16462
16463 let Some((buffer, buffer_position)) =
16464 self.buffer.read(cx).text_anchor_for_position(position, cx)
16465 else {
16466 return;
16467 };
16468
16469 let project = self.project.clone();
16470
16471 cx.spawn_in(window, async move |_, cx| {
16472 let result = find_file(&buffer, project, buffer_position, cx).await;
16473
16474 if let Some((_, path)) = result {
16475 workspace
16476 .update_in(cx, |workspace, window, cx| {
16477 workspace.open_resolved_path(path, window, cx)
16478 })?
16479 .await?;
16480 }
16481 anyhow::Ok(())
16482 })
16483 .detach();
16484 }
16485
16486 pub(crate) fn navigate_to_hover_links(
16487 &mut self,
16488 kind: Option<GotoDefinitionKind>,
16489 definitions: Vec<HoverLink>,
16490 split: bool,
16491 window: &mut Window,
16492 cx: &mut Context<Editor>,
16493 ) -> Task<Result<Navigated>> {
16494 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16495 let mut first_url_or_file = None;
16496 let definitions: Vec<_> = definitions
16497 .into_iter()
16498 .filter_map(|def| match def {
16499 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16500 HoverLink::InlayHint(lsp_location, server_id) => {
16501 let computation =
16502 self.compute_target_location(lsp_location, server_id, window, cx);
16503 Some(cx.background_spawn(computation))
16504 }
16505 HoverLink::Url(url) => {
16506 first_url_or_file = Some(Either::Left(url));
16507 None
16508 }
16509 HoverLink::File(path) => {
16510 first_url_or_file = Some(Either::Right(path));
16511 None
16512 }
16513 })
16514 .collect();
16515
16516 let workspace = self.workspace();
16517
16518 cx.spawn_in(window, async move |editor, cx| {
16519 let locations: Vec<Location> = future::join_all(definitions)
16520 .await
16521 .into_iter()
16522 .filter_map(|location| location.transpose())
16523 .collect::<Result<_>>()
16524 .context("location tasks")?;
16525 let mut locations = cx.update(|_, cx| {
16526 locations
16527 .into_iter()
16528 .map(|location| {
16529 let buffer = location.buffer.read(cx);
16530 (location.buffer, location.range.to_point(buffer))
16531 })
16532 .into_group_map()
16533 })?;
16534 let mut num_locations = 0;
16535 for ranges in locations.values_mut() {
16536 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16537 ranges.dedup();
16538 num_locations += ranges.len();
16539 }
16540
16541 if num_locations > 1 {
16542 let Some(workspace) = workspace else {
16543 return Ok(Navigated::No);
16544 };
16545
16546 let tab_kind = match kind {
16547 Some(GotoDefinitionKind::Implementation) => "Implementations",
16548 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16549 Some(GotoDefinitionKind::Declaration) => "Declarations",
16550 Some(GotoDefinitionKind::Type) => "Types",
16551 };
16552 let title = editor
16553 .update_in(cx, |_, _, cx| {
16554 let target = locations
16555 .iter()
16556 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16557 .map(|(buffer, location)| {
16558 buffer
16559 .read(cx)
16560 .text_for_range(location.clone())
16561 .collect::<String>()
16562 })
16563 .filter(|text| !text.contains('\n'))
16564 .unique()
16565 .take(3)
16566 .join(", ");
16567 if target.is_empty() {
16568 tab_kind.to_owned()
16569 } else {
16570 format!("{tab_kind} for {target}")
16571 }
16572 })
16573 .context("buffer title")?;
16574
16575 let opened = workspace
16576 .update_in(cx, |workspace, window, cx| {
16577 Self::open_locations_in_multibuffer(
16578 workspace,
16579 locations,
16580 title,
16581 split,
16582 MultibufferSelectionMode::First,
16583 window,
16584 cx,
16585 )
16586 })
16587 .is_ok();
16588
16589 anyhow::Ok(Navigated::from_bool(opened))
16590 } else if num_locations == 0 {
16591 // If there is one url or file, open it directly
16592 match first_url_or_file {
16593 Some(Either::Left(url)) => {
16594 cx.update(|_, cx| cx.open_url(&url))?;
16595 Ok(Navigated::Yes)
16596 }
16597 Some(Either::Right(path)) => {
16598 let Some(workspace) = workspace else {
16599 return Ok(Navigated::No);
16600 };
16601
16602 workspace
16603 .update_in(cx, |workspace, window, cx| {
16604 workspace.open_resolved_path(path, window, cx)
16605 })?
16606 .await?;
16607 Ok(Navigated::Yes)
16608 }
16609 None => Ok(Navigated::No),
16610 }
16611 } else {
16612 let Some(workspace) = workspace else {
16613 return Ok(Navigated::No);
16614 };
16615
16616 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16617 let target_range = target_ranges.first().unwrap().clone();
16618
16619 editor.update_in(cx, |editor, window, cx| {
16620 let range = target_range.to_point(target_buffer.read(cx));
16621 let range = editor.range_for_match(&range);
16622 let range = collapse_multiline_range(range);
16623
16624 if !split
16625 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16626 {
16627 editor.go_to_singleton_buffer_range(range, window, cx);
16628 } else {
16629 let pane = workspace.read(cx).active_pane().clone();
16630 window.defer(cx, move |window, cx| {
16631 let target_editor: Entity<Self> =
16632 workspace.update(cx, |workspace, cx| {
16633 let pane = if split {
16634 workspace.adjacent_pane(window, cx)
16635 } else {
16636 workspace.active_pane().clone()
16637 };
16638
16639 workspace.open_project_item(
16640 pane,
16641 target_buffer.clone(),
16642 true,
16643 true,
16644 window,
16645 cx,
16646 )
16647 });
16648 target_editor.update(cx, |target_editor, cx| {
16649 // When selecting a definition in a different buffer, disable the nav history
16650 // to avoid creating a history entry at the previous cursor location.
16651 pane.update(cx, |pane, _| pane.disable_history());
16652 target_editor.go_to_singleton_buffer_range(range, window, cx);
16653 pane.update(cx, |pane, _| pane.enable_history());
16654 });
16655 });
16656 }
16657 Navigated::Yes
16658 })
16659 }
16660 })
16661 }
16662
16663 fn compute_target_location(
16664 &self,
16665 lsp_location: lsp::Location,
16666 server_id: LanguageServerId,
16667 window: &mut Window,
16668 cx: &mut Context<Self>,
16669 ) -> Task<anyhow::Result<Option<Location>>> {
16670 let Some(project) = self.project.clone() else {
16671 return Task::ready(Ok(None));
16672 };
16673
16674 cx.spawn_in(window, async move |editor, cx| {
16675 let location_task = editor.update(cx, |_, cx| {
16676 project.update(cx, |project, cx| {
16677 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16678 })
16679 })?;
16680 let location = Some({
16681 let target_buffer_handle = location_task.await.context("open local buffer")?;
16682 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16683 let target_start = target_buffer
16684 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16685 let target_end = target_buffer
16686 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16687 target_buffer.anchor_after(target_start)
16688 ..target_buffer.anchor_before(target_end)
16689 })?;
16690 Location {
16691 buffer: target_buffer_handle,
16692 range,
16693 }
16694 });
16695 Ok(location)
16696 })
16697 }
16698
16699 pub fn find_all_references(
16700 &mut self,
16701 _: &FindAllReferences,
16702 window: &mut Window,
16703 cx: &mut Context<Self>,
16704 ) -> Option<Task<Result<Navigated>>> {
16705 let selection = self.selections.newest::<usize>(cx);
16706 let multi_buffer = self.buffer.read(cx);
16707 let head = selection.head();
16708
16709 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16710 let head_anchor = multi_buffer_snapshot.anchor_at(
16711 head,
16712 if head < selection.tail() {
16713 Bias::Right
16714 } else {
16715 Bias::Left
16716 },
16717 );
16718
16719 match self
16720 .find_all_references_task_sources
16721 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16722 {
16723 Ok(_) => {
16724 log::info!(
16725 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16726 );
16727 return None;
16728 }
16729 Err(i) => {
16730 self.find_all_references_task_sources.insert(i, head_anchor);
16731 }
16732 }
16733
16734 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16735 let workspace = self.workspace()?;
16736 let project = workspace.read(cx).project().clone();
16737 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16738 Some(cx.spawn_in(window, async move |editor, cx| {
16739 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16740 if let Ok(i) = editor
16741 .find_all_references_task_sources
16742 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16743 {
16744 editor.find_all_references_task_sources.remove(i);
16745 }
16746 });
16747
16748 let Some(locations) = references.await? else {
16749 return anyhow::Ok(Navigated::No);
16750 };
16751 let mut locations = cx.update(|_, cx| {
16752 locations
16753 .into_iter()
16754 .map(|location| {
16755 let buffer = location.buffer.read(cx);
16756 (location.buffer, location.range.to_point(buffer))
16757 })
16758 .into_group_map()
16759 })?;
16760 if locations.is_empty() {
16761 return anyhow::Ok(Navigated::No);
16762 }
16763 for ranges in locations.values_mut() {
16764 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16765 ranges.dedup();
16766 }
16767
16768 workspace.update_in(cx, |workspace, window, cx| {
16769 let target = locations
16770 .iter()
16771 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16772 .map(|(buffer, location)| {
16773 buffer
16774 .read(cx)
16775 .text_for_range(location.clone())
16776 .collect::<String>()
16777 })
16778 .filter(|text| !text.contains('\n'))
16779 .unique()
16780 .take(3)
16781 .join(", ");
16782 let title = if target.is_empty() {
16783 "References".to_owned()
16784 } else {
16785 format!("References to {target}")
16786 };
16787 Self::open_locations_in_multibuffer(
16788 workspace,
16789 locations,
16790 title,
16791 false,
16792 MultibufferSelectionMode::First,
16793 window,
16794 cx,
16795 );
16796 Navigated::Yes
16797 })
16798 }))
16799 }
16800
16801 /// Opens a multibuffer with the given project locations in it
16802 pub fn open_locations_in_multibuffer(
16803 workspace: &mut Workspace,
16804 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16805 title: String,
16806 split: bool,
16807 multibuffer_selection_mode: MultibufferSelectionMode,
16808 window: &mut Window,
16809 cx: &mut Context<Workspace>,
16810 ) {
16811 if locations.is_empty() {
16812 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16813 return;
16814 }
16815
16816 let capability = workspace.project().read(cx).capability();
16817 let mut ranges = <Vec<Range<Anchor>>>::new();
16818
16819 // a key to find existing multibuffer editors with the same set of locations
16820 // to prevent us from opening more and more multibuffer tabs for searches and the like
16821 let mut key = (title.clone(), vec![]);
16822 let excerpt_buffer = cx.new(|cx| {
16823 let key = &mut key.1;
16824 let mut multibuffer = MultiBuffer::new(capability);
16825 for (buffer, mut ranges_for_buffer) in locations {
16826 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16827 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16828 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16829 PathKey::for_buffer(&buffer, cx),
16830 buffer.clone(),
16831 ranges_for_buffer,
16832 multibuffer_context_lines(cx),
16833 cx,
16834 );
16835 ranges.extend(new_ranges)
16836 }
16837
16838 multibuffer.with_title(title)
16839 });
16840 let existing = workspace.active_pane().update(cx, |pane, cx| {
16841 pane.items()
16842 .filter_map(|item| item.downcast::<Editor>())
16843 .find(|editor| {
16844 editor
16845 .read(cx)
16846 .lookup_key
16847 .as_ref()
16848 .and_then(|it| {
16849 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16850 })
16851 .is_some_and(|it| *it == key)
16852 })
16853 });
16854 let editor = existing.unwrap_or_else(|| {
16855 cx.new(|cx| {
16856 let mut editor = Editor::for_multibuffer(
16857 excerpt_buffer,
16858 Some(workspace.project().clone()),
16859 window,
16860 cx,
16861 );
16862 editor.lookup_key = Some(Box::new(key));
16863 editor
16864 })
16865 });
16866 editor.update(cx, |editor, cx| {
16867 match multibuffer_selection_mode {
16868 MultibufferSelectionMode::First => {
16869 if let Some(first_range) = ranges.first() {
16870 editor.change_selections(
16871 SelectionEffects::no_scroll(),
16872 window,
16873 cx,
16874 |selections| {
16875 selections.clear_disjoint();
16876 selections
16877 .select_anchor_ranges(std::iter::once(first_range.clone()));
16878 },
16879 );
16880 }
16881 editor.highlight_background::<Self>(
16882 &ranges,
16883 |theme| theme.colors().editor_highlighted_line_background,
16884 cx,
16885 );
16886 }
16887 MultibufferSelectionMode::All => {
16888 editor.change_selections(
16889 SelectionEffects::no_scroll(),
16890 window,
16891 cx,
16892 |selections| {
16893 selections.clear_disjoint();
16894 selections.select_anchor_ranges(ranges);
16895 },
16896 );
16897 }
16898 }
16899 editor.register_buffers_with_language_servers(cx);
16900 });
16901
16902 let item = Box::new(editor);
16903 let item_id = item.item_id();
16904
16905 if split {
16906 let pane = workspace.adjacent_pane(window, cx);
16907 workspace.add_item(pane, item, None, true, true, window, cx);
16908 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16909 let (preview_item_id, preview_item_idx) =
16910 workspace.active_pane().read_with(cx, |pane, _| {
16911 (pane.preview_item_id(), pane.preview_item_idx())
16912 });
16913
16914 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16915
16916 if let Some(preview_item_id) = preview_item_id {
16917 workspace.active_pane().update(cx, |pane, cx| {
16918 pane.remove_item(preview_item_id, false, false, window, cx);
16919 });
16920 }
16921 } else {
16922 workspace.add_item_to_active_pane(item, None, true, window, cx);
16923 }
16924 workspace.active_pane().update(cx, |pane, cx| {
16925 pane.set_preview_item_id(Some(item_id), cx);
16926 });
16927 }
16928
16929 pub fn rename(
16930 &mut self,
16931 _: &Rename,
16932 window: &mut Window,
16933 cx: &mut Context<Self>,
16934 ) -> Option<Task<Result<()>>> {
16935 use language::ToOffset as _;
16936
16937 let provider = self.semantics_provider.clone()?;
16938 let selection = self.selections.newest_anchor().clone();
16939 let (cursor_buffer, cursor_buffer_position) = self
16940 .buffer
16941 .read(cx)
16942 .text_anchor_for_position(selection.head(), cx)?;
16943 let (tail_buffer, cursor_buffer_position_end) = self
16944 .buffer
16945 .read(cx)
16946 .text_anchor_for_position(selection.tail(), cx)?;
16947 if tail_buffer != cursor_buffer {
16948 return None;
16949 }
16950
16951 let snapshot = cursor_buffer.read(cx).snapshot();
16952 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16953 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16954 let prepare_rename = provider
16955 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16956 .unwrap_or_else(|| Task::ready(Ok(None)));
16957 drop(snapshot);
16958
16959 Some(cx.spawn_in(window, async move |this, cx| {
16960 let rename_range = if let Some(range) = prepare_rename.await? {
16961 Some(range)
16962 } else {
16963 this.update(cx, |this, cx| {
16964 let buffer = this.buffer.read(cx).snapshot(cx);
16965 let mut buffer_highlights = this
16966 .document_highlights_for_position(selection.head(), &buffer)
16967 .filter(|highlight| {
16968 highlight.start.excerpt_id == selection.head().excerpt_id
16969 && highlight.end.excerpt_id == selection.head().excerpt_id
16970 });
16971 buffer_highlights
16972 .next()
16973 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16974 })?
16975 };
16976 if let Some(rename_range) = rename_range {
16977 this.update_in(cx, |this, window, cx| {
16978 let snapshot = cursor_buffer.read(cx).snapshot();
16979 let rename_buffer_range = rename_range.to_offset(&snapshot);
16980 let cursor_offset_in_rename_range =
16981 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16982 let cursor_offset_in_rename_range_end =
16983 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16984
16985 this.take_rename(false, window, cx);
16986 let buffer = this.buffer.read(cx).read(cx);
16987 let cursor_offset = selection.head().to_offset(&buffer);
16988 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16989 let rename_end = rename_start + rename_buffer_range.len();
16990 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16991 let mut old_highlight_id = None;
16992 let old_name: Arc<str> = buffer
16993 .chunks(rename_start..rename_end, true)
16994 .map(|chunk| {
16995 if old_highlight_id.is_none() {
16996 old_highlight_id = chunk.syntax_highlight_id;
16997 }
16998 chunk.text
16999 })
17000 .collect::<String>()
17001 .into();
17002
17003 drop(buffer);
17004
17005 // Position the selection in the rename editor so that it matches the current selection.
17006 this.show_local_selections = false;
17007 let rename_editor = cx.new(|cx| {
17008 let mut editor = Editor::single_line(window, cx);
17009 editor.buffer.update(cx, |buffer, cx| {
17010 buffer.edit([(0..0, old_name.clone())], None, cx)
17011 });
17012 let rename_selection_range = match cursor_offset_in_rename_range
17013 .cmp(&cursor_offset_in_rename_range_end)
17014 {
17015 Ordering::Equal => {
17016 editor.select_all(&SelectAll, window, cx);
17017 return editor;
17018 }
17019 Ordering::Less => {
17020 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17021 }
17022 Ordering::Greater => {
17023 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17024 }
17025 };
17026 if rename_selection_range.end > old_name.len() {
17027 editor.select_all(&SelectAll, window, cx);
17028 } else {
17029 editor.change_selections(Default::default(), window, cx, |s| {
17030 s.select_ranges([rename_selection_range]);
17031 });
17032 }
17033 editor
17034 });
17035 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17036 if e == &EditorEvent::Focused {
17037 cx.emit(EditorEvent::FocusedIn)
17038 }
17039 })
17040 .detach();
17041
17042 let write_highlights =
17043 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17044 let read_highlights =
17045 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17046 let ranges = write_highlights
17047 .iter()
17048 .flat_map(|(_, ranges)| ranges.iter())
17049 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17050 .cloned()
17051 .collect();
17052
17053 this.highlight_text::<Rename>(
17054 ranges,
17055 HighlightStyle {
17056 fade_out: Some(0.6),
17057 ..Default::default()
17058 },
17059 cx,
17060 );
17061 let rename_focus_handle = rename_editor.focus_handle(cx);
17062 window.focus(&rename_focus_handle);
17063 let block_id = this.insert_blocks(
17064 [BlockProperties {
17065 style: BlockStyle::Flex,
17066 placement: BlockPlacement::Below(range.start),
17067 height: Some(1),
17068 render: Arc::new({
17069 let rename_editor = rename_editor.clone();
17070 move |cx: &mut BlockContext| {
17071 let mut text_style = cx.editor_style.text.clone();
17072 if let Some(highlight_style) = old_highlight_id
17073 .and_then(|h| h.style(&cx.editor_style.syntax))
17074 {
17075 text_style = text_style.highlight(highlight_style);
17076 }
17077 div()
17078 .block_mouse_except_scroll()
17079 .pl(cx.anchor_x)
17080 .child(EditorElement::new(
17081 &rename_editor,
17082 EditorStyle {
17083 background: cx.theme().system().transparent,
17084 local_player: cx.editor_style.local_player,
17085 text: text_style,
17086 scrollbar_width: cx.editor_style.scrollbar_width,
17087 syntax: cx.editor_style.syntax.clone(),
17088 status: cx.editor_style.status.clone(),
17089 inlay_hints_style: HighlightStyle {
17090 font_weight: Some(FontWeight::BOLD),
17091 ..make_inlay_hints_style(cx.app)
17092 },
17093 edit_prediction_styles: make_suggestion_styles(
17094 cx.app,
17095 ),
17096 ..EditorStyle::default()
17097 },
17098 ))
17099 .into_any_element()
17100 }
17101 }),
17102 priority: 0,
17103 }],
17104 Some(Autoscroll::fit()),
17105 cx,
17106 )[0];
17107 this.pending_rename = Some(RenameState {
17108 range,
17109 old_name,
17110 editor: rename_editor,
17111 block_id,
17112 });
17113 })?;
17114 }
17115
17116 Ok(())
17117 }))
17118 }
17119
17120 pub fn confirm_rename(
17121 &mut self,
17122 _: &ConfirmRename,
17123 window: &mut Window,
17124 cx: &mut Context<Self>,
17125 ) -> Option<Task<Result<()>>> {
17126 let rename = self.take_rename(false, window, cx)?;
17127 let workspace = self.workspace()?.downgrade();
17128 let (buffer, start) = self
17129 .buffer
17130 .read(cx)
17131 .text_anchor_for_position(rename.range.start, cx)?;
17132 let (end_buffer, _) = self
17133 .buffer
17134 .read(cx)
17135 .text_anchor_for_position(rename.range.end, cx)?;
17136 if buffer != end_buffer {
17137 return None;
17138 }
17139
17140 let old_name = rename.old_name;
17141 let new_name = rename.editor.read(cx).text(cx);
17142
17143 let rename = self.semantics_provider.as_ref()?.perform_rename(
17144 &buffer,
17145 start,
17146 new_name.clone(),
17147 cx,
17148 )?;
17149
17150 Some(cx.spawn_in(window, async move |editor, cx| {
17151 let project_transaction = rename.await?;
17152 Self::open_project_transaction(
17153 &editor,
17154 workspace,
17155 project_transaction,
17156 format!("Rename: {} → {}", old_name, new_name),
17157 cx,
17158 )
17159 .await?;
17160
17161 editor.update(cx, |editor, cx| {
17162 editor.refresh_document_highlights(cx);
17163 })?;
17164 Ok(())
17165 }))
17166 }
17167
17168 fn take_rename(
17169 &mut self,
17170 moving_cursor: bool,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) -> Option<RenameState> {
17174 let rename = self.pending_rename.take()?;
17175 if rename.editor.focus_handle(cx).is_focused(window) {
17176 window.focus(&self.focus_handle);
17177 }
17178
17179 self.remove_blocks(
17180 [rename.block_id].into_iter().collect(),
17181 Some(Autoscroll::fit()),
17182 cx,
17183 );
17184 self.clear_highlights::<Rename>(cx);
17185 self.show_local_selections = true;
17186
17187 if moving_cursor {
17188 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17189 editor.selections.newest::<usize>(cx).head()
17190 });
17191
17192 // Update the selection to match the position of the selection inside
17193 // the rename editor.
17194 let snapshot = self.buffer.read(cx).read(cx);
17195 let rename_range = rename.range.to_offset(&snapshot);
17196 let cursor_in_editor = snapshot
17197 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17198 .min(rename_range.end);
17199 drop(snapshot);
17200
17201 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17202 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17203 });
17204 } else {
17205 self.refresh_document_highlights(cx);
17206 }
17207
17208 Some(rename)
17209 }
17210
17211 pub fn pending_rename(&self) -> Option<&RenameState> {
17212 self.pending_rename.as_ref()
17213 }
17214
17215 fn format(
17216 &mut self,
17217 _: &Format,
17218 window: &mut Window,
17219 cx: &mut Context<Self>,
17220 ) -> Option<Task<Result<()>>> {
17221 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17222
17223 let project = match &self.project {
17224 Some(project) => project.clone(),
17225 None => return None,
17226 };
17227
17228 Some(self.perform_format(
17229 project,
17230 FormatTrigger::Manual,
17231 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17232 window,
17233 cx,
17234 ))
17235 }
17236
17237 fn format_selections(
17238 &mut self,
17239 _: &FormatSelections,
17240 window: &mut Window,
17241 cx: &mut Context<Self>,
17242 ) -> Option<Task<Result<()>>> {
17243 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17244
17245 let project = match &self.project {
17246 Some(project) => project.clone(),
17247 None => return None,
17248 };
17249
17250 let ranges = self
17251 .selections
17252 .all_adjusted(cx)
17253 .into_iter()
17254 .map(|selection| selection.range())
17255 .collect_vec();
17256
17257 Some(self.perform_format(
17258 project,
17259 FormatTrigger::Manual,
17260 FormatTarget::Ranges(ranges),
17261 window,
17262 cx,
17263 ))
17264 }
17265
17266 fn perform_format(
17267 &mut self,
17268 project: Entity<Project>,
17269 trigger: FormatTrigger,
17270 target: FormatTarget,
17271 window: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) -> Task<Result<()>> {
17274 let buffer = self.buffer.clone();
17275 let (buffers, target) = match target {
17276 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17277 FormatTarget::Ranges(selection_ranges) => {
17278 let multi_buffer = buffer.read(cx);
17279 let snapshot = multi_buffer.read(cx);
17280 let mut buffers = HashSet::default();
17281 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17282 BTreeMap::new();
17283 for selection_range in selection_ranges {
17284 for (buffer, buffer_range, _) in
17285 snapshot.range_to_buffer_ranges(selection_range)
17286 {
17287 let buffer_id = buffer.remote_id();
17288 let start = buffer.anchor_before(buffer_range.start);
17289 let end = buffer.anchor_after(buffer_range.end);
17290 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17291 buffer_id_to_ranges
17292 .entry(buffer_id)
17293 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17294 .or_insert_with(|| vec![start..end]);
17295 }
17296 }
17297 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17298 }
17299 };
17300
17301 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17302 let selections_prev = transaction_id_prev
17303 .and_then(|transaction_id_prev| {
17304 // default to selections as they were after the last edit, if we have them,
17305 // instead of how they are now.
17306 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17307 // will take you back to where you made the last edit, instead of staying where you scrolled
17308 self.selection_history
17309 .transaction(transaction_id_prev)
17310 .map(|t| t.0.clone())
17311 })
17312 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17313
17314 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17315 let format = project.update(cx, |project, cx| {
17316 project.format(buffers, target, true, trigger, cx)
17317 });
17318
17319 cx.spawn_in(window, async move |editor, cx| {
17320 let transaction = futures::select_biased! {
17321 transaction = format.log_err().fuse() => transaction,
17322 () = timeout => {
17323 log::warn!("timed out waiting for formatting");
17324 None
17325 }
17326 };
17327
17328 buffer
17329 .update(cx, |buffer, cx| {
17330 if let Some(transaction) = transaction
17331 && !buffer.is_singleton()
17332 {
17333 buffer.push_transaction(&transaction.0, cx);
17334 }
17335 cx.notify();
17336 })
17337 .ok();
17338
17339 if let Some(transaction_id_now) =
17340 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17341 {
17342 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17343 if has_new_transaction {
17344 _ = editor.update(cx, |editor, _| {
17345 editor
17346 .selection_history
17347 .insert_transaction(transaction_id_now, selections_prev);
17348 });
17349 }
17350 }
17351
17352 Ok(())
17353 })
17354 }
17355
17356 fn organize_imports(
17357 &mut self,
17358 _: &OrganizeImports,
17359 window: &mut Window,
17360 cx: &mut Context<Self>,
17361 ) -> Option<Task<Result<()>>> {
17362 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17363 let project = match &self.project {
17364 Some(project) => project.clone(),
17365 None => return None,
17366 };
17367 Some(self.perform_code_action_kind(
17368 project,
17369 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17370 window,
17371 cx,
17372 ))
17373 }
17374
17375 fn perform_code_action_kind(
17376 &mut self,
17377 project: Entity<Project>,
17378 kind: CodeActionKind,
17379 window: &mut Window,
17380 cx: &mut Context<Self>,
17381 ) -> Task<Result<()>> {
17382 let buffer = self.buffer.clone();
17383 let buffers = buffer.read(cx).all_buffers();
17384 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17385 let apply_action = project.update(cx, |project, cx| {
17386 project.apply_code_action_kind(buffers, kind, true, cx)
17387 });
17388 cx.spawn_in(window, async move |_, cx| {
17389 let transaction = futures::select_biased! {
17390 () = timeout => {
17391 log::warn!("timed out waiting for executing code action");
17392 None
17393 }
17394 transaction = apply_action.log_err().fuse() => transaction,
17395 };
17396 buffer
17397 .update(cx, |buffer, cx| {
17398 // check if we need this
17399 if let Some(transaction) = transaction
17400 && !buffer.is_singleton()
17401 {
17402 buffer.push_transaction(&transaction.0, cx);
17403 }
17404 cx.notify();
17405 })
17406 .ok();
17407 Ok(())
17408 })
17409 }
17410
17411 pub fn restart_language_server(
17412 &mut self,
17413 _: &RestartLanguageServer,
17414 _: &mut Window,
17415 cx: &mut Context<Self>,
17416 ) {
17417 if let Some(project) = self.project.clone() {
17418 self.buffer.update(cx, |multi_buffer, cx| {
17419 project.update(cx, |project, cx| {
17420 project.restart_language_servers_for_buffers(
17421 multi_buffer.all_buffers().into_iter().collect(),
17422 HashSet::default(),
17423 cx,
17424 );
17425 });
17426 })
17427 }
17428 }
17429
17430 pub fn stop_language_server(
17431 &mut self,
17432 _: &StopLanguageServer,
17433 _: &mut Window,
17434 cx: &mut Context<Self>,
17435 ) {
17436 if let Some(project) = self.project.clone() {
17437 self.buffer.update(cx, |multi_buffer, cx| {
17438 project.update(cx, |project, cx| {
17439 project.stop_language_servers_for_buffers(
17440 multi_buffer.all_buffers().into_iter().collect(),
17441 HashSet::default(),
17442 cx,
17443 );
17444 cx.emit(project::Event::RefreshInlayHints);
17445 });
17446 });
17447 }
17448 }
17449
17450 fn cancel_language_server_work(
17451 workspace: &mut Workspace,
17452 _: &actions::CancelLanguageServerWork,
17453 _: &mut Window,
17454 cx: &mut Context<Workspace>,
17455 ) {
17456 let project = workspace.project();
17457 let buffers = workspace
17458 .active_item(cx)
17459 .and_then(|item| item.act_as::<Editor>(cx))
17460 .map_or(HashSet::default(), |editor| {
17461 editor.read(cx).buffer.read(cx).all_buffers()
17462 });
17463 project.update(cx, |project, cx| {
17464 project.cancel_language_server_work_for_buffers(buffers, cx);
17465 });
17466 }
17467
17468 fn show_character_palette(
17469 &mut self,
17470 _: &ShowCharacterPalette,
17471 window: &mut Window,
17472 _: &mut Context<Self>,
17473 ) {
17474 window.show_character_palette();
17475 }
17476
17477 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17478 if !self.diagnostics_enabled() {
17479 return;
17480 }
17481
17482 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17483 let buffer = self.buffer.read(cx).snapshot(cx);
17484 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17485 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17486 let is_valid = buffer
17487 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17488 .any(|entry| {
17489 entry.diagnostic.is_primary
17490 && !entry.range.is_empty()
17491 && entry.range.start == primary_range_start
17492 && entry.diagnostic.message == active_diagnostics.active_message
17493 });
17494
17495 if !is_valid {
17496 self.dismiss_diagnostics(cx);
17497 }
17498 }
17499 }
17500
17501 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17502 match &self.active_diagnostics {
17503 ActiveDiagnostic::Group(group) => Some(group),
17504 _ => None,
17505 }
17506 }
17507
17508 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17509 if !self.diagnostics_enabled() {
17510 return;
17511 }
17512 self.dismiss_diagnostics(cx);
17513 self.active_diagnostics = ActiveDiagnostic::All;
17514 }
17515
17516 fn activate_diagnostics(
17517 &mut self,
17518 buffer_id: BufferId,
17519 diagnostic: DiagnosticEntryRef<'_, usize>,
17520 window: &mut Window,
17521 cx: &mut Context<Self>,
17522 ) {
17523 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17524 return;
17525 }
17526 self.dismiss_diagnostics(cx);
17527 let snapshot = self.snapshot(window, cx);
17528 let buffer = self.buffer.read(cx).snapshot(cx);
17529 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17530 return;
17531 };
17532
17533 let diagnostic_group = buffer
17534 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17535 .collect::<Vec<_>>();
17536
17537 let blocks =
17538 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17539
17540 let blocks = self.display_map.update(cx, |display_map, cx| {
17541 display_map.insert_blocks(blocks, cx).into_iter().collect()
17542 });
17543 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17544 active_range: buffer.anchor_before(diagnostic.range.start)
17545 ..buffer.anchor_after(diagnostic.range.end),
17546 active_message: diagnostic.diagnostic.message.clone(),
17547 group_id: diagnostic.diagnostic.group_id,
17548 blocks,
17549 });
17550 cx.notify();
17551 }
17552
17553 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17554 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17555 return;
17556 };
17557
17558 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17559 if let ActiveDiagnostic::Group(group) = prev {
17560 self.display_map.update(cx, |display_map, cx| {
17561 display_map.remove_blocks(group.blocks, cx);
17562 });
17563 cx.notify();
17564 }
17565 }
17566
17567 /// Disable inline diagnostics rendering for this editor.
17568 pub fn disable_inline_diagnostics(&mut self) {
17569 self.inline_diagnostics_enabled = false;
17570 self.inline_diagnostics_update = Task::ready(());
17571 self.inline_diagnostics.clear();
17572 }
17573
17574 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17575 self.diagnostics_enabled = false;
17576 self.dismiss_diagnostics(cx);
17577 self.inline_diagnostics_update = Task::ready(());
17578 self.inline_diagnostics.clear();
17579 }
17580
17581 pub fn disable_word_completions(&mut self) {
17582 self.word_completions_enabled = false;
17583 }
17584
17585 pub fn diagnostics_enabled(&self) -> bool {
17586 self.diagnostics_enabled && self.mode.is_full()
17587 }
17588
17589 pub fn inline_diagnostics_enabled(&self) -> bool {
17590 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17591 }
17592
17593 pub fn show_inline_diagnostics(&self) -> bool {
17594 self.show_inline_diagnostics
17595 }
17596
17597 pub fn toggle_inline_diagnostics(
17598 &mut self,
17599 _: &ToggleInlineDiagnostics,
17600 window: &mut Window,
17601 cx: &mut Context<Editor>,
17602 ) {
17603 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17604 self.refresh_inline_diagnostics(false, window, cx);
17605 }
17606
17607 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17608 self.diagnostics_max_severity = severity;
17609 self.display_map.update(cx, |display_map, _| {
17610 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17611 });
17612 }
17613
17614 pub fn toggle_diagnostics(
17615 &mut self,
17616 _: &ToggleDiagnostics,
17617 window: &mut Window,
17618 cx: &mut Context<Editor>,
17619 ) {
17620 if !self.diagnostics_enabled() {
17621 return;
17622 }
17623
17624 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17625 EditorSettings::get_global(cx)
17626 .diagnostics_max_severity
17627 .filter(|severity| severity != &DiagnosticSeverity::Off)
17628 .unwrap_or(DiagnosticSeverity::Hint)
17629 } else {
17630 DiagnosticSeverity::Off
17631 };
17632 self.set_max_diagnostics_severity(new_severity, cx);
17633 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17634 self.active_diagnostics = ActiveDiagnostic::None;
17635 self.inline_diagnostics_update = Task::ready(());
17636 self.inline_diagnostics.clear();
17637 } else {
17638 self.refresh_inline_diagnostics(false, window, cx);
17639 }
17640
17641 cx.notify();
17642 }
17643
17644 pub fn toggle_minimap(
17645 &mut self,
17646 _: &ToggleMinimap,
17647 window: &mut Window,
17648 cx: &mut Context<Editor>,
17649 ) {
17650 if self.supports_minimap(cx) {
17651 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17652 }
17653 }
17654
17655 fn refresh_inline_diagnostics(
17656 &mut self,
17657 debounce: bool,
17658 window: &mut Window,
17659 cx: &mut Context<Self>,
17660 ) {
17661 let max_severity = ProjectSettings::get_global(cx)
17662 .diagnostics
17663 .inline
17664 .max_severity
17665 .unwrap_or(self.diagnostics_max_severity);
17666
17667 if !self.inline_diagnostics_enabled()
17668 || !self.show_inline_diagnostics
17669 || max_severity == DiagnosticSeverity::Off
17670 {
17671 self.inline_diagnostics_update = Task::ready(());
17672 self.inline_diagnostics.clear();
17673 return;
17674 }
17675
17676 let debounce_ms = ProjectSettings::get_global(cx)
17677 .diagnostics
17678 .inline
17679 .update_debounce_ms;
17680 let debounce = if debounce && debounce_ms > 0 {
17681 Some(Duration::from_millis(debounce_ms))
17682 } else {
17683 None
17684 };
17685 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17686 if let Some(debounce) = debounce {
17687 cx.background_executor().timer(debounce).await;
17688 }
17689 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17690 editor
17691 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17692 .ok()
17693 }) else {
17694 return;
17695 };
17696
17697 let new_inline_diagnostics = cx
17698 .background_spawn(async move {
17699 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17700 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17701 let message = diagnostic_entry
17702 .diagnostic
17703 .message
17704 .split_once('\n')
17705 .map(|(line, _)| line)
17706 .map(SharedString::new)
17707 .unwrap_or_else(|| {
17708 SharedString::new(&*diagnostic_entry.diagnostic.message)
17709 });
17710 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17711 let (Ok(i) | Err(i)) = inline_diagnostics
17712 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17713 inline_diagnostics.insert(
17714 i,
17715 (
17716 start_anchor,
17717 InlineDiagnostic {
17718 message,
17719 group_id: diagnostic_entry.diagnostic.group_id,
17720 start: diagnostic_entry.range.start.to_point(&snapshot),
17721 is_primary: diagnostic_entry.diagnostic.is_primary,
17722 severity: diagnostic_entry.diagnostic.severity,
17723 },
17724 ),
17725 );
17726 }
17727 inline_diagnostics
17728 })
17729 .await;
17730
17731 editor
17732 .update(cx, |editor, cx| {
17733 editor.inline_diagnostics = new_inline_diagnostics;
17734 cx.notify();
17735 })
17736 .ok();
17737 });
17738 }
17739
17740 fn pull_diagnostics(
17741 &mut self,
17742 buffer_id: Option<BufferId>,
17743 window: &Window,
17744 cx: &mut Context<Self>,
17745 ) -> Option<()> {
17746 if !self.mode().is_full() {
17747 return None;
17748 }
17749 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17750 .diagnostics
17751 .lsp_pull_diagnostics;
17752 if !pull_diagnostics_settings.enabled {
17753 return None;
17754 }
17755 let project = self.project()?.downgrade();
17756 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17757 let mut buffers = self.buffer.read(cx).all_buffers();
17758 if let Some(buffer_id) = buffer_id {
17759 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17760 }
17761
17762 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17763 cx.background_executor().timer(debounce).await;
17764
17765 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17766 buffers
17767 .into_iter()
17768 .filter_map(|buffer| {
17769 project
17770 .update(cx, |project, cx| {
17771 project.lsp_store().update(cx, |lsp_store, cx| {
17772 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17773 })
17774 })
17775 .ok()
17776 })
17777 .collect::<FuturesUnordered<_>>()
17778 }) else {
17779 return;
17780 };
17781
17782 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17783 match pull_task {
17784 Ok(()) => {
17785 if editor
17786 .update_in(cx, |editor, window, cx| {
17787 editor.update_diagnostics_state(window, cx);
17788 })
17789 .is_err()
17790 {
17791 return;
17792 }
17793 }
17794 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17795 }
17796 }
17797 });
17798
17799 Some(())
17800 }
17801
17802 pub fn set_selections_from_remote(
17803 &mut self,
17804 selections: Vec<Selection<Anchor>>,
17805 pending_selection: Option<Selection<Anchor>>,
17806 window: &mut Window,
17807 cx: &mut Context<Self>,
17808 ) {
17809 let old_cursor_position = self.selections.newest_anchor().head();
17810 self.selections.change_with(cx, |s| {
17811 s.select_anchors(selections);
17812 if let Some(pending_selection) = pending_selection {
17813 s.set_pending(pending_selection, SelectMode::Character);
17814 } else {
17815 s.clear_pending();
17816 }
17817 });
17818 self.selections_did_change(
17819 false,
17820 &old_cursor_position,
17821 SelectionEffects::default(),
17822 window,
17823 cx,
17824 );
17825 }
17826
17827 pub fn transact(
17828 &mut self,
17829 window: &mut Window,
17830 cx: &mut Context<Self>,
17831 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17832 ) -> Option<TransactionId> {
17833 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17834 this.start_transaction_at(Instant::now(), window, cx);
17835 update(this, window, cx);
17836 this.end_transaction_at(Instant::now(), cx)
17837 })
17838 }
17839
17840 pub fn start_transaction_at(
17841 &mut self,
17842 now: Instant,
17843 window: &mut Window,
17844 cx: &mut Context<Self>,
17845 ) -> Option<TransactionId> {
17846 self.end_selection(window, cx);
17847 if let Some(tx_id) = self
17848 .buffer
17849 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17850 {
17851 self.selection_history
17852 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17853 cx.emit(EditorEvent::TransactionBegun {
17854 transaction_id: tx_id,
17855 });
17856 Some(tx_id)
17857 } else {
17858 None
17859 }
17860 }
17861
17862 pub fn end_transaction_at(
17863 &mut self,
17864 now: Instant,
17865 cx: &mut Context<Self>,
17866 ) -> Option<TransactionId> {
17867 if let Some(transaction_id) = self
17868 .buffer
17869 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17870 {
17871 if let Some((_, end_selections)) =
17872 self.selection_history.transaction_mut(transaction_id)
17873 {
17874 *end_selections = Some(self.selections.disjoint_anchors_arc());
17875 } else {
17876 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17877 }
17878
17879 cx.emit(EditorEvent::Edited { transaction_id });
17880 Some(transaction_id)
17881 } else {
17882 None
17883 }
17884 }
17885
17886 pub fn modify_transaction_selection_history(
17887 &mut self,
17888 transaction_id: TransactionId,
17889 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17890 ) -> bool {
17891 self.selection_history
17892 .transaction_mut(transaction_id)
17893 .map(modify)
17894 .is_some()
17895 }
17896
17897 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17898 if self.selection_mark_mode {
17899 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17900 s.move_with(|_, sel| {
17901 sel.collapse_to(sel.head(), SelectionGoal::None);
17902 });
17903 })
17904 }
17905 self.selection_mark_mode = true;
17906 cx.notify();
17907 }
17908
17909 pub fn swap_selection_ends(
17910 &mut self,
17911 _: &actions::SwapSelectionEnds,
17912 window: &mut Window,
17913 cx: &mut Context<Self>,
17914 ) {
17915 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17916 s.move_with(|_, sel| {
17917 if sel.start != sel.end {
17918 sel.reversed = !sel.reversed
17919 }
17920 });
17921 });
17922 self.request_autoscroll(Autoscroll::newest(), cx);
17923 cx.notify();
17924 }
17925
17926 pub fn toggle_focus(
17927 workspace: &mut Workspace,
17928 _: &actions::ToggleFocus,
17929 window: &mut Window,
17930 cx: &mut Context<Workspace>,
17931 ) {
17932 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17933 return;
17934 };
17935 workspace.activate_item(&item, true, true, window, cx);
17936 }
17937
17938 pub fn toggle_fold(
17939 &mut self,
17940 _: &actions::ToggleFold,
17941 window: &mut Window,
17942 cx: &mut Context<Self>,
17943 ) {
17944 if self.is_singleton(cx) {
17945 let selection = self.selections.newest::<Point>(cx);
17946
17947 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17948 let range = if selection.is_empty() {
17949 let point = selection.head().to_display_point(&display_map);
17950 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17951 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17952 .to_point(&display_map);
17953 start..end
17954 } else {
17955 selection.range()
17956 };
17957 if display_map.folds_in_range(range).next().is_some() {
17958 self.unfold_lines(&Default::default(), window, cx)
17959 } else {
17960 self.fold(&Default::default(), window, cx)
17961 }
17962 } else {
17963 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17964 let buffer_ids: HashSet<_> = self
17965 .selections
17966 .disjoint_anchor_ranges()
17967 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17968 .collect();
17969
17970 let should_unfold = buffer_ids
17971 .iter()
17972 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17973
17974 for buffer_id in buffer_ids {
17975 if should_unfold {
17976 self.unfold_buffer(buffer_id, cx);
17977 } else {
17978 self.fold_buffer(buffer_id, cx);
17979 }
17980 }
17981 }
17982 }
17983
17984 pub fn toggle_fold_recursive(
17985 &mut self,
17986 _: &actions::ToggleFoldRecursive,
17987 window: &mut Window,
17988 cx: &mut Context<Self>,
17989 ) {
17990 let selection = self.selections.newest::<Point>(cx);
17991
17992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17993 let range = if selection.is_empty() {
17994 let point = selection.head().to_display_point(&display_map);
17995 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17996 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17997 .to_point(&display_map);
17998 start..end
17999 } else {
18000 selection.range()
18001 };
18002 if display_map.folds_in_range(range).next().is_some() {
18003 self.unfold_recursive(&Default::default(), window, cx)
18004 } else {
18005 self.fold_recursive(&Default::default(), window, cx)
18006 }
18007 }
18008
18009 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18010 if self.is_singleton(cx) {
18011 let mut to_fold = Vec::new();
18012 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18013 let selections = self.selections.all_adjusted(cx);
18014
18015 for selection in selections {
18016 let range = selection.range().sorted();
18017 let buffer_start_row = range.start.row;
18018
18019 if range.start.row != range.end.row {
18020 let mut found = false;
18021 let mut row = range.start.row;
18022 while row <= range.end.row {
18023 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18024 {
18025 found = true;
18026 row = crease.range().end.row + 1;
18027 to_fold.push(crease);
18028 } else {
18029 row += 1
18030 }
18031 }
18032 if found {
18033 continue;
18034 }
18035 }
18036
18037 for row in (0..=range.start.row).rev() {
18038 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18039 && crease.range().end.row >= buffer_start_row
18040 {
18041 to_fold.push(crease);
18042 if row <= range.start.row {
18043 break;
18044 }
18045 }
18046 }
18047 }
18048
18049 self.fold_creases(to_fold, true, window, cx);
18050 } else {
18051 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18052 let buffer_ids = self
18053 .selections
18054 .disjoint_anchor_ranges()
18055 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18056 .collect::<HashSet<_>>();
18057 for buffer_id in buffer_ids {
18058 self.fold_buffer(buffer_id, cx);
18059 }
18060 }
18061 }
18062
18063 pub fn toggle_fold_all(
18064 &mut self,
18065 _: &actions::ToggleFoldAll,
18066 window: &mut Window,
18067 cx: &mut Context<Self>,
18068 ) {
18069 if self.buffer.read(cx).is_singleton() {
18070 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18071 let has_folds = display_map
18072 .folds_in_range(0..display_map.buffer_snapshot.len())
18073 .next()
18074 .is_some();
18075
18076 if has_folds {
18077 self.unfold_all(&actions::UnfoldAll, window, cx);
18078 } else {
18079 self.fold_all(&actions::FoldAll, window, cx);
18080 }
18081 } else {
18082 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18083 let should_unfold = buffer_ids
18084 .iter()
18085 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18086
18087 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18088 editor
18089 .update_in(cx, |editor, _, cx| {
18090 for buffer_id in buffer_ids {
18091 if should_unfold {
18092 editor.unfold_buffer(buffer_id, cx);
18093 } else {
18094 editor.fold_buffer(buffer_id, cx);
18095 }
18096 }
18097 })
18098 .ok();
18099 });
18100 }
18101 }
18102
18103 fn fold_at_level(
18104 &mut self,
18105 fold_at: &FoldAtLevel,
18106 window: &mut Window,
18107 cx: &mut Context<Self>,
18108 ) {
18109 if !self.buffer.read(cx).is_singleton() {
18110 return;
18111 }
18112
18113 let fold_at_level = fold_at.0;
18114 let snapshot = self.buffer.read(cx).snapshot(cx);
18115 let mut to_fold = Vec::new();
18116 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18117
18118 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18119 while start_row < end_row {
18120 match self
18121 .snapshot(window, cx)
18122 .crease_for_buffer_row(MultiBufferRow(start_row))
18123 {
18124 Some(crease) => {
18125 let nested_start_row = crease.range().start.row + 1;
18126 let nested_end_row = crease.range().end.row;
18127
18128 if current_level < fold_at_level {
18129 stack.push((nested_start_row, nested_end_row, current_level + 1));
18130 } else if current_level == fold_at_level {
18131 to_fold.push(crease);
18132 }
18133
18134 start_row = nested_end_row + 1;
18135 }
18136 None => start_row += 1,
18137 }
18138 }
18139 }
18140
18141 self.fold_creases(to_fold, true, window, cx);
18142 }
18143
18144 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18145 if self.buffer.read(cx).is_singleton() {
18146 let mut fold_ranges = Vec::new();
18147 let snapshot = self.buffer.read(cx).snapshot(cx);
18148
18149 for row in 0..snapshot.max_row().0 {
18150 if let Some(foldable_range) = self
18151 .snapshot(window, cx)
18152 .crease_for_buffer_row(MultiBufferRow(row))
18153 {
18154 fold_ranges.push(foldable_range);
18155 }
18156 }
18157
18158 self.fold_creases(fold_ranges, true, window, cx);
18159 } else {
18160 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18161 editor
18162 .update_in(cx, |editor, _, cx| {
18163 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18164 editor.fold_buffer(buffer_id, cx);
18165 }
18166 })
18167 .ok();
18168 });
18169 }
18170 }
18171
18172 pub fn fold_function_bodies(
18173 &mut self,
18174 _: &actions::FoldFunctionBodies,
18175 window: &mut Window,
18176 cx: &mut Context<Self>,
18177 ) {
18178 let snapshot = self.buffer.read(cx).snapshot(cx);
18179
18180 let ranges = snapshot
18181 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18182 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18183 .collect::<Vec<_>>();
18184
18185 let creases = ranges
18186 .into_iter()
18187 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18188 .collect();
18189
18190 self.fold_creases(creases, true, window, cx);
18191 }
18192
18193 pub fn fold_recursive(
18194 &mut self,
18195 _: &actions::FoldRecursive,
18196 window: &mut Window,
18197 cx: &mut Context<Self>,
18198 ) {
18199 let mut to_fold = Vec::new();
18200 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18201 let selections = self.selections.all_adjusted(cx);
18202
18203 for selection in selections {
18204 let range = selection.range().sorted();
18205 let buffer_start_row = range.start.row;
18206
18207 if range.start.row != range.end.row {
18208 let mut found = false;
18209 for row in range.start.row..=range.end.row {
18210 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18211 found = true;
18212 to_fold.push(crease);
18213 }
18214 }
18215 if found {
18216 continue;
18217 }
18218 }
18219
18220 for row in (0..=range.start.row).rev() {
18221 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18222 if crease.range().end.row >= buffer_start_row {
18223 to_fold.push(crease);
18224 } else {
18225 break;
18226 }
18227 }
18228 }
18229 }
18230
18231 self.fold_creases(to_fold, true, window, cx);
18232 }
18233
18234 pub fn fold_at(
18235 &mut self,
18236 buffer_row: MultiBufferRow,
18237 window: &mut Window,
18238 cx: &mut Context<Self>,
18239 ) {
18240 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18241
18242 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18243 let autoscroll = self
18244 .selections
18245 .all::<Point>(cx)
18246 .iter()
18247 .any(|selection| crease.range().overlaps(&selection.range()));
18248
18249 self.fold_creases(vec![crease], autoscroll, window, cx);
18250 }
18251 }
18252
18253 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18254 if self.is_singleton(cx) {
18255 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18256 let buffer = &display_map.buffer_snapshot;
18257 let selections = self.selections.all::<Point>(cx);
18258 let ranges = selections
18259 .iter()
18260 .map(|s| {
18261 let range = s.display_range(&display_map).sorted();
18262 let mut start = range.start.to_point(&display_map);
18263 let mut end = range.end.to_point(&display_map);
18264 start.column = 0;
18265 end.column = buffer.line_len(MultiBufferRow(end.row));
18266 start..end
18267 })
18268 .collect::<Vec<_>>();
18269
18270 self.unfold_ranges(&ranges, true, true, cx);
18271 } else {
18272 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18273 let buffer_ids = self
18274 .selections
18275 .disjoint_anchor_ranges()
18276 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18277 .collect::<HashSet<_>>();
18278 for buffer_id in buffer_ids {
18279 self.unfold_buffer(buffer_id, cx);
18280 }
18281 }
18282 }
18283
18284 pub fn unfold_recursive(
18285 &mut self,
18286 _: &UnfoldRecursive,
18287 _window: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) {
18290 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18291 let selections = self.selections.all::<Point>(cx);
18292 let ranges = selections
18293 .iter()
18294 .map(|s| {
18295 let mut range = s.display_range(&display_map).sorted();
18296 *range.start.column_mut() = 0;
18297 *range.end.column_mut() = display_map.line_len(range.end.row());
18298 let start = range.start.to_point(&display_map);
18299 let end = range.end.to_point(&display_map);
18300 start..end
18301 })
18302 .collect::<Vec<_>>();
18303
18304 self.unfold_ranges(&ranges, true, true, cx);
18305 }
18306
18307 pub fn unfold_at(
18308 &mut self,
18309 buffer_row: MultiBufferRow,
18310 _window: &mut Window,
18311 cx: &mut Context<Self>,
18312 ) {
18313 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18314
18315 let intersection_range = Point::new(buffer_row.0, 0)
18316 ..Point::new(
18317 buffer_row.0,
18318 display_map.buffer_snapshot.line_len(buffer_row),
18319 );
18320
18321 let autoscroll = self
18322 .selections
18323 .all::<Point>(cx)
18324 .iter()
18325 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18326
18327 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18328 }
18329
18330 pub fn unfold_all(
18331 &mut self,
18332 _: &actions::UnfoldAll,
18333 _window: &mut Window,
18334 cx: &mut Context<Self>,
18335 ) {
18336 if self.buffer.read(cx).is_singleton() {
18337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18338 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18339 } else {
18340 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18341 editor
18342 .update(cx, |editor, cx| {
18343 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18344 editor.unfold_buffer(buffer_id, cx);
18345 }
18346 })
18347 .ok();
18348 });
18349 }
18350 }
18351
18352 pub fn fold_selected_ranges(
18353 &mut self,
18354 _: &FoldSelectedRanges,
18355 window: &mut Window,
18356 cx: &mut Context<Self>,
18357 ) {
18358 let selections = self.selections.all_adjusted(cx);
18359 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18360 let ranges = selections
18361 .into_iter()
18362 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18363 .collect::<Vec<_>>();
18364 self.fold_creases(ranges, true, window, cx);
18365 }
18366
18367 pub fn fold_ranges<T: ToOffset + Clone>(
18368 &mut self,
18369 ranges: Vec<Range<T>>,
18370 auto_scroll: bool,
18371 window: &mut Window,
18372 cx: &mut Context<Self>,
18373 ) {
18374 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18375 let ranges = ranges
18376 .into_iter()
18377 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18378 .collect::<Vec<_>>();
18379 self.fold_creases(ranges, auto_scroll, window, cx);
18380 }
18381
18382 pub fn fold_creases<T: ToOffset + Clone>(
18383 &mut self,
18384 creases: Vec<Crease<T>>,
18385 auto_scroll: bool,
18386 _window: &mut Window,
18387 cx: &mut Context<Self>,
18388 ) {
18389 if creases.is_empty() {
18390 return;
18391 }
18392
18393 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18394
18395 if auto_scroll {
18396 self.request_autoscroll(Autoscroll::fit(), cx);
18397 }
18398
18399 cx.notify();
18400
18401 self.scrollbar_marker_state.dirty = true;
18402 self.folds_did_change(cx);
18403 }
18404
18405 /// Removes any folds whose ranges intersect any of the given ranges.
18406 pub fn unfold_ranges<T: ToOffset + Clone>(
18407 &mut self,
18408 ranges: &[Range<T>],
18409 inclusive: bool,
18410 auto_scroll: bool,
18411 cx: &mut Context<Self>,
18412 ) {
18413 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18414 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18415 });
18416 self.folds_did_change(cx);
18417 }
18418
18419 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18420 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18421 return;
18422 }
18423 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18424 self.display_map.update(cx, |display_map, cx| {
18425 display_map.fold_buffers([buffer_id], cx)
18426 });
18427 cx.emit(EditorEvent::BufferFoldToggled {
18428 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18429 folded: true,
18430 });
18431 cx.notify();
18432 }
18433
18434 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18435 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18436 return;
18437 }
18438 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18439 self.display_map.update(cx, |display_map, cx| {
18440 display_map.unfold_buffers([buffer_id], cx);
18441 });
18442 cx.emit(EditorEvent::BufferFoldToggled {
18443 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18444 folded: false,
18445 });
18446 cx.notify();
18447 }
18448
18449 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18450 self.display_map.read(cx).is_buffer_folded(buffer)
18451 }
18452
18453 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18454 self.display_map.read(cx).folded_buffers()
18455 }
18456
18457 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18458 self.display_map.update(cx, |display_map, cx| {
18459 display_map.disable_header_for_buffer(buffer_id, cx);
18460 });
18461 cx.notify();
18462 }
18463
18464 /// Removes any folds with the given ranges.
18465 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18466 &mut self,
18467 ranges: &[Range<T>],
18468 type_id: TypeId,
18469 auto_scroll: bool,
18470 cx: &mut Context<Self>,
18471 ) {
18472 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18473 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18474 });
18475 self.folds_did_change(cx);
18476 }
18477
18478 fn remove_folds_with<T: ToOffset + Clone>(
18479 &mut self,
18480 ranges: &[Range<T>],
18481 auto_scroll: bool,
18482 cx: &mut Context<Self>,
18483 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18484 ) {
18485 if ranges.is_empty() {
18486 return;
18487 }
18488
18489 let mut buffers_affected = HashSet::default();
18490 let multi_buffer = self.buffer().read(cx);
18491 for range in ranges {
18492 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18493 buffers_affected.insert(buffer.read(cx).remote_id());
18494 };
18495 }
18496
18497 self.display_map.update(cx, update);
18498
18499 if auto_scroll {
18500 self.request_autoscroll(Autoscroll::fit(), cx);
18501 }
18502
18503 cx.notify();
18504 self.scrollbar_marker_state.dirty = true;
18505 self.active_indent_guides_state.dirty = true;
18506 }
18507
18508 pub fn update_renderer_widths(
18509 &mut self,
18510 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18511 cx: &mut Context<Self>,
18512 ) -> bool {
18513 self.display_map
18514 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18515 }
18516
18517 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18518 self.display_map.read(cx).fold_placeholder.clone()
18519 }
18520
18521 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18522 self.buffer.update(cx, |buffer, cx| {
18523 buffer.set_all_diff_hunks_expanded(cx);
18524 });
18525 }
18526
18527 pub fn expand_all_diff_hunks(
18528 &mut self,
18529 _: &ExpandAllDiffHunks,
18530 _window: &mut Window,
18531 cx: &mut Context<Self>,
18532 ) {
18533 self.buffer.update(cx, |buffer, cx| {
18534 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18535 });
18536 }
18537
18538 pub fn toggle_selected_diff_hunks(
18539 &mut self,
18540 _: &ToggleSelectedDiffHunks,
18541 _window: &mut Window,
18542 cx: &mut Context<Self>,
18543 ) {
18544 let ranges: Vec<_> = self
18545 .selections
18546 .disjoint_anchors()
18547 .iter()
18548 .map(|s| s.range())
18549 .collect();
18550 self.toggle_diff_hunks_in_ranges(ranges, cx);
18551 }
18552
18553 pub fn diff_hunks_in_ranges<'a>(
18554 &'a self,
18555 ranges: &'a [Range<Anchor>],
18556 buffer: &'a MultiBufferSnapshot,
18557 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18558 ranges.iter().flat_map(move |range| {
18559 let end_excerpt_id = range.end.excerpt_id;
18560 let range = range.to_point(buffer);
18561 let mut peek_end = range.end;
18562 if range.end.row < buffer.max_row().0 {
18563 peek_end = Point::new(range.end.row + 1, 0);
18564 }
18565 buffer
18566 .diff_hunks_in_range(range.start..peek_end)
18567 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18568 })
18569 }
18570
18571 pub fn has_stageable_diff_hunks_in_ranges(
18572 &self,
18573 ranges: &[Range<Anchor>],
18574 snapshot: &MultiBufferSnapshot,
18575 ) -> bool {
18576 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18577 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18578 }
18579
18580 pub fn toggle_staged_selected_diff_hunks(
18581 &mut self,
18582 _: &::git::ToggleStaged,
18583 _: &mut Window,
18584 cx: &mut Context<Self>,
18585 ) {
18586 let snapshot = self.buffer.read(cx).snapshot(cx);
18587 let ranges: Vec<_> = self
18588 .selections
18589 .disjoint_anchors()
18590 .iter()
18591 .map(|s| s.range())
18592 .collect();
18593 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18594 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18595 }
18596
18597 pub fn set_render_diff_hunk_controls(
18598 &mut self,
18599 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18600 cx: &mut Context<Self>,
18601 ) {
18602 self.render_diff_hunk_controls = render_diff_hunk_controls;
18603 cx.notify();
18604 }
18605
18606 pub fn stage_and_next(
18607 &mut self,
18608 _: &::git::StageAndNext,
18609 window: &mut Window,
18610 cx: &mut Context<Self>,
18611 ) {
18612 self.do_stage_or_unstage_and_next(true, window, cx);
18613 }
18614
18615 pub fn unstage_and_next(
18616 &mut self,
18617 _: &::git::UnstageAndNext,
18618 window: &mut Window,
18619 cx: &mut Context<Self>,
18620 ) {
18621 self.do_stage_or_unstage_and_next(false, window, cx);
18622 }
18623
18624 pub fn stage_or_unstage_diff_hunks(
18625 &mut self,
18626 stage: bool,
18627 ranges: Vec<Range<Anchor>>,
18628 cx: &mut Context<Self>,
18629 ) {
18630 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18631 cx.spawn(async move |this, cx| {
18632 task.await?;
18633 this.update(cx, |this, cx| {
18634 let snapshot = this.buffer.read(cx).snapshot(cx);
18635 let chunk_by = this
18636 .diff_hunks_in_ranges(&ranges, &snapshot)
18637 .chunk_by(|hunk| hunk.buffer_id);
18638 for (buffer_id, hunks) in &chunk_by {
18639 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18640 }
18641 })
18642 })
18643 .detach_and_log_err(cx);
18644 }
18645
18646 fn save_buffers_for_ranges_if_needed(
18647 &mut self,
18648 ranges: &[Range<Anchor>],
18649 cx: &mut Context<Editor>,
18650 ) -> Task<Result<()>> {
18651 let multibuffer = self.buffer.read(cx);
18652 let snapshot = multibuffer.read(cx);
18653 let buffer_ids: HashSet<_> = ranges
18654 .iter()
18655 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18656 .collect();
18657 drop(snapshot);
18658
18659 let mut buffers = HashSet::default();
18660 for buffer_id in buffer_ids {
18661 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18662 let buffer = buffer_entity.read(cx);
18663 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18664 {
18665 buffers.insert(buffer_entity);
18666 }
18667 }
18668 }
18669
18670 if let Some(project) = &self.project {
18671 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18672 } else {
18673 Task::ready(Ok(()))
18674 }
18675 }
18676
18677 fn do_stage_or_unstage_and_next(
18678 &mut self,
18679 stage: bool,
18680 window: &mut Window,
18681 cx: &mut Context<Self>,
18682 ) {
18683 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18684
18685 if ranges.iter().any(|range| range.start != range.end) {
18686 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18687 return;
18688 }
18689
18690 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18691 let snapshot = self.snapshot(window, cx);
18692 let position = self.selections.newest::<Point>(cx).head();
18693 let mut row = snapshot
18694 .buffer_snapshot
18695 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18696 .find(|hunk| hunk.row_range.start.0 > position.row)
18697 .map(|hunk| hunk.row_range.start);
18698
18699 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18700 // Outside of the project diff editor, wrap around to the beginning.
18701 if !all_diff_hunks_expanded {
18702 row = row.or_else(|| {
18703 snapshot
18704 .buffer_snapshot
18705 .diff_hunks_in_range(Point::zero()..position)
18706 .find(|hunk| hunk.row_range.end.0 < position.row)
18707 .map(|hunk| hunk.row_range.start)
18708 });
18709 }
18710
18711 if let Some(row) = row {
18712 let destination = Point::new(row.0, 0);
18713 let autoscroll = Autoscroll::center();
18714
18715 self.unfold_ranges(&[destination..destination], false, false, cx);
18716 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18717 s.select_ranges([destination..destination]);
18718 });
18719 }
18720 }
18721
18722 fn do_stage_or_unstage(
18723 &self,
18724 stage: bool,
18725 buffer_id: BufferId,
18726 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18727 cx: &mut App,
18728 ) -> Option<()> {
18729 let project = self.project()?;
18730 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18731 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18732 let buffer_snapshot = buffer.read(cx).snapshot();
18733 let file_exists = buffer_snapshot
18734 .file()
18735 .is_some_and(|file| file.disk_state().exists());
18736 diff.update(cx, |diff, cx| {
18737 diff.stage_or_unstage_hunks(
18738 stage,
18739 &hunks
18740 .map(|hunk| buffer_diff::DiffHunk {
18741 buffer_range: hunk.buffer_range,
18742 diff_base_byte_range: hunk.diff_base_byte_range,
18743 secondary_status: hunk.secondary_status,
18744 range: Point::zero()..Point::zero(), // unused
18745 })
18746 .collect::<Vec<_>>(),
18747 &buffer_snapshot,
18748 file_exists,
18749 cx,
18750 )
18751 });
18752 None
18753 }
18754
18755 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18756 let ranges: Vec<_> = self
18757 .selections
18758 .disjoint_anchors()
18759 .iter()
18760 .map(|s| s.range())
18761 .collect();
18762 self.buffer
18763 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18764 }
18765
18766 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18767 self.buffer.update(cx, |buffer, cx| {
18768 let ranges = vec![Anchor::min()..Anchor::max()];
18769 if !buffer.all_diff_hunks_expanded()
18770 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18771 {
18772 buffer.collapse_diff_hunks(ranges, cx);
18773 true
18774 } else {
18775 false
18776 }
18777 })
18778 }
18779
18780 fn toggle_diff_hunks_in_ranges(
18781 &mut self,
18782 ranges: Vec<Range<Anchor>>,
18783 cx: &mut Context<Editor>,
18784 ) {
18785 self.buffer.update(cx, |buffer, cx| {
18786 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18787 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18788 })
18789 }
18790
18791 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18792 self.buffer.update(cx, |buffer, cx| {
18793 let snapshot = buffer.snapshot(cx);
18794 let excerpt_id = range.end.excerpt_id;
18795 let point_range = range.to_point(&snapshot);
18796 let expand = !buffer.single_hunk_is_expanded(range, cx);
18797 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18798 })
18799 }
18800
18801 pub(crate) fn apply_all_diff_hunks(
18802 &mut self,
18803 _: &ApplyAllDiffHunks,
18804 window: &mut Window,
18805 cx: &mut Context<Self>,
18806 ) {
18807 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18808
18809 let buffers = self.buffer.read(cx).all_buffers();
18810 for branch_buffer in buffers {
18811 branch_buffer.update(cx, |branch_buffer, cx| {
18812 branch_buffer.merge_into_base(Vec::new(), cx);
18813 });
18814 }
18815
18816 if let Some(project) = self.project.clone() {
18817 self.save(
18818 SaveOptions {
18819 format: true,
18820 autosave: false,
18821 },
18822 project,
18823 window,
18824 cx,
18825 )
18826 .detach_and_log_err(cx);
18827 }
18828 }
18829
18830 pub(crate) fn apply_selected_diff_hunks(
18831 &mut self,
18832 _: &ApplyDiffHunk,
18833 window: &mut Window,
18834 cx: &mut Context<Self>,
18835 ) {
18836 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18837 let snapshot = self.snapshot(window, cx);
18838 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18839 let mut ranges_by_buffer = HashMap::default();
18840 self.transact(window, cx, |editor, _window, cx| {
18841 for hunk in hunks {
18842 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18843 ranges_by_buffer
18844 .entry(buffer.clone())
18845 .or_insert_with(Vec::new)
18846 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18847 }
18848 }
18849
18850 for (buffer, ranges) in ranges_by_buffer {
18851 buffer.update(cx, |buffer, cx| {
18852 buffer.merge_into_base(ranges, cx);
18853 });
18854 }
18855 });
18856
18857 if let Some(project) = self.project.clone() {
18858 self.save(
18859 SaveOptions {
18860 format: true,
18861 autosave: false,
18862 },
18863 project,
18864 window,
18865 cx,
18866 )
18867 .detach_and_log_err(cx);
18868 }
18869 }
18870
18871 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18872 if hovered != self.gutter_hovered {
18873 self.gutter_hovered = hovered;
18874 cx.notify();
18875 }
18876 }
18877
18878 pub fn insert_blocks(
18879 &mut self,
18880 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18881 autoscroll: Option<Autoscroll>,
18882 cx: &mut Context<Self>,
18883 ) -> Vec<CustomBlockId> {
18884 let blocks = self
18885 .display_map
18886 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18887 if let Some(autoscroll) = autoscroll {
18888 self.request_autoscroll(autoscroll, cx);
18889 }
18890 cx.notify();
18891 blocks
18892 }
18893
18894 pub fn resize_blocks(
18895 &mut self,
18896 heights: HashMap<CustomBlockId, u32>,
18897 autoscroll: Option<Autoscroll>,
18898 cx: &mut Context<Self>,
18899 ) {
18900 self.display_map
18901 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18902 if let Some(autoscroll) = autoscroll {
18903 self.request_autoscroll(autoscroll, cx);
18904 }
18905 cx.notify();
18906 }
18907
18908 pub fn replace_blocks(
18909 &mut self,
18910 renderers: HashMap<CustomBlockId, RenderBlock>,
18911 autoscroll: Option<Autoscroll>,
18912 cx: &mut Context<Self>,
18913 ) {
18914 self.display_map
18915 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18916 if let Some(autoscroll) = autoscroll {
18917 self.request_autoscroll(autoscroll, cx);
18918 }
18919 cx.notify();
18920 }
18921
18922 pub fn remove_blocks(
18923 &mut self,
18924 block_ids: HashSet<CustomBlockId>,
18925 autoscroll: Option<Autoscroll>,
18926 cx: &mut Context<Self>,
18927 ) {
18928 self.display_map.update(cx, |display_map, cx| {
18929 display_map.remove_blocks(block_ids, cx)
18930 });
18931 if let Some(autoscroll) = autoscroll {
18932 self.request_autoscroll(autoscroll, cx);
18933 }
18934 cx.notify();
18935 }
18936
18937 pub fn row_for_block(
18938 &self,
18939 block_id: CustomBlockId,
18940 cx: &mut Context<Self>,
18941 ) -> Option<DisplayRow> {
18942 self.display_map
18943 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18944 }
18945
18946 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18947 self.focused_block = Some(focused_block);
18948 }
18949
18950 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18951 self.focused_block.take()
18952 }
18953
18954 pub fn insert_creases(
18955 &mut self,
18956 creases: impl IntoIterator<Item = Crease<Anchor>>,
18957 cx: &mut Context<Self>,
18958 ) -> Vec<CreaseId> {
18959 self.display_map
18960 .update(cx, |map, cx| map.insert_creases(creases, cx))
18961 }
18962
18963 pub fn remove_creases(
18964 &mut self,
18965 ids: impl IntoIterator<Item = CreaseId>,
18966 cx: &mut Context<Self>,
18967 ) -> Vec<(CreaseId, Range<Anchor>)> {
18968 self.display_map
18969 .update(cx, |map, cx| map.remove_creases(ids, cx))
18970 }
18971
18972 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18973 self.display_map
18974 .update(cx, |map, cx| map.snapshot(cx))
18975 .longest_row()
18976 }
18977
18978 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18979 self.display_map
18980 .update(cx, |map, cx| map.snapshot(cx))
18981 .max_point()
18982 }
18983
18984 pub fn text(&self, cx: &App) -> String {
18985 self.buffer.read(cx).read(cx).text()
18986 }
18987
18988 pub fn is_empty(&self, cx: &App) -> bool {
18989 self.buffer.read(cx).read(cx).is_empty()
18990 }
18991
18992 pub fn text_option(&self, cx: &App) -> Option<String> {
18993 let text = self.text(cx);
18994 let text = text.trim();
18995
18996 if text.is_empty() {
18997 return None;
18998 }
18999
19000 Some(text.to_string())
19001 }
19002
19003 pub fn set_text(
19004 &mut self,
19005 text: impl Into<Arc<str>>,
19006 window: &mut Window,
19007 cx: &mut Context<Self>,
19008 ) {
19009 self.transact(window, cx, |this, _, cx| {
19010 this.buffer
19011 .read(cx)
19012 .as_singleton()
19013 .expect("you can only call set_text on editors for singleton buffers")
19014 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19015 });
19016 }
19017
19018 pub fn display_text(&self, cx: &mut App) -> String {
19019 self.display_map
19020 .update(cx, |map, cx| map.snapshot(cx))
19021 .text()
19022 }
19023
19024 fn create_minimap(
19025 &self,
19026 minimap_settings: MinimapSettings,
19027 window: &mut Window,
19028 cx: &mut Context<Self>,
19029 ) -> Option<Entity<Self>> {
19030 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
19031 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19032 }
19033
19034 fn initialize_new_minimap(
19035 &self,
19036 minimap_settings: MinimapSettings,
19037 window: &mut Window,
19038 cx: &mut Context<Self>,
19039 ) -> Entity<Self> {
19040 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19041
19042 let mut minimap = Editor::new_internal(
19043 EditorMode::Minimap {
19044 parent: cx.weak_entity(),
19045 },
19046 self.buffer.clone(),
19047 None,
19048 Some(self.display_map.clone()),
19049 window,
19050 cx,
19051 );
19052 minimap.scroll_manager.clone_state(&self.scroll_manager);
19053 minimap.set_text_style_refinement(TextStyleRefinement {
19054 font_size: Some(MINIMAP_FONT_SIZE),
19055 font_weight: Some(MINIMAP_FONT_WEIGHT),
19056 ..Default::default()
19057 });
19058 minimap.update_minimap_configuration(minimap_settings, cx);
19059 cx.new(|_| minimap)
19060 }
19061
19062 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19063 let current_line_highlight = minimap_settings
19064 .current_line_highlight
19065 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19066 self.set_current_line_highlight(Some(current_line_highlight));
19067 }
19068
19069 pub fn minimap(&self) -> Option<&Entity<Self>> {
19070 self.minimap
19071 .as_ref()
19072 .filter(|_| self.minimap_visibility.visible())
19073 }
19074
19075 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19076 let mut wrap_guides = smallvec![];
19077
19078 if self.show_wrap_guides == Some(false) {
19079 return wrap_guides;
19080 }
19081
19082 let settings = self.buffer.read(cx).language_settings(cx);
19083 if settings.show_wrap_guides {
19084 match self.soft_wrap_mode(cx) {
19085 SoftWrap::Column(soft_wrap) => {
19086 wrap_guides.push((soft_wrap as usize, true));
19087 }
19088 SoftWrap::Bounded(soft_wrap) => {
19089 wrap_guides.push((soft_wrap as usize, true));
19090 }
19091 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19092 }
19093 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19094 }
19095
19096 wrap_guides
19097 }
19098
19099 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19100 let settings = self.buffer.read(cx).language_settings(cx);
19101 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19102 match mode {
19103 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19104 SoftWrap::None
19105 }
19106 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19107 language_settings::SoftWrap::PreferredLineLength => {
19108 SoftWrap::Column(settings.preferred_line_length)
19109 }
19110 language_settings::SoftWrap::Bounded => {
19111 SoftWrap::Bounded(settings.preferred_line_length)
19112 }
19113 }
19114 }
19115
19116 pub fn set_soft_wrap_mode(
19117 &mut self,
19118 mode: language_settings::SoftWrap,
19119
19120 cx: &mut Context<Self>,
19121 ) {
19122 self.soft_wrap_mode_override = Some(mode);
19123 cx.notify();
19124 }
19125
19126 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19127 self.hard_wrap = hard_wrap;
19128 cx.notify();
19129 }
19130
19131 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19132 self.text_style_refinement = Some(style);
19133 }
19134
19135 /// called by the Element so we know what style we were most recently rendered with.
19136 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19137 // We intentionally do not inform the display map about the minimap style
19138 // so that wrapping is not recalculated and stays consistent for the editor
19139 // and its linked minimap.
19140 if !self.mode.is_minimap() {
19141 let font = style.text.font();
19142 let font_size = style.text.font_size.to_pixels(window.rem_size());
19143 let display_map = self
19144 .placeholder_display_map
19145 .as_ref()
19146 .filter(|_| self.is_empty(cx))
19147 .unwrap_or(&self.display_map);
19148
19149 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19150 }
19151 self.style = Some(style);
19152 }
19153
19154 pub fn style(&self) -> Option<&EditorStyle> {
19155 self.style.as_ref()
19156 }
19157
19158 // Called by the element. This method is not designed to be called outside of the editor
19159 // element's layout code because it does not notify when rewrapping is computed synchronously.
19160 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19161 if self.is_empty(cx) {
19162 self.placeholder_display_map
19163 .as_ref()
19164 .map_or(false, |display_map| {
19165 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19166 })
19167 } else {
19168 self.display_map
19169 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19170 }
19171 }
19172
19173 pub fn set_soft_wrap(&mut self) {
19174 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19175 }
19176
19177 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19178 if self.soft_wrap_mode_override.is_some() {
19179 self.soft_wrap_mode_override.take();
19180 } else {
19181 let soft_wrap = match self.soft_wrap_mode(cx) {
19182 SoftWrap::GitDiff => return,
19183 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19184 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19185 language_settings::SoftWrap::None
19186 }
19187 };
19188 self.soft_wrap_mode_override = Some(soft_wrap);
19189 }
19190 cx.notify();
19191 }
19192
19193 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19194 let Some(workspace) = self.workspace() else {
19195 return;
19196 };
19197 let fs = workspace.read(cx).app_state().fs.clone();
19198 let current_show = TabBarSettings::get_global(cx).show;
19199 update_settings_file(fs, cx, move |setting, _| {
19200 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19201 });
19202 }
19203
19204 pub fn toggle_indent_guides(
19205 &mut self,
19206 _: &ToggleIndentGuides,
19207 _: &mut Window,
19208 cx: &mut Context<Self>,
19209 ) {
19210 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19211 self.buffer
19212 .read(cx)
19213 .language_settings(cx)
19214 .indent_guides
19215 .enabled
19216 });
19217 self.show_indent_guides = Some(!currently_enabled);
19218 cx.notify();
19219 }
19220
19221 fn should_show_indent_guides(&self) -> Option<bool> {
19222 self.show_indent_guides
19223 }
19224
19225 pub fn toggle_line_numbers(
19226 &mut self,
19227 _: &ToggleLineNumbers,
19228 _: &mut Window,
19229 cx: &mut Context<Self>,
19230 ) {
19231 let mut editor_settings = EditorSettings::get_global(cx).clone();
19232 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19233 EditorSettings::override_global(editor_settings, cx);
19234 }
19235
19236 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19237 if let Some(show_line_numbers) = self.show_line_numbers {
19238 return show_line_numbers;
19239 }
19240 EditorSettings::get_global(cx).gutter.line_numbers
19241 }
19242
19243 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19244 self.use_relative_line_numbers
19245 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19246 }
19247
19248 pub fn toggle_relative_line_numbers(
19249 &mut self,
19250 _: &ToggleRelativeLineNumbers,
19251 _: &mut Window,
19252 cx: &mut Context<Self>,
19253 ) {
19254 let is_relative = self.should_use_relative_line_numbers(cx);
19255 self.set_relative_line_number(Some(!is_relative), cx)
19256 }
19257
19258 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19259 self.use_relative_line_numbers = is_relative;
19260 cx.notify();
19261 }
19262
19263 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19264 self.show_gutter = show_gutter;
19265 cx.notify();
19266 }
19267
19268 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19269 self.show_scrollbars = ScrollbarAxes {
19270 horizontal: show,
19271 vertical: show,
19272 };
19273 cx.notify();
19274 }
19275
19276 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19277 self.show_scrollbars.vertical = show;
19278 cx.notify();
19279 }
19280
19281 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19282 self.show_scrollbars.horizontal = show;
19283 cx.notify();
19284 }
19285
19286 pub fn set_minimap_visibility(
19287 &mut self,
19288 minimap_visibility: MinimapVisibility,
19289 window: &mut Window,
19290 cx: &mut Context<Self>,
19291 ) {
19292 if self.minimap_visibility != minimap_visibility {
19293 if minimap_visibility.visible() && self.minimap.is_none() {
19294 let minimap_settings = EditorSettings::get_global(cx).minimap;
19295 self.minimap =
19296 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19297 }
19298 self.minimap_visibility = minimap_visibility;
19299 cx.notify();
19300 }
19301 }
19302
19303 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19304 self.set_show_scrollbars(false, cx);
19305 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19306 }
19307
19308 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19309 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19310 }
19311
19312 /// Normally the text in full mode and auto height editors is padded on the
19313 /// left side by roughly half a character width for improved hit testing.
19314 ///
19315 /// Use this method to disable this for cases where this is not wanted (e.g.
19316 /// if you want to align the editor text with some other text above or below)
19317 /// or if you want to add this padding to single-line editors.
19318 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19319 self.offset_content = offset_content;
19320 cx.notify();
19321 }
19322
19323 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19324 self.show_line_numbers = Some(show_line_numbers);
19325 cx.notify();
19326 }
19327
19328 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19329 self.disable_expand_excerpt_buttons = true;
19330 cx.notify();
19331 }
19332
19333 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19334 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19335 cx.notify();
19336 }
19337
19338 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19339 self.show_code_actions = Some(show_code_actions);
19340 cx.notify();
19341 }
19342
19343 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19344 self.show_runnables = Some(show_runnables);
19345 cx.notify();
19346 }
19347
19348 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19349 self.show_breakpoints = Some(show_breakpoints);
19350 cx.notify();
19351 }
19352
19353 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19354 if self.display_map.read(cx).masked != masked {
19355 self.display_map.update(cx, |map, _| map.masked = masked);
19356 }
19357 cx.notify()
19358 }
19359
19360 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19361 self.show_wrap_guides = Some(show_wrap_guides);
19362 cx.notify();
19363 }
19364
19365 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19366 self.show_indent_guides = Some(show_indent_guides);
19367 cx.notify();
19368 }
19369
19370 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19371 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19372 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19373 && let Some(dir) = file.abs_path(cx).parent()
19374 {
19375 return Some(dir.to_owned());
19376 }
19377 }
19378
19379 None
19380 }
19381
19382 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19383 self.active_excerpt(cx)?
19384 .1
19385 .read(cx)
19386 .file()
19387 .and_then(|f| f.as_local())
19388 }
19389
19390 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19391 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19392 let buffer = buffer.read(cx);
19393 if let Some(project_path) = buffer.project_path(cx) {
19394 let project = self.project()?.read(cx);
19395 project.absolute_path(&project_path, cx)
19396 } else {
19397 buffer
19398 .file()
19399 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19400 }
19401 })
19402 }
19403
19404 pub fn reveal_in_finder(
19405 &mut self,
19406 _: &RevealInFileManager,
19407 _window: &mut Window,
19408 cx: &mut Context<Self>,
19409 ) {
19410 if let Some(target) = self.target_file(cx) {
19411 cx.reveal_path(&target.abs_path(cx));
19412 }
19413 }
19414
19415 pub fn copy_path(
19416 &mut self,
19417 _: &zed_actions::workspace::CopyPath,
19418 _window: &mut Window,
19419 cx: &mut Context<Self>,
19420 ) {
19421 if let Some(path) = self.target_file_abs_path(cx)
19422 && let Some(path) = path.to_str()
19423 {
19424 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19425 } else {
19426 cx.propagate();
19427 }
19428 }
19429
19430 pub fn copy_relative_path(
19431 &mut self,
19432 _: &zed_actions::workspace::CopyRelativePath,
19433 _window: &mut Window,
19434 cx: &mut Context<Self>,
19435 ) {
19436 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19437 let project = self.project()?.read(cx);
19438 let path = buffer.read(cx).file()?.path();
19439 let path = path.display(project.path_style(cx));
19440 Some(path)
19441 }) {
19442 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19443 } else {
19444 cx.propagate();
19445 }
19446 }
19447
19448 /// Returns the project path for the editor's buffer, if any buffer is
19449 /// opened in the editor.
19450 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19451 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19452 buffer.read(cx).project_path(cx)
19453 } else {
19454 None
19455 }
19456 }
19457
19458 // Returns true if the editor handled a go-to-line request
19459 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19460 maybe!({
19461 let breakpoint_store = self.breakpoint_store.as_ref()?;
19462
19463 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19464 else {
19465 self.clear_row_highlights::<ActiveDebugLine>();
19466 return None;
19467 };
19468
19469 let position = active_stack_frame.position;
19470 let buffer_id = position.buffer_id?;
19471 let snapshot = self
19472 .project
19473 .as_ref()?
19474 .read(cx)
19475 .buffer_for_id(buffer_id, cx)?
19476 .read(cx)
19477 .snapshot();
19478
19479 let mut handled = false;
19480 for (id, ExcerptRange { context, .. }) in
19481 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19482 {
19483 if context.start.cmp(&position, &snapshot).is_ge()
19484 || context.end.cmp(&position, &snapshot).is_lt()
19485 {
19486 continue;
19487 }
19488 let snapshot = self.buffer.read(cx).snapshot(cx);
19489 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19490
19491 handled = true;
19492 self.clear_row_highlights::<ActiveDebugLine>();
19493
19494 self.go_to_line::<ActiveDebugLine>(
19495 multibuffer_anchor,
19496 Some(cx.theme().colors().editor_debugger_active_line_background),
19497 window,
19498 cx,
19499 );
19500
19501 cx.notify();
19502 }
19503
19504 handled.then_some(())
19505 })
19506 .is_some()
19507 }
19508
19509 pub fn copy_file_name_without_extension(
19510 &mut self,
19511 _: &CopyFileNameWithoutExtension,
19512 _: &mut Window,
19513 cx: &mut Context<Self>,
19514 ) {
19515 if let Some(file) = self.target_file(cx)
19516 && let Some(file_stem) = file.path().file_stem()
19517 {
19518 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19519 }
19520 }
19521
19522 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19523 if let Some(file) = self.target_file(cx)
19524 && let Some(name) = file.path().file_name()
19525 {
19526 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19527 }
19528 }
19529
19530 pub fn toggle_git_blame(
19531 &mut self,
19532 _: &::git::Blame,
19533 window: &mut Window,
19534 cx: &mut Context<Self>,
19535 ) {
19536 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19537
19538 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19539 self.start_git_blame(true, window, cx);
19540 }
19541
19542 cx.notify();
19543 }
19544
19545 pub fn toggle_git_blame_inline(
19546 &mut self,
19547 _: &ToggleGitBlameInline,
19548 window: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) {
19551 self.toggle_git_blame_inline_internal(true, window, cx);
19552 cx.notify();
19553 }
19554
19555 pub fn open_git_blame_commit(
19556 &mut self,
19557 _: &OpenGitBlameCommit,
19558 window: &mut Window,
19559 cx: &mut Context<Self>,
19560 ) {
19561 self.open_git_blame_commit_internal(window, cx);
19562 }
19563
19564 fn open_git_blame_commit_internal(
19565 &mut self,
19566 window: &mut Window,
19567 cx: &mut Context<Self>,
19568 ) -> Option<()> {
19569 let blame = self.blame.as_ref()?;
19570 let snapshot = self.snapshot(window, cx);
19571 let cursor = self.selections.newest::<Point>(cx).head();
19572 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19573 let (_, blame_entry) = blame
19574 .update(cx, |blame, cx| {
19575 blame
19576 .blame_for_rows(
19577 &[RowInfo {
19578 buffer_id: Some(buffer.remote_id()),
19579 buffer_row: Some(point.row),
19580 ..Default::default()
19581 }],
19582 cx,
19583 )
19584 .next()
19585 })
19586 .flatten()?;
19587 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19588 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19589 let workspace = self.workspace()?.downgrade();
19590 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19591 None
19592 }
19593
19594 pub fn git_blame_inline_enabled(&self) -> bool {
19595 self.git_blame_inline_enabled
19596 }
19597
19598 pub fn toggle_selection_menu(
19599 &mut self,
19600 _: &ToggleSelectionMenu,
19601 _: &mut Window,
19602 cx: &mut Context<Self>,
19603 ) {
19604 self.show_selection_menu = self
19605 .show_selection_menu
19606 .map(|show_selections_menu| !show_selections_menu)
19607 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19608
19609 cx.notify();
19610 }
19611
19612 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19613 self.show_selection_menu
19614 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19615 }
19616
19617 fn start_git_blame(
19618 &mut self,
19619 user_triggered: bool,
19620 window: &mut Window,
19621 cx: &mut Context<Self>,
19622 ) {
19623 if let Some(project) = self.project() {
19624 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19625 && buffer.read(cx).file().is_none()
19626 {
19627 return;
19628 }
19629
19630 let focused = self.focus_handle(cx).contains_focused(window, cx);
19631
19632 let project = project.clone();
19633 let blame = cx
19634 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19635 self.blame_subscription =
19636 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19637 self.blame = Some(blame);
19638 }
19639 }
19640
19641 fn toggle_git_blame_inline_internal(
19642 &mut self,
19643 user_triggered: bool,
19644 window: &mut Window,
19645 cx: &mut Context<Self>,
19646 ) {
19647 if self.git_blame_inline_enabled {
19648 self.git_blame_inline_enabled = false;
19649 self.show_git_blame_inline = false;
19650 self.show_git_blame_inline_delay_task.take();
19651 } else {
19652 self.git_blame_inline_enabled = true;
19653 self.start_git_blame_inline(user_triggered, window, cx);
19654 }
19655
19656 cx.notify();
19657 }
19658
19659 fn start_git_blame_inline(
19660 &mut self,
19661 user_triggered: bool,
19662 window: &mut Window,
19663 cx: &mut Context<Self>,
19664 ) {
19665 self.start_git_blame(user_triggered, window, cx);
19666
19667 if ProjectSettings::get_global(cx)
19668 .git
19669 .inline_blame_delay()
19670 .is_some()
19671 {
19672 self.start_inline_blame_timer(window, cx);
19673 } else {
19674 self.show_git_blame_inline = true
19675 }
19676 }
19677
19678 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19679 self.blame.as_ref()
19680 }
19681
19682 pub fn show_git_blame_gutter(&self) -> bool {
19683 self.show_git_blame_gutter
19684 }
19685
19686 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19687 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19688 }
19689
19690 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19691 self.show_git_blame_inline
19692 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19693 && !self.newest_selection_head_on_empty_line(cx)
19694 && self.has_blame_entries(cx)
19695 }
19696
19697 fn has_blame_entries(&self, cx: &App) -> bool {
19698 self.blame()
19699 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19700 }
19701
19702 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19703 let cursor_anchor = self.selections.newest_anchor().head();
19704
19705 let snapshot = self.buffer.read(cx).snapshot(cx);
19706 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19707
19708 snapshot.line_len(buffer_row) == 0
19709 }
19710
19711 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19712 let buffer_and_selection = maybe!({
19713 let selection = self.selections.newest::<Point>(cx);
19714 let selection_range = selection.range();
19715
19716 let multi_buffer = self.buffer().read(cx);
19717 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19718 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19719
19720 let (buffer, range, _) = if selection.reversed {
19721 buffer_ranges.first()
19722 } else {
19723 buffer_ranges.last()
19724 }?;
19725
19726 let selection = text::ToPoint::to_point(&range.start, buffer).row
19727 ..text::ToPoint::to_point(&range.end, buffer).row;
19728 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19729 });
19730
19731 let Some((buffer, selection)) = buffer_and_selection else {
19732 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19733 };
19734
19735 let Some(project) = self.project() else {
19736 return Task::ready(Err(anyhow!("editor does not have project")));
19737 };
19738
19739 project.update(cx, |project, cx| {
19740 project.get_permalink_to_line(&buffer, selection, cx)
19741 })
19742 }
19743
19744 pub fn copy_permalink_to_line(
19745 &mut self,
19746 _: &CopyPermalinkToLine,
19747 window: &mut Window,
19748 cx: &mut Context<Self>,
19749 ) {
19750 let permalink_task = self.get_permalink_to_line(cx);
19751 let workspace = self.workspace();
19752
19753 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19754 Ok(permalink) => {
19755 cx.update(|_, cx| {
19756 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19757 })
19758 .ok();
19759 }
19760 Err(err) => {
19761 let message = format!("Failed to copy permalink: {err}");
19762
19763 anyhow::Result::<()>::Err(err).log_err();
19764
19765 if let Some(workspace) = workspace {
19766 workspace
19767 .update_in(cx, |workspace, _, cx| {
19768 struct CopyPermalinkToLine;
19769
19770 workspace.show_toast(
19771 Toast::new(
19772 NotificationId::unique::<CopyPermalinkToLine>(),
19773 message,
19774 ),
19775 cx,
19776 )
19777 })
19778 .ok();
19779 }
19780 }
19781 })
19782 .detach();
19783 }
19784
19785 pub fn copy_file_location(
19786 &mut self,
19787 _: &CopyFileLocation,
19788 _: &mut Window,
19789 cx: &mut Context<Self>,
19790 ) {
19791 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19792 if let Some(file) = self.target_file(cx) {
19793 let path = file.path().display(file.path_style(cx));
19794 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19795 }
19796 }
19797
19798 pub fn open_permalink_to_line(
19799 &mut self,
19800 _: &OpenPermalinkToLine,
19801 window: &mut Window,
19802 cx: &mut Context<Self>,
19803 ) {
19804 let permalink_task = self.get_permalink_to_line(cx);
19805 let workspace = self.workspace();
19806
19807 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19808 Ok(permalink) => {
19809 cx.update(|_, cx| {
19810 cx.open_url(permalink.as_ref());
19811 })
19812 .ok();
19813 }
19814 Err(err) => {
19815 let message = format!("Failed to open permalink: {err}");
19816
19817 anyhow::Result::<()>::Err(err).log_err();
19818
19819 if let Some(workspace) = workspace {
19820 workspace
19821 .update(cx, |workspace, cx| {
19822 struct OpenPermalinkToLine;
19823
19824 workspace.show_toast(
19825 Toast::new(
19826 NotificationId::unique::<OpenPermalinkToLine>(),
19827 message,
19828 ),
19829 cx,
19830 )
19831 })
19832 .ok();
19833 }
19834 }
19835 })
19836 .detach();
19837 }
19838
19839 pub fn insert_uuid_v4(
19840 &mut self,
19841 _: &InsertUuidV4,
19842 window: &mut Window,
19843 cx: &mut Context<Self>,
19844 ) {
19845 self.insert_uuid(UuidVersion::V4, window, cx);
19846 }
19847
19848 pub fn insert_uuid_v7(
19849 &mut self,
19850 _: &InsertUuidV7,
19851 window: &mut Window,
19852 cx: &mut Context<Self>,
19853 ) {
19854 self.insert_uuid(UuidVersion::V7, window, cx);
19855 }
19856
19857 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19858 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19859 self.transact(window, cx, |this, window, cx| {
19860 let edits = this
19861 .selections
19862 .all::<Point>(cx)
19863 .into_iter()
19864 .map(|selection| {
19865 let uuid = match version {
19866 UuidVersion::V4 => uuid::Uuid::new_v4(),
19867 UuidVersion::V7 => uuid::Uuid::now_v7(),
19868 };
19869
19870 (selection.range(), uuid.to_string())
19871 });
19872 this.edit(edits, cx);
19873 this.refresh_edit_prediction(true, false, window, cx);
19874 });
19875 }
19876
19877 pub fn open_selections_in_multibuffer(
19878 &mut self,
19879 _: &OpenSelectionsInMultibuffer,
19880 window: &mut Window,
19881 cx: &mut Context<Self>,
19882 ) {
19883 let multibuffer = self.buffer.read(cx);
19884
19885 let Some(buffer) = multibuffer.as_singleton() else {
19886 return;
19887 };
19888
19889 let Some(workspace) = self.workspace() else {
19890 return;
19891 };
19892
19893 let title = multibuffer.title(cx).to_string();
19894
19895 let locations = self
19896 .selections
19897 .all_anchors(cx)
19898 .iter()
19899 .map(|selection| {
19900 (
19901 buffer.clone(),
19902 (selection.start.text_anchor..selection.end.text_anchor)
19903 .to_point(buffer.read(cx)),
19904 )
19905 })
19906 .into_group_map();
19907
19908 cx.spawn_in(window, async move |_, cx| {
19909 workspace.update_in(cx, |workspace, window, cx| {
19910 Self::open_locations_in_multibuffer(
19911 workspace,
19912 locations,
19913 format!("Selections for '{title}'"),
19914 false,
19915 MultibufferSelectionMode::All,
19916 window,
19917 cx,
19918 );
19919 })
19920 })
19921 .detach();
19922 }
19923
19924 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19925 /// last highlight added will be used.
19926 ///
19927 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19928 pub fn highlight_rows<T: 'static>(
19929 &mut self,
19930 range: Range<Anchor>,
19931 color: Hsla,
19932 options: RowHighlightOptions,
19933 cx: &mut Context<Self>,
19934 ) {
19935 let snapshot = self.buffer().read(cx).snapshot(cx);
19936 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19937 let ix = row_highlights.binary_search_by(|highlight| {
19938 Ordering::Equal
19939 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19940 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19941 });
19942
19943 if let Err(mut ix) = ix {
19944 let index = post_inc(&mut self.highlight_order);
19945
19946 // If this range intersects with the preceding highlight, then merge it with
19947 // the preceding highlight. Otherwise insert a new highlight.
19948 let mut merged = false;
19949 if ix > 0 {
19950 let prev_highlight = &mut row_highlights[ix - 1];
19951 if prev_highlight
19952 .range
19953 .end
19954 .cmp(&range.start, &snapshot)
19955 .is_ge()
19956 {
19957 ix -= 1;
19958 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19959 prev_highlight.range.end = range.end;
19960 }
19961 merged = true;
19962 prev_highlight.index = index;
19963 prev_highlight.color = color;
19964 prev_highlight.options = options;
19965 }
19966 }
19967
19968 if !merged {
19969 row_highlights.insert(
19970 ix,
19971 RowHighlight {
19972 range,
19973 index,
19974 color,
19975 options,
19976 type_id: TypeId::of::<T>(),
19977 },
19978 );
19979 }
19980
19981 // If any of the following highlights intersect with this one, merge them.
19982 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19983 let highlight = &row_highlights[ix];
19984 if next_highlight
19985 .range
19986 .start
19987 .cmp(&highlight.range.end, &snapshot)
19988 .is_le()
19989 {
19990 if next_highlight
19991 .range
19992 .end
19993 .cmp(&highlight.range.end, &snapshot)
19994 .is_gt()
19995 {
19996 row_highlights[ix].range.end = next_highlight.range.end;
19997 }
19998 row_highlights.remove(ix + 1);
19999 } else {
20000 break;
20001 }
20002 }
20003 }
20004 }
20005
20006 /// Remove any highlighted row ranges of the given type that intersect the
20007 /// given ranges.
20008 pub fn remove_highlighted_rows<T: 'static>(
20009 &mut self,
20010 ranges_to_remove: Vec<Range<Anchor>>,
20011 cx: &mut Context<Self>,
20012 ) {
20013 let snapshot = self.buffer().read(cx).snapshot(cx);
20014 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20015 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20016 row_highlights.retain(|highlight| {
20017 while let Some(range_to_remove) = ranges_to_remove.peek() {
20018 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20019 Ordering::Less | Ordering::Equal => {
20020 ranges_to_remove.next();
20021 }
20022 Ordering::Greater => {
20023 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20024 Ordering::Less | Ordering::Equal => {
20025 return false;
20026 }
20027 Ordering::Greater => break,
20028 }
20029 }
20030 }
20031 }
20032
20033 true
20034 })
20035 }
20036
20037 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20038 pub fn clear_row_highlights<T: 'static>(&mut self) {
20039 self.highlighted_rows.remove(&TypeId::of::<T>());
20040 }
20041
20042 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20043 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20044 self.highlighted_rows
20045 .get(&TypeId::of::<T>())
20046 .map_or(&[] as &[_], |vec| vec.as_slice())
20047 .iter()
20048 .map(|highlight| (highlight.range.clone(), highlight.color))
20049 }
20050
20051 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20052 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20053 /// Allows to ignore certain kinds of highlights.
20054 pub fn highlighted_display_rows(
20055 &self,
20056 window: &mut Window,
20057 cx: &mut App,
20058 ) -> BTreeMap<DisplayRow, LineHighlight> {
20059 let snapshot = self.snapshot(window, cx);
20060 let mut used_highlight_orders = HashMap::default();
20061 self.highlighted_rows
20062 .iter()
20063 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20064 .fold(
20065 BTreeMap::<DisplayRow, LineHighlight>::new(),
20066 |mut unique_rows, highlight| {
20067 let start = highlight.range.start.to_display_point(&snapshot);
20068 let end = highlight.range.end.to_display_point(&snapshot);
20069 let start_row = start.row().0;
20070 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20071 && end.column() == 0
20072 {
20073 end.row().0.saturating_sub(1)
20074 } else {
20075 end.row().0
20076 };
20077 for row in start_row..=end_row {
20078 let used_index =
20079 used_highlight_orders.entry(row).or_insert(highlight.index);
20080 if highlight.index >= *used_index {
20081 *used_index = highlight.index;
20082 unique_rows.insert(
20083 DisplayRow(row),
20084 LineHighlight {
20085 include_gutter: highlight.options.include_gutter,
20086 border: None,
20087 background: highlight.color.into(),
20088 type_id: Some(highlight.type_id),
20089 },
20090 );
20091 }
20092 }
20093 unique_rows
20094 },
20095 )
20096 }
20097
20098 pub fn highlighted_display_row_for_autoscroll(
20099 &self,
20100 snapshot: &DisplaySnapshot,
20101 ) -> Option<DisplayRow> {
20102 self.highlighted_rows
20103 .values()
20104 .flat_map(|highlighted_rows| highlighted_rows.iter())
20105 .filter_map(|highlight| {
20106 if highlight.options.autoscroll {
20107 Some(highlight.range.start.to_display_point(snapshot).row())
20108 } else {
20109 None
20110 }
20111 })
20112 .min()
20113 }
20114
20115 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20116 self.highlight_background::<SearchWithinRange>(
20117 ranges,
20118 |colors| colors.colors().editor_document_highlight_read_background,
20119 cx,
20120 )
20121 }
20122
20123 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20124 self.breadcrumb_header = Some(new_header);
20125 }
20126
20127 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20128 self.clear_background_highlights::<SearchWithinRange>(cx);
20129 }
20130
20131 pub fn highlight_background<T: 'static>(
20132 &mut self,
20133 ranges: &[Range<Anchor>],
20134 color_fetcher: fn(&Theme) -> Hsla,
20135 cx: &mut Context<Self>,
20136 ) {
20137 self.background_highlights.insert(
20138 HighlightKey::Type(TypeId::of::<T>()),
20139 (color_fetcher, Arc::from(ranges)),
20140 );
20141 self.scrollbar_marker_state.dirty = true;
20142 cx.notify();
20143 }
20144
20145 pub fn highlight_background_key<T: 'static>(
20146 &mut self,
20147 key: usize,
20148 ranges: &[Range<Anchor>],
20149 color_fetcher: fn(&Theme) -> Hsla,
20150 cx: &mut Context<Self>,
20151 ) {
20152 self.background_highlights.insert(
20153 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20154 (color_fetcher, Arc::from(ranges)),
20155 );
20156 self.scrollbar_marker_state.dirty = true;
20157 cx.notify();
20158 }
20159
20160 pub fn clear_background_highlights<T: 'static>(
20161 &mut self,
20162 cx: &mut Context<Self>,
20163 ) -> Option<BackgroundHighlight> {
20164 let text_highlights = self
20165 .background_highlights
20166 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20167 if !text_highlights.1.is_empty() {
20168 self.scrollbar_marker_state.dirty = true;
20169 cx.notify();
20170 }
20171 Some(text_highlights)
20172 }
20173
20174 pub fn highlight_gutter<T: 'static>(
20175 &mut self,
20176 ranges: impl Into<Vec<Range<Anchor>>>,
20177 color_fetcher: fn(&App) -> Hsla,
20178 cx: &mut Context<Self>,
20179 ) {
20180 self.gutter_highlights
20181 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20182 cx.notify();
20183 }
20184
20185 pub fn clear_gutter_highlights<T: 'static>(
20186 &mut self,
20187 cx: &mut Context<Self>,
20188 ) -> Option<GutterHighlight> {
20189 cx.notify();
20190 self.gutter_highlights.remove(&TypeId::of::<T>())
20191 }
20192
20193 pub fn insert_gutter_highlight<T: 'static>(
20194 &mut self,
20195 range: Range<Anchor>,
20196 color_fetcher: fn(&App) -> Hsla,
20197 cx: &mut Context<Self>,
20198 ) {
20199 let snapshot = self.buffer().read(cx).snapshot(cx);
20200 let mut highlights = self
20201 .gutter_highlights
20202 .remove(&TypeId::of::<T>())
20203 .map(|(_, highlights)| highlights)
20204 .unwrap_or_default();
20205 let ix = highlights.binary_search_by(|highlight| {
20206 Ordering::Equal
20207 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20208 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20209 });
20210 if let Err(ix) = ix {
20211 highlights.insert(ix, range);
20212 }
20213 self.gutter_highlights
20214 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20215 }
20216
20217 pub fn remove_gutter_highlights<T: 'static>(
20218 &mut self,
20219 ranges_to_remove: Vec<Range<Anchor>>,
20220 cx: &mut Context<Self>,
20221 ) {
20222 let snapshot = self.buffer().read(cx).snapshot(cx);
20223 let Some((color_fetcher, mut gutter_highlights)) =
20224 self.gutter_highlights.remove(&TypeId::of::<T>())
20225 else {
20226 return;
20227 };
20228 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20229 gutter_highlights.retain(|highlight| {
20230 while let Some(range_to_remove) = ranges_to_remove.peek() {
20231 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20232 Ordering::Less | Ordering::Equal => {
20233 ranges_to_remove.next();
20234 }
20235 Ordering::Greater => {
20236 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20237 Ordering::Less | Ordering::Equal => {
20238 return false;
20239 }
20240 Ordering::Greater => break,
20241 }
20242 }
20243 }
20244 }
20245
20246 true
20247 });
20248 self.gutter_highlights
20249 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20250 }
20251
20252 #[cfg(feature = "test-support")]
20253 pub fn all_text_highlights(
20254 &self,
20255 window: &mut Window,
20256 cx: &mut Context<Self>,
20257 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20258 let snapshot = self.snapshot(window, cx);
20259 self.display_map.update(cx, |display_map, _| {
20260 display_map
20261 .all_text_highlights()
20262 .map(|highlight| {
20263 let (style, ranges) = highlight.as_ref();
20264 (
20265 *style,
20266 ranges
20267 .iter()
20268 .map(|range| range.clone().to_display_points(&snapshot))
20269 .collect(),
20270 )
20271 })
20272 .collect()
20273 })
20274 }
20275
20276 #[cfg(feature = "test-support")]
20277 pub fn all_text_background_highlights(
20278 &self,
20279 window: &mut Window,
20280 cx: &mut Context<Self>,
20281 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20282 let snapshot = self.snapshot(window, cx);
20283 let buffer = &snapshot.buffer_snapshot;
20284 let start = buffer.anchor_before(0);
20285 let end = buffer.anchor_after(buffer.len());
20286 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20287 }
20288
20289 #[cfg(any(test, feature = "test-support"))]
20290 pub fn sorted_background_highlights_in_range(
20291 &self,
20292 search_range: Range<Anchor>,
20293 display_snapshot: &DisplaySnapshot,
20294 theme: &Theme,
20295 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20296 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20297 res.sort_by(|a, b| {
20298 a.0.start
20299 .cmp(&b.0.start)
20300 .then_with(|| a.0.end.cmp(&b.0.end))
20301 .then_with(|| a.1.cmp(&b.1))
20302 });
20303 res
20304 }
20305
20306 #[cfg(feature = "test-support")]
20307 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20308 let snapshot = self.buffer().read(cx).snapshot(cx);
20309
20310 let highlights = self
20311 .background_highlights
20312 .get(&HighlightKey::Type(TypeId::of::<
20313 items::BufferSearchHighlights,
20314 >()));
20315
20316 if let Some((_color, ranges)) = highlights {
20317 ranges
20318 .iter()
20319 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20320 .collect_vec()
20321 } else {
20322 vec![]
20323 }
20324 }
20325
20326 fn document_highlights_for_position<'a>(
20327 &'a self,
20328 position: Anchor,
20329 buffer: &'a MultiBufferSnapshot,
20330 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20331 let read_highlights = self
20332 .background_highlights
20333 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20334 .map(|h| &h.1);
20335 let write_highlights = self
20336 .background_highlights
20337 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20338 .map(|h| &h.1);
20339 let left_position = position.bias_left(buffer);
20340 let right_position = position.bias_right(buffer);
20341 read_highlights
20342 .into_iter()
20343 .chain(write_highlights)
20344 .flat_map(move |ranges| {
20345 let start_ix = match ranges.binary_search_by(|probe| {
20346 let cmp = probe.end.cmp(&left_position, buffer);
20347 if cmp.is_ge() {
20348 Ordering::Greater
20349 } else {
20350 Ordering::Less
20351 }
20352 }) {
20353 Ok(i) | Err(i) => i,
20354 };
20355
20356 ranges[start_ix..]
20357 .iter()
20358 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20359 })
20360 }
20361
20362 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20363 self.background_highlights
20364 .get(&HighlightKey::Type(TypeId::of::<T>()))
20365 .is_some_and(|(_, highlights)| !highlights.is_empty())
20366 }
20367
20368 /// Returns all background highlights for a given range.
20369 ///
20370 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20371 pub fn background_highlights_in_range(
20372 &self,
20373 search_range: Range<Anchor>,
20374 display_snapshot: &DisplaySnapshot,
20375 theme: &Theme,
20376 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20377 let mut results = Vec::new();
20378 for (color_fetcher, ranges) in self.background_highlights.values() {
20379 let color = color_fetcher(theme);
20380 let start_ix = match ranges.binary_search_by(|probe| {
20381 let cmp = probe
20382 .end
20383 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20384 if cmp.is_gt() {
20385 Ordering::Greater
20386 } else {
20387 Ordering::Less
20388 }
20389 }) {
20390 Ok(i) | Err(i) => i,
20391 };
20392 for range in &ranges[start_ix..] {
20393 if range
20394 .start
20395 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20396 .is_ge()
20397 {
20398 break;
20399 }
20400
20401 let start = range.start.to_display_point(display_snapshot);
20402 let end = range.end.to_display_point(display_snapshot);
20403 results.push((start..end, color))
20404 }
20405 }
20406 results
20407 }
20408
20409 pub fn gutter_highlights_in_range(
20410 &self,
20411 search_range: Range<Anchor>,
20412 display_snapshot: &DisplaySnapshot,
20413 cx: &App,
20414 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20415 let mut results = Vec::new();
20416 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20417 let color = color_fetcher(cx);
20418 let start_ix = match ranges.binary_search_by(|probe| {
20419 let cmp = probe
20420 .end
20421 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20422 if cmp.is_gt() {
20423 Ordering::Greater
20424 } else {
20425 Ordering::Less
20426 }
20427 }) {
20428 Ok(i) | Err(i) => i,
20429 };
20430 for range in &ranges[start_ix..] {
20431 if range
20432 .start
20433 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20434 .is_ge()
20435 {
20436 break;
20437 }
20438
20439 let start = range.start.to_display_point(display_snapshot);
20440 let end = range.end.to_display_point(display_snapshot);
20441 results.push((start..end, color))
20442 }
20443 }
20444 results
20445 }
20446
20447 /// Get the text ranges corresponding to the redaction query
20448 pub fn redacted_ranges(
20449 &self,
20450 search_range: Range<Anchor>,
20451 display_snapshot: &DisplaySnapshot,
20452 cx: &App,
20453 ) -> Vec<Range<DisplayPoint>> {
20454 display_snapshot
20455 .buffer_snapshot
20456 .redacted_ranges(search_range, |file| {
20457 if let Some(file) = file {
20458 file.is_private()
20459 && EditorSettings::get(
20460 Some(SettingsLocation {
20461 worktree_id: file.worktree_id(cx),
20462 path: file.path().as_ref(),
20463 }),
20464 cx,
20465 )
20466 .redact_private_values
20467 } else {
20468 false
20469 }
20470 })
20471 .map(|range| {
20472 range.start.to_display_point(display_snapshot)
20473 ..range.end.to_display_point(display_snapshot)
20474 })
20475 .collect()
20476 }
20477
20478 pub fn highlight_text_key<T: 'static>(
20479 &mut self,
20480 key: usize,
20481 ranges: Vec<Range<Anchor>>,
20482 style: HighlightStyle,
20483 cx: &mut Context<Self>,
20484 ) {
20485 self.display_map.update(cx, |map, _| {
20486 map.highlight_text(
20487 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20488 ranges,
20489 style,
20490 );
20491 });
20492 cx.notify();
20493 }
20494
20495 pub fn highlight_text<T: 'static>(
20496 &mut self,
20497 ranges: Vec<Range<Anchor>>,
20498 style: HighlightStyle,
20499 cx: &mut Context<Self>,
20500 ) {
20501 self.display_map.update(cx, |map, _| {
20502 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20503 });
20504 cx.notify();
20505 }
20506
20507 pub(crate) fn highlight_inlays<T: 'static>(
20508 &mut self,
20509 highlights: Vec<InlayHighlight>,
20510 style: HighlightStyle,
20511 cx: &mut Context<Self>,
20512 ) {
20513 self.display_map.update(cx, |map, _| {
20514 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20515 });
20516 cx.notify();
20517 }
20518
20519 pub fn text_highlights<'a, T: 'static>(
20520 &'a self,
20521 cx: &'a App,
20522 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20523 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20524 }
20525
20526 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20527 let cleared = self
20528 .display_map
20529 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20530 if cleared {
20531 cx.notify();
20532 }
20533 }
20534
20535 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20536 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20537 && self.focus_handle.is_focused(window)
20538 }
20539
20540 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20541 self.show_cursor_when_unfocused = is_enabled;
20542 cx.notify();
20543 }
20544
20545 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20546 cx.notify();
20547 }
20548
20549 fn on_debug_session_event(
20550 &mut self,
20551 _session: Entity<Session>,
20552 event: &SessionEvent,
20553 cx: &mut Context<Self>,
20554 ) {
20555 if let SessionEvent::InvalidateInlineValue = event {
20556 self.refresh_inline_values(cx);
20557 }
20558 }
20559
20560 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20561 let Some(project) = self.project.clone() else {
20562 return;
20563 };
20564
20565 if !self.inline_value_cache.enabled {
20566 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20567 self.splice_inlays(&inlays, Vec::new(), cx);
20568 return;
20569 }
20570
20571 let current_execution_position = self
20572 .highlighted_rows
20573 .get(&TypeId::of::<ActiveDebugLine>())
20574 .and_then(|lines| lines.last().map(|line| line.range.end));
20575
20576 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20577 let inline_values = editor
20578 .update(cx, |editor, cx| {
20579 let Some(current_execution_position) = current_execution_position else {
20580 return Some(Task::ready(Ok(Vec::new())));
20581 };
20582
20583 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20584 let snapshot = buffer.snapshot(cx);
20585
20586 let excerpt = snapshot.excerpt_containing(
20587 current_execution_position..current_execution_position,
20588 )?;
20589
20590 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20591 })?;
20592
20593 let range =
20594 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20595
20596 project.inline_values(buffer, range, cx)
20597 })
20598 .ok()
20599 .flatten()?
20600 .await
20601 .context("refreshing debugger inlays")
20602 .log_err()?;
20603
20604 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20605
20606 for (buffer_id, inline_value) in inline_values
20607 .into_iter()
20608 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20609 {
20610 buffer_inline_values
20611 .entry(buffer_id)
20612 .or_default()
20613 .push(inline_value);
20614 }
20615
20616 editor
20617 .update(cx, |editor, cx| {
20618 let snapshot = editor.buffer.read(cx).snapshot(cx);
20619 let mut new_inlays = Vec::default();
20620
20621 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20622 let buffer_id = buffer_snapshot.remote_id();
20623 buffer_inline_values
20624 .get(&buffer_id)
20625 .into_iter()
20626 .flatten()
20627 .for_each(|hint| {
20628 let inlay = Inlay::debugger(
20629 post_inc(&mut editor.next_inlay_id),
20630 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20631 hint.text(),
20632 );
20633 if !inlay.text().chars().contains(&'\n') {
20634 new_inlays.push(inlay);
20635 }
20636 });
20637 }
20638
20639 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20640 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20641
20642 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20643 })
20644 .ok()?;
20645 Some(())
20646 });
20647 }
20648
20649 fn on_buffer_event(
20650 &mut self,
20651 multibuffer: &Entity<MultiBuffer>,
20652 event: &multi_buffer::Event,
20653 window: &mut Window,
20654 cx: &mut Context<Self>,
20655 ) {
20656 match event {
20657 multi_buffer::Event::Edited {
20658 singleton_buffer_edited,
20659 edited_buffer,
20660 } => {
20661 self.scrollbar_marker_state.dirty = true;
20662 self.active_indent_guides_state.dirty = true;
20663 self.refresh_active_diagnostics(cx);
20664 self.refresh_code_actions(window, cx);
20665 self.refresh_selected_text_highlights(true, window, cx);
20666 self.refresh_single_line_folds(window, cx);
20667 refresh_matching_bracket_highlights(self, window, cx);
20668 if self.has_active_edit_prediction() {
20669 self.update_visible_edit_prediction(window, cx);
20670 }
20671 if let Some(project) = self.project.as_ref()
20672 && let Some(edited_buffer) = edited_buffer
20673 {
20674 project.update(cx, |project, cx| {
20675 self.registered_buffers
20676 .entry(edited_buffer.read(cx).remote_id())
20677 .or_insert_with(|| {
20678 project.register_buffer_with_language_servers(edited_buffer, cx)
20679 });
20680 });
20681 }
20682 cx.emit(EditorEvent::BufferEdited);
20683 cx.emit(SearchEvent::MatchesInvalidated);
20684
20685 if let Some(buffer) = edited_buffer {
20686 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20687 }
20688
20689 if *singleton_buffer_edited {
20690 if let Some(buffer) = edited_buffer
20691 && buffer.read(cx).file().is_none()
20692 {
20693 cx.emit(EditorEvent::TitleChanged);
20694 }
20695 if let Some(project) = &self.project {
20696 #[allow(clippy::mutable_key_type)]
20697 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20698 multibuffer
20699 .all_buffers()
20700 .into_iter()
20701 .filter_map(|buffer| {
20702 buffer.update(cx, |buffer, cx| {
20703 let language = buffer.language()?;
20704 let should_discard = project.update(cx, |project, cx| {
20705 project.is_local()
20706 && !project.has_language_servers_for(buffer, cx)
20707 });
20708 should_discard.not().then_some(language.clone())
20709 })
20710 })
20711 .collect::<HashSet<_>>()
20712 });
20713 if !languages_affected.is_empty() {
20714 self.refresh_inlay_hints(
20715 InlayHintRefreshReason::BufferEdited(languages_affected),
20716 cx,
20717 );
20718 }
20719 }
20720 }
20721
20722 let Some(project) = &self.project else { return };
20723 let (telemetry, is_via_ssh) = {
20724 let project = project.read(cx);
20725 let telemetry = project.client().telemetry().clone();
20726 let is_via_ssh = project.is_via_remote_server();
20727 (telemetry, is_via_ssh)
20728 };
20729 refresh_linked_ranges(self, window, cx);
20730 telemetry.log_edit_event("editor", is_via_ssh);
20731 }
20732 multi_buffer::Event::ExcerptsAdded {
20733 buffer,
20734 predecessor,
20735 excerpts,
20736 } => {
20737 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20738 let buffer_id = buffer.read(cx).remote_id();
20739 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20740 && let Some(project) = &self.project
20741 {
20742 update_uncommitted_diff_for_buffer(
20743 cx.entity(),
20744 project,
20745 [buffer.clone()],
20746 self.buffer.clone(),
20747 cx,
20748 )
20749 .detach();
20750 }
20751 if self.active_diagnostics != ActiveDiagnostic::All {
20752 self.update_lsp_data(false, Some(buffer_id), window, cx);
20753 }
20754 cx.emit(EditorEvent::ExcerptsAdded {
20755 buffer: buffer.clone(),
20756 predecessor: *predecessor,
20757 excerpts: excerpts.clone(),
20758 });
20759 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20760 }
20761 multi_buffer::Event::ExcerptsRemoved {
20762 ids,
20763 removed_buffer_ids,
20764 } => {
20765 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20766 let buffer = self.buffer.read(cx);
20767 self.registered_buffers
20768 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20769 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20770 cx.emit(EditorEvent::ExcerptsRemoved {
20771 ids: ids.clone(),
20772 removed_buffer_ids: removed_buffer_ids.clone(),
20773 });
20774 }
20775 multi_buffer::Event::ExcerptsEdited {
20776 excerpt_ids,
20777 buffer_ids,
20778 } => {
20779 self.display_map.update(cx, |map, cx| {
20780 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20781 });
20782 cx.emit(EditorEvent::ExcerptsEdited {
20783 ids: excerpt_ids.clone(),
20784 });
20785 }
20786 multi_buffer::Event::ExcerptsExpanded { ids } => {
20787 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20788 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20789 }
20790 multi_buffer::Event::Reparsed(buffer_id) => {
20791 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20792 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20793
20794 cx.emit(EditorEvent::Reparsed(*buffer_id));
20795 }
20796 multi_buffer::Event::DiffHunksToggled => {
20797 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20798 }
20799 multi_buffer::Event::LanguageChanged(buffer_id) => {
20800 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20801 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20802 cx.emit(EditorEvent::Reparsed(*buffer_id));
20803 cx.notify();
20804 }
20805 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20806 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20807 multi_buffer::Event::FileHandleChanged
20808 | multi_buffer::Event::Reloaded
20809 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20810 multi_buffer::Event::DiagnosticsUpdated => {
20811 self.update_diagnostics_state(window, cx);
20812 }
20813 _ => {}
20814 };
20815 }
20816
20817 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20818 if !self.diagnostics_enabled() {
20819 return;
20820 }
20821 self.refresh_active_diagnostics(cx);
20822 self.refresh_inline_diagnostics(true, window, cx);
20823 self.scrollbar_marker_state.dirty = true;
20824 cx.notify();
20825 }
20826
20827 pub fn start_temporary_diff_override(&mut self) {
20828 self.load_diff_task.take();
20829 self.temporary_diff_override = true;
20830 }
20831
20832 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20833 self.temporary_diff_override = false;
20834 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20835 self.buffer.update(cx, |buffer, cx| {
20836 buffer.set_all_diff_hunks_collapsed(cx);
20837 });
20838
20839 if let Some(project) = self.project.clone() {
20840 self.load_diff_task = Some(
20841 update_uncommitted_diff_for_buffer(
20842 cx.entity(),
20843 &project,
20844 self.buffer.read(cx).all_buffers(),
20845 self.buffer.clone(),
20846 cx,
20847 )
20848 .shared(),
20849 );
20850 }
20851 }
20852
20853 fn on_display_map_changed(
20854 &mut self,
20855 _: Entity<DisplayMap>,
20856 _: &mut Window,
20857 cx: &mut Context<Self>,
20858 ) {
20859 cx.notify();
20860 }
20861
20862 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20863 if self.diagnostics_enabled() {
20864 let new_severity = EditorSettings::get_global(cx)
20865 .diagnostics_max_severity
20866 .unwrap_or(DiagnosticSeverity::Hint);
20867 self.set_max_diagnostics_severity(new_severity, cx);
20868 }
20869 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20870 self.update_edit_prediction_settings(cx);
20871 self.refresh_edit_prediction(true, false, window, cx);
20872 self.refresh_inline_values(cx);
20873 self.refresh_inlay_hints(
20874 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20875 self.selections.newest_anchor().head(),
20876 &self.buffer.read(cx).snapshot(cx),
20877 cx,
20878 )),
20879 cx,
20880 );
20881
20882 let old_cursor_shape = self.cursor_shape;
20883 let old_show_breadcrumbs = self.show_breadcrumbs;
20884
20885 {
20886 let editor_settings = EditorSettings::get_global(cx);
20887 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20888 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20889 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20890 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20891 }
20892
20893 if old_cursor_shape != self.cursor_shape {
20894 cx.emit(EditorEvent::CursorShapeChanged);
20895 }
20896
20897 if old_show_breadcrumbs != self.show_breadcrumbs {
20898 cx.emit(EditorEvent::BreadcrumbsChanged);
20899 }
20900
20901 let project_settings = ProjectSettings::get_global(cx);
20902 self.serialize_dirty_buffers =
20903 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20904
20905 if self.mode.is_full() {
20906 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20907 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20908 if self.show_inline_diagnostics != show_inline_diagnostics {
20909 self.show_inline_diagnostics = show_inline_diagnostics;
20910 self.refresh_inline_diagnostics(false, window, cx);
20911 }
20912
20913 if self.git_blame_inline_enabled != inline_blame_enabled {
20914 self.toggle_git_blame_inline_internal(false, window, cx);
20915 }
20916
20917 let minimap_settings = EditorSettings::get_global(cx).minimap;
20918 if self.minimap_visibility != MinimapVisibility::Disabled {
20919 if self.minimap_visibility.settings_visibility()
20920 != minimap_settings.minimap_enabled()
20921 {
20922 self.set_minimap_visibility(
20923 MinimapVisibility::for_mode(self.mode(), cx),
20924 window,
20925 cx,
20926 );
20927 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20928 minimap_entity.update(cx, |minimap_editor, cx| {
20929 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20930 })
20931 }
20932 }
20933 }
20934
20935 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20936 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20937 }) {
20938 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20939 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20940 }
20941 self.refresh_colors(false, None, window, cx);
20942 }
20943
20944 cx.notify();
20945 }
20946
20947 pub fn set_searchable(&mut self, searchable: bool) {
20948 self.searchable = searchable;
20949 }
20950
20951 pub fn searchable(&self) -> bool {
20952 self.searchable
20953 }
20954
20955 fn open_proposed_changes_editor(
20956 &mut self,
20957 _: &OpenProposedChangesEditor,
20958 window: &mut Window,
20959 cx: &mut Context<Self>,
20960 ) {
20961 let Some(workspace) = self.workspace() else {
20962 cx.propagate();
20963 return;
20964 };
20965
20966 let selections = self.selections.all::<usize>(cx);
20967 let multi_buffer = self.buffer.read(cx);
20968 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20969 let mut new_selections_by_buffer = HashMap::default();
20970 for selection in selections {
20971 for (buffer, range, _) in
20972 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20973 {
20974 let mut range = range.to_point(buffer);
20975 range.start.column = 0;
20976 range.end.column = buffer.line_len(range.end.row);
20977 new_selections_by_buffer
20978 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20979 .or_insert(Vec::new())
20980 .push(range)
20981 }
20982 }
20983
20984 let proposed_changes_buffers = new_selections_by_buffer
20985 .into_iter()
20986 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20987 .collect::<Vec<_>>();
20988 let proposed_changes_editor = cx.new(|cx| {
20989 ProposedChangesEditor::new(
20990 "Proposed changes",
20991 proposed_changes_buffers,
20992 self.project.clone(),
20993 window,
20994 cx,
20995 )
20996 });
20997
20998 window.defer(cx, move |window, cx| {
20999 workspace.update(cx, |workspace, cx| {
21000 workspace.active_pane().update(cx, |pane, cx| {
21001 pane.add_item(
21002 Box::new(proposed_changes_editor),
21003 true,
21004 true,
21005 None,
21006 window,
21007 cx,
21008 );
21009 });
21010 });
21011 });
21012 }
21013
21014 pub fn open_excerpts_in_split(
21015 &mut self,
21016 _: &OpenExcerptsSplit,
21017 window: &mut Window,
21018 cx: &mut Context<Self>,
21019 ) {
21020 self.open_excerpts_common(None, true, window, cx)
21021 }
21022
21023 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21024 self.open_excerpts_common(None, false, window, cx)
21025 }
21026
21027 fn open_excerpts_common(
21028 &mut self,
21029 jump_data: Option<JumpData>,
21030 split: bool,
21031 window: &mut Window,
21032 cx: &mut Context<Self>,
21033 ) {
21034 let Some(workspace) = self.workspace() else {
21035 cx.propagate();
21036 return;
21037 };
21038
21039 if self.buffer.read(cx).is_singleton() {
21040 cx.propagate();
21041 return;
21042 }
21043
21044 let mut new_selections_by_buffer = HashMap::default();
21045 match &jump_data {
21046 Some(JumpData::MultiBufferPoint {
21047 excerpt_id,
21048 position,
21049 anchor,
21050 line_offset_from_top,
21051 }) => {
21052 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21053 if let Some(buffer) = multi_buffer_snapshot
21054 .buffer_id_for_excerpt(*excerpt_id)
21055 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21056 {
21057 let buffer_snapshot = buffer.read(cx).snapshot();
21058 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21059 language::ToPoint::to_point(anchor, &buffer_snapshot)
21060 } else {
21061 buffer_snapshot.clip_point(*position, Bias::Left)
21062 };
21063 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21064 new_selections_by_buffer.insert(
21065 buffer,
21066 (
21067 vec![jump_to_offset..jump_to_offset],
21068 Some(*line_offset_from_top),
21069 ),
21070 );
21071 }
21072 }
21073 Some(JumpData::MultiBufferRow {
21074 row,
21075 line_offset_from_top,
21076 }) => {
21077 let point = MultiBufferPoint::new(row.0, 0);
21078 if let Some((buffer, buffer_point, _)) =
21079 self.buffer.read(cx).point_to_buffer_point(point, cx)
21080 {
21081 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21082 new_selections_by_buffer
21083 .entry(buffer)
21084 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21085 .0
21086 .push(buffer_offset..buffer_offset)
21087 }
21088 }
21089 None => {
21090 let selections = self.selections.all::<usize>(cx);
21091 let multi_buffer = self.buffer.read(cx);
21092 for selection in selections {
21093 for (snapshot, range, _, anchor) in multi_buffer
21094 .snapshot(cx)
21095 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21096 {
21097 if let Some(anchor) = anchor {
21098 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21099 else {
21100 continue;
21101 };
21102 let offset = text::ToOffset::to_offset(
21103 &anchor.text_anchor,
21104 &buffer_handle.read(cx).snapshot(),
21105 );
21106 let range = offset..offset;
21107 new_selections_by_buffer
21108 .entry(buffer_handle)
21109 .or_insert((Vec::new(), None))
21110 .0
21111 .push(range)
21112 } else {
21113 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21114 else {
21115 continue;
21116 };
21117 new_selections_by_buffer
21118 .entry(buffer_handle)
21119 .or_insert((Vec::new(), None))
21120 .0
21121 .push(range)
21122 }
21123 }
21124 }
21125 }
21126 }
21127
21128 new_selections_by_buffer
21129 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21130
21131 if new_selections_by_buffer.is_empty() {
21132 return;
21133 }
21134
21135 // We defer the pane interaction because we ourselves are a workspace item
21136 // and activating a new item causes the pane to call a method on us reentrantly,
21137 // which panics if we're on the stack.
21138 window.defer(cx, move |window, cx| {
21139 workspace.update(cx, |workspace, cx| {
21140 let pane = if split {
21141 workspace.adjacent_pane(window, cx)
21142 } else {
21143 workspace.active_pane().clone()
21144 };
21145
21146 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21147 let editor = buffer
21148 .read(cx)
21149 .file()
21150 .is_none()
21151 .then(|| {
21152 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21153 // so `workspace.open_project_item` will never find them, always opening a new editor.
21154 // Instead, we try to activate the existing editor in the pane first.
21155 let (editor, pane_item_index) =
21156 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21157 let editor = item.downcast::<Editor>()?;
21158 let singleton_buffer =
21159 editor.read(cx).buffer().read(cx).as_singleton()?;
21160 if singleton_buffer == buffer {
21161 Some((editor, i))
21162 } else {
21163 None
21164 }
21165 })?;
21166 pane.update(cx, |pane, cx| {
21167 pane.activate_item(pane_item_index, true, true, window, cx)
21168 });
21169 Some(editor)
21170 })
21171 .flatten()
21172 .unwrap_or_else(|| {
21173 workspace.open_project_item::<Self>(
21174 pane.clone(),
21175 buffer,
21176 true,
21177 true,
21178 window,
21179 cx,
21180 )
21181 });
21182
21183 editor.update(cx, |editor, cx| {
21184 let autoscroll = match scroll_offset {
21185 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21186 None => Autoscroll::newest(),
21187 };
21188 let nav_history = editor.nav_history.take();
21189 editor.change_selections(
21190 SelectionEffects::scroll(autoscroll),
21191 window,
21192 cx,
21193 |s| {
21194 s.select_ranges(ranges);
21195 },
21196 );
21197 editor.nav_history = nav_history;
21198 });
21199 }
21200 })
21201 });
21202 }
21203
21204 // For now, don't allow opening excerpts in buffers that aren't backed by
21205 // regular project files.
21206 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21207 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21208 }
21209
21210 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21211 let snapshot = self.buffer.read(cx).read(cx);
21212 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21213 Some(
21214 ranges
21215 .iter()
21216 .map(move |range| {
21217 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21218 })
21219 .collect(),
21220 )
21221 }
21222
21223 fn selection_replacement_ranges(
21224 &self,
21225 range: Range<OffsetUtf16>,
21226 cx: &mut App,
21227 ) -> Vec<Range<OffsetUtf16>> {
21228 let selections = self.selections.all::<OffsetUtf16>(cx);
21229 let newest_selection = selections
21230 .iter()
21231 .max_by_key(|selection| selection.id)
21232 .unwrap();
21233 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21234 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21235 let snapshot = self.buffer.read(cx).read(cx);
21236 selections
21237 .into_iter()
21238 .map(|mut selection| {
21239 selection.start.0 =
21240 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21241 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21242 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21243 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21244 })
21245 .collect()
21246 }
21247
21248 fn report_editor_event(
21249 &self,
21250 reported_event: ReportEditorEvent,
21251 file_extension: Option<String>,
21252 cx: &App,
21253 ) {
21254 if cfg!(any(test, feature = "test-support")) {
21255 return;
21256 }
21257
21258 let Some(project) = &self.project else { return };
21259
21260 // If None, we are in a file without an extension
21261 let file = self
21262 .buffer
21263 .read(cx)
21264 .as_singleton()
21265 .and_then(|b| b.read(cx).file());
21266 let file_extension = file_extension.or(file
21267 .as_ref()
21268 .and_then(|file| Path::new(file.file_name(cx)).extension())
21269 .and_then(|e| e.to_str())
21270 .map(|a| a.to_string()));
21271
21272 let vim_mode = vim_enabled(cx);
21273
21274 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21275 let copilot_enabled = edit_predictions_provider
21276 == language::language_settings::EditPredictionProvider::Copilot;
21277 let copilot_enabled_for_language = self
21278 .buffer
21279 .read(cx)
21280 .language_settings(cx)
21281 .show_edit_predictions;
21282
21283 let project = project.read(cx);
21284 let event_type = reported_event.event_type();
21285
21286 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21287 telemetry::event!(
21288 event_type,
21289 type = if auto_saved {"autosave"} else {"manual"},
21290 file_extension,
21291 vim_mode,
21292 copilot_enabled,
21293 copilot_enabled_for_language,
21294 edit_predictions_provider,
21295 is_via_ssh = project.is_via_remote_server(),
21296 );
21297 } else {
21298 telemetry::event!(
21299 event_type,
21300 file_extension,
21301 vim_mode,
21302 copilot_enabled,
21303 copilot_enabled_for_language,
21304 edit_predictions_provider,
21305 is_via_ssh = project.is_via_remote_server(),
21306 );
21307 };
21308 }
21309
21310 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21311 /// with each line being an array of {text, highlight} objects.
21312 fn copy_highlight_json(
21313 &mut self,
21314 _: &CopyHighlightJson,
21315 window: &mut Window,
21316 cx: &mut Context<Self>,
21317 ) {
21318 #[derive(Serialize)]
21319 struct Chunk<'a> {
21320 text: String,
21321 highlight: Option<&'a str>,
21322 }
21323
21324 let snapshot = self.buffer.read(cx).snapshot(cx);
21325 let range = self
21326 .selected_text_range(false, window, cx)
21327 .and_then(|selection| {
21328 if selection.range.is_empty() {
21329 None
21330 } else {
21331 Some(selection.range)
21332 }
21333 })
21334 .unwrap_or_else(|| 0..snapshot.len());
21335
21336 let chunks = snapshot.chunks(range, true);
21337 let mut lines = Vec::new();
21338 let mut line: VecDeque<Chunk> = VecDeque::new();
21339
21340 let Some(style) = self.style.as_ref() else {
21341 return;
21342 };
21343
21344 for chunk in chunks {
21345 let highlight = chunk
21346 .syntax_highlight_id
21347 .and_then(|id| id.name(&style.syntax));
21348 let mut chunk_lines = chunk.text.split('\n').peekable();
21349 while let Some(text) = chunk_lines.next() {
21350 let mut merged_with_last_token = false;
21351 if let Some(last_token) = line.back_mut()
21352 && last_token.highlight == highlight
21353 {
21354 last_token.text.push_str(text);
21355 merged_with_last_token = true;
21356 }
21357
21358 if !merged_with_last_token {
21359 line.push_back(Chunk {
21360 text: text.into(),
21361 highlight,
21362 });
21363 }
21364
21365 if chunk_lines.peek().is_some() {
21366 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21367 line.pop_front();
21368 }
21369 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21370 line.pop_back();
21371 }
21372
21373 lines.push(mem::take(&mut line));
21374 }
21375 }
21376 }
21377
21378 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21379 return;
21380 };
21381 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21382 }
21383
21384 pub fn open_context_menu(
21385 &mut self,
21386 _: &OpenContextMenu,
21387 window: &mut Window,
21388 cx: &mut Context<Self>,
21389 ) {
21390 self.request_autoscroll(Autoscroll::newest(), cx);
21391 let position = self.selections.newest_display(cx).start;
21392 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21393 }
21394
21395 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21396 &self.inlay_hint_cache
21397 }
21398
21399 pub fn replay_insert_event(
21400 &mut self,
21401 text: &str,
21402 relative_utf16_range: Option<Range<isize>>,
21403 window: &mut Window,
21404 cx: &mut Context<Self>,
21405 ) {
21406 if !self.input_enabled {
21407 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21408 return;
21409 }
21410 if let Some(relative_utf16_range) = relative_utf16_range {
21411 let selections = self.selections.all::<OffsetUtf16>(cx);
21412 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21413 let new_ranges = selections.into_iter().map(|range| {
21414 let start = OffsetUtf16(
21415 range
21416 .head()
21417 .0
21418 .saturating_add_signed(relative_utf16_range.start),
21419 );
21420 let end = OffsetUtf16(
21421 range
21422 .head()
21423 .0
21424 .saturating_add_signed(relative_utf16_range.end),
21425 );
21426 start..end
21427 });
21428 s.select_ranges(new_ranges);
21429 });
21430 }
21431
21432 self.handle_input(text, window, cx);
21433 }
21434
21435 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21436 let Some(provider) = self.semantics_provider.as_ref() else {
21437 return false;
21438 };
21439
21440 let mut supports = false;
21441 self.buffer().update(cx, |this, cx| {
21442 this.for_each_buffer(|buffer| {
21443 supports |= provider.supports_inlay_hints(buffer, cx);
21444 });
21445 });
21446
21447 supports
21448 }
21449
21450 pub fn is_focused(&self, window: &Window) -> bool {
21451 self.focus_handle.is_focused(window)
21452 }
21453
21454 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21455 cx.emit(EditorEvent::Focused);
21456
21457 if let Some(descendant) = self
21458 .last_focused_descendant
21459 .take()
21460 .and_then(|descendant| descendant.upgrade())
21461 {
21462 window.focus(&descendant);
21463 } else {
21464 if let Some(blame) = self.blame.as_ref() {
21465 blame.update(cx, GitBlame::focus)
21466 }
21467
21468 self.blink_manager.update(cx, BlinkManager::enable);
21469 self.show_cursor_names(window, cx);
21470 self.buffer.update(cx, |buffer, cx| {
21471 buffer.finalize_last_transaction(cx);
21472 if self.leader_id.is_none() {
21473 buffer.set_active_selections(
21474 &self.selections.disjoint_anchors_arc(),
21475 self.selections.line_mode(),
21476 self.cursor_shape,
21477 cx,
21478 );
21479 }
21480 });
21481 }
21482 }
21483
21484 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21485 cx.emit(EditorEvent::FocusedIn)
21486 }
21487
21488 fn handle_focus_out(
21489 &mut self,
21490 event: FocusOutEvent,
21491 _window: &mut Window,
21492 cx: &mut Context<Self>,
21493 ) {
21494 if event.blurred != self.focus_handle {
21495 self.last_focused_descendant = Some(event.blurred);
21496 }
21497 self.selection_drag_state = SelectionDragState::None;
21498 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21499 }
21500
21501 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21502 self.blink_manager.update(cx, BlinkManager::disable);
21503 self.buffer
21504 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21505
21506 if let Some(blame) = self.blame.as_ref() {
21507 blame.update(cx, GitBlame::blur)
21508 }
21509 if !self.hover_state.focused(window, cx) {
21510 hide_hover(self, cx);
21511 }
21512 if !self
21513 .context_menu
21514 .borrow()
21515 .as_ref()
21516 .is_some_and(|context_menu| context_menu.focused(window, cx))
21517 {
21518 self.hide_context_menu(window, cx);
21519 }
21520 self.take_active_edit_prediction(cx);
21521 cx.emit(EditorEvent::Blurred);
21522 cx.notify();
21523 }
21524
21525 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21526 let mut pending: String = window
21527 .pending_input_keystrokes()
21528 .into_iter()
21529 .flatten()
21530 .filter_map(|keystroke| {
21531 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21532 keystroke.key_char.clone()
21533 } else {
21534 None
21535 }
21536 })
21537 .collect();
21538
21539 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21540 pending = "".to_string();
21541 }
21542
21543 let existing_pending = self
21544 .text_highlights::<PendingInput>(cx)
21545 .map(|(_, ranges)| ranges.to_vec());
21546 if existing_pending.is_none() && pending.is_empty() {
21547 return;
21548 }
21549 let transaction =
21550 self.transact(window, cx, |this, window, cx| {
21551 let selections = this.selections.all::<usize>(cx);
21552 let edits = selections
21553 .iter()
21554 .map(|selection| (selection.end..selection.end, pending.clone()));
21555 this.edit(edits, cx);
21556 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21557 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21558 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21559 }));
21560 });
21561 if let Some(existing_ranges) = existing_pending {
21562 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21563 this.edit(edits, cx);
21564 }
21565 });
21566
21567 let snapshot = self.snapshot(window, cx);
21568 let ranges = self
21569 .selections
21570 .all::<usize>(cx)
21571 .into_iter()
21572 .map(|selection| {
21573 snapshot.buffer_snapshot.anchor_after(selection.end)
21574 ..snapshot
21575 .buffer_snapshot
21576 .anchor_before(selection.end + pending.len())
21577 })
21578 .collect();
21579
21580 if pending.is_empty() {
21581 self.clear_highlights::<PendingInput>(cx);
21582 } else {
21583 self.highlight_text::<PendingInput>(
21584 ranges,
21585 HighlightStyle {
21586 underline: Some(UnderlineStyle {
21587 thickness: px(1.),
21588 color: None,
21589 wavy: false,
21590 }),
21591 ..Default::default()
21592 },
21593 cx,
21594 );
21595 }
21596
21597 self.ime_transaction = self.ime_transaction.or(transaction);
21598 if let Some(transaction) = self.ime_transaction {
21599 self.buffer.update(cx, |buffer, cx| {
21600 buffer.group_until_transaction(transaction, cx);
21601 });
21602 }
21603
21604 if self.text_highlights::<PendingInput>(cx).is_none() {
21605 self.ime_transaction.take();
21606 }
21607 }
21608
21609 pub fn register_action_renderer(
21610 &mut self,
21611 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21612 ) -> Subscription {
21613 let id = self.next_editor_action_id.post_inc();
21614 self.editor_actions
21615 .borrow_mut()
21616 .insert(id, Box::new(listener));
21617
21618 let editor_actions = self.editor_actions.clone();
21619 Subscription::new(move || {
21620 editor_actions.borrow_mut().remove(&id);
21621 })
21622 }
21623
21624 pub fn register_action<A: Action>(
21625 &mut self,
21626 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21627 ) -> Subscription {
21628 let id = self.next_editor_action_id.post_inc();
21629 let listener = Arc::new(listener);
21630 self.editor_actions.borrow_mut().insert(
21631 id,
21632 Box::new(move |_, window, _| {
21633 let listener = listener.clone();
21634 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21635 let action = action.downcast_ref().unwrap();
21636 if phase == DispatchPhase::Bubble {
21637 listener(action, window, cx)
21638 }
21639 })
21640 }),
21641 );
21642
21643 let editor_actions = self.editor_actions.clone();
21644 Subscription::new(move || {
21645 editor_actions.borrow_mut().remove(&id);
21646 })
21647 }
21648
21649 pub fn file_header_size(&self) -> u32 {
21650 FILE_HEADER_HEIGHT
21651 }
21652
21653 pub fn restore(
21654 &mut self,
21655 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21656 window: &mut Window,
21657 cx: &mut Context<Self>,
21658 ) {
21659 let workspace = self.workspace();
21660 let project = self.project();
21661 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21662 let mut tasks = Vec::new();
21663 for (buffer_id, changes) in revert_changes {
21664 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21665 buffer.update(cx, |buffer, cx| {
21666 buffer.edit(
21667 changes
21668 .into_iter()
21669 .map(|(range, text)| (range, text.to_string())),
21670 None,
21671 cx,
21672 );
21673 });
21674
21675 if let Some(project) =
21676 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21677 {
21678 project.update(cx, |project, cx| {
21679 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21680 })
21681 }
21682 }
21683 }
21684 tasks
21685 });
21686 cx.spawn_in(window, async move |_, cx| {
21687 for (buffer, task) in save_tasks {
21688 let result = task.await;
21689 if result.is_err() {
21690 let Some(path) = buffer
21691 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21692 .ok()
21693 else {
21694 continue;
21695 };
21696 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21697 let Some(task) = cx
21698 .update_window_entity(workspace, |workspace, window, cx| {
21699 workspace
21700 .open_path_preview(path, None, false, false, false, window, cx)
21701 })
21702 .ok()
21703 else {
21704 continue;
21705 };
21706 task.await.log_err();
21707 }
21708 }
21709 }
21710 })
21711 .detach();
21712 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21713 selections.refresh()
21714 });
21715 }
21716
21717 pub fn to_pixel_point(
21718 &self,
21719 source: multi_buffer::Anchor,
21720 editor_snapshot: &EditorSnapshot,
21721 window: &mut Window,
21722 ) -> Option<gpui::Point<Pixels>> {
21723 let source_point = source.to_display_point(editor_snapshot);
21724 self.display_to_pixel_point(source_point, editor_snapshot, window)
21725 }
21726
21727 pub fn display_to_pixel_point(
21728 &self,
21729 source: DisplayPoint,
21730 editor_snapshot: &EditorSnapshot,
21731 window: &mut Window,
21732 ) -> Option<gpui::Point<Pixels>> {
21733 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21734 let text_layout_details = self.text_layout_details(window);
21735 let scroll_top = text_layout_details
21736 .scroll_anchor
21737 .scroll_position(editor_snapshot)
21738 .y;
21739
21740 if source.row().as_f32() < scroll_top.floor() {
21741 return None;
21742 }
21743 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21744 let source_y = line_height * (source.row().as_f32() - scroll_top);
21745 Some(gpui::Point::new(source_x, source_y))
21746 }
21747
21748 pub fn has_visible_completions_menu(&self) -> bool {
21749 !self.edit_prediction_preview_is_active()
21750 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21751 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21752 })
21753 }
21754
21755 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21756 if self.mode.is_minimap() {
21757 return;
21758 }
21759 self.addons
21760 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21761 }
21762
21763 pub fn unregister_addon<T: Addon>(&mut self) {
21764 self.addons.remove(&std::any::TypeId::of::<T>());
21765 }
21766
21767 pub fn addon<T: Addon>(&self) -> Option<&T> {
21768 let type_id = std::any::TypeId::of::<T>();
21769 self.addons
21770 .get(&type_id)
21771 .and_then(|item| item.to_any().downcast_ref::<T>())
21772 }
21773
21774 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21775 let type_id = std::any::TypeId::of::<T>();
21776 self.addons
21777 .get_mut(&type_id)
21778 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21779 }
21780
21781 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21782 let text_layout_details = self.text_layout_details(window);
21783 let style = &text_layout_details.editor_style;
21784 let font_id = window.text_system().resolve_font(&style.text.font());
21785 let font_size = style.text.font_size.to_pixels(window.rem_size());
21786 let line_height = style.text.line_height_in_pixels(window.rem_size());
21787 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21788 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21789
21790 CharacterDimensions {
21791 em_width,
21792 em_advance,
21793 line_height,
21794 }
21795 }
21796
21797 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21798 self.load_diff_task.clone()
21799 }
21800
21801 fn read_metadata_from_db(
21802 &mut self,
21803 item_id: u64,
21804 workspace_id: WorkspaceId,
21805 window: &mut Window,
21806 cx: &mut Context<Editor>,
21807 ) {
21808 if self.is_singleton(cx)
21809 && !self.mode.is_minimap()
21810 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21811 {
21812 let buffer_snapshot = OnceCell::new();
21813
21814 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21815 && !folds.is_empty()
21816 {
21817 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21818 self.fold_ranges(
21819 folds
21820 .into_iter()
21821 .map(|(start, end)| {
21822 snapshot.clip_offset(start, Bias::Left)
21823 ..snapshot.clip_offset(end, Bias::Right)
21824 })
21825 .collect(),
21826 false,
21827 window,
21828 cx,
21829 );
21830 }
21831
21832 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21833 && !selections.is_empty()
21834 {
21835 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21836 // skip adding the initial selection to selection history
21837 self.selection_history.mode = SelectionHistoryMode::Skipping;
21838 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21839 s.select_ranges(selections.into_iter().map(|(start, end)| {
21840 snapshot.clip_offset(start, Bias::Left)
21841 ..snapshot.clip_offset(end, Bias::Right)
21842 }));
21843 });
21844 self.selection_history.mode = SelectionHistoryMode::Normal;
21845 };
21846 }
21847
21848 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21849 }
21850
21851 fn update_lsp_data(
21852 &mut self,
21853 ignore_cache: bool,
21854 for_buffer: Option<BufferId>,
21855 window: &mut Window,
21856 cx: &mut Context<'_, Self>,
21857 ) {
21858 self.pull_diagnostics(for_buffer, window, cx);
21859 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21860 }
21861}
21862
21863fn edit_for_markdown_paste<'a>(
21864 buffer: &MultiBufferSnapshot,
21865 range: Range<usize>,
21866 to_insert: &'a str,
21867 url: Option<url::Url>,
21868) -> (Range<usize>, Cow<'a, str>) {
21869 if url.is_none() {
21870 return (range, Cow::Borrowed(to_insert));
21871 };
21872
21873 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21874
21875 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21876 Cow::Borrowed(to_insert)
21877 } else {
21878 Cow::Owned(format!("[{old_text}]({to_insert})"))
21879 };
21880 (range, new_text)
21881}
21882
21883fn vim_enabled(cx: &App) -> bool {
21884 vim_mode_setting::VimModeSetting::try_get(cx)
21885 .map(|vim_mode| vim_mode.0)
21886 .unwrap_or(false)
21887}
21888
21889fn process_completion_for_edit(
21890 completion: &Completion,
21891 intent: CompletionIntent,
21892 buffer: &Entity<Buffer>,
21893 cursor_position: &text::Anchor,
21894 cx: &mut Context<Editor>,
21895) -> CompletionEdit {
21896 let buffer = buffer.read(cx);
21897 let buffer_snapshot = buffer.snapshot();
21898 let (snippet, new_text) = if completion.is_snippet() {
21899 let mut snippet_source = completion.new_text.clone();
21900 // Workaround for typescript language server issues so that methods don't expand within
21901 // strings and functions with type expressions. The previous point is used because the query
21902 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21903 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
21904 let previous_point = if previous_point.column > 0 {
21905 cursor_position.to_previous_offset(&buffer_snapshot)
21906 } else {
21907 cursor_position.to_offset(&buffer_snapshot)
21908 };
21909 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21910 && scope.prefers_label_for_snippet_in_completion()
21911 && let Some(label) = completion.label()
21912 && matches!(
21913 completion.kind(),
21914 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21915 )
21916 {
21917 snippet_source = label;
21918 }
21919 match Snippet::parse(&snippet_source).log_err() {
21920 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21921 None => (None, completion.new_text.clone()),
21922 }
21923 } else {
21924 (None, completion.new_text.clone())
21925 };
21926
21927 let mut range_to_replace = {
21928 let replace_range = &completion.replace_range;
21929 if let CompletionSource::Lsp {
21930 insert_range: Some(insert_range),
21931 ..
21932 } = &completion.source
21933 {
21934 debug_assert_eq!(
21935 insert_range.start, replace_range.start,
21936 "insert_range and replace_range should start at the same position"
21937 );
21938 debug_assert!(
21939 insert_range
21940 .start
21941 .cmp(cursor_position, &buffer_snapshot)
21942 .is_le(),
21943 "insert_range should start before or at cursor position"
21944 );
21945 debug_assert!(
21946 replace_range
21947 .start
21948 .cmp(cursor_position, &buffer_snapshot)
21949 .is_le(),
21950 "replace_range should start before or at cursor position"
21951 );
21952
21953 let should_replace = match intent {
21954 CompletionIntent::CompleteWithInsert => false,
21955 CompletionIntent::CompleteWithReplace => true,
21956 CompletionIntent::Complete | CompletionIntent::Compose => {
21957 let insert_mode =
21958 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21959 .completions
21960 .lsp_insert_mode;
21961 match insert_mode {
21962 LspInsertMode::Insert => false,
21963 LspInsertMode::Replace => true,
21964 LspInsertMode::ReplaceSubsequence => {
21965 let mut text_to_replace = buffer.chars_for_range(
21966 buffer.anchor_before(replace_range.start)
21967 ..buffer.anchor_after(replace_range.end),
21968 );
21969 let mut current_needle = text_to_replace.next();
21970 for haystack_ch in completion.label.text.chars() {
21971 if let Some(needle_ch) = current_needle
21972 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21973 {
21974 current_needle = text_to_replace.next();
21975 }
21976 }
21977 current_needle.is_none()
21978 }
21979 LspInsertMode::ReplaceSuffix => {
21980 if replace_range
21981 .end
21982 .cmp(cursor_position, &buffer_snapshot)
21983 .is_gt()
21984 {
21985 let range_after_cursor = *cursor_position..replace_range.end;
21986 let text_after_cursor = buffer
21987 .text_for_range(
21988 buffer.anchor_before(range_after_cursor.start)
21989 ..buffer.anchor_after(range_after_cursor.end),
21990 )
21991 .collect::<String>()
21992 .to_ascii_lowercase();
21993 completion
21994 .label
21995 .text
21996 .to_ascii_lowercase()
21997 .ends_with(&text_after_cursor)
21998 } else {
21999 true
22000 }
22001 }
22002 }
22003 }
22004 };
22005
22006 if should_replace {
22007 replace_range.clone()
22008 } else {
22009 insert_range.clone()
22010 }
22011 } else {
22012 replace_range.clone()
22013 }
22014 };
22015
22016 if range_to_replace
22017 .end
22018 .cmp(cursor_position, &buffer_snapshot)
22019 .is_lt()
22020 {
22021 range_to_replace.end = *cursor_position;
22022 }
22023
22024 CompletionEdit {
22025 new_text,
22026 replace_range: range_to_replace.to_offset(buffer),
22027 snippet,
22028 }
22029}
22030
22031struct CompletionEdit {
22032 new_text: String,
22033 replace_range: Range<usize>,
22034 snippet: Option<Snippet>,
22035}
22036
22037fn insert_extra_newline_brackets(
22038 buffer: &MultiBufferSnapshot,
22039 range: Range<usize>,
22040 language: &language::LanguageScope,
22041) -> bool {
22042 let leading_whitespace_len = buffer
22043 .reversed_chars_at(range.start)
22044 .take_while(|c| c.is_whitespace() && *c != '\n')
22045 .map(|c| c.len_utf8())
22046 .sum::<usize>();
22047 let trailing_whitespace_len = buffer
22048 .chars_at(range.end)
22049 .take_while(|c| c.is_whitespace() && *c != '\n')
22050 .map(|c| c.len_utf8())
22051 .sum::<usize>();
22052 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22053
22054 language.brackets().any(|(pair, enabled)| {
22055 let pair_start = pair.start.trim_end();
22056 let pair_end = pair.end.trim_start();
22057
22058 enabled
22059 && pair.newline
22060 && buffer.contains_str_at(range.end, pair_end)
22061 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22062 })
22063}
22064
22065fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22066 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22067 [(buffer, range, _)] => (*buffer, range.clone()),
22068 _ => return false,
22069 };
22070 let pair = {
22071 let mut result: Option<BracketMatch> = None;
22072
22073 for pair in buffer
22074 .all_bracket_ranges(range.clone())
22075 .filter(move |pair| {
22076 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22077 })
22078 {
22079 let len = pair.close_range.end - pair.open_range.start;
22080
22081 if let Some(existing) = &result {
22082 let existing_len = existing.close_range.end - existing.open_range.start;
22083 if len > existing_len {
22084 continue;
22085 }
22086 }
22087
22088 result = Some(pair);
22089 }
22090
22091 result
22092 };
22093 let Some(pair) = pair else {
22094 return false;
22095 };
22096 pair.newline_only
22097 && buffer
22098 .chars_for_range(pair.open_range.end..range.start)
22099 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22100 .all(|c| c.is_whitespace() && c != '\n')
22101}
22102
22103fn update_uncommitted_diff_for_buffer(
22104 editor: Entity<Editor>,
22105 project: &Entity<Project>,
22106 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22107 buffer: Entity<MultiBuffer>,
22108 cx: &mut App,
22109) -> Task<()> {
22110 let mut tasks = Vec::new();
22111 project.update(cx, |project, cx| {
22112 for buffer in buffers {
22113 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22114 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22115 }
22116 }
22117 });
22118 cx.spawn(async move |cx| {
22119 let diffs = future::join_all(tasks).await;
22120 if editor
22121 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22122 .unwrap_or(false)
22123 {
22124 return;
22125 }
22126
22127 buffer
22128 .update(cx, |buffer, cx| {
22129 for diff in diffs.into_iter().flatten() {
22130 buffer.add_diff(diff, cx);
22131 }
22132 })
22133 .ok();
22134 })
22135}
22136
22137fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22138 let tab_size = tab_size.get() as usize;
22139 let mut width = offset;
22140
22141 for ch in text.chars() {
22142 width += if ch == '\t' {
22143 tab_size - (width % tab_size)
22144 } else {
22145 1
22146 };
22147 }
22148
22149 width - offset
22150}
22151
22152#[cfg(test)]
22153mod tests {
22154 use super::*;
22155
22156 #[test]
22157 fn test_string_size_with_expanded_tabs() {
22158 let nz = |val| NonZeroU32::new(val).unwrap();
22159 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22160 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22161 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22162 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22163 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22164 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22165 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22166 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22167 }
22168}
22169
22170/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22171struct WordBreakingTokenizer<'a> {
22172 input: &'a str,
22173}
22174
22175impl<'a> WordBreakingTokenizer<'a> {
22176 fn new(input: &'a str) -> Self {
22177 Self { input }
22178 }
22179}
22180
22181fn is_char_ideographic(ch: char) -> bool {
22182 use unicode_script::Script::*;
22183 use unicode_script::UnicodeScript;
22184 matches!(ch.script(), Han | Tangut | Yi)
22185}
22186
22187fn is_grapheme_ideographic(text: &str) -> bool {
22188 text.chars().any(is_char_ideographic)
22189}
22190
22191fn is_grapheme_whitespace(text: &str) -> bool {
22192 text.chars().any(|x| x.is_whitespace())
22193}
22194
22195fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22196 text.chars()
22197 .next()
22198 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22199}
22200
22201#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22202enum WordBreakToken<'a> {
22203 Word { token: &'a str, grapheme_len: usize },
22204 InlineWhitespace { token: &'a str, grapheme_len: usize },
22205 Newline,
22206}
22207
22208impl<'a> Iterator for WordBreakingTokenizer<'a> {
22209 /// Yields a span, the count of graphemes in the token, and whether it was
22210 /// whitespace. Note that it also breaks at word boundaries.
22211 type Item = WordBreakToken<'a>;
22212
22213 fn next(&mut self) -> Option<Self::Item> {
22214 use unicode_segmentation::UnicodeSegmentation;
22215 if self.input.is_empty() {
22216 return None;
22217 }
22218
22219 let mut iter = self.input.graphemes(true).peekable();
22220 let mut offset = 0;
22221 let mut grapheme_len = 0;
22222 if let Some(first_grapheme) = iter.next() {
22223 let is_newline = first_grapheme == "\n";
22224 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22225 offset += first_grapheme.len();
22226 grapheme_len += 1;
22227 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22228 if let Some(grapheme) = iter.peek().copied()
22229 && should_stay_with_preceding_ideograph(grapheme)
22230 {
22231 offset += grapheme.len();
22232 grapheme_len += 1;
22233 }
22234 } else {
22235 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22236 let mut next_word_bound = words.peek().copied();
22237 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22238 next_word_bound = words.next();
22239 }
22240 while let Some(grapheme) = iter.peek().copied() {
22241 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22242 break;
22243 };
22244 if is_grapheme_whitespace(grapheme) != is_whitespace
22245 || (grapheme == "\n") != is_newline
22246 {
22247 break;
22248 };
22249 offset += grapheme.len();
22250 grapheme_len += 1;
22251 iter.next();
22252 }
22253 }
22254 let token = &self.input[..offset];
22255 self.input = &self.input[offset..];
22256 if token == "\n" {
22257 Some(WordBreakToken::Newline)
22258 } else if is_whitespace {
22259 Some(WordBreakToken::InlineWhitespace {
22260 token,
22261 grapheme_len,
22262 })
22263 } else {
22264 Some(WordBreakToken::Word {
22265 token,
22266 grapheme_len,
22267 })
22268 }
22269 } else {
22270 None
22271 }
22272 }
22273}
22274
22275#[test]
22276fn test_word_breaking_tokenizer() {
22277 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22278 ("", &[]),
22279 (" ", &[whitespace(" ", 2)]),
22280 ("Ʒ", &[word("Ʒ", 1)]),
22281 ("Ǽ", &[word("Ǽ", 1)]),
22282 ("⋑", &[word("⋑", 1)]),
22283 ("⋑⋑", &[word("⋑⋑", 2)]),
22284 (
22285 "原理,进而",
22286 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22287 ),
22288 (
22289 "hello world",
22290 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22291 ),
22292 (
22293 "hello, world",
22294 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22295 ),
22296 (
22297 " hello world",
22298 &[
22299 whitespace(" ", 2),
22300 word("hello", 5),
22301 whitespace(" ", 1),
22302 word("world", 5),
22303 ],
22304 ),
22305 (
22306 "这是什么 \n 钢笔",
22307 &[
22308 word("这", 1),
22309 word("是", 1),
22310 word("什", 1),
22311 word("么", 1),
22312 whitespace(" ", 1),
22313 newline(),
22314 whitespace(" ", 1),
22315 word("钢", 1),
22316 word("笔", 1),
22317 ],
22318 ),
22319 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22320 ];
22321
22322 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22323 WordBreakToken::Word {
22324 token,
22325 grapheme_len,
22326 }
22327 }
22328
22329 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22330 WordBreakToken::InlineWhitespace {
22331 token,
22332 grapheme_len,
22333 }
22334 }
22335
22336 fn newline() -> WordBreakToken<'static> {
22337 WordBreakToken::Newline
22338 }
22339
22340 for (input, result) in tests {
22341 assert_eq!(
22342 WordBreakingTokenizer::new(input)
22343 .collect::<Vec<_>>()
22344 .as_slice(),
22345 *result,
22346 );
22347 }
22348}
22349
22350fn wrap_with_prefix(
22351 first_line_prefix: String,
22352 subsequent_lines_prefix: String,
22353 unwrapped_text: String,
22354 wrap_column: usize,
22355 tab_size: NonZeroU32,
22356 preserve_existing_whitespace: bool,
22357) -> String {
22358 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22359 let subsequent_lines_prefix_len =
22360 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22361 let mut wrapped_text = String::new();
22362 let mut current_line = first_line_prefix;
22363 let mut is_first_line = true;
22364
22365 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22366 let mut current_line_len = first_line_prefix_len;
22367 let mut in_whitespace = false;
22368 for token in tokenizer {
22369 let have_preceding_whitespace = in_whitespace;
22370 match token {
22371 WordBreakToken::Word {
22372 token,
22373 grapheme_len,
22374 } => {
22375 in_whitespace = false;
22376 let current_prefix_len = if is_first_line {
22377 first_line_prefix_len
22378 } else {
22379 subsequent_lines_prefix_len
22380 };
22381 if current_line_len + grapheme_len > wrap_column
22382 && current_line_len != current_prefix_len
22383 {
22384 wrapped_text.push_str(current_line.trim_end());
22385 wrapped_text.push('\n');
22386 is_first_line = false;
22387 current_line = subsequent_lines_prefix.clone();
22388 current_line_len = subsequent_lines_prefix_len;
22389 }
22390 current_line.push_str(token);
22391 current_line_len += grapheme_len;
22392 }
22393 WordBreakToken::InlineWhitespace {
22394 mut token,
22395 mut grapheme_len,
22396 } => {
22397 in_whitespace = true;
22398 if have_preceding_whitespace && !preserve_existing_whitespace {
22399 continue;
22400 }
22401 if !preserve_existing_whitespace {
22402 // Keep a single whitespace grapheme as-is
22403 if let Some(first) =
22404 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22405 {
22406 token = first;
22407 } else {
22408 token = " ";
22409 }
22410 grapheme_len = 1;
22411 }
22412 let current_prefix_len = if is_first_line {
22413 first_line_prefix_len
22414 } else {
22415 subsequent_lines_prefix_len
22416 };
22417 if current_line_len + grapheme_len > wrap_column {
22418 wrapped_text.push_str(current_line.trim_end());
22419 wrapped_text.push('\n');
22420 is_first_line = false;
22421 current_line = subsequent_lines_prefix.clone();
22422 current_line_len = subsequent_lines_prefix_len;
22423 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22424 current_line.push_str(token);
22425 current_line_len += grapheme_len;
22426 }
22427 }
22428 WordBreakToken::Newline => {
22429 in_whitespace = true;
22430 let current_prefix_len = if is_first_line {
22431 first_line_prefix_len
22432 } else {
22433 subsequent_lines_prefix_len
22434 };
22435 if preserve_existing_whitespace {
22436 wrapped_text.push_str(current_line.trim_end());
22437 wrapped_text.push('\n');
22438 is_first_line = false;
22439 current_line = subsequent_lines_prefix.clone();
22440 current_line_len = subsequent_lines_prefix_len;
22441 } else if have_preceding_whitespace {
22442 continue;
22443 } else if current_line_len + 1 > wrap_column
22444 && current_line_len != current_prefix_len
22445 {
22446 wrapped_text.push_str(current_line.trim_end());
22447 wrapped_text.push('\n');
22448 is_first_line = false;
22449 current_line = subsequent_lines_prefix.clone();
22450 current_line_len = subsequent_lines_prefix_len;
22451 } else if current_line_len != current_prefix_len {
22452 current_line.push(' ');
22453 current_line_len += 1;
22454 }
22455 }
22456 }
22457 }
22458
22459 if !current_line.is_empty() {
22460 wrapped_text.push_str(¤t_line);
22461 }
22462 wrapped_text
22463}
22464
22465#[test]
22466fn test_wrap_with_prefix() {
22467 assert_eq!(
22468 wrap_with_prefix(
22469 "# ".to_string(),
22470 "# ".to_string(),
22471 "abcdefg".to_string(),
22472 4,
22473 NonZeroU32::new(4).unwrap(),
22474 false,
22475 ),
22476 "# abcdefg"
22477 );
22478 assert_eq!(
22479 wrap_with_prefix(
22480 "".to_string(),
22481 "".to_string(),
22482 "\thello world".to_string(),
22483 8,
22484 NonZeroU32::new(4).unwrap(),
22485 false,
22486 ),
22487 "hello\nworld"
22488 );
22489 assert_eq!(
22490 wrap_with_prefix(
22491 "// ".to_string(),
22492 "// ".to_string(),
22493 "xx \nyy zz aa bb cc".to_string(),
22494 12,
22495 NonZeroU32::new(4).unwrap(),
22496 false,
22497 ),
22498 "// xx yy zz\n// aa bb cc"
22499 );
22500 assert_eq!(
22501 wrap_with_prefix(
22502 String::new(),
22503 String::new(),
22504 "这是什么 \n 钢笔".to_string(),
22505 3,
22506 NonZeroU32::new(4).unwrap(),
22507 false,
22508 ),
22509 "这是什\n么 钢\n笔"
22510 );
22511 assert_eq!(
22512 wrap_with_prefix(
22513 String::new(),
22514 String::new(),
22515 format!("foo{}bar", '\u{2009}'), // thin space
22516 80,
22517 NonZeroU32::new(4).unwrap(),
22518 false,
22519 ),
22520 format!("foo{}bar", '\u{2009}')
22521 );
22522}
22523
22524pub trait CollaborationHub {
22525 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22526 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22527 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22528}
22529
22530impl CollaborationHub for Entity<Project> {
22531 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22532 self.read(cx).collaborators()
22533 }
22534
22535 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22536 self.read(cx).user_store().read(cx).participant_indices()
22537 }
22538
22539 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22540 let this = self.read(cx);
22541 let user_ids = this.collaborators().values().map(|c| c.user_id);
22542 this.user_store().read(cx).participant_names(user_ids, cx)
22543 }
22544}
22545
22546pub trait SemanticsProvider {
22547 fn hover(
22548 &self,
22549 buffer: &Entity<Buffer>,
22550 position: text::Anchor,
22551 cx: &mut App,
22552 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22553
22554 fn inline_values(
22555 &self,
22556 buffer_handle: Entity<Buffer>,
22557 range: Range<text::Anchor>,
22558 cx: &mut App,
22559 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22560
22561 fn inlay_hints(
22562 &self,
22563 buffer_handle: Entity<Buffer>,
22564 range: Range<text::Anchor>,
22565 cx: &mut App,
22566 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22567
22568 fn resolve_inlay_hint(
22569 &self,
22570 hint: InlayHint,
22571 buffer_handle: Entity<Buffer>,
22572 server_id: LanguageServerId,
22573 cx: &mut App,
22574 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22575
22576 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22577
22578 fn document_highlights(
22579 &self,
22580 buffer: &Entity<Buffer>,
22581 position: text::Anchor,
22582 cx: &mut App,
22583 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22584
22585 fn definitions(
22586 &self,
22587 buffer: &Entity<Buffer>,
22588 position: text::Anchor,
22589 kind: GotoDefinitionKind,
22590 cx: &mut App,
22591 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22592
22593 fn range_for_rename(
22594 &self,
22595 buffer: &Entity<Buffer>,
22596 position: text::Anchor,
22597 cx: &mut App,
22598 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22599
22600 fn perform_rename(
22601 &self,
22602 buffer: &Entity<Buffer>,
22603 position: text::Anchor,
22604 new_name: String,
22605 cx: &mut App,
22606 ) -> Option<Task<Result<ProjectTransaction>>>;
22607}
22608
22609pub trait CompletionProvider {
22610 fn completions(
22611 &self,
22612 excerpt_id: ExcerptId,
22613 buffer: &Entity<Buffer>,
22614 buffer_position: text::Anchor,
22615 trigger: CompletionContext,
22616 window: &mut Window,
22617 cx: &mut Context<Editor>,
22618 ) -> Task<Result<Vec<CompletionResponse>>>;
22619
22620 fn resolve_completions(
22621 &self,
22622 _buffer: Entity<Buffer>,
22623 _completion_indices: Vec<usize>,
22624 _completions: Rc<RefCell<Box<[Completion]>>>,
22625 _cx: &mut Context<Editor>,
22626 ) -> Task<Result<bool>> {
22627 Task::ready(Ok(false))
22628 }
22629
22630 fn apply_additional_edits_for_completion(
22631 &self,
22632 _buffer: Entity<Buffer>,
22633 _completions: Rc<RefCell<Box<[Completion]>>>,
22634 _completion_index: usize,
22635 _push_to_history: bool,
22636 _cx: &mut Context<Editor>,
22637 ) -> Task<Result<Option<language::Transaction>>> {
22638 Task::ready(Ok(None))
22639 }
22640
22641 fn is_completion_trigger(
22642 &self,
22643 buffer: &Entity<Buffer>,
22644 position: language::Anchor,
22645 text: &str,
22646 trigger_in_words: bool,
22647 menu_is_open: bool,
22648 cx: &mut Context<Editor>,
22649 ) -> bool;
22650
22651 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22652
22653 fn sort_completions(&self) -> bool {
22654 true
22655 }
22656
22657 fn filter_completions(&self) -> bool {
22658 true
22659 }
22660}
22661
22662pub trait CodeActionProvider {
22663 fn id(&self) -> Arc<str>;
22664
22665 fn code_actions(
22666 &self,
22667 buffer: &Entity<Buffer>,
22668 range: Range<text::Anchor>,
22669 window: &mut Window,
22670 cx: &mut App,
22671 ) -> Task<Result<Vec<CodeAction>>>;
22672
22673 fn apply_code_action(
22674 &self,
22675 buffer_handle: Entity<Buffer>,
22676 action: CodeAction,
22677 excerpt_id: ExcerptId,
22678 push_to_history: bool,
22679 window: &mut Window,
22680 cx: &mut App,
22681 ) -> Task<Result<ProjectTransaction>>;
22682}
22683
22684impl CodeActionProvider for Entity<Project> {
22685 fn id(&self) -> Arc<str> {
22686 "project".into()
22687 }
22688
22689 fn code_actions(
22690 &self,
22691 buffer: &Entity<Buffer>,
22692 range: Range<text::Anchor>,
22693 _window: &mut Window,
22694 cx: &mut App,
22695 ) -> Task<Result<Vec<CodeAction>>> {
22696 self.update(cx, |project, cx| {
22697 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22698 let code_actions = project.code_actions(buffer, range, None, cx);
22699 cx.background_spawn(async move {
22700 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22701 Ok(code_lens_actions
22702 .context("code lens fetch")?
22703 .into_iter()
22704 .flatten()
22705 .chain(
22706 code_actions
22707 .context("code action fetch")?
22708 .into_iter()
22709 .flatten(),
22710 )
22711 .collect())
22712 })
22713 })
22714 }
22715
22716 fn apply_code_action(
22717 &self,
22718 buffer_handle: Entity<Buffer>,
22719 action: CodeAction,
22720 _excerpt_id: ExcerptId,
22721 push_to_history: bool,
22722 _window: &mut Window,
22723 cx: &mut App,
22724 ) -> Task<Result<ProjectTransaction>> {
22725 self.update(cx, |project, cx| {
22726 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22727 })
22728 }
22729}
22730
22731fn snippet_completions(
22732 project: &Project,
22733 buffer: &Entity<Buffer>,
22734 buffer_position: text::Anchor,
22735 cx: &mut App,
22736) -> Task<Result<CompletionResponse>> {
22737 let languages = buffer.read(cx).languages_at(buffer_position);
22738 let snippet_store = project.snippets().read(cx);
22739
22740 let scopes: Vec<_> = languages
22741 .iter()
22742 .filter_map(|language| {
22743 let language_name = language.lsp_id();
22744 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22745
22746 if snippets.is_empty() {
22747 None
22748 } else {
22749 Some((language.default_scope(), snippets))
22750 }
22751 })
22752 .collect();
22753
22754 if scopes.is_empty() {
22755 return Task::ready(Ok(CompletionResponse {
22756 completions: vec![],
22757 display_options: CompletionDisplayOptions::default(),
22758 is_incomplete: false,
22759 }));
22760 }
22761
22762 let snapshot = buffer.read(cx).text_snapshot();
22763 let chars: String = snapshot
22764 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22765 .collect();
22766 let executor = cx.background_executor().clone();
22767
22768 cx.background_spawn(async move {
22769 let mut is_incomplete = false;
22770 let mut completions: Vec<Completion> = Vec::new();
22771 for (scope, snippets) in scopes.into_iter() {
22772 let classifier =
22773 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22774 let mut last_word = chars
22775 .chars()
22776 .take_while(|c| classifier.is_word(*c))
22777 .collect::<String>();
22778 last_word = last_word.chars().rev().collect();
22779
22780 if last_word.is_empty() {
22781 return Ok(CompletionResponse {
22782 completions: vec![],
22783 display_options: CompletionDisplayOptions::default(),
22784 is_incomplete: true,
22785 });
22786 }
22787
22788 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22789 let to_lsp = |point: &text::Anchor| {
22790 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22791 point_to_lsp(end)
22792 };
22793 let lsp_end = to_lsp(&buffer_position);
22794
22795 let candidates = snippets
22796 .iter()
22797 .enumerate()
22798 .flat_map(|(ix, snippet)| {
22799 snippet
22800 .prefix
22801 .iter()
22802 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22803 })
22804 .collect::<Vec<StringMatchCandidate>>();
22805
22806 const MAX_RESULTS: usize = 100;
22807 let mut matches = fuzzy::match_strings(
22808 &candidates,
22809 &last_word,
22810 last_word.chars().any(|c| c.is_uppercase()),
22811 true,
22812 MAX_RESULTS,
22813 &Default::default(),
22814 executor.clone(),
22815 )
22816 .await;
22817
22818 if matches.len() >= MAX_RESULTS {
22819 is_incomplete = true;
22820 }
22821
22822 // Remove all candidates where the query's start does not match the start of any word in the candidate
22823 if let Some(query_start) = last_word.chars().next() {
22824 matches.retain(|string_match| {
22825 split_words(&string_match.string).any(|word| {
22826 // Check that the first codepoint of the word as lowercase matches the first
22827 // codepoint of the query as lowercase
22828 word.chars()
22829 .flat_map(|codepoint| codepoint.to_lowercase())
22830 .zip(query_start.to_lowercase())
22831 .all(|(word_cp, query_cp)| word_cp == query_cp)
22832 })
22833 });
22834 }
22835
22836 let matched_strings = matches
22837 .into_iter()
22838 .map(|m| m.string)
22839 .collect::<HashSet<_>>();
22840
22841 completions.extend(snippets.iter().filter_map(|snippet| {
22842 let matching_prefix = snippet
22843 .prefix
22844 .iter()
22845 .find(|prefix| matched_strings.contains(*prefix))?;
22846 let start = as_offset - last_word.len();
22847 let start = snapshot.anchor_before(start);
22848 let range = start..buffer_position;
22849 let lsp_start = to_lsp(&start);
22850 let lsp_range = lsp::Range {
22851 start: lsp_start,
22852 end: lsp_end,
22853 };
22854 Some(Completion {
22855 replace_range: range,
22856 new_text: snippet.body.clone(),
22857 source: CompletionSource::Lsp {
22858 insert_range: None,
22859 server_id: LanguageServerId(usize::MAX),
22860 resolved: true,
22861 lsp_completion: Box::new(lsp::CompletionItem {
22862 label: snippet.prefix.first().unwrap().clone(),
22863 kind: Some(CompletionItemKind::SNIPPET),
22864 label_details: snippet.description.as_ref().map(|description| {
22865 lsp::CompletionItemLabelDetails {
22866 detail: Some(description.clone()),
22867 description: None,
22868 }
22869 }),
22870 insert_text_format: Some(InsertTextFormat::SNIPPET),
22871 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22872 lsp::InsertReplaceEdit {
22873 new_text: snippet.body.clone(),
22874 insert: lsp_range,
22875 replace: lsp_range,
22876 },
22877 )),
22878 filter_text: Some(snippet.body.clone()),
22879 sort_text: Some(char::MAX.to_string()),
22880 ..lsp::CompletionItem::default()
22881 }),
22882 lsp_defaults: None,
22883 },
22884 label: CodeLabel {
22885 text: matching_prefix.clone(),
22886 runs: Vec::new(),
22887 filter_range: 0..matching_prefix.len(),
22888 },
22889 icon_path: None,
22890 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22891 single_line: snippet.name.clone().into(),
22892 plain_text: snippet
22893 .description
22894 .clone()
22895 .map(|description| description.into()),
22896 }),
22897 insert_text_mode: None,
22898 confirm: None,
22899 })
22900 }))
22901 }
22902
22903 Ok(CompletionResponse {
22904 completions,
22905 display_options: CompletionDisplayOptions::default(),
22906 is_incomplete,
22907 })
22908 })
22909}
22910
22911impl CompletionProvider for Entity<Project> {
22912 fn completions(
22913 &self,
22914 _excerpt_id: ExcerptId,
22915 buffer: &Entity<Buffer>,
22916 buffer_position: text::Anchor,
22917 options: CompletionContext,
22918 _window: &mut Window,
22919 cx: &mut Context<Editor>,
22920 ) -> Task<Result<Vec<CompletionResponse>>> {
22921 self.update(cx, |project, cx| {
22922 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22923 let project_completions = project.completions(buffer, buffer_position, options, cx);
22924 cx.background_spawn(async move {
22925 let mut responses = project_completions.await?;
22926 let snippets = snippets.await?;
22927 if !snippets.completions.is_empty() {
22928 responses.push(snippets);
22929 }
22930 Ok(responses)
22931 })
22932 })
22933 }
22934
22935 fn resolve_completions(
22936 &self,
22937 buffer: Entity<Buffer>,
22938 completion_indices: Vec<usize>,
22939 completions: Rc<RefCell<Box<[Completion]>>>,
22940 cx: &mut Context<Editor>,
22941 ) -> Task<Result<bool>> {
22942 self.update(cx, |project, cx| {
22943 project.lsp_store().update(cx, |lsp_store, cx| {
22944 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22945 })
22946 })
22947 }
22948
22949 fn apply_additional_edits_for_completion(
22950 &self,
22951 buffer: Entity<Buffer>,
22952 completions: Rc<RefCell<Box<[Completion]>>>,
22953 completion_index: usize,
22954 push_to_history: bool,
22955 cx: &mut Context<Editor>,
22956 ) -> Task<Result<Option<language::Transaction>>> {
22957 self.update(cx, |project, cx| {
22958 project.lsp_store().update(cx, |lsp_store, cx| {
22959 lsp_store.apply_additional_edits_for_completion(
22960 buffer,
22961 completions,
22962 completion_index,
22963 push_to_history,
22964 cx,
22965 )
22966 })
22967 })
22968 }
22969
22970 fn is_completion_trigger(
22971 &self,
22972 buffer: &Entity<Buffer>,
22973 position: language::Anchor,
22974 text: &str,
22975 trigger_in_words: bool,
22976 menu_is_open: bool,
22977 cx: &mut Context<Editor>,
22978 ) -> bool {
22979 let mut chars = text.chars();
22980 let char = if let Some(char) = chars.next() {
22981 char
22982 } else {
22983 return false;
22984 };
22985 if chars.next().is_some() {
22986 return false;
22987 }
22988
22989 let buffer = buffer.read(cx);
22990 let snapshot = buffer.snapshot();
22991 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22992 return false;
22993 }
22994 let classifier = snapshot
22995 .char_classifier_at(position)
22996 .scope_context(Some(CharScopeContext::Completion));
22997 if trigger_in_words && classifier.is_word(char) {
22998 return true;
22999 }
23000
23001 buffer.completion_triggers().contains(text)
23002 }
23003}
23004
23005impl SemanticsProvider for Entity<Project> {
23006 fn hover(
23007 &self,
23008 buffer: &Entity<Buffer>,
23009 position: text::Anchor,
23010 cx: &mut App,
23011 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23012 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23013 }
23014
23015 fn document_highlights(
23016 &self,
23017 buffer: &Entity<Buffer>,
23018 position: text::Anchor,
23019 cx: &mut App,
23020 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23021 Some(self.update(cx, |project, cx| {
23022 project.document_highlights(buffer, position, cx)
23023 }))
23024 }
23025
23026 fn definitions(
23027 &self,
23028 buffer: &Entity<Buffer>,
23029 position: text::Anchor,
23030 kind: GotoDefinitionKind,
23031 cx: &mut App,
23032 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23033 Some(self.update(cx, |project, cx| match kind {
23034 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23035 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23036 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23037 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23038 }))
23039 }
23040
23041 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23042 self.update(cx, |project, cx| {
23043 if project
23044 .active_debug_session(cx)
23045 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23046 {
23047 return true;
23048 }
23049
23050 buffer.update(cx, |buffer, cx| {
23051 project.any_language_server_supports_inlay_hints(buffer, cx)
23052 })
23053 })
23054 }
23055
23056 fn inline_values(
23057 &self,
23058 buffer_handle: Entity<Buffer>,
23059 range: Range<text::Anchor>,
23060 cx: &mut App,
23061 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23062 self.update(cx, |project, cx| {
23063 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23064
23065 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23066 })
23067 }
23068
23069 fn inlay_hints(
23070 &self,
23071 buffer_handle: Entity<Buffer>,
23072 range: Range<text::Anchor>,
23073 cx: &mut App,
23074 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23075 Some(self.update(cx, |project, cx| {
23076 project.inlay_hints(buffer_handle, range, cx)
23077 }))
23078 }
23079
23080 fn resolve_inlay_hint(
23081 &self,
23082 hint: InlayHint,
23083 buffer_handle: Entity<Buffer>,
23084 server_id: LanguageServerId,
23085 cx: &mut App,
23086 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23087 Some(self.update(cx, |project, cx| {
23088 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23089 }))
23090 }
23091
23092 fn range_for_rename(
23093 &self,
23094 buffer: &Entity<Buffer>,
23095 position: text::Anchor,
23096 cx: &mut App,
23097 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23098 Some(self.update(cx, |project, cx| {
23099 let buffer = buffer.clone();
23100 let task = project.prepare_rename(buffer.clone(), position, cx);
23101 cx.spawn(async move |_, cx| {
23102 Ok(match task.await? {
23103 PrepareRenameResponse::Success(range) => Some(range),
23104 PrepareRenameResponse::InvalidPosition => None,
23105 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23106 // Fallback on using TreeSitter info to determine identifier range
23107 buffer.read_with(cx, |buffer, _| {
23108 let snapshot = buffer.snapshot();
23109 let (range, kind) = snapshot.surrounding_word(position, None);
23110 if kind != Some(CharKind::Word) {
23111 return None;
23112 }
23113 Some(
23114 snapshot.anchor_before(range.start)
23115 ..snapshot.anchor_after(range.end),
23116 )
23117 })?
23118 }
23119 })
23120 })
23121 }))
23122 }
23123
23124 fn perform_rename(
23125 &self,
23126 buffer: &Entity<Buffer>,
23127 position: text::Anchor,
23128 new_name: String,
23129 cx: &mut App,
23130 ) -> Option<Task<Result<ProjectTransaction>>> {
23131 Some(self.update(cx, |project, cx| {
23132 project.perform_rename(buffer.clone(), position, new_name, cx)
23133 }))
23134 }
23135}
23136
23137fn inlay_hint_settings(
23138 location: Anchor,
23139 snapshot: &MultiBufferSnapshot,
23140 cx: &mut Context<Editor>,
23141) -> InlayHintSettings {
23142 let file = snapshot.file_at(location);
23143 let language = snapshot.language_at(location).map(|l| l.name());
23144 language_settings(language, file, cx).inlay_hints
23145}
23146
23147fn consume_contiguous_rows(
23148 contiguous_row_selections: &mut Vec<Selection<Point>>,
23149 selection: &Selection<Point>,
23150 display_map: &DisplaySnapshot,
23151 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23152) -> (MultiBufferRow, MultiBufferRow) {
23153 contiguous_row_selections.push(selection.clone());
23154 let start_row = starting_row(selection, display_map);
23155 let mut end_row = ending_row(selection, display_map);
23156
23157 while let Some(next_selection) = selections.peek() {
23158 if next_selection.start.row <= end_row.0 {
23159 end_row = ending_row(next_selection, display_map);
23160 contiguous_row_selections.push(selections.next().unwrap().clone());
23161 } else {
23162 break;
23163 }
23164 }
23165 (start_row, end_row)
23166}
23167
23168fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23169 if selection.start.column > 0 {
23170 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23171 } else {
23172 MultiBufferRow(selection.start.row)
23173 }
23174}
23175
23176fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23177 if next_selection.end.column > 0 || next_selection.is_empty() {
23178 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23179 } else {
23180 MultiBufferRow(next_selection.end.row)
23181 }
23182}
23183
23184impl EditorSnapshot {
23185 pub fn remote_selections_in_range<'a>(
23186 &'a self,
23187 range: &'a Range<Anchor>,
23188 collaboration_hub: &dyn CollaborationHub,
23189 cx: &'a App,
23190 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23191 let participant_names = collaboration_hub.user_names(cx);
23192 let participant_indices = collaboration_hub.user_participant_indices(cx);
23193 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23194 let collaborators_by_replica_id = collaborators_by_peer_id
23195 .values()
23196 .map(|collaborator| (collaborator.replica_id, collaborator))
23197 .collect::<HashMap<_, _>>();
23198 self.buffer_snapshot
23199 .selections_in_range(range, false)
23200 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23201 if replica_id == AGENT_REPLICA_ID {
23202 Some(RemoteSelection {
23203 replica_id,
23204 selection,
23205 cursor_shape,
23206 line_mode,
23207 collaborator_id: CollaboratorId::Agent,
23208 user_name: Some("Agent".into()),
23209 color: cx.theme().players().agent(),
23210 })
23211 } else {
23212 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23213 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23214 let user_name = participant_names.get(&collaborator.user_id).cloned();
23215 Some(RemoteSelection {
23216 replica_id,
23217 selection,
23218 cursor_shape,
23219 line_mode,
23220 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23221 user_name,
23222 color: if let Some(index) = participant_index {
23223 cx.theme().players().color_for_participant(index.0)
23224 } else {
23225 cx.theme().players().absent()
23226 },
23227 })
23228 }
23229 })
23230 }
23231
23232 pub fn hunks_for_ranges(
23233 &self,
23234 ranges: impl IntoIterator<Item = Range<Point>>,
23235 ) -> Vec<MultiBufferDiffHunk> {
23236 let mut hunks = Vec::new();
23237 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23238 HashMap::default();
23239 for query_range in ranges {
23240 let query_rows =
23241 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23242 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23243 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23244 ) {
23245 // Include deleted hunks that are adjacent to the query range, because
23246 // otherwise they would be missed.
23247 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23248 if hunk.status().is_deleted() {
23249 intersects_range |= hunk.row_range.start == query_rows.end;
23250 intersects_range |= hunk.row_range.end == query_rows.start;
23251 }
23252 if intersects_range {
23253 if !processed_buffer_rows
23254 .entry(hunk.buffer_id)
23255 .or_default()
23256 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23257 {
23258 continue;
23259 }
23260 hunks.push(hunk);
23261 }
23262 }
23263 }
23264
23265 hunks
23266 }
23267
23268 fn display_diff_hunks_for_rows<'a>(
23269 &'a self,
23270 display_rows: Range<DisplayRow>,
23271 folded_buffers: &'a HashSet<BufferId>,
23272 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23273 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23274 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23275
23276 self.buffer_snapshot
23277 .diff_hunks_in_range(buffer_start..buffer_end)
23278 .filter_map(|hunk| {
23279 if folded_buffers.contains(&hunk.buffer_id) {
23280 return None;
23281 }
23282
23283 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23284 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23285
23286 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23287 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23288
23289 let display_hunk = if hunk_display_start.column() != 0 {
23290 DisplayDiffHunk::Folded {
23291 display_row: hunk_display_start.row(),
23292 }
23293 } else {
23294 let mut end_row = hunk_display_end.row();
23295 if hunk_display_end.column() > 0 {
23296 end_row.0 += 1;
23297 }
23298 let is_created_file = hunk.is_created_file();
23299 DisplayDiffHunk::Unfolded {
23300 status: hunk.status(),
23301 diff_base_byte_range: hunk.diff_base_byte_range,
23302 display_row_range: hunk_display_start.row()..end_row,
23303 multi_buffer_range: Anchor::range_in_buffer(
23304 hunk.excerpt_id,
23305 hunk.buffer_id,
23306 hunk.buffer_range,
23307 ),
23308 is_created_file,
23309 }
23310 };
23311
23312 Some(display_hunk)
23313 })
23314 }
23315
23316 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23317 self.display_snapshot.buffer_snapshot.language_at(position)
23318 }
23319
23320 pub fn is_focused(&self) -> bool {
23321 self.is_focused
23322 }
23323
23324 pub fn placeholder_text(&self) -> Option<String> {
23325 self.placeholder_display_snapshot
23326 .as_ref()
23327 .map(|display_map| display_map.text())
23328 }
23329
23330 pub fn scroll_position(&self) -> gpui::Point<f32> {
23331 self.scroll_anchor.scroll_position(&self.display_snapshot)
23332 }
23333
23334 fn gutter_dimensions(
23335 &self,
23336 font_id: FontId,
23337 font_size: Pixels,
23338 max_line_number_width: Pixels,
23339 cx: &App,
23340 ) -> Option<GutterDimensions> {
23341 if !self.show_gutter {
23342 return None;
23343 }
23344
23345 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23346 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23347
23348 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23349 matches!(
23350 ProjectSettings::get_global(cx).git.git_gutter,
23351 GitGutterSetting::TrackedFiles
23352 )
23353 });
23354 let gutter_settings = EditorSettings::get_global(cx).gutter;
23355 let show_line_numbers = self
23356 .show_line_numbers
23357 .unwrap_or(gutter_settings.line_numbers);
23358 let line_gutter_width = if show_line_numbers {
23359 // Avoid flicker-like gutter resizes when the line number gains another digit by
23360 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23361 let min_width_for_number_on_gutter =
23362 ch_advance * gutter_settings.min_line_number_digits as f32;
23363 max_line_number_width.max(min_width_for_number_on_gutter)
23364 } else {
23365 0.0.into()
23366 };
23367
23368 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23369 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23370
23371 let git_blame_entries_width =
23372 self.git_blame_gutter_max_author_length
23373 .map(|max_author_length| {
23374 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23375 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23376
23377 /// The number of characters to dedicate to gaps and margins.
23378 const SPACING_WIDTH: usize = 4;
23379
23380 let max_char_count = max_author_length.min(renderer.max_author_length())
23381 + ::git::SHORT_SHA_LENGTH
23382 + MAX_RELATIVE_TIMESTAMP.len()
23383 + SPACING_WIDTH;
23384
23385 ch_advance * max_char_count
23386 });
23387
23388 let is_singleton = self.buffer_snapshot.is_singleton();
23389
23390 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23391 left_padding += if !is_singleton {
23392 ch_width * 4.0
23393 } else if show_runnables || show_breakpoints {
23394 ch_width * 3.0
23395 } else if show_git_gutter && show_line_numbers {
23396 ch_width * 2.0
23397 } else if show_git_gutter || show_line_numbers {
23398 ch_width
23399 } else {
23400 px(0.)
23401 };
23402
23403 let shows_folds = is_singleton && gutter_settings.folds;
23404
23405 let right_padding = if shows_folds && show_line_numbers {
23406 ch_width * 4.0
23407 } else if shows_folds || (!is_singleton && show_line_numbers) {
23408 ch_width * 3.0
23409 } else if show_line_numbers {
23410 ch_width
23411 } else {
23412 px(0.)
23413 };
23414
23415 Some(GutterDimensions {
23416 left_padding,
23417 right_padding,
23418 width: line_gutter_width + left_padding + right_padding,
23419 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23420 git_blame_entries_width,
23421 })
23422 }
23423
23424 pub fn render_crease_toggle(
23425 &self,
23426 buffer_row: MultiBufferRow,
23427 row_contains_cursor: bool,
23428 editor: Entity<Editor>,
23429 window: &mut Window,
23430 cx: &mut App,
23431 ) -> Option<AnyElement> {
23432 let folded = self.is_line_folded(buffer_row);
23433 let mut is_foldable = false;
23434
23435 if let Some(crease) = self
23436 .crease_snapshot
23437 .query_row(buffer_row, &self.buffer_snapshot)
23438 {
23439 is_foldable = true;
23440 match crease {
23441 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23442 if let Some(render_toggle) = render_toggle {
23443 let toggle_callback =
23444 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23445 if folded {
23446 editor.update(cx, |editor, cx| {
23447 editor.fold_at(buffer_row, window, cx)
23448 });
23449 } else {
23450 editor.update(cx, |editor, cx| {
23451 editor.unfold_at(buffer_row, window, cx)
23452 });
23453 }
23454 });
23455 return Some((render_toggle)(
23456 buffer_row,
23457 folded,
23458 toggle_callback,
23459 window,
23460 cx,
23461 ));
23462 }
23463 }
23464 }
23465 }
23466
23467 is_foldable |= self.starts_indent(buffer_row);
23468
23469 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23470 Some(
23471 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23472 .toggle_state(folded)
23473 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23474 if folded {
23475 this.unfold_at(buffer_row, window, cx);
23476 } else {
23477 this.fold_at(buffer_row, window, cx);
23478 }
23479 }))
23480 .into_any_element(),
23481 )
23482 } else {
23483 None
23484 }
23485 }
23486
23487 pub fn render_crease_trailer(
23488 &self,
23489 buffer_row: MultiBufferRow,
23490 window: &mut Window,
23491 cx: &mut App,
23492 ) -> Option<AnyElement> {
23493 let folded = self.is_line_folded(buffer_row);
23494 if let Crease::Inline { render_trailer, .. } = self
23495 .crease_snapshot
23496 .query_row(buffer_row, &self.buffer_snapshot)?
23497 {
23498 let render_trailer = render_trailer.as_ref()?;
23499 Some(render_trailer(buffer_row, folded, window, cx))
23500 } else {
23501 None
23502 }
23503 }
23504}
23505
23506impl Deref for EditorSnapshot {
23507 type Target = DisplaySnapshot;
23508
23509 fn deref(&self) -> &Self::Target {
23510 &self.display_snapshot
23511 }
23512}
23513
23514#[derive(Clone, Debug, PartialEq, Eq)]
23515pub enum EditorEvent {
23516 InputIgnored {
23517 text: Arc<str>,
23518 },
23519 InputHandled {
23520 utf16_range_to_replace: Option<Range<isize>>,
23521 text: Arc<str>,
23522 },
23523 ExcerptsAdded {
23524 buffer: Entity<Buffer>,
23525 predecessor: ExcerptId,
23526 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23527 },
23528 ExcerptsRemoved {
23529 ids: Vec<ExcerptId>,
23530 removed_buffer_ids: Vec<BufferId>,
23531 },
23532 BufferFoldToggled {
23533 ids: Vec<ExcerptId>,
23534 folded: bool,
23535 },
23536 ExcerptsEdited {
23537 ids: Vec<ExcerptId>,
23538 },
23539 ExcerptsExpanded {
23540 ids: Vec<ExcerptId>,
23541 },
23542 BufferEdited,
23543 Edited {
23544 transaction_id: clock::Lamport,
23545 },
23546 Reparsed(BufferId),
23547 Focused,
23548 FocusedIn,
23549 Blurred,
23550 DirtyChanged,
23551 Saved,
23552 TitleChanged,
23553 SelectionsChanged {
23554 local: bool,
23555 },
23556 ScrollPositionChanged {
23557 local: bool,
23558 autoscroll: bool,
23559 },
23560 TransactionUndone {
23561 transaction_id: clock::Lamport,
23562 },
23563 TransactionBegun {
23564 transaction_id: clock::Lamport,
23565 },
23566 CursorShapeChanged,
23567 BreadcrumbsChanged,
23568 PushedToNavHistory {
23569 anchor: Anchor,
23570 is_deactivate: bool,
23571 },
23572}
23573
23574impl EventEmitter<EditorEvent> for Editor {}
23575
23576impl Focusable for Editor {
23577 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23578 self.focus_handle.clone()
23579 }
23580}
23581
23582impl Render for Editor {
23583 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23584 let settings = ThemeSettings::get_global(cx);
23585
23586 let mut text_style = match self.mode {
23587 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23588 color: cx.theme().colors().editor_foreground,
23589 font_family: settings.ui_font.family.clone(),
23590 font_features: settings.ui_font.features.clone(),
23591 font_fallbacks: settings.ui_font.fallbacks.clone(),
23592 font_size: rems(0.875).into(),
23593 font_weight: settings.ui_font.weight,
23594 line_height: relative(settings.buffer_line_height.value()),
23595 ..Default::default()
23596 },
23597 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23598 color: cx.theme().colors().editor_foreground,
23599 font_family: settings.buffer_font.family.clone(),
23600 font_features: settings.buffer_font.features.clone(),
23601 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23602 font_size: settings.buffer_font_size(cx).into(),
23603 font_weight: settings.buffer_font.weight,
23604 line_height: relative(settings.buffer_line_height.value()),
23605 ..Default::default()
23606 },
23607 };
23608 if let Some(text_style_refinement) = &self.text_style_refinement {
23609 text_style.refine(text_style_refinement)
23610 }
23611
23612 let background = match self.mode {
23613 EditorMode::SingleLine => cx.theme().system().transparent,
23614 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23615 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23616 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23617 };
23618
23619 EditorElement::new(
23620 &cx.entity(),
23621 EditorStyle {
23622 background,
23623 border: cx.theme().colors().border,
23624 local_player: cx.theme().players().local(),
23625 text: text_style,
23626 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23627 syntax: cx.theme().syntax().clone(),
23628 status: cx.theme().status().clone(),
23629 inlay_hints_style: make_inlay_hints_style(cx),
23630 edit_prediction_styles: make_suggestion_styles(cx),
23631 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23632 show_underlines: self.diagnostics_enabled(),
23633 },
23634 )
23635 }
23636}
23637
23638impl EntityInputHandler for Editor {
23639 fn text_for_range(
23640 &mut self,
23641 range_utf16: Range<usize>,
23642 adjusted_range: &mut Option<Range<usize>>,
23643 _: &mut Window,
23644 cx: &mut Context<Self>,
23645 ) -> Option<String> {
23646 let snapshot = self.buffer.read(cx).read(cx);
23647 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23648 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23649 if (start.0..end.0) != range_utf16 {
23650 adjusted_range.replace(start.0..end.0);
23651 }
23652 Some(snapshot.text_for_range(start..end).collect())
23653 }
23654
23655 fn selected_text_range(
23656 &mut self,
23657 ignore_disabled_input: bool,
23658 _: &mut Window,
23659 cx: &mut Context<Self>,
23660 ) -> Option<UTF16Selection> {
23661 // Prevent the IME menu from appearing when holding down an alphabetic key
23662 // while input is disabled.
23663 if !ignore_disabled_input && !self.input_enabled {
23664 return None;
23665 }
23666
23667 let selection = self.selections.newest::<OffsetUtf16>(cx);
23668 let range = selection.range();
23669
23670 Some(UTF16Selection {
23671 range: range.start.0..range.end.0,
23672 reversed: selection.reversed,
23673 })
23674 }
23675
23676 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23677 let snapshot = self.buffer.read(cx).read(cx);
23678 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23679 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23680 }
23681
23682 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23683 self.clear_highlights::<InputComposition>(cx);
23684 self.ime_transaction.take();
23685 }
23686
23687 fn replace_text_in_range(
23688 &mut self,
23689 range_utf16: Option<Range<usize>>,
23690 text: &str,
23691 window: &mut Window,
23692 cx: &mut Context<Self>,
23693 ) {
23694 if !self.input_enabled {
23695 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23696 return;
23697 }
23698
23699 self.transact(window, cx, |this, window, cx| {
23700 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23701 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23702 Some(this.selection_replacement_ranges(range_utf16, cx))
23703 } else {
23704 this.marked_text_ranges(cx)
23705 };
23706
23707 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23708 let newest_selection_id = this.selections.newest_anchor().id;
23709 this.selections
23710 .all::<OffsetUtf16>(cx)
23711 .iter()
23712 .zip(ranges_to_replace.iter())
23713 .find_map(|(selection, range)| {
23714 if selection.id == newest_selection_id {
23715 Some(
23716 (range.start.0 as isize - selection.head().0 as isize)
23717 ..(range.end.0 as isize - selection.head().0 as isize),
23718 )
23719 } else {
23720 None
23721 }
23722 })
23723 });
23724
23725 cx.emit(EditorEvent::InputHandled {
23726 utf16_range_to_replace: range_to_replace,
23727 text: text.into(),
23728 });
23729
23730 if let Some(new_selected_ranges) = new_selected_ranges {
23731 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23732 selections.select_ranges(new_selected_ranges)
23733 });
23734 this.backspace(&Default::default(), window, cx);
23735 }
23736
23737 this.handle_input(text, window, cx);
23738 });
23739
23740 if let Some(transaction) = self.ime_transaction {
23741 self.buffer.update(cx, |buffer, cx| {
23742 buffer.group_until_transaction(transaction, cx);
23743 });
23744 }
23745
23746 self.unmark_text(window, cx);
23747 }
23748
23749 fn replace_and_mark_text_in_range(
23750 &mut self,
23751 range_utf16: Option<Range<usize>>,
23752 text: &str,
23753 new_selected_range_utf16: Option<Range<usize>>,
23754 window: &mut Window,
23755 cx: &mut Context<Self>,
23756 ) {
23757 if !self.input_enabled {
23758 return;
23759 }
23760
23761 let transaction = self.transact(window, cx, |this, window, cx| {
23762 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23763 let snapshot = this.buffer.read(cx).read(cx);
23764 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23765 for marked_range in &mut marked_ranges {
23766 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23767 marked_range.start.0 += relative_range_utf16.start;
23768 marked_range.start =
23769 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23770 marked_range.end =
23771 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23772 }
23773 }
23774 Some(marked_ranges)
23775 } else if let Some(range_utf16) = range_utf16 {
23776 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23777 Some(this.selection_replacement_ranges(range_utf16, cx))
23778 } else {
23779 None
23780 };
23781
23782 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23783 let newest_selection_id = this.selections.newest_anchor().id;
23784 this.selections
23785 .all::<OffsetUtf16>(cx)
23786 .iter()
23787 .zip(ranges_to_replace.iter())
23788 .find_map(|(selection, range)| {
23789 if selection.id == newest_selection_id {
23790 Some(
23791 (range.start.0 as isize - selection.head().0 as isize)
23792 ..(range.end.0 as isize - selection.head().0 as isize),
23793 )
23794 } else {
23795 None
23796 }
23797 })
23798 });
23799
23800 cx.emit(EditorEvent::InputHandled {
23801 utf16_range_to_replace: range_to_replace,
23802 text: text.into(),
23803 });
23804
23805 if let Some(ranges) = ranges_to_replace {
23806 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23807 s.select_ranges(ranges)
23808 });
23809 }
23810
23811 let marked_ranges = {
23812 let snapshot = this.buffer.read(cx).read(cx);
23813 this.selections
23814 .disjoint_anchors_arc()
23815 .iter()
23816 .map(|selection| {
23817 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23818 })
23819 .collect::<Vec<_>>()
23820 };
23821
23822 if text.is_empty() {
23823 this.unmark_text(window, cx);
23824 } else {
23825 this.highlight_text::<InputComposition>(
23826 marked_ranges.clone(),
23827 HighlightStyle {
23828 underline: Some(UnderlineStyle {
23829 thickness: px(1.),
23830 color: None,
23831 wavy: false,
23832 }),
23833 ..Default::default()
23834 },
23835 cx,
23836 );
23837 }
23838
23839 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23840 let use_autoclose = this.use_autoclose;
23841 let use_auto_surround = this.use_auto_surround;
23842 this.set_use_autoclose(false);
23843 this.set_use_auto_surround(false);
23844 this.handle_input(text, window, cx);
23845 this.set_use_autoclose(use_autoclose);
23846 this.set_use_auto_surround(use_auto_surround);
23847
23848 if let Some(new_selected_range) = new_selected_range_utf16 {
23849 let snapshot = this.buffer.read(cx).read(cx);
23850 let new_selected_ranges = marked_ranges
23851 .into_iter()
23852 .map(|marked_range| {
23853 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23854 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23855 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23856 snapshot.clip_offset_utf16(new_start, Bias::Left)
23857 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23858 })
23859 .collect::<Vec<_>>();
23860
23861 drop(snapshot);
23862 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23863 selections.select_ranges(new_selected_ranges)
23864 });
23865 }
23866 });
23867
23868 self.ime_transaction = self.ime_transaction.or(transaction);
23869 if let Some(transaction) = self.ime_transaction {
23870 self.buffer.update(cx, |buffer, cx| {
23871 buffer.group_until_transaction(transaction, cx);
23872 });
23873 }
23874
23875 if self.text_highlights::<InputComposition>(cx).is_none() {
23876 self.ime_transaction.take();
23877 }
23878 }
23879
23880 fn bounds_for_range(
23881 &mut self,
23882 range_utf16: Range<usize>,
23883 element_bounds: gpui::Bounds<Pixels>,
23884 window: &mut Window,
23885 cx: &mut Context<Self>,
23886 ) -> Option<gpui::Bounds<Pixels>> {
23887 let text_layout_details = self.text_layout_details(window);
23888 let CharacterDimensions {
23889 em_width,
23890 em_advance,
23891 line_height,
23892 } = self.character_dimensions(window);
23893
23894 let snapshot = self.snapshot(window, cx);
23895 let scroll_position = snapshot.scroll_position();
23896 let scroll_left = scroll_position.x * em_advance;
23897
23898 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23899 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23900 + self.gutter_dimensions.full_width();
23901 let y = line_height * (start.row().as_f32() - scroll_position.y);
23902
23903 Some(Bounds {
23904 origin: element_bounds.origin + point(x, y),
23905 size: size(em_width, line_height),
23906 })
23907 }
23908
23909 fn character_index_for_point(
23910 &mut self,
23911 point: gpui::Point<Pixels>,
23912 _window: &mut Window,
23913 _cx: &mut Context<Self>,
23914 ) -> Option<usize> {
23915 let position_map = self.last_position_map.as_ref()?;
23916 if !position_map.text_hitbox.contains(&point) {
23917 return None;
23918 }
23919 let display_point = position_map.point_for_position(point).previous_valid;
23920 let anchor = position_map
23921 .snapshot
23922 .display_point_to_anchor(display_point, Bias::Left);
23923 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23924 Some(utf16_offset.0)
23925 }
23926}
23927
23928trait SelectionExt {
23929 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23930 fn spanned_rows(
23931 &self,
23932 include_end_if_at_line_start: bool,
23933 map: &DisplaySnapshot,
23934 ) -> Range<MultiBufferRow>;
23935}
23936
23937impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23938 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23939 let start = self
23940 .start
23941 .to_point(&map.buffer_snapshot)
23942 .to_display_point(map);
23943 let end = self
23944 .end
23945 .to_point(&map.buffer_snapshot)
23946 .to_display_point(map);
23947 if self.reversed {
23948 end..start
23949 } else {
23950 start..end
23951 }
23952 }
23953
23954 fn spanned_rows(
23955 &self,
23956 include_end_if_at_line_start: bool,
23957 map: &DisplaySnapshot,
23958 ) -> Range<MultiBufferRow> {
23959 let start = self.start.to_point(&map.buffer_snapshot);
23960 let mut end = self.end.to_point(&map.buffer_snapshot);
23961 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23962 end.row -= 1;
23963 }
23964
23965 let buffer_start = map.prev_line_boundary(start).0;
23966 let buffer_end = map.next_line_boundary(end).0;
23967 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23968 }
23969}
23970
23971impl<T: InvalidationRegion> InvalidationStack<T> {
23972 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23973 where
23974 S: Clone + ToOffset,
23975 {
23976 while let Some(region) = self.last() {
23977 let all_selections_inside_invalidation_ranges =
23978 if selections.len() == region.ranges().len() {
23979 selections
23980 .iter()
23981 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23982 .all(|(selection, invalidation_range)| {
23983 let head = selection.head().to_offset(buffer);
23984 invalidation_range.start <= head && invalidation_range.end >= head
23985 })
23986 } else {
23987 false
23988 };
23989
23990 if all_selections_inside_invalidation_ranges {
23991 break;
23992 } else {
23993 self.pop();
23994 }
23995 }
23996 }
23997}
23998
23999impl<T> Default for InvalidationStack<T> {
24000 fn default() -> Self {
24001 Self(Default::default())
24002 }
24003}
24004
24005impl<T> Deref for InvalidationStack<T> {
24006 type Target = Vec<T>;
24007
24008 fn deref(&self) -> &Self::Target {
24009 &self.0
24010 }
24011}
24012
24013impl<T> DerefMut for InvalidationStack<T> {
24014 fn deref_mut(&mut self) -> &mut Self::Target {
24015 &mut self.0
24016 }
24017}
24018
24019impl InvalidationRegion for SnippetState {
24020 fn ranges(&self) -> &[Range<Anchor>] {
24021 &self.ranges[self.active_index]
24022 }
24023}
24024
24025fn edit_prediction_edit_text(
24026 current_snapshot: &BufferSnapshot,
24027 edits: &[(Range<Anchor>, String)],
24028 edit_preview: &EditPreview,
24029 include_deletions: bool,
24030 cx: &App,
24031) -> HighlightedText {
24032 let edits = edits
24033 .iter()
24034 .map(|(anchor, text)| {
24035 (
24036 anchor.start.text_anchor..anchor.end.text_anchor,
24037 text.clone(),
24038 )
24039 })
24040 .collect::<Vec<_>>();
24041
24042 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24043}
24044
24045fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24046 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24047 // Just show the raw edit text with basic styling
24048 let mut text = String::new();
24049 let mut highlights = Vec::new();
24050
24051 let insertion_highlight_style = HighlightStyle {
24052 color: Some(cx.theme().colors().text),
24053 ..Default::default()
24054 };
24055
24056 for (_, edit_text) in edits {
24057 let start_offset = text.len();
24058 text.push_str(edit_text);
24059 let end_offset = text.len();
24060
24061 if start_offset < end_offset {
24062 highlights.push((start_offset..end_offset, insertion_highlight_style));
24063 }
24064 }
24065
24066 HighlightedText {
24067 text: text.into(),
24068 highlights,
24069 }
24070}
24071
24072pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24073 match severity {
24074 lsp::DiagnosticSeverity::ERROR => colors.error,
24075 lsp::DiagnosticSeverity::WARNING => colors.warning,
24076 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24077 lsp::DiagnosticSeverity::HINT => colors.info,
24078 _ => colors.ignored,
24079 }
24080}
24081
24082pub fn styled_runs_for_code_label<'a>(
24083 label: &'a CodeLabel,
24084 syntax_theme: &'a theme::SyntaxTheme,
24085) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24086 let fade_out = HighlightStyle {
24087 fade_out: Some(0.35),
24088 ..Default::default()
24089 };
24090
24091 let mut prev_end = label.filter_range.end;
24092 label
24093 .runs
24094 .iter()
24095 .enumerate()
24096 .flat_map(move |(ix, (range, highlight_id))| {
24097 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24098 style
24099 } else {
24100 return Default::default();
24101 };
24102 let muted_style = style.highlight(fade_out);
24103
24104 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24105 if range.start >= label.filter_range.end {
24106 if range.start > prev_end {
24107 runs.push((prev_end..range.start, fade_out));
24108 }
24109 runs.push((range.clone(), muted_style));
24110 } else if range.end <= label.filter_range.end {
24111 runs.push((range.clone(), style));
24112 } else {
24113 runs.push((range.start..label.filter_range.end, style));
24114 runs.push((label.filter_range.end..range.end, muted_style));
24115 }
24116 prev_end = cmp::max(prev_end, range.end);
24117
24118 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24119 runs.push((prev_end..label.text.len(), fade_out));
24120 }
24121
24122 runs
24123 })
24124}
24125
24126pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24127 let mut prev_index = 0;
24128 let mut prev_codepoint: Option<char> = None;
24129 text.char_indices()
24130 .chain([(text.len(), '\0')])
24131 .filter_map(move |(index, codepoint)| {
24132 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24133 let is_boundary = index == text.len()
24134 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24135 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24136 if is_boundary {
24137 let chunk = &text[prev_index..index];
24138 prev_index = index;
24139 Some(chunk)
24140 } else {
24141 None
24142 }
24143 })
24144}
24145
24146pub trait RangeToAnchorExt: Sized {
24147 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24148
24149 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24150 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24151 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24152 }
24153}
24154
24155impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24156 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24157 let start_offset = self.start.to_offset(snapshot);
24158 let end_offset = self.end.to_offset(snapshot);
24159 if start_offset == end_offset {
24160 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24161 } else {
24162 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24163 }
24164 }
24165}
24166
24167pub trait RowExt {
24168 fn as_f32(&self) -> f32;
24169
24170 fn next_row(&self) -> Self;
24171
24172 fn previous_row(&self) -> Self;
24173
24174 fn minus(&self, other: Self) -> u32;
24175}
24176
24177impl RowExt for DisplayRow {
24178 fn as_f32(&self) -> f32 {
24179 self.0 as f32
24180 }
24181
24182 fn next_row(&self) -> Self {
24183 Self(self.0 + 1)
24184 }
24185
24186 fn previous_row(&self) -> Self {
24187 Self(self.0.saturating_sub(1))
24188 }
24189
24190 fn minus(&self, other: Self) -> u32 {
24191 self.0 - other.0
24192 }
24193}
24194
24195impl RowExt for MultiBufferRow {
24196 fn as_f32(&self) -> f32 {
24197 self.0 as f32
24198 }
24199
24200 fn next_row(&self) -> Self {
24201 Self(self.0 + 1)
24202 }
24203
24204 fn previous_row(&self) -> Self {
24205 Self(self.0.saturating_sub(1))
24206 }
24207
24208 fn minus(&self, other: Self) -> u32 {
24209 self.0 - other.0
24210 }
24211}
24212
24213trait RowRangeExt {
24214 type Row;
24215
24216 fn len(&self) -> usize;
24217
24218 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24219}
24220
24221impl RowRangeExt for Range<MultiBufferRow> {
24222 type Row = MultiBufferRow;
24223
24224 fn len(&self) -> usize {
24225 (self.end.0 - self.start.0) as usize
24226 }
24227
24228 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24229 (self.start.0..self.end.0).map(MultiBufferRow)
24230 }
24231}
24232
24233impl RowRangeExt for Range<DisplayRow> {
24234 type Row = DisplayRow;
24235
24236 fn len(&self) -> usize {
24237 (self.end.0 - self.start.0) as usize
24238 }
24239
24240 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24241 (self.start.0..self.end.0).map(DisplayRow)
24242 }
24243}
24244
24245/// If select range has more than one line, we
24246/// just point the cursor to range.start.
24247fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24248 if range.start.row == range.end.row {
24249 range
24250 } else {
24251 range.start..range.start
24252 }
24253}
24254pub struct KillRing(ClipboardItem);
24255impl Global for KillRing {}
24256
24257const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24258
24259enum BreakpointPromptEditAction {
24260 Log,
24261 Condition,
24262 HitCondition,
24263}
24264
24265struct BreakpointPromptEditor {
24266 pub(crate) prompt: Entity<Editor>,
24267 editor: WeakEntity<Editor>,
24268 breakpoint_anchor: Anchor,
24269 breakpoint: Breakpoint,
24270 edit_action: BreakpointPromptEditAction,
24271 block_ids: HashSet<CustomBlockId>,
24272 editor_margins: Arc<Mutex<EditorMargins>>,
24273 _subscriptions: Vec<Subscription>,
24274}
24275
24276impl BreakpointPromptEditor {
24277 const MAX_LINES: u8 = 4;
24278
24279 fn new(
24280 editor: WeakEntity<Editor>,
24281 breakpoint_anchor: Anchor,
24282 breakpoint: Breakpoint,
24283 edit_action: BreakpointPromptEditAction,
24284 window: &mut Window,
24285 cx: &mut Context<Self>,
24286 ) -> Self {
24287 let base_text = match edit_action {
24288 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24289 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24290 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24291 }
24292 .map(|msg| msg.to_string())
24293 .unwrap_or_default();
24294
24295 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24296 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24297
24298 let prompt = cx.new(|cx| {
24299 let mut prompt = Editor::new(
24300 EditorMode::AutoHeight {
24301 min_lines: 1,
24302 max_lines: Some(Self::MAX_LINES as usize),
24303 },
24304 buffer,
24305 None,
24306 window,
24307 cx,
24308 );
24309 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24310 prompt.set_show_cursor_when_unfocused(false, cx);
24311 prompt.set_placeholder_text(
24312 match edit_action {
24313 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24314 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24315 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24316 },
24317 window,
24318 cx,
24319 );
24320
24321 prompt
24322 });
24323
24324 Self {
24325 prompt,
24326 editor,
24327 breakpoint_anchor,
24328 breakpoint,
24329 edit_action,
24330 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24331 block_ids: Default::default(),
24332 _subscriptions: vec![],
24333 }
24334 }
24335
24336 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24337 self.block_ids.extend(block_ids)
24338 }
24339
24340 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24341 if let Some(editor) = self.editor.upgrade() {
24342 let message = self
24343 .prompt
24344 .read(cx)
24345 .buffer
24346 .read(cx)
24347 .as_singleton()
24348 .expect("A multi buffer in breakpoint prompt isn't possible")
24349 .read(cx)
24350 .as_rope()
24351 .to_string();
24352
24353 editor.update(cx, |editor, cx| {
24354 editor.edit_breakpoint_at_anchor(
24355 self.breakpoint_anchor,
24356 self.breakpoint.clone(),
24357 match self.edit_action {
24358 BreakpointPromptEditAction::Log => {
24359 BreakpointEditAction::EditLogMessage(message.into())
24360 }
24361 BreakpointPromptEditAction::Condition => {
24362 BreakpointEditAction::EditCondition(message.into())
24363 }
24364 BreakpointPromptEditAction::HitCondition => {
24365 BreakpointEditAction::EditHitCondition(message.into())
24366 }
24367 },
24368 cx,
24369 );
24370
24371 editor.remove_blocks(self.block_ids.clone(), None, cx);
24372 cx.focus_self(window);
24373 });
24374 }
24375 }
24376
24377 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24378 self.editor
24379 .update(cx, |editor, cx| {
24380 editor.remove_blocks(self.block_ids.clone(), None, cx);
24381 window.focus(&editor.focus_handle);
24382 })
24383 .log_err();
24384 }
24385
24386 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24387 let settings = ThemeSettings::get_global(cx);
24388 let text_style = TextStyle {
24389 color: if self.prompt.read(cx).read_only(cx) {
24390 cx.theme().colors().text_disabled
24391 } else {
24392 cx.theme().colors().text
24393 },
24394 font_family: settings.buffer_font.family.clone(),
24395 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24396 font_size: settings.buffer_font_size(cx).into(),
24397 font_weight: settings.buffer_font.weight,
24398 line_height: relative(settings.buffer_line_height.value()),
24399 ..Default::default()
24400 };
24401 EditorElement::new(
24402 &self.prompt,
24403 EditorStyle {
24404 background: cx.theme().colors().editor_background,
24405 local_player: cx.theme().players().local(),
24406 text: text_style,
24407 ..Default::default()
24408 },
24409 )
24410 }
24411}
24412
24413impl Render for BreakpointPromptEditor {
24414 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24415 let editor_margins = *self.editor_margins.lock();
24416 let gutter_dimensions = editor_margins.gutter;
24417 h_flex()
24418 .key_context("Editor")
24419 .bg(cx.theme().colors().editor_background)
24420 .border_y_1()
24421 .border_color(cx.theme().status().info_border)
24422 .size_full()
24423 .py(window.line_height() / 2.5)
24424 .on_action(cx.listener(Self::confirm))
24425 .on_action(cx.listener(Self::cancel))
24426 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24427 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24428 }
24429}
24430
24431impl Focusable for BreakpointPromptEditor {
24432 fn focus_handle(&self, cx: &App) -> FocusHandle {
24433 self.prompt.focus_handle(cx)
24434 }
24435}
24436
24437fn all_edits_insertions_or_deletions(
24438 edits: &Vec<(Range<Anchor>, String)>,
24439 snapshot: &MultiBufferSnapshot,
24440) -> bool {
24441 let mut all_insertions = true;
24442 let mut all_deletions = true;
24443
24444 for (range, new_text) in edits.iter() {
24445 let range_is_empty = range.to_offset(snapshot).is_empty();
24446 let text_is_empty = new_text.is_empty();
24447
24448 if range_is_empty != text_is_empty {
24449 if range_is_empty {
24450 all_deletions = false;
24451 } else {
24452 all_insertions = false;
24453 }
24454 } else {
24455 return false;
24456 }
24457
24458 if !all_insertions && !all_deletions {
24459 return false;
24460 }
24461 }
24462 all_insertions || all_deletions
24463}
24464
24465struct MissingEditPredictionKeybindingTooltip;
24466
24467impl Render for MissingEditPredictionKeybindingTooltip {
24468 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24469 ui::tooltip_container(cx, |container, cx| {
24470 container
24471 .flex_shrink_0()
24472 .max_w_80()
24473 .min_h(rems_from_px(124.))
24474 .justify_between()
24475 .child(
24476 v_flex()
24477 .flex_1()
24478 .text_ui_sm(cx)
24479 .child(Label::new("Conflict with Accept Keybinding"))
24480 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24481 )
24482 .child(
24483 h_flex()
24484 .pb_1()
24485 .gap_1()
24486 .items_end()
24487 .w_full()
24488 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24489 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24490 }))
24491 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24492 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24493 })),
24494 )
24495 })
24496 }
24497}
24498
24499#[derive(Debug, Clone, Copy, PartialEq)]
24500pub struct LineHighlight {
24501 pub background: Background,
24502 pub border: Option<gpui::Hsla>,
24503 pub include_gutter: bool,
24504 pub type_id: Option<TypeId>,
24505}
24506
24507struct LineManipulationResult {
24508 pub new_text: String,
24509 pub line_count_before: usize,
24510 pub line_count_after: usize,
24511}
24512
24513fn render_diff_hunk_controls(
24514 row: u32,
24515 status: &DiffHunkStatus,
24516 hunk_range: Range<Anchor>,
24517 is_created_file: bool,
24518 line_height: Pixels,
24519 editor: &Entity<Editor>,
24520 _window: &mut Window,
24521 cx: &mut App,
24522) -> AnyElement {
24523 h_flex()
24524 .h(line_height)
24525 .mr_1()
24526 .gap_1()
24527 .px_0p5()
24528 .pb_1()
24529 .border_x_1()
24530 .border_b_1()
24531 .border_color(cx.theme().colors().border_variant)
24532 .rounded_b_lg()
24533 .bg(cx.theme().colors().editor_background)
24534 .gap_1()
24535 .block_mouse_except_scroll()
24536 .shadow_md()
24537 .child(if status.has_secondary_hunk() {
24538 Button::new(("stage", row as u64), "Stage")
24539 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24540 .tooltip({
24541 let focus_handle = editor.focus_handle(cx);
24542 move |window, cx| {
24543 Tooltip::for_action_in(
24544 "Stage Hunk",
24545 &::git::ToggleStaged,
24546 &focus_handle,
24547 window,
24548 cx,
24549 )
24550 }
24551 })
24552 .on_click({
24553 let editor = editor.clone();
24554 move |_event, _window, cx| {
24555 editor.update(cx, |editor, cx| {
24556 editor.stage_or_unstage_diff_hunks(
24557 true,
24558 vec![hunk_range.start..hunk_range.start],
24559 cx,
24560 );
24561 });
24562 }
24563 })
24564 } else {
24565 Button::new(("unstage", row as u64), "Unstage")
24566 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24567 .tooltip({
24568 let focus_handle = editor.focus_handle(cx);
24569 move |window, cx| {
24570 Tooltip::for_action_in(
24571 "Unstage Hunk",
24572 &::git::ToggleStaged,
24573 &focus_handle,
24574 window,
24575 cx,
24576 )
24577 }
24578 })
24579 .on_click({
24580 let editor = editor.clone();
24581 move |_event, _window, cx| {
24582 editor.update(cx, |editor, cx| {
24583 editor.stage_or_unstage_diff_hunks(
24584 false,
24585 vec![hunk_range.start..hunk_range.start],
24586 cx,
24587 );
24588 });
24589 }
24590 })
24591 })
24592 .child(
24593 Button::new(("restore", row as u64), "Restore")
24594 .tooltip({
24595 let focus_handle = editor.focus_handle(cx);
24596 move |window, cx| {
24597 Tooltip::for_action_in(
24598 "Restore Hunk",
24599 &::git::Restore,
24600 &focus_handle,
24601 window,
24602 cx,
24603 )
24604 }
24605 })
24606 .on_click({
24607 let editor = editor.clone();
24608 move |_event, window, cx| {
24609 editor.update(cx, |editor, cx| {
24610 let snapshot = editor.snapshot(window, cx);
24611 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24612 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24613 });
24614 }
24615 })
24616 .disabled(is_created_file),
24617 )
24618 .when(
24619 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24620 |el| {
24621 el.child(
24622 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24623 .shape(IconButtonShape::Square)
24624 .icon_size(IconSize::Small)
24625 // .disabled(!has_multiple_hunks)
24626 .tooltip({
24627 let focus_handle = editor.focus_handle(cx);
24628 move |window, cx| {
24629 Tooltip::for_action_in(
24630 "Next Hunk",
24631 &GoToHunk,
24632 &focus_handle,
24633 window,
24634 cx,
24635 )
24636 }
24637 })
24638 .on_click({
24639 let editor = editor.clone();
24640 move |_event, window, cx| {
24641 editor.update(cx, |editor, cx| {
24642 let snapshot = editor.snapshot(window, cx);
24643 let position =
24644 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24645 editor.go_to_hunk_before_or_after_position(
24646 &snapshot,
24647 position,
24648 Direction::Next,
24649 window,
24650 cx,
24651 );
24652 editor.expand_selected_diff_hunks(cx);
24653 });
24654 }
24655 }),
24656 )
24657 .child(
24658 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24659 .shape(IconButtonShape::Square)
24660 .icon_size(IconSize::Small)
24661 // .disabled(!has_multiple_hunks)
24662 .tooltip({
24663 let focus_handle = editor.focus_handle(cx);
24664 move |window, cx| {
24665 Tooltip::for_action_in(
24666 "Previous Hunk",
24667 &GoToPreviousHunk,
24668 &focus_handle,
24669 window,
24670 cx,
24671 )
24672 }
24673 })
24674 .on_click({
24675 let editor = editor.clone();
24676 move |_event, window, cx| {
24677 editor.update(cx, |editor, cx| {
24678 let snapshot = editor.snapshot(window, cx);
24679 let point =
24680 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24681 editor.go_to_hunk_before_or_after_position(
24682 &snapshot,
24683 point,
24684 Direction::Prev,
24685 window,
24686 cx,
24687 );
24688 editor.expand_selected_diff_hunks(cx);
24689 });
24690 }
24691 }),
24692 )
24693 },
24694 )
24695 .into_any_element()
24696}
24697
24698pub fn multibuffer_context_lines(cx: &App) -> u32 {
24699 EditorSettings::try_get(cx)
24700 .map(|settings| settings.excerpt_context_lines)
24701 .unwrap_or(2)
24702 .min(32)
24703}