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 DiagnosticEntry, 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};
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<DiagnosticEntry<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<DiagnosticEntry<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(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3228 .log_err();
3229 });
3230 }
3231 }
3232
3233 cx.notify();
3234 }
3235
3236 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3237 use text::ToOffset as _;
3238 use text::ToPoint as _;
3239
3240 if self.mode.is_minimap()
3241 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3242 {
3243 return;
3244 }
3245
3246 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3247 return;
3248 };
3249
3250 let snapshot = singleton.read(cx).snapshot();
3251 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3252 let display_snapshot = display_map.snapshot(cx);
3253
3254 display_snapshot
3255 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3256 .map(|fold| {
3257 fold.range.start.text_anchor.to_point(&snapshot)
3258 ..fold.range.end.text_anchor.to_point(&snapshot)
3259 })
3260 .collect()
3261 });
3262 self.update_restoration_data(cx, |data| {
3263 data.folds = inmemory_folds;
3264 });
3265
3266 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3267 return;
3268 };
3269 let background_executor = cx.background_executor().clone();
3270 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3271 let db_folds = self.display_map.update(cx, |display_map, cx| {
3272 display_map
3273 .snapshot(cx)
3274 .folds_in_range(0..snapshot.len())
3275 .map(|fold| {
3276 (
3277 fold.range.start.text_anchor.to_offset(&snapshot),
3278 fold.range.end.text_anchor.to_offset(&snapshot),
3279 )
3280 })
3281 .collect()
3282 });
3283 self.serialize_folds = cx.background_spawn(async move {
3284 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3285 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3286 .await
3287 .with_context(|| {
3288 format!(
3289 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3290 )
3291 })
3292 .log_err();
3293 });
3294 }
3295
3296 pub fn sync_selections(
3297 &mut self,
3298 other: Entity<Editor>,
3299 cx: &mut Context<Self>,
3300 ) -> gpui::Subscription {
3301 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3302 if !other_selections.is_empty() {
3303 self.selections.change_with(cx, |selections| {
3304 selections.select_anchors(other_selections);
3305 });
3306 }
3307
3308 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3309 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3310 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3311 if other_selections.is_empty() {
3312 return;
3313 }
3314 this.selections.change_with(cx, |selections| {
3315 selections.select_anchors(other_selections);
3316 });
3317 }
3318 });
3319
3320 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3321 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3322 let these_selections = this.selections.disjoint_anchors().to_vec();
3323 if these_selections.is_empty() {
3324 return;
3325 }
3326 other.update(cx, |other_editor, cx| {
3327 other_editor.selections.change_with(cx, |selections| {
3328 selections.select_anchors(these_selections);
3329 })
3330 });
3331 }
3332 });
3333
3334 Subscription::join(other_subscription, this_subscription)
3335 }
3336
3337 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3338 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3339 /// effects of selection change occur at the end of the transaction.
3340 pub fn change_selections<R>(
3341 &mut self,
3342 effects: SelectionEffects,
3343 window: &mut Window,
3344 cx: &mut Context<Self>,
3345 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3346 ) -> R {
3347 if let Some(state) = &mut self.deferred_selection_effects_state {
3348 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3349 state.effects.completions = effects.completions;
3350 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3351 let (changed, result) = self.selections.change_with(cx, change);
3352 state.changed |= changed;
3353 return result;
3354 }
3355 let mut state = DeferredSelectionEffectsState {
3356 changed: false,
3357 effects,
3358 old_cursor_position: self.selections.newest_anchor().head(),
3359 history_entry: SelectionHistoryEntry {
3360 selections: self.selections.disjoint_anchors_arc(),
3361 select_next_state: self.select_next_state.clone(),
3362 select_prev_state: self.select_prev_state.clone(),
3363 add_selections_state: self.add_selections_state.clone(),
3364 },
3365 };
3366 let (changed, result) = self.selections.change_with(cx, change);
3367 state.changed = state.changed || changed;
3368 if self.defer_selection_effects {
3369 self.deferred_selection_effects_state = Some(state);
3370 } else {
3371 self.apply_selection_effects(state, window, cx);
3372 }
3373 result
3374 }
3375
3376 /// Defers the effects of selection change, so that the effects of multiple calls to
3377 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3378 /// to selection history and the state of popovers based on selection position aren't
3379 /// erroneously updated.
3380 pub fn with_selection_effects_deferred<R>(
3381 &mut self,
3382 window: &mut Window,
3383 cx: &mut Context<Self>,
3384 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3385 ) -> R {
3386 let already_deferred = self.defer_selection_effects;
3387 self.defer_selection_effects = true;
3388 let result = update(self, window, cx);
3389 if !already_deferred {
3390 self.defer_selection_effects = false;
3391 if let Some(state) = self.deferred_selection_effects_state.take() {
3392 self.apply_selection_effects(state, window, cx);
3393 }
3394 }
3395 result
3396 }
3397
3398 fn apply_selection_effects(
3399 &mut self,
3400 state: DeferredSelectionEffectsState,
3401 window: &mut Window,
3402 cx: &mut Context<Self>,
3403 ) {
3404 if state.changed {
3405 self.selection_history.push(state.history_entry);
3406
3407 if let Some(autoscroll) = state.effects.scroll {
3408 self.request_autoscroll(autoscroll, cx);
3409 }
3410
3411 let old_cursor_position = &state.old_cursor_position;
3412
3413 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3414
3415 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3416 self.show_signature_help(&ShowSignatureHelp, window, cx);
3417 }
3418 }
3419 }
3420
3421 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3422 where
3423 I: IntoIterator<Item = (Range<S>, T)>,
3424 S: ToOffset,
3425 T: Into<Arc<str>>,
3426 {
3427 if self.read_only(cx) {
3428 return;
3429 }
3430
3431 self.buffer
3432 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3433 }
3434
3435 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3436 where
3437 I: IntoIterator<Item = (Range<S>, T)>,
3438 S: ToOffset,
3439 T: Into<Arc<str>>,
3440 {
3441 if self.read_only(cx) {
3442 return;
3443 }
3444
3445 self.buffer.update(cx, |buffer, cx| {
3446 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3447 });
3448 }
3449
3450 pub fn edit_with_block_indent<I, S, T>(
3451 &mut self,
3452 edits: I,
3453 original_indent_columns: Vec<Option<u32>>,
3454 cx: &mut Context<Self>,
3455 ) where
3456 I: IntoIterator<Item = (Range<S>, T)>,
3457 S: ToOffset,
3458 T: Into<Arc<str>>,
3459 {
3460 if self.read_only(cx) {
3461 return;
3462 }
3463
3464 self.buffer.update(cx, |buffer, cx| {
3465 buffer.edit(
3466 edits,
3467 Some(AutoindentMode::Block {
3468 original_indent_columns,
3469 }),
3470 cx,
3471 )
3472 });
3473 }
3474
3475 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3476 self.hide_context_menu(window, cx);
3477
3478 match phase {
3479 SelectPhase::Begin {
3480 position,
3481 add,
3482 click_count,
3483 } => self.begin_selection(position, add, click_count, window, cx),
3484 SelectPhase::BeginColumnar {
3485 position,
3486 goal_column,
3487 reset,
3488 mode,
3489 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3490 SelectPhase::Extend {
3491 position,
3492 click_count,
3493 } => self.extend_selection(position, click_count, window, cx),
3494 SelectPhase::Update {
3495 position,
3496 goal_column,
3497 scroll_delta,
3498 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3499 SelectPhase::End => self.end_selection(window, cx),
3500 }
3501 }
3502
3503 fn extend_selection(
3504 &mut self,
3505 position: DisplayPoint,
3506 click_count: usize,
3507 window: &mut Window,
3508 cx: &mut Context<Self>,
3509 ) {
3510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3511 let tail = self.selections.newest::<usize>(cx).tail();
3512 self.begin_selection(position, false, click_count, window, cx);
3513
3514 let position = position.to_offset(&display_map, Bias::Left);
3515 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3516
3517 let mut pending_selection = self
3518 .selections
3519 .pending_anchor()
3520 .cloned()
3521 .expect("extend_selection not called with pending selection");
3522 if position >= tail {
3523 pending_selection.start = tail_anchor;
3524 } else {
3525 pending_selection.end = tail_anchor;
3526 pending_selection.reversed = true;
3527 }
3528
3529 let mut pending_mode = self.selections.pending_mode().unwrap();
3530 match &mut pending_mode {
3531 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3532 _ => {}
3533 }
3534
3535 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3536 SelectionEffects::scroll(Autoscroll::fit())
3537 } else {
3538 SelectionEffects::no_scroll()
3539 };
3540
3541 self.change_selections(effects, window, cx, |s| {
3542 s.set_pending(pending_selection.clone(), pending_mode)
3543 });
3544 }
3545
3546 fn begin_selection(
3547 &mut self,
3548 position: DisplayPoint,
3549 add: bool,
3550 click_count: usize,
3551 window: &mut Window,
3552 cx: &mut Context<Self>,
3553 ) {
3554 if !self.focus_handle.is_focused(window) {
3555 self.last_focused_descendant = None;
3556 window.focus(&self.focus_handle);
3557 }
3558
3559 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3560 let buffer = &display_map.buffer_snapshot;
3561 let position = display_map.clip_point(position, Bias::Left);
3562
3563 let start;
3564 let end;
3565 let mode;
3566 let mut auto_scroll;
3567 match click_count {
3568 1 => {
3569 start = buffer.anchor_before(position.to_point(&display_map));
3570 end = start;
3571 mode = SelectMode::Character;
3572 auto_scroll = true;
3573 }
3574 2 => {
3575 let position = display_map
3576 .clip_point(position, Bias::Left)
3577 .to_offset(&display_map, Bias::Left);
3578 let (range, _) = buffer.surrounding_word(position, None);
3579 start = buffer.anchor_before(range.start);
3580 end = buffer.anchor_before(range.end);
3581 mode = SelectMode::Word(start..end);
3582 auto_scroll = true;
3583 }
3584 3 => {
3585 let position = display_map
3586 .clip_point(position, Bias::Left)
3587 .to_point(&display_map);
3588 let line_start = display_map.prev_line_boundary(position).0;
3589 let next_line_start = buffer.clip_point(
3590 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3591 Bias::Left,
3592 );
3593 start = buffer.anchor_before(line_start);
3594 end = buffer.anchor_before(next_line_start);
3595 mode = SelectMode::Line(start..end);
3596 auto_scroll = true;
3597 }
3598 _ => {
3599 start = buffer.anchor_before(0);
3600 end = buffer.anchor_before(buffer.len());
3601 mode = SelectMode::All;
3602 auto_scroll = false;
3603 }
3604 }
3605 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3606
3607 let point_to_delete: Option<usize> = {
3608 let selected_points: Vec<Selection<Point>> =
3609 self.selections.disjoint_in_range(start..end, cx);
3610
3611 if !add || click_count > 1 {
3612 None
3613 } else if !selected_points.is_empty() {
3614 Some(selected_points[0].id)
3615 } else {
3616 let clicked_point_already_selected =
3617 self.selections.disjoint_anchors().iter().find(|selection| {
3618 selection.start.to_point(buffer) == start.to_point(buffer)
3619 || selection.end.to_point(buffer) == end.to_point(buffer)
3620 });
3621
3622 clicked_point_already_selected.map(|selection| selection.id)
3623 }
3624 };
3625
3626 let selections_count = self.selections.count();
3627 let effects = if auto_scroll {
3628 SelectionEffects::default()
3629 } else {
3630 SelectionEffects::no_scroll()
3631 };
3632
3633 self.change_selections(effects, window, cx, |s| {
3634 if let Some(point_to_delete) = point_to_delete {
3635 s.delete(point_to_delete);
3636
3637 if selections_count == 1 {
3638 s.set_pending_anchor_range(start..end, mode);
3639 }
3640 } else {
3641 if !add {
3642 s.clear_disjoint();
3643 }
3644
3645 s.set_pending_anchor_range(start..end, mode);
3646 }
3647 });
3648 }
3649
3650 fn begin_columnar_selection(
3651 &mut self,
3652 position: DisplayPoint,
3653 goal_column: u32,
3654 reset: bool,
3655 mode: ColumnarMode,
3656 window: &mut Window,
3657 cx: &mut Context<Self>,
3658 ) {
3659 if !self.focus_handle.is_focused(window) {
3660 self.last_focused_descendant = None;
3661 window.focus(&self.focus_handle);
3662 }
3663
3664 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3665
3666 if reset {
3667 let pointer_position = display_map
3668 .buffer_snapshot
3669 .anchor_before(position.to_point(&display_map));
3670
3671 self.change_selections(
3672 SelectionEffects::scroll(Autoscroll::newest()),
3673 window,
3674 cx,
3675 |s| {
3676 s.clear_disjoint();
3677 s.set_pending_anchor_range(
3678 pointer_position..pointer_position,
3679 SelectMode::Character,
3680 );
3681 },
3682 );
3683 };
3684
3685 let tail = self.selections.newest::<Point>(cx).tail();
3686 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3687 self.columnar_selection_state = match mode {
3688 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3689 selection_tail: selection_anchor,
3690 display_point: if reset {
3691 if position.column() != goal_column {
3692 Some(DisplayPoint::new(position.row(), goal_column))
3693 } else {
3694 None
3695 }
3696 } else {
3697 None
3698 },
3699 }),
3700 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3701 selection_tail: selection_anchor,
3702 }),
3703 };
3704
3705 if !reset {
3706 self.select_columns(position, goal_column, &display_map, window, cx);
3707 }
3708 }
3709
3710 fn update_selection(
3711 &mut self,
3712 position: DisplayPoint,
3713 goal_column: u32,
3714 scroll_delta: gpui::Point<f32>,
3715 window: &mut Window,
3716 cx: &mut Context<Self>,
3717 ) {
3718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3719
3720 if self.columnar_selection_state.is_some() {
3721 self.select_columns(position, goal_column, &display_map, window, cx);
3722 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3723 let buffer = &display_map.buffer_snapshot;
3724 let head;
3725 let tail;
3726 let mode = self.selections.pending_mode().unwrap();
3727 match &mode {
3728 SelectMode::Character => {
3729 head = position.to_point(&display_map);
3730 tail = pending.tail().to_point(buffer);
3731 }
3732 SelectMode::Word(original_range) => {
3733 let offset = display_map
3734 .clip_point(position, Bias::Left)
3735 .to_offset(&display_map, Bias::Left);
3736 let original_range = original_range.to_offset(buffer);
3737
3738 let head_offset = if buffer.is_inside_word(offset, None)
3739 || original_range.contains(&offset)
3740 {
3741 let (word_range, _) = buffer.surrounding_word(offset, None);
3742 if word_range.start < original_range.start {
3743 word_range.start
3744 } else {
3745 word_range.end
3746 }
3747 } else {
3748 offset
3749 };
3750
3751 head = head_offset.to_point(buffer);
3752 if head_offset <= original_range.start {
3753 tail = original_range.end.to_point(buffer);
3754 } else {
3755 tail = original_range.start.to_point(buffer);
3756 }
3757 }
3758 SelectMode::Line(original_range) => {
3759 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3760
3761 let position = display_map
3762 .clip_point(position, Bias::Left)
3763 .to_point(&display_map);
3764 let line_start = display_map.prev_line_boundary(position).0;
3765 let next_line_start = buffer.clip_point(
3766 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3767 Bias::Left,
3768 );
3769
3770 if line_start < original_range.start {
3771 head = line_start
3772 } else {
3773 head = next_line_start
3774 }
3775
3776 if head <= original_range.start {
3777 tail = original_range.end;
3778 } else {
3779 tail = original_range.start;
3780 }
3781 }
3782 SelectMode::All => {
3783 return;
3784 }
3785 };
3786
3787 if head < tail {
3788 pending.start = buffer.anchor_before(head);
3789 pending.end = buffer.anchor_before(tail);
3790 pending.reversed = true;
3791 } else {
3792 pending.start = buffer.anchor_before(tail);
3793 pending.end = buffer.anchor_before(head);
3794 pending.reversed = false;
3795 }
3796
3797 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3798 s.set_pending(pending.clone(), mode);
3799 });
3800 } else {
3801 log::error!("update_selection dispatched with no pending selection");
3802 return;
3803 }
3804
3805 self.apply_scroll_delta(scroll_delta, window, cx);
3806 cx.notify();
3807 }
3808
3809 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3810 self.columnar_selection_state.take();
3811 if self.selections.pending_anchor().is_some() {
3812 let selections = self.selections.all::<usize>(cx);
3813 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3814 s.select(selections);
3815 s.clear_pending();
3816 });
3817 }
3818 }
3819
3820 fn select_columns(
3821 &mut self,
3822 head: DisplayPoint,
3823 goal_column: u32,
3824 display_map: &DisplaySnapshot,
3825 window: &mut Window,
3826 cx: &mut Context<Self>,
3827 ) {
3828 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3829 return;
3830 };
3831
3832 let tail = match columnar_state {
3833 ColumnarSelectionState::FromMouse {
3834 selection_tail,
3835 display_point,
3836 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3837 ColumnarSelectionState::FromSelection { selection_tail } => {
3838 selection_tail.to_display_point(display_map)
3839 }
3840 };
3841
3842 let start_row = cmp::min(tail.row(), head.row());
3843 let end_row = cmp::max(tail.row(), head.row());
3844 let start_column = cmp::min(tail.column(), goal_column);
3845 let end_column = cmp::max(tail.column(), goal_column);
3846 let reversed = start_column < tail.column();
3847
3848 let selection_ranges = (start_row.0..=end_row.0)
3849 .map(DisplayRow)
3850 .filter_map(|row| {
3851 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3852 || start_column <= display_map.line_len(row))
3853 && !display_map.is_block_line(row)
3854 {
3855 let start = display_map
3856 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3857 .to_point(display_map);
3858 let end = display_map
3859 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3860 .to_point(display_map);
3861 if reversed {
3862 Some(end..start)
3863 } else {
3864 Some(start..end)
3865 }
3866 } else {
3867 None
3868 }
3869 })
3870 .collect::<Vec<_>>();
3871
3872 let ranges = match columnar_state {
3873 ColumnarSelectionState::FromMouse { .. } => {
3874 let mut non_empty_ranges = selection_ranges
3875 .iter()
3876 .filter(|selection_range| selection_range.start != selection_range.end)
3877 .peekable();
3878 if non_empty_ranges.peek().is_some() {
3879 non_empty_ranges.cloned().collect()
3880 } else {
3881 selection_ranges
3882 }
3883 }
3884 _ => selection_ranges,
3885 };
3886
3887 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3888 s.select_ranges(ranges);
3889 });
3890 cx.notify();
3891 }
3892
3893 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3894 self.selections
3895 .all_adjusted(cx)
3896 .iter()
3897 .any(|selection| !selection.is_empty())
3898 }
3899
3900 pub fn has_pending_nonempty_selection(&self) -> bool {
3901 let pending_nonempty_selection = match self.selections.pending_anchor() {
3902 Some(Selection { start, end, .. }) => start != end,
3903 None => false,
3904 };
3905
3906 pending_nonempty_selection
3907 || (self.columnar_selection_state.is_some()
3908 && self.selections.disjoint_anchors().len() > 1)
3909 }
3910
3911 pub fn has_pending_selection(&self) -> bool {
3912 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3913 }
3914
3915 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3916 self.selection_mark_mode = false;
3917 self.selection_drag_state = SelectionDragState::None;
3918
3919 if self.clear_expanded_diff_hunks(cx) {
3920 cx.notify();
3921 return;
3922 }
3923 if self.dismiss_menus_and_popups(true, window, cx) {
3924 return;
3925 }
3926
3927 if self.mode.is_full()
3928 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3929 {
3930 return;
3931 }
3932
3933 cx.propagate();
3934 }
3935
3936 pub fn dismiss_menus_and_popups(
3937 &mut self,
3938 is_user_requested: bool,
3939 window: &mut Window,
3940 cx: &mut Context<Self>,
3941 ) -> bool {
3942 if self.take_rename(false, window, cx).is_some() {
3943 return true;
3944 }
3945
3946 if hide_hover(self, cx) {
3947 return true;
3948 }
3949
3950 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3951 return true;
3952 }
3953
3954 if self.hide_context_menu(window, cx).is_some() {
3955 return true;
3956 }
3957
3958 if self.mouse_context_menu.take().is_some() {
3959 return true;
3960 }
3961
3962 if is_user_requested && self.discard_edit_prediction(true, cx) {
3963 return true;
3964 }
3965
3966 if self.snippet_stack.pop().is_some() {
3967 return true;
3968 }
3969
3970 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3971 self.dismiss_diagnostics(cx);
3972 return true;
3973 }
3974
3975 false
3976 }
3977
3978 fn linked_editing_ranges_for(
3979 &self,
3980 selection: Range<text::Anchor>,
3981 cx: &App,
3982 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3983 if self.linked_edit_ranges.is_empty() {
3984 return None;
3985 }
3986 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3987 selection.end.buffer_id.and_then(|end_buffer_id| {
3988 if selection.start.buffer_id != Some(end_buffer_id) {
3989 return None;
3990 }
3991 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3992 let snapshot = buffer.read(cx).snapshot();
3993 self.linked_edit_ranges
3994 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3995 .map(|ranges| (ranges, snapshot, buffer))
3996 })?;
3997 use text::ToOffset as TO;
3998 // find offset from the start of current range to current cursor position
3999 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4000
4001 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4002 let start_difference = start_offset - start_byte_offset;
4003 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4004 let end_difference = end_offset - start_byte_offset;
4005 // Current range has associated linked ranges.
4006 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4007 for range in linked_ranges.iter() {
4008 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4009 let end_offset = start_offset + end_difference;
4010 let start_offset = start_offset + start_difference;
4011 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4012 continue;
4013 }
4014 if self.selections.disjoint_anchor_ranges().any(|s| {
4015 if s.start.buffer_id != selection.start.buffer_id
4016 || s.end.buffer_id != selection.end.buffer_id
4017 {
4018 return false;
4019 }
4020 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4021 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4022 }) {
4023 continue;
4024 }
4025 let start = buffer_snapshot.anchor_after(start_offset);
4026 let end = buffer_snapshot.anchor_after(end_offset);
4027 linked_edits
4028 .entry(buffer.clone())
4029 .or_default()
4030 .push(start..end);
4031 }
4032 Some(linked_edits)
4033 }
4034
4035 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4036 let text: Arc<str> = text.into();
4037
4038 if self.read_only(cx) {
4039 return;
4040 }
4041
4042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4043
4044 let selections = self.selections.all_adjusted(cx);
4045 let mut bracket_inserted = false;
4046 let mut edits = Vec::new();
4047 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4048 let mut new_selections = Vec::with_capacity(selections.len());
4049 let mut new_autoclose_regions = Vec::new();
4050 let snapshot = self.buffer.read(cx).read(cx);
4051 let mut clear_linked_edit_ranges = false;
4052
4053 for (selection, autoclose_region) in
4054 self.selections_with_autoclose_regions(selections, &snapshot)
4055 {
4056 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4057 // Determine if the inserted text matches the opening or closing
4058 // bracket of any of this language's bracket pairs.
4059 let mut bracket_pair = None;
4060 let mut is_bracket_pair_start = false;
4061 let mut is_bracket_pair_end = false;
4062 if !text.is_empty() {
4063 let mut bracket_pair_matching_end = None;
4064 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4065 // and they are removing the character that triggered IME popup.
4066 for (pair, enabled) in scope.brackets() {
4067 if !pair.close && !pair.surround {
4068 continue;
4069 }
4070
4071 if enabled && pair.start.ends_with(text.as_ref()) {
4072 let prefix_len = pair.start.len() - text.len();
4073 let preceding_text_matches_prefix = prefix_len == 0
4074 || (selection.start.column >= (prefix_len as u32)
4075 && snapshot.contains_str_at(
4076 Point::new(
4077 selection.start.row,
4078 selection.start.column - (prefix_len as u32),
4079 ),
4080 &pair.start[..prefix_len],
4081 ));
4082 if preceding_text_matches_prefix {
4083 bracket_pair = Some(pair.clone());
4084 is_bracket_pair_start = true;
4085 break;
4086 }
4087 }
4088 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4089 {
4090 // take first bracket pair matching end, but don't break in case a later bracket
4091 // pair matches start
4092 bracket_pair_matching_end = Some(pair.clone());
4093 }
4094 }
4095 if let Some(end) = bracket_pair_matching_end
4096 && bracket_pair.is_none()
4097 {
4098 bracket_pair = Some(end);
4099 is_bracket_pair_end = true;
4100 }
4101 }
4102
4103 if let Some(bracket_pair) = bracket_pair {
4104 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4105 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4106 let auto_surround =
4107 self.use_auto_surround && snapshot_settings.use_auto_surround;
4108 if selection.is_empty() {
4109 if is_bracket_pair_start {
4110 // If the inserted text is a suffix of an opening bracket and the
4111 // selection is preceded by the rest of the opening bracket, then
4112 // insert the closing bracket.
4113 let following_text_allows_autoclose = snapshot
4114 .chars_at(selection.start)
4115 .next()
4116 .is_none_or(|c| scope.should_autoclose_before(c));
4117
4118 let preceding_text_allows_autoclose = selection.start.column == 0
4119 || snapshot
4120 .reversed_chars_at(selection.start)
4121 .next()
4122 .is_none_or(|c| {
4123 bracket_pair.start != bracket_pair.end
4124 || !snapshot
4125 .char_classifier_at(selection.start)
4126 .is_word(c)
4127 });
4128
4129 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4130 && bracket_pair.start.len() == 1
4131 {
4132 let target = bracket_pair.start.chars().next().unwrap();
4133 let current_line_count = snapshot
4134 .reversed_chars_at(selection.start)
4135 .take_while(|&c| c != '\n')
4136 .filter(|&c| c == target)
4137 .count();
4138 current_line_count % 2 == 1
4139 } else {
4140 false
4141 };
4142
4143 if autoclose
4144 && bracket_pair.close
4145 && following_text_allows_autoclose
4146 && preceding_text_allows_autoclose
4147 && !is_closing_quote
4148 {
4149 let anchor = snapshot.anchor_before(selection.end);
4150 new_selections.push((selection.map(|_| anchor), text.len()));
4151 new_autoclose_regions.push((
4152 anchor,
4153 text.len(),
4154 selection.id,
4155 bracket_pair.clone(),
4156 ));
4157 edits.push((
4158 selection.range(),
4159 format!("{}{}", text, bracket_pair.end).into(),
4160 ));
4161 bracket_inserted = true;
4162 continue;
4163 }
4164 }
4165
4166 if let Some(region) = autoclose_region {
4167 // If the selection is followed by an auto-inserted closing bracket,
4168 // then don't insert that closing bracket again; just move the selection
4169 // past the closing bracket.
4170 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4171 && text.as_ref() == region.pair.end.as_str()
4172 && snapshot.contains_str_at(region.range.end, text.as_ref());
4173 if should_skip {
4174 let anchor = snapshot.anchor_after(selection.end);
4175 new_selections
4176 .push((selection.map(|_| anchor), region.pair.end.len()));
4177 continue;
4178 }
4179 }
4180
4181 let always_treat_brackets_as_autoclosed = snapshot
4182 .language_settings_at(selection.start, cx)
4183 .always_treat_brackets_as_autoclosed;
4184 if always_treat_brackets_as_autoclosed
4185 && is_bracket_pair_end
4186 && snapshot.contains_str_at(selection.end, text.as_ref())
4187 {
4188 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4189 // and the inserted text is a closing bracket and the selection is followed
4190 // by the closing bracket then move the selection past the closing bracket.
4191 let anchor = snapshot.anchor_after(selection.end);
4192 new_selections.push((selection.map(|_| anchor), text.len()));
4193 continue;
4194 }
4195 }
4196 // If an opening bracket is 1 character long and is typed while
4197 // text is selected, then surround that text with the bracket pair.
4198 else if auto_surround
4199 && bracket_pair.surround
4200 && is_bracket_pair_start
4201 && bracket_pair.start.chars().count() == 1
4202 {
4203 edits.push((selection.start..selection.start, text.clone()));
4204 edits.push((
4205 selection.end..selection.end,
4206 bracket_pair.end.as_str().into(),
4207 ));
4208 bracket_inserted = true;
4209 new_selections.push((
4210 Selection {
4211 id: selection.id,
4212 start: snapshot.anchor_after(selection.start),
4213 end: snapshot.anchor_before(selection.end),
4214 reversed: selection.reversed,
4215 goal: selection.goal,
4216 },
4217 0,
4218 ));
4219 continue;
4220 }
4221 }
4222 }
4223
4224 if self.auto_replace_emoji_shortcode
4225 && selection.is_empty()
4226 && text.as_ref().ends_with(':')
4227 && let Some(possible_emoji_short_code) =
4228 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4229 && !possible_emoji_short_code.is_empty()
4230 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4231 {
4232 let emoji_shortcode_start = Point::new(
4233 selection.start.row,
4234 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4235 );
4236
4237 // Remove shortcode from buffer
4238 edits.push((
4239 emoji_shortcode_start..selection.start,
4240 "".to_string().into(),
4241 ));
4242 new_selections.push((
4243 Selection {
4244 id: selection.id,
4245 start: snapshot.anchor_after(emoji_shortcode_start),
4246 end: snapshot.anchor_before(selection.start),
4247 reversed: selection.reversed,
4248 goal: selection.goal,
4249 },
4250 0,
4251 ));
4252
4253 // Insert emoji
4254 let selection_start_anchor = snapshot.anchor_after(selection.start);
4255 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4256 edits.push((selection.start..selection.end, emoji.to_string().into()));
4257
4258 continue;
4259 }
4260
4261 // If not handling any auto-close operation, then just replace the selected
4262 // text with the given input and move the selection to the end of the
4263 // newly inserted text.
4264 let anchor = snapshot.anchor_after(selection.end);
4265 if !self.linked_edit_ranges.is_empty() {
4266 let start_anchor = snapshot.anchor_before(selection.start);
4267
4268 let is_word_char = text.chars().next().is_none_or(|char| {
4269 let classifier = snapshot
4270 .char_classifier_at(start_anchor.to_offset(&snapshot))
4271 .scope_context(Some(CharScopeContext::LinkedEdit));
4272 classifier.is_word(char)
4273 });
4274
4275 if is_word_char {
4276 if let Some(ranges) = self
4277 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4278 {
4279 for (buffer, edits) in ranges {
4280 linked_edits
4281 .entry(buffer.clone())
4282 .or_default()
4283 .extend(edits.into_iter().map(|range| (range, text.clone())));
4284 }
4285 }
4286 } else {
4287 clear_linked_edit_ranges = true;
4288 }
4289 }
4290
4291 new_selections.push((selection.map(|_| anchor), 0));
4292 edits.push((selection.start..selection.end, text.clone()));
4293 }
4294
4295 drop(snapshot);
4296
4297 self.transact(window, cx, |this, window, cx| {
4298 if clear_linked_edit_ranges {
4299 this.linked_edit_ranges.clear();
4300 }
4301 let initial_buffer_versions =
4302 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4303
4304 this.buffer.update(cx, |buffer, cx| {
4305 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4306 });
4307 for (buffer, edits) in linked_edits {
4308 buffer.update(cx, |buffer, cx| {
4309 let snapshot = buffer.snapshot();
4310 let edits = edits
4311 .into_iter()
4312 .map(|(range, text)| {
4313 use text::ToPoint as TP;
4314 let end_point = TP::to_point(&range.end, &snapshot);
4315 let start_point = TP::to_point(&range.start, &snapshot);
4316 (start_point..end_point, text)
4317 })
4318 .sorted_by_key(|(range, _)| range.start);
4319 buffer.edit(edits, None, cx);
4320 })
4321 }
4322 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4323 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4324 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4325 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4326 .zip(new_selection_deltas)
4327 .map(|(selection, delta)| Selection {
4328 id: selection.id,
4329 start: selection.start + delta,
4330 end: selection.end + delta,
4331 reversed: selection.reversed,
4332 goal: SelectionGoal::None,
4333 })
4334 .collect::<Vec<_>>();
4335
4336 let mut i = 0;
4337 for (position, delta, selection_id, pair) in new_autoclose_regions {
4338 let position = position.to_offset(&map.buffer_snapshot) + delta;
4339 let start = map.buffer_snapshot.anchor_before(position);
4340 let end = map.buffer_snapshot.anchor_after(position);
4341 while let Some(existing_state) = this.autoclose_regions.get(i) {
4342 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4343 Ordering::Less => i += 1,
4344 Ordering::Greater => break,
4345 Ordering::Equal => {
4346 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4347 Ordering::Less => i += 1,
4348 Ordering::Equal => break,
4349 Ordering::Greater => break,
4350 }
4351 }
4352 }
4353 }
4354 this.autoclose_regions.insert(
4355 i,
4356 AutocloseRegion {
4357 selection_id,
4358 range: start..end,
4359 pair,
4360 },
4361 );
4362 }
4363
4364 let had_active_edit_prediction = this.has_active_edit_prediction();
4365 this.change_selections(
4366 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4367 window,
4368 cx,
4369 |s| s.select(new_selections),
4370 );
4371
4372 if !bracket_inserted
4373 && let Some(on_type_format_task) =
4374 this.trigger_on_type_formatting(text.to_string(), window, cx)
4375 {
4376 on_type_format_task.detach_and_log_err(cx);
4377 }
4378
4379 let editor_settings = EditorSettings::get_global(cx);
4380 if bracket_inserted
4381 && (editor_settings.auto_signature_help
4382 || editor_settings.show_signature_help_after_edits)
4383 {
4384 this.show_signature_help(&ShowSignatureHelp, window, cx);
4385 }
4386
4387 let trigger_in_words =
4388 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4389 if this.hard_wrap.is_some() {
4390 let latest: Range<Point> = this.selections.newest(cx).range();
4391 if latest.is_empty()
4392 && this
4393 .buffer()
4394 .read(cx)
4395 .snapshot(cx)
4396 .line_len(MultiBufferRow(latest.start.row))
4397 == latest.start.column
4398 {
4399 this.rewrap_impl(
4400 RewrapOptions {
4401 override_language_settings: true,
4402 preserve_existing_whitespace: true,
4403 },
4404 cx,
4405 )
4406 }
4407 }
4408 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4409 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4410 this.refresh_edit_prediction(true, false, window, cx);
4411 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4412 });
4413 }
4414
4415 fn find_possible_emoji_shortcode_at_position(
4416 snapshot: &MultiBufferSnapshot,
4417 position: Point,
4418 ) -> Option<String> {
4419 let mut chars = Vec::new();
4420 let mut found_colon = false;
4421 for char in snapshot.reversed_chars_at(position).take(100) {
4422 // Found a possible emoji shortcode in the middle of the buffer
4423 if found_colon {
4424 if char.is_whitespace() {
4425 chars.reverse();
4426 return Some(chars.iter().collect());
4427 }
4428 // If the previous character is not a whitespace, we are in the middle of a word
4429 // and we only want to complete the shortcode if the word is made up of other emojis
4430 let mut containing_word = String::new();
4431 for ch in snapshot
4432 .reversed_chars_at(position)
4433 .skip(chars.len() + 1)
4434 .take(100)
4435 {
4436 if ch.is_whitespace() {
4437 break;
4438 }
4439 containing_word.push(ch);
4440 }
4441 let containing_word = containing_word.chars().rev().collect::<String>();
4442 if util::word_consists_of_emojis(containing_word.as_str()) {
4443 chars.reverse();
4444 return Some(chars.iter().collect());
4445 }
4446 }
4447
4448 if char.is_whitespace() || !char.is_ascii() {
4449 return None;
4450 }
4451 if char == ':' {
4452 found_colon = true;
4453 } else {
4454 chars.push(char);
4455 }
4456 }
4457 // Found a possible emoji shortcode at the beginning of the buffer
4458 chars.reverse();
4459 Some(chars.iter().collect())
4460 }
4461
4462 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4463 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4464 self.transact(window, cx, |this, window, cx| {
4465 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4466 let selections = this.selections.all::<usize>(cx);
4467 let multi_buffer = this.buffer.read(cx);
4468 let buffer = multi_buffer.snapshot(cx);
4469 selections
4470 .iter()
4471 .map(|selection| {
4472 let start_point = selection.start.to_point(&buffer);
4473 let mut existing_indent =
4474 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4475 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4476 let start = selection.start;
4477 let end = selection.end;
4478 let selection_is_empty = start == end;
4479 let language_scope = buffer.language_scope_at(start);
4480 let (
4481 comment_delimiter,
4482 doc_delimiter,
4483 insert_extra_newline,
4484 indent_on_newline,
4485 indent_on_extra_newline,
4486 ) = if let Some(language) = &language_scope {
4487 let mut insert_extra_newline =
4488 insert_extra_newline_brackets(&buffer, start..end, language)
4489 || insert_extra_newline_tree_sitter(&buffer, start..end);
4490
4491 // Comment extension on newline is allowed only for cursor selections
4492 let comment_delimiter = maybe!({
4493 if !selection_is_empty {
4494 return None;
4495 }
4496
4497 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4498 return None;
4499 }
4500
4501 let delimiters = language.line_comment_prefixes();
4502 let max_len_of_delimiter =
4503 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4504 let (snapshot, range) =
4505 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4506
4507 let num_of_whitespaces = snapshot
4508 .chars_for_range(range.clone())
4509 .take_while(|c| c.is_whitespace())
4510 .count();
4511 let comment_candidate = snapshot
4512 .chars_for_range(range.clone())
4513 .skip(num_of_whitespaces)
4514 .take(max_len_of_delimiter)
4515 .collect::<String>();
4516 let (delimiter, trimmed_len) = delimiters
4517 .iter()
4518 .filter_map(|delimiter| {
4519 let prefix = delimiter.trim_end();
4520 if comment_candidate.starts_with(prefix) {
4521 Some((delimiter, prefix.len()))
4522 } else {
4523 None
4524 }
4525 })
4526 .max_by_key(|(_, len)| *len)?;
4527
4528 if let Some(BlockCommentConfig {
4529 start: block_start, ..
4530 }) = language.block_comment()
4531 {
4532 let block_start_trimmed = block_start.trim_end();
4533 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4534 let line_content = snapshot
4535 .chars_for_range(range)
4536 .skip(num_of_whitespaces)
4537 .take(block_start_trimmed.len())
4538 .collect::<String>();
4539
4540 if line_content.starts_with(block_start_trimmed) {
4541 return None;
4542 }
4543 }
4544 }
4545
4546 let cursor_is_placed_after_comment_marker =
4547 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4548 if cursor_is_placed_after_comment_marker {
4549 Some(delimiter.clone())
4550 } else {
4551 None
4552 }
4553 });
4554
4555 let mut indent_on_newline = IndentSize::spaces(0);
4556 let mut indent_on_extra_newline = IndentSize::spaces(0);
4557
4558 let doc_delimiter = maybe!({
4559 if !selection_is_empty {
4560 return None;
4561 }
4562
4563 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4564 return None;
4565 }
4566
4567 let BlockCommentConfig {
4568 start: start_tag,
4569 end: end_tag,
4570 prefix: delimiter,
4571 tab_size: len,
4572 } = language.documentation_comment()?;
4573 let is_within_block_comment = buffer
4574 .language_scope_at(start_point)
4575 .is_some_and(|scope| scope.override_name() == Some("comment"));
4576 if !is_within_block_comment {
4577 return None;
4578 }
4579
4580 let (snapshot, range) =
4581 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4582
4583 let num_of_whitespaces = snapshot
4584 .chars_for_range(range.clone())
4585 .take_while(|c| c.is_whitespace())
4586 .count();
4587
4588 // 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.
4589 let column = start_point.column;
4590 let cursor_is_after_start_tag = {
4591 let start_tag_len = start_tag.len();
4592 let start_tag_line = snapshot
4593 .chars_for_range(range.clone())
4594 .skip(num_of_whitespaces)
4595 .take(start_tag_len)
4596 .collect::<String>();
4597 if start_tag_line.starts_with(start_tag.as_ref()) {
4598 num_of_whitespaces + start_tag_len <= column as usize
4599 } else {
4600 false
4601 }
4602 };
4603
4604 let cursor_is_after_delimiter = {
4605 let delimiter_trim = delimiter.trim_end();
4606 let delimiter_line = snapshot
4607 .chars_for_range(range.clone())
4608 .skip(num_of_whitespaces)
4609 .take(delimiter_trim.len())
4610 .collect::<String>();
4611 if delimiter_line.starts_with(delimiter_trim) {
4612 num_of_whitespaces + delimiter_trim.len() <= column as usize
4613 } else {
4614 false
4615 }
4616 };
4617
4618 let cursor_is_before_end_tag_if_exists = {
4619 let mut char_position = 0u32;
4620 let mut end_tag_offset = None;
4621
4622 'outer: for chunk in snapshot.text_for_range(range) {
4623 if let Some(byte_pos) = chunk.find(&**end_tag) {
4624 let chars_before_match =
4625 chunk[..byte_pos].chars().count() as u32;
4626 end_tag_offset =
4627 Some(char_position + chars_before_match);
4628 break 'outer;
4629 }
4630 char_position += chunk.chars().count() as u32;
4631 }
4632
4633 if let Some(end_tag_offset) = end_tag_offset {
4634 let cursor_is_before_end_tag = column <= end_tag_offset;
4635 if cursor_is_after_start_tag {
4636 if cursor_is_before_end_tag {
4637 insert_extra_newline = true;
4638 }
4639 let cursor_is_at_start_of_end_tag =
4640 column == end_tag_offset;
4641 if cursor_is_at_start_of_end_tag {
4642 indent_on_extra_newline.len = *len;
4643 }
4644 }
4645 cursor_is_before_end_tag
4646 } else {
4647 true
4648 }
4649 };
4650
4651 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4652 && cursor_is_before_end_tag_if_exists
4653 {
4654 if cursor_is_after_start_tag {
4655 indent_on_newline.len = *len;
4656 }
4657 Some(delimiter.clone())
4658 } else {
4659 None
4660 }
4661 });
4662
4663 (
4664 comment_delimiter,
4665 doc_delimiter,
4666 insert_extra_newline,
4667 indent_on_newline,
4668 indent_on_extra_newline,
4669 )
4670 } else {
4671 (
4672 None,
4673 None,
4674 false,
4675 IndentSize::default(),
4676 IndentSize::default(),
4677 )
4678 };
4679
4680 let prevent_auto_indent = doc_delimiter.is_some();
4681 let delimiter = comment_delimiter.or(doc_delimiter);
4682
4683 let capacity_for_delimiter =
4684 delimiter.as_deref().map(str::len).unwrap_or_default();
4685 let mut new_text = String::with_capacity(
4686 1 + capacity_for_delimiter
4687 + existing_indent.len as usize
4688 + indent_on_newline.len as usize
4689 + indent_on_extra_newline.len as usize,
4690 );
4691 new_text.push('\n');
4692 new_text.extend(existing_indent.chars());
4693 new_text.extend(indent_on_newline.chars());
4694
4695 if let Some(delimiter) = &delimiter {
4696 new_text.push_str(delimiter);
4697 }
4698
4699 if insert_extra_newline {
4700 new_text.push('\n');
4701 new_text.extend(existing_indent.chars());
4702 new_text.extend(indent_on_extra_newline.chars());
4703 }
4704
4705 let anchor = buffer.anchor_after(end);
4706 let new_selection = selection.map(|_| anchor);
4707 (
4708 ((start..end, new_text), prevent_auto_indent),
4709 (insert_extra_newline, new_selection),
4710 )
4711 })
4712 .unzip()
4713 };
4714
4715 let mut auto_indent_edits = Vec::new();
4716 let mut edits = Vec::new();
4717 for (edit, prevent_auto_indent) in edits_with_flags {
4718 if prevent_auto_indent {
4719 edits.push(edit);
4720 } else {
4721 auto_indent_edits.push(edit);
4722 }
4723 }
4724 if !edits.is_empty() {
4725 this.edit(edits, cx);
4726 }
4727 if !auto_indent_edits.is_empty() {
4728 this.edit_with_autoindent(auto_indent_edits, cx);
4729 }
4730
4731 let buffer = this.buffer.read(cx).snapshot(cx);
4732 let new_selections = selection_info
4733 .into_iter()
4734 .map(|(extra_newline_inserted, new_selection)| {
4735 let mut cursor = new_selection.end.to_point(&buffer);
4736 if extra_newline_inserted {
4737 cursor.row -= 1;
4738 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4739 }
4740 new_selection.map(|_| cursor)
4741 })
4742 .collect();
4743
4744 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4745 this.refresh_edit_prediction(true, false, window, cx);
4746 });
4747 }
4748
4749 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4751
4752 let buffer = self.buffer.read(cx);
4753 let snapshot = buffer.snapshot(cx);
4754
4755 let mut edits = Vec::new();
4756 let mut rows = Vec::new();
4757
4758 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4759 let cursor = selection.head();
4760 let row = cursor.row;
4761
4762 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4763
4764 let newline = "\n".to_string();
4765 edits.push((start_of_line..start_of_line, newline));
4766
4767 rows.push(row + rows_inserted as u32);
4768 }
4769
4770 self.transact(window, cx, |editor, window, cx| {
4771 editor.edit(edits, cx);
4772
4773 editor.change_selections(Default::default(), window, cx, |s| {
4774 let mut index = 0;
4775 s.move_cursors_with(|map, _, _| {
4776 let row = rows[index];
4777 index += 1;
4778
4779 let point = Point::new(row, 0);
4780 let boundary = map.next_line_boundary(point).1;
4781 let clipped = map.clip_point(boundary, Bias::Left);
4782
4783 (clipped, SelectionGoal::None)
4784 });
4785 });
4786
4787 let mut indent_edits = Vec::new();
4788 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4789 for row in rows {
4790 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4791 for (row, indent) in indents {
4792 if indent.len == 0 {
4793 continue;
4794 }
4795
4796 let text = match indent.kind {
4797 IndentKind::Space => " ".repeat(indent.len as usize),
4798 IndentKind::Tab => "\t".repeat(indent.len as usize),
4799 };
4800 let point = Point::new(row.0, 0);
4801 indent_edits.push((point..point, text));
4802 }
4803 }
4804 editor.edit(indent_edits, cx);
4805 });
4806 }
4807
4808 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4809 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4810
4811 let buffer = self.buffer.read(cx);
4812 let snapshot = buffer.snapshot(cx);
4813
4814 let mut edits = Vec::new();
4815 let mut rows = Vec::new();
4816 let mut rows_inserted = 0;
4817
4818 for selection in self.selections.all_adjusted(cx) {
4819 let cursor = selection.head();
4820 let row = cursor.row;
4821
4822 let point = Point::new(row + 1, 0);
4823 let start_of_line = snapshot.clip_point(point, Bias::Left);
4824
4825 let newline = "\n".to_string();
4826 edits.push((start_of_line..start_of_line, newline));
4827
4828 rows_inserted += 1;
4829 rows.push(row + rows_inserted);
4830 }
4831
4832 self.transact(window, cx, |editor, window, cx| {
4833 editor.edit(edits, cx);
4834
4835 editor.change_selections(Default::default(), window, cx, |s| {
4836 let mut index = 0;
4837 s.move_cursors_with(|map, _, _| {
4838 let row = rows[index];
4839 index += 1;
4840
4841 let point = Point::new(row, 0);
4842 let boundary = map.next_line_boundary(point).1;
4843 let clipped = map.clip_point(boundary, Bias::Left);
4844
4845 (clipped, SelectionGoal::None)
4846 });
4847 });
4848
4849 let mut indent_edits = Vec::new();
4850 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4851 for row in rows {
4852 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4853 for (row, indent) in indents {
4854 if indent.len == 0 {
4855 continue;
4856 }
4857
4858 let text = match indent.kind {
4859 IndentKind::Space => " ".repeat(indent.len as usize),
4860 IndentKind::Tab => "\t".repeat(indent.len as usize),
4861 };
4862 let point = Point::new(row.0, 0);
4863 indent_edits.push((point..point, text));
4864 }
4865 }
4866 editor.edit(indent_edits, cx);
4867 });
4868 }
4869
4870 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4871 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4872 original_indent_columns: Vec::new(),
4873 });
4874 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4875 }
4876
4877 fn insert_with_autoindent_mode(
4878 &mut self,
4879 text: &str,
4880 autoindent_mode: Option<AutoindentMode>,
4881 window: &mut Window,
4882 cx: &mut Context<Self>,
4883 ) {
4884 if self.read_only(cx) {
4885 return;
4886 }
4887
4888 let text: Arc<str> = text.into();
4889 self.transact(window, cx, |this, window, cx| {
4890 let old_selections = this.selections.all_adjusted(cx);
4891 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4892 let anchors = {
4893 let snapshot = buffer.read(cx);
4894 old_selections
4895 .iter()
4896 .map(|s| {
4897 let anchor = snapshot.anchor_after(s.head());
4898 s.map(|_| anchor)
4899 })
4900 .collect::<Vec<_>>()
4901 };
4902 buffer.edit(
4903 old_selections
4904 .iter()
4905 .map(|s| (s.start..s.end, text.clone())),
4906 autoindent_mode,
4907 cx,
4908 );
4909 anchors
4910 });
4911
4912 this.change_selections(Default::default(), window, cx, |s| {
4913 s.select_anchors(selection_anchors);
4914 });
4915
4916 cx.notify();
4917 });
4918 }
4919
4920 fn trigger_completion_on_input(
4921 &mut self,
4922 text: &str,
4923 trigger_in_words: bool,
4924 window: &mut Window,
4925 cx: &mut Context<Self>,
4926 ) {
4927 let completions_source = self
4928 .context_menu
4929 .borrow()
4930 .as_ref()
4931 .and_then(|menu| match menu {
4932 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4933 CodeContextMenu::CodeActions(_) => None,
4934 });
4935
4936 match completions_source {
4937 Some(CompletionsMenuSource::Words { .. }) => {
4938 self.open_or_update_completions_menu(
4939 Some(CompletionsMenuSource::Words {
4940 ignore_threshold: false,
4941 }),
4942 None,
4943 window,
4944 cx,
4945 );
4946 }
4947 Some(CompletionsMenuSource::Normal)
4948 | Some(CompletionsMenuSource::SnippetChoices)
4949 | None
4950 if self.is_completion_trigger(
4951 text,
4952 trigger_in_words,
4953 completions_source.is_some(),
4954 cx,
4955 ) =>
4956 {
4957 self.show_completions(
4958 &ShowCompletions {
4959 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4960 },
4961 window,
4962 cx,
4963 )
4964 }
4965 _ => {
4966 self.hide_context_menu(window, cx);
4967 }
4968 }
4969 }
4970
4971 fn is_completion_trigger(
4972 &self,
4973 text: &str,
4974 trigger_in_words: bool,
4975 menu_is_open: bool,
4976 cx: &mut Context<Self>,
4977 ) -> bool {
4978 let position = self.selections.newest_anchor().head();
4979 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4980 return false;
4981 };
4982
4983 if let Some(completion_provider) = &self.completion_provider {
4984 completion_provider.is_completion_trigger(
4985 &buffer,
4986 position.text_anchor,
4987 text,
4988 trigger_in_words,
4989 menu_is_open,
4990 cx,
4991 )
4992 } else {
4993 false
4994 }
4995 }
4996
4997 /// If any empty selections is touching the start of its innermost containing autoclose
4998 /// region, expand it to select the brackets.
4999 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5000 let selections = self.selections.all::<usize>(cx);
5001 let buffer = self.buffer.read(cx).read(cx);
5002 let new_selections = self
5003 .selections_with_autoclose_regions(selections, &buffer)
5004 .map(|(mut selection, region)| {
5005 if !selection.is_empty() {
5006 return selection;
5007 }
5008
5009 if let Some(region) = region {
5010 let mut range = region.range.to_offset(&buffer);
5011 if selection.start == range.start && range.start >= region.pair.start.len() {
5012 range.start -= region.pair.start.len();
5013 if buffer.contains_str_at(range.start, ®ion.pair.start)
5014 && buffer.contains_str_at(range.end, ®ion.pair.end)
5015 {
5016 range.end += region.pair.end.len();
5017 selection.start = range.start;
5018 selection.end = range.end;
5019
5020 return selection;
5021 }
5022 }
5023 }
5024
5025 let always_treat_brackets_as_autoclosed = buffer
5026 .language_settings_at(selection.start, cx)
5027 .always_treat_brackets_as_autoclosed;
5028
5029 if !always_treat_brackets_as_autoclosed {
5030 return selection;
5031 }
5032
5033 if let Some(scope) = buffer.language_scope_at(selection.start) {
5034 for (pair, enabled) in scope.brackets() {
5035 if !enabled || !pair.close {
5036 continue;
5037 }
5038
5039 if buffer.contains_str_at(selection.start, &pair.end) {
5040 let pair_start_len = pair.start.len();
5041 if buffer.contains_str_at(
5042 selection.start.saturating_sub(pair_start_len),
5043 &pair.start,
5044 ) {
5045 selection.start -= pair_start_len;
5046 selection.end += pair.end.len();
5047
5048 return selection;
5049 }
5050 }
5051 }
5052 }
5053
5054 selection
5055 })
5056 .collect();
5057
5058 drop(buffer);
5059 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5060 selections.select(new_selections)
5061 });
5062 }
5063
5064 /// Iterate the given selections, and for each one, find the smallest surrounding
5065 /// autoclose region. This uses the ordering of the selections and the autoclose
5066 /// regions to avoid repeated comparisons.
5067 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5068 &'a self,
5069 selections: impl IntoIterator<Item = Selection<D>>,
5070 buffer: &'a MultiBufferSnapshot,
5071 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5072 let mut i = 0;
5073 let mut regions = self.autoclose_regions.as_slice();
5074 selections.into_iter().map(move |selection| {
5075 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5076
5077 let mut enclosing = None;
5078 while let Some(pair_state) = regions.get(i) {
5079 if pair_state.range.end.to_offset(buffer) < range.start {
5080 regions = ®ions[i + 1..];
5081 i = 0;
5082 } else if pair_state.range.start.to_offset(buffer) > range.end {
5083 break;
5084 } else {
5085 if pair_state.selection_id == selection.id {
5086 enclosing = Some(pair_state);
5087 }
5088 i += 1;
5089 }
5090 }
5091
5092 (selection, enclosing)
5093 })
5094 }
5095
5096 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5097 fn invalidate_autoclose_regions(
5098 &mut self,
5099 mut selections: &[Selection<Anchor>],
5100 buffer: &MultiBufferSnapshot,
5101 ) {
5102 self.autoclose_regions.retain(|state| {
5103 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5104 return false;
5105 }
5106
5107 let mut i = 0;
5108 while let Some(selection) = selections.get(i) {
5109 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5110 selections = &selections[1..];
5111 continue;
5112 }
5113 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5114 break;
5115 }
5116 if selection.id == state.selection_id {
5117 return true;
5118 } else {
5119 i += 1;
5120 }
5121 }
5122 false
5123 });
5124 }
5125
5126 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5127 let offset = position.to_offset(buffer);
5128 let (word_range, kind) =
5129 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5130 if offset > word_range.start && kind == Some(CharKind::Word) {
5131 Some(
5132 buffer
5133 .text_for_range(word_range.start..offset)
5134 .collect::<String>(),
5135 )
5136 } else {
5137 None
5138 }
5139 }
5140
5141 pub fn toggle_inline_values(
5142 &mut self,
5143 _: &ToggleInlineValues,
5144 _: &mut Window,
5145 cx: &mut Context<Self>,
5146 ) {
5147 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5148
5149 self.refresh_inline_values(cx);
5150 }
5151
5152 pub fn toggle_inlay_hints(
5153 &mut self,
5154 _: &ToggleInlayHints,
5155 _: &mut Window,
5156 cx: &mut Context<Self>,
5157 ) {
5158 self.refresh_inlay_hints(
5159 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5160 cx,
5161 );
5162 }
5163
5164 pub fn inlay_hints_enabled(&self) -> bool {
5165 self.inlay_hint_cache.enabled
5166 }
5167
5168 pub fn inline_values_enabled(&self) -> bool {
5169 self.inline_value_cache.enabled
5170 }
5171
5172 #[cfg(any(test, feature = "test-support"))]
5173 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5174 self.display_map
5175 .read(cx)
5176 .current_inlays()
5177 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5178 .cloned()
5179 .collect()
5180 }
5181
5182 #[cfg(any(test, feature = "test-support"))]
5183 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5184 self.display_map
5185 .read(cx)
5186 .current_inlays()
5187 .cloned()
5188 .collect()
5189 }
5190
5191 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5192 if self.semantics_provider.is_none() || !self.mode.is_full() {
5193 return;
5194 }
5195
5196 let reason_description = reason.description();
5197 let ignore_debounce = matches!(
5198 reason,
5199 InlayHintRefreshReason::SettingsChange(_)
5200 | InlayHintRefreshReason::Toggle(_)
5201 | InlayHintRefreshReason::ExcerptsRemoved(_)
5202 | InlayHintRefreshReason::ModifiersChanged(_)
5203 );
5204 let (invalidate_cache, required_languages) = match reason {
5205 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5206 match self.inlay_hint_cache.modifiers_override(enabled) {
5207 Some(enabled) => {
5208 if enabled {
5209 (InvalidationStrategy::RefreshRequested, None)
5210 } else {
5211 self.splice_inlays(
5212 &self
5213 .visible_inlay_hints(cx)
5214 .iter()
5215 .map(|inlay| inlay.id)
5216 .collect::<Vec<InlayId>>(),
5217 Vec::new(),
5218 cx,
5219 );
5220 return;
5221 }
5222 }
5223 None => return,
5224 }
5225 }
5226 InlayHintRefreshReason::Toggle(enabled) => {
5227 if self.inlay_hint_cache.toggle(enabled) {
5228 if enabled {
5229 (InvalidationStrategy::RefreshRequested, None)
5230 } else {
5231 self.splice_inlays(
5232 &self
5233 .visible_inlay_hints(cx)
5234 .iter()
5235 .map(|inlay| inlay.id)
5236 .collect::<Vec<InlayId>>(),
5237 Vec::new(),
5238 cx,
5239 );
5240 return;
5241 }
5242 } else {
5243 return;
5244 }
5245 }
5246 InlayHintRefreshReason::SettingsChange(new_settings) => {
5247 match self.inlay_hint_cache.update_settings(
5248 &self.buffer,
5249 new_settings,
5250 self.visible_inlay_hints(cx),
5251 cx,
5252 ) {
5253 ControlFlow::Break(Some(InlaySplice {
5254 to_remove,
5255 to_insert,
5256 })) => {
5257 self.splice_inlays(&to_remove, to_insert, cx);
5258 return;
5259 }
5260 ControlFlow::Break(None) => return,
5261 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5262 }
5263 }
5264 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5265 if let Some(InlaySplice {
5266 to_remove,
5267 to_insert,
5268 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5269 {
5270 self.splice_inlays(&to_remove, to_insert, cx);
5271 }
5272 self.display_map.update(cx, |display_map, _| {
5273 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5274 });
5275 return;
5276 }
5277 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5278 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5279 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5280 }
5281 InlayHintRefreshReason::RefreshRequested => {
5282 (InvalidationStrategy::RefreshRequested, None)
5283 }
5284 };
5285
5286 if let Some(InlaySplice {
5287 to_remove,
5288 to_insert,
5289 }) = self.inlay_hint_cache.spawn_hint_refresh(
5290 reason_description,
5291 self.visible_excerpts(required_languages.as_ref(), cx),
5292 invalidate_cache,
5293 ignore_debounce,
5294 cx,
5295 ) {
5296 self.splice_inlays(&to_remove, to_insert, cx);
5297 }
5298 }
5299
5300 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5301 self.display_map
5302 .read(cx)
5303 .current_inlays()
5304 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5305 .cloned()
5306 .collect()
5307 }
5308
5309 pub fn visible_excerpts(
5310 &self,
5311 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5312 cx: &mut Context<Editor>,
5313 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5314 let Some(project) = self.project() else {
5315 return HashMap::default();
5316 };
5317 let project = project.read(cx);
5318 let multi_buffer = self.buffer().read(cx);
5319 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5320 let multi_buffer_visible_start = self
5321 .scroll_manager
5322 .anchor()
5323 .anchor
5324 .to_point(&multi_buffer_snapshot);
5325 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5326 multi_buffer_visible_start
5327 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5328 Bias::Left,
5329 );
5330 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5331 multi_buffer_snapshot
5332 .range_to_buffer_ranges(multi_buffer_visible_range)
5333 .into_iter()
5334 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5335 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5336 let buffer_file = project::File::from_dyn(buffer.file())?;
5337 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5338 let worktree_entry = buffer_worktree
5339 .read(cx)
5340 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5341 if worktree_entry.is_ignored {
5342 return None;
5343 }
5344
5345 let language = buffer.language()?;
5346 if let Some(restrict_to_languages) = restrict_to_languages
5347 && !restrict_to_languages.contains(language)
5348 {
5349 return None;
5350 }
5351 Some((
5352 excerpt_id,
5353 (
5354 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5355 buffer.version().clone(),
5356 excerpt_visible_range,
5357 ),
5358 ))
5359 })
5360 .collect()
5361 }
5362
5363 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5364 TextLayoutDetails {
5365 text_system: window.text_system().clone(),
5366 editor_style: self.style.clone().unwrap(),
5367 rem_size: window.rem_size(),
5368 scroll_anchor: self.scroll_manager.anchor(),
5369 visible_rows: self.visible_line_count(),
5370 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5371 }
5372 }
5373
5374 pub fn splice_inlays(
5375 &self,
5376 to_remove: &[InlayId],
5377 to_insert: Vec<Inlay>,
5378 cx: &mut Context<Self>,
5379 ) {
5380 self.display_map.update(cx, |display_map, cx| {
5381 display_map.splice_inlays(to_remove, to_insert, cx)
5382 });
5383 cx.notify();
5384 }
5385
5386 fn trigger_on_type_formatting(
5387 &self,
5388 input: String,
5389 window: &mut Window,
5390 cx: &mut Context<Self>,
5391 ) -> Option<Task<Result<()>>> {
5392 if input.len() != 1 {
5393 return None;
5394 }
5395
5396 let project = self.project()?;
5397 let position = self.selections.newest_anchor().head();
5398 let (buffer, buffer_position) = self
5399 .buffer
5400 .read(cx)
5401 .text_anchor_for_position(position, cx)?;
5402
5403 let settings = language_settings::language_settings(
5404 buffer
5405 .read(cx)
5406 .language_at(buffer_position)
5407 .map(|l| l.name()),
5408 buffer.read(cx).file(),
5409 cx,
5410 );
5411 if !settings.use_on_type_format {
5412 return None;
5413 }
5414
5415 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5416 // hence we do LSP request & edit on host side only — add formats to host's history.
5417 let push_to_lsp_host_history = true;
5418 // If this is not the host, append its history with new edits.
5419 let push_to_client_history = project.read(cx).is_via_collab();
5420
5421 let on_type_formatting = project.update(cx, |project, cx| {
5422 project.on_type_format(
5423 buffer.clone(),
5424 buffer_position,
5425 input,
5426 push_to_lsp_host_history,
5427 cx,
5428 )
5429 });
5430 Some(cx.spawn_in(window, async move |editor, cx| {
5431 if let Some(transaction) = on_type_formatting.await? {
5432 if push_to_client_history {
5433 buffer
5434 .update(cx, |buffer, _| {
5435 buffer.push_transaction(transaction, Instant::now());
5436 buffer.finalize_last_transaction();
5437 })
5438 .ok();
5439 }
5440 editor.update(cx, |editor, cx| {
5441 editor.refresh_document_highlights(cx);
5442 })?;
5443 }
5444 Ok(())
5445 }))
5446 }
5447
5448 pub fn show_word_completions(
5449 &mut self,
5450 _: &ShowWordCompletions,
5451 window: &mut Window,
5452 cx: &mut Context<Self>,
5453 ) {
5454 self.open_or_update_completions_menu(
5455 Some(CompletionsMenuSource::Words {
5456 ignore_threshold: true,
5457 }),
5458 None,
5459 window,
5460 cx,
5461 );
5462 }
5463
5464 pub fn show_completions(
5465 &mut self,
5466 options: &ShowCompletions,
5467 window: &mut Window,
5468 cx: &mut Context<Self>,
5469 ) {
5470 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5471 }
5472
5473 fn open_or_update_completions_menu(
5474 &mut self,
5475 requested_source: Option<CompletionsMenuSource>,
5476 trigger: Option<&str>,
5477 window: &mut Window,
5478 cx: &mut Context<Self>,
5479 ) {
5480 if self.pending_rename.is_some() {
5481 return;
5482 }
5483
5484 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5485
5486 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5487 // inserted and selected. To handle that case, the start of the selection is used so that
5488 // the menu starts with all choices.
5489 let position = self
5490 .selections
5491 .newest_anchor()
5492 .start
5493 .bias_right(&multibuffer_snapshot);
5494 if position.diff_base_anchor.is_some() {
5495 return;
5496 }
5497 let buffer_position = multibuffer_snapshot.anchor_before(position);
5498 let Some(buffer) = buffer_position
5499 .buffer_id
5500 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5501 else {
5502 return;
5503 };
5504 let buffer_snapshot = buffer.read(cx).snapshot();
5505
5506 let query: Option<Arc<String>> =
5507 Self::completion_query(&multibuffer_snapshot, buffer_position)
5508 .map(|query| query.into());
5509
5510 drop(multibuffer_snapshot);
5511
5512 // Hide the current completions menu when query is empty. Without this, cached
5513 // completions from before the trigger char may be reused (#32774).
5514 if query.is_none() {
5515 let menu_is_open = matches!(
5516 self.context_menu.borrow().as_ref(),
5517 Some(CodeContextMenu::Completions(_))
5518 );
5519 if menu_is_open {
5520 self.hide_context_menu(window, cx);
5521 }
5522 }
5523
5524 let mut ignore_word_threshold = false;
5525 let provider = match requested_source {
5526 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5527 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5528 ignore_word_threshold = ignore_threshold;
5529 None
5530 }
5531 Some(CompletionsMenuSource::SnippetChoices) => {
5532 log::error!("bug: SnippetChoices requested_source is not handled");
5533 None
5534 }
5535 };
5536
5537 let sort_completions = provider
5538 .as_ref()
5539 .is_some_and(|provider| provider.sort_completions());
5540
5541 let filter_completions = provider
5542 .as_ref()
5543 .is_none_or(|provider| provider.filter_completions());
5544
5545 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5546 if filter_completions {
5547 menu.filter(query.clone(), provider.clone(), window, cx);
5548 }
5549 // When `is_incomplete` is false, no need to re-query completions when the current query
5550 // is a suffix of the initial query.
5551 if !menu.is_incomplete {
5552 // If the new query is a suffix of the old query (typing more characters) and
5553 // the previous result was complete, the existing completions can be filtered.
5554 //
5555 // Note that this is always true for snippet completions.
5556 let query_matches = match (&menu.initial_query, &query) {
5557 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5558 (None, _) => true,
5559 _ => false,
5560 };
5561 if query_matches {
5562 let position_matches = if menu.initial_position == position {
5563 true
5564 } else {
5565 let snapshot = self.buffer.read(cx).read(cx);
5566 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5567 };
5568 if position_matches {
5569 return;
5570 }
5571 }
5572 }
5573 };
5574
5575 let trigger_kind = match trigger {
5576 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5577 CompletionTriggerKind::TRIGGER_CHARACTER
5578 }
5579 _ => CompletionTriggerKind::INVOKED,
5580 };
5581 let completion_context = CompletionContext {
5582 trigger_character: trigger.and_then(|trigger| {
5583 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5584 Some(String::from(trigger))
5585 } else {
5586 None
5587 }
5588 }),
5589 trigger_kind,
5590 };
5591
5592 let Anchor {
5593 excerpt_id: buffer_excerpt_id,
5594 text_anchor: buffer_position,
5595 ..
5596 } = buffer_position;
5597
5598 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5599 buffer_snapshot.surrounding_word(buffer_position, None)
5600 {
5601 let word_to_exclude = buffer_snapshot
5602 .text_for_range(word_range.clone())
5603 .collect::<String>();
5604 (
5605 buffer_snapshot.anchor_before(word_range.start)
5606 ..buffer_snapshot.anchor_after(buffer_position),
5607 Some(word_to_exclude),
5608 )
5609 } else {
5610 (buffer_position..buffer_position, None)
5611 };
5612
5613 let language = buffer_snapshot
5614 .language_at(buffer_position)
5615 .map(|language| language.name());
5616
5617 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5618 .completions
5619 .clone();
5620
5621 let show_completion_documentation = buffer_snapshot
5622 .settings_at(buffer_position, cx)
5623 .show_completion_documentation;
5624
5625 // The document can be large, so stay in reasonable bounds when searching for words,
5626 // otherwise completion pop-up might be slow to appear.
5627 const WORD_LOOKUP_ROWS: u32 = 5_000;
5628 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5629 let min_word_search = buffer_snapshot.clip_point(
5630 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5631 Bias::Left,
5632 );
5633 let max_word_search = buffer_snapshot.clip_point(
5634 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5635 Bias::Right,
5636 );
5637 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5638 ..buffer_snapshot.point_to_offset(max_word_search);
5639
5640 let skip_digits = query
5641 .as_ref()
5642 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5643
5644 let omit_word_completions = !self.word_completions_enabled
5645 || (!ignore_word_threshold
5646 && match &query {
5647 Some(query) => query.chars().count() < completion_settings.words_min_length,
5648 None => completion_settings.words_min_length != 0,
5649 });
5650
5651 let (mut words, provider_responses) = match &provider {
5652 Some(provider) => {
5653 let provider_responses = provider.completions(
5654 buffer_excerpt_id,
5655 &buffer,
5656 buffer_position,
5657 completion_context,
5658 window,
5659 cx,
5660 );
5661
5662 let words = match (omit_word_completions, completion_settings.words) {
5663 (true, _) | (_, WordsCompletionMode::Disabled) => {
5664 Task::ready(BTreeMap::default())
5665 }
5666 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5667 .background_spawn(async move {
5668 buffer_snapshot.words_in_range(WordsQuery {
5669 fuzzy_contents: None,
5670 range: word_search_range,
5671 skip_digits,
5672 })
5673 }),
5674 };
5675
5676 (words, provider_responses)
5677 }
5678 None => {
5679 let words = if omit_word_completions {
5680 Task::ready(BTreeMap::default())
5681 } else {
5682 cx.background_spawn(async move {
5683 buffer_snapshot.words_in_range(WordsQuery {
5684 fuzzy_contents: None,
5685 range: word_search_range,
5686 skip_digits,
5687 })
5688 })
5689 };
5690 (words, Task::ready(Ok(Vec::new())))
5691 }
5692 };
5693
5694 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5695
5696 let id = post_inc(&mut self.next_completion_id);
5697 let task = cx.spawn_in(window, async move |editor, cx| {
5698 let Ok(()) = editor.update(cx, |this, _| {
5699 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5700 }) else {
5701 return;
5702 };
5703
5704 // TODO: Ideally completions from different sources would be selectively re-queried, so
5705 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5706 let mut completions = Vec::new();
5707 let mut is_incomplete = false;
5708 let mut display_options: Option<CompletionDisplayOptions> = None;
5709 if let Some(provider_responses) = provider_responses.await.log_err()
5710 && !provider_responses.is_empty()
5711 {
5712 for response in provider_responses {
5713 completions.extend(response.completions);
5714 is_incomplete = is_incomplete || response.is_incomplete;
5715 match display_options.as_mut() {
5716 None => {
5717 display_options = Some(response.display_options);
5718 }
5719 Some(options) => options.merge(&response.display_options),
5720 }
5721 }
5722 if completion_settings.words == WordsCompletionMode::Fallback {
5723 words = Task::ready(BTreeMap::default());
5724 }
5725 }
5726 let display_options = display_options.unwrap_or_default();
5727
5728 let mut words = words.await;
5729 if let Some(word_to_exclude) = &word_to_exclude {
5730 words.remove(word_to_exclude);
5731 }
5732 for lsp_completion in &completions {
5733 words.remove(&lsp_completion.new_text);
5734 }
5735 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5736 replace_range: word_replace_range.clone(),
5737 new_text: word.clone(),
5738 label: CodeLabel::plain(word, None),
5739 icon_path: None,
5740 documentation: None,
5741 source: CompletionSource::BufferWord {
5742 word_range,
5743 resolved: false,
5744 },
5745 insert_text_mode: Some(InsertTextMode::AS_IS),
5746 confirm: None,
5747 }));
5748
5749 let menu = if completions.is_empty() {
5750 None
5751 } else {
5752 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5753 let languages = editor
5754 .workspace
5755 .as_ref()
5756 .and_then(|(workspace, _)| workspace.upgrade())
5757 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5758 let menu = CompletionsMenu::new(
5759 id,
5760 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5761 sort_completions,
5762 show_completion_documentation,
5763 position,
5764 query.clone(),
5765 is_incomplete,
5766 buffer.clone(),
5767 completions.into(),
5768 display_options,
5769 snippet_sort_order,
5770 languages,
5771 language,
5772 cx,
5773 );
5774
5775 let query = if filter_completions { query } else { None };
5776 let matches_task = if let Some(query) = query {
5777 menu.do_async_filtering(query, cx)
5778 } else {
5779 Task::ready(menu.unfiltered_matches())
5780 };
5781 (menu, matches_task)
5782 }) else {
5783 return;
5784 };
5785
5786 let matches = matches_task.await;
5787
5788 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5789 // Newer menu already set, so exit.
5790 if let Some(CodeContextMenu::Completions(prev_menu)) =
5791 editor.context_menu.borrow().as_ref()
5792 && prev_menu.id > id
5793 {
5794 return;
5795 };
5796
5797 // Only valid to take prev_menu because it the new menu is immediately set
5798 // below, or the menu is hidden.
5799 if let Some(CodeContextMenu::Completions(prev_menu)) =
5800 editor.context_menu.borrow_mut().take()
5801 {
5802 let position_matches =
5803 if prev_menu.initial_position == menu.initial_position {
5804 true
5805 } else {
5806 let snapshot = editor.buffer.read(cx).read(cx);
5807 prev_menu.initial_position.to_offset(&snapshot)
5808 == menu.initial_position.to_offset(&snapshot)
5809 };
5810 if position_matches {
5811 // Preserve markdown cache before `set_filter_results` because it will
5812 // try to populate the documentation cache.
5813 menu.preserve_markdown_cache(prev_menu);
5814 }
5815 };
5816
5817 menu.set_filter_results(matches, provider, window, cx);
5818 }) else {
5819 return;
5820 };
5821
5822 menu.visible().then_some(menu)
5823 };
5824
5825 editor
5826 .update_in(cx, |editor, window, cx| {
5827 if editor.focus_handle.is_focused(window)
5828 && let Some(menu) = menu
5829 {
5830 *editor.context_menu.borrow_mut() =
5831 Some(CodeContextMenu::Completions(menu));
5832
5833 crate::hover_popover::hide_hover(editor, cx);
5834 if editor.show_edit_predictions_in_menu() {
5835 editor.update_visible_edit_prediction(window, cx);
5836 } else {
5837 editor.discard_edit_prediction(false, cx);
5838 }
5839
5840 cx.notify();
5841 return;
5842 }
5843
5844 if editor.completion_tasks.len() <= 1 {
5845 // If there are no more completion tasks and the last menu was empty, we should hide it.
5846 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5847 // If it was already hidden and we don't show edit predictions in the menu,
5848 // we should also show the edit prediction when available.
5849 if was_hidden && editor.show_edit_predictions_in_menu() {
5850 editor.update_visible_edit_prediction(window, cx);
5851 }
5852 }
5853 })
5854 .ok();
5855 });
5856
5857 self.completion_tasks.push((id, task));
5858 }
5859
5860 #[cfg(feature = "test-support")]
5861 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5862 let menu = self.context_menu.borrow();
5863 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5864 let completions = menu.completions.borrow();
5865 Some(completions.to_vec())
5866 } else {
5867 None
5868 }
5869 }
5870
5871 pub fn with_completions_menu_matching_id<R>(
5872 &self,
5873 id: CompletionId,
5874 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5875 ) -> R {
5876 let mut context_menu = self.context_menu.borrow_mut();
5877 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5878 return f(None);
5879 };
5880 if completions_menu.id != id {
5881 return f(None);
5882 }
5883 f(Some(completions_menu))
5884 }
5885
5886 pub fn confirm_completion(
5887 &mut self,
5888 action: &ConfirmCompletion,
5889 window: &mut Window,
5890 cx: &mut Context<Self>,
5891 ) -> Option<Task<Result<()>>> {
5892 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5893 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5894 }
5895
5896 pub fn confirm_completion_insert(
5897 &mut self,
5898 _: &ConfirmCompletionInsert,
5899 window: &mut Window,
5900 cx: &mut Context<Self>,
5901 ) -> Option<Task<Result<()>>> {
5902 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5903 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5904 }
5905
5906 pub fn confirm_completion_replace(
5907 &mut self,
5908 _: &ConfirmCompletionReplace,
5909 window: &mut Window,
5910 cx: &mut Context<Self>,
5911 ) -> Option<Task<Result<()>>> {
5912 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5913 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5914 }
5915
5916 pub fn compose_completion(
5917 &mut self,
5918 action: &ComposeCompletion,
5919 window: &mut Window,
5920 cx: &mut Context<Self>,
5921 ) -> Option<Task<Result<()>>> {
5922 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5923 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5924 }
5925
5926 fn do_completion(
5927 &mut self,
5928 item_ix: Option<usize>,
5929 intent: CompletionIntent,
5930 window: &mut Window,
5931 cx: &mut Context<Editor>,
5932 ) -> Option<Task<Result<()>>> {
5933 use language::ToOffset as _;
5934
5935 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5936 else {
5937 return None;
5938 };
5939
5940 let candidate_id = {
5941 let entries = completions_menu.entries.borrow();
5942 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5943 if self.show_edit_predictions_in_menu() {
5944 self.discard_edit_prediction(true, cx);
5945 }
5946 mat.candidate_id
5947 };
5948
5949 let completion = completions_menu
5950 .completions
5951 .borrow()
5952 .get(candidate_id)?
5953 .clone();
5954 cx.stop_propagation();
5955
5956 let buffer_handle = completions_menu.buffer.clone();
5957
5958 let CompletionEdit {
5959 new_text,
5960 snippet,
5961 replace_range,
5962 } = process_completion_for_edit(
5963 &completion,
5964 intent,
5965 &buffer_handle,
5966 &completions_menu.initial_position.text_anchor,
5967 cx,
5968 );
5969
5970 let buffer = buffer_handle.read(cx);
5971 let snapshot = self.buffer.read(cx).snapshot(cx);
5972 let newest_anchor = self.selections.newest_anchor();
5973 let replace_range_multibuffer = {
5974 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5975 let multibuffer_anchor = snapshot
5976 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5977 .unwrap()
5978 ..snapshot
5979 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5980 .unwrap();
5981 multibuffer_anchor.start.to_offset(&snapshot)
5982 ..multibuffer_anchor.end.to_offset(&snapshot)
5983 };
5984 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5985 return None;
5986 }
5987
5988 let old_text = buffer
5989 .text_for_range(replace_range.clone())
5990 .collect::<String>();
5991 let lookbehind = newest_anchor
5992 .start
5993 .text_anchor
5994 .to_offset(buffer)
5995 .saturating_sub(replace_range.start);
5996 let lookahead = replace_range
5997 .end
5998 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5999 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6000 let suffix = &old_text[lookbehind.min(old_text.len())..];
6001
6002 let selections = self.selections.all::<usize>(cx);
6003 let mut ranges = Vec::new();
6004 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6005
6006 for selection in &selections {
6007 let range = if selection.id == newest_anchor.id {
6008 replace_range_multibuffer.clone()
6009 } else {
6010 let mut range = selection.range();
6011
6012 // if prefix is present, don't duplicate it
6013 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6014 range.start = range.start.saturating_sub(lookbehind);
6015
6016 // if suffix is also present, mimic the newest cursor and replace it
6017 if selection.id != newest_anchor.id
6018 && snapshot.contains_str_at(range.end, suffix)
6019 {
6020 range.end += lookahead;
6021 }
6022 }
6023 range
6024 };
6025
6026 ranges.push(range.clone());
6027
6028 if !self.linked_edit_ranges.is_empty() {
6029 let start_anchor = snapshot.anchor_before(range.start);
6030 let end_anchor = snapshot.anchor_after(range.end);
6031 if let Some(ranges) = self
6032 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6033 {
6034 for (buffer, edits) in ranges {
6035 linked_edits
6036 .entry(buffer.clone())
6037 .or_default()
6038 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6039 }
6040 }
6041 }
6042 }
6043
6044 let common_prefix_len = old_text
6045 .chars()
6046 .zip(new_text.chars())
6047 .take_while(|(a, b)| a == b)
6048 .map(|(a, _)| a.len_utf8())
6049 .sum::<usize>();
6050
6051 cx.emit(EditorEvent::InputHandled {
6052 utf16_range_to_replace: None,
6053 text: new_text[common_prefix_len..].into(),
6054 });
6055
6056 self.transact(window, cx, |editor, window, cx| {
6057 if let Some(mut snippet) = snippet {
6058 snippet.text = new_text.to_string();
6059 editor
6060 .insert_snippet(&ranges, snippet, window, cx)
6061 .log_err();
6062 } else {
6063 editor.buffer.update(cx, |multi_buffer, cx| {
6064 let auto_indent = match completion.insert_text_mode {
6065 Some(InsertTextMode::AS_IS) => None,
6066 _ => editor.autoindent_mode.clone(),
6067 };
6068 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6069 multi_buffer.edit(edits, auto_indent, cx);
6070 });
6071 }
6072 for (buffer, edits) in linked_edits {
6073 buffer.update(cx, |buffer, cx| {
6074 let snapshot = buffer.snapshot();
6075 let edits = edits
6076 .into_iter()
6077 .map(|(range, text)| {
6078 use text::ToPoint as TP;
6079 let end_point = TP::to_point(&range.end, &snapshot);
6080 let start_point = TP::to_point(&range.start, &snapshot);
6081 (start_point..end_point, text)
6082 })
6083 .sorted_by_key(|(range, _)| range.start);
6084 buffer.edit(edits, None, cx);
6085 })
6086 }
6087
6088 editor.refresh_edit_prediction(true, false, window, cx);
6089 });
6090 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6091
6092 let show_new_completions_on_confirm = completion
6093 .confirm
6094 .as_ref()
6095 .is_some_and(|confirm| confirm(intent, window, cx));
6096 if show_new_completions_on_confirm {
6097 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6098 }
6099
6100 let provider = self.completion_provider.as_ref()?;
6101 drop(completion);
6102 let apply_edits = provider.apply_additional_edits_for_completion(
6103 buffer_handle,
6104 completions_menu.completions.clone(),
6105 candidate_id,
6106 true,
6107 cx,
6108 );
6109
6110 let editor_settings = EditorSettings::get_global(cx);
6111 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6112 // After the code completion is finished, users often want to know what signatures are needed.
6113 // so we should automatically call signature_help
6114 self.show_signature_help(&ShowSignatureHelp, window, cx);
6115 }
6116
6117 Some(cx.foreground_executor().spawn(async move {
6118 apply_edits.await?;
6119 Ok(())
6120 }))
6121 }
6122
6123 pub fn toggle_code_actions(
6124 &mut self,
6125 action: &ToggleCodeActions,
6126 window: &mut Window,
6127 cx: &mut Context<Self>,
6128 ) {
6129 let quick_launch = action.quick_launch;
6130 let mut context_menu = self.context_menu.borrow_mut();
6131 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6132 if code_actions.deployed_from == action.deployed_from {
6133 // Toggle if we're selecting the same one
6134 *context_menu = None;
6135 cx.notify();
6136 return;
6137 } else {
6138 // Otherwise, clear it and start a new one
6139 *context_menu = None;
6140 cx.notify();
6141 }
6142 }
6143 drop(context_menu);
6144 let snapshot = self.snapshot(window, cx);
6145 let deployed_from = action.deployed_from.clone();
6146 let action = action.clone();
6147 self.completion_tasks.clear();
6148 self.discard_edit_prediction(false, cx);
6149
6150 let multibuffer_point = match &action.deployed_from {
6151 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6152 DisplayPoint::new(*row, 0).to_point(&snapshot)
6153 }
6154 _ => self.selections.newest::<Point>(cx).head(),
6155 };
6156 let Some((buffer, buffer_row)) = snapshot
6157 .buffer_snapshot
6158 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6159 .and_then(|(buffer_snapshot, range)| {
6160 self.buffer()
6161 .read(cx)
6162 .buffer(buffer_snapshot.remote_id())
6163 .map(|buffer| (buffer, range.start.row))
6164 })
6165 else {
6166 return;
6167 };
6168 let buffer_id = buffer.read(cx).remote_id();
6169 let tasks = self
6170 .tasks
6171 .get(&(buffer_id, buffer_row))
6172 .map(|t| Arc::new(t.to_owned()));
6173
6174 if !self.focus_handle.is_focused(window) {
6175 return;
6176 }
6177 let project = self.project.clone();
6178
6179 let code_actions_task = match deployed_from {
6180 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6181 _ => self.code_actions(buffer_row, window, cx),
6182 };
6183
6184 let runnable_task = match deployed_from {
6185 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6186 _ => {
6187 let mut task_context_task = Task::ready(None);
6188 if let Some(tasks) = &tasks
6189 && let Some(project) = project
6190 {
6191 task_context_task =
6192 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6193 }
6194
6195 cx.spawn_in(window, {
6196 let buffer = buffer.clone();
6197 async move |editor, cx| {
6198 let task_context = task_context_task.await;
6199
6200 let resolved_tasks =
6201 tasks
6202 .zip(task_context.clone())
6203 .map(|(tasks, task_context)| ResolvedTasks {
6204 templates: tasks.resolve(&task_context).collect(),
6205 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6206 multibuffer_point.row,
6207 tasks.column,
6208 )),
6209 });
6210 let debug_scenarios = editor
6211 .update(cx, |editor, cx| {
6212 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6213 })?
6214 .await;
6215 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6216 }
6217 })
6218 }
6219 };
6220
6221 cx.spawn_in(window, async move |editor, cx| {
6222 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6223 let code_actions = code_actions_task.await;
6224 let spawn_straight_away = quick_launch
6225 && resolved_tasks
6226 .as_ref()
6227 .is_some_and(|tasks| tasks.templates.len() == 1)
6228 && code_actions
6229 .as_ref()
6230 .is_none_or(|actions| actions.is_empty())
6231 && debug_scenarios.is_empty();
6232
6233 editor.update_in(cx, |editor, window, cx| {
6234 crate::hover_popover::hide_hover(editor, cx);
6235 let actions = CodeActionContents::new(
6236 resolved_tasks,
6237 code_actions,
6238 debug_scenarios,
6239 task_context.unwrap_or_default(),
6240 );
6241
6242 // Don't show the menu if there are no actions available
6243 if actions.is_empty() {
6244 cx.notify();
6245 return Task::ready(Ok(()));
6246 }
6247
6248 *editor.context_menu.borrow_mut() =
6249 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6250 buffer,
6251 actions,
6252 selected_item: Default::default(),
6253 scroll_handle: UniformListScrollHandle::default(),
6254 deployed_from,
6255 }));
6256 cx.notify();
6257 if spawn_straight_away
6258 && let Some(task) = editor.confirm_code_action(
6259 &ConfirmCodeAction { item_ix: Some(0) },
6260 window,
6261 cx,
6262 )
6263 {
6264 return task;
6265 }
6266
6267 Task::ready(Ok(()))
6268 })
6269 })
6270 .detach_and_log_err(cx);
6271 }
6272
6273 fn debug_scenarios(
6274 &mut self,
6275 resolved_tasks: &Option<ResolvedTasks>,
6276 buffer: &Entity<Buffer>,
6277 cx: &mut App,
6278 ) -> Task<Vec<task::DebugScenario>> {
6279 maybe!({
6280 let project = self.project()?;
6281 let dap_store = project.read(cx).dap_store();
6282 let mut scenarios = vec![];
6283 let resolved_tasks = resolved_tasks.as_ref()?;
6284 let buffer = buffer.read(cx);
6285 let language = buffer.language()?;
6286 let file = buffer.file();
6287 let debug_adapter = language_settings(language.name().into(), file, cx)
6288 .debuggers
6289 .first()
6290 .map(SharedString::from)
6291 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6292
6293 dap_store.update(cx, |dap_store, cx| {
6294 for (_, task) in &resolved_tasks.templates {
6295 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6296 task.original_task().clone(),
6297 debug_adapter.clone().into(),
6298 task.display_label().to_owned().into(),
6299 cx,
6300 );
6301 scenarios.push(maybe_scenario);
6302 }
6303 });
6304 Some(cx.background_spawn(async move {
6305 futures::future::join_all(scenarios)
6306 .await
6307 .into_iter()
6308 .flatten()
6309 .collect::<Vec<_>>()
6310 }))
6311 })
6312 .unwrap_or_else(|| Task::ready(vec![]))
6313 }
6314
6315 fn code_actions(
6316 &mut self,
6317 buffer_row: u32,
6318 window: &mut Window,
6319 cx: &mut Context<Self>,
6320 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6321 let mut task = self.code_actions_task.take();
6322 cx.spawn_in(window, async move |editor, cx| {
6323 while let Some(prev_task) = task {
6324 prev_task.await.log_err();
6325 task = editor
6326 .update(cx, |this, _| this.code_actions_task.take())
6327 .ok()?;
6328 }
6329
6330 editor
6331 .update(cx, |editor, cx| {
6332 editor
6333 .available_code_actions
6334 .clone()
6335 .and_then(|(location, code_actions)| {
6336 let snapshot = location.buffer.read(cx).snapshot();
6337 let point_range = location.range.to_point(&snapshot);
6338 let point_range = point_range.start.row..=point_range.end.row;
6339 if point_range.contains(&buffer_row) {
6340 Some(code_actions)
6341 } else {
6342 None
6343 }
6344 })
6345 })
6346 .ok()
6347 .flatten()
6348 })
6349 }
6350
6351 pub fn confirm_code_action(
6352 &mut self,
6353 action: &ConfirmCodeAction,
6354 window: &mut Window,
6355 cx: &mut Context<Self>,
6356 ) -> Option<Task<Result<()>>> {
6357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6358
6359 let actions_menu =
6360 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6361 menu
6362 } else {
6363 return None;
6364 };
6365
6366 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6367 let action = actions_menu.actions.get(action_ix)?;
6368 let title = action.label();
6369 let buffer = actions_menu.buffer;
6370 let workspace = self.workspace()?;
6371
6372 match action {
6373 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6374 workspace.update(cx, |workspace, cx| {
6375 workspace.schedule_resolved_task(
6376 task_source_kind,
6377 resolved_task,
6378 false,
6379 window,
6380 cx,
6381 );
6382
6383 Some(Task::ready(Ok(())))
6384 })
6385 }
6386 CodeActionsItem::CodeAction {
6387 excerpt_id,
6388 action,
6389 provider,
6390 } => {
6391 let apply_code_action =
6392 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6393 let workspace = workspace.downgrade();
6394 Some(cx.spawn_in(window, async move |editor, cx| {
6395 let project_transaction = apply_code_action.await?;
6396 Self::open_project_transaction(
6397 &editor,
6398 workspace,
6399 project_transaction,
6400 title,
6401 cx,
6402 )
6403 .await
6404 }))
6405 }
6406 CodeActionsItem::DebugScenario(scenario) => {
6407 let context = actions_menu.actions.context;
6408
6409 workspace.update(cx, |workspace, cx| {
6410 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6411 workspace.start_debug_session(
6412 scenario,
6413 context,
6414 Some(buffer),
6415 None,
6416 window,
6417 cx,
6418 );
6419 });
6420 Some(Task::ready(Ok(())))
6421 }
6422 }
6423 }
6424
6425 pub async fn open_project_transaction(
6426 editor: &WeakEntity<Editor>,
6427 workspace: WeakEntity<Workspace>,
6428 transaction: ProjectTransaction,
6429 title: String,
6430 cx: &mut AsyncWindowContext,
6431 ) -> Result<()> {
6432 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6433 cx.update(|_, cx| {
6434 entries.sort_unstable_by_key(|(buffer, _)| {
6435 buffer.read(cx).file().map(|f| f.path().clone())
6436 });
6437 })?;
6438 if entries.is_empty() {
6439 return Ok(());
6440 }
6441
6442 // If the project transaction's edits are all contained within this editor, then
6443 // avoid opening a new editor to display them.
6444
6445 if let [(buffer, transaction)] = &*entries {
6446 let excerpt = editor.update(cx, |editor, cx| {
6447 editor
6448 .buffer()
6449 .read(cx)
6450 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6451 })?;
6452 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6453 && excerpted_buffer == *buffer
6454 {
6455 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6456 let excerpt_range = excerpt_range.to_offset(buffer);
6457 buffer
6458 .edited_ranges_for_transaction::<usize>(transaction)
6459 .all(|range| {
6460 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6461 })
6462 })?;
6463
6464 if all_edits_within_excerpt {
6465 return Ok(());
6466 }
6467 }
6468 }
6469
6470 let mut ranges_to_highlight = Vec::new();
6471 let excerpt_buffer = cx.new(|cx| {
6472 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6473 for (buffer_handle, transaction) in &entries {
6474 let edited_ranges = buffer_handle
6475 .read(cx)
6476 .edited_ranges_for_transaction::<Point>(transaction)
6477 .collect::<Vec<_>>();
6478 let (ranges, _) = multibuffer.set_excerpts_for_path(
6479 PathKey::for_buffer(buffer_handle, cx),
6480 buffer_handle.clone(),
6481 edited_ranges,
6482 multibuffer_context_lines(cx),
6483 cx,
6484 );
6485
6486 ranges_to_highlight.extend(ranges);
6487 }
6488 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6489 multibuffer
6490 })?;
6491
6492 workspace.update_in(cx, |workspace, window, cx| {
6493 let project = workspace.project().clone();
6494 let editor =
6495 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6496 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6497 editor.update(cx, |editor, cx| {
6498 editor.highlight_background::<Self>(
6499 &ranges_to_highlight,
6500 |theme| theme.colors().editor_highlighted_line_background,
6501 cx,
6502 );
6503 });
6504 })?;
6505
6506 Ok(())
6507 }
6508
6509 pub fn clear_code_action_providers(&mut self) {
6510 self.code_action_providers.clear();
6511 self.available_code_actions.take();
6512 }
6513
6514 pub fn add_code_action_provider(
6515 &mut self,
6516 provider: Rc<dyn CodeActionProvider>,
6517 window: &mut Window,
6518 cx: &mut Context<Self>,
6519 ) {
6520 if self
6521 .code_action_providers
6522 .iter()
6523 .any(|existing_provider| existing_provider.id() == provider.id())
6524 {
6525 return;
6526 }
6527
6528 self.code_action_providers.push(provider);
6529 self.refresh_code_actions(window, cx);
6530 }
6531
6532 pub fn remove_code_action_provider(
6533 &mut self,
6534 id: Arc<str>,
6535 window: &mut Window,
6536 cx: &mut Context<Self>,
6537 ) {
6538 self.code_action_providers
6539 .retain(|provider| provider.id() != id);
6540 self.refresh_code_actions(window, cx);
6541 }
6542
6543 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6544 !self.code_action_providers.is_empty()
6545 && EditorSettings::get_global(cx).toolbar.code_actions
6546 }
6547
6548 pub fn has_available_code_actions(&self) -> bool {
6549 self.available_code_actions
6550 .as_ref()
6551 .is_some_and(|(_, actions)| !actions.is_empty())
6552 }
6553
6554 fn render_inline_code_actions(
6555 &self,
6556 icon_size: ui::IconSize,
6557 display_row: DisplayRow,
6558 is_active: bool,
6559 cx: &mut Context<Self>,
6560 ) -> AnyElement {
6561 let show_tooltip = !self.context_menu_visible();
6562 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6563 .icon_size(icon_size)
6564 .shape(ui::IconButtonShape::Square)
6565 .icon_color(ui::Color::Hidden)
6566 .toggle_state(is_active)
6567 .when(show_tooltip, |this| {
6568 this.tooltip({
6569 let focus_handle = self.focus_handle.clone();
6570 move |window, cx| {
6571 Tooltip::for_action_in(
6572 "Toggle Code Actions",
6573 &ToggleCodeActions {
6574 deployed_from: None,
6575 quick_launch: false,
6576 },
6577 &focus_handle,
6578 window,
6579 cx,
6580 )
6581 }
6582 })
6583 })
6584 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6585 window.focus(&editor.focus_handle(cx));
6586 editor.toggle_code_actions(
6587 &crate::actions::ToggleCodeActions {
6588 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6589 display_row,
6590 )),
6591 quick_launch: false,
6592 },
6593 window,
6594 cx,
6595 );
6596 }))
6597 .into_any_element()
6598 }
6599
6600 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6601 &self.context_menu
6602 }
6603
6604 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6605 let newest_selection = self.selections.newest_anchor().clone();
6606 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6607 let buffer = self.buffer.read(cx);
6608 if newest_selection.head().diff_base_anchor.is_some() {
6609 return None;
6610 }
6611 let (start_buffer, start) =
6612 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6613 let (end_buffer, end) =
6614 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6615 if start_buffer != end_buffer {
6616 return None;
6617 }
6618
6619 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6620 cx.background_executor()
6621 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6622 .await;
6623
6624 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6625 let providers = this.code_action_providers.clone();
6626 let tasks = this
6627 .code_action_providers
6628 .iter()
6629 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6630 .collect::<Vec<_>>();
6631 (providers, tasks)
6632 })?;
6633
6634 let mut actions = Vec::new();
6635 for (provider, provider_actions) in
6636 providers.into_iter().zip(future::join_all(tasks).await)
6637 {
6638 if let Some(provider_actions) = provider_actions.log_err() {
6639 actions.extend(provider_actions.into_iter().map(|action| {
6640 AvailableCodeAction {
6641 excerpt_id: newest_selection.start.excerpt_id,
6642 action,
6643 provider: provider.clone(),
6644 }
6645 }));
6646 }
6647 }
6648
6649 this.update(cx, |this, cx| {
6650 this.available_code_actions = if actions.is_empty() {
6651 None
6652 } else {
6653 Some((
6654 Location {
6655 buffer: start_buffer,
6656 range: start..end,
6657 },
6658 actions.into(),
6659 ))
6660 };
6661 cx.notify();
6662 })
6663 }));
6664 None
6665 }
6666
6667 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6668 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6669 self.show_git_blame_inline = false;
6670
6671 self.show_git_blame_inline_delay_task =
6672 Some(cx.spawn_in(window, async move |this, cx| {
6673 cx.background_executor().timer(delay).await;
6674
6675 this.update(cx, |this, cx| {
6676 this.show_git_blame_inline = true;
6677 cx.notify();
6678 })
6679 .log_err();
6680 }));
6681 }
6682 }
6683
6684 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6685 let snapshot = self.snapshot(window, cx);
6686 let cursor = self.selections.newest::<Point>(cx).head();
6687 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6688 else {
6689 return;
6690 };
6691
6692 let Some(blame) = self.blame.as_ref() else {
6693 return;
6694 };
6695
6696 let row_info = RowInfo {
6697 buffer_id: Some(buffer.remote_id()),
6698 buffer_row: Some(point.row),
6699 ..Default::default()
6700 };
6701 let Some((buffer, blame_entry)) = blame
6702 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6703 .flatten()
6704 else {
6705 return;
6706 };
6707
6708 let anchor = self.selections.newest_anchor().head();
6709 let position = self.to_pixel_point(anchor, &snapshot, window);
6710 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6711 self.show_blame_popover(
6712 buffer,
6713 &blame_entry,
6714 position + last_bounds.origin,
6715 true,
6716 cx,
6717 );
6718 };
6719 }
6720
6721 fn show_blame_popover(
6722 &mut self,
6723 buffer: BufferId,
6724 blame_entry: &BlameEntry,
6725 position: gpui::Point<Pixels>,
6726 ignore_timeout: bool,
6727 cx: &mut Context<Self>,
6728 ) {
6729 if let Some(state) = &mut self.inline_blame_popover {
6730 state.hide_task.take();
6731 } else {
6732 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6733 let blame_entry = blame_entry.clone();
6734 let show_task = cx.spawn(async move |editor, cx| {
6735 if !ignore_timeout {
6736 cx.background_executor()
6737 .timer(std::time::Duration::from_millis(blame_popover_delay))
6738 .await;
6739 }
6740 editor
6741 .update(cx, |editor, cx| {
6742 editor.inline_blame_popover_show_task.take();
6743 let Some(blame) = editor.blame.as_ref() else {
6744 return;
6745 };
6746 let blame = blame.read(cx);
6747 let details = blame.details_for_entry(buffer, &blame_entry);
6748 let markdown = cx.new(|cx| {
6749 Markdown::new(
6750 details
6751 .as_ref()
6752 .map(|message| message.message.clone())
6753 .unwrap_or_default(),
6754 None,
6755 None,
6756 cx,
6757 )
6758 });
6759 editor.inline_blame_popover = Some(InlineBlamePopover {
6760 position,
6761 hide_task: None,
6762 popover_bounds: None,
6763 popover_state: InlineBlamePopoverState {
6764 scroll_handle: ScrollHandle::new(),
6765 commit_message: details,
6766 markdown,
6767 },
6768 keyboard_grace: ignore_timeout,
6769 });
6770 cx.notify();
6771 })
6772 .ok();
6773 });
6774 self.inline_blame_popover_show_task = Some(show_task);
6775 }
6776 }
6777
6778 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6779 self.inline_blame_popover_show_task.take();
6780 if let Some(state) = &mut self.inline_blame_popover {
6781 let hide_task = cx.spawn(async move |editor, cx| {
6782 cx.background_executor()
6783 .timer(std::time::Duration::from_millis(100))
6784 .await;
6785 editor
6786 .update(cx, |editor, cx| {
6787 editor.inline_blame_popover.take();
6788 cx.notify();
6789 })
6790 .ok();
6791 });
6792 state.hide_task = Some(hide_task);
6793 }
6794 }
6795
6796 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6797 if self.pending_rename.is_some() {
6798 return None;
6799 }
6800
6801 let provider = self.semantics_provider.clone()?;
6802 let buffer = self.buffer.read(cx);
6803 let newest_selection = self.selections.newest_anchor().clone();
6804 let cursor_position = newest_selection.head();
6805 let (cursor_buffer, cursor_buffer_position) =
6806 buffer.text_anchor_for_position(cursor_position, cx)?;
6807 let (tail_buffer, tail_buffer_position) =
6808 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6809 if cursor_buffer != tail_buffer {
6810 return None;
6811 }
6812
6813 let snapshot = cursor_buffer.read(cx).snapshot();
6814 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6815 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6816 if start_word_range != end_word_range {
6817 self.document_highlights_task.take();
6818 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6819 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6820 return None;
6821 }
6822
6823 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6824 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6825 cx.background_executor()
6826 .timer(Duration::from_millis(debounce))
6827 .await;
6828
6829 let highlights = if let Some(highlights) = cx
6830 .update(|cx| {
6831 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6832 })
6833 .ok()
6834 .flatten()
6835 {
6836 highlights.await.log_err()
6837 } else {
6838 None
6839 };
6840
6841 if let Some(highlights) = highlights {
6842 this.update(cx, |this, cx| {
6843 if this.pending_rename.is_some() {
6844 return;
6845 }
6846
6847 let buffer = this.buffer.read(cx);
6848 if buffer
6849 .text_anchor_for_position(cursor_position, cx)
6850 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6851 {
6852 return;
6853 }
6854
6855 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6856 let mut write_ranges = Vec::new();
6857 let mut read_ranges = Vec::new();
6858 for highlight in highlights {
6859 let buffer_id = cursor_buffer.read(cx).remote_id();
6860 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6861 {
6862 let start = highlight
6863 .range
6864 .start
6865 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6866 let end = highlight
6867 .range
6868 .end
6869 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6870 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6871 continue;
6872 }
6873
6874 let range = Anchor {
6875 buffer_id: Some(buffer_id),
6876 excerpt_id,
6877 text_anchor: start,
6878 diff_base_anchor: None,
6879 }..Anchor {
6880 buffer_id: Some(buffer_id),
6881 excerpt_id,
6882 text_anchor: end,
6883 diff_base_anchor: None,
6884 };
6885 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6886 write_ranges.push(range);
6887 } else {
6888 read_ranges.push(range);
6889 }
6890 }
6891 }
6892
6893 this.highlight_background::<DocumentHighlightRead>(
6894 &read_ranges,
6895 |theme| theme.colors().editor_document_highlight_read_background,
6896 cx,
6897 );
6898 this.highlight_background::<DocumentHighlightWrite>(
6899 &write_ranges,
6900 |theme| theme.colors().editor_document_highlight_write_background,
6901 cx,
6902 );
6903 cx.notify();
6904 })
6905 .log_err();
6906 }
6907 }));
6908 None
6909 }
6910
6911 fn prepare_highlight_query_from_selection(
6912 &mut self,
6913 cx: &mut Context<Editor>,
6914 ) -> Option<(String, Range<Anchor>)> {
6915 if matches!(self.mode, EditorMode::SingleLine) {
6916 return None;
6917 }
6918 if !EditorSettings::get_global(cx).selection_highlight {
6919 return None;
6920 }
6921 if self.selections.count() != 1 || self.selections.line_mode() {
6922 return None;
6923 }
6924 let selection = self.selections.newest::<Point>(cx);
6925 if selection.is_empty() || selection.start.row != selection.end.row {
6926 return None;
6927 }
6928 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6929 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6930 let query = multi_buffer_snapshot
6931 .text_for_range(selection_anchor_range.clone())
6932 .collect::<String>();
6933 if query.trim().is_empty() {
6934 return None;
6935 }
6936 Some((query, selection_anchor_range))
6937 }
6938
6939 fn update_selection_occurrence_highlights(
6940 &mut self,
6941 query_text: String,
6942 query_range: Range<Anchor>,
6943 multi_buffer_range_to_query: Range<Point>,
6944 use_debounce: bool,
6945 window: &mut Window,
6946 cx: &mut Context<Editor>,
6947 ) -> Task<()> {
6948 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6949 cx.spawn_in(window, async move |editor, cx| {
6950 if use_debounce {
6951 cx.background_executor()
6952 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6953 .await;
6954 }
6955 let match_task = cx.background_spawn(async move {
6956 let buffer_ranges = multi_buffer_snapshot
6957 .range_to_buffer_ranges(multi_buffer_range_to_query)
6958 .into_iter()
6959 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6960 let mut match_ranges = Vec::new();
6961 let Ok(regex) = project::search::SearchQuery::text(
6962 query_text.clone(),
6963 false,
6964 false,
6965 false,
6966 Default::default(),
6967 Default::default(),
6968 false,
6969 None,
6970 ) else {
6971 return Vec::default();
6972 };
6973 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6974 match_ranges.extend(
6975 regex
6976 .search(buffer_snapshot, Some(search_range.clone()))
6977 .await
6978 .into_iter()
6979 .filter_map(|match_range| {
6980 let match_start = buffer_snapshot
6981 .anchor_after(search_range.start + match_range.start);
6982 let match_end = buffer_snapshot
6983 .anchor_before(search_range.start + match_range.end);
6984 let match_anchor_range = Anchor::range_in_buffer(
6985 excerpt_id,
6986 buffer_snapshot.remote_id(),
6987 match_start..match_end,
6988 );
6989 (match_anchor_range != query_range).then_some(match_anchor_range)
6990 }),
6991 );
6992 }
6993 match_ranges
6994 });
6995 let match_ranges = match_task.await;
6996 editor
6997 .update_in(cx, |editor, _, cx| {
6998 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6999 if !match_ranges.is_empty() {
7000 editor.highlight_background::<SelectedTextHighlight>(
7001 &match_ranges,
7002 |theme| theme.colors().editor_document_highlight_bracket_background,
7003 cx,
7004 )
7005 }
7006 })
7007 .log_err();
7008 })
7009 }
7010
7011 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7012 struct NewlineFold;
7013 let type_id = std::any::TypeId::of::<NewlineFold>();
7014 if !self.mode.is_single_line() {
7015 return;
7016 }
7017 let snapshot = self.snapshot(window, cx);
7018 if snapshot.buffer_snapshot.max_point().row == 0 {
7019 return;
7020 }
7021 let task = cx.background_spawn(async move {
7022 let new_newlines = snapshot
7023 .buffer_chars_at(0)
7024 .filter_map(|(c, i)| {
7025 if c == '\n' {
7026 Some(
7027 snapshot.buffer_snapshot.anchor_after(i)
7028 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7029 )
7030 } else {
7031 None
7032 }
7033 })
7034 .collect::<Vec<_>>();
7035 let existing_newlines = snapshot
7036 .folds_in_range(0..snapshot.buffer_snapshot.len())
7037 .filter_map(|fold| {
7038 if fold.placeholder.type_tag == Some(type_id) {
7039 Some(fold.range.start..fold.range.end)
7040 } else {
7041 None
7042 }
7043 })
7044 .collect::<Vec<_>>();
7045
7046 (new_newlines, existing_newlines)
7047 });
7048 self.folding_newlines = cx.spawn(async move |this, cx| {
7049 let (new_newlines, existing_newlines) = task.await;
7050 if new_newlines == existing_newlines {
7051 return;
7052 }
7053 let placeholder = FoldPlaceholder {
7054 render: Arc::new(move |_, _, cx| {
7055 div()
7056 .bg(cx.theme().status().hint_background)
7057 .border_b_1()
7058 .size_full()
7059 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7060 .border_color(cx.theme().status().hint)
7061 .child("\\n")
7062 .into_any()
7063 }),
7064 constrain_width: false,
7065 merge_adjacent: false,
7066 type_tag: Some(type_id),
7067 };
7068 let creases = new_newlines
7069 .into_iter()
7070 .map(|range| Crease::simple(range, placeholder.clone()))
7071 .collect();
7072 this.update(cx, |this, cx| {
7073 this.display_map.update(cx, |display_map, cx| {
7074 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7075 display_map.fold(creases, cx);
7076 });
7077 })
7078 .ok();
7079 });
7080 }
7081
7082 fn refresh_selected_text_highlights(
7083 &mut self,
7084 on_buffer_edit: bool,
7085 window: &mut Window,
7086 cx: &mut Context<Editor>,
7087 ) {
7088 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7089 else {
7090 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7091 self.quick_selection_highlight_task.take();
7092 self.debounced_selection_highlight_task.take();
7093 return;
7094 };
7095 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7096 if on_buffer_edit
7097 || self
7098 .quick_selection_highlight_task
7099 .as_ref()
7100 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7101 {
7102 let multi_buffer_visible_start = self
7103 .scroll_manager
7104 .anchor()
7105 .anchor
7106 .to_point(&multi_buffer_snapshot);
7107 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7108 multi_buffer_visible_start
7109 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7110 Bias::Left,
7111 );
7112 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7113 self.quick_selection_highlight_task = Some((
7114 query_range.clone(),
7115 self.update_selection_occurrence_highlights(
7116 query_text.clone(),
7117 query_range.clone(),
7118 multi_buffer_visible_range,
7119 false,
7120 window,
7121 cx,
7122 ),
7123 ));
7124 }
7125 if on_buffer_edit
7126 || self
7127 .debounced_selection_highlight_task
7128 .as_ref()
7129 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7130 {
7131 let multi_buffer_start = multi_buffer_snapshot
7132 .anchor_before(0)
7133 .to_point(&multi_buffer_snapshot);
7134 let multi_buffer_end = multi_buffer_snapshot
7135 .anchor_after(multi_buffer_snapshot.len())
7136 .to_point(&multi_buffer_snapshot);
7137 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7138 self.debounced_selection_highlight_task = Some((
7139 query_range.clone(),
7140 self.update_selection_occurrence_highlights(
7141 query_text,
7142 query_range,
7143 multi_buffer_full_range,
7144 true,
7145 window,
7146 cx,
7147 ),
7148 ));
7149 }
7150 }
7151
7152 pub fn refresh_edit_prediction(
7153 &mut self,
7154 debounce: bool,
7155 user_requested: bool,
7156 window: &mut Window,
7157 cx: &mut Context<Self>,
7158 ) -> Option<()> {
7159 if DisableAiSettings::get_global(cx).disable_ai {
7160 return None;
7161 }
7162
7163 let provider = self.edit_prediction_provider()?;
7164 let cursor = self.selections.newest_anchor().head();
7165 let (buffer, cursor_buffer_position) =
7166 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7167
7168 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7169 self.discard_edit_prediction(false, cx);
7170 return None;
7171 }
7172
7173 self.update_visible_edit_prediction(window, cx);
7174
7175 if !user_requested
7176 && (!self.should_show_edit_predictions()
7177 || !self.is_focused(window)
7178 || buffer.read(cx).is_empty())
7179 {
7180 self.discard_edit_prediction(false, cx);
7181 return None;
7182 }
7183
7184 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7185 Some(())
7186 }
7187
7188 fn show_edit_predictions_in_menu(&self) -> bool {
7189 match self.edit_prediction_settings {
7190 EditPredictionSettings::Disabled => false,
7191 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7192 }
7193 }
7194
7195 pub fn edit_predictions_enabled(&self) -> bool {
7196 match self.edit_prediction_settings {
7197 EditPredictionSettings::Disabled => false,
7198 EditPredictionSettings::Enabled { .. } => true,
7199 }
7200 }
7201
7202 fn edit_prediction_requires_modifier(&self) -> bool {
7203 match self.edit_prediction_settings {
7204 EditPredictionSettings::Disabled => false,
7205 EditPredictionSettings::Enabled {
7206 preview_requires_modifier,
7207 ..
7208 } => preview_requires_modifier,
7209 }
7210 }
7211
7212 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7213 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7214 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7215 self.discard_edit_prediction(false, cx);
7216 } else {
7217 let selection = self.selections.newest_anchor();
7218 let cursor = selection.head();
7219
7220 if let Some((buffer, cursor_buffer_position)) =
7221 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7222 {
7223 self.edit_prediction_settings =
7224 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7225 }
7226 }
7227 }
7228
7229 fn edit_prediction_settings_at_position(
7230 &self,
7231 buffer: &Entity<Buffer>,
7232 buffer_position: language::Anchor,
7233 cx: &App,
7234 ) -> EditPredictionSettings {
7235 if !self.mode.is_full()
7236 || !self.show_edit_predictions_override.unwrap_or(true)
7237 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7238 {
7239 return EditPredictionSettings::Disabled;
7240 }
7241
7242 let buffer = buffer.read(cx);
7243
7244 let file = buffer.file();
7245
7246 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7247 return EditPredictionSettings::Disabled;
7248 };
7249
7250 let by_provider = matches!(
7251 self.menu_edit_predictions_policy,
7252 MenuEditPredictionsPolicy::ByProvider
7253 );
7254
7255 let show_in_menu = by_provider
7256 && self
7257 .edit_prediction_provider
7258 .as_ref()
7259 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7260
7261 let preview_requires_modifier =
7262 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7263
7264 EditPredictionSettings::Enabled {
7265 show_in_menu,
7266 preview_requires_modifier,
7267 }
7268 }
7269
7270 fn should_show_edit_predictions(&self) -> bool {
7271 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7272 }
7273
7274 pub fn edit_prediction_preview_is_active(&self) -> bool {
7275 matches!(
7276 self.edit_prediction_preview,
7277 EditPredictionPreview::Active { .. }
7278 )
7279 }
7280
7281 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7282 let cursor = self.selections.newest_anchor().head();
7283 if let Some((buffer, cursor_position)) =
7284 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7285 {
7286 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7287 } else {
7288 false
7289 }
7290 }
7291
7292 pub fn supports_minimap(&self, cx: &App) -> bool {
7293 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7294 }
7295
7296 fn edit_predictions_enabled_in_buffer(
7297 &self,
7298 buffer: &Entity<Buffer>,
7299 buffer_position: language::Anchor,
7300 cx: &App,
7301 ) -> bool {
7302 maybe!({
7303 if self.read_only(cx) {
7304 return Some(false);
7305 }
7306 let provider = self.edit_prediction_provider()?;
7307 if !provider.is_enabled(buffer, buffer_position, cx) {
7308 return Some(false);
7309 }
7310 let buffer = buffer.read(cx);
7311 let Some(file) = buffer.file() else {
7312 return Some(true);
7313 };
7314 let settings = all_language_settings(Some(file), cx);
7315 Some(settings.edit_predictions_enabled_for_file(file, cx))
7316 })
7317 .unwrap_or(false)
7318 }
7319
7320 fn cycle_edit_prediction(
7321 &mut self,
7322 direction: Direction,
7323 window: &mut Window,
7324 cx: &mut Context<Self>,
7325 ) -> Option<()> {
7326 let provider = self.edit_prediction_provider()?;
7327 let cursor = self.selections.newest_anchor().head();
7328 let (buffer, cursor_buffer_position) =
7329 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7330 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7331 return None;
7332 }
7333
7334 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7335 self.update_visible_edit_prediction(window, cx);
7336
7337 Some(())
7338 }
7339
7340 pub fn show_edit_prediction(
7341 &mut self,
7342 _: &ShowEditPrediction,
7343 window: &mut Window,
7344 cx: &mut Context<Self>,
7345 ) {
7346 if !self.has_active_edit_prediction() {
7347 self.refresh_edit_prediction(false, true, window, cx);
7348 return;
7349 }
7350
7351 self.update_visible_edit_prediction(window, cx);
7352 }
7353
7354 pub fn display_cursor_names(
7355 &mut self,
7356 _: &DisplayCursorNames,
7357 window: &mut Window,
7358 cx: &mut Context<Self>,
7359 ) {
7360 self.show_cursor_names(window, cx);
7361 }
7362
7363 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7364 self.show_cursor_names = true;
7365 cx.notify();
7366 cx.spawn_in(window, async move |this, cx| {
7367 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7368 this.update(cx, |this, cx| {
7369 this.show_cursor_names = false;
7370 cx.notify()
7371 })
7372 .ok()
7373 })
7374 .detach();
7375 }
7376
7377 pub fn next_edit_prediction(
7378 &mut self,
7379 _: &NextEditPrediction,
7380 window: &mut Window,
7381 cx: &mut Context<Self>,
7382 ) {
7383 if self.has_active_edit_prediction() {
7384 self.cycle_edit_prediction(Direction::Next, window, cx);
7385 } else {
7386 let is_copilot_disabled = self
7387 .refresh_edit_prediction(false, true, window, cx)
7388 .is_none();
7389 if is_copilot_disabled {
7390 cx.propagate();
7391 }
7392 }
7393 }
7394
7395 pub fn previous_edit_prediction(
7396 &mut self,
7397 _: &PreviousEditPrediction,
7398 window: &mut Window,
7399 cx: &mut Context<Self>,
7400 ) {
7401 if self.has_active_edit_prediction() {
7402 self.cycle_edit_prediction(Direction::Prev, window, cx);
7403 } else {
7404 let is_copilot_disabled = self
7405 .refresh_edit_prediction(false, true, window, cx)
7406 .is_none();
7407 if is_copilot_disabled {
7408 cx.propagate();
7409 }
7410 }
7411 }
7412
7413 pub fn accept_edit_prediction(
7414 &mut self,
7415 _: &AcceptEditPrediction,
7416 window: &mut Window,
7417 cx: &mut Context<Self>,
7418 ) {
7419 if self.show_edit_predictions_in_menu() {
7420 self.hide_context_menu(window, cx);
7421 }
7422
7423 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7424 return;
7425 };
7426
7427 match &active_edit_prediction.completion {
7428 EditPrediction::MoveWithin { target, .. } => {
7429 let target = *target;
7430
7431 if let Some(position_map) = &self.last_position_map {
7432 if position_map
7433 .visible_row_range
7434 .contains(&target.to_display_point(&position_map.snapshot).row())
7435 || !self.edit_prediction_requires_modifier()
7436 {
7437 self.unfold_ranges(&[target..target], true, false, cx);
7438 // Note that this is also done in vim's handler of the Tab action.
7439 self.change_selections(
7440 SelectionEffects::scroll(Autoscroll::newest()),
7441 window,
7442 cx,
7443 |selections| {
7444 selections.select_anchor_ranges([target..target]);
7445 },
7446 );
7447 self.clear_row_highlights::<EditPredictionPreview>();
7448
7449 self.edit_prediction_preview
7450 .set_previous_scroll_position(None);
7451 } else {
7452 self.edit_prediction_preview
7453 .set_previous_scroll_position(Some(
7454 position_map.snapshot.scroll_anchor,
7455 ));
7456
7457 self.highlight_rows::<EditPredictionPreview>(
7458 target..target,
7459 cx.theme().colors().editor_highlighted_line_background,
7460 RowHighlightOptions {
7461 autoscroll: true,
7462 ..Default::default()
7463 },
7464 cx,
7465 );
7466 self.request_autoscroll(Autoscroll::fit(), cx);
7467 }
7468 }
7469 }
7470 EditPrediction::MoveOutside { snapshot, target } => {
7471 if let Some(workspace) = self.workspace() {
7472 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7473 .detach_and_log_err(cx);
7474 }
7475 }
7476 EditPrediction::Edit { edits, .. } => {
7477 self.report_edit_prediction_event(
7478 active_edit_prediction.completion_id.clone(),
7479 true,
7480 cx,
7481 );
7482
7483 if let Some(provider) = self.edit_prediction_provider() {
7484 provider.accept(cx);
7485 }
7486
7487 // Store the transaction ID and selections before applying the edit
7488 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7489
7490 let snapshot = self.buffer.read(cx).snapshot(cx);
7491 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7492
7493 self.buffer.update(cx, |buffer, cx| {
7494 buffer.edit(edits.iter().cloned(), None, cx)
7495 });
7496
7497 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7498 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7499 });
7500
7501 let selections = self.selections.disjoint_anchors_arc();
7502 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7503 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7504 if has_new_transaction {
7505 self.selection_history
7506 .insert_transaction(transaction_id_now, selections);
7507 }
7508 }
7509
7510 self.update_visible_edit_prediction(window, cx);
7511 if self.active_edit_prediction.is_none() {
7512 self.refresh_edit_prediction(true, true, window, cx);
7513 }
7514
7515 cx.notify();
7516 }
7517 }
7518
7519 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7520 }
7521
7522 pub fn accept_partial_edit_prediction(
7523 &mut self,
7524 _: &AcceptPartialEditPrediction,
7525 window: &mut Window,
7526 cx: &mut Context<Self>,
7527 ) {
7528 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7529 return;
7530 };
7531 if self.selections.count() != 1 {
7532 return;
7533 }
7534
7535 match &active_edit_prediction.completion {
7536 EditPrediction::MoveWithin { target, .. } => {
7537 let target = *target;
7538 self.change_selections(
7539 SelectionEffects::scroll(Autoscroll::newest()),
7540 window,
7541 cx,
7542 |selections| {
7543 selections.select_anchor_ranges([target..target]);
7544 },
7545 );
7546 }
7547 EditPrediction::MoveOutside { snapshot, target } => {
7548 if let Some(workspace) = self.workspace() {
7549 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7550 .detach_and_log_err(cx);
7551 }
7552 }
7553 EditPrediction::Edit { edits, .. } => {
7554 self.report_edit_prediction_event(
7555 active_edit_prediction.completion_id.clone(),
7556 true,
7557 cx,
7558 );
7559
7560 // Find an insertion that starts at the cursor position.
7561 let snapshot = self.buffer.read(cx).snapshot(cx);
7562 let cursor_offset = self.selections.newest::<usize>(cx).head();
7563 let insertion = edits.iter().find_map(|(range, text)| {
7564 let range = range.to_offset(&snapshot);
7565 if range.is_empty() && range.start == cursor_offset {
7566 Some(text)
7567 } else {
7568 None
7569 }
7570 });
7571
7572 if let Some(text) = insertion {
7573 let mut partial_completion = text
7574 .chars()
7575 .by_ref()
7576 .take_while(|c| c.is_alphabetic())
7577 .collect::<String>();
7578 if partial_completion.is_empty() {
7579 partial_completion = text
7580 .chars()
7581 .by_ref()
7582 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7583 .collect::<String>();
7584 }
7585
7586 cx.emit(EditorEvent::InputHandled {
7587 utf16_range_to_replace: None,
7588 text: partial_completion.clone().into(),
7589 });
7590
7591 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7592
7593 self.refresh_edit_prediction(true, true, window, cx);
7594 cx.notify();
7595 } else {
7596 self.accept_edit_prediction(&Default::default(), window, cx);
7597 }
7598 }
7599 }
7600 }
7601
7602 fn discard_edit_prediction(
7603 &mut self,
7604 should_report_edit_prediction_event: bool,
7605 cx: &mut Context<Self>,
7606 ) -> bool {
7607 if should_report_edit_prediction_event {
7608 let completion_id = self
7609 .active_edit_prediction
7610 .as_ref()
7611 .and_then(|active_completion| active_completion.completion_id.clone());
7612
7613 self.report_edit_prediction_event(completion_id, false, cx);
7614 }
7615
7616 if let Some(provider) = self.edit_prediction_provider() {
7617 provider.discard(cx);
7618 }
7619
7620 self.take_active_edit_prediction(cx)
7621 }
7622
7623 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7624 let Some(provider) = self.edit_prediction_provider() else {
7625 return;
7626 };
7627
7628 let Some((_, buffer, _)) = self
7629 .buffer
7630 .read(cx)
7631 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7632 else {
7633 return;
7634 };
7635
7636 let extension = buffer
7637 .read(cx)
7638 .file()
7639 .and_then(|file| Some(file.path().extension()?.to_string()));
7640
7641 let event_type = match accepted {
7642 true => "Edit Prediction Accepted",
7643 false => "Edit Prediction Discarded",
7644 };
7645 telemetry::event!(
7646 event_type,
7647 provider = provider.name(),
7648 prediction_id = id,
7649 suggestion_accepted = accepted,
7650 file_extension = extension,
7651 );
7652 }
7653
7654 fn open_editor_at_anchor(
7655 snapshot: &language::BufferSnapshot,
7656 target: language::Anchor,
7657 workspace: &Entity<Workspace>,
7658 window: &mut Window,
7659 cx: &mut App,
7660 ) -> Task<Result<()>> {
7661 workspace.update(cx, |workspace, cx| {
7662 let path = snapshot.file().map(|file| file.full_path(cx));
7663 let Some(path) =
7664 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7665 else {
7666 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7667 };
7668 let target = text::ToPoint::to_point(&target, snapshot);
7669 let item = workspace.open_path(path, None, true, window, cx);
7670 window.spawn(cx, async move |cx| {
7671 let Some(editor) = item.await?.downcast::<Editor>() else {
7672 return Ok(());
7673 };
7674 editor
7675 .update_in(cx, |editor, window, cx| {
7676 editor.go_to_singleton_buffer_point(target, window, cx);
7677 })
7678 .ok();
7679 anyhow::Ok(())
7680 })
7681 })
7682 }
7683
7684 pub fn has_active_edit_prediction(&self) -> bool {
7685 self.active_edit_prediction.is_some()
7686 }
7687
7688 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7689 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7690 return false;
7691 };
7692
7693 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7694 self.clear_highlights::<EditPredictionHighlight>(cx);
7695 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7696 true
7697 }
7698
7699 /// Returns true when we're displaying the edit prediction popover below the cursor
7700 /// like we are not previewing and the LSP autocomplete menu is visible
7701 /// or we are in `when_holding_modifier` mode.
7702 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7703 if self.edit_prediction_preview_is_active()
7704 || !self.show_edit_predictions_in_menu()
7705 || !self.edit_predictions_enabled()
7706 {
7707 return false;
7708 }
7709
7710 if self.has_visible_completions_menu() {
7711 return true;
7712 }
7713
7714 has_completion && self.edit_prediction_requires_modifier()
7715 }
7716
7717 fn handle_modifiers_changed(
7718 &mut self,
7719 modifiers: Modifiers,
7720 position_map: &PositionMap,
7721 window: &mut Window,
7722 cx: &mut Context<Self>,
7723 ) {
7724 if self.show_edit_predictions_in_menu() {
7725 self.update_edit_prediction_preview(&modifiers, window, cx);
7726 }
7727
7728 self.update_selection_mode(&modifiers, position_map, window, cx);
7729
7730 let mouse_position = window.mouse_position();
7731 if !position_map.text_hitbox.is_hovered(window) {
7732 return;
7733 }
7734
7735 self.update_hovered_link(
7736 position_map.point_for_position(mouse_position),
7737 &position_map.snapshot,
7738 modifiers,
7739 window,
7740 cx,
7741 )
7742 }
7743
7744 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7745 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7746 if invert {
7747 match multi_cursor_setting {
7748 MultiCursorModifier::Alt => modifiers.alt,
7749 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7750 }
7751 } else {
7752 match multi_cursor_setting {
7753 MultiCursorModifier::Alt => modifiers.secondary(),
7754 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7755 }
7756 }
7757 }
7758
7759 fn columnar_selection_mode(
7760 modifiers: &Modifiers,
7761 cx: &mut Context<Self>,
7762 ) -> Option<ColumnarMode> {
7763 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7764 if Self::multi_cursor_modifier(false, modifiers, cx) {
7765 Some(ColumnarMode::FromMouse)
7766 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7767 Some(ColumnarMode::FromSelection)
7768 } else {
7769 None
7770 }
7771 } else {
7772 None
7773 }
7774 }
7775
7776 fn update_selection_mode(
7777 &mut self,
7778 modifiers: &Modifiers,
7779 position_map: &PositionMap,
7780 window: &mut Window,
7781 cx: &mut Context<Self>,
7782 ) {
7783 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7784 return;
7785 };
7786 if self.selections.pending_anchor().is_none() {
7787 return;
7788 }
7789
7790 let mouse_position = window.mouse_position();
7791 let point_for_position = position_map.point_for_position(mouse_position);
7792 let position = point_for_position.previous_valid;
7793
7794 self.select(
7795 SelectPhase::BeginColumnar {
7796 position,
7797 reset: false,
7798 mode,
7799 goal_column: point_for_position.exact_unclipped.column(),
7800 },
7801 window,
7802 cx,
7803 );
7804 }
7805
7806 fn update_edit_prediction_preview(
7807 &mut self,
7808 modifiers: &Modifiers,
7809 window: &mut Window,
7810 cx: &mut Context<Self>,
7811 ) {
7812 let mut modifiers_held = false;
7813 if let Some(accept_keystroke) = self
7814 .accept_edit_prediction_keybind(false, window, cx)
7815 .keystroke()
7816 {
7817 modifiers_held = modifiers_held
7818 || (accept_keystroke.modifiers() == modifiers
7819 && accept_keystroke.modifiers().modified());
7820 };
7821 if let Some(accept_partial_keystroke) = self
7822 .accept_edit_prediction_keybind(true, window, cx)
7823 .keystroke()
7824 {
7825 modifiers_held = modifiers_held
7826 || (accept_partial_keystroke.modifiers() == modifiers
7827 && accept_partial_keystroke.modifiers().modified());
7828 }
7829
7830 if modifiers_held {
7831 if matches!(
7832 self.edit_prediction_preview,
7833 EditPredictionPreview::Inactive { .. }
7834 ) {
7835 self.edit_prediction_preview = EditPredictionPreview::Active {
7836 previous_scroll_position: None,
7837 since: Instant::now(),
7838 };
7839
7840 self.update_visible_edit_prediction(window, cx);
7841 cx.notify();
7842 }
7843 } else if let EditPredictionPreview::Active {
7844 previous_scroll_position,
7845 since,
7846 } = self.edit_prediction_preview
7847 {
7848 if let (Some(previous_scroll_position), Some(position_map)) =
7849 (previous_scroll_position, self.last_position_map.as_ref())
7850 {
7851 self.set_scroll_position(
7852 previous_scroll_position
7853 .scroll_position(&position_map.snapshot.display_snapshot),
7854 window,
7855 cx,
7856 );
7857 }
7858
7859 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7860 released_too_fast: since.elapsed() < Duration::from_millis(200),
7861 };
7862 self.clear_row_highlights::<EditPredictionPreview>();
7863 self.update_visible_edit_prediction(window, cx);
7864 cx.notify();
7865 }
7866 }
7867
7868 fn update_visible_edit_prediction(
7869 &mut self,
7870 _window: &mut Window,
7871 cx: &mut Context<Self>,
7872 ) -> Option<()> {
7873 if DisableAiSettings::get_global(cx).disable_ai {
7874 return None;
7875 }
7876
7877 if self.ime_transaction.is_some() {
7878 self.discard_edit_prediction(false, cx);
7879 return None;
7880 }
7881
7882 let selection = self.selections.newest_anchor();
7883 let cursor = selection.head();
7884 let multibuffer = self.buffer.read(cx).snapshot(cx);
7885 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7886 let excerpt_id = cursor.excerpt_id;
7887
7888 let show_in_menu = self.show_edit_predictions_in_menu();
7889 let completions_menu_has_precedence = !show_in_menu
7890 && (self.context_menu.borrow().is_some()
7891 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7892
7893 if completions_menu_has_precedence
7894 || !offset_selection.is_empty()
7895 || self
7896 .active_edit_prediction
7897 .as_ref()
7898 .is_some_and(|completion| {
7899 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7900 return false;
7901 };
7902 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7903 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7904 !invalidation_range.contains(&offset_selection.head())
7905 })
7906 {
7907 self.discard_edit_prediction(false, cx);
7908 return None;
7909 }
7910
7911 self.take_active_edit_prediction(cx);
7912 let Some(provider) = self.edit_prediction_provider() else {
7913 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7914 return None;
7915 };
7916
7917 let (buffer, cursor_buffer_position) =
7918 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7919
7920 self.edit_prediction_settings =
7921 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7922
7923 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7924
7925 if self.edit_prediction_indent_conflict {
7926 let cursor_point = cursor.to_point(&multibuffer);
7927
7928 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7929
7930 if let Some((_, indent)) = indents.iter().next()
7931 && indent.len == cursor_point.column
7932 {
7933 self.edit_prediction_indent_conflict = false;
7934 }
7935 }
7936
7937 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7938
7939 let (completion_id, edits, edit_preview) = match edit_prediction {
7940 edit_prediction::EditPrediction::Local {
7941 id,
7942 edits,
7943 edit_preview,
7944 } => (id, edits, edit_preview),
7945 edit_prediction::EditPrediction::Jump {
7946 id,
7947 snapshot,
7948 target,
7949 } => {
7950 self.stale_edit_prediction_in_menu = None;
7951 self.active_edit_prediction = Some(EditPredictionState {
7952 inlay_ids: vec![],
7953 completion: EditPrediction::MoveOutside { snapshot, target },
7954 completion_id: id,
7955 invalidation_range: None,
7956 });
7957 cx.notify();
7958 return Some(());
7959 }
7960 };
7961
7962 let edits = edits
7963 .into_iter()
7964 .flat_map(|(range, new_text)| {
7965 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7966 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7967 Some((start..end, new_text))
7968 })
7969 .collect::<Vec<_>>();
7970 if edits.is_empty() {
7971 return None;
7972 }
7973
7974 let first_edit_start = edits.first().unwrap().0.start;
7975 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7976 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7977
7978 let last_edit_end = edits.last().unwrap().0.end;
7979 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7980 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7981
7982 let cursor_row = cursor.to_point(&multibuffer).row;
7983
7984 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7985
7986 let mut inlay_ids = Vec::new();
7987 let invalidation_row_range;
7988 let move_invalidation_row_range = if cursor_row < edit_start_row {
7989 Some(cursor_row..edit_end_row)
7990 } else if cursor_row > edit_end_row {
7991 Some(edit_start_row..cursor_row)
7992 } else {
7993 None
7994 };
7995 let supports_jump = self
7996 .edit_prediction_provider
7997 .as_ref()
7998 .map(|provider| provider.provider.supports_jump_to_edit())
7999 .unwrap_or(true);
8000
8001 let is_move = supports_jump
8002 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8003 let completion = if is_move {
8004 invalidation_row_range =
8005 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8006 let target = first_edit_start;
8007 EditPrediction::MoveWithin { target, snapshot }
8008 } else {
8009 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8010 && !self.edit_predictions_hidden_for_vim_mode;
8011
8012 if show_completions_in_buffer {
8013 if edits
8014 .iter()
8015 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8016 {
8017 let mut inlays = Vec::new();
8018 for (range, new_text) in &edits {
8019 let inlay = Inlay::edit_prediction(
8020 post_inc(&mut self.next_inlay_id),
8021 range.start,
8022 new_text.as_str(),
8023 );
8024 inlay_ids.push(inlay.id);
8025 inlays.push(inlay);
8026 }
8027
8028 self.splice_inlays(&[], inlays, cx);
8029 } else {
8030 let background_color = cx.theme().status().deleted_background;
8031 self.highlight_text::<EditPredictionHighlight>(
8032 edits.iter().map(|(range, _)| range.clone()).collect(),
8033 HighlightStyle {
8034 background_color: Some(background_color),
8035 ..Default::default()
8036 },
8037 cx,
8038 );
8039 }
8040 }
8041
8042 invalidation_row_range = edit_start_row..edit_end_row;
8043
8044 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8045 if provider.show_tab_accept_marker() {
8046 EditDisplayMode::TabAccept
8047 } else {
8048 EditDisplayMode::Inline
8049 }
8050 } else {
8051 EditDisplayMode::DiffPopover
8052 };
8053
8054 EditPrediction::Edit {
8055 edits,
8056 edit_preview,
8057 display_mode,
8058 snapshot,
8059 }
8060 };
8061
8062 let invalidation_range = multibuffer
8063 .anchor_before(Point::new(invalidation_row_range.start, 0))
8064 ..multibuffer.anchor_after(Point::new(
8065 invalidation_row_range.end,
8066 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8067 ));
8068
8069 self.stale_edit_prediction_in_menu = None;
8070 self.active_edit_prediction = Some(EditPredictionState {
8071 inlay_ids,
8072 completion,
8073 completion_id,
8074 invalidation_range: Some(invalidation_range),
8075 });
8076
8077 cx.notify();
8078
8079 Some(())
8080 }
8081
8082 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8083 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8084 }
8085
8086 fn clear_tasks(&mut self) {
8087 self.tasks.clear()
8088 }
8089
8090 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8091 if self.tasks.insert(key, value).is_some() {
8092 // This case should hopefully be rare, but just in case...
8093 log::error!(
8094 "multiple different run targets found on a single line, only the last target will be rendered"
8095 )
8096 }
8097 }
8098
8099 /// Get all display points of breakpoints that will be rendered within editor
8100 ///
8101 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8102 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8103 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8104 fn active_breakpoints(
8105 &self,
8106 range: Range<DisplayRow>,
8107 window: &mut Window,
8108 cx: &mut Context<Self>,
8109 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8110 let mut breakpoint_display_points = HashMap::default();
8111
8112 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8113 return breakpoint_display_points;
8114 };
8115
8116 let snapshot = self.snapshot(window, cx);
8117
8118 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8119 let Some(project) = self.project() else {
8120 return breakpoint_display_points;
8121 };
8122
8123 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8124 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8125
8126 for (buffer_snapshot, range, excerpt_id) in
8127 multi_buffer_snapshot.range_to_buffer_ranges(range)
8128 {
8129 let Some(buffer) = project
8130 .read(cx)
8131 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8132 else {
8133 continue;
8134 };
8135 let breakpoints = breakpoint_store.read(cx).breakpoints(
8136 &buffer,
8137 Some(
8138 buffer_snapshot.anchor_before(range.start)
8139 ..buffer_snapshot.anchor_after(range.end),
8140 ),
8141 buffer_snapshot,
8142 cx,
8143 );
8144 for (breakpoint, state) in breakpoints {
8145 let multi_buffer_anchor =
8146 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8147 let position = multi_buffer_anchor
8148 .to_point(multi_buffer_snapshot)
8149 .to_display_point(&snapshot);
8150
8151 breakpoint_display_points.insert(
8152 position.row(),
8153 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8154 );
8155 }
8156 }
8157
8158 breakpoint_display_points
8159 }
8160
8161 fn breakpoint_context_menu(
8162 &self,
8163 anchor: Anchor,
8164 window: &mut Window,
8165 cx: &mut Context<Self>,
8166 ) -> Entity<ui::ContextMenu> {
8167 let weak_editor = cx.weak_entity();
8168 let focus_handle = self.focus_handle(cx);
8169
8170 let row = self
8171 .buffer
8172 .read(cx)
8173 .snapshot(cx)
8174 .summary_for_anchor::<Point>(&anchor)
8175 .row;
8176
8177 let breakpoint = self
8178 .breakpoint_at_row(row, window, cx)
8179 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8180
8181 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8182 "Edit Log Breakpoint"
8183 } else {
8184 "Set Log Breakpoint"
8185 };
8186
8187 let condition_breakpoint_msg = if breakpoint
8188 .as_ref()
8189 .is_some_and(|bp| bp.1.condition.is_some())
8190 {
8191 "Edit Condition Breakpoint"
8192 } else {
8193 "Set Condition Breakpoint"
8194 };
8195
8196 let hit_condition_breakpoint_msg = if breakpoint
8197 .as_ref()
8198 .is_some_and(|bp| bp.1.hit_condition.is_some())
8199 {
8200 "Edit Hit Condition Breakpoint"
8201 } else {
8202 "Set Hit Condition Breakpoint"
8203 };
8204
8205 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8206 "Unset Breakpoint"
8207 } else {
8208 "Set Breakpoint"
8209 };
8210
8211 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8212
8213 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8214 BreakpointState::Enabled => Some("Disable"),
8215 BreakpointState::Disabled => Some("Enable"),
8216 });
8217
8218 let (anchor, breakpoint) =
8219 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8220
8221 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8222 menu.on_blur_subscription(Subscription::new(|| {}))
8223 .context(focus_handle)
8224 .when(run_to_cursor, |this| {
8225 let weak_editor = weak_editor.clone();
8226 this.entry("Run to cursor", None, move |window, cx| {
8227 weak_editor
8228 .update(cx, |editor, cx| {
8229 editor.change_selections(
8230 SelectionEffects::no_scroll(),
8231 window,
8232 cx,
8233 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8234 );
8235 })
8236 .ok();
8237
8238 window.dispatch_action(Box::new(RunToCursor), cx);
8239 })
8240 .separator()
8241 })
8242 .when_some(toggle_state_msg, |this, msg| {
8243 this.entry(msg, None, {
8244 let weak_editor = weak_editor.clone();
8245 let breakpoint = breakpoint.clone();
8246 move |_window, cx| {
8247 weak_editor
8248 .update(cx, |this, cx| {
8249 this.edit_breakpoint_at_anchor(
8250 anchor,
8251 breakpoint.as_ref().clone(),
8252 BreakpointEditAction::InvertState,
8253 cx,
8254 );
8255 })
8256 .log_err();
8257 }
8258 })
8259 })
8260 .entry(set_breakpoint_msg, None, {
8261 let weak_editor = weak_editor.clone();
8262 let breakpoint = breakpoint.clone();
8263 move |_window, cx| {
8264 weak_editor
8265 .update(cx, |this, cx| {
8266 this.edit_breakpoint_at_anchor(
8267 anchor,
8268 breakpoint.as_ref().clone(),
8269 BreakpointEditAction::Toggle,
8270 cx,
8271 );
8272 })
8273 .log_err();
8274 }
8275 })
8276 .entry(log_breakpoint_msg, None, {
8277 let breakpoint = breakpoint.clone();
8278 let weak_editor = weak_editor.clone();
8279 move |window, cx| {
8280 weak_editor
8281 .update(cx, |this, cx| {
8282 this.add_edit_breakpoint_block(
8283 anchor,
8284 breakpoint.as_ref(),
8285 BreakpointPromptEditAction::Log,
8286 window,
8287 cx,
8288 );
8289 })
8290 .log_err();
8291 }
8292 })
8293 .entry(condition_breakpoint_msg, None, {
8294 let breakpoint = breakpoint.clone();
8295 let weak_editor = weak_editor.clone();
8296 move |window, cx| {
8297 weak_editor
8298 .update(cx, |this, cx| {
8299 this.add_edit_breakpoint_block(
8300 anchor,
8301 breakpoint.as_ref(),
8302 BreakpointPromptEditAction::Condition,
8303 window,
8304 cx,
8305 );
8306 })
8307 .log_err();
8308 }
8309 })
8310 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8311 weak_editor
8312 .update(cx, |this, cx| {
8313 this.add_edit_breakpoint_block(
8314 anchor,
8315 breakpoint.as_ref(),
8316 BreakpointPromptEditAction::HitCondition,
8317 window,
8318 cx,
8319 );
8320 })
8321 .log_err();
8322 })
8323 })
8324 }
8325
8326 fn render_breakpoint(
8327 &self,
8328 position: Anchor,
8329 row: DisplayRow,
8330 breakpoint: &Breakpoint,
8331 state: Option<BreakpointSessionState>,
8332 cx: &mut Context<Self>,
8333 ) -> IconButton {
8334 let is_rejected = state.is_some_and(|s| !s.verified);
8335 // Is it a breakpoint that shows up when hovering over gutter?
8336 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8337 (false, false),
8338 |PhantomBreakpointIndicator {
8339 is_active,
8340 display_row,
8341 collides_with_existing_breakpoint,
8342 }| {
8343 (
8344 is_active && display_row == row,
8345 collides_with_existing_breakpoint,
8346 )
8347 },
8348 );
8349
8350 let (color, icon) = {
8351 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8352 (false, false) => ui::IconName::DebugBreakpoint,
8353 (true, false) => ui::IconName::DebugLogBreakpoint,
8354 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8355 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8356 };
8357
8358 let color = if is_phantom {
8359 Color::Hint
8360 } else if is_rejected {
8361 Color::Disabled
8362 } else {
8363 Color::Debugger
8364 };
8365
8366 (color, icon)
8367 };
8368
8369 let breakpoint = Arc::from(breakpoint.clone());
8370
8371 let alt_as_text = gpui::Keystroke {
8372 modifiers: Modifiers::secondary_key(),
8373 ..Default::default()
8374 };
8375 let primary_action_text = if breakpoint.is_disabled() {
8376 "Enable breakpoint"
8377 } else if is_phantom && !collides_with_existing {
8378 "Set breakpoint"
8379 } else {
8380 "Unset breakpoint"
8381 };
8382 let focus_handle = self.focus_handle.clone();
8383
8384 let meta = if is_rejected {
8385 SharedString::from("No executable code is associated with this line.")
8386 } else if collides_with_existing && !breakpoint.is_disabled() {
8387 SharedString::from(format!(
8388 "{alt_as_text}-click to disable,\nright-click for more options."
8389 ))
8390 } else {
8391 SharedString::from("Right-click for more options.")
8392 };
8393 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8394 .icon_size(IconSize::XSmall)
8395 .size(ui::ButtonSize::None)
8396 .when(is_rejected, |this| {
8397 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8398 })
8399 .icon_color(color)
8400 .style(ButtonStyle::Transparent)
8401 .on_click(cx.listener({
8402 move |editor, event: &ClickEvent, window, cx| {
8403 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8404 BreakpointEditAction::InvertState
8405 } else {
8406 BreakpointEditAction::Toggle
8407 };
8408
8409 window.focus(&editor.focus_handle(cx));
8410 editor.edit_breakpoint_at_anchor(
8411 position,
8412 breakpoint.as_ref().clone(),
8413 edit_action,
8414 cx,
8415 );
8416 }
8417 }))
8418 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8419 editor.set_breakpoint_context_menu(
8420 row,
8421 Some(position),
8422 event.position(),
8423 window,
8424 cx,
8425 );
8426 }))
8427 .tooltip(move |window, cx| {
8428 Tooltip::with_meta_in(
8429 primary_action_text,
8430 Some(&ToggleBreakpoint),
8431 meta.clone(),
8432 &focus_handle,
8433 window,
8434 cx,
8435 )
8436 })
8437 }
8438
8439 fn build_tasks_context(
8440 project: &Entity<Project>,
8441 buffer: &Entity<Buffer>,
8442 buffer_row: u32,
8443 tasks: &Arc<RunnableTasks>,
8444 cx: &mut Context<Self>,
8445 ) -> Task<Option<task::TaskContext>> {
8446 let position = Point::new(buffer_row, tasks.column);
8447 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8448 let location = Location {
8449 buffer: buffer.clone(),
8450 range: range_start..range_start,
8451 };
8452 // Fill in the environmental variables from the tree-sitter captures
8453 let mut captured_task_variables = TaskVariables::default();
8454 for (capture_name, value) in tasks.extra_variables.clone() {
8455 captured_task_variables.insert(
8456 task::VariableName::Custom(capture_name.into()),
8457 value.clone(),
8458 );
8459 }
8460 project.update(cx, |project, cx| {
8461 project.task_store().update(cx, |task_store, cx| {
8462 task_store.task_context_for_location(captured_task_variables, location, cx)
8463 })
8464 })
8465 }
8466
8467 pub fn spawn_nearest_task(
8468 &mut self,
8469 action: &SpawnNearestTask,
8470 window: &mut Window,
8471 cx: &mut Context<Self>,
8472 ) {
8473 let Some((workspace, _)) = self.workspace.clone() else {
8474 return;
8475 };
8476 let Some(project) = self.project.clone() else {
8477 return;
8478 };
8479
8480 // Try to find a closest, enclosing node using tree-sitter that has a task
8481 let Some((buffer, buffer_row, tasks)) = self
8482 .find_enclosing_node_task(cx)
8483 // Or find the task that's closest in row-distance.
8484 .or_else(|| self.find_closest_task(cx))
8485 else {
8486 return;
8487 };
8488
8489 let reveal_strategy = action.reveal;
8490 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8491 cx.spawn_in(window, async move |_, cx| {
8492 let context = task_context.await?;
8493 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8494
8495 let resolved = &mut resolved_task.resolved;
8496 resolved.reveal = reveal_strategy;
8497
8498 workspace
8499 .update_in(cx, |workspace, window, cx| {
8500 workspace.schedule_resolved_task(
8501 task_source_kind,
8502 resolved_task,
8503 false,
8504 window,
8505 cx,
8506 );
8507 })
8508 .ok()
8509 })
8510 .detach();
8511 }
8512
8513 fn find_closest_task(
8514 &mut self,
8515 cx: &mut Context<Self>,
8516 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8517 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8518
8519 let ((buffer_id, row), tasks) = self
8520 .tasks
8521 .iter()
8522 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8523
8524 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8525 let tasks = Arc::new(tasks.to_owned());
8526 Some((buffer, *row, tasks))
8527 }
8528
8529 fn find_enclosing_node_task(
8530 &mut self,
8531 cx: &mut Context<Self>,
8532 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8533 let snapshot = self.buffer.read(cx).snapshot(cx);
8534 let offset = self.selections.newest::<usize>(cx).head();
8535 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8536 let buffer_id = excerpt.buffer().remote_id();
8537
8538 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8539 let mut cursor = layer.node().walk();
8540
8541 while cursor.goto_first_child_for_byte(offset).is_some() {
8542 if cursor.node().end_byte() == offset {
8543 cursor.goto_next_sibling();
8544 }
8545 }
8546
8547 // Ascend to the smallest ancestor that contains the range and has a task.
8548 loop {
8549 let node = cursor.node();
8550 let node_range = node.byte_range();
8551 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8552
8553 // Check if this node contains our offset
8554 if node_range.start <= offset && node_range.end >= offset {
8555 // If it contains offset, check for task
8556 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8557 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8558 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8559 }
8560 }
8561
8562 if !cursor.goto_parent() {
8563 break;
8564 }
8565 }
8566 None
8567 }
8568
8569 fn render_run_indicator(
8570 &self,
8571 _style: &EditorStyle,
8572 is_active: bool,
8573 row: DisplayRow,
8574 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8575 cx: &mut Context<Self>,
8576 ) -> IconButton {
8577 let color = Color::Muted;
8578 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8579
8580 IconButton::new(
8581 ("run_indicator", row.0 as usize),
8582 ui::IconName::PlayOutlined,
8583 )
8584 .shape(ui::IconButtonShape::Square)
8585 .icon_size(IconSize::XSmall)
8586 .icon_color(color)
8587 .toggle_state(is_active)
8588 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8589 let quick_launch = match e {
8590 ClickEvent::Keyboard(_) => true,
8591 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8592 };
8593
8594 window.focus(&editor.focus_handle(cx));
8595 editor.toggle_code_actions(
8596 &ToggleCodeActions {
8597 deployed_from: Some(CodeActionSource::RunMenu(row)),
8598 quick_launch,
8599 },
8600 window,
8601 cx,
8602 );
8603 }))
8604 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8605 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8606 }))
8607 }
8608
8609 pub fn context_menu_visible(&self) -> bool {
8610 !self.edit_prediction_preview_is_active()
8611 && self
8612 .context_menu
8613 .borrow()
8614 .as_ref()
8615 .is_some_and(|menu| menu.visible())
8616 }
8617
8618 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8619 self.context_menu
8620 .borrow()
8621 .as_ref()
8622 .map(|menu| menu.origin())
8623 }
8624
8625 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8626 self.context_menu_options = Some(options);
8627 }
8628
8629 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8630 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8631
8632 fn render_edit_prediction_popover(
8633 &mut self,
8634 text_bounds: &Bounds<Pixels>,
8635 content_origin: gpui::Point<Pixels>,
8636 right_margin: Pixels,
8637 editor_snapshot: &EditorSnapshot,
8638 visible_row_range: Range<DisplayRow>,
8639 scroll_top: f32,
8640 scroll_bottom: f32,
8641 line_layouts: &[LineWithInvisibles],
8642 line_height: Pixels,
8643 scroll_pixel_position: gpui::Point<Pixels>,
8644 newest_selection_head: Option<DisplayPoint>,
8645 editor_width: Pixels,
8646 style: &EditorStyle,
8647 window: &mut Window,
8648 cx: &mut App,
8649 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8650 if self.mode().is_minimap() {
8651 return None;
8652 }
8653 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8654
8655 if self.edit_prediction_visible_in_cursor_popover(true) {
8656 return None;
8657 }
8658
8659 match &active_edit_prediction.completion {
8660 EditPrediction::MoveWithin { target, .. } => {
8661 let target_display_point = target.to_display_point(editor_snapshot);
8662
8663 if self.edit_prediction_requires_modifier() {
8664 if !self.edit_prediction_preview_is_active() {
8665 return None;
8666 }
8667
8668 self.render_edit_prediction_modifier_jump_popover(
8669 text_bounds,
8670 content_origin,
8671 visible_row_range,
8672 line_layouts,
8673 line_height,
8674 scroll_pixel_position,
8675 newest_selection_head,
8676 target_display_point,
8677 window,
8678 cx,
8679 )
8680 } else {
8681 self.render_edit_prediction_eager_jump_popover(
8682 text_bounds,
8683 content_origin,
8684 editor_snapshot,
8685 visible_row_range,
8686 scroll_top,
8687 scroll_bottom,
8688 line_height,
8689 scroll_pixel_position,
8690 target_display_point,
8691 editor_width,
8692 window,
8693 cx,
8694 )
8695 }
8696 }
8697 EditPrediction::Edit {
8698 display_mode: EditDisplayMode::Inline,
8699 ..
8700 } => None,
8701 EditPrediction::Edit {
8702 display_mode: EditDisplayMode::TabAccept,
8703 edits,
8704 ..
8705 } => {
8706 let range = &edits.first()?.0;
8707 let target_display_point = range.end.to_display_point(editor_snapshot);
8708
8709 self.render_edit_prediction_end_of_line_popover(
8710 "Accept",
8711 editor_snapshot,
8712 visible_row_range,
8713 target_display_point,
8714 line_height,
8715 scroll_pixel_position,
8716 content_origin,
8717 editor_width,
8718 window,
8719 cx,
8720 )
8721 }
8722 EditPrediction::Edit {
8723 edits,
8724 edit_preview,
8725 display_mode: EditDisplayMode::DiffPopover,
8726 snapshot,
8727 } => self.render_edit_prediction_diff_popover(
8728 text_bounds,
8729 content_origin,
8730 right_margin,
8731 editor_snapshot,
8732 visible_row_range,
8733 line_layouts,
8734 line_height,
8735 scroll_pixel_position,
8736 newest_selection_head,
8737 editor_width,
8738 style,
8739 edits,
8740 edit_preview,
8741 snapshot,
8742 window,
8743 cx,
8744 ),
8745 EditPrediction::MoveOutside { snapshot, .. } => {
8746 let file_name = snapshot
8747 .file()
8748 .map(|file| file.file_name(cx))
8749 .unwrap_or("untitled");
8750 let mut element = self
8751 .render_edit_prediction_line_popover(
8752 format!("Jump to {file_name}"),
8753 Some(IconName::ZedPredict),
8754 window,
8755 cx,
8756 )
8757 .into_any();
8758
8759 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8760 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8761 let origin_y = text_bounds.size.height - size.height - px(30.);
8762 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8763 element.prepaint_at(origin, window, cx);
8764
8765 Some((element, origin))
8766 }
8767 }
8768 }
8769
8770 fn render_edit_prediction_modifier_jump_popover(
8771 &mut self,
8772 text_bounds: &Bounds<Pixels>,
8773 content_origin: gpui::Point<Pixels>,
8774 visible_row_range: Range<DisplayRow>,
8775 line_layouts: &[LineWithInvisibles],
8776 line_height: Pixels,
8777 scroll_pixel_position: gpui::Point<Pixels>,
8778 newest_selection_head: Option<DisplayPoint>,
8779 target_display_point: DisplayPoint,
8780 window: &mut Window,
8781 cx: &mut App,
8782 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8783 let scrolled_content_origin =
8784 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8785
8786 const SCROLL_PADDING_Y: Pixels = px(12.);
8787
8788 if target_display_point.row() < visible_row_range.start {
8789 return self.render_edit_prediction_scroll_popover(
8790 |_| SCROLL_PADDING_Y,
8791 IconName::ArrowUp,
8792 visible_row_range,
8793 line_layouts,
8794 newest_selection_head,
8795 scrolled_content_origin,
8796 window,
8797 cx,
8798 );
8799 } else if target_display_point.row() >= visible_row_range.end {
8800 return self.render_edit_prediction_scroll_popover(
8801 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8802 IconName::ArrowDown,
8803 visible_row_range,
8804 line_layouts,
8805 newest_selection_head,
8806 scrolled_content_origin,
8807 window,
8808 cx,
8809 );
8810 }
8811
8812 const POLE_WIDTH: Pixels = px(2.);
8813
8814 let line_layout =
8815 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8816 let target_column = target_display_point.column() as usize;
8817
8818 let target_x = line_layout.x_for_index(target_column);
8819 let target_y =
8820 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8821
8822 let flag_on_right = target_x < text_bounds.size.width / 2.;
8823
8824 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8825 border_color.l += 0.001;
8826
8827 let mut element = v_flex()
8828 .items_end()
8829 .when(flag_on_right, |el| el.items_start())
8830 .child(if flag_on_right {
8831 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8832 .rounded_bl(px(0.))
8833 .rounded_tl(px(0.))
8834 .border_l_2()
8835 .border_color(border_color)
8836 } else {
8837 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8838 .rounded_br(px(0.))
8839 .rounded_tr(px(0.))
8840 .border_r_2()
8841 .border_color(border_color)
8842 })
8843 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8844 .into_any();
8845
8846 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8847
8848 let mut origin = scrolled_content_origin + point(target_x, target_y)
8849 - point(
8850 if flag_on_right {
8851 POLE_WIDTH
8852 } else {
8853 size.width - POLE_WIDTH
8854 },
8855 size.height - line_height,
8856 );
8857
8858 origin.x = origin.x.max(content_origin.x);
8859
8860 element.prepaint_at(origin, window, cx);
8861
8862 Some((element, origin))
8863 }
8864
8865 fn render_edit_prediction_scroll_popover(
8866 &mut self,
8867 to_y: impl Fn(Size<Pixels>) -> Pixels,
8868 scroll_icon: IconName,
8869 visible_row_range: Range<DisplayRow>,
8870 line_layouts: &[LineWithInvisibles],
8871 newest_selection_head: Option<DisplayPoint>,
8872 scrolled_content_origin: gpui::Point<Pixels>,
8873 window: &mut Window,
8874 cx: &mut App,
8875 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8876 let mut element = self
8877 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8878 .into_any();
8879
8880 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8881
8882 let cursor = newest_selection_head?;
8883 let cursor_row_layout =
8884 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8885 let cursor_column = cursor.column() as usize;
8886
8887 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8888
8889 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8890
8891 element.prepaint_at(origin, window, cx);
8892 Some((element, origin))
8893 }
8894
8895 fn render_edit_prediction_eager_jump_popover(
8896 &mut self,
8897 text_bounds: &Bounds<Pixels>,
8898 content_origin: gpui::Point<Pixels>,
8899 editor_snapshot: &EditorSnapshot,
8900 visible_row_range: Range<DisplayRow>,
8901 scroll_top: f32,
8902 scroll_bottom: f32,
8903 line_height: Pixels,
8904 scroll_pixel_position: gpui::Point<Pixels>,
8905 target_display_point: DisplayPoint,
8906 editor_width: Pixels,
8907 window: &mut Window,
8908 cx: &mut App,
8909 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8910 if target_display_point.row().as_f32() < scroll_top {
8911 let mut element = self
8912 .render_edit_prediction_line_popover(
8913 "Jump to Edit",
8914 Some(IconName::ArrowUp),
8915 window,
8916 cx,
8917 )
8918 .into_any();
8919
8920 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8921 let offset = point(
8922 (text_bounds.size.width - size.width) / 2.,
8923 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8924 );
8925
8926 let origin = text_bounds.origin + offset;
8927 element.prepaint_at(origin, window, cx);
8928 Some((element, origin))
8929 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8930 let mut element = self
8931 .render_edit_prediction_line_popover(
8932 "Jump to Edit",
8933 Some(IconName::ArrowDown),
8934 window,
8935 cx,
8936 )
8937 .into_any();
8938
8939 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8940 let offset = point(
8941 (text_bounds.size.width - size.width) / 2.,
8942 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8943 );
8944
8945 let origin = text_bounds.origin + offset;
8946 element.prepaint_at(origin, window, cx);
8947 Some((element, origin))
8948 } else {
8949 self.render_edit_prediction_end_of_line_popover(
8950 "Jump to Edit",
8951 editor_snapshot,
8952 visible_row_range,
8953 target_display_point,
8954 line_height,
8955 scroll_pixel_position,
8956 content_origin,
8957 editor_width,
8958 window,
8959 cx,
8960 )
8961 }
8962 }
8963
8964 fn render_edit_prediction_end_of_line_popover(
8965 self: &mut Editor,
8966 label: &'static str,
8967 editor_snapshot: &EditorSnapshot,
8968 visible_row_range: Range<DisplayRow>,
8969 target_display_point: DisplayPoint,
8970 line_height: Pixels,
8971 scroll_pixel_position: gpui::Point<Pixels>,
8972 content_origin: gpui::Point<Pixels>,
8973 editor_width: Pixels,
8974 window: &mut Window,
8975 cx: &mut App,
8976 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8977 let target_line_end = DisplayPoint::new(
8978 target_display_point.row(),
8979 editor_snapshot.line_len(target_display_point.row()),
8980 );
8981
8982 let mut element = self
8983 .render_edit_prediction_line_popover(label, None, window, cx)
8984 .into_any();
8985
8986 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8987
8988 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8989
8990 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8991 let mut origin = start_point
8992 + line_origin
8993 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8994 origin.x = origin.x.max(content_origin.x);
8995
8996 let max_x = content_origin.x + editor_width - size.width;
8997
8998 if origin.x > max_x {
8999 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9000
9001 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9002 origin.y += offset;
9003 IconName::ArrowUp
9004 } else {
9005 origin.y -= offset;
9006 IconName::ArrowDown
9007 };
9008
9009 element = self
9010 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9011 .into_any();
9012
9013 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9014
9015 origin.x = content_origin.x + editor_width - size.width - px(2.);
9016 }
9017
9018 element.prepaint_at(origin, window, cx);
9019 Some((element, origin))
9020 }
9021
9022 fn render_edit_prediction_diff_popover(
9023 self: &Editor,
9024 text_bounds: &Bounds<Pixels>,
9025 content_origin: gpui::Point<Pixels>,
9026 right_margin: Pixels,
9027 editor_snapshot: &EditorSnapshot,
9028 visible_row_range: Range<DisplayRow>,
9029 line_layouts: &[LineWithInvisibles],
9030 line_height: Pixels,
9031 scroll_pixel_position: gpui::Point<Pixels>,
9032 newest_selection_head: Option<DisplayPoint>,
9033 editor_width: Pixels,
9034 style: &EditorStyle,
9035 edits: &Vec<(Range<Anchor>, String)>,
9036 edit_preview: &Option<language::EditPreview>,
9037 snapshot: &language::BufferSnapshot,
9038 window: &mut Window,
9039 cx: &mut App,
9040 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9041 let edit_start = edits
9042 .first()
9043 .unwrap()
9044 .0
9045 .start
9046 .to_display_point(editor_snapshot);
9047 let edit_end = edits
9048 .last()
9049 .unwrap()
9050 .0
9051 .end
9052 .to_display_point(editor_snapshot);
9053
9054 let is_visible = visible_row_range.contains(&edit_start.row())
9055 || visible_row_range.contains(&edit_end.row());
9056 if !is_visible {
9057 return None;
9058 }
9059
9060 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9061 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9062 } else {
9063 // Fallback for providers without edit_preview
9064 crate::edit_prediction_fallback_text(edits, cx)
9065 };
9066
9067 let styled_text = highlighted_edits.to_styled_text(&style.text);
9068 let line_count = highlighted_edits.text.lines().count();
9069
9070 const BORDER_WIDTH: Pixels = px(1.);
9071
9072 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9073 let has_keybind = keybind.is_some();
9074
9075 let mut element = h_flex()
9076 .items_start()
9077 .child(
9078 h_flex()
9079 .bg(cx.theme().colors().editor_background)
9080 .border(BORDER_WIDTH)
9081 .shadow_xs()
9082 .border_color(cx.theme().colors().border)
9083 .rounded_l_lg()
9084 .when(line_count > 1, |el| el.rounded_br_lg())
9085 .pr_1()
9086 .child(styled_text),
9087 )
9088 .child(
9089 h_flex()
9090 .h(line_height + BORDER_WIDTH * 2.)
9091 .px_1p5()
9092 .gap_1()
9093 // Workaround: For some reason, there's a gap if we don't do this
9094 .ml(-BORDER_WIDTH)
9095 .shadow(vec![gpui::BoxShadow {
9096 color: gpui::black().opacity(0.05),
9097 offset: point(px(1.), px(1.)),
9098 blur_radius: px(2.),
9099 spread_radius: px(0.),
9100 }])
9101 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9102 .border(BORDER_WIDTH)
9103 .border_color(cx.theme().colors().border)
9104 .rounded_r_lg()
9105 .id("edit_prediction_diff_popover_keybind")
9106 .when(!has_keybind, |el| {
9107 let status_colors = cx.theme().status();
9108
9109 el.bg(status_colors.error_background)
9110 .border_color(status_colors.error.opacity(0.6))
9111 .child(Icon::new(IconName::Info).color(Color::Error))
9112 .cursor_default()
9113 .hoverable_tooltip(move |_window, cx| {
9114 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9115 })
9116 })
9117 .children(keybind),
9118 )
9119 .into_any();
9120
9121 let longest_row =
9122 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9123 let longest_line_width = if visible_row_range.contains(&longest_row) {
9124 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9125 } else {
9126 layout_line(
9127 longest_row,
9128 editor_snapshot,
9129 style,
9130 editor_width,
9131 |_| false,
9132 window,
9133 cx,
9134 )
9135 .width
9136 };
9137
9138 let viewport_bounds =
9139 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9140 right: -right_margin,
9141 ..Default::default()
9142 });
9143
9144 let x_after_longest =
9145 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9146 - scroll_pixel_position.x;
9147
9148 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9149
9150 // Fully visible if it can be displayed within the window (allow overlapping other
9151 // panes). However, this is only allowed if the popover starts within text_bounds.
9152 let can_position_to_the_right = x_after_longest < text_bounds.right()
9153 && x_after_longest + element_bounds.width < viewport_bounds.right();
9154
9155 let mut origin = if can_position_to_the_right {
9156 point(
9157 x_after_longest,
9158 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9159 - scroll_pixel_position.y,
9160 )
9161 } else {
9162 let cursor_row = newest_selection_head.map(|head| head.row());
9163 let above_edit = edit_start
9164 .row()
9165 .0
9166 .checked_sub(line_count as u32)
9167 .map(DisplayRow);
9168 let below_edit = Some(edit_end.row() + 1);
9169 let above_cursor =
9170 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9171 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9172
9173 // Place the edit popover adjacent to the edit if there is a location
9174 // available that is onscreen and does not obscure the cursor. Otherwise,
9175 // place it adjacent to the cursor.
9176 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9177 .into_iter()
9178 .flatten()
9179 .find(|&start_row| {
9180 let end_row = start_row + line_count as u32;
9181 visible_row_range.contains(&start_row)
9182 && visible_row_range.contains(&end_row)
9183 && cursor_row
9184 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9185 })?;
9186
9187 content_origin
9188 + point(
9189 -scroll_pixel_position.x,
9190 row_target.as_f32() * line_height - scroll_pixel_position.y,
9191 )
9192 };
9193
9194 origin.x -= BORDER_WIDTH;
9195
9196 window.defer_draw(element, origin, 1);
9197
9198 // Do not return an element, since it will already be drawn due to defer_draw.
9199 None
9200 }
9201
9202 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9203 px(30.)
9204 }
9205
9206 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9207 if self.read_only(cx) {
9208 cx.theme().players().read_only()
9209 } else {
9210 self.style.as_ref().unwrap().local_player
9211 }
9212 }
9213
9214 fn render_edit_prediction_accept_keybind(
9215 &self,
9216 window: &mut Window,
9217 cx: &App,
9218 ) -> Option<AnyElement> {
9219 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9220 let accept_keystroke = accept_binding.keystroke()?;
9221
9222 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9223
9224 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9225 Color::Accent
9226 } else {
9227 Color::Muted
9228 };
9229
9230 h_flex()
9231 .px_0p5()
9232 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9233 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9234 .text_size(TextSize::XSmall.rems(cx))
9235 .child(h_flex().children(ui::render_modifiers(
9236 accept_keystroke.modifiers(),
9237 PlatformStyle::platform(),
9238 Some(modifiers_color),
9239 Some(IconSize::XSmall.rems().into()),
9240 true,
9241 )))
9242 .when(is_platform_style_mac, |parent| {
9243 parent.child(accept_keystroke.key().to_string())
9244 })
9245 .when(!is_platform_style_mac, |parent| {
9246 parent.child(
9247 Key::new(
9248 util::capitalize(accept_keystroke.key()),
9249 Some(Color::Default),
9250 )
9251 .size(Some(IconSize::XSmall.rems().into())),
9252 )
9253 })
9254 .into_any()
9255 .into()
9256 }
9257
9258 fn render_edit_prediction_line_popover(
9259 &self,
9260 label: impl Into<SharedString>,
9261 icon: Option<IconName>,
9262 window: &mut Window,
9263 cx: &App,
9264 ) -> Stateful<Div> {
9265 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9266
9267 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9268 let has_keybind = keybind.is_some();
9269
9270 h_flex()
9271 .id("ep-line-popover")
9272 .py_0p5()
9273 .pl_1()
9274 .pr(padding_right)
9275 .gap_1()
9276 .rounded_md()
9277 .border_1()
9278 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9279 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9280 .shadow_xs()
9281 .when(!has_keybind, |el| {
9282 let status_colors = cx.theme().status();
9283
9284 el.bg(status_colors.error_background)
9285 .border_color(status_colors.error.opacity(0.6))
9286 .pl_2()
9287 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9288 .cursor_default()
9289 .hoverable_tooltip(move |_window, cx| {
9290 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9291 })
9292 })
9293 .children(keybind)
9294 .child(
9295 Label::new(label)
9296 .size(LabelSize::Small)
9297 .when(!has_keybind, |el| {
9298 el.color(cx.theme().status().error.into()).strikethrough()
9299 }),
9300 )
9301 .when(!has_keybind, |el| {
9302 el.child(
9303 h_flex().ml_1().child(
9304 Icon::new(IconName::Info)
9305 .size(IconSize::Small)
9306 .color(cx.theme().status().error.into()),
9307 ),
9308 )
9309 })
9310 .when_some(icon, |element, icon| {
9311 element.child(
9312 div()
9313 .mt(px(1.5))
9314 .child(Icon::new(icon).size(IconSize::Small)),
9315 )
9316 })
9317 }
9318
9319 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9320 let accent_color = cx.theme().colors().text_accent;
9321 let editor_bg_color = cx.theme().colors().editor_background;
9322 editor_bg_color.blend(accent_color.opacity(0.1))
9323 }
9324
9325 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9326 let accent_color = cx.theme().colors().text_accent;
9327 let editor_bg_color = cx.theme().colors().editor_background;
9328 editor_bg_color.blend(accent_color.opacity(0.6))
9329 }
9330 fn get_prediction_provider_icon_name(
9331 provider: &Option<RegisteredEditPredictionProvider>,
9332 ) -> IconName {
9333 match provider {
9334 Some(provider) => match provider.provider.name() {
9335 "copilot" => IconName::Copilot,
9336 "supermaven" => IconName::Supermaven,
9337 _ => IconName::ZedPredict,
9338 },
9339 None => IconName::ZedPredict,
9340 }
9341 }
9342
9343 fn render_edit_prediction_cursor_popover(
9344 &self,
9345 min_width: Pixels,
9346 max_width: Pixels,
9347 cursor_point: Point,
9348 style: &EditorStyle,
9349 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9350 _window: &Window,
9351 cx: &mut Context<Editor>,
9352 ) -> Option<AnyElement> {
9353 let provider = self.edit_prediction_provider.as_ref()?;
9354 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9355
9356 let is_refreshing = provider.provider.is_refreshing(cx);
9357
9358 fn pending_completion_container(icon: IconName) -> Div {
9359 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9360 }
9361
9362 let completion = match &self.active_edit_prediction {
9363 Some(prediction) => {
9364 if !self.has_visible_completions_menu() {
9365 const RADIUS: Pixels = px(6.);
9366 const BORDER_WIDTH: Pixels = px(1.);
9367
9368 return Some(
9369 h_flex()
9370 .elevation_2(cx)
9371 .border(BORDER_WIDTH)
9372 .border_color(cx.theme().colors().border)
9373 .when(accept_keystroke.is_none(), |el| {
9374 el.border_color(cx.theme().status().error)
9375 })
9376 .rounded(RADIUS)
9377 .rounded_tl(px(0.))
9378 .overflow_hidden()
9379 .child(div().px_1p5().child(match &prediction.completion {
9380 EditPrediction::MoveWithin { target, snapshot } => {
9381 use text::ToPoint as _;
9382 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9383 {
9384 Icon::new(IconName::ZedPredictDown)
9385 } else {
9386 Icon::new(IconName::ZedPredictUp)
9387 }
9388 }
9389 EditPrediction::MoveOutside { .. } => {
9390 // TODO [zeta2] custom icon for external jump?
9391 Icon::new(provider_icon)
9392 }
9393 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9394 }))
9395 .child(
9396 h_flex()
9397 .gap_1()
9398 .py_1()
9399 .px_2()
9400 .rounded_r(RADIUS - BORDER_WIDTH)
9401 .border_l_1()
9402 .border_color(cx.theme().colors().border)
9403 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9404 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9405 el.child(
9406 Label::new("Hold")
9407 .size(LabelSize::Small)
9408 .when(accept_keystroke.is_none(), |el| {
9409 el.strikethrough()
9410 })
9411 .line_height_style(LineHeightStyle::UiLabel),
9412 )
9413 })
9414 .id("edit_prediction_cursor_popover_keybind")
9415 .when(accept_keystroke.is_none(), |el| {
9416 let status_colors = cx.theme().status();
9417
9418 el.bg(status_colors.error_background)
9419 .border_color(status_colors.error.opacity(0.6))
9420 .child(Icon::new(IconName::Info).color(Color::Error))
9421 .cursor_default()
9422 .hoverable_tooltip(move |_window, cx| {
9423 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9424 .into()
9425 })
9426 })
9427 .when_some(
9428 accept_keystroke.as_ref(),
9429 |el, accept_keystroke| {
9430 el.child(h_flex().children(ui::render_modifiers(
9431 accept_keystroke.modifiers(),
9432 PlatformStyle::platform(),
9433 Some(Color::Default),
9434 Some(IconSize::XSmall.rems().into()),
9435 false,
9436 )))
9437 },
9438 ),
9439 )
9440 .into_any(),
9441 );
9442 }
9443
9444 self.render_edit_prediction_cursor_popover_preview(
9445 prediction,
9446 cursor_point,
9447 style,
9448 cx,
9449 )?
9450 }
9451
9452 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9453 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9454 stale_completion,
9455 cursor_point,
9456 style,
9457 cx,
9458 )?,
9459
9460 None => pending_completion_container(provider_icon)
9461 .child(Label::new("...").size(LabelSize::Small)),
9462 },
9463
9464 None => pending_completion_container(provider_icon)
9465 .child(Label::new("...").size(LabelSize::Small)),
9466 };
9467
9468 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9469 completion
9470 .with_animation(
9471 "loading-completion",
9472 Animation::new(Duration::from_secs(2))
9473 .repeat()
9474 .with_easing(pulsating_between(0.4, 0.8)),
9475 |label, delta| label.opacity(delta),
9476 )
9477 .into_any_element()
9478 } else {
9479 completion.into_any_element()
9480 };
9481
9482 let has_completion = self.active_edit_prediction.is_some();
9483
9484 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9485 Some(
9486 h_flex()
9487 .min_w(min_width)
9488 .max_w(max_width)
9489 .flex_1()
9490 .elevation_2(cx)
9491 .border_color(cx.theme().colors().border)
9492 .child(
9493 div()
9494 .flex_1()
9495 .py_1()
9496 .px_2()
9497 .overflow_hidden()
9498 .child(completion),
9499 )
9500 .when_some(accept_keystroke, |el, accept_keystroke| {
9501 if !accept_keystroke.modifiers().modified() {
9502 return el;
9503 }
9504
9505 el.child(
9506 h_flex()
9507 .h_full()
9508 .border_l_1()
9509 .rounded_r_lg()
9510 .border_color(cx.theme().colors().border)
9511 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9512 .gap_1()
9513 .py_1()
9514 .px_2()
9515 .child(
9516 h_flex()
9517 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9518 .when(is_platform_style_mac, |parent| parent.gap_1())
9519 .child(h_flex().children(ui::render_modifiers(
9520 accept_keystroke.modifiers(),
9521 PlatformStyle::platform(),
9522 Some(if !has_completion {
9523 Color::Muted
9524 } else {
9525 Color::Default
9526 }),
9527 None,
9528 false,
9529 ))),
9530 )
9531 .child(Label::new("Preview").into_any_element())
9532 .opacity(if has_completion { 1.0 } else { 0.4 }),
9533 )
9534 })
9535 .into_any(),
9536 )
9537 }
9538
9539 fn render_edit_prediction_cursor_popover_preview(
9540 &self,
9541 completion: &EditPredictionState,
9542 cursor_point: Point,
9543 style: &EditorStyle,
9544 cx: &mut Context<Editor>,
9545 ) -> Option<Div> {
9546 use text::ToPoint as _;
9547
9548 fn render_relative_row_jump(
9549 prefix: impl Into<String>,
9550 current_row: u32,
9551 target_row: u32,
9552 ) -> Div {
9553 let (row_diff, arrow) = if target_row < current_row {
9554 (current_row - target_row, IconName::ArrowUp)
9555 } else {
9556 (target_row - current_row, IconName::ArrowDown)
9557 };
9558
9559 h_flex()
9560 .child(
9561 Label::new(format!("{}{}", prefix.into(), row_diff))
9562 .color(Color::Muted)
9563 .size(LabelSize::Small),
9564 )
9565 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9566 }
9567
9568 let supports_jump = self
9569 .edit_prediction_provider
9570 .as_ref()
9571 .map(|provider| provider.provider.supports_jump_to_edit())
9572 .unwrap_or(true);
9573
9574 match &completion.completion {
9575 EditPrediction::MoveWithin {
9576 target, snapshot, ..
9577 } => {
9578 if !supports_jump {
9579 return None;
9580 }
9581
9582 Some(
9583 h_flex()
9584 .px_2()
9585 .gap_2()
9586 .flex_1()
9587 .child(
9588 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9589 Icon::new(IconName::ZedPredictDown)
9590 } else {
9591 Icon::new(IconName::ZedPredictUp)
9592 },
9593 )
9594 .child(Label::new("Jump to Edit")),
9595 )
9596 }
9597 EditPrediction::MoveOutside { snapshot, .. } => {
9598 let file_name = snapshot
9599 .file()
9600 .map(|file| file.file_name(cx))
9601 .unwrap_or("untitled");
9602 Some(
9603 h_flex()
9604 .px_2()
9605 .gap_2()
9606 .flex_1()
9607 .child(Icon::new(IconName::ZedPredict))
9608 .child(Label::new(format!("Jump to {file_name}"))),
9609 )
9610 }
9611 EditPrediction::Edit {
9612 edits,
9613 edit_preview,
9614 snapshot,
9615 display_mode: _,
9616 } => {
9617 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9618
9619 let (highlighted_edits, has_more_lines) =
9620 if let Some(edit_preview) = edit_preview.as_ref() {
9621 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9622 .first_line_preview()
9623 } else {
9624 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9625 };
9626
9627 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9628 .with_default_highlights(&style.text, highlighted_edits.highlights);
9629
9630 let preview = h_flex()
9631 .gap_1()
9632 .min_w_16()
9633 .child(styled_text)
9634 .when(has_more_lines, |parent| parent.child("…"));
9635
9636 let left = if supports_jump && first_edit_row != cursor_point.row {
9637 render_relative_row_jump("", cursor_point.row, first_edit_row)
9638 .into_any_element()
9639 } else {
9640 let icon_name =
9641 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9642 Icon::new(icon_name).into_any_element()
9643 };
9644
9645 Some(
9646 h_flex()
9647 .h_full()
9648 .flex_1()
9649 .gap_2()
9650 .pr_1()
9651 .overflow_x_hidden()
9652 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9653 .child(left)
9654 .child(preview),
9655 )
9656 }
9657 }
9658 }
9659
9660 pub fn render_context_menu(
9661 &self,
9662 style: &EditorStyle,
9663 max_height_in_lines: u32,
9664 window: &mut Window,
9665 cx: &mut Context<Editor>,
9666 ) -> Option<AnyElement> {
9667 let menu = self.context_menu.borrow();
9668 let menu = menu.as_ref()?;
9669 if !menu.visible() {
9670 return None;
9671 };
9672 Some(menu.render(style, max_height_in_lines, window, cx))
9673 }
9674
9675 fn render_context_menu_aside(
9676 &mut self,
9677 max_size: Size<Pixels>,
9678 window: &mut Window,
9679 cx: &mut Context<Editor>,
9680 ) -> Option<AnyElement> {
9681 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9682 if menu.visible() {
9683 menu.render_aside(max_size, window, cx)
9684 } else {
9685 None
9686 }
9687 })
9688 }
9689
9690 fn hide_context_menu(
9691 &mut self,
9692 window: &mut Window,
9693 cx: &mut Context<Self>,
9694 ) -> Option<CodeContextMenu> {
9695 cx.notify();
9696 self.completion_tasks.clear();
9697 let context_menu = self.context_menu.borrow_mut().take();
9698 self.stale_edit_prediction_in_menu.take();
9699 self.update_visible_edit_prediction(window, cx);
9700 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9701 && let Some(completion_provider) = &self.completion_provider
9702 {
9703 completion_provider.selection_changed(None, window, cx);
9704 }
9705 context_menu
9706 }
9707
9708 fn show_snippet_choices(
9709 &mut self,
9710 choices: &Vec<String>,
9711 selection: Range<Anchor>,
9712 cx: &mut Context<Self>,
9713 ) {
9714 let Some((_, buffer, _)) = self
9715 .buffer()
9716 .read(cx)
9717 .excerpt_containing(selection.start, cx)
9718 else {
9719 return;
9720 };
9721 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9722 else {
9723 return;
9724 };
9725 if buffer != end_buffer {
9726 log::error!("expected anchor range to have matching buffer IDs");
9727 return;
9728 }
9729
9730 let id = post_inc(&mut self.next_completion_id);
9731 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9732 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9733 CompletionsMenu::new_snippet_choices(
9734 id,
9735 true,
9736 choices,
9737 selection,
9738 buffer,
9739 snippet_sort_order,
9740 ),
9741 ));
9742 }
9743
9744 pub fn insert_snippet(
9745 &mut self,
9746 insertion_ranges: &[Range<usize>],
9747 snippet: Snippet,
9748 window: &mut Window,
9749 cx: &mut Context<Self>,
9750 ) -> Result<()> {
9751 struct Tabstop<T> {
9752 is_end_tabstop: bool,
9753 ranges: Vec<Range<T>>,
9754 choices: Option<Vec<String>>,
9755 }
9756
9757 let tabstops = self.buffer.update(cx, |buffer, cx| {
9758 let snippet_text: Arc<str> = snippet.text.clone().into();
9759 let edits = insertion_ranges
9760 .iter()
9761 .cloned()
9762 .map(|range| (range, snippet_text.clone()));
9763 let autoindent_mode = AutoindentMode::Block {
9764 original_indent_columns: Vec::new(),
9765 };
9766 buffer.edit(edits, Some(autoindent_mode), cx);
9767
9768 let snapshot = &*buffer.read(cx);
9769 let snippet = &snippet;
9770 snippet
9771 .tabstops
9772 .iter()
9773 .map(|tabstop| {
9774 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9775 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9776 });
9777 let mut tabstop_ranges = tabstop
9778 .ranges
9779 .iter()
9780 .flat_map(|tabstop_range| {
9781 let mut delta = 0_isize;
9782 insertion_ranges.iter().map(move |insertion_range| {
9783 let insertion_start = insertion_range.start as isize + delta;
9784 delta +=
9785 snippet.text.len() as isize - insertion_range.len() as isize;
9786
9787 let start = ((insertion_start + tabstop_range.start) as usize)
9788 .min(snapshot.len());
9789 let end = ((insertion_start + tabstop_range.end) as usize)
9790 .min(snapshot.len());
9791 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9792 })
9793 })
9794 .collect::<Vec<_>>();
9795 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9796
9797 Tabstop {
9798 is_end_tabstop,
9799 ranges: tabstop_ranges,
9800 choices: tabstop.choices.clone(),
9801 }
9802 })
9803 .collect::<Vec<_>>()
9804 });
9805 if let Some(tabstop) = tabstops.first() {
9806 self.change_selections(Default::default(), window, cx, |s| {
9807 // Reverse order so that the first range is the newest created selection.
9808 // Completions will use it and autoscroll will prioritize it.
9809 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9810 });
9811
9812 if let Some(choices) = &tabstop.choices
9813 && let Some(selection) = tabstop.ranges.first()
9814 {
9815 self.show_snippet_choices(choices, selection.clone(), cx)
9816 }
9817
9818 // If we're already at the last tabstop and it's at the end of the snippet,
9819 // we're done, we don't need to keep the state around.
9820 if !tabstop.is_end_tabstop {
9821 let choices = tabstops
9822 .iter()
9823 .map(|tabstop| tabstop.choices.clone())
9824 .collect();
9825
9826 let ranges = tabstops
9827 .into_iter()
9828 .map(|tabstop| tabstop.ranges)
9829 .collect::<Vec<_>>();
9830
9831 self.snippet_stack.push(SnippetState {
9832 active_index: 0,
9833 ranges,
9834 choices,
9835 });
9836 }
9837
9838 // Check whether the just-entered snippet ends with an auto-closable bracket.
9839 if self.autoclose_regions.is_empty() {
9840 let snapshot = self.buffer.read(cx).snapshot(cx);
9841 let mut all_selections = self.selections.all::<Point>(cx);
9842 for selection in &mut all_selections {
9843 let selection_head = selection.head();
9844 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9845 continue;
9846 };
9847
9848 let mut bracket_pair = None;
9849 let max_lookup_length = scope
9850 .brackets()
9851 .map(|(pair, _)| {
9852 pair.start
9853 .as_str()
9854 .chars()
9855 .count()
9856 .max(pair.end.as_str().chars().count())
9857 })
9858 .max();
9859 if let Some(max_lookup_length) = max_lookup_length {
9860 let next_text = snapshot
9861 .chars_at(selection_head)
9862 .take(max_lookup_length)
9863 .collect::<String>();
9864 let prev_text = snapshot
9865 .reversed_chars_at(selection_head)
9866 .take(max_lookup_length)
9867 .collect::<String>();
9868
9869 for (pair, enabled) in scope.brackets() {
9870 if enabled
9871 && pair.close
9872 && prev_text.starts_with(pair.start.as_str())
9873 && next_text.starts_with(pair.end.as_str())
9874 {
9875 bracket_pair = Some(pair.clone());
9876 break;
9877 }
9878 }
9879 }
9880
9881 if let Some(pair) = bracket_pair {
9882 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9883 let autoclose_enabled =
9884 self.use_autoclose && snapshot_settings.use_autoclose;
9885 if autoclose_enabled {
9886 let start = snapshot.anchor_after(selection_head);
9887 let end = snapshot.anchor_after(selection_head);
9888 self.autoclose_regions.push(AutocloseRegion {
9889 selection_id: selection.id,
9890 range: start..end,
9891 pair,
9892 });
9893 }
9894 }
9895 }
9896 }
9897 }
9898 Ok(())
9899 }
9900
9901 pub fn move_to_next_snippet_tabstop(
9902 &mut self,
9903 window: &mut Window,
9904 cx: &mut Context<Self>,
9905 ) -> bool {
9906 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9907 }
9908
9909 pub fn move_to_prev_snippet_tabstop(
9910 &mut self,
9911 window: &mut Window,
9912 cx: &mut Context<Self>,
9913 ) -> bool {
9914 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9915 }
9916
9917 pub fn move_to_snippet_tabstop(
9918 &mut self,
9919 bias: Bias,
9920 window: &mut Window,
9921 cx: &mut Context<Self>,
9922 ) -> bool {
9923 if let Some(mut snippet) = self.snippet_stack.pop() {
9924 match bias {
9925 Bias::Left => {
9926 if snippet.active_index > 0 {
9927 snippet.active_index -= 1;
9928 } else {
9929 self.snippet_stack.push(snippet);
9930 return false;
9931 }
9932 }
9933 Bias::Right => {
9934 if snippet.active_index + 1 < snippet.ranges.len() {
9935 snippet.active_index += 1;
9936 } else {
9937 self.snippet_stack.push(snippet);
9938 return false;
9939 }
9940 }
9941 }
9942 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9943 self.change_selections(Default::default(), window, cx, |s| {
9944 // Reverse order so that the first range is the newest created selection.
9945 // Completions will use it and autoscroll will prioritize it.
9946 s.select_ranges(current_ranges.iter().rev().cloned())
9947 });
9948
9949 if let Some(choices) = &snippet.choices[snippet.active_index]
9950 && let Some(selection) = current_ranges.first()
9951 {
9952 self.show_snippet_choices(choices, selection.clone(), cx);
9953 }
9954
9955 // If snippet state is not at the last tabstop, push it back on the stack
9956 if snippet.active_index + 1 < snippet.ranges.len() {
9957 self.snippet_stack.push(snippet);
9958 }
9959 return true;
9960 }
9961 }
9962
9963 false
9964 }
9965
9966 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9967 self.transact(window, cx, |this, window, cx| {
9968 this.select_all(&SelectAll, window, cx);
9969 this.insert("", window, cx);
9970 });
9971 }
9972
9973 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9974 if self.read_only(cx) {
9975 return;
9976 }
9977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9978 self.transact(window, cx, |this, window, cx| {
9979 this.select_autoclose_pair(window, cx);
9980 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9981 if !this.linked_edit_ranges.is_empty() {
9982 let selections = this.selections.all::<MultiBufferPoint>(cx);
9983 let snapshot = this.buffer.read(cx).snapshot(cx);
9984
9985 for selection in selections.iter() {
9986 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9987 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9988 if selection_start.buffer_id != selection_end.buffer_id {
9989 continue;
9990 }
9991 if let Some(ranges) =
9992 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9993 {
9994 for (buffer, entries) in ranges {
9995 linked_ranges.entry(buffer).or_default().extend(entries);
9996 }
9997 }
9998 }
9999 }
10000
10001 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10002 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10003 for selection in &mut selections {
10004 if selection.is_empty() {
10005 let old_head = selection.head();
10006 let mut new_head =
10007 movement::left(&display_map, old_head.to_display_point(&display_map))
10008 .to_point(&display_map);
10009 if let Some((buffer, line_buffer_range)) = display_map
10010 .buffer_snapshot
10011 .buffer_line_for_row(MultiBufferRow(old_head.row))
10012 {
10013 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10014 let indent_len = match indent_size.kind {
10015 IndentKind::Space => {
10016 buffer.settings_at(line_buffer_range.start, cx).tab_size
10017 }
10018 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10019 };
10020 if old_head.column <= indent_size.len && old_head.column > 0 {
10021 let indent_len = indent_len.get();
10022 new_head = cmp::min(
10023 new_head,
10024 MultiBufferPoint::new(
10025 old_head.row,
10026 ((old_head.column - 1) / indent_len) * indent_len,
10027 ),
10028 );
10029 }
10030 }
10031
10032 selection.set_head(new_head, SelectionGoal::None);
10033 }
10034 }
10035
10036 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10037 this.insert("", window, cx);
10038 let empty_str: Arc<str> = Arc::from("");
10039 for (buffer, edits) in linked_ranges {
10040 let snapshot = buffer.read(cx).snapshot();
10041 use text::ToPoint as TP;
10042
10043 let edits = edits
10044 .into_iter()
10045 .map(|range| {
10046 let end_point = TP::to_point(&range.end, &snapshot);
10047 let mut start_point = TP::to_point(&range.start, &snapshot);
10048
10049 if end_point == start_point {
10050 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10051 .saturating_sub(1);
10052 start_point =
10053 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10054 };
10055
10056 (start_point..end_point, empty_str.clone())
10057 })
10058 .sorted_by_key(|(range, _)| range.start)
10059 .collect::<Vec<_>>();
10060 buffer.update(cx, |this, cx| {
10061 this.edit(edits, None, cx);
10062 })
10063 }
10064 this.refresh_edit_prediction(true, false, window, cx);
10065 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10066 });
10067 }
10068
10069 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10070 if self.read_only(cx) {
10071 return;
10072 }
10073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10074 self.transact(window, cx, |this, window, cx| {
10075 this.change_selections(Default::default(), window, cx, |s| {
10076 s.move_with(|map, selection| {
10077 if selection.is_empty() {
10078 let cursor = movement::right(map, selection.head());
10079 selection.end = cursor;
10080 selection.reversed = true;
10081 selection.goal = SelectionGoal::None;
10082 }
10083 })
10084 });
10085 this.insert("", window, cx);
10086 this.refresh_edit_prediction(true, false, window, cx);
10087 });
10088 }
10089
10090 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10091 if self.mode.is_single_line() {
10092 cx.propagate();
10093 return;
10094 }
10095
10096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10097 if self.move_to_prev_snippet_tabstop(window, cx) {
10098 return;
10099 }
10100 self.outdent(&Outdent, window, cx);
10101 }
10102
10103 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10104 if self.mode.is_single_line() {
10105 cx.propagate();
10106 return;
10107 }
10108
10109 if self.move_to_next_snippet_tabstop(window, cx) {
10110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10111 return;
10112 }
10113 if self.read_only(cx) {
10114 return;
10115 }
10116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10117 let mut selections = self.selections.all_adjusted(cx);
10118 let buffer = self.buffer.read(cx);
10119 let snapshot = buffer.snapshot(cx);
10120 let rows_iter = selections.iter().map(|s| s.head().row);
10121 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10122
10123 let has_some_cursor_in_whitespace = selections
10124 .iter()
10125 .filter(|selection| selection.is_empty())
10126 .any(|selection| {
10127 let cursor = selection.head();
10128 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10129 cursor.column < current_indent.len
10130 });
10131
10132 let mut edits = Vec::new();
10133 let mut prev_edited_row = 0;
10134 let mut row_delta = 0;
10135 for selection in &mut selections {
10136 if selection.start.row != prev_edited_row {
10137 row_delta = 0;
10138 }
10139 prev_edited_row = selection.end.row;
10140
10141 // If the selection is non-empty, then increase the indentation of the selected lines.
10142 if !selection.is_empty() {
10143 row_delta =
10144 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10145 continue;
10146 }
10147
10148 let cursor = selection.head();
10149 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10150 if let Some(suggested_indent) =
10151 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10152 {
10153 // Don't do anything if already at suggested indent
10154 // and there is any other cursor which is not
10155 if has_some_cursor_in_whitespace
10156 && cursor.column == current_indent.len
10157 && current_indent.len == suggested_indent.len
10158 {
10159 continue;
10160 }
10161
10162 // Adjust line and move cursor to suggested indent
10163 // if cursor is not at suggested indent
10164 if cursor.column < suggested_indent.len
10165 && cursor.column <= current_indent.len
10166 && current_indent.len <= suggested_indent.len
10167 {
10168 selection.start = Point::new(cursor.row, suggested_indent.len);
10169 selection.end = selection.start;
10170 if row_delta == 0 {
10171 edits.extend(Buffer::edit_for_indent_size_adjustment(
10172 cursor.row,
10173 current_indent,
10174 suggested_indent,
10175 ));
10176 row_delta = suggested_indent.len - current_indent.len;
10177 }
10178 continue;
10179 }
10180
10181 // If current indent is more than suggested indent
10182 // only move cursor to current indent and skip indent
10183 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10184 selection.start = Point::new(cursor.row, current_indent.len);
10185 selection.end = selection.start;
10186 continue;
10187 }
10188 }
10189
10190 // Otherwise, insert a hard or soft tab.
10191 let settings = buffer.language_settings_at(cursor, cx);
10192 let tab_size = if settings.hard_tabs {
10193 IndentSize::tab()
10194 } else {
10195 let tab_size = settings.tab_size.get();
10196 let indent_remainder = snapshot
10197 .text_for_range(Point::new(cursor.row, 0)..cursor)
10198 .flat_map(str::chars)
10199 .fold(row_delta % tab_size, |counter: u32, c| {
10200 if c == '\t' {
10201 0
10202 } else {
10203 (counter + 1) % tab_size
10204 }
10205 });
10206
10207 let chars_to_next_tab_stop = tab_size - indent_remainder;
10208 IndentSize::spaces(chars_to_next_tab_stop)
10209 };
10210 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10211 selection.end = selection.start;
10212 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10213 row_delta += tab_size.len;
10214 }
10215
10216 self.transact(window, cx, |this, window, cx| {
10217 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10218 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10219 this.refresh_edit_prediction(true, false, window, cx);
10220 });
10221 }
10222
10223 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10224 if self.read_only(cx) {
10225 return;
10226 }
10227 if self.mode.is_single_line() {
10228 cx.propagate();
10229 return;
10230 }
10231
10232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10233 let mut selections = self.selections.all::<Point>(cx);
10234 let mut prev_edited_row = 0;
10235 let mut row_delta = 0;
10236 let mut edits = Vec::new();
10237 let buffer = self.buffer.read(cx);
10238 let snapshot = buffer.snapshot(cx);
10239 for selection in &mut selections {
10240 if selection.start.row != prev_edited_row {
10241 row_delta = 0;
10242 }
10243 prev_edited_row = selection.end.row;
10244
10245 row_delta =
10246 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10247 }
10248
10249 self.transact(window, cx, |this, window, cx| {
10250 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10251 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10252 });
10253 }
10254
10255 fn indent_selection(
10256 buffer: &MultiBuffer,
10257 snapshot: &MultiBufferSnapshot,
10258 selection: &mut Selection<Point>,
10259 edits: &mut Vec<(Range<Point>, String)>,
10260 delta_for_start_row: u32,
10261 cx: &App,
10262 ) -> u32 {
10263 let settings = buffer.language_settings_at(selection.start, cx);
10264 let tab_size = settings.tab_size.get();
10265 let indent_kind = if settings.hard_tabs {
10266 IndentKind::Tab
10267 } else {
10268 IndentKind::Space
10269 };
10270 let mut start_row = selection.start.row;
10271 let mut end_row = selection.end.row + 1;
10272
10273 // If a selection ends at the beginning of a line, don't indent
10274 // that last line.
10275 if selection.end.column == 0 && selection.end.row > selection.start.row {
10276 end_row -= 1;
10277 }
10278
10279 // Avoid re-indenting a row that has already been indented by a
10280 // previous selection, but still update this selection's column
10281 // to reflect that indentation.
10282 if delta_for_start_row > 0 {
10283 start_row += 1;
10284 selection.start.column += delta_for_start_row;
10285 if selection.end.row == selection.start.row {
10286 selection.end.column += delta_for_start_row;
10287 }
10288 }
10289
10290 let mut delta_for_end_row = 0;
10291 let has_multiple_rows = start_row + 1 != end_row;
10292 for row in start_row..end_row {
10293 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10294 let indent_delta = match (current_indent.kind, indent_kind) {
10295 (IndentKind::Space, IndentKind::Space) => {
10296 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10297 IndentSize::spaces(columns_to_next_tab_stop)
10298 }
10299 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10300 (_, IndentKind::Tab) => IndentSize::tab(),
10301 };
10302
10303 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10304 0
10305 } else {
10306 selection.start.column
10307 };
10308 let row_start = Point::new(row, start);
10309 edits.push((
10310 row_start..row_start,
10311 indent_delta.chars().collect::<String>(),
10312 ));
10313
10314 // Update this selection's endpoints to reflect the indentation.
10315 if row == selection.start.row {
10316 selection.start.column += indent_delta.len;
10317 }
10318 if row == selection.end.row {
10319 selection.end.column += indent_delta.len;
10320 delta_for_end_row = indent_delta.len;
10321 }
10322 }
10323
10324 if selection.start.row == selection.end.row {
10325 delta_for_start_row + delta_for_end_row
10326 } else {
10327 delta_for_end_row
10328 }
10329 }
10330
10331 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10332 if self.read_only(cx) {
10333 return;
10334 }
10335 if self.mode.is_single_line() {
10336 cx.propagate();
10337 return;
10338 }
10339
10340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10342 let selections = self.selections.all::<Point>(cx);
10343 let mut deletion_ranges = Vec::new();
10344 let mut last_outdent = None;
10345 {
10346 let buffer = self.buffer.read(cx);
10347 let snapshot = buffer.snapshot(cx);
10348 for selection in &selections {
10349 let settings = buffer.language_settings_at(selection.start, cx);
10350 let tab_size = settings.tab_size.get();
10351 let mut rows = selection.spanned_rows(false, &display_map);
10352
10353 // Avoid re-outdenting a row that has already been outdented by a
10354 // previous selection.
10355 if let Some(last_row) = last_outdent
10356 && last_row == rows.start
10357 {
10358 rows.start = rows.start.next_row();
10359 }
10360 let has_multiple_rows = rows.len() > 1;
10361 for row in rows.iter_rows() {
10362 let indent_size = snapshot.indent_size_for_line(row);
10363 if indent_size.len > 0 {
10364 let deletion_len = match indent_size.kind {
10365 IndentKind::Space => {
10366 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10367 if columns_to_prev_tab_stop == 0 {
10368 tab_size
10369 } else {
10370 columns_to_prev_tab_stop
10371 }
10372 }
10373 IndentKind::Tab => 1,
10374 };
10375 let start = if has_multiple_rows
10376 || deletion_len > selection.start.column
10377 || indent_size.len < selection.start.column
10378 {
10379 0
10380 } else {
10381 selection.start.column - deletion_len
10382 };
10383 deletion_ranges.push(
10384 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10385 );
10386 last_outdent = Some(row);
10387 }
10388 }
10389 }
10390 }
10391
10392 self.transact(window, cx, |this, window, cx| {
10393 this.buffer.update(cx, |buffer, cx| {
10394 let empty_str: Arc<str> = Arc::default();
10395 buffer.edit(
10396 deletion_ranges
10397 .into_iter()
10398 .map(|range| (range, empty_str.clone())),
10399 None,
10400 cx,
10401 );
10402 });
10403 let selections = this.selections.all::<usize>(cx);
10404 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10405 });
10406 }
10407
10408 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10409 if self.read_only(cx) {
10410 return;
10411 }
10412 if self.mode.is_single_line() {
10413 cx.propagate();
10414 return;
10415 }
10416
10417 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10418 let selections = self
10419 .selections
10420 .all::<usize>(cx)
10421 .into_iter()
10422 .map(|s| s.range());
10423
10424 self.transact(window, cx, |this, window, cx| {
10425 this.buffer.update(cx, |buffer, cx| {
10426 buffer.autoindent_ranges(selections, cx);
10427 });
10428 let selections = this.selections.all::<usize>(cx);
10429 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10430 });
10431 }
10432
10433 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10436 let selections = self.selections.all::<Point>(cx);
10437
10438 let mut new_cursors = Vec::new();
10439 let mut edit_ranges = Vec::new();
10440 let mut selections = selections.iter().peekable();
10441 while let Some(selection) = selections.next() {
10442 let mut rows = selection.spanned_rows(false, &display_map);
10443 let goal_display_column = selection.head().to_display_point(&display_map).column();
10444
10445 // Accumulate contiguous regions of rows that we want to delete.
10446 while let Some(next_selection) = selections.peek() {
10447 let next_rows = next_selection.spanned_rows(false, &display_map);
10448 if next_rows.start <= rows.end {
10449 rows.end = next_rows.end;
10450 selections.next().unwrap();
10451 } else {
10452 break;
10453 }
10454 }
10455
10456 let buffer = &display_map.buffer_snapshot;
10457 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10458 let edit_end;
10459 let cursor_buffer_row;
10460 if buffer.max_point().row >= rows.end.0 {
10461 // If there's a line after the range, delete the \n from the end of the row range
10462 // and position the cursor on the next line.
10463 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10464 cursor_buffer_row = rows.end;
10465 } else {
10466 // If there isn't a line after the range, delete the \n from the line before the
10467 // start of the row range and position the cursor there.
10468 edit_start = edit_start.saturating_sub(1);
10469 edit_end = buffer.len();
10470 cursor_buffer_row = rows.start.previous_row();
10471 }
10472
10473 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10474 *cursor.column_mut() =
10475 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10476
10477 new_cursors.push((
10478 selection.id,
10479 buffer.anchor_after(cursor.to_point(&display_map)),
10480 ));
10481 edit_ranges.push(edit_start..edit_end);
10482 }
10483
10484 self.transact(window, cx, |this, window, cx| {
10485 let buffer = this.buffer.update(cx, |buffer, cx| {
10486 let empty_str: Arc<str> = Arc::default();
10487 buffer.edit(
10488 edit_ranges
10489 .into_iter()
10490 .map(|range| (range, empty_str.clone())),
10491 None,
10492 cx,
10493 );
10494 buffer.snapshot(cx)
10495 });
10496 let new_selections = new_cursors
10497 .into_iter()
10498 .map(|(id, cursor)| {
10499 let cursor = cursor.to_point(&buffer);
10500 Selection {
10501 id,
10502 start: cursor,
10503 end: cursor,
10504 reversed: false,
10505 goal: SelectionGoal::None,
10506 }
10507 })
10508 .collect();
10509
10510 this.change_selections(Default::default(), window, cx, |s| {
10511 s.select(new_selections);
10512 });
10513 });
10514 }
10515
10516 pub fn join_lines_impl(
10517 &mut self,
10518 insert_whitespace: bool,
10519 window: &mut Window,
10520 cx: &mut Context<Self>,
10521 ) {
10522 if self.read_only(cx) {
10523 return;
10524 }
10525 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10526 for selection in self.selections.all::<Point>(cx) {
10527 let start = MultiBufferRow(selection.start.row);
10528 // Treat single line selections as if they include the next line. Otherwise this action
10529 // would do nothing for single line selections individual cursors.
10530 let end = if selection.start.row == selection.end.row {
10531 MultiBufferRow(selection.start.row + 1)
10532 } else {
10533 MultiBufferRow(selection.end.row)
10534 };
10535
10536 if let Some(last_row_range) = row_ranges.last_mut()
10537 && start <= last_row_range.end
10538 {
10539 last_row_range.end = end;
10540 continue;
10541 }
10542 row_ranges.push(start..end);
10543 }
10544
10545 let snapshot = self.buffer.read(cx).snapshot(cx);
10546 let mut cursor_positions = Vec::new();
10547 for row_range in &row_ranges {
10548 let anchor = snapshot.anchor_before(Point::new(
10549 row_range.end.previous_row().0,
10550 snapshot.line_len(row_range.end.previous_row()),
10551 ));
10552 cursor_positions.push(anchor..anchor);
10553 }
10554
10555 self.transact(window, cx, |this, window, cx| {
10556 for row_range in row_ranges.into_iter().rev() {
10557 for row in row_range.iter_rows().rev() {
10558 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10559 let next_line_row = row.next_row();
10560 let indent = snapshot.indent_size_for_line(next_line_row);
10561 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10562
10563 let replace =
10564 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10565 " "
10566 } else {
10567 ""
10568 };
10569
10570 this.buffer.update(cx, |buffer, cx| {
10571 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10572 });
10573 }
10574 }
10575
10576 this.change_selections(Default::default(), window, cx, |s| {
10577 s.select_anchor_ranges(cursor_positions)
10578 });
10579 });
10580 }
10581
10582 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10583 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10584 self.join_lines_impl(true, window, cx);
10585 }
10586
10587 pub fn sort_lines_case_sensitive(
10588 &mut self,
10589 _: &SortLinesCaseSensitive,
10590 window: &mut Window,
10591 cx: &mut Context<Self>,
10592 ) {
10593 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10594 }
10595
10596 pub fn sort_lines_by_length(
10597 &mut self,
10598 _: &SortLinesByLength,
10599 window: &mut Window,
10600 cx: &mut Context<Self>,
10601 ) {
10602 self.manipulate_immutable_lines(window, cx, |lines| {
10603 lines.sort_by_key(|&line| line.chars().count())
10604 })
10605 }
10606
10607 pub fn sort_lines_case_insensitive(
10608 &mut self,
10609 _: &SortLinesCaseInsensitive,
10610 window: &mut Window,
10611 cx: &mut Context<Self>,
10612 ) {
10613 self.manipulate_immutable_lines(window, cx, |lines| {
10614 lines.sort_by_key(|line| line.to_lowercase())
10615 })
10616 }
10617
10618 pub fn unique_lines_case_insensitive(
10619 &mut self,
10620 _: &UniqueLinesCaseInsensitive,
10621 window: &mut Window,
10622 cx: &mut Context<Self>,
10623 ) {
10624 self.manipulate_immutable_lines(window, cx, |lines| {
10625 let mut seen = HashSet::default();
10626 lines.retain(|line| seen.insert(line.to_lowercase()));
10627 })
10628 }
10629
10630 pub fn unique_lines_case_sensitive(
10631 &mut self,
10632 _: &UniqueLinesCaseSensitive,
10633 window: &mut Window,
10634 cx: &mut Context<Self>,
10635 ) {
10636 self.manipulate_immutable_lines(window, cx, |lines| {
10637 let mut seen = HashSet::default();
10638 lines.retain(|line| seen.insert(*line));
10639 })
10640 }
10641
10642 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10643 let snapshot = self.buffer.read(cx).snapshot(cx);
10644 for selection in self.selections.disjoint_anchors_arc().iter() {
10645 if snapshot
10646 .language_at(selection.start)
10647 .and_then(|lang| lang.config().wrap_characters.as_ref())
10648 .is_some()
10649 {
10650 return true;
10651 }
10652 }
10653 false
10654 }
10655
10656 fn wrap_selections_in_tag(
10657 &mut self,
10658 _: &WrapSelectionsInTag,
10659 window: &mut Window,
10660 cx: &mut Context<Self>,
10661 ) {
10662 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10663
10664 let snapshot = self.buffer.read(cx).snapshot(cx);
10665
10666 let mut edits = Vec::new();
10667 let mut boundaries = Vec::new();
10668
10669 for selection in self.selections.all::<Point>(cx).iter() {
10670 let Some(wrap_config) = snapshot
10671 .language_at(selection.start)
10672 .and_then(|lang| lang.config().wrap_characters.clone())
10673 else {
10674 continue;
10675 };
10676
10677 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10678 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10679
10680 let start_before = snapshot.anchor_before(selection.start);
10681 let end_after = snapshot.anchor_after(selection.end);
10682
10683 edits.push((start_before..start_before, open_tag));
10684 edits.push((end_after..end_after, close_tag));
10685
10686 boundaries.push((
10687 start_before,
10688 end_after,
10689 wrap_config.start_prefix.len(),
10690 wrap_config.end_suffix.len(),
10691 ));
10692 }
10693
10694 if edits.is_empty() {
10695 return;
10696 }
10697
10698 self.transact(window, cx, |this, window, cx| {
10699 let buffer = this.buffer.update(cx, |buffer, cx| {
10700 buffer.edit(edits, None, cx);
10701 buffer.snapshot(cx)
10702 });
10703
10704 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10705 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10706 boundaries.into_iter()
10707 {
10708 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10709 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10710 new_selections.push(open_offset..open_offset);
10711 new_selections.push(close_offset..close_offset);
10712 }
10713
10714 this.change_selections(Default::default(), window, cx, |s| {
10715 s.select_ranges(new_selections);
10716 });
10717
10718 this.request_autoscroll(Autoscroll::fit(), cx);
10719 });
10720 }
10721
10722 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10723 let Some(project) = self.project.clone() else {
10724 return;
10725 };
10726 self.reload(project, window, cx)
10727 .detach_and_notify_err(window, cx);
10728 }
10729
10730 pub fn restore_file(
10731 &mut self,
10732 _: &::git::RestoreFile,
10733 window: &mut Window,
10734 cx: &mut Context<Self>,
10735 ) {
10736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10737 let mut buffer_ids = HashSet::default();
10738 let snapshot = self.buffer().read(cx).snapshot(cx);
10739 for selection in self.selections.all::<usize>(cx) {
10740 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10741 }
10742
10743 let buffer = self.buffer().read(cx);
10744 let ranges = buffer_ids
10745 .into_iter()
10746 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10747 .collect::<Vec<_>>();
10748
10749 self.restore_hunks_in_ranges(ranges, window, cx);
10750 }
10751
10752 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10754 let selections = self
10755 .selections
10756 .all(cx)
10757 .into_iter()
10758 .map(|s| s.range())
10759 .collect();
10760 self.restore_hunks_in_ranges(selections, window, cx);
10761 }
10762
10763 pub fn restore_hunks_in_ranges(
10764 &mut self,
10765 ranges: Vec<Range<Point>>,
10766 window: &mut Window,
10767 cx: &mut Context<Editor>,
10768 ) {
10769 let mut revert_changes = HashMap::default();
10770 let chunk_by = self
10771 .snapshot(window, cx)
10772 .hunks_for_ranges(ranges)
10773 .into_iter()
10774 .chunk_by(|hunk| hunk.buffer_id);
10775 for (buffer_id, hunks) in &chunk_by {
10776 let hunks = hunks.collect::<Vec<_>>();
10777 for hunk in &hunks {
10778 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10779 }
10780 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10781 }
10782 drop(chunk_by);
10783 if !revert_changes.is_empty() {
10784 self.transact(window, cx, |editor, window, cx| {
10785 editor.restore(revert_changes, window, cx);
10786 });
10787 }
10788 }
10789
10790 pub fn open_active_item_in_terminal(
10791 &mut self,
10792 _: &OpenInTerminal,
10793 window: &mut Window,
10794 cx: &mut Context<Self>,
10795 ) {
10796 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10797 let project_path = buffer.read(cx).project_path(cx)?;
10798 let project = self.project()?.read(cx);
10799 let entry = project.entry_for_path(&project_path, cx)?;
10800 let parent = match &entry.canonical_path {
10801 Some(canonical_path) => canonical_path.to_path_buf(),
10802 None => project.absolute_path(&project_path, cx)?,
10803 }
10804 .parent()?
10805 .to_path_buf();
10806 Some(parent)
10807 }) {
10808 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10809 }
10810 }
10811
10812 fn set_breakpoint_context_menu(
10813 &mut self,
10814 display_row: DisplayRow,
10815 position: Option<Anchor>,
10816 clicked_point: gpui::Point<Pixels>,
10817 window: &mut Window,
10818 cx: &mut Context<Self>,
10819 ) {
10820 let source = self
10821 .buffer
10822 .read(cx)
10823 .snapshot(cx)
10824 .anchor_before(Point::new(display_row.0, 0u32));
10825
10826 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10827
10828 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10829 self,
10830 source,
10831 clicked_point,
10832 context_menu,
10833 window,
10834 cx,
10835 );
10836 }
10837
10838 fn add_edit_breakpoint_block(
10839 &mut self,
10840 anchor: Anchor,
10841 breakpoint: &Breakpoint,
10842 edit_action: BreakpointPromptEditAction,
10843 window: &mut Window,
10844 cx: &mut Context<Self>,
10845 ) {
10846 let weak_editor = cx.weak_entity();
10847 let bp_prompt = cx.new(|cx| {
10848 BreakpointPromptEditor::new(
10849 weak_editor,
10850 anchor,
10851 breakpoint.clone(),
10852 edit_action,
10853 window,
10854 cx,
10855 )
10856 });
10857
10858 let height = bp_prompt.update(cx, |this, cx| {
10859 this.prompt
10860 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10861 });
10862 let cloned_prompt = bp_prompt.clone();
10863 let blocks = vec![BlockProperties {
10864 style: BlockStyle::Sticky,
10865 placement: BlockPlacement::Above(anchor),
10866 height: Some(height),
10867 render: Arc::new(move |cx| {
10868 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10869 cloned_prompt.clone().into_any_element()
10870 }),
10871 priority: 0,
10872 }];
10873
10874 let focus_handle = bp_prompt.focus_handle(cx);
10875 window.focus(&focus_handle);
10876
10877 let block_ids = self.insert_blocks(blocks, None, cx);
10878 bp_prompt.update(cx, |prompt, _| {
10879 prompt.add_block_ids(block_ids);
10880 });
10881 }
10882
10883 pub(crate) fn breakpoint_at_row(
10884 &self,
10885 row: u32,
10886 window: &mut Window,
10887 cx: &mut Context<Self>,
10888 ) -> Option<(Anchor, Breakpoint)> {
10889 let snapshot = self.snapshot(window, cx);
10890 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10891
10892 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10893 }
10894
10895 pub(crate) fn breakpoint_at_anchor(
10896 &self,
10897 breakpoint_position: Anchor,
10898 snapshot: &EditorSnapshot,
10899 cx: &mut Context<Self>,
10900 ) -> Option<(Anchor, Breakpoint)> {
10901 let buffer = self
10902 .buffer
10903 .read(cx)
10904 .buffer_for_anchor(breakpoint_position, cx)?;
10905
10906 let enclosing_excerpt = breakpoint_position.excerpt_id;
10907 let buffer_snapshot = buffer.read(cx).snapshot();
10908
10909 let row = buffer_snapshot
10910 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10911 .row;
10912
10913 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10914 let anchor_end = snapshot
10915 .buffer_snapshot
10916 .anchor_after(Point::new(row, line_len));
10917
10918 self.breakpoint_store
10919 .as_ref()?
10920 .read_with(cx, |breakpoint_store, cx| {
10921 breakpoint_store
10922 .breakpoints(
10923 &buffer,
10924 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10925 &buffer_snapshot,
10926 cx,
10927 )
10928 .next()
10929 .and_then(|(bp, _)| {
10930 let breakpoint_row = buffer_snapshot
10931 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10932 .row;
10933
10934 if breakpoint_row == row {
10935 snapshot
10936 .buffer_snapshot
10937 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10938 .map(|position| (position, bp.bp.clone()))
10939 } else {
10940 None
10941 }
10942 })
10943 })
10944 }
10945
10946 pub fn edit_log_breakpoint(
10947 &mut self,
10948 _: &EditLogBreakpoint,
10949 window: &mut Window,
10950 cx: &mut Context<Self>,
10951 ) {
10952 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10953 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10954 message: None,
10955 state: BreakpointState::Enabled,
10956 condition: None,
10957 hit_condition: None,
10958 });
10959
10960 self.add_edit_breakpoint_block(
10961 anchor,
10962 &breakpoint,
10963 BreakpointPromptEditAction::Log,
10964 window,
10965 cx,
10966 );
10967 }
10968 }
10969
10970 fn breakpoints_at_cursors(
10971 &self,
10972 window: &mut Window,
10973 cx: &mut Context<Self>,
10974 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10975 let snapshot = self.snapshot(window, cx);
10976 let cursors = self
10977 .selections
10978 .disjoint_anchors_arc()
10979 .iter()
10980 .map(|selection| {
10981 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10982
10983 let breakpoint_position = self
10984 .breakpoint_at_row(cursor_position.row, window, cx)
10985 .map(|bp| bp.0)
10986 .unwrap_or_else(|| {
10987 snapshot
10988 .display_snapshot
10989 .buffer_snapshot
10990 .anchor_after(Point::new(cursor_position.row, 0))
10991 });
10992
10993 let breakpoint = self
10994 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10995 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10996
10997 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10998 })
10999 // 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.
11000 .collect::<HashMap<Anchor, _>>();
11001
11002 cursors.into_iter().collect()
11003 }
11004
11005 pub fn enable_breakpoint(
11006 &mut self,
11007 _: &crate::actions::EnableBreakpoint,
11008 window: &mut Window,
11009 cx: &mut Context<Self>,
11010 ) {
11011 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11012 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11013 continue;
11014 };
11015 self.edit_breakpoint_at_anchor(
11016 anchor,
11017 breakpoint,
11018 BreakpointEditAction::InvertState,
11019 cx,
11020 );
11021 }
11022 }
11023
11024 pub fn disable_breakpoint(
11025 &mut self,
11026 _: &crate::actions::DisableBreakpoint,
11027 window: &mut Window,
11028 cx: &mut Context<Self>,
11029 ) {
11030 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11031 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11032 continue;
11033 };
11034 self.edit_breakpoint_at_anchor(
11035 anchor,
11036 breakpoint,
11037 BreakpointEditAction::InvertState,
11038 cx,
11039 );
11040 }
11041 }
11042
11043 pub fn toggle_breakpoint(
11044 &mut self,
11045 _: &crate::actions::ToggleBreakpoint,
11046 window: &mut Window,
11047 cx: &mut Context<Self>,
11048 ) {
11049 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11050 if let Some(breakpoint) = breakpoint {
11051 self.edit_breakpoint_at_anchor(
11052 anchor,
11053 breakpoint,
11054 BreakpointEditAction::Toggle,
11055 cx,
11056 );
11057 } else {
11058 self.edit_breakpoint_at_anchor(
11059 anchor,
11060 Breakpoint::new_standard(),
11061 BreakpointEditAction::Toggle,
11062 cx,
11063 );
11064 }
11065 }
11066 }
11067
11068 pub fn edit_breakpoint_at_anchor(
11069 &mut self,
11070 breakpoint_position: Anchor,
11071 breakpoint: Breakpoint,
11072 edit_action: BreakpointEditAction,
11073 cx: &mut Context<Self>,
11074 ) {
11075 let Some(breakpoint_store) = &self.breakpoint_store else {
11076 return;
11077 };
11078
11079 let Some(buffer) = self
11080 .buffer
11081 .read(cx)
11082 .buffer_for_anchor(breakpoint_position, cx)
11083 else {
11084 return;
11085 };
11086
11087 breakpoint_store.update(cx, |breakpoint_store, cx| {
11088 breakpoint_store.toggle_breakpoint(
11089 buffer,
11090 BreakpointWithPosition {
11091 position: breakpoint_position.text_anchor,
11092 bp: breakpoint,
11093 },
11094 edit_action,
11095 cx,
11096 );
11097 });
11098
11099 cx.notify();
11100 }
11101
11102 #[cfg(any(test, feature = "test-support"))]
11103 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11104 self.breakpoint_store.clone()
11105 }
11106
11107 pub fn prepare_restore_change(
11108 &self,
11109 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11110 hunk: &MultiBufferDiffHunk,
11111 cx: &mut App,
11112 ) -> Option<()> {
11113 if hunk.is_created_file() {
11114 return None;
11115 }
11116 let buffer = self.buffer.read(cx);
11117 let diff = buffer.diff_for(hunk.buffer_id)?;
11118 let buffer = buffer.buffer(hunk.buffer_id)?;
11119 let buffer = buffer.read(cx);
11120 let original_text = diff
11121 .read(cx)
11122 .base_text()
11123 .as_rope()
11124 .slice(hunk.diff_base_byte_range.clone());
11125 let buffer_snapshot = buffer.snapshot();
11126 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11127 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11128 probe
11129 .0
11130 .start
11131 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11132 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11133 }) {
11134 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11135 Some(())
11136 } else {
11137 None
11138 }
11139 }
11140
11141 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11142 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11143 }
11144
11145 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11146 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11147 }
11148
11149 fn manipulate_lines<M>(
11150 &mut self,
11151 window: &mut Window,
11152 cx: &mut Context<Self>,
11153 mut manipulate: M,
11154 ) where
11155 M: FnMut(&str) -> LineManipulationResult,
11156 {
11157 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11158
11159 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11160 let buffer = self.buffer.read(cx).snapshot(cx);
11161
11162 let mut edits = Vec::new();
11163
11164 let selections = self.selections.all::<Point>(cx);
11165 let mut selections = selections.iter().peekable();
11166 let mut contiguous_row_selections = Vec::new();
11167 let mut new_selections = Vec::new();
11168 let mut added_lines = 0;
11169 let mut removed_lines = 0;
11170
11171 while let Some(selection) = selections.next() {
11172 let (start_row, end_row) = consume_contiguous_rows(
11173 &mut contiguous_row_selections,
11174 selection,
11175 &display_map,
11176 &mut selections,
11177 );
11178
11179 let start_point = Point::new(start_row.0, 0);
11180 let end_point = Point::new(
11181 end_row.previous_row().0,
11182 buffer.line_len(end_row.previous_row()),
11183 );
11184 let text = buffer
11185 .text_for_range(start_point..end_point)
11186 .collect::<String>();
11187
11188 let LineManipulationResult {
11189 new_text,
11190 line_count_before,
11191 line_count_after,
11192 } = manipulate(&text);
11193
11194 edits.push((start_point..end_point, new_text));
11195
11196 // Selections must change based on added and removed line count
11197 let start_row =
11198 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11199 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11200 new_selections.push(Selection {
11201 id: selection.id,
11202 start: start_row,
11203 end: end_row,
11204 goal: SelectionGoal::None,
11205 reversed: selection.reversed,
11206 });
11207
11208 if line_count_after > line_count_before {
11209 added_lines += line_count_after - line_count_before;
11210 } else if line_count_before > line_count_after {
11211 removed_lines += line_count_before - line_count_after;
11212 }
11213 }
11214
11215 self.transact(window, cx, |this, window, cx| {
11216 let buffer = this.buffer.update(cx, |buffer, cx| {
11217 buffer.edit(edits, None, cx);
11218 buffer.snapshot(cx)
11219 });
11220
11221 // Recalculate offsets on newly edited buffer
11222 let new_selections = new_selections
11223 .iter()
11224 .map(|s| {
11225 let start_point = Point::new(s.start.0, 0);
11226 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11227 Selection {
11228 id: s.id,
11229 start: buffer.point_to_offset(start_point),
11230 end: buffer.point_to_offset(end_point),
11231 goal: s.goal,
11232 reversed: s.reversed,
11233 }
11234 })
11235 .collect();
11236
11237 this.change_selections(Default::default(), window, cx, |s| {
11238 s.select(new_selections);
11239 });
11240
11241 this.request_autoscroll(Autoscroll::fit(), cx);
11242 });
11243 }
11244
11245 fn manipulate_immutable_lines<Fn>(
11246 &mut self,
11247 window: &mut Window,
11248 cx: &mut Context<Self>,
11249 mut callback: Fn,
11250 ) where
11251 Fn: FnMut(&mut Vec<&str>),
11252 {
11253 self.manipulate_lines(window, cx, |text| {
11254 let mut lines: Vec<&str> = text.split('\n').collect();
11255 let line_count_before = lines.len();
11256
11257 callback(&mut lines);
11258
11259 LineManipulationResult {
11260 new_text: lines.join("\n"),
11261 line_count_before,
11262 line_count_after: lines.len(),
11263 }
11264 });
11265 }
11266
11267 fn manipulate_mutable_lines<Fn>(
11268 &mut self,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 mut callback: Fn,
11272 ) where
11273 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11274 {
11275 self.manipulate_lines(window, cx, |text| {
11276 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11277 let line_count_before = lines.len();
11278
11279 callback(&mut lines);
11280
11281 LineManipulationResult {
11282 new_text: lines.join("\n"),
11283 line_count_before,
11284 line_count_after: lines.len(),
11285 }
11286 });
11287 }
11288
11289 pub fn convert_indentation_to_spaces(
11290 &mut self,
11291 _: &ConvertIndentationToSpaces,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 let settings = self.buffer.read(cx).language_settings(cx);
11296 let tab_size = settings.tab_size.get() as usize;
11297
11298 self.manipulate_mutable_lines(window, cx, |lines| {
11299 // Allocates a reasonably sized scratch buffer once for the whole loop
11300 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11301 // Avoids recomputing spaces that could be inserted many times
11302 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11303 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11304 .collect();
11305
11306 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11307 let mut chars = line.as_ref().chars();
11308 let mut col = 0;
11309 let mut changed = false;
11310
11311 for ch in chars.by_ref() {
11312 match ch {
11313 ' ' => {
11314 reindented_line.push(' ');
11315 col += 1;
11316 }
11317 '\t' => {
11318 // \t are converted to spaces depending on the current column
11319 let spaces_len = tab_size - (col % tab_size);
11320 reindented_line.extend(&space_cache[spaces_len - 1]);
11321 col += spaces_len;
11322 changed = true;
11323 }
11324 _ => {
11325 // If we dont append before break, the character is consumed
11326 reindented_line.push(ch);
11327 break;
11328 }
11329 }
11330 }
11331
11332 if !changed {
11333 reindented_line.clear();
11334 continue;
11335 }
11336 // Append the rest of the line and replace old reference with new one
11337 reindented_line.extend(chars);
11338 *line = Cow::Owned(reindented_line.clone());
11339 reindented_line.clear();
11340 }
11341 });
11342 }
11343
11344 pub fn convert_indentation_to_tabs(
11345 &mut self,
11346 _: &ConvertIndentationToTabs,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 let settings = self.buffer.read(cx).language_settings(cx);
11351 let tab_size = settings.tab_size.get() as usize;
11352
11353 self.manipulate_mutable_lines(window, cx, |lines| {
11354 // Allocates a reasonably sized buffer once for the whole loop
11355 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11356 // Avoids recomputing spaces that could be inserted many times
11357 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11358 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11359 .collect();
11360
11361 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11362 let mut chars = line.chars();
11363 let mut spaces_count = 0;
11364 let mut first_non_indent_char = None;
11365 let mut changed = false;
11366
11367 for ch in chars.by_ref() {
11368 match ch {
11369 ' ' => {
11370 // Keep track of spaces. Append \t when we reach tab_size
11371 spaces_count += 1;
11372 changed = true;
11373 if spaces_count == tab_size {
11374 reindented_line.push('\t');
11375 spaces_count = 0;
11376 }
11377 }
11378 '\t' => {
11379 reindented_line.push('\t');
11380 spaces_count = 0;
11381 }
11382 _ => {
11383 // Dont append it yet, we might have remaining spaces
11384 first_non_indent_char = Some(ch);
11385 break;
11386 }
11387 }
11388 }
11389
11390 if !changed {
11391 reindented_line.clear();
11392 continue;
11393 }
11394 // Remaining spaces that didn't make a full tab stop
11395 if spaces_count > 0 {
11396 reindented_line.extend(&space_cache[spaces_count - 1]);
11397 }
11398 // If we consume an extra character that was not indentation, add it back
11399 if let Some(extra_char) = first_non_indent_char {
11400 reindented_line.push(extra_char);
11401 }
11402 // Append the rest of the line and replace old reference with new one
11403 reindented_line.extend(chars);
11404 *line = Cow::Owned(reindented_line.clone());
11405 reindented_line.clear();
11406 }
11407 });
11408 }
11409
11410 pub fn convert_to_upper_case(
11411 &mut self,
11412 _: &ConvertToUpperCase,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 self.manipulate_text(window, cx, |text| text.to_uppercase())
11417 }
11418
11419 pub fn convert_to_lower_case(
11420 &mut self,
11421 _: &ConvertToLowerCase,
11422 window: &mut Window,
11423 cx: &mut Context<Self>,
11424 ) {
11425 self.manipulate_text(window, cx, |text| text.to_lowercase())
11426 }
11427
11428 pub fn convert_to_title_case(
11429 &mut self,
11430 _: &ConvertToTitleCase,
11431 window: &mut Window,
11432 cx: &mut Context<Self>,
11433 ) {
11434 self.manipulate_text(window, cx, |text| {
11435 text.split('\n')
11436 .map(|line| line.to_case(Case::Title))
11437 .join("\n")
11438 })
11439 }
11440
11441 pub fn convert_to_snake_case(
11442 &mut self,
11443 _: &ConvertToSnakeCase,
11444 window: &mut Window,
11445 cx: &mut Context<Self>,
11446 ) {
11447 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11448 }
11449
11450 pub fn convert_to_kebab_case(
11451 &mut self,
11452 _: &ConvertToKebabCase,
11453 window: &mut Window,
11454 cx: &mut Context<Self>,
11455 ) {
11456 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11457 }
11458
11459 pub fn convert_to_upper_camel_case(
11460 &mut self,
11461 _: &ConvertToUpperCamelCase,
11462 window: &mut Window,
11463 cx: &mut Context<Self>,
11464 ) {
11465 self.manipulate_text(window, cx, |text| {
11466 text.split('\n')
11467 .map(|line| line.to_case(Case::UpperCamel))
11468 .join("\n")
11469 })
11470 }
11471
11472 pub fn convert_to_lower_camel_case(
11473 &mut self,
11474 _: &ConvertToLowerCamelCase,
11475 window: &mut Window,
11476 cx: &mut Context<Self>,
11477 ) {
11478 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11479 }
11480
11481 pub fn convert_to_opposite_case(
11482 &mut self,
11483 _: &ConvertToOppositeCase,
11484 window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 self.manipulate_text(window, cx, |text| {
11488 text.chars()
11489 .fold(String::with_capacity(text.len()), |mut t, c| {
11490 if c.is_uppercase() {
11491 t.extend(c.to_lowercase());
11492 } else {
11493 t.extend(c.to_uppercase());
11494 }
11495 t
11496 })
11497 })
11498 }
11499
11500 pub fn convert_to_sentence_case(
11501 &mut self,
11502 _: &ConvertToSentenceCase,
11503 window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11507 }
11508
11509 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11510 self.manipulate_text(window, cx, |text| {
11511 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11512 if has_upper_case_characters {
11513 text.to_lowercase()
11514 } else {
11515 text.to_uppercase()
11516 }
11517 })
11518 }
11519
11520 pub fn convert_to_rot13(
11521 &mut self,
11522 _: &ConvertToRot13,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 self.manipulate_text(window, cx, |text| {
11527 text.chars()
11528 .map(|c| match c {
11529 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11530 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11531 _ => c,
11532 })
11533 .collect()
11534 })
11535 }
11536
11537 pub fn convert_to_rot47(
11538 &mut self,
11539 _: &ConvertToRot47,
11540 window: &mut Window,
11541 cx: &mut Context<Self>,
11542 ) {
11543 self.manipulate_text(window, cx, |text| {
11544 text.chars()
11545 .map(|c| {
11546 let code_point = c as u32;
11547 if code_point >= 33 && code_point <= 126 {
11548 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11549 }
11550 c
11551 })
11552 .collect()
11553 })
11554 }
11555
11556 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11557 where
11558 Fn: FnMut(&str) -> String,
11559 {
11560 let buffer = self.buffer.read(cx).snapshot(cx);
11561
11562 let mut new_selections = Vec::new();
11563 let mut edits = Vec::new();
11564 let mut selection_adjustment = 0i32;
11565
11566 for selection in self.selections.all_adjusted(cx) {
11567 let selection_is_empty = selection.is_empty();
11568
11569 let (start, end) = if selection_is_empty {
11570 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11571 (word_range.start, word_range.end)
11572 } else {
11573 (
11574 buffer.point_to_offset(selection.start),
11575 buffer.point_to_offset(selection.end),
11576 )
11577 };
11578
11579 let text = buffer.text_for_range(start..end).collect::<String>();
11580 let old_length = text.len() as i32;
11581 let text = callback(&text);
11582
11583 new_selections.push(Selection {
11584 start: (start as i32 - selection_adjustment) as usize,
11585 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11586 goal: SelectionGoal::None,
11587 id: selection.id,
11588 reversed: selection.reversed,
11589 });
11590
11591 selection_adjustment += old_length - text.len() as i32;
11592
11593 edits.push((start..end, text));
11594 }
11595
11596 self.transact(window, cx, |this, window, cx| {
11597 this.buffer.update(cx, |buffer, cx| {
11598 buffer.edit(edits, None, cx);
11599 });
11600
11601 this.change_selections(Default::default(), window, cx, |s| {
11602 s.select(new_selections);
11603 });
11604
11605 this.request_autoscroll(Autoscroll::fit(), cx);
11606 });
11607 }
11608
11609 pub fn move_selection_on_drop(
11610 &mut self,
11611 selection: &Selection<Anchor>,
11612 target: DisplayPoint,
11613 is_cut: bool,
11614 window: &mut Window,
11615 cx: &mut Context<Self>,
11616 ) {
11617 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11618 let buffer = &display_map.buffer_snapshot;
11619 let mut edits = Vec::new();
11620 let insert_point = display_map
11621 .clip_point(target, Bias::Left)
11622 .to_point(&display_map);
11623 let text = buffer
11624 .text_for_range(selection.start..selection.end)
11625 .collect::<String>();
11626 if is_cut {
11627 edits.push(((selection.start..selection.end), String::new()));
11628 }
11629 let insert_anchor = buffer.anchor_before(insert_point);
11630 edits.push(((insert_anchor..insert_anchor), text));
11631 let last_edit_start = insert_anchor.bias_left(buffer);
11632 let last_edit_end = insert_anchor.bias_right(buffer);
11633 self.transact(window, cx, |this, window, cx| {
11634 this.buffer.update(cx, |buffer, cx| {
11635 buffer.edit(edits, None, cx);
11636 });
11637 this.change_selections(Default::default(), window, cx, |s| {
11638 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11639 });
11640 });
11641 }
11642
11643 pub fn clear_selection_drag_state(&mut self) {
11644 self.selection_drag_state = SelectionDragState::None;
11645 }
11646
11647 pub fn duplicate(
11648 &mut self,
11649 upwards: bool,
11650 whole_lines: bool,
11651 window: &mut Window,
11652 cx: &mut Context<Self>,
11653 ) {
11654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11655
11656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11657 let buffer = &display_map.buffer_snapshot;
11658 let selections = self.selections.all::<Point>(cx);
11659
11660 let mut edits = Vec::new();
11661 let mut selections_iter = selections.iter().peekable();
11662 while let Some(selection) = selections_iter.next() {
11663 let mut rows = selection.spanned_rows(false, &display_map);
11664 // duplicate line-wise
11665 if whole_lines || selection.start == selection.end {
11666 // Avoid duplicating the same lines twice.
11667 while let Some(next_selection) = selections_iter.peek() {
11668 let next_rows = next_selection.spanned_rows(false, &display_map);
11669 if next_rows.start < rows.end {
11670 rows.end = next_rows.end;
11671 selections_iter.next().unwrap();
11672 } else {
11673 break;
11674 }
11675 }
11676
11677 // Copy the text from the selected row region and splice it either at the start
11678 // or end of the region.
11679 let start = Point::new(rows.start.0, 0);
11680 let end = Point::new(
11681 rows.end.previous_row().0,
11682 buffer.line_len(rows.end.previous_row()),
11683 );
11684 let text = buffer
11685 .text_for_range(start..end)
11686 .chain(Some("\n"))
11687 .collect::<String>();
11688 let insert_location = if upwards {
11689 Point::new(rows.end.0, 0)
11690 } else {
11691 start
11692 };
11693 edits.push((insert_location..insert_location, text));
11694 } else {
11695 // duplicate character-wise
11696 let start = selection.start;
11697 let end = selection.end;
11698 let text = buffer.text_for_range(start..end).collect::<String>();
11699 edits.push((selection.end..selection.end, text));
11700 }
11701 }
11702
11703 self.transact(window, cx, |this, _, cx| {
11704 this.buffer.update(cx, |buffer, cx| {
11705 buffer.edit(edits, None, cx);
11706 });
11707
11708 this.request_autoscroll(Autoscroll::fit(), cx);
11709 });
11710 }
11711
11712 pub fn duplicate_line_up(
11713 &mut self,
11714 _: &DuplicateLineUp,
11715 window: &mut Window,
11716 cx: &mut Context<Self>,
11717 ) {
11718 self.duplicate(true, true, window, cx);
11719 }
11720
11721 pub fn duplicate_line_down(
11722 &mut self,
11723 _: &DuplicateLineDown,
11724 window: &mut Window,
11725 cx: &mut Context<Self>,
11726 ) {
11727 self.duplicate(false, true, window, cx);
11728 }
11729
11730 pub fn duplicate_selection(
11731 &mut self,
11732 _: &DuplicateSelection,
11733 window: &mut Window,
11734 cx: &mut Context<Self>,
11735 ) {
11736 self.duplicate(false, false, window, cx);
11737 }
11738
11739 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11741 if self.mode.is_single_line() {
11742 cx.propagate();
11743 return;
11744 }
11745
11746 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11747 let buffer = self.buffer.read(cx).snapshot(cx);
11748
11749 let mut edits = Vec::new();
11750 let mut unfold_ranges = Vec::new();
11751 let mut refold_creases = Vec::new();
11752
11753 let selections = self.selections.all::<Point>(cx);
11754 let mut selections = selections.iter().peekable();
11755 let mut contiguous_row_selections = Vec::new();
11756 let mut new_selections = Vec::new();
11757
11758 while let Some(selection) = selections.next() {
11759 // Find all the selections that span a contiguous row range
11760 let (start_row, end_row) = consume_contiguous_rows(
11761 &mut contiguous_row_selections,
11762 selection,
11763 &display_map,
11764 &mut selections,
11765 );
11766
11767 // Move the text spanned by the row range to be before the line preceding the row range
11768 if start_row.0 > 0 {
11769 let range_to_move = Point::new(
11770 start_row.previous_row().0,
11771 buffer.line_len(start_row.previous_row()),
11772 )
11773 ..Point::new(
11774 end_row.previous_row().0,
11775 buffer.line_len(end_row.previous_row()),
11776 );
11777 let insertion_point = display_map
11778 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11779 .0;
11780
11781 // Don't move lines across excerpts
11782 if buffer
11783 .excerpt_containing(insertion_point..range_to_move.end)
11784 .is_some()
11785 {
11786 let text = buffer
11787 .text_for_range(range_to_move.clone())
11788 .flat_map(|s| s.chars())
11789 .skip(1)
11790 .chain(['\n'])
11791 .collect::<String>();
11792
11793 edits.push((
11794 buffer.anchor_after(range_to_move.start)
11795 ..buffer.anchor_before(range_to_move.end),
11796 String::new(),
11797 ));
11798 let insertion_anchor = buffer.anchor_after(insertion_point);
11799 edits.push((insertion_anchor..insertion_anchor, text));
11800
11801 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11802
11803 // Move selections up
11804 new_selections.extend(contiguous_row_selections.drain(..).map(
11805 |mut selection| {
11806 selection.start.row -= row_delta;
11807 selection.end.row -= row_delta;
11808 selection
11809 },
11810 ));
11811
11812 // Move folds up
11813 unfold_ranges.push(range_to_move.clone());
11814 for fold in display_map.folds_in_range(
11815 buffer.anchor_before(range_to_move.start)
11816 ..buffer.anchor_after(range_to_move.end),
11817 ) {
11818 let mut start = fold.range.start.to_point(&buffer);
11819 let mut end = fold.range.end.to_point(&buffer);
11820 start.row -= row_delta;
11821 end.row -= row_delta;
11822 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11823 }
11824 }
11825 }
11826
11827 // If we didn't move line(s), preserve the existing selections
11828 new_selections.append(&mut contiguous_row_selections);
11829 }
11830
11831 self.transact(window, cx, |this, window, cx| {
11832 this.unfold_ranges(&unfold_ranges, true, true, cx);
11833 this.buffer.update(cx, |buffer, cx| {
11834 for (range, text) in edits {
11835 buffer.edit([(range, text)], None, cx);
11836 }
11837 });
11838 this.fold_creases(refold_creases, true, window, cx);
11839 this.change_selections(Default::default(), window, cx, |s| {
11840 s.select(new_selections);
11841 })
11842 });
11843 }
11844
11845 pub fn move_line_down(
11846 &mut self,
11847 _: &MoveLineDown,
11848 window: &mut Window,
11849 cx: &mut Context<Self>,
11850 ) {
11851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11852 if self.mode.is_single_line() {
11853 cx.propagate();
11854 return;
11855 }
11856
11857 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11858 let buffer = self.buffer.read(cx).snapshot(cx);
11859
11860 let mut edits = Vec::new();
11861 let mut unfold_ranges = Vec::new();
11862 let mut refold_creases = Vec::new();
11863
11864 let selections = self.selections.all::<Point>(cx);
11865 let mut selections = selections.iter().peekable();
11866 let mut contiguous_row_selections = Vec::new();
11867 let mut new_selections = Vec::new();
11868
11869 while let Some(selection) = selections.next() {
11870 // Find all the selections that span a contiguous row range
11871 let (start_row, end_row) = consume_contiguous_rows(
11872 &mut contiguous_row_selections,
11873 selection,
11874 &display_map,
11875 &mut selections,
11876 );
11877
11878 // Move the text spanned by the row range to be after the last line of the row range
11879 if end_row.0 <= buffer.max_point().row {
11880 let range_to_move =
11881 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11882 let insertion_point = display_map
11883 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11884 .0;
11885
11886 // Don't move lines across excerpt boundaries
11887 if buffer
11888 .excerpt_containing(range_to_move.start..insertion_point)
11889 .is_some()
11890 {
11891 let mut text = String::from("\n");
11892 text.extend(buffer.text_for_range(range_to_move.clone()));
11893 text.pop(); // Drop trailing newline
11894 edits.push((
11895 buffer.anchor_after(range_to_move.start)
11896 ..buffer.anchor_before(range_to_move.end),
11897 String::new(),
11898 ));
11899 let insertion_anchor = buffer.anchor_after(insertion_point);
11900 edits.push((insertion_anchor..insertion_anchor, text));
11901
11902 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11903
11904 // Move selections down
11905 new_selections.extend(contiguous_row_selections.drain(..).map(
11906 |mut selection| {
11907 selection.start.row += row_delta;
11908 selection.end.row += row_delta;
11909 selection
11910 },
11911 ));
11912
11913 // Move folds down
11914 unfold_ranges.push(range_to_move.clone());
11915 for fold in display_map.folds_in_range(
11916 buffer.anchor_before(range_to_move.start)
11917 ..buffer.anchor_after(range_to_move.end),
11918 ) {
11919 let mut start = fold.range.start.to_point(&buffer);
11920 let mut end = fold.range.end.to_point(&buffer);
11921 start.row += row_delta;
11922 end.row += row_delta;
11923 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11924 }
11925 }
11926 }
11927
11928 // If we didn't move line(s), preserve the existing selections
11929 new_selections.append(&mut contiguous_row_selections);
11930 }
11931
11932 self.transact(window, cx, |this, window, cx| {
11933 this.unfold_ranges(&unfold_ranges, true, true, cx);
11934 this.buffer.update(cx, |buffer, cx| {
11935 for (range, text) in edits {
11936 buffer.edit([(range, text)], None, cx);
11937 }
11938 });
11939 this.fold_creases(refold_creases, true, window, cx);
11940 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11941 });
11942 }
11943
11944 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11946 let text_layout_details = &self.text_layout_details(window);
11947 self.transact(window, cx, |this, window, cx| {
11948 let edits = this.change_selections(Default::default(), window, cx, |s| {
11949 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11950 s.move_with(|display_map, selection| {
11951 if !selection.is_empty() {
11952 return;
11953 }
11954
11955 let mut head = selection.head();
11956 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11957 if head.column() == display_map.line_len(head.row()) {
11958 transpose_offset = display_map
11959 .buffer_snapshot
11960 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11961 }
11962
11963 if transpose_offset == 0 {
11964 return;
11965 }
11966
11967 *head.column_mut() += 1;
11968 head = display_map.clip_point(head, Bias::Right);
11969 let goal = SelectionGoal::HorizontalPosition(
11970 display_map
11971 .x_for_display_point(head, text_layout_details)
11972 .into(),
11973 );
11974 selection.collapse_to(head, goal);
11975
11976 let transpose_start = display_map
11977 .buffer_snapshot
11978 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11979 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11980 let transpose_end = display_map
11981 .buffer_snapshot
11982 .clip_offset(transpose_offset + 1, Bias::Right);
11983 if let Some(ch) =
11984 display_map.buffer_snapshot.chars_at(transpose_start).next()
11985 {
11986 edits.push((transpose_start..transpose_offset, String::new()));
11987 edits.push((transpose_end..transpose_end, ch.to_string()));
11988 }
11989 }
11990 });
11991 edits
11992 });
11993 this.buffer
11994 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11995 let selections = this.selections.all::<usize>(cx);
11996 this.change_selections(Default::default(), window, cx, |s| {
11997 s.select(selections);
11998 });
11999 });
12000 }
12001
12002 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12004 if self.mode.is_single_line() {
12005 cx.propagate();
12006 return;
12007 }
12008
12009 self.rewrap_impl(RewrapOptions::default(), cx)
12010 }
12011
12012 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12013 let buffer = self.buffer.read(cx).snapshot(cx);
12014 let selections = self.selections.all::<Point>(cx);
12015
12016 #[derive(Clone, Debug, PartialEq)]
12017 enum CommentFormat {
12018 /// single line comment, with prefix for line
12019 Line(String),
12020 /// single line within a block comment, with prefix for line
12021 BlockLine(String),
12022 /// a single line of a block comment that includes the initial delimiter
12023 BlockCommentWithStart(BlockCommentConfig),
12024 /// a single line of a block comment that includes the ending delimiter
12025 BlockCommentWithEnd(BlockCommentConfig),
12026 }
12027
12028 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12029 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12030 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12031 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12032 .peekable();
12033
12034 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12035 row
12036 } else {
12037 return Vec::new();
12038 };
12039
12040 let language_settings = buffer.language_settings_at(selection.head(), cx);
12041 let language_scope = buffer.language_scope_at(selection.head());
12042
12043 let indent_and_prefix_for_row =
12044 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12045 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12046 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12047 &language_scope
12048 {
12049 let indent_end = Point::new(row, indent.len);
12050 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12051 let line_text_after_indent = buffer
12052 .text_for_range(indent_end..line_end)
12053 .collect::<String>();
12054
12055 let is_within_comment_override = buffer
12056 .language_scope_at(indent_end)
12057 .is_some_and(|scope| scope.override_name() == Some("comment"));
12058 let comment_delimiters = if is_within_comment_override {
12059 // we are within a comment syntax node, but we don't
12060 // yet know what kind of comment: block, doc or line
12061 match (
12062 language_scope.documentation_comment(),
12063 language_scope.block_comment(),
12064 ) {
12065 (Some(config), _) | (_, Some(config))
12066 if buffer.contains_str_at(indent_end, &config.start) =>
12067 {
12068 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12069 }
12070 (Some(config), _) | (_, Some(config))
12071 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12072 {
12073 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12074 }
12075 (Some(config), _) | (_, Some(config))
12076 if buffer.contains_str_at(indent_end, &config.prefix) =>
12077 {
12078 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12079 }
12080 (_, _) => language_scope
12081 .line_comment_prefixes()
12082 .iter()
12083 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12084 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12085 }
12086 } else {
12087 // we not in an overridden comment node, but we may
12088 // be within a non-overridden line comment node
12089 language_scope
12090 .line_comment_prefixes()
12091 .iter()
12092 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12093 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12094 };
12095
12096 let rewrap_prefix = language_scope
12097 .rewrap_prefixes()
12098 .iter()
12099 .find_map(|prefix_regex| {
12100 prefix_regex.find(&line_text_after_indent).map(|mat| {
12101 if mat.start() == 0 {
12102 Some(mat.as_str().to_string())
12103 } else {
12104 None
12105 }
12106 })
12107 })
12108 .flatten();
12109 (comment_delimiters, rewrap_prefix)
12110 } else {
12111 (None, None)
12112 };
12113 (indent, comment_prefix, rewrap_prefix)
12114 };
12115
12116 let mut ranges = Vec::new();
12117 let from_empty_selection = selection.is_empty();
12118
12119 let mut current_range_start = first_row;
12120 let mut prev_row = first_row;
12121 let (
12122 mut current_range_indent,
12123 mut current_range_comment_delimiters,
12124 mut current_range_rewrap_prefix,
12125 ) = indent_and_prefix_for_row(first_row);
12126
12127 for row in non_blank_rows_iter.skip(1) {
12128 let has_paragraph_break = row > prev_row + 1;
12129
12130 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12131 indent_and_prefix_for_row(row);
12132
12133 let has_indent_change = row_indent != current_range_indent;
12134 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12135
12136 let has_boundary_change = has_comment_change
12137 || row_rewrap_prefix.is_some()
12138 || (has_indent_change && current_range_comment_delimiters.is_some());
12139
12140 if has_paragraph_break || has_boundary_change {
12141 ranges.push((
12142 language_settings.clone(),
12143 Point::new(current_range_start, 0)
12144 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12145 current_range_indent,
12146 current_range_comment_delimiters.clone(),
12147 current_range_rewrap_prefix.clone(),
12148 from_empty_selection,
12149 ));
12150 current_range_start = row;
12151 current_range_indent = row_indent;
12152 current_range_comment_delimiters = row_comment_delimiters;
12153 current_range_rewrap_prefix = row_rewrap_prefix;
12154 }
12155 prev_row = row;
12156 }
12157
12158 ranges.push((
12159 language_settings.clone(),
12160 Point::new(current_range_start, 0)
12161 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12162 current_range_indent,
12163 current_range_comment_delimiters,
12164 current_range_rewrap_prefix,
12165 from_empty_selection,
12166 ));
12167
12168 ranges
12169 });
12170
12171 let mut edits = Vec::new();
12172 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12173
12174 for (
12175 language_settings,
12176 wrap_range,
12177 mut indent_size,
12178 comment_prefix,
12179 rewrap_prefix,
12180 from_empty_selection,
12181 ) in wrap_ranges
12182 {
12183 let mut start_row = wrap_range.start.row;
12184 let mut end_row = wrap_range.end.row;
12185
12186 // Skip selections that overlap with a range that has already been rewrapped.
12187 let selection_range = start_row..end_row;
12188 if rewrapped_row_ranges
12189 .iter()
12190 .any(|range| range.overlaps(&selection_range))
12191 {
12192 continue;
12193 }
12194
12195 let tab_size = language_settings.tab_size;
12196
12197 let (line_prefix, inside_comment) = match &comment_prefix {
12198 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12199 (Some(prefix.as_str()), true)
12200 }
12201 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12202 (Some(prefix.as_ref()), true)
12203 }
12204 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12205 start: _,
12206 end: _,
12207 prefix,
12208 tab_size,
12209 })) => {
12210 indent_size.len += tab_size;
12211 (Some(prefix.as_ref()), true)
12212 }
12213 None => (None, false),
12214 };
12215 let indent_prefix = indent_size.chars().collect::<String>();
12216 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12217
12218 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12219 RewrapBehavior::InComments => inside_comment,
12220 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12221 RewrapBehavior::Anywhere => true,
12222 };
12223
12224 let should_rewrap = options.override_language_settings
12225 || allow_rewrap_based_on_language
12226 || self.hard_wrap.is_some();
12227 if !should_rewrap {
12228 continue;
12229 }
12230
12231 if from_empty_selection {
12232 'expand_upwards: while start_row > 0 {
12233 let prev_row = start_row - 1;
12234 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12235 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12236 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12237 {
12238 start_row = prev_row;
12239 } else {
12240 break 'expand_upwards;
12241 }
12242 }
12243
12244 'expand_downwards: while end_row < buffer.max_point().row {
12245 let next_row = end_row + 1;
12246 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12247 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12248 && !buffer.is_line_blank(MultiBufferRow(next_row))
12249 {
12250 end_row = next_row;
12251 } else {
12252 break 'expand_downwards;
12253 }
12254 }
12255 }
12256
12257 let start = Point::new(start_row, 0);
12258 let start_offset = start.to_offset(&buffer);
12259 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12260 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12261 let mut first_line_delimiter = None;
12262 let mut last_line_delimiter = None;
12263 let Some(lines_without_prefixes) = selection_text
12264 .lines()
12265 .enumerate()
12266 .map(|(ix, line)| {
12267 let line_trimmed = line.trim_start();
12268 if rewrap_prefix.is_some() && ix > 0 {
12269 Ok(line_trimmed)
12270 } else if let Some(
12271 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12272 start,
12273 prefix,
12274 end,
12275 tab_size,
12276 })
12277 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12278 start,
12279 prefix,
12280 end,
12281 tab_size,
12282 }),
12283 ) = &comment_prefix
12284 {
12285 let line_trimmed = line_trimmed
12286 .strip_prefix(start.as_ref())
12287 .map(|s| {
12288 let mut indent_size = indent_size;
12289 indent_size.len -= tab_size;
12290 let indent_prefix: String = indent_size.chars().collect();
12291 first_line_delimiter = Some((indent_prefix, start));
12292 s.trim_start()
12293 })
12294 .unwrap_or(line_trimmed);
12295 let line_trimmed = line_trimmed
12296 .strip_suffix(end.as_ref())
12297 .map(|s| {
12298 last_line_delimiter = Some(end);
12299 s.trim_end()
12300 })
12301 .unwrap_or(line_trimmed);
12302 let line_trimmed = line_trimmed
12303 .strip_prefix(prefix.as_ref())
12304 .unwrap_or(line_trimmed);
12305 Ok(line_trimmed)
12306 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12307 line_trimmed.strip_prefix(prefix).with_context(|| {
12308 format!("line did not start with prefix {prefix:?}: {line:?}")
12309 })
12310 } else {
12311 line_trimmed
12312 .strip_prefix(&line_prefix.trim_start())
12313 .with_context(|| {
12314 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12315 })
12316 }
12317 })
12318 .collect::<Result<Vec<_>, _>>()
12319 .log_err()
12320 else {
12321 continue;
12322 };
12323
12324 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12325 buffer
12326 .language_settings_at(Point::new(start_row, 0), cx)
12327 .preferred_line_length as usize
12328 });
12329
12330 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12331 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12332 } else {
12333 line_prefix.clone()
12334 };
12335
12336 let wrapped_text = {
12337 let mut wrapped_text = wrap_with_prefix(
12338 line_prefix,
12339 subsequent_lines_prefix,
12340 lines_without_prefixes.join("\n"),
12341 wrap_column,
12342 tab_size,
12343 options.preserve_existing_whitespace,
12344 );
12345
12346 if let Some((indent, delimiter)) = first_line_delimiter {
12347 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12348 }
12349 if let Some(last_line) = last_line_delimiter {
12350 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12351 }
12352
12353 wrapped_text
12354 };
12355
12356 // TODO: should always use char-based diff while still supporting cursor behavior that
12357 // matches vim.
12358 let mut diff_options = DiffOptions::default();
12359 if options.override_language_settings {
12360 diff_options.max_word_diff_len = 0;
12361 diff_options.max_word_diff_line_count = 0;
12362 } else {
12363 diff_options.max_word_diff_len = usize::MAX;
12364 diff_options.max_word_diff_line_count = usize::MAX;
12365 }
12366
12367 for (old_range, new_text) in
12368 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12369 {
12370 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12371 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12372 edits.push((edit_start..edit_end, new_text));
12373 }
12374
12375 rewrapped_row_ranges.push(start_row..=end_row);
12376 }
12377
12378 self.buffer
12379 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12380 }
12381
12382 pub fn cut_common(
12383 &mut self,
12384 cut_no_selection_line: bool,
12385 window: &mut Window,
12386 cx: &mut Context<Self>,
12387 ) -> ClipboardItem {
12388 let mut text = String::new();
12389 let buffer = self.buffer.read(cx).snapshot(cx);
12390 let mut selections = self.selections.all::<Point>(cx);
12391 let mut clipboard_selections = Vec::with_capacity(selections.len());
12392 {
12393 let max_point = buffer.max_point();
12394 let mut is_first = true;
12395 for selection in &mut selections {
12396 let is_entire_line =
12397 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12398 if is_entire_line {
12399 selection.start = Point::new(selection.start.row, 0);
12400 if !selection.is_empty() && selection.end.column == 0 {
12401 selection.end = cmp::min(max_point, selection.end);
12402 } else {
12403 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12404 }
12405 selection.goal = SelectionGoal::None;
12406 }
12407 if is_first {
12408 is_first = false;
12409 } else {
12410 text += "\n";
12411 }
12412 let mut len = 0;
12413 for chunk in buffer.text_for_range(selection.start..selection.end) {
12414 text.push_str(chunk);
12415 len += chunk.len();
12416 }
12417 clipboard_selections.push(ClipboardSelection {
12418 len,
12419 is_entire_line,
12420 first_line_indent: buffer
12421 .indent_size_for_line(MultiBufferRow(selection.start.row))
12422 .len,
12423 });
12424 }
12425 }
12426
12427 self.transact(window, cx, |this, window, cx| {
12428 this.change_selections(Default::default(), window, cx, |s| {
12429 s.select(selections);
12430 });
12431 this.insert("", window, cx);
12432 });
12433 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12434 }
12435
12436 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12437 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12438 let item = self.cut_common(true, window, cx);
12439 cx.write_to_clipboard(item);
12440 }
12441
12442 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12443 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12444 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12445 s.move_with(|snapshot, sel| {
12446 if sel.is_empty() {
12447 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12448 }
12449 if sel.is_empty() {
12450 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12451 }
12452 });
12453 });
12454 let item = self.cut_common(false, window, cx);
12455 cx.set_global(KillRing(item))
12456 }
12457
12458 pub fn kill_ring_yank(
12459 &mut self,
12460 _: &KillRingYank,
12461 window: &mut Window,
12462 cx: &mut Context<Self>,
12463 ) {
12464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12465 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12466 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12467 (kill_ring.text().to_string(), kill_ring.metadata_json())
12468 } else {
12469 return;
12470 }
12471 } else {
12472 return;
12473 };
12474 self.do_paste(&text, metadata, false, window, cx);
12475 }
12476
12477 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12478 self.do_copy(true, cx);
12479 }
12480
12481 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12482 self.do_copy(false, cx);
12483 }
12484
12485 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12486 let selections = self.selections.all::<Point>(cx);
12487 let buffer = self.buffer.read(cx).read(cx);
12488 let mut text = String::new();
12489
12490 let mut clipboard_selections = Vec::with_capacity(selections.len());
12491 {
12492 let max_point = buffer.max_point();
12493 let mut is_first = true;
12494 for selection in &selections {
12495 let mut start = selection.start;
12496 let mut end = selection.end;
12497 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12498 if is_entire_line {
12499 start = Point::new(start.row, 0);
12500 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12501 }
12502
12503 let mut trimmed_selections = Vec::new();
12504 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12505 let row = MultiBufferRow(start.row);
12506 let first_indent = buffer.indent_size_for_line(row);
12507 if first_indent.len == 0 || start.column > first_indent.len {
12508 trimmed_selections.push(start..end);
12509 } else {
12510 trimmed_selections.push(
12511 Point::new(row.0, first_indent.len)
12512 ..Point::new(row.0, buffer.line_len(row)),
12513 );
12514 for row in start.row + 1..=end.row {
12515 let mut line_len = buffer.line_len(MultiBufferRow(row));
12516 if row == end.row {
12517 line_len = end.column;
12518 }
12519 if line_len == 0 {
12520 trimmed_selections
12521 .push(Point::new(row, 0)..Point::new(row, line_len));
12522 continue;
12523 }
12524 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12525 if row_indent_size.len >= first_indent.len {
12526 trimmed_selections.push(
12527 Point::new(row, first_indent.len)..Point::new(row, line_len),
12528 );
12529 } else {
12530 trimmed_selections.clear();
12531 trimmed_selections.push(start..end);
12532 break;
12533 }
12534 }
12535 }
12536 } else {
12537 trimmed_selections.push(start..end);
12538 }
12539
12540 for trimmed_range in trimmed_selections {
12541 if is_first {
12542 is_first = false;
12543 } else {
12544 text += "\n";
12545 }
12546 let mut len = 0;
12547 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12548 text.push_str(chunk);
12549 len += chunk.len();
12550 }
12551 clipboard_selections.push(ClipboardSelection {
12552 len,
12553 is_entire_line,
12554 first_line_indent: buffer
12555 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12556 .len,
12557 });
12558 }
12559 }
12560 }
12561
12562 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12563 text,
12564 clipboard_selections,
12565 ));
12566 }
12567
12568 pub fn do_paste(
12569 &mut self,
12570 text: &String,
12571 clipboard_selections: Option<Vec<ClipboardSelection>>,
12572 handle_entire_lines: bool,
12573 window: &mut Window,
12574 cx: &mut Context<Self>,
12575 ) {
12576 if self.read_only(cx) {
12577 return;
12578 }
12579
12580 let clipboard_text = Cow::Borrowed(text.as_str());
12581
12582 self.transact(window, cx, |this, window, cx| {
12583 let had_active_edit_prediction = this.has_active_edit_prediction();
12584 let old_selections = this.selections.all::<usize>(cx);
12585 let cursor_offset = this.selections.last::<usize>(cx).head();
12586
12587 if let Some(mut clipboard_selections) = clipboard_selections {
12588 let all_selections_were_entire_line =
12589 clipboard_selections.iter().all(|s| s.is_entire_line);
12590 let first_selection_indent_column =
12591 clipboard_selections.first().map(|s| s.first_line_indent);
12592 if clipboard_selections.len() != old_selections.len() {
12593 clipboard_selections.drain(..);
12594 }
12595 let mut auto_indent_on_paste = true;
12596
12597 this.buffer.update(cx, |buffer, cx| {
12598 let snapshot = buffer.read(cx);
12599 auto_indent_on_paste = snapshot
12600 .language_settings_at(cursor_offset, cx)
12601 .auto_indent_on_paste;
12602
12603 let mut start_offset = 0;
12604 let mut edits = Vec::new();
12605 let mut original_indent_columns = Vec::new();
12606 for (ix, selection) in old_selections.iter().enumerate() {
12607 let to_insert;
12608 let entire_line;
12609 let original_indent_column;
12610 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12611 let end_offset = start_offset + clipboard_selection.len;
12612 to_insert = &clipboard_text[start_offset..end_offset];
12613 entire_line = clipboard_selection.is_entire_line;
12614 start_offset = end_offset + 1;
12615 original_indent_column = Some(clipboard_selection.first_line_indent);
12616 } else {
12617 to_insert = &*clipboard_text;
12618 entire_line = all_selections_were_entire_line;
12619 original_indent_column = first_selection_indent_column
12620 }
12621
12622 let (range, to_insert) =
12623 if selection.is_empty() && handle_entire_lines && entire_line {
12624 // If the corresponding selection was empty when this slice of the
12625 // clipboard text was written, then the entire line containing the
12626 // selection was copied. If this selection is also currently empty,
12627 // then paste the line before the current line of the buffer.
12628 let column = selection.start.to_point(&snapshot).column as usize;
12629 let line_start = selection.start - column;
12630 (line_start..line_start, Cow::Borrowed(to_insert))
12631 } else {
12632 let language = snapshot.language_at(selection.head());
12633 let range = selection.range();
12634 if let Some(language) = language
12635 && language.name() == "Markdown".into()
12636 {
12637 edit_for_markdown_paste(
12638 &snapshot,
12639 range,
12640 to_insert,
12641 url::Url::parse(to_insert).ok(),
12642 )
12643 } else {
12644 (range, Cow::Borrowed(to_insert))
12645 }
12646 };
12647
12648 edits.push((range, to_insert));
12649 original_indent_columns.push(original_indent_column);
12650 }
12651 drop(snapshot);
12652
12653 buffer.edit(
12654 edits,
12655 if auto_indent_on_paste {
12656 Some(AutoindentMode::Block {
12657 original_indent_columns,
12658 })
12659 } else {
12660 None
12661 },
12662 cx,
12663 );
12664 });
12665
12666 let selections = this.selections.all::<usize>(cx);
12667 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12668 } else {
12669 let url = url::Url::parse(&clipboard_text).ok();
12670
12671 let auto_indent_mode = if !clipboard_text.is_empty() {
12672 Some(AutoindentMode::Block {
12673 original_indent_columns: Vec::new(),
12674 })
12675 } else {
12676 None
12677 };
12678
12679 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12680 let snapshot = buffer.snapshot(cx);
12681
12682 let anchors = old_selections
12683 .iter()
12684 .map(|s| {
12685 let anchor = snapshot.anchor_after(s.head());
12686 s.map(|_| anchor)
12687 })
12688 .collect::<Vec<_>>();
12689
12690 let mut edits = Vec::new();
12691
12692 for selection in old_selections.iter() {
12693 let language = snapshot.language_at(selection.head());
12694 let range = selection.range();
12695
12696 let (edit_range, edit_text) = if let Some(language) = language
12697 && language.name() == "Markdown".into()
12698 {
12699 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12700 } else {
12701 (range, clipboard_text.clone())
12702 };
12703
12704 edits.push((edit_range, edit_text));
12705 }
12706
12707 drop(snapshot);
12708 buffer.edit(edits, auto_indent_mode, cx);
12709
12710 anchors
12711 });
12712
12713 this.change_selections(Default::default(), window, cx, |s| {
12714 s.select_anchors(selection_anchors);
12715 });
12716 }
12717
12718 let trigger_in_words =
12719 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12720
12721 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12722 });
12723 }
12724
12725 pub fn diff_clipboard_with_selection(
12726 &mut self,
12727 _: &DiffClipboardWithSelection,
12728 window: &mut Window,
12729 cx: &mut Context<Self>,
12730 ) {
12731 let selections = self.selections.all::<usize>(cx);
12732
12733 if selections.is_empty() {
12734 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12735 return;
12736 };
12737
12738 let clipboard_text = match cx.read_from_clipboard() {
12739 Some(item) => match item.entries().first() {
12740 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12741 _ => None,
12742 },
12743 None => None,
12744 };
12745
12746 let Some(clipboard_text) = clipboard_text else {
12747 log::warn!("Clipboard doesn't contain text.");
12748 return;
12749 };
12750
12751 window.dispatch_action(
12752 Box::new(DiffClipboardWithSelectionData {
12753 clipboard_text,
12754 editor: cx.entity(),
12755 }),
12756 cx,
12757 );
12758 }
12759
12760 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12761 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12762 if let Some(item) = cx.read_from_clipboard() {
12763 let entries = item.entries();
12764
12765 match entries.first() {
12766 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12767 // of all the pasted entries.
12768 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12769 .do_paste(
12770 clipboard_string.text(),
12771 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12772 true,
12773 window,
12774 cx,
12775 ),
12776 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12777 }
12778 }
12779 }
12780
12781 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12782 if self.read_only(cx) {
12783 return;
12784 }
12785
12786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12787
12788 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12789 if let Some((selections, _)) =
12790 self.selection_history.transaction(transaction_id).cloned()
12791 {
12792 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12793 s.select_anchors(selections.to_vec());
12794 });
12795 } else {
12796 log::error!(
12797 "No entry in selection_history found for undo. \
12798 This may correspond to a bug where undo does not update the selection. \
12799 If this is occurring, please add details to \
12800 https://github.com/zed-industries/zed/issues/22692"
12801 );
12802 }
12803 self.request_autoscroll(Autoscroll::fit(), cx);
12804 self.unmark_text(window, cx);
12805 self.refresh_edit_prediction(true, false, window, cx);
12806 cx.emit(EditorEvent::Edited { transaction_id });
12807 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12808 }
12809 }
12810
12811 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12812 if self.read_only(cx) {
12813 return;
12814 }
12815
12816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12817
12818 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12819 if let Some((_, Some(selections))) =
12820 self.selection_history.transaction(transaction_id).cloned()
12821 {
12822 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12823 s.select_anchors(selections.to_vec());
12824 });
12825 } else {
12826 log::error!(
12827 "No entry in selection_history found for redo. \
12828 This may correspond to a bug where undo does not update the selection. \
12829 If this is occurring, please add details to \
12830 https://github.com/zed-industries/zed/issues/22692"
12831 );
12832 }
12833 self.request_autoscroll(Autoscroll::fit(), cx);
12834 self.unmark_text(window, cx);
12835 self.refresh_edit_prediction(true, false, window, cx);
12836 cx.emit(EditorEvent::Edited { transaction_id });
12837 }
12838 }
12839
12840 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12841 self.buffer
12842 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12843 }
12844
12845 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12846 self.buffer
12847 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12848 }
12849
12850 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12852 self.change_selections(Default::default(), window, cx, |s| {
12853 s.move_with(|map, selection| {
12854 let cursor = if selection.is_empty() {
12855 movement::left(map, selection.start)
12856 } else {
12857 selection.start
12858 };
12859 selection.collapse_to(cursor, SelectionGoal::None);
12860 });
12861 })
12862 }
12863
12864 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12866 self.change_selections(Default::default(), window, cx, |s| {
12867 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12868 })
12869 }
12870
12871 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12873 self.change_selections(Default::default(), window, cx, |s| {
12874 s.move_with(|map, selection| {
12875 let cursor = if selection.is_empty() {
12876 movement::right(map, selection.end)
12877 } else {
12878 selection.end
12879 };
12880 selection.collapse_to(cursor, SelectionGoal::None)
12881 });
12882 })
12883 }
12884
12885 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12887 self.change_selections(Default::default(), window, cx, |s| {
12888 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12889 })
12890 }
12891
12892 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12893 if self.take_rename(true, window, cx).is_some() {
12894 return;
12895 }
12896
12897 if self.mode.is_single_line() {
12898 cx.propagate();
12899 return;
12900 }
12901
12902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12903
12904 let text_layout_details = &self.text_layout_details(window);
12905 let selection_count = self.selections.count();
12906 let first_selection = self.selections.first_anchor();
12907
12908 self.change_selections(Default::default(), window, cx, |s| {
12909 s.move_with(|map, selection| {
12910 if !selection.is_empty() {
12911 selection.goal = SelectionGoal::None;
12912 }
12913 let (cursor, goal) = movement::up(
12914 map,
12915 selection.start,
12916 selection.goal,
12917 false,
12918 text_layout_details,
12919 );
12920 selection.collapse_to(cursor, goal);
12921 });
12922 });
12923
12924 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12925 {
12926 cx.propagate();
12927 }
12928 }
12929
12930 pub fn move_up_by_lines(
12931 &mut self,
12932 action: &MoveUpByLines,
12933 window: &mut Window,
12934 cx: &mut Context<Self>,
12935 ) {
12936 if self.take_rename(true, window, cx).is_some() {
12937 return;
12938 }
12939
12940 if self.mode.is_single_line() {
12941 cx.propagate();
12942 return;
12943 }
12944
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12946
12947 let text_layout_details = &self.text_layout_details(window);
12948
12949 self.change_selections(Default::default(), window, cx, |s| {
12950 s.move_with(|map, selection| {
12951 if !selection.is_empty() {
12952 selection.goal = SelectionGoal::None;
12953 }
12954 let (cursor, goal) = movement::up_by_rows(
12955 map,
12956 selection.start,
12957 action.lines,
12958 selection.goal,
12959 false,
12960 text_layout_details,
12961 );
12962 selection.collapse_to(cursor, goal);
12963 });
12964 })
12965 }
12966
12967 pub fn move_down_by_lines(
12968 &mut self,
12969 action: &MoveDownByLines,
12970 window: &mut Window,
12971 cx: &mut Context<Self>,
12972 ) {
12973 if self.take_rename(true, window, cx).is_some() {
12974 return;
12975 }
12976
12977 if self.mode.is_single_line() {
12978 cx.propagate();
12979 return;
12980 }
12981
12982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12983
12984 let text_layout_details = &self.text_layout_details(window);
12985
12986 self.change_selections(Default::default(), window, cx, |s| {
12987 s.move_with(|map, selection| {
12988 if !selection.is_empty() {
12989 selection.goal = SelectionGoal::None;
12990 }
12991 let (cursor, goal) = movement::down_by_rows(
12992 map,
12993 selection.start,
12994 action.lines,
12995 selection.goal,
12996 false,
12997 text_layout_details,
12998 );
12999 selection.collapse_to(cursor, goal);
13000 });
13001 })
13002 }
13003
13004 pub fn select_down_by_lines(
13005 &mut self,
13006 action: &SelectDownByLines,
13007 window: &mut Window,
13008 cx: &mut Context<Self>,
13009 ) {
13010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13011 let text_layout_details = &self.text_layout_details(window);
13012 self.change_selections(Default::default(), window, cx, |s| {
13013 s.move_heads_with(|map, head, goal| {
13014 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13015 })
13016 })
13017 }
13018
13019 pub fn select_up_by_lines(
13020 &mut self,
13021 action: &SelectUpByLines,
13022 window: &mut Window,
13023 cx: &mut Context<Self>,
13024 ) {
13025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13026 let text_layout_details = &self.text_layout_details(window);
13027 self.change_selections(Default::default(), window, cx, |s| {
13028 s.move_heads_with(|map, head, goal| {
13029 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13030 })
13031 })
13032 }
13033
13034 pub fn select_page_up(
13035 &mut self,
13036 _: &SelectPageUp,
13037 window: &mut Window,
13038 cx: &mut Context<Self>,
13039 ) {
13040 let Some(row_count) = self.visible_row_count() else {
13041 return;
13042 };
13043
13044 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13045
13046 let text_layout_details = &self.text_layout_details(window);
13047
13048 self.change_selections(Default::default(), window, cx, |s| {
13049 s.move_heads_with(|map, head, goal| {
13050 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13051 })
13052 })
13053 }
13054
13055 pub fn move_page_up(
13056 &mut self,
13057 action: &MovePageUp,
13058 window: &mut Window,
13059 cx: &mut Context<Self>,
13060 ) {
13061 if self.take_rename(true, window, cx).is_some() {
13062 return;
13063 }
13064
13065 if self
13066 .context_menu
13067 .borrow_mut()
13068 .as_mut()
13069 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13070 .unwrap_or(false)
13071 {
13072 return;
13073 }
13074
13075 if matches!(self.mode, EditorMode::SingleLine) {
13076 cx.propagate();
13077 return;
13078 }
13079
13080 let Some(row_count) = self.visible_row_count() else {
13081 return;
13082 };
13083
13084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13085
13086 let effects = if action.center_cursor {
13087 SelectionEffects::scroll(Autoscroll::center())
13088 } else {
13089 SelectionEffects::default()
13090 };
13091
13092 let text_layout_details = &self.text_layout_details(window);
13093
13094 self.change_selections(effects, window, cx, |s| {
13095 s.move_with(|map, selection| {
13096 if !selection.is_empty() {
13097 selection.goal = SelectionGoal::None;
13098 }
13099 let (cursor, goal) = movement::up_by_rows(
13100 map,
13101 selection.end,
13102 row_count,
13103 selection.goal,
13104 false,
13105 text_layout_details,
13106 );
13107 selection.collapse_to(cursor, goal);
13108 });
13109 });
13110 }
13111
13112 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13114 let text_layout_details = &self.text_layout_details(window);
13115 self.change_selections(Default::default(), window, cx, |s| {
13116 s.move_heads_with(|map, head, goal| {
13117 movement::up(map, head, goal, false, text_layout_details)
13118 })
13119 })
13120 }
13121
13122 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13123 self.take_rename(true, window, cx);
13124
13125 if self.mode.is_single_line() {
13126 cx.propagate();
13127 return;
13128 }
13129
13130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13131
13132 let text_layout_details = &self.text_layout_details(window);
13133 let selection_count = self.selections.count();
13134 let first_selection = self.selections.first_anchor();
13135
13136 self.change_selections(Default::default(), window, cx, |s| {
13137 s.move_with(|map, selection| {
13138 if !selection.is_empty() {
13139 selection.goal = SelectionGoal::None;
13140 }
13141 let (cursor, goal) = movement::down(
13142 map,
13143 selection.end,
13144 selection.goal,
13145 false,
13146 text_layout_details,
13147 );
13148 selection.collapse_to(cursor, goal);
13149 });
13150 });
13151
13152 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13153 {
13154 cx.propagate();
13155 }
13156 }
13157
13158 pub fn select_page_down(
13159 &mut self,
13160 _: &SelectPageDown,
13161 window: &mut Window,
13162 cx: &mut Context<Self>,
13163 ) {
13164 let Some(row_count) = self.visible_row_count() else {
13165 return;
13166 };
13167
13168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13169
13170 let text_layout_details = &self.text_layout_details(window);
13171
13172 self.change_selections(Default::default(), window, cx, |s| {
13173 s.move_heads_with(|map, head, goal| {
13174 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13175 })
13176 })
13177 }
13178
13179 pub fn move_page_down(
13180 &mut self,
13181 action: &MovePageDown,
13182 window: &mut Window,
13183 cx: &mut Context<Self>,
13184 ) {
13185 if self.take_rename(true, window, cx).is_some() {
13186 return;
13187 }
13188
13189 if self
13190 .context_menu
13191 .borrow_mut()
13192 .as_mut()
13193 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13194 .unwrap_or(false)
13195 {
13196 return;
13197 }
13198
13199 if matches!(self.mode, EditorMode::SingleLine) {
13200 cx.propagate();
13201 return;
13202 }
13203
13204 let Some(row_count) = self.visible_row_count() else {
13205 return;
13206 };
13207
13208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13209
13210 let effects = if action.center_cursor {
13211 SelectionEffects::scroll(Autoscroll::center())
13212 } else {
13213 SelectionEffects::default()
13214 };
13215
13216 let text_layout_details = &self.text_layout_details(window);
13217 self.change_selections(effects, window, cx, |s| {
13218 s.move_with(|map, selection| {
13219 if !selection.is_empty() {
13220 selection.goal = SelectionGoal::None;
13221 }
13222 let (cursor, goal) = movement::down_by_rows(
13223 map,
13224 selection.end,
13225 row_count,
13226 selection.goal,
13227 false,
13228 text_layout_details,
13229 );
13230 selection.collapse_to(cursor, goal);
13231 });
13232 });
13233 }
13234
13235 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13236 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13237 let text_layout_details = &self.text_layout_details(window);
13238 self.change_selections(Default::default(), window, cx, |s| {
13239 s.move_heads_with(|map, head, goal| {
13240 movement::down(map, head, goal, false, text_layout_details)
13241 })
13242 });
13243 }
13244
13245 pub fn context_menu_first(
13246 &mut self,
13247 _: &ContextMenuFirst,
13248 window: &mut Window,
13249 cx: &mut Context<Self>,
13250 ) {
13251 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13252 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13253 }
13254 }
13255
13256 pub fn context_menu_prev(
13257 &mut self,
13258 _: &ContextMenuPrevious,
13259 window: &mut Window,
13260 cx: &mut Context<Self>,
13261 ) {
13262 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13263 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13264 }
13265 }
13266
13267 pub fn context_menu_next(
13268 &mut self,
13269 _: &ContextMenuNext,
13270 window: &mut Window,
13271 cx: &mut Context<Self>,
13272 ) {
13273 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13274 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13275 }
13276 }
13277
13278 pub fn context_menu_last(
13279 &mut self,
13280 _: &ContextMenuLast,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) {
13284 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13285 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13286 }
13287 }
13288
13289 pub fn signature_help_prev(
13290 &mut self,
13291 _: &SignatureHelpPrevious,
13292 _: &mut Window,
13293 cx: &mut Context<Self>,
13294 ) {
13295 if let Some(popover) = self.signature_help_state.popover_mut() {
13296 if popover.current_signature == 0 {
13297 popover.current_signature = popover.signatures.len() - 1;
13298 } else {
13299 popover.current_signature -= 1;
13300 }
13301 cx.notify();
13302 }
13303 }
13304
13305 pub fn signature_help_next(
13306 &mut self,
13307 _: &SignatureHelpNext,
13308 _: &mut Window,
13309 cx: &mut Context<Self>,
13310 ) {
13311 if let Some(popover) = self.signature_help_state.popover_mut() {
13312 if popover.current_signature + 1 == popover.signatures.len() {
13313 popover.current_signature = 0;
13314 } else {
13315 popover.current_signature += 1;
13316 }
13317 cx.notify();
13318 }
13319 }
13320
13321 pub fn move_to_previous_word_start(
13322 &mut self,
13323 _: &MoveToPreviousWordStart,
13324 window: &mut Window,
13325 cx: &mut Context<Self>,
13326 ) {
13327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13328 self.change_selections(Default::default(), window, cx, |s| {
13329 s.move_cursors_with(|map, head, _| {
13330 (
13331 movement::previous_word_start(map, head),
13332 SelectionGoal::None,
13333 )
13334 });
13335 })
13336 }
13337
13338 pub fn move_to_previous_subword_start(
13339 &mut self,
13340 _: &MoveToPreviousSubwordStart,
13341 window: &mut Window,
13342 cx: &mut Context<Self>,
13343 ) {
13344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13345 self.change_selections(Default::default(), window, cx, |s| {
13346 s.move_cursors_with(|map, head, _| {
13347 (
13348 movement::previous_subword_start(map, head),
13349 SelectionGoal::None,
13350 )
13351 });
13352 })
13353 }
13354
13355 pub fn select_to_previous_word_start(
13356 &mut self,
13357 _: &SelectToPreviousWordStart,
13358 window: &mut Window,
13359 cx: &mut Context<Self>,
13360 ) {
13361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13362 self.change_selections(Default::default(), window, cx, |s| {
13363 s.move_heads_with(|map, head, _| {
13364 (
13365 movement::previous_word_start(map, head),
13366 SelectionGoal::None,
13367 )
13368 });
13369 })
13370 }
13371
13372 pub fn select_to_previous_subword_start(
13373 &mut self,
13374 _: &SelectToPreviousSubwordStart,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13379 self.change_selections(Default::default(), window, cx, |s| {
13380 s.move_heads_with(|map, head, _| {
13381 (
13382 movement::previous_subword_start(map, head),
13383 SelectionGoal::None,
13384 )
13385 });
13386 })
13387 }
13388
13389 pub fn delete_to_previous_word_start(
13390 &mut self,
13391 action: &DeleteToPreviousWordStart,
13392 window: &mut Window,
13393 cx: &mut Context<Self>,
13394 ) {
13395 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13396 self.transact(window, cx, |this, window, cx| {
13397 this.select_autoclose_pair(window, cx);
13398 this.change_selections(Default::default(), window, cx, |s| {
13399 s.move_with(|map, selection| {
13400 if selection.is_empty() {
13401 let mut cursor = if action.ignore_newlines {
13402 movement::previous_word_start(map, selection.head())
13403 } else {
13404 movement::previous_word_start_or_newline(map, selection.head())
13405 };
13406 cursor = movement::adjust_greedy_deletion(
13407 map,
13408 selection.head(),
13409 cursor,
13410 action.ignore_brackets,
13411 );
13412 selection.set_head(cursor, SelectionGoal::None);
13413 }
13414 });
13415 });
13416 this.insert("", window, cx);
13417 });
13418 }
13419
13420 pub fn delete_to_previous_subword_start(
13421 &mut self,
13422 _: &DeleteToPreviousSubwordStart,
13423 window: &mut Window,
13424 cx: &mut Context<Self>,
13425 ) {
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13427 self.transact(window, cx, |this, window, cx| {
13428 this.select_autoclose_pair(window, cx);
13429 this.change_selections(Default::default(), window, cx, |s| {
13430 s.move_with(|map, selection| {
13431 if selection.is_empty() {
13432 let mut cursor = movement::previous_subword_start(map, selection.head());
13433 cursor =
13434 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13435 selection.set_head(cursor, SelectionGoal::None);
13436 }
13437 });
13438 });
13439 this.insert("", window, cx);
13440 });
13441 }
13442
13443 pub fn move_to_next_word_end(
13444 &mut self,
13445 _: &MoveToNextWordEnd,
13446 window: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) {
13449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13450 self.change_selections(Default::default(), window, cx, |s| {
13451 s.move_cursors_with(|map, head, _| {
13452 (movement::next_word_end(map, head), SelectionGoal::None)
13453 });
13454 })
13455 }
13456
13457 pub fn move_to_next_subword_end(
13458 &mut self,
13459 _: &MoveToNextSubwordEnd,
13460 window: &mut Window,
13461 cx: &mut Context<Self>,
13462 ) {
13463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13464 self.change_selections(Default::default(), window, cx, |s| {
13465 s.move_cursors_with(|map, head, _| {
13466 (movement::next_subword_end(map, head), SelectionGoal::None)
13467 });
13468 })
13469 }
13470
13471 pub fn select_to_next_word_end(
13472 &mut self,
13473 _: &SelectToNextWordEnd,
13474 window: &mut Window,
13475 cx: &mut Context<Self>,
13476 ) {
13477 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13478 self.change_selections(Default::default(), window, cx, |s| {
13479 s.move_heads_with(|map, head, _| {
13480 (movement::next_word_end(map, head), SelectionGoal::None)
13481 });
13482 })
13483 }
13484
13485 pub fn select_to_next_subword_end(
13486 &mut self,
13487 _: &SelectToNextSubwordEnd,
13488 window: &mut Window,
13489 cx: &mut Context<Self>,
13490 ) {
13491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13492 self.change_selections(Default::default(), window, cx, |s| {
13493 s.move_heads_with(|map, head, _| {
13494 (movement::next_subword_end(map, head), SelectionGoal::None)
13495 });
13496 })
13497 }
13498
13499 pub fn delete_to_next_word_end(
13500 &mut self,
13501 action: &DeleteToNextWordEnd,
13502 window: &mut Window,
13503 cx: &mut Context<Self>,
13504 ) {
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13506 self.transact(window, cx, |this, window, cx| {
13507 this.change_selections(Default::default(), window, cx, |s| {
13508 s.move_with(|map, selection| {
13509 if selection.is_empty() {
13510 let mut cursor = if action.ignore_newlines {
13511 movement::next_word_end(map, selection.head())
13512 } else {
13513 movement::next_word_end_or_newline(map, selection.head())
13514 };
13515 cursor = movement::adjust_greedy_deletion(
13516 map,
13517 selection.head(),
13518 cursor,
13519 action.ignore_brackets,
13520 );
13521 selection.set_head(cursor, SelectionGoal::None);
13522 }
13523 });
13524 });
13525 this.insert("", window, cx);
13526 });
13527 }
13528
13529 pub fn delete_to_next_subword_end(
13530 &mut self,
13531 _: &DeleteToNextSubwordEnd,
13532 window: &mut Window,
13533 cx: &mut Context<Self>,
13534 ) {
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13536 self.transact(window, cx, |this, window, cx| {
13537 this.change_selections(Default::default(), window, cx, |s| {
13538 s.move_with(|map, selection| {
13539 if selection.is_empty() {
13540 let mut cursor = movement::next_subword_end(map, selection.head());
13541 cursor =
13542 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13543 selection.set_head(cursor, SelectionGoal::None);
13544 }
13545 });
13546 });
13547 this.insert("", window, cx);
13548 });
13549 }
13550
13551 pub fn move_to_beginning_of_line(
13552 &mut self,
13553 action: &MoveToBeginningOfLine,
13554 window: &mut Window,
13555 cx: &mut Context<Self>,
13556 ) {
13557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13558 self.change_selections(Default::default(), window, cx, |s| {
13559 s.move_cursors_with(|map, head, _| {
13560 (
13561 movement::indented_line_beginning(
13562 map,
13563 head,
13564 action.stop_at_soft_wraps,
13565 action.stop_at_indent,
13566 ),
13567 SelectionGoal::None,
13568 )
13569 });
13570 })
13571 }
13572
13573 pub fn select_to_beginning_of_line(
13574 &mut self,
13575 action: &SelectToBeginningOfLine,
13576 window: &mut Window,
13577 cx: &mut Context<Self>,
13578 ) {
13579 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13580 self.change_selections(Default::default(), window, cx, |s| {
13581 s.move_heads_with(|map, head, _| {
13582 (
13583 movement::indented_line_beginning(
13584 map,
13585 head,
13586 action.stop_at_soft_wraps,
13587 action.stop_at_indent,
13588 ),
13589 SelectionGoal::None,
13590 )
13591 });
13592 });
13593 }
13594
13595 pub fn delete_to_beginning_of_line(
13596 &mut self,
13597 action: &DeleteToBeginningOfLine,
13598 window: &mut Window,
13599 cx: &mut Context<Self>,
13600 ) {
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13602 self.transact(window, cx, |this, window, cx| {
13603 this.change_selections(Default::default(), window, cx, |s| {
13604 s.move_with(|_, selection| {
13605 selection.reversed = true;
13606 });
13607 });
13608
13609 this.select_to_beginning_of_line(
13610 &SelectToBeginningOfLine {
13611 stop_at_soft_wraps: false,
13612 stop_at_indent: action.stop_at_indent,
13613 },
13614 window,
13615 cx,
13616 );
13617 this.backspace(&Backspace, window, cx);
13618 });
13619 }
13620
13621 pub fn move_to_end_of_line(
13622 &mut self,
13623 action: &MoveToEndOfLine,
13624 window: &mut Window,
13625 cx: &mut Context<Self>,
13626 ) {
13627 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13628 self.change_selections(Default::default(), window, cx, |s| {
13629 s.move_cursors_with(|map, head, _| {
13630 (
13631 movement::line_end(map, head, action.stop_at_soft_wraps),
13632 SelectionGoal::None,
13633 )
13634 });
13635 })
13636 }
13637
13638 pub fn select_to_end_of_line(
13639 &mut self,
13640 action: &SelectToEndOfLine,
13641 window: &mut Window,
13642 cx: &mut Context<Self>,
13643 ) {
13644 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13645 self.change_selections(Default::default(), window, cx, |s| {
13646 s.move_heads_with(|map, head, _| {
13647 (
13648 movement::line_end(map, head, action.stop_at_soft_wraps),
13649 SelectionGoal::None,
13650 )
13651 });
13652 })
13653 }
13654
13655 pub fn delete_to_end_of_line(
13656 &mut self,
13657 _: &DeleteToEndOfLine,
13658 window: &mut Window,
13659 cx: &mut Context<Self>,
13660 ) {
13661 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13662 self.transact(window, cx, |this, window, cx| {
13663 this.select_to_end_of_line(
13664 &SelectToEndOfLine {
13665 stop_at_soft_wraps: false,
13666 },
13667 window,
13668 cx,
13669 );
13670 this.delete(&Delete, window, cx);
13671 });
13672 }
13673
13674 pub fn cut_to_end_of_line(
13675 &mut self,
13676 action: &CutToEndOfLine,
13677 window: &mut Window,
13678 cx: &mut Context<Self>,
13679 ) {
13680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13681 self.transact(window, cx, |this, window, cx| {
13682 this.select_to_end_of_line(
13683 &SelectToEndOfLine {
13684 stop_at_soft_wraps: false,
13685 },
13686 window,
13687 cx,
13688 );
13689 if !action.stop_at_newlines {
13690 this.change_selections(Default::default(), window, cx, |s| {
13691 s.move_with(|_, sel| {
13692 if sel.is_empty() {
13693 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13694 }
13695 });
13696 });
13697 }
13698 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13699 let item = this.cut_common(false, window, cx);
13700 cx.write_to_clipboard(item);
13701 });
13702 }
13703
13704 pub fn move_to_start_of_paragraph(
13705 &mut self,
13706 _: &MoveToStartOfParagraph,
13707 window: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 if matches!(self.mode, EditorMode::SingleLine) {
13711 cx.propagate();
13712 return;
13713 }
13714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13715 self.change_selections(Default::default(), window, cx, |s| {
13716 s.move_with(|map, selection| {
13717 selection.collapse_to(
13718 movement::start_of_paragraph(map, selection.head(), 1),
13719 SelectionGoal::None,
13720 )
13721 });
13722 })
13723 }
13724
13725 pub fn move_to_end_of_paragraph(
13726 &mut self,
13727 _: &MoveToEndOfParagraph,
13728 window: &mut Window,
13729 cx: &mut Context<Self>,
13730 ) {
13731 if matches!(self.mode, EditorMode::SingleLine) {
13732 cx.propagate();
13733 return;
13734 }
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736 self.change_selections(Default::default(), window, cx, |s| {
13737 s.move_with(|map, selection| {
13738 selection.collapse_to(
13739 movement::end_of_paragraph(map, selection.head(), 1),
13740 SelectionGoal::None,
13741 )
13742 });
13743 })
13744 }
13745
13746 pub fn select_to_start_of_paragraph(
13747 &mut self,
13748 _: &SelectToStartOfParagraph,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 if matches!(self.mode, EditorMode::SingleLine) {
13753 cx.propagate();
13754 return;
13755 }
13756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13757 self.change_selections(Default::default(), window, cx, |s| {
13758 s.move_heads_with(|map, head, _| {
13759 (
13760 movement::start_of_paragraph(map, head, 1),
13761 SelectionGoal::None,
13762 )
13763 });
13764 })
13765 }
13766
13767 pub fn select_to_end_of_paragraph(
13768 &mut self,
13769 _: &SelectToEndOfParagraph,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 if matches!(self.mode, EditorMode::SingleLine) {
13774 cx.propagate();
13775 return;
13776 }
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 self.change_selections(Default::default(), window, cx, |s| {
13779 s.move_heads_with(|map, head, _| {
13780 (
13781 movement::end_of_paragraph(map, head, 1),
13782 SelectionGoal::None,
13783 )
13784 });
13785 })
13786 }
13787
13788 pub fn move_to_start_of_excerpt(
13789 &mut self,
13790 _: &MoveToStartOfExcerpt,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 ) {
13794 if matches!(self.mode, EditorMode::SingleLine) {
13795 cx.propagate();
13796 return;
13797 }
13798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13799 self.change_selections(Default::default(), window, cx, |s| {
13800 s.move_with(|map, selection| {
13801 selection.collapse_to(
13802 movement::start_of_excerpt(
13803 map,
13804 selection.head(),
13805 workspace::searchable::Direction::Prev,
13806 ),
13807 SelectionGoal::None,
13808 )
13809 });
13810 })
13811 }
13812
13813 pub fn move_to_start_of_next_excerpt(
13814 &mut self,
13815 _: &MoveToStartOfNextExcerpt,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) {
13819 if matches!(self.mode, EditorMode::SingleLine) {
13820 cx.propagate();
13821 return;
13822 }
13823
13824 self.change_selections(Default::default(), window, cx, |s| {
13825 s.move_with(|map, selection| {
13826 selection.collapse_to(
13827 movement::start_of_excerpt(
13828 map,
13829 selection.head(),
13830 workspace::searchable::Direction::Next,
13831 ),
13832 SelectionGoal::None,
13833 )
13834 });
13835 })
13836 }
13837
13838 pub fn move_to_end_of_excerpt(
13839 &mut self,
13840 _: &MoveToEndOfExcerpt,
13841 window: &mut Window,
13842 cx: &mut Context<Self>,
13843 ) {
13844 if matches!(self.mode, EditorMode::SingleLine) {
13845 cx.propagate();
13846 return;
13847 }
13848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13849 self.change_selections(Default::default(), window, cx, |s| {
13850 s.move_with(|map, selection| {
13851 selection.collapse_to(
13852 movement::end_of_excerpt(
13853 map,
13854 selection.head(),
13855 workspace::searchable::Direction::Next,
13856 ),
13857 SelectionGoal::None,
13858 )
13859 });
13860 })
13861 }
13862
13863 pub fn move_to_end_of_previous_excerpt(
13864 &mut self,
13865 _: &MoveToEndOfPreviousExcerpt,
13866 window: &mut Window,
13867 cx: &mut Context<Self>,
13868 ) {
13869 if matches!(self.mode, EditorMode::SingleLine) {
13870 cx.propagate();
13871 return;
13872 }
13873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13874 self.change_selections(Default::default(), window, cx, |s| {
13875 s.move_with(|map, selection| {
13876 selection.collapse_to(
13877 movement::end_of_excerpt(
13878 map,
13879 selection.head(),
13880 workspace::searchable::Direction::Prev,
13881 ),
13882 SelectionGoal::None,
13883 )
13884 });
13885 })
13886 }
13887
13888 pub fn select_to_start_of_excerpt(
13889 &mut self,
13890 _: &SelectToStartOfExcerpt,
13891 window: &mut Window,
13892 cx: &mut Context<Self>,
13893 ) {
13894 if matches!(self.mode, EditorMode::SingleLine) {
13895 cx.propagate();
13896 return;
13897 }
13898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13899 self.change_selections(Default::default(), window, cx, |s| {
13900 s.move_heads_with(|map, head, _| {
13901 (
13902 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13903 SelectionGoal::None,
13904 )
13905 });
13906 })
13907 }
13908
13909 pub fn select_to_start_of_next_excerpt(
13910 &mut self,
13911 _: &SelectToStartOfNextExcerpt,
13912 window: &mut Window,
13913 cx: &mut Context<Self>,
13914 ) {
13915 if matches!(self.mode, EditorMode::SingleLine) {
13916 cx.propagate();
13917 return;
13918 }
13919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13920 self.change_selections(Default::default(), window, cx, |s| {
13921 s.move_heads_with(|map, head, _| {
13922 (
13923 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13924 SelectionGoal::None,
13925 )
13926 });
13927 })
13928 }
13929
13930 pub fn select_to_end_of_excerpt(
13931 &mut self,
13932 _: &SelectToEndOfExcerpt,
13933 window: &mut Window,
13934 cx: &mut Context<Self>,
13935 ) {
13936 if matches!(self.mode, EditorMode::SingleLine) {
13937 cx.propagate();
13938 return;
13939 }
13940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13941 self.change_selections(Default::default(), window, cx, |s| {
13942 s.move_heads_with(|map, head, _| {
13943 (
13944 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13945 SelectionGoal::None,
13946 )
13947 });
13948 })
13949 }
13950
13951 pub fn select_to_end_of_previous_excerpt(
13952 &mut self,
13953 _: &SelectToEndOfPreviousExcerpt,
13954 window: &mut Window,
13955 cx: &mut Context<Self>,
13956 ) {
13957 if matches!(self.mode, EditorMode::SingleLine) {
13958 cx.propagate();
13959 return;
13960 }
13961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13962 self.change_selections(Default::default(), window, cx, |s| {
13963 s.move_heads_with(|map, head, _| {
13964 (
13965 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13966 SelectionGoal::None,
13967 )
13968 });
13969 })
13970 }
13971
13972 pub fn move_to_beginning(
13973 &mut self,
13974 _: &MoveToBeginning,
13975 window: &mut Window,
13976 cx: &mut Context<Self>,
13977 ) {
13978 if matches!(self.mode, EditorMode::SingleLine) {
13979 cx.propagate();
13980 return;
13981 }
13982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13983 self.change_selections(Default::default(), window, cx, |s| {
13984 s.select_ranges(vec![0..0]);
13985 });
13986 }
13987
13988 pub fn select_to_beginning(
13989 &mut self,
13990 _: &SelectToBeginning,
13991 window: &mut Window,
13992 cx: &mut Context<Self>,
13993 ) {
13994 let mut selection = self.selections.last::<Point>(cx);
13995 selection.set_head(Point::zero(), SelectionGoal::None);
13996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13997 self.change_selections(Default::default(), window, cx, |s| {
13998 s.select(vec![selection]);
13999 });
14000 }
14001
14002 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14003 if matches!(self.mode, EditorMode::SingleLine) {
14004 cx.propagate();
14005 return;
14006 }
14007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14008 let cursor = self.buffer.read(cx).read(cx).len();
14009 self.change_selections(Default::default(), window, cx, |s| {
14010 s.select_ranges(vec![cursor..cursor])
14011 });
14012 }
14013
14014 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14015 self.nav_history = nav_history;
14016 }
14017
14018 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14019 self.nav_history.as_ref()
14020 }
14021
14022 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14023 self.push_to_nav_history(
14024 self.selections.newest_anchor().head(),
14025 None,
14026 false,
14027 true,
14028 cx,
14029 );
14030 }
14031
14032 fn push_to_nav_history(
14033 &mut self,
14034 cursor_anchor: Anchor,
14035 new_position: Option<Point>,
14036 is_deactivate: bool,
14037 always: bool,
14038 cx: &mut Context<Self>,
14039 ) {
14040 if let Some(nav_history) = self.nav_history.as_mut() {
14041 let buffer = self.buffer.read(cx).read(cx);
14042 let cursor_position = cursor_anchor.to_point(&buffer);
14043 let scroll_state = self.scroll_manager.anchor();
14044 let scroll_top_row = scroll_state.top_row(&buffer);
14045 drop(buffer);
14046
14047 if let Some(new_position) = new_position {
14048 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14049 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14050 return;
14051 }
14052 }
14053
14054 nav_history.push(
14055 Some(NavigationData {
14056 cursor_anchor,
14057 cursor_position,
14058 scroll_anchor: scroll_state,
14059 scroll_top_row,
14060 }),
14061 cx,
14062 );
14063 cx.emit(EditorEvent::PushedToNavHistory {
14064 anchor: cursor_anchor,
14065 is_deactivate,
14066 })
14067 }
14068 }
14069
14070 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14071 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14072 let buffer = self.buffer.read(cx).snapshot(cx);
14073 let mut selection = self.selections.first::<usize>(cx);
14074 selection.set_head(buffer.len(), SelectionGoal::None);
14075 self.change_selections(Default::default(), window, cx, |s| {
14076 s.select(vec![selection]);
14077 });
14078 }
14079
14080 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14082 let end = self.buffer.read(cx).read(cx).len();
14083 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14084 s.select_ranges(vec![0..end]);
14085 });
14086 }
14087
14088 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14090 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14091 let mut selections = self.selections.all::<Point>(cx);
14092 let max_point = display_map.buffer_snapshot.max_point();
14093 for selection in &mut selections {
14094 let rows = selection.spanned_rows(true, &display_map);
14095 selection.start = Point::new(rows.start.0, 0);
14096 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14097 selection.reversed = false;
14098 }
14099 self.change_selections(Default::default(), window, cx, |s| {
14100 s.select(selections);
14101 });
14102 }
14103
14104 pub fn split_selection_into_lines(
14105 &mut self,
14106 action: &SplitSelectionIntoLines,
14107 window: &mut Window,
14108 cx: &mut Context<Self>,
14109 ) {
14110 let selections = self
14111 .selections
14112 .all::<Point>(cx)
14113 .into_iter()
14114 .map(|selection| selection.start..selection.end)
14115 .collect::<Vec<_>>();
14116 self.unfold_ranges(&selections, true, true, cx);
14117
14118 let mut new_selection_ranges = Vec::new();
14119 {
14120 let buffer = self.buffer.read(cx).read(cx);
14121 for selection in selections {
14122 for row in selection.start.row..selection.end.row {
14123 let line_start = Point::new(row, 0);
14124 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14125
14126 if action.keep_selections {
14127 // Keep the selection range for each line
14128 let selection_start = if row == selection.start.row {
14129 selection.start
14130 } else {
14131 line_start
14132 };
14133 new_selection_ranges.push(selection_start..line_end);
14134 } else {
14135 // Collapse to cursor at end of line
14136 new_selection_ranges.push(line_end..line_end);
14137 }
14138 }
14139
14140 let is_multiline_selection = selection.start.row != selection.end.row;
14141 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14142 // so this action feels more ergonomic when paired with other selection operations
14143 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14144 if !should_skip_last {
14145 if action.keep_selections {
14146 if is_multiline_selection {
14147 let line_start = Point::new(selection.end.row, 0);
14148 new_selection_ranges.push(line_start..selection.end);
14149 } else {
14150 new_selection_ranges.push(selection.start..selection.end);
14151 }
14152 } else {
14153 new_selection_ranges.push(selection.end..selection.end);
14154 }
14155 }
14156 }
14157 }
14158 self.change_selections(Default::default(), window, cx, |s| {
14159 s.select_ranges(new_selection_ranges);
14160 });
14161 }
14162
14163 pub fn add_selection_above(
14164 &mut self,
14165 _: &AddSelectionAbove,
14166 window: &mut Window,
14167 cx: &mut Context<Self>,
14168 ) {
14169 self.add_selection(true, window, cx);
14170 }
14171
14172 pub fn add_selection_below(
14173 &mut self,
14174 _: &AddSelectionBelow,
14175 window: &mut Window,
14176 cx: &mut Context<Self>,
14177 ) {
14178 self.add_selection(false, window, cx);
14179 }
14180
14181 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14183
14184 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14185 let all_selections = self.selections.all::<Point>(cx);
14186 let text_layout_details = self.text_layout_details(window);
14187
14188 let (mut columnar_selections, new_selections_to_columnarize) = {
14189 if let Some(state) = self.add_selections_state.as_ref() {
14190 let columnar_selection_ids: HashSet<_> = state
14191 .groups
14192 .iter()
14193 .flat_map(|group| group.stack.iter())
14194 .copied()
14195 .collect();
14196
14197 all_selections
14198 .into_iter()
14199 .partition(|s| columnar_selection_ids.contains(&s.id))
14200 } else {
14201 (Vec::new(), all_selections)
14202 }
14203 };
14204
14205 let mut state = self
14206 .add_selections_state
14207 .take()
14208 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14209
14210 for selection in new_selections_to_columnarize {
14211 let range = selection.display_range(&display_map).sorted();
14212 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14213 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14214 let positions = start_x.min(end_x)..start_x.max(end_x);
14215 let mut stack = Vec::new();
14216 for row in range.start.row().0..=range.end.row().0 {
14217 if let Some(selection) = self.selections.build_columnar_selection(
14218 &display_map,
14219 DisplayRow(row),
14220 &positions,
14221 selection.reversed,
14222 &text_layout_details,
14223 ) {
14224 stack.push(selection.id);
14225 columnar_selections.push(selection);
14226 }
14227 }
14228 if !stack.is_empty() {
14229 if above {
14230 stack.reverse();
14231 }
14232 state.groups.push(AddSelectionsGroup { above, stack });
14233 }
14234 }
14235
14236 let mut final_selections = Vec::new();
14237 let end_row = if above {
14238 DisplayRow(0)
14239 } else {
14240 display_map.max_point().row()
14241 };
14242
14243 let mut last_added_item_per_group = HashMap::default();
14244 for group in state.groups.iter_mut() {
14245 if let Some(last_id) = group.stack.last() {
14246 last_added_item_per_group.insert(*last_id, group);
14247 }
14248 }
14249
14250 for selection in columnar_selections {
14251 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14252 if above == group.above {
14253 let range = selection.display_range(&display_map).sorted();
14254 debug_assert_eq!(range.start.row(), range.end.row());
14255 let mut row = range.start.row();
14256 let positions =
14257 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14258 px(start)..px(end)
14259 } else {
14260 let start_x =
14261 display_map.x_for_display_point(range.start, &text_layout_details);
14262 let end_x =
14263 display_map.x_for_display_point(range.end, &text_layout_details);
14264 start_x.min(end_x)..start_x.max(end_x)
14265 };
14266
14267 let mut maybe_new_selection = None;
14268 while row != end_row {
14269 if above {
14270 row.0 -= 1;
14271 } else {
14272 row.0 += 1;
14273 }
14274 if let Some(new_selection) = self.selections.build_columnar_selection(
14275 &display_map,
14276 row,
14277 &positions,
14278 selection.reversed,
14279 &text_layout_details,
14280 ) {
14281 maybe_new_selection = Some(new_selection);
14282 break;
14283 }
14284 }
14285
14286 if let Some(new_selection) = maybe_new_selection {
14287 group.stack.push(new_selection.id);
14288 if above {
14289 final_selections.push(new_selection);
14290 final_selections.push(selection);
14291 } else {
14292 final_selections.push(selection);
14293 final_selections.push(new_selection);
14294 }
14295 } else {
14296 final_selections.push(selection);
14297 }
14298 } else {
14299 group.stack.pop();
14300 }
14301 } else {
14302 final_selections.push(selection);
14303 }
14304 }
14305
14306 self.change_selections(Default::default(), window, cx, |s| {
14307 s.select(final_selections);
14308 });
14309
14310 let final_selection_ids: HashSet<_> = self
14311 .selections
14312 .all::<Point>(cx)
14313 .iter()
14314 .map(|s| s.id)
14315 .collect();
14316 state.groups.retain_mut(|group| {
14317 // selections might get merged above so we remove invalid items from stacks
14318 group.stack.retain(|id| final_selection_ids.contains(id));
14319
14320 // single selection in stack can be treated as initial state
14321 group.stack.len() > 1
14322 });
14323
14324 if !state.groups.is_empty() {
14325 self.add_selections_state = Some(state);
14326 }
14327 }
14328
14329 fn select_match_ranges(
14330 &mut self,
14331 range: Range<usize>,
14332 reversed: bool,
14333 replace_newest: bool,
14334 auto_scroll: Option<Autoscroll>,
14335 window: &mut Window,
14336 cx: &mut Context<Editor>,
14337 ) {
14338 self.unfold_ranges(
14339 std::slice::from_ref(&range),
14340 false,
14341 auto_scroll.is_some(),
14342 cx,
14343 );
14344 let effects = if let Some(scroll) = auto_scroll {
14345 SelectionEffects::scroll(scroll)
14346 } else {
14347 SelectionEffects::no_scroll()
14348 };
14349 self.change_selections(effects, window, cx, |s| {
14350 if replace_newest {
14351 s.delete(s.newest_anchor().id);
14352 }
14353 if reversed {
14354 s.insert_range(range.end..range.start);
14355 } else {
14356 s.insert_range(range);
14357 }
14358 });
14359 }
14360
14361 pub fn select_next_match_internal(
14362 &mut self,
14363 display_map: &DisplaySnapshot,
14364 replace_newest: bool,
14365 autoscroll: Option<Autoscroll>,
14366 window: &mut Window,
14367 cx: &mut Context<Self>,
14368 ) -> Result<()> {
14369 let buffer = &display_map.buffer_snapshot;
14370 let mut selections = self.selections.all::<usize>(cx);
14371 if let Some(mut select_next_state) = self.select_next_state.take() {
14372 let query = &select_next_state.query;
14373 if !select_next_state.done {
14374 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14375 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14376 let mut next_selected_range = None;
14377
14378 let bytes_after_last_selection =
14379 buffer.bytes_in_range(last_selection.end..buffer.len());
14380 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14381 let query_matches = query
14382 .stream_find_iter(bytes_after_last_selection)
14383 .map(|result| (last_selection.end, result))
14384 .chain(
14385 query
14386 .stream_find_iter(bytes_before_first_selection)
14387 .map(|result| (0, result)),
14388 );
14389
14390 for (start_offset, query_match) in query_matches {
14391 let query_match = query_match.unwrap(); // can only fail due to I/O
14392 let offset_range =
14393 start_offset + query_match.start()..start_offset + query_match.end();
14394
14395 if !select_next_state.wordwise
14396 || (!buffer.is_inside_word(offset_range.start, None)
14397 && !buffer.is_inside_word(offset_range.end, None))
14398 {
14399 // TODO: This is n^2, because we might check all the selections
14400 if !selections
14401 .iter()
14402 .any(|selection| selection.range().overlaps(&offset_range))
14403 {
14404 next_selected_range = Some(offset_range);
14405 break;
14406 }
14407 }
14408 }
14409
14410 if let Some(next_selected_range) = next_selected_range {
14411 self.select_match_ranges(
14412 next_selected_range,
14413 last_selection.reversed,
14414 replace_newest,
14415 autoscroll,
14416 window,
14417 cx,
14418 );
14419 } else {
14420 select_next_state.done = true;
14421 }
14422 }
14423
14424 self.select_next_state = Some(select_next_state);
14425 } else {
14426 let mut only_carets = true;
14427 let mut same_text_selected = true;
14428 let mut selected_text = None;
14429
14430 let mut selections_iter = selections.iter().peekable();
14431 while let Some(selection) = selections_iter.next() {
14432 if selection.start != selection.end {
14433 only_carets = false;
14434 }
14435
14436 if same_text_selected {
14437 if selected_text.is_none() {
14438 selected_text =
14439 Some(buffer.text_for_range(selection.range()).collect::<String>());
14440 }
14441
14442 if let Some(next_selection) = selections_iter.peek() {
14443 if next_selection.range().len() == selection.range().len() {
14444 let next_selected_text = buffer
14445 .text_for_range(next_selection.range())
14446 .collect::<String>();
14447 if Some(next_selected_text) != selected_text {
14448 same_text_selected = false;
14449 selected_text = None;
14450 }
14451 } else {
14452 same_text_selected = false;
14453 selected_text = None;
14454 }
14455 }
14456 }
14457 }
14458
14459 if only_carets {
14460 for selection in &mut selections {
14461 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14462 selection.start = word_range.start;
14463 selection.end = word_range.end;
14464 selection.goal = SelectionGoal::None;
14465 selection.reversed = false;
14466 self.select_match_ranges(
14467 selection.start..selection.end,
14468 selection.reversed,
14469 replace_newest,
14470 autoscroll,
14471 window,
14472 cx,
14473 );
14474 }
14475
14476 if selections.len() == 1 {
14477 let selection = selections
14478 .last()
14479 .expect("ensured that there's only one selection");
14480 let query = buffer
14481 .text_for_range(selection.start..selection.end)
14482 .collect::<String>();
14483 let is_empty = query.is_empty();
14484 let select_state = SelectNextState {
14485 query: AhoCorasick::new(&[query])?,
14486 wordwise: true,
14487 done: is_empty,
14488 };
14489 self.select_next_state = Some(select_state);
14490 } else {
14491 self.select_next_state = None;
14492 }
14493 } else if let Some(selected_text) = selected_text {
14494 self.select_next_state = Some(SelectNextState {
14495 query: AhoCorasick::new(&[selected_text])?,
14496 wordwise: false,
14497 done: false,
14498 });
14499 self.select_next_match_internal(
14500 display_map,
14501 replace_newest,
14502 autoscroll,
14503 window,
14504 cx,
14505 )?;
14506 }
14507 }
14508 Ok(())
14509 }
14510
14511 pub fn select_all_matches(
14512 &mut self,
14513 _action: &SelectAllMatches,
14514 window: &mut Window,
14515 cx: &mut Context<Self>,
14516 ) -> Result<()> {
14517 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14518
14519 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14520
14521 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14522 let Some(select_next_state) = self.select_next_state.as_mut() else {
14523 return Ok(());
14524 };
14525 if select_next_state.done {
14526 return Ok(());
14527 }
14528
14529 let mut new_selections = Vec::new();
14530
14531 let reversed = self.selections.oldest::<usize>(cx).reversed;
14532 let buffer = &display_map.buffer_snapshot;
14533 let query_matches = select_next_state
14534 .query
14535 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14536
14537 for query_match in query_matches.into_iter() {
14538 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14539 let offset_range = if reversed {
14540 query_match.end()..query_match.start()
14541 } else {
14542 query_match.start()..query_match.end()
14543 };
14544
14545 if !select_next_state.wordwise
14546 || (!buffer.is_inside_word(offset_range.start, None)
14547 && !buffer.is_inside_word(offset_range.end, None))
14548 {
14549 new_selections.push(offset_range.start..offset_range.end);
14550 }
14551 }
14552
14553 select_next_state.done = true;
14554
14555 if new_selections.is_empty() {
14556 log::error!("bug: new_selections is empty in select_all_matches");
14557 return Ok(());
14558 }
14559
14560 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14561 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14562 selections.select_ranges(new_selections)
14563 });
14564
14565 Ok(())
14566 }
14567
14568 pub fn select_next(
14569 &mut self,
14570 action: &SelectNext,
14571 window: &mut Window,
14572 cx: &mut Context<Self>,
14573 ) -> Result<()> {
14574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14576 self.select_next_match_internal(
14577 &display_map,
14578 action.replace_newest,
14579 Some(Autoscroll::newest()),
14580 window,
14581 cx,
14582 )?;
14583 Ok(())
14584 }
14585
14586 pub fn select_previous(
14587 &mut self,
14588 action: &SelectPrevious,
14589 window: &mut Window,
14590 cx: &mut Context<Self>,
14591 ) -> Result<()> {
14592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14593 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14594 let buffer = &display_map.buffer_snapshot;
14595 let mut selections = self.selections.all::<usize>(cx);
14596 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14597 let query = &select_prev_state.query;
14598 if !select_prev_state.done {
14599 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14600 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14601 let mut next_selected_range = None;
14602 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14603 let bytes_before_last_selection =
14604 buffer.reversed_bytes_in_range(0..last_selection.start);
14605 let bytes_after_first_selection =
14606 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14607 let query_matches = query
14608 .stream_find_iter(bytes_before_last_selection)
14609 .map(|result| (last_selection.start, result))
14610 .chain(
14611 query
14612 .stream_find_iter(bytes_after_first_selection)
14613 .map(|result| (buffer.len(), result)),
14614 );
14615 for (end_offset, query_match) in query_matches {
14616 let query_match = query_match.unwrap(); // can only fail due to I/O
14617 let offset_range =
14618 end_offset - query_match.end()..end_offset - query_match.start();
14619
14620 if !select_prev_state.wordwise
14621 || (!buffer.is_inside_word(offset_range.start, None)
14622 && !buffer.is_inside_word(offset_range.end, None))
14623 {
14624 next_selected_range = Some(offset_range);
14625 break;
14626 }
14627 }
14628
14629 if let Some(next_selected_range) = next_selected_range {
14630 self.select_match_ranges(
14631 next_selected_range,
14632 last_selection.reversed,
14633 action.replace_newest,
14634 Some(Autoscroll::newest()),
14635 window,
14636 cx,
14637 );
14638 } else {
14639 select_prev_state.done = true;
14640 }
14641 }
14642
14643 self.select_prev_state = Some(select_prev_state);
14644 } else {
14645 let mut only_carets = true;
14646 let mut same_text_selected = true;
14647 let mut selected_text = None;
14648
14649 let mut selections_iter = selections.iter().peekable();
14650 while let Some(selection) = selections_iter.next() {
14651 if selection.start != selection.end {
14652 only_carets = false;
14653 }
14654
14655 if same_text_selected {
14656 if selected_text.is_none() {
14657 selected_text =
14658 Some(buffer.text_for_range(selection.range()).collect::<String>());
14659 }
14660
14661 if let Some(next_selection) = selections_iter.peek() {
14662 if next_selection.range().len() == selection.range().len() {
14663 let next_selected_text = buffer
14664 .text_for_range(next_selection.range())
14665 .collect::<String>();
14666 if Some(next_selected_text) != selected_text {
14667 same_text_selected = false;
14668 selected_text = None;
14669 }
14670 } else {
14671 same_text_selected = false;
14672 selected_text = None;
14673 }
14674 }
14675 }
14676 }
14677
14678 if only_carets {
14679 for selection in &mut selections {
14680 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14681 selection.start = word_range.start;
14682 selection.end = word_range.end;
14683 selection.goal = SelectionGoal::None;
14684 selection.reversed = false;
14685 self.select_match_ranges(
14686 selection.start..selection.end,
14687 selection.reversed,
14688 action.replace_newest,
14689 Some(Autoscroll::newest()),
14690 window,
14691 cx,
14692 );
14693 }
14694 if selections.len() == 1 {
14695 let selection = selections
14696 .last()
14697 .expect("ensured that there's only one selection");
14698 let query = buffer
14699 .text_for_range(selection.start..selection.end)
14700 .collect::<String>();
14701 let is_empty = query.is_empty();
14702 let select_state = SelectNextState {
14703 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14704 wordwise: true,
14705 done: is_empty,
14706 };
14707 self.select_prev_state = Some(select_state);
14708 } else {
14709 self.select_prev_state = None;
14710 }
14711 } else if let Some(selected_text) = selected_text {
14712 self.select_prev_state = Some(SelectNextState {
14713 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14714 wordwise: false,
14715 done: false,
14716 });
14717 self.select_previous(action, window, cx)?;
14718 }
14719 }
14720 Ok(())
14721 }
14722
14723 pub fn find_next_match(
14724 &mut self,
14725 _: &FindNextMatch,
14726 window: &mut Window,
14727 cx: &mut Context<Self>,
14728 ) -> Result<()> {
14729 let selections = self.selections.disjoint_anchors_arc();
14730 match selections.first() {
14731 Some(first) if selections.len() >= 2 => {
14732 self.change_selections(Default::default(), window, cx, |s| {
14733 s.select_ranges([first.range()]);
14734 });
14735 }
14736 _ => self.select_next(
14737 &SelectNext {
14738 replace_newest: true,
14739 },
14740 window,
14741 cx,
14742 )?,
14743 }
14744 Ok(())
14745 }
14746
14747 pub fn find_previous_match(
14748 &mut self,
14749 _: &FindPreviousMatch,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) -> Result<()> {
14753 let selections = self.selections.disjoint_anchors_arc();
14754 match selections.last() {
14755 Some(last) if selections.len() >= 2 => {
14756 self.change_selections(Default::default(), window, cx, |s| {
14757 s.select_ranges([last.range()]);
14758 });
14759 }
14760 _ => self.select_previous(
14761 &SelectPrevious {
14762 replace_newest: true,
14763 },
14764 window,
14765 cx,
14766 )?,
14767 }
14768 Ok(())
14769 }
14770
14771 pub fn toggle_comments(
14772 &mut self,
14773 action: &ToggleComments,
14774 window: &mut Window,
14775 cx: &mut Context<Self>,
14776 ) {
14777 if self.read_only(cx) {
14778 return;
14779 }
14780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14781 let text_layout_details = &self.text_layout_details(window);
14782 self.transact(window, cx, |this, window, cx| {
14783 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14784 let mut edits = Vec::new();
14785 let mut selection_edit_ranges = Vec::new();
14786 let mut last_toggled_row = None;
14787 let snapshot = this.buffer.read(cx).read(cx);
14788 let empty_str: Arc<str> = Arc::default();
14789 let mut suffixes_inserted = Vec::new();
14790 let ignore_indent = action.ignore_indent;
14791
14792 fn comment_prefix_range(
14793 snapshot: &MultiBufferSnapshot,
14794 row: MultiBufferRow,
14795 comment_prefix: &str,
14796 comment_prefix_whitespace: &str,
14797 ignore_indent: bool,
14798 ) -> Range<Point> {
14799 let indent_size = if ignore_indent {
14800 0
14801 } else {
14802 snapshot.indent_size_for_line(row).len
14803 };
14804
14805 let start = Point::new(row.0, indent_size);
14806
14807 let mut line_bytes = snapshot
14808 .bytes_in_range(start..snapshot.max_point())
14809 .flatten()
14810 .copied();
14811
14812 // If this line currently begins with the line comment prefix, then record
14813 // the range containing the prefix.
14814 if line_bytes
14815 .by_ref()
14816 .take(comment_prefix.len())
14817 .eq(comment_prefix.bytes())
14818 {
14819 // Include any whitespace that matches the comment prefix.
14820 let matching_whitespace_len = line_bytes
14821 .zip(comment_prefix_whitespace.bytes())
14822 .take_while(|(a, b)| a == b)
14823 .count() as u32;
14824 let end = Point::new(
14825 start.row,
14826 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14827 );
14828 start..end
14829 } else {
14830 start..start
14831 }
14832 }
14833
14834 fn comment_suffix_range(
14835 snapshot: &MultiBufferSnapshot,
14836 row: MultiBufferRow,
14837 comment_suffix: &str,
14838 comment_suffix_has_leading_space: bool,
14839 ) -> Range<Point> {
14840 let end = Point::new(row.0, snapshot.line_len(row));
14841 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14842
14843 let mut line_end_bytes = snapshot
14844 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14845 .flatten()
14846 .copied();
14847
14848 let leading_space_len = if suffix_start_column > 0
14849 && line_end_bytes.next() == Some(b' ')
14850 && comment_suffix_has_leading_space
14851 {
14852 1
14853 } else {
14854 0
14855 };
14856
14857 // If this line currently begins with the line comment prefix, then record
14858 // the range containing the prefix.
14859 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14860 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14861 start..end
14862 } else {
14863 end..end
14864 }
14865 }
14866
14867 // TODO: Handle selections that cross excerpts
14868 for selection in &mut selections {
14869 let start_column = snapshot
14870 .indent_size_for_line(MultiBufferRow(selection.start.row))
14871 .len;
14872 let language = if let Some(language) =
14873 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14874 {
14875 language
14876 } else {
14877 continue;
14878 };
14879
14880 selection_edit_ranges.clear();
14881
14882 // If multiple selections contain a given row, avoid processing that
14883 // row more than once.
14884 let mut start_row = MultiBufferRow(selection.start.row);
14885 if last_toggled_row == Some(start_row) {
14886 start_row = start_row.next_row();
14887 }
14888 let end_row =
14889 if selection.end.row > selection.start.row && selection.end.column == 0 {
14890 MultiBufferRow(selection.end.row - 1)
14891 } else {
14892 MultiBufferRow(selection.end.row)
14893 };
14894 last_toggled_row = Some(end_row);
14895
14896 if start_row > end_row {
14897 continue;
14898 }
14899
14900 // If the language has line comments, toggle those.
14901 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14902
14903 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14904 if ignore_indent {
14905 full_comment_prefixes = full_comment_prefixes
14906 .into_iter()
14907 .map(|s| Arc::from(s.trim_end()))
14908 .collect();
14909 }
14910
14911 if !full_comment_prefixes.is_empty() {
14912 let first_prefix = full_comment_prefixes
14913 .first()
14914 .expect("prefixes is non-empty");
14915 let prefix_trimmed_lengths = full_comment_prefixes
14916 .iter()
14917 .map(|p| p.trim_end_matches(' ').len())
14918 .collect::<SmallVec<[usize; 4]>>();
14919
14920 let mut all_selection_lines_are_comments = true;
14921
14922 for row in start_row.0..=end_row.0 {
14923 let row = MultiBufferRow(row);
14924 if start_row < end_row && snapshot.is_line_blank(row) {
14925 continue;
14926 }
14927
14928 let prefix_range = full_comment_prefixes
14929 .iter()
14930 .zip(prefix_trimmed_lengths.iter().copied())
14931 .map(|(prefix, trimmed_prefix_len)| {
14932 comment_prefix_range(
14933 snapshot.deref(),
14934 row,
14935 &prefix[..trimmed_prefix_len],
14936 &prefix[trimmed_prefix_len..],
14937 ignore_indent,
14938 )
14939 })
14940 .max_by_key(|range| range.end.column - range.start.column)
14941 .expect("prefixes is non-empty");
14942
14943 if prefix_range.is_empty() {
14944 all_selection_lines_are_comments = false;
14945 }
14946
14947 selection_edit_ranges.push(prefix_range);
14948 }
14949
14950 if all_selection_lines_are_comments {
14951 edits.extend(
14952 selection_edit_ranges
14953 .iter()
14954 .cloned()
14955 .map(|range| (range, empty_str.clone())),
14956 );
14957 } else {
14958 let min_column = selection_edit_ranges
14959 .iter()
14960 .map(|range| range.start.column)
14961 .min()
14962 .unwrap_or(0);
14963 edits.extend(selection_edit_ranges.iter().map(|range| {
14964 let position = Point::new(range.start.row, min_column);
14965 (position..position, first_prefix.clone())
14966 }));
14967 }
14968 } else if let Some(BlockCommentConfig {
14969 start: full_comment_prefix,
14970 end: comment_suffix,
14971 ..
14972 }) = language.block_comment()
14973 {
14974 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14975 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14976 let prefix_range = comment_prefix_range(
14977 snapshot.deref(),
14978 start_row,
14979 comment_prefix,
14980 comment_prefix_whitespace,
14981 ignore_indent,
14982 );
14983 let suffix_range = comment_suffix_range(
14984 snapshot.deref(),
14985 end_row,
14986 comment_suffix.trim_start_matches(' '),
14987 comment_suffix.starts_with(' '),
14988 );
14989
14990 if prefix_range.is_empty() || suffix_range.is_empty() {
14991 edits.push((
14992 prefix_range.start..prefix_range.start,
14993 full_comment_prefix.clone(),
14994 ));
14995 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14996 suffixes_inserted.push((end_row, comment_suffix.len()));
14997 } else {
14998 edits.push((prefix_range, empty_str.clone()));
14999 edits.push((suffix_range, empty_str.clone()));
15000 }
15001 } else {
15002 continue;
15003 }
15004 }
15005
15006 drop(snapshot);
15007 this.buffer.update(cx, |buffer, cx| {
15008 buffer.edit(edits, None, cx);
15009 });
15010
15011 // Adjust selections so that they end before any comment suffixes that
15012 // were inserted.
15013 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15014 let mut selections = this.selections.all::<Point>(cx);
15015 let snapshot = this.buffer.read(cx).read(cx);
15016 for selection in &mut selections {
15017 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15018 match row.cmp(&MultiBufferRow(selection.end.row)) {
15019 Ordering::Less => {
15020 suffixes_inserted.next();
15021 continue;
15022 }
15023 Ordering::Greater => break,
15024 Ordering::Equal => {
15025 if selection.end.column == snapshot.line_len(row) {
15026 if selection.is_empty() {
15027 selection.start.column -= suffix_len as u32;
15028 }
15029 selection.end.column -= suffix_len as u32;
15030 }
15031 break;
15032 }
15033 }
15034 }
15035 }
15036
15037 drop(snapshot);
15038 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15039
15040 let selections = this.selections.all::<Point>(cx);
15041 let selections_on_single_row = selections.windows(2).all(|selections| {
15042 selections[0].start.row == selections[1].start.row
15043 && selections[0].end.row == selections[1].end.row
15044 && selections[0].start.row == selections[0].end.row
15045 });
15046 let selections_selecting = selections
15047 .iter()
15048 .any(|selection| selection.start != selection.end);
15049 let advance_downwards = action.advance_downwards
15050 && selections_on_single_row
15051 && !selections_selecting
15052 && !matches!(this.mode, EditorMode::SingleLine);
15053
15054 if advance_downwards {
15055 let snapshot = this.buffer.read(cx).snapshot(cx);
15056
15057 this.change_selections(Default::default(), window, cx, |s| {
15058 s.move_cursors_with(|display_snapshot, display_point, _| {
15059 let mut point = display_point.to_point(display_snapshot);
15060 point.row += 1;
15061 point = snapshot.clip_point(point, Bias::Left);
15062 let display_point = point.to_display_point(display_snapshot);
15063 let goal = SelectionGoal::HorizontalPosition(
15064 display_snapshot
15065 .x_for_display_point(display_point, text_layout_details)
15066 .into(),
15067 );
15068 (display_point, goal)
15069 })
15070 });
15071 }
15072 });
15073 }
15074
15075 pub fn select_enclosing_symbol(
15076 &mut self,
15077 _: &SelectEnclosingSymbol,
15078 window: &mut Window,
15079 cx: &mut Context<Self>,
15080 ) {
15081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15082
15083 let buffer = self.buffer.read(cx).snapshot(cx);
15084 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15085
15086 fn update_selection(
15087 selection: &Selection<usize>,
15088 buffer_snap: &MultiBufferSnapshot,
15089 ) -> Option<Selection<usize>> {
15090 let cursor = selection.head();
15091 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15092 for symbol in symbols.iter().rev() {
15093 let start = symbol.range.start.to_offset(buffer_snap);
15094 let end = symbol.range.end.to_offset(buffer_snap);
15095 let new_range = start..end;
15096 if start < selection.start || end > selection.end {
15097 return Some(Selection {
15098 id: selection.id,
15099 start: new_range.start,
15100 end: new_range.end,
15101 goal: SelectionGoal::None,
15102 reversed: selection.reversed,
15103 });
15104 }
15105 }
15106 None
15107 }
15108
15109 let mut selected_larger_symbol = false;
15110 let new_selections = old_selections
15111 .iter()
15112 .map(|selection| match update_selection(selection, &buffer) {
15113 Some(new_selection) => {
15114 if new_selection.range() != selection.range() {
15115 selected_larger_symbol = true;
15116 }
15117 new_selection
15118 }
15119 None => selection.clone(),
15120 })
15121 .collect::<Vec<_>>();
15122
15123 if selected_larger_symbol {
15124 self.change_selections(Default::default(), window, cx, |s| {
15125 s.select(new_selections);
15126 });
15127 }
15128 }
15129
15130 pub fn select_larger_syntax_node(
15131 &mut self,
15132 _: &SelectLargerSyntaxNode,
15133 window: &mut Window,
15134 cx: &mut Context<Self>,
15135 ) {
15136 let Some(visible_row_count) = self.visible_row_count() else {
15137 return;
15138 };
15139 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15140 if old_selections.is_empty() {
15141 return;
15142 }
15143
15144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15145
15146 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15147 let buffer = self.buffer.read(cx).snapshot(cx);
15148
15149 let mut selected_larger_node = false;
15150 let mut new_selections = old_selections
15151 .iter()
15152 .map(|selection| {
15153 let old_range = selection.start..selection.end;
15154
15155 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15156 // manually select word at selection
15157 if ["string_content", "inline"].contains(&node.kind()) {
15158 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15159 // ignore if word is already selected
15160 if !word_range.is_empty() && old_range != word_range {
15161 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15162 // only select word if start and end point belongs to same word
15163 if word_range == last_word_range {
15164 selected_larger_node = true;
15165 return Selection {
15166 id: selection.id,
15167 start: word_range.start,
15168 end: word_range.end,
15169 goal: SelectionGoal::None,
15170 reversed: selection.reversed,
15171 };
15172 }
15173 }
15174 }
15175 }
15176
15177 let mut new_range = old_range.clone();
15178 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15179 new_range = range;
15180 if !node.is_named() {
15181 continue;
15182 }
15183 if !display_map.intersects_fold(new_range.start)
15184 && !display_map.intersects_fold(new_range.end)
15185 {
15186 break;
15187 }
15188 }
15189
15190 selected_larger_node |= new_range != old_range;
15191 Selection {
15192 id: selection.id,
15193 start: new_range.start,
15194 end: new_range.end,
15195 goal: SelectionGoal::None,
15196 reversed: selection.reversed,
15197 }
15198 })
15199 .collect::<Vec<_>>();
15200
15201 if !selected_larger_node {
15202 return; // don't put this call in the history
15203 }
15204
15205 // scroll based on transformation done to the last selection created by the user
15206 let (last_old, last_new) = old_selections
15207 .last()
15208 .zip(new_selections.last().cloned())
15209 .expect("old_selections isn't empty");
15210
15211 // revert selection
15212 let is_selection_reversed = {
15213 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15214 new_selections.last_mut().expect("checked above").reversed =
15215 should_newest_selection_be_reversed;
15216 should_newest_selection_be_reversed
15217 };
15218
15219 if selected_larger_node {
15220 self.select_syntax_node_history.disable_clearing = true;
15221 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15222 s.select(new_selections.clone());
15223 });
15224 self.select_syntax_node_history.disable_clearing = false;
15225 }
15226
15227 let start_row = last_new.start.to_display_point(&display_map).row().0;
15228 let end_row = last_new.end.to_display_point(&display_map).row().0;
15229 let selection_height = end_row - start_row + 1;
15230 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15231
15232 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15233 let scroll_behavior = if fits_on_the_screen {
15234 self.request_autoscroll(Autoscroll::fit(), cx);
15235 SelectSyntaxNodeScrollBehavior::FitSelection
15236 } else if is_selection_reversed {
15237 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15238 SelectSyntaxNodeScrollBehavior::CursorTop
15239 } else {
15240 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15241 SelectSyntaxNodeScrollBehavior::CursorBottom
15242 };
15243
15244 self.select_syntax_node_history.push((
15245 old_selections,
15246 scroll_behavior,
15247 is_selection_reversed,
15248 ));
15249 }
15250
15251 pub fn select_smaller_syntax_node(
15252 &mut self,
15253 _: &SelectSmallerSyntaxNode,
15254 window: &mut Window,
15255 cx: &mut Context<Self>,
15256 ) {
15257 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15258
15259 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15260 self.select_syntax_node_history.pop()
15261 {
15262 if let Some(selection) = selections.last_mut() {
15263 selection.reversed = is_selection_reversed;
15264 }
15265
15266 self.select_syntax_node_history.disable_clearing = true;
15267 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15268 s.select(selections.to_vec());
15269 });
15270 self.select_syntax_node_history.disable_clearing = false;
15271
15272 match scroll_behavior {
15273 SelectSyntaxNodeScrollBehavior::CursorTop => {
15274 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15275 }
15276 SelectSyntaxNodeScrollBehavior::FitSelection => {
15277 self.request_autoscroll(Autoscroll::fit(), cx);
15278 }
15279 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15280 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15281 }
15282 }
15283 }
15284 }
15285
15286 pub fn unwrap_syntax_node(
15287 &mut self,
15288 _: &UnwrapSyntaxNode,
15289 window: &mut Window,
15290 cx: &mut Context<Self>,
15291 ) {
15292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15293
15294 let buffer = self.buffer.read(cx).snapshot(cx);
15295 let selections = self
15296 .selections
15297 .all::<usize>(cx)
15298 .into_iter()
15299 // subtracting the offset requires sorting
15300 .sorted_by_key(|i| i.start);
15301
15302 let full_edits = selections
15303 .into_iter()
15304 .filter_map(|selection| {
15305 let child = if selection.is_empty()
15306 && let Some((_, ancestor_range)) =
15307 buffer.syntax_ancestor(selection.start..selection.end)
15308 {
15309 ancestor_range
15310 } else {
15311 selection.range()
15312 };
15313
15314 let mut parent = child.clone();
15315 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15316 parent = ancestor_range;
15317 if parent.start < child.start || parent.end > child.end {
15318 break;
15319 }
15320 }
15321
15322 if parent == child {
15323 return None;
15324 }
15325 let text = buffer.text_for_range(child).collect::<String>();
15326 Some((selection.id, parent, text))
15327 })
15328 .collect::<Vec<_>>();
15329 if full_edits.is_empty() {
15330 return;
15331 }
15332
15333 self.transact(window, cx, |this, window, cx| {
15334 this.buffer.update(cx, |buffer, cx| {
15335 buffer.edit(
15336 full_edits
15337 .iter()
15338 .map(|(_, p, t)| (p.clone(), t.clone()))
15339 .collect::<Vec<_>>(),
15340 None,
15341 cx,
15342 );
15343 });
15344 this.change_selections(Default::default(), window, cx, |s| {
15345 let mut offset = 0;
15346 let mut selections = vec![];
15347 for (id, parent, text) in full_edits {
15348 let start = parent.start - offset;
15349 offset += parent.len() - text.len();
15350 selections.push(Selection {
15351 id,
15352 start,
15353 end: start + text.len(),
15354 reversed: false,
15355 goal: Default::default(),
15356 });
15357 }
15358 s.select(selections);
15359 });
15360 });
15361 }
15362
15363 pub fn select_next_syntax_node(
15364 &mut self,
15365 _: &SelectNextSyntaxNode,
15366 window: &mut Window,
15367 cx: &mut Context<Self>,
15368 ) {
15369 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15370 if old_selections.is_empty() {
15371 return;
15372 }
15373
15374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15375
15376 let buffer = self.buffer.read(cx).snapshot(cx);
15377 let mut selected_sibling = false;
15378
15379 let new_selections = old_selections
15380 .iter()
15381 .map(|selection| {
15382 let old_range = selection.start..selection.end;
15383
15384 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15385 let new_range = node.byte_range();
15386 selected_sibling = true;
15387 Selection {
15388 id: selection.id,
15389 start: new_range.start,
15390 end: new_range.end,
15391 goal: SelectionGoal::None,
15392 reversed: selection.reversed,
15393 }
15394 } else {
15395 selection.clone()
15396 }
15397 })
15398 .collect::<Vec<_>>();
15399
15400 if selected_sibling {
15401 self.change_selections(
15402 SelectionEffects::scroll(Autoscroll::fit()),
15403 window,
15404 cx,
15405 |s| {
15406 s.select(new_selections);
15407 },
15408 );
15409 }
15410 }
15411
15412 pub fn select_prev_syntax_node(
15413 &mut self,
15414 _: &SelectPreviousSyntaxNode,
15415 window: &mut Window,
15416 cx: &mut Context<Self>,
15417 ) {
15418 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15419 if old_selections.is_empty() {
15420 return;
15421 }
15422
15423 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15424
15425 let buffer = self.buffer.read(cx).snapshot(cx);
15426 let mut selected_sibling = false;
15427
15428 let new_selections = old_selections
15429 .iter()
15430 .map(|selection| {
15431 let old_range = selection.start..selection.end;
15432
15433 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15434 let new_range = node.byte_range();
15435 selected_sibling = true;
15436 Selection {
15437 id: selection.id,
15438 start: new_range.start,
15439 end: new_range.end,
15440 goal: SelectionGoal::None,
15441 reversed: selection.reversed,
15442 }
15443 } else {
15444 selection.clone()
15445 }
15446 })
15447 .collect::<Vec<_>>();
15448
15449 if selected_sibling {
15450 self.change_selections(
15451 SelectionEffects::scroll(Autoscroll::fit()),
15452 window,
15453 cx,
15454 |s| {
15455 s.select(new_selections);
15456 },
15457 );
15458 }
15459 }
15460
15461 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15462 if !EditorSettings::get_global(cx).gutter.runnables {
15463 self.clear_tasks();
15464 return Task::ready(());
15465 }
15466 let project = self.project().map(Entity::downgrade);
15467 let task_sources = self.lsp_task_sources(cx);
15468 let multi_buffer = self.buffer.downgrade();
15469 cx.spawn_in(window, async move |editor, cx| {
15470 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15471 let Some(project) = project.and_then(|p| p.upgrade()) else {
15472 return;
15473 };
15474 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15475 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15476 }) else {
15477 return;
15478 };
15479
15480 let hide_runnables = project
15481 .update(cx, |project, _| project.is_via_collab())
15482 .unwrap_or(true);
15483 if hide_runnables {
15484 return;
15485 }
15486 let new_rows =
15487 cx.background_spawn({
15488 let snapshot = display_snapshot.clone();
15489 async move {
15490 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15491 }
15492 })
15493 .await;
15494 let Ok(lsp_tasks) =
15495 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15496 else {
15497 return;
15498 };
15499 let lsp_tasks = lsp_tasks.await;
15500
15501 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15502 lsp_tasks
15503 .into_iter()
15504 .flat_map(|(kind, tasks)| {
15505 tasks.into_iter().filter_map(move |(location, task)| {
15506 Some((kind.clone(), location?, task))
15507 })
15508 })
15509 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15510 let buffer = location.target.buffer;
15511 let buffer_snapshot = buffer.read(cx).snapshot();
15512 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15513 |(excerpt_id, snapshot, _)| {
15514 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15515 display_snapshot
15516 .buffer_snapshot
15517 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15518 } else {
15519 None
15520 }
15521 },
15522 );
15523 if let Some(offset) = offset {
15524 let task_buffer_range =
15525 location.target.range.to_point(&buffer_snapshot);
15526 let context_buffer_range =
15527 task_buffer_range.to_offset(&buffer_snapshot);
15528 let context_range = BufferOffset(context_buffer_range.start)
15529 ..BufferOffset(context_buffer_range.end);
15530
15531 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15532 .or_insert_with(|| RunnableTasks {
15533 templates: Vec::new(),
15534 offset,
15535 column: task_buffer_range.start.column,
15536 extra_variables: HashMap::default(),
15537 context_range,
15538 })
15539 .templates
15540 .push((kind, task.original_task().clone()));
15541 }
15542
15543 acc
15544 })
15545 }) else {
15546 return;
15547 };
15548
15549 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15550 buffer.language_settings(cx).tasks.prefer_lsp
15551 }) else {
15552 return;
15553 };
15554
15555 let rows = Self::runnable_rows(
15556 project,
15557 display_snapshot,
15558 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15559 new_rows,
15560 cx.clone(),
15561 )
15562 .await;
15563 editor
15564 .update(cx, |editor, _| {
15565 editor.clear_tasks();
15566 for (key, mut value) in rows {
15567 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15568 value.templates.extend(lsp_tasks.templates);
15569 }
15570
15571 editor.insert_tasks(key, value);
15572 }
15573 for (key, value) in lsp_tasks_by_rows {
15574 editor.insert_tasks(key, value);
15575 }
15576 })
15577 .ok();
15578 })
15579 }
15580 fn fetch_runnable_ranges(
15581 snapshot: &DisplaySnapshot,
15582 range: Range<Anchor>,
15583 ) -> Vec<language::RunnableRange> {
15584 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15585 }
15586
15587 fn runnable_rows(
15588 project: Entity<Project>,
15589 snapshot: DisplaySnapshot,
15590 prefer_lsp: bool,
15591 runnable_ranges: Vec<RunnableRange>,
15592 cx: AsyncWindowContext,
15593 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15594 cx.spawn(async move |cx| {
15595 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15596 for mut runnable in runnable_ranges {
15597 let Some(tasks) = cx
15598 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15599 .ok()
15600 else {
15601 continue;
15602 };
15603 let mut tasks = tasks.await;
15604
15605 if prefer_lsp {
15606 tasks.retain(|(task_kind, _)| {
15607 !matches!(task_kind, TaskSourceKind::Language { .. })
15608 });
15609 }
15610 if tasks.is_empty() {
15611 continue;
15612 }
15613
15614 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15615 let Some(row) = snapshot
15616 .buffer_snapshot
15617 .buffer_line_for_row(MultiBufferRow(point.row))
15618 .map(|(_, range)| range.start.row)
15619 else {
15620 continue;
15621 };
15622
15623 let context_range =
15624 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15625 runnable_rows.push((
15626 (runnable.buffer_id, row),
15627 RunnableTasks {
15628 templates: tasks,
15629 offset: snapshot
15630 .buffer_snapshot
15631 .anchor_before(runnable.run_range.start),
15632 context_range,
15633 column: point.column,
15634 extra_variables: runnable.extra_captures,
15635 },
15636 ));
15637 }
15638 runnable_rows
15639 })
15640 }
15641
15642 fn templates_with_tags(
15643 project: &Entity<Project>,
15644 runnable: &mut Runnable,
15645 cx: &mut App,
15646 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15647 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15648 let (worktree_id, file) = project
15649 .buffer_for_id(runnable.buffer, cx)
15650 .and_then(|buffer| buffer.read(cx).file())
15651 .map(|file| (file.worktree_id(cx), file.clone()))
15652 .unzip();
15653
15654 (
15655 project.task_store().read(cx).task_inventory().cloned(),
15656 worktree_id,
15657 file,
15658 )
15659 });
15660
15661 let tags = mem::take(&mut runnable.tags);
15662 let language = runnable.language.clone();
15663 cx.spawn(async move |cx| {
15664 let mut templates_with_tags = Vec::new();
15665 if let Some(inventory) = inventory {
15666 for RunnableTag(tag) in tags {
15667 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15668 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15669 }) else {
15670 return templates_with_tags;
15671 };
15672 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15673 move |(_, template)| {
15674 template.tags.iter().any(|source_tag| source_tag == &tag)
15675 },
15676 ));
15677 }
15678 }
15679 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15680
15681 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15682 // Strongest source wins; if we have worktree tag binding, prefer that to
15683 // global and language bindings;
15684 // if we have a global binding, prefer that to language binding.
15685 let first_mismatch = templates_with_tags
15686 .iter()
15687 .position(|(tag_source, _)| tag_source != leading_tag_source);
15688 if let Some(index) = first_mismatch {
15689 templates_with_tags.truncate(index);
15690 }
15691 }
15692
15693 templates_with_tags
15694 })
15695 }
15696
15697 pub fn move_to_enclosing_bracket(
15698 &mut self,
15699 _: &MoveToEnclosingBracket,
15700 window: &mut Window,
15701 cx: &mut Context<Self>,
15702 ) {
15703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15704 self.change_selections(Default::default(), window, cx, |s| {
15705 s.move_offsets_with(|snapshot, selection| {
15706 let Some(enclosing_bracket_ranges) =
15707 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15708 else {
15709 return;
15710 };
15711
15712 let mut best_length = usize::MAX;
15713 let mut best_inside = false;
15714 let mut best_in_bracket_range = false;
15715 let mut best_destination = None;
15716 for (open, close) in enclosing_bracket_ranges {
15717 let close = close.to_inclusive();
15718 let length = close.end() - open.start;
15719 let inside = selection.start >= open.end && selection.end <= *close.start();
15720 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15721 || close.contains(&selection.head());
15722
15723 // If best is next to a bracket and current isn't, skip
15724 if !in_bracket_range && best_in_bracket_range {
15725 continue;
15726 }
15727
15728 // Prefer smaller lengths unless best is inside and current isn't
15729 if length > best_length && (best_inside || !inside) {
15730 continue;
15731 }
15732
15733 best_length = length;
15734 best_inside = inside;
15735 best_in_bracket_range = in_bracket_range;
15736 best_destination = Some(
15737 if close.contains(&selection.start) && close.contains(&selection.end) {
15738 if inside { open.end } else { open.start }
15739 } else if inside {
15740 *close.start()
15741 } else {
15742 *close.end()
15743 },
15744 );
15745 }
15746
15747 if let Some(destination) = best_destination {
15748 selection.collapse_to(destination, SelectionGoal::None);
15749 }
15750 })
15751 });
15752 }
15753
15754 pub fn undo_selection(
15755 &mut self,
15756 _: &UndoSelection,
15757 window: &mut Window,
15758 cx: &mut Context<Self>,
15759 ) {
15760 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15761 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15762 self.selection_history.mode = SelectionHistoryMode::Undoing;
15763 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15764 this.end_selection(window, cx);
15765 this.change_selections(
15766 SelectionEffects::scroll(Autoscroll::newest()),
15767 window,
15768 cx,
15769 |s| s.select_anchors(entry.selections.to_vec()),
15770 );
15771 });
15772 self.selection_history.mode = SelectionHistoryMode::Normal;
15773
15774 self.select_next_state = entry.select_next_state;
15775 self.select_prev_state = entry.select_prev_state;
15776 self.add_selections_state = entry.add_selections_state;
15777 }
15778 }
15779
15780 pub fn redo_selection(
15781 &mut self,
15782 _: &RedoSelection,
15783 window: &mut Window,
15784 cx: &mut Context<Self>,
15785 ) {
15786 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15787 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15788 self.selection_history.mode = SelectionHistoryMode::Redoing;
15789 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15790 this.end_selection(window, cx);
15791 this.change_selections(
15792 SelectionEffects::scroll(Autoscroll::newest()),
15793 window,
15794 cx,
15795 |s| s.select_anchors(entry.selections.to_vec()),
15796 );
15797 });
15798 self.selection_history.mode = SelectionHistoryMode::Normal;
15799
15800 self.select_next_state = entry.select_next_state;
15801 self.select_prev_state = entry.select_prev_state;
15802 self.add_selections_state = entry.add_selections_state;
15803 }
15804 }
15805
15806 pub fn expand_excerpts(
15807 &mut self,
15808 action: &ExpandExcerpts,
15809 _: &mut Window,
15810 cx: &mut Context<Self>,
15811 ) {
15812 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15813 }
15814
15815 pub fn expand_excerpts_down(
15816 &mut self,
15817 action: &ExpandExcerptsDown,
15818 _: &mut Window,
15819 cx: &mut Context<Self>,
15820 ) {
15821 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15822 }
15823
15824 pub fn expand_excerpts_up(
15825 &mut self,
15826 action: &ExpandExcerptsUp,
15827 _: &mut Window,
15828 cx: &mut Context<Self>,
15829 ) {
15830 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15831 }
15832
15833 pub fn expand_excerpts_for_direction(
15834 &mut self,
15835 lines: u32,
15836 direction: ExpandExcerptDirection,
15837
15838 cx: &mut Context<Self>,
15839 ) {
15840 let selections = self.selections.disjoint_anchors_arc();
15841
15842 let lines = if lines == 0 {
15843 EditorSettings::get_global(cx).expand_excerpt_lines
15844 } else {
15845 lines
15846 };
15847
15848 self.buffer.update(cx, |buffer, cx| {
15849 let snapshot = buffer.snapshot(cx);
15850 let mut excerpt_ids = selections
15851 .iter()
15852 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15853 .collect::<Vec<_>>();
15854 excerpt_ids.sort();
15855 excerpt_ids.dedup();
15856 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15857 })
15858 }
15859
15860 pub fn expand_excerpt(
15861 &mut self,
15862 excerpt: ExcerptId,
15863 direction: ExpandExcerptDirection,
15864 window: &mut Window,
15865 cx: &mut Context<Self>,
15866 ) {
15867 let current_scroll_position = self.scroll_position(cx);
15868 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15869 let mut should_scroll_up = false;
15870
15871 if direction == ExpandExcerptDirection::Down {
15872 let multi_buffer = self.buffer.read(cx);
15873 let snapshot = multi_buffer.snapshot(cx);
15874 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15875 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15876 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15877 {
15878 let buffer_snapshot = buffer.read(cx).snapshot();
15879 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15880 let last_row = buffer_snapshot.max_point().row;
15881 let lines_below = last_row.saturating_sub(excerpt_end_row);
15882 should_scroll_up = lines_below >= lines_to_expand;
15883 }
15884 }
15885
15886 self.buffer.update(cx, |buffer, cx| {
15887 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15888 });
15889
15890 if should_scroll_up {
15891 let new_scroll_position =
15892 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15893 self.set_scroll_position(new_scroll_position, window, cx);
15894 }
15895 }
15896
15897 pub fn go_to_singleton_buffer_point(
15898 &mut self,
15899 point: Point,
15900 window: &mut Window,
15901 cx: &mut Context<Self>,
15902 ) {
15903 self.go_to_singleton_buffer_range(point..point, window, cx);
15904 }
15905
15906 pub fn go_to_singleton_buffer_range(
15907 &mut self,
15908 range: Range<Point>,
15909 window: &mut Window,
15910 cx: &mut Context<Self>,
15911 ) {
15912 let multibuffer = self.buffer().read(cx);
15913 let Some(buffer) = multibuffer.as_singleton() else {
15914 return;
15915 };
15916 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15917 return;
15918 };
15919 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15920 return;
15921 };
15922 self.change_selections(
15923 SelectionEffects::default().nav_history(true),
15924 window,
15925 cx,
15926 |s| s.select_anchor_ranges([start..end]),
15927 );
15928 }
15929
15930 pub fn go_to_diagnostic(
15931 &mut self,
15932 action: &GoToDiagnostic,
15933 window: &mut Window,
15934 cx: &mut Context<Self>,
15935 ) {
15936 if !self.diagnostics_enabled() {
15937 return;
15938 }
15939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15940 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15941 }
15942
15943 pub fn go_to_prev_diagnostic(
15944 &mut self,
15945 action: &GoToPreviousDiagnostic,
15946 window: &mut Window,
15947 cx: &mut Context<Self>,
15948 ) {
15949 if !self.diagnostics_enabled() {
15950 return;
15951 }
15952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15953 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15954 }
15955
15956 pub fn go_to_diagnostic_impl(
15957 &mut self,
15958 direction: Direction,
15959 severity: GoToDiagnosticSeverityFilter,
15960 window: &mut Window,
15961 cx: &mut Context<Self>,
15962 ) {
15963 let buffer = self.buffer.read(cx).snapshot(cx);
15964 let selection = self.selections.newest::<usize>(cx);
15965
15966 let mut active_group_id = None;
15967 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15968 && active_group.active_range.start.to_offset(&buffer) == selection.start
15969 {
15970 active_group_id = Some(active_group.group_id);
15971 }
15972
15973 fn filtered(
15974 snapshot: EditorSnapshot,
15975 severity: GoToDiagnosticSeverityFilter,
15976 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15977 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15978 diagnostics
15979 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15980 .filter(|entry| entry.range.start != entry.range.end)
15981 .filter(|entry| !entry.diagnostic.is_unnecessary)
15982 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15983 }
15984
15985 let snapshot = self.snapshot(window, cx);
15986 let before = filtered(
15987 snapshot.clone(),
15988 severity,
15989 buffer
15990 .diagnostics_in_range(0..selection.start)
15991 .filter(|entry| entry.range.start <= selection.start),
15992 );
15993 let after = filtered(
15994 snapshot,
15995 severity,
15996 buffer
15997 .diagnostics_in_range(selection.start..buffer.len())
15998 .filter(|entry| entry.range.start >= selection.start),
15999 );
16000
16001 let mut found: Option<DiagnosticEntry<usize>> = None;
16002 if direction == Direction::Prev {
16003 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16004 {
16005 for diagnostic in prev_diagnostics.into_iter().rev() {
16006 if diagnostic.range.start != selection.start
16007 || active_group_id
16008 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16009 {
16010 found = Some(diagnostic);
16011 break 'outer;
16012 }
16013 }
16014 }
16015 } else {
16016 for diagnostic in after.chain(before) {
16017 if diagnostic.range.start != selection.start
16018 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16019 {
16020 found = Some(diagnostic);
16021 break;
16022 }
16023 }
16024 }
16025 let Some(next_diagnostic) = found else {
16026 return;
16027 };
16028
16029 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16030 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16031 return;
16032 };
16033 self.change_selections(Default::default(), window, cx, |s| {
16034 s.select_ranges(vec![
16035 next_diagnostic.range.start..next_diagnostic.range.start,
16036 ])
16037 });
16038 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16039 self.refresh_edit_prediction(false, true, window, cx);
16040 }
16041
16042 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16044 let snapshot = self.snapshot(window, cx);
16045 let selection = self.selections.newest::<Point>(cx);
16046 self.go_to_hunk_before_or_after_position(
16047 &snapshot,
16048 selection.head(),
16049 Direction::Next,
16050 window,
16051 cx,
16052 );
16053 }
16054
16055 pub fn go_to_hunk_before_or_after_position(
16056 &mut self,
16057 snapshot: &EditorSnapshot,
16058 position: Point,
16059 direction: Direction,
16060 window: &mut Window,
16061 cx: &mut Context<Editor>,
16062 ) {
16063 let row = if direction == Direction::Next {
16064 self.hunk_after_position(snapshot, position)
16065 .map(|hunk| hunk.row_range.start)
16066 } else {
16067 self.hunk_before_position(snapshot, position)
16068 };
16069
16070 if let Some(row) = row {
16071 let destination = Point::new(row.0, 0);
16072 let autoscroll = Autoscroll::center();
16073
16074 self.unfold_ranges(&[destination..destination], false, false, cx);
16075 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16076 s.select_ranges([destination..destination]);
16077 });
16078 }
16079 }
16080
16081 fn hunk_after_position(
16082 &mut self,
16083 snapshot: &EditorSnapshot,
16084 position: Point,
16085 ) -> Option<MultiBufferDiffHunk> {
16086 snapshot
16087 .buffer_snapshot
16088 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16089 .find(|hunk| hunk.row_range.start.0 > position.row)
16090 .or_else(|| {
16091 snapshot
16092 .buffer_snapshot
16093 .diff_hunks_in_range(Point::zero()..position)
16094 .find(|hunk| hunk.row_range.end.0 < position.row)
16095 })
16096 }
16097
16098 fn go_to_prev_hunk(
16099 &mut self,
16100 _: &GoToPreviousHunk,
16101 window: &mut Window,
16102 cx: &mut Context<Self>,
16103 ) {
16104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16105 let snapshot = self.snapshot(window, cx);
16106 let selection = self.selections.newest::<Point>(cx);
16107 self.go_to_hunk_before_or_after_position(
16108 &snapshot,
16109 selection.head(),
16110 Direction::Prev,
16111 window,
16112 cx,
16113 );
16114 }
16115
16116 fn hunk_before_position(
16117 &mut self,
16118 snapshot: &EditorSnapshot,
16119 position: Point,
16120 ) -> Option<MultiBufferRow> {
16121 snapshot
16122 .buffer_snapshot
16123 .diff_hunk_before(position)
16124 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16125 }
16126
16127 fn go_to_next_change(
16128 &mut self,
16129 _: &GoToNextChange,
16130 window: &mut Window,
16131 cx: &mut Context<Self>,
16132 ) {
16133 if let Some(selections) = self
16134 .change_list
16135 .next_change(1, Direction::Next)
16136 .map(|s| s.to_vec())
16137 {
16138 self.change_selections(Default::default(), window, cx, |s| {
16139 let map = s.display_map();
16140 s.select_display_ranges(selections.iter().map(|a| {
16141 let point = a.to_display_point(&map);
16142 point..point
16143 }))
16144 })
16145 }
16146 }
16147
16148 fn go_to_previous_change(
16149 &mut self,
16150 _: &GoToPreviousChange,
16151 window: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) {
16154 if let Some(selections) = self
16155 .change_list
16156 .next_change(1, Direction::Prev)
16157 .map(|s| s.to_vec())
16158 {
16159 self.change_selections(Default::default(), window, cx, |s| {
16160 let map = s.display_map();
16161 s.select_display_ranges(selections.iter().map(|a| {
16162 let point = a.to_display_point(&map);
16163 point..point
16164 }))
16165 })
16166 }
16167 }
16168
16169 pub fn go_to_next_document_highlight(
16170 &mut self,
16171 _: &GoToNextDocumentHighlight,
16172 window: &mut Window,
16173 cx: &mut Context<Self>,
16174 ) {
16175 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16176 }
16177
16178 pub fn go_to_prev_document_highlight(
16179 &mut self,
16180 _: &GoToPreviousDocumentHighlight,
16181 window: &mut Window,
16182 cx: &mut Context<Self>,
16183 ) {
16184 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16185 }
16186
16187 pub fn go_to_document_highlight_before_or_after_position(
16188 &mut self,
16189 direction: Direction,
16190 window: &mut Window,
16191 cx: &mut Context<Editor>,
16192 ) {
16193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16194 let snapshot = self.snapshot(window, cx);
16195 let buffer = &snapshot.buffer_snapshot;
16196 let position = self.selections.newest::<Point>(cx).head();
16197 let anchor_position = buffer.anchor_after(position);
16198
16199 // Get all document highlights (both read and write)
16200 let mut all_highlights = Vec::new();
16201
16202 if let Some((_, read_highlights)) = self
16203 .background_highlights
16204 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16205 {
16206 all_highlights.extend(read_highlights.iter());
16207 }
16208
16209 if let Some((_, write_highlights)) = self
16210 .background_highlights
16211 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16212 {
16213 all_highlights.extend(write_highlights.iter());
16214 }
16215
16216 if all_highlights.is_empty() {
16217 return;
16218 }
16219
16220 // Sort highlights by position
16221 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16222
16223 let target_highlight = match direction {
16224 Direction::Next => {
16225 // Find the first highlight after the current position
16226 all_highlights
16227 .iter()
16228 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16229 }
16230 Direction::Prev => {
16231 // Find the last highlight before the current position
16232 all_highlights
16233 .iter()
16234 .rev()
16235 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16236 }
16237 };
16238
16239 if let Some(highlight) = target_highlight {
16240 let destination = highlight.start.to_point(buffer);
16241 let autoscroll = Autoscroll::center();
16242
16243 self.unfold_ranges(&[destination..destination], false, false, cx);
16244 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16245 s.select_ranges([destination..destination]);
16246 });
16247 }
16248 }
16249
16250 fn go_to_line<T: 'static>(
16251 &mut self,
16252 position: Anchor,
16253 highlight_color: Option<Hsla>,
16254 window: &mut Window,
16255 cx: &mut Context<Self>,
16256 ) {
16257 let snapshot = self.snapshot(window, cx).display_snapshot;
16258 let position = position.to_point(&snapshot.buffer_snapshot);
16259 let start = snapshot
16260 .buffer_snapshot
16261 .clip_point(Point::new(position.row, 0), Bias::Left);
16262 let end = start + Point::new(1, 0);
16263 let start = snapshot.buffer_snapshot.anchor_before(start);
16264 let end = snapshot.buffer_snapshot.anchor_before(end);
16265
16266 self.highlight_rows::<T>(
16267 start..end,
16268 highlight_color
16269 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16270 Default::default(),
16271 cx,
16272 );
16273
16274 if self.buffer.read(cx).is_singleton() {
16275 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16276 }
16277 }
16278
16279 pub fn go_to_definition(
16280 &mut self,
16281 _: &GoToDefinition,
16282 window: &mut Window,
16283 cx: &mut Context<Self>,
16284 ) -> Task<Result<Navigated>> {
16285 let definition =
16286 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16287 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16288 cx.spawn_in(window, async move |editor, cx| {
16289 if definition.await? == Navigated::Yes {
16290 return Ok(Navigated::Yes);
16291 }
16292 match fallback_strategy {
16293 GoToDefinitionFallback::None => Ok(Navigated::No),
16294 GoToDefinitionFallback::FindAllReferences => {
16295 match editor.update_in(cx, |editor, window, cx| {
16296 editor.find_all_references(&FindAllReferences, window, cx)
16297 })? {
16298 Some(references) => references.await,
16299 None => Ok(Navigated::No),
16300 }
16301 }
16302 }
16303 })
16304 }
16305
16306 pub fn go_to_declaration(
16307 &mut self,
16308 _: &GoToDeclaration,
16309 window: &mut Window,
16310 cx: &mut Context<Self>,
16311 ) -> Task<Result<Navigated>> {
16312 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16313 }
16314
16315 pub fn go_to_declaration_split(
16316 &mut self,
16317 _: &GoToDeclaration,
16318 window: &mut Window,
16319 cx: &mut Context<Self>,
16320 ) -> Task<Result<Navigated>> {
16321 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16322 }
16323
16324 pub fn go_to_implementation(
16325 &mut self,
16326 _: &GoToImplementation,
16327 window: &mut Window,
16328 cx: &mut Context<Self>,
16329 ) -> Task<Result<Navigated>> {
16330 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16331 }
16332
16333 pub fn go_to_implementation_split(
16334 &mut self,
16335 _: &GoToImplementationSplit,
16336 window: &mut Window,
16337 cx: &mut Context<Self>,
16338 ) -> Task<Result<Navigated>> {
16339 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16340 }
16341
16342 pub fn go_to_type_definition(
16343 &mut self,
16344 _: &GoToTypeDefinition,
16345 window: &mut Window,
16346 cx: &mut Context<Self>,
16347 ) -> Task<Result<Navigated>> {
16348 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16349 }
16350
16351 pub fn go_to_definition_split(
16352 &mut self,
16353 _: &GoToDefinitionSplit,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) -> Task<Result<Navigated>> {
16357 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16358 }
16359
16360 pub fn go_to_type_definition_split(
16361 &mut self,
16362 _: &GoToTypeDefinitionSplit,
16363 window: &mut Window,
16364 cx: &mut Context<Self>,
16365 ) -> Task<Result<Navigated>> {
16366 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16367 }
16368
16369 fn go_to_definition_of_kind(
16370 &mut self,
16371 kind: GotoDefinitionKind,
16372 split: bool,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) -> Task<Result<Navigated>> {
16376 let Some(provider) = self.semantics_provider.clone() else {
16377 return Task::ready(Ok(Navigated::No));
16378 };
16379 let head = self.selections.newest::<usize>(cx).head();
16380 let buffer = self.buffer.read(cx);
16381 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16382 return Task::ready(Ok(Navigated::No));
16383 };
16384 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16385 return Task::ready(Ok(Navigated::No));
16386 };
16387
16388 cx.spawn_in(window, async move |editor, cx| {
16389 let Some(definitions) = definitions.await? else {
16390 return Ok(Navigated::No);
16391 };
16392 let navigated = editor
16393 .update_in(cx, |editor, window, cx| {
16394 editor.navigate_to_hover_links(
16395 Some(kind),
16396 definitions
16397 .into_iter()
16398 .filter(|location| {
16399 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16400 })
16401 .map(HoverLink::Text)
16402 .collect::<Vec<_>>(),
16403 split,
16404 window,
16405 cx,
16406 )
16407 })?
16408 .await?;
16409 anyhow::Ok(navigated)
16410 })
16411 }
16412
16413 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16414 let selection = self.selections.newest_anchor();
16415 let head = selection.head();
16416 let tail = selection.tail();
16417
16418 let Some((buffer, start_position)) =
16419 self.buffer.read(cx).text_anchor_for_position(head, cx)
16420 else {
16421 return;
16422 };
16423
16424 let end_position = if head != tail {
16425 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16426 return;
16427 };
16428 Some(pos)
16429 } else {
16430 None
16431 };
16432
16433 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16434 let url = if let Some(end_pos) = end_position {
16435 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16436 } else {
16437 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16438 };
16439
16440 if let Some(url) = url {
16441 cx.update(|window, cx| {
16442 if parse_zed_link(&url, cx).is_some() {
16443 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16444 } else {
16445 cx.open_url(&url);
16446 }
16447 })?;
16448 }
16449
16450 anyhow::Ok(())
16451 });
16452
16453 url_finder.detach();
16454 }
16455
16456 pub fn open_selected_filename(
16457 &mut self,
16458 _: &OpenSelectedFilename,
16459 window: &mut Window,
16460 cx: &mut Context<Self>,
16461 ) {
16462 let Some(workspace) = self.workspace() else {
16463 return;
16464 };
16465
16466 let position = self.selections.newest_anchor().head();
16467
16468 let Some((buffer, buffer_position)) =
16469 self.buffer.read(cx).text_anchor_for_position(position, cx)
16470 else {
16471 return;
16472 };
16473
16474 let project = self.project.clone();
16475
16476 cx.spawn_in(window, async move |_, cx| {
16477 let result = find_file(&buffer, project, buffer_position, cx).await;
16478
16479 if let Some((_, path)) = result {
16480 workspace
16481 .update_in(cx, |workspace, window, cx| {
16482 workspace.open_resolved_path(path, window, cx)
16483 })?
16484 .await?;
16485 }
16486 anyhow::Ok(())
16487 })
16488 .detach();
16489 }
16490
16491 pub(crate) fn navigate_to_hover_links(
16492 &mut self,
16493 kind: Option<GotoDefinitionKind>,
16494 definitions: Vec<HoverLink>,
16495 split: bool,
16496 window: &mut Window,
16497 cx: &mut Context<Editor>,
16498 ) -> Task<Result<Navigated>> {
16499 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16500 let mut first_url_or_file = None;
16501 let definitions: Vec<_> = definitions
16502 .into_iter()
16503 .filter_map(|def| match def {
16504 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16505 HoverLink::InlayHint(lsp_location, server_id) => {
16506 let computation =
16507 self.compute_target_location(lsp_location, server_id, window, cx);
16508 Some(cx.background_spawn(computation))
16509 }
16510 HoverLink::Url(url) => {
16511 first_url_or_file = Some(Either::Left(url));
16512 None
16513 }
16514 HoverLink::File(path) => {
16515 first_url_or_file = Some(Either::Right(path));
16516 None
16517 }
16518 })
16519 .collect();
16520
16521 let workspace = self.workspace();
16522
16523 cx.spawn_in(window, async move |editor, cx| {
16524 let locations: Vec<Location> = future::join_all(definitions)
16525 .await
16526 .into_iter()
16527 .filter_map(|location| location.transpose())
16528 .collect::<Result<_>>()
16529 .context("location tasks")?;
16530 let mut locations = cx.update(|_, cx| {
16531 locations
16532 .into_iter()
16533 .map(|location| {
16534 let buffer = location.buffer.read(cx);
16535 (location.buffer, location.range.to_point(buffer))
16536 })
16537 .into_group_map()
16538 })?;
16539 let mut num_locations = 0;
16540 for ranges in locations.values_mut() {
16541 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16542 ranges.dedup();
16543 num_locations += ranges.len();
16544 }
16545
16546 if num_locations > 1 {
16547 let Some(workspace) = workspace else {
16548 return Ok(Navigated::No);
16549 };
16550
16551 let tab_kind = match kind {
16552 Some(GotoDefinitionKind::Implementation) => "Implementations",
16553 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16554 Some(GotoDefinitionKind::Declaration) => "Declarations",
16555 Some(GotoDefinitionKind::Type) => "Types",
16556 };
16557 let title = editor
16558 .update_in(cx, |_, _, cx| {
16559 let target = locations
16560 .iter()
16561 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16562 .map(|(buffer, location)| {
16563 buffer
16564 .read(cx)
16565 .text_for_range(location.clone())
16566 .collect::<String>()
16567 })
16568 .filter(|text| !text.contains('\n'))
16569 .unique()
16570 .take(3)
16571 .join(", ");
16572 if target.is_empty() {
16573 tab_kind.to_owned()
16574 } else {
16575 format!("{tab_kind} for {target}")
16576 }
16577 })
16578 .context("buffer title")?;
16579
16580 let opened = workspace
16581 .update_in(cx, |workspace, window, cx| {
16582 Self::open_locations_in_multibuffer(
16583 workspace,
16584 locations,
16585 title,
16586 split,
16587 MultibufferSelectionMode::First,
16588 window,
16589 cx,
16590 )
16591 })
16592 .is_ok();
16593
16594 anyhow::Ok(Navigated::from_bool(opened))
16595 } else if num_locations == 0 {
16596 // If there is one url or file, open it directly
16597 match first_url_or_file {
16598 Some(Either::Left(url)) => {
16599 cx.update(|_, cx| cx.open_url(&url))?;
16600 Ok(Navigated::Yes)
16601 }
16602 Some(Either::Right(path)) => {
16603 let Some(workspace) = workspace else {
16604 return Ok(Navigated::No);
16605 };
16606
16607 workspace
16608 .update_in(cx, |workspace, window, cx| {
16609 workspace.open_resolved_path(path, window, cx)
16610 })?
16611 .await?;
16612 Ok(Navigated::Yes)
16613 }
16614 None => Ok(Navigated::No),
16615 }
16616 } else {
16617 let Some(workspace) = workspace else {
16618 return Ok(Navigated::No);
16619 };
16620
16621 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16622 let target_range = target_ranges.first().unwrap().clone();
16623
16624 editor.update_in(cx, |editor, window, cx| {
16625 let range = target_range.to_point(target_buffer.read(cx));
16626 let range = editor.range_for_match(&range);
16627 let range = collapse_multiline_range(range);
16628
16629 if !split
16630 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16631 {
16632 editor.go_to_singleton_buffer_range(range, window, cx);
16633 } else {
16634 let pane = workspace.read(cx).active_pane().clone();
16635 window.defer(cx, move |window, cx| {
16636 let target_editor: Entity<Self> =
16637 workspace.update(cx, |workspace, cx| {
16638 let pane = if split {
16639 workspace.adjacent_pane(window, cx)
16640 } else {
16641 workspace.active_pane().clone()
16642 };
16643
16644 workspace.open_project_item(
16645 pane,
16646 target_buffer.clone(),
16647 true,
16648 true,
16649 window,
16650 cx,
16651 )
16652 });
16653 target_editor.update(cx, |target_editor, cx| {
16654 // When selecting a definition in a different buffer, disable the nav history
16655 // to avoid creating a history entry at the previous cursor location.
16656 pane.update(cx, |pane, _| pane.disable_history());
16657 target_editor.go_to_singleton_buffer_range(range, window, cx);
16658 pane.update(cx, |pane, _| pane.enable_history());
16659 });
16660 });
16661 }
16662 Navigated::Yes
16663 })
16664 }
16665 })
16666 }
16667
16668 fn compute_target_location(
16669 &self,
16670 lsp_location: lsp::Location,
16671 server_id: LanguageServerId,
16672 window: &mut Window,
16673 cx: &mut Context<Self>,
16674 ) -> Task<anyhow::Result<Option<Location>>> {
16675 let Some(project) = self.project.clone() else {
16676 return Task::ready(Ok(None));
16677 };
16678
16679 cx.spawn_in(window, async move |editor, cx| {
16680 let location_task = editor.update(cx, |_, cx| {
16681 project.update(cx, |project, cx| {
16682 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16683 })
16684 })?;
16685 let location = Some({
16686 let target_buffer_handle = location_task.await.context("open local buffer")?;
16687 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16688 let target_start = target_buffer
16689 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16690 let target_end = target_buffer
16691 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16692 target_buffer.anchor_after(target_start)
16693 ..target_buffer.anchor_before(target_end)
16694 })?;
16695 Location {
16696 buffer: target_buffer_handle,
16697 range,
16698 }
16699 });
16700 Ok(location)
16701 })
16702 }
16703
16704 pub fn find_all_references(
16705 &mut self,
16706 _: &FindAllReferences,
16707 window: &mut Window,
16708 cx: &mut Context<Self>,
16709 ) -> Option<Task<Result<Navigated>>> {
16710 let selection = self.selections.newest::<usize>(cx);
16711 let multi_buffer = self.buffer.read(cx);
16712 let head = selection.head();
16713
16714 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16715 let head_anchor = multi_buffer_snapshot.anchor_at(
16716 head,
16717 if head < selection.tail() {
16718 Bias::Right
16719 } else {
16720 Bias::Left
16721 },
16722 );
16723
16724 match self
16725 .find_all_references_task_sources
16726 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16727 {
16728 Ok(_) => {
16729 log::info!(
16730 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16731 );
16732 return None;
16733 }
16734 Err(i) => {
16735 self.find_all_references_task_sources.insert(i, head_anchor);
16736 }
16737 }
16738
16739 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16740 let workspace = self.workspace()?;
16741 let project = workspace.read(cx).project().clone();
16742 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16743 Some(cx.spawn_in(window, async move |editor, cx| {
16744 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16745 if let Ok(i) = editor
16746 .find_all_references_task_sources
16747 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16748 {
16749 editor.find_all_references_task_sources.remove(i);
16750 }
16751 });
16752
16753 let Some(locations) = references.await? else {
16754 return anyhow::Ok(Navigated::No);
16755 };
16756 let mut locations = cx.update(|_, cx| {
16757 locations
16758 .into_iter()
16759 .map(|location| {
16760 let buffer = location.buffer.read(cx);
16761 (location.buffer, location.range.to_point(buffer))
16762 })
16763 .into_group_map()
16764 })?;
16765 if locations.is_empty() {
16766 return anyhow::Ok(Navigated::No);
16767 }
16768 for ranges in locations.values_mut() {
16769 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16770 ranges.dedup();
16771 }
16772
16773 workspace.update_in(cx, |workspace, window, cx| {
16774 let target = locations
16775 .iter()
16776 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16777 .map(|(buffer, location)| {
16778 buffer
16779 .read(cx)
16780 .text_for_range(location.clone())
16781 .collect::<String>()
16782 })
16783 .filter(|text| !text.contains('\n'))
16784 .unique()
16785 .take(3)
16786 .join(", ");
16787 let title = if target.is_empty() {
16788 "References".to_owned()
16789 } else {
16790 format!("References to {target}")
16791 };
16792 Self::open_locations_in_multibuffer(
16793 workspace,
16794 locations,
16795 title,
16796 false,
16797 MultibufferSelectionMode::First,
16798 window,
16799 cx,
16800 );
16801 Navigated::Yes
16802 })
16803 }))
16804 }
16805
16806 /// Opens a multibuffer with the given project locations in it
16807 pub fn open_locations_in_multibuffer(
16808 workspace: &mut Workspace,
16809 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16810 title: String,
16811 split: bool,
16812 multibuffer_selection_mode: MultibufferSelectionMode,
16813 window: &mut Window,
16814 cx: &mut Context<Workspace>,
16815 ) {
16816 if locations.is_empty() {
16817 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16818 return;
16819 }
16820
16821 let capability = workspace.project().read(cx).capability();
16822 let mut ranges = <Vec<Range<Anchor>>>::new();
16823
16824 // a key to find existing multibuffer editors with the same set of locations
16825 // to prevent us from opening more and more multibuffer tabs for searches and the like
16826 let mut key = (title.clone(), vec![]);
16827 let excerpt_buffer = cx.new(|cx| {
16828 let key = &mut key.1;
16829 let mut multibuffer = MultiBuffer::new(capability);
16830 for (buffer, mut ranges_for_buffer) in locations {
16831 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16832 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16833 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16834 PathKey::for_buffer(&buffer, cx),
16835 buffer.clone(),
16836 ranges_for_buffer,
16837 multibuffer_context_lines(cx),
16838 cx,
16839 );
16840 ranges.extend(new_ranges)
16841 }
16842
16843 multibuffer.with_title(title)
16844 });
16845 let existing = workspace.active_pane().update(cx, |pane, cx| {
16846 pane.items()
16847 .filter_map(|item| item.downcast::<Editor>())
16848 .find(|editor| {
16849 editor
16850 .read(cx)
16851 .lookup_key
16852 .as_ref()
16853 .and_then(|it| {
16854 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16855 })
16856 .is_some_and(|it| *it == key)
16857 })
16858 });
16859 let editor = existing.unwrap_or_else(|| {
16860 cx.new(|cx| {
16861 let mut editor = Editor::for_multibuffer(
16862 excerpt_buffer,
16863 Some(workspace.project().clone()),
16864 window,
16865 cx,
16866 );
16867 editor.lookup_key = Some(Box::new(key));
16868 editor
16869 })
16870 });
16871 editor.update(cx, |editor, cx| {
16872 match multibuffer_selection_mode {
16873 MultibufferSelectionMode::First => {
16874 if let Some(first_range) = ranges.first() {
16875 editor.change_selections(
16876 SelectionEffects::no_scroll(),
16877 window,
16878 cx,
16879 |selections| {
16880 selections.clear_disjoint();
16881 selections
16882 .select_anchor_ranges(std::iter::once(first_range.clone()));
16883 },
16884 );
16885 }
16886 editor.highlight_background::<Self>(
16887 &ranges,
16888 |theme| theme.colors().editor_highlighted_line_background,
16889 cx,
16890 );
16891 }
16892 MultibufferSelectionMode::All => {
16893 editor.change_selections(
16894 SelectionEffects::no_scroll(),
16895 window,
16896 cx,
16897 |selections| {
16898 selections.clear_disjoint();
16899 selections.select_anchor_ranges(ranges);
16900 },
16901 );
16902 }
16903 }
16904 editor.register_buffers_with_language_servers(cx);
16905 });
16906
16907 let item = Box::new(editor);
16908 let item_id = item.item_id();
16909
16910 if split {
16911 let pane = workspace.adjacent_pane(window, cx);
16912 workspace.add_item(pane, item, None, true, true, window, cx);
16913 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16914 let (preview_item_id, preview_item_idx) =
16915 workspace.active_pane().read_with(cx, |pane, _| {
16916 (pane.preview_item_id(), pane.preview_item_idx())
16917 });
16918
16919 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16920
16921 if let Some(preview_item_id) = preview_item_id {
16922 workspace.active_pane().update(cx, |pane, cx| {
16923 pane.remove_item(preview_item_id, false, false, window, cx);
16924 });
16925 }
16926 } else {
16927 workspace.add_item_to_active_pane(item, None, true, window, cx);
16928 }
16929 workspace.active_pane().update(cx, |pane, cx| {
16930 pane.set_preview_item_id(Some(item_id), cx);
16931 });
16932 }
16933
16934 pub fn rename(
16935 &mut self,
16936 _: &Rename,
16937 window: &mut Window,
16938 cx: &mut Context<Self>,
16939 ) -> Option<Task<Result<()>>> {
16940 use language::ToOffset as _;
16941
16942 let provider = self.semantics_provider.clone()?;
16943 let selection = self.selections.newest_anchor().clone();
16944 let (cursor_buffer, cursor_buffer_position) = self
16945 .buffer
16946 .read(cx)
16947 .text_anchor_for_position(selection.head(), cx)?;
16948 let (tail_buffer, cursor_buffer_position_end) = self
16949 .buffer
16950 .read(cx)
16951 .text_anchor_for_position(selection.tail(), cx)?;
16952 if tail_buffer != cursor_buffer {
16953 return None;
16954 }
16955
16956 let snapshot = cursor_buffer.read(cx).snapshot();
16957 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16958 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16959 let prepare_rename = provider
16960 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16961 .unwrap_or_else(|| Task::ready(Ok(None)));
16962 drop(snapshot);
16963
16964 Some(cx.spawn_in(window, async move |this, cx| {
16965 let rename_range = if let Some(range) = prepare_rename.await? {
16966 Some(range)
16967 } else {
16968 this.update(cx, |this, cx| {
16969 let buffer = this.buffer.read(cx).snapshot(cx);
16970 let mut buffer_highlights = this
16971 .document_highlights_for_position(selection.head(), &buffer)
16972 .filter(|highlight| {
16973 highlight.start.excerpt_id == selection.head().excerpt_id
16974 && highlight.end.excerpt_id == selection.head().excerpt_id
16975 });
16976 buffer_highlights
16977 .next()
16978 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16979 })?
16980 };
16981 if let Some(rename_range) = rename_range {
16982 this.update_in(cx, |this, window, cx| {
16983 let snapshot = cursor_buffer.read(cx).snapshot();
16984 let rename_buffer_range = rename_range.to_offset(&snapshot);
16985 let cursor_offset_in_rename_range =
16986 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16987 let cursor_offset_in_rename_range_end =
16988 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16989
16990 this.take_rename(false, window, cx);
16991 let buffer = this.buffer.read(cx).read(cx);
16992 let cursor_offset = selection.head().to_offset(&buffer);
16993 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16994 let rename_end = rename_start + rename_buffer_range.len();
16995 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16996 let mut old_highlight_id = None;
16997 let old_name: Arc<str> = buffer
16998 .chunks(rename_start..rename_end, true)
16999 .map(|chunk| {
17000 if old_highlight_id.is_none() {
17001 old_highlight_id = chunk.syntax_highlight_id;
17002 }
17003 chunk.text
17004 })
17005 .collect::<String>()
17006 .into();
17007
17008 drop(buffer);
17009
17010 // Position the selection in the rename editor so that it matches the current selection.
17011 this.show_local_selections = false;
17012 let rename_editor = cx.new(|cx| {
17013 let mut editor = Editor::single_line(window, cx);
17014 editor.buffer.update(cx, |buffer, cx| {
17015 buffer.edit([(0..0, old_name.clone())], None, cx)
17016 });
17017 let rename_selection_range = match cursor_offset_in_rename_range
17018 .cmp(&cursor_offset_in_rename_range_end)
17019 {
17020 Ordering::Equal => {
17021 editor.select_all(&SelectAll, window, cx);
17022 return editor;
17023 }
17024 Ordering::Less => {
17025 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17026 }
17027 Ordering::Greater => {
17028 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17029 }
17030 };
17031 if rename_selection_range.end > old_name.len() {
17032 editor.select_all(&SelectAll, window, cx);
17033 } else {
17034 editor.change_selections(Default::default(), window, cx, |s| {
17035 s.select_ranges([rename_selection_range]);
17036 });
17037 }
17038 editor
17039 });
17040 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17041 if e == &EditorEvent::Focused {
17042 cx.emit(EditorEvent::FocusedIn)
17043 }
17044 })
17045 .detach();
17046
17047 let write_highlights =
17048 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17049 let read_highlights =
17050 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17051 let ranges = write_highlights
17052 .iter()
17053 .flat_map(|(_, ranges)| ranges.iter())
17054 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17055 .cloned()
17056 .collect();
17057
17058 this.highlight_text::<Rename>(
17059 ranges,
17060 HighlightStyle {
17061 fade_out: Some(0.6),
17062 ..Default::default()
17063 },
17064 cx,
17065 );
17066 let rename_focus_handle = rename_editor.focus_handle(cx);
17067 window.focus(&rename_focus_handle);
17068 let block_id = this.insert_blocks(
17069 [BlockProperties {
17070 style: BlockStyle::Flex,
17071 placement: BlockPlacement::Below(range.start),
17072 height: Some(1),
17073 render: Arc::new({
17074 let rename_editor = rename_editor.clone();
17075 move |cx: &mut BlockContext| {
17076 let mut text_style = cx.editor_style.text.clone();
17077 if let Some(highlight_style) = old_highlight_id
17078 .and_then(|h| h.style(&cx.editor_style.syntax))
17079 {
17080 text_style = text_style.highlight(highlight_style);
17081 }
17082 div()
17083 .block_mouse_except_scroll()
17084 .pl(cx.anchor_x)
17085 .child(EditorElement::new(
17086 &rename_editor,
17087 EditorStyle {
17088 background: cx.theme().system().transparent,
17089 local_player: cx.editor_style.local_player,
17090 text: text_style,
17091 scrollbar_width: cx.editor_style.scrollbar_width,
17092 syntax: cx.editor_style.syntax.clone(),
17093 status: cx.editor_style.status.clone(),
17094 inlay_hints_style: HighlightStyle {
17095 font_weight: Some(FontWeight::BOLD),
17096 ..make_inlay_hints_style(cx.app)
17097 },
17098 edit_prediction_styles: make_suggestion_styles(
17099 cx.app,
17100 ),
17101 ..EditorStyle::default()
17102 },
17103 ))
17104 .into_any_element()
17105 }
17106 }),
17107 priority: 0,
17108 }],
17109 Some(Autoscroll::fit()),
17110 cx,
17111 )[0];
17112 this.pending_rename = Some(RenameState {
17113 range,
17114 old_name,
17115 editor: rename_editor,
17116 block_id,
17117 });
17118 })?;
17119 }
17120
17121 Ok(())
17122 }))
17123 }
17124
17125 pub fn confirm_rename(
17126 &mut self,
17127 _: &ConfirmRename,
17128 window: &mut Window,
17129 cx: &mut Context<Self>,
17130 ) -> Option<Task<Result<()>>> {
17131 let rename = self.take_rename(false, window, cx)?;
17132 let workspace = self.workspace()?.downgrade();
17133 let (buffer, start) = self
17134 .buffer
17135 .read(cx)
17136 .text_anchor_for_position(rename.range.start, cx)?;
17137 let (end_buffer, _) = self
17138 .buffer
17139 .read(cx)
17140 .text_anchor_for_position(rename.range.end, cx)?;
17141 if buffer != end_buffer {
17142 return None;
17143 }
17144
17145 let old_name = rename.old_name;
17146 let new_name = rename.editor.read(cx).text(cx);
17147
17148 let rename = self.semantics_provider.as_ref()?.perform_rename(
17149 &buffer,
17150 start,
17151 new_name.clone(),
17152 cx,
17153 )?;
17154
17155 Some(cx.spawn_in(window, async move |editor, cx| {
17156 let project_transaction = rename.await?;
17157 Self::open_project_transaction(
17158 &editor,
17159 workspace,
17160 project_transaction,
17161 format!("Rename: {} → {}", old_name, new_name),
17162 cx,
17163 )
17164 .await?;
17165
17166 editor.update(cx, |editor, cx| {
17167 editor.refresh_document_highlights(cx);
17168 })?;
17169 Ok(())
17170 }))
17171 }
17172
17173 fn take_rename(
17174 &mut self,
17175 moving_cursor: bool,
17176 window: &mut Window,
17177 cx: &mut Context<Self>,
17178 ) -> Option<RenameState> {
17179 let rename = self.pending_rename.take()?;
17180 if rename.editor.focus_handle(cx).is_focused(window) {
17181 window.focus(&self.focus_handle);
17182 }
17183
17184 self.remove_blocks(
17185 [rename.block_id].into_iter().collect(),
17186 Some(Autoscroll::fit()),
17187 cx,
17188 );
17189 self.clear_highlights::<Rename>(cx);
17190 self.show_local_selections = true;
17191
17192 if moving_cursor {
17193 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17194 editor.selections.newest::<usize>(cx).head()
17195 });
17196
17197 // Update the selection to match the position of the selection inside
17198 // the rename editor.
17199 let snapshot = self.buffer.read(cx).read(cx);
17200 let rename_range = rename.range.to_offset(&snapshot);
17201 let cursor_in_editor = snapshot
17202 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17203 .min(rename_range.end);
17204 drop(snapshot);
17205
17206 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17207 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17208 });
17209 } else {
17210 self.refresh_document_highlights(cx);
17211 }
17212
17213 Some(rename)
17214 }
17215
17216 pub fn pending_rename(&self) -> Option<&RenameState> {
17217 self.pending_rename.as_ref()
17218 }
17219
17220 fn format(
17221 &mut self,
17222 _: &Format,
17223 window: &mut Window,
17224 cx: &mut Context<Self>,
17225 ) -> Option<Task<Result<()>>> {
17226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17227
17228 let project = match &self.project {
17229 Some(project) => project.clone(),
17230 None => return None,
17231 };
17232
17233 Some(self.perform_format(
17234 project,
17235 FormatTrigger::Manual,
17236 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17237 window,
17238 cx,
17239 ))
17240 }
17241
17242 fn format_selections(
17243 &mut self,
17244 _: &FormatSelections,
17245 window: &mut Window,
17246 cx: &mut Context<Self>,
17247 ) -> Option<Task<Result<()>>> {
17248 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17249
17250 let project = match &self.project {
17251 Some(project) => project.clone(),
17252 None => return None,
17253 };
17254
17255 let ranges = self
17256 .selections
17257 .all_adjusted(cx)
17258 .into_iter()
17259 .map(|selection| selection.range())
17260 .collect_vec();
17261
17262 Some(self.perform_format(
17263 project,
17264 FormatTrigger::Manual,
17265 FormatTarget::Ranges(ranges),
17266 window,
17267 cx,
17268 ))
17269 }
17270
17271 fn perform_format(
17272 &mut self,
17273 project: Entity<Project>,
17274 trigger: FormatTrigger,
17275 target: FormatTarget,
17276 window: &mut Window,
17277 cx: &mut Context<Self>,
17278 ) -> Task<Result<()>> {
17279 let buffer = self.buffer.clone();
17280 let (buffers, target) = match target {
17281 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17282 FormatTarget::Ranges(selection_ranges) => {
17283 let multi_buffer = buffer.read(cx);
17284 let snapshot = multi_buffer.read(cx);
17285 let mut buffers = HashSet::default();
17286 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17287 BTreeMap::new();
17288 for selection_range in selection_ranges {
17289 for (buffer, buffer_range, _) in
17290 snapshot.range_to_buffer_ranges(selection_range)
17291 {
17292 let buffer_id = buffer.remote_id();
17293 let start = buffer.anchor_before(buffer_range.start);
17294 let end = buffer.anchor_after(buffer_range.end);
17295 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17296 buffer_id_to_ranges
17297 .entry(buffer_id)
17298 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17299 .or_insert_with(|| vec![start..end]);
17300 }
17301 }
17302 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17303 }
17304 };
17305
17306 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17307 let selections_prev = transaction_id_prev
17308 .and_then(|transaction_id_prev| {
17309 // default to selections as they were after the last edit, if we have them,
17310 // instead of how they are now.
17311 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17312 // will take you back to where you made the last edit, instead of staying where you scrolled
17313 self.selection_history
17314 .transaction(transaction_id_prev)
17315 .map(|t| t.0.clone())
17316 })
17317 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17318
17319 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17320 let format = project.update(cx, |project, cx| {
17321 project.format(buffers, target, true, trigger, cx)
17322 });
17323
17324 cx.spawn_in(window, async move |editor, cx| {
17325 let transaction = futures::select_biased! {
17326 transaction = format.log_err().fuse() => transaction,
17327 () = timeout => {
17328 log::warn!("timed out waiting for formatting");
17329 None
17330 }
17331 };
17332
17333 buffer
17334 .update(cx, |buffer, cx| {
17335 if let Some(transaction) = transaction
17336 && !buffer.is_singleton()
17337 {
17338 buffer.push_transaction(&transaction.0, cx);
17339 }
17340 cx.notify();
17341 })
17342 .ok();
17343
17344 if let Some(transaction_id_now) =
17345 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17346 {
17347 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17348 if has_new_transaction {
17349 _ = editor.update(cx, |editor, _| {
17350 editor
17351 .selection_history
17352 .insert_transaction(transaction_id_now, selections_prev);
17353 });
17354 }
17355 }
17356
17357 Ok(())
17358 })
17359 }
17360
17361 fn organize_imports(
17362 &mut self,
17363 _: &OrganizeImports,
17364 window: &mut Window,
17365 cx: &mut Context<Self>,
17366 ) -> Option<Task<Result<()>>> {
17367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17368 let project = match &self.project {
17369 Some(project) => project.clone(),
17370 None => return None,
17371 };
17372 Some(self.perform_code_action_kind(
17373 project,
17374 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17375 window,
17376 cx,
17377 ))
17378 }
17379
17380 fn perform_code_action_kind(
17381 &mut self,
17382 project: Entity<Project>,
17383 kind: CodeActionKind,
17384 window: &mut Window,
17385 cx: &mut Context<Self>,
17386 ) -> Task<Result<()>> {
17387 let buffer = self.buffer.clone();
17388 let buffers = buffer.read(cx).all_buffers();
17389 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17390 let apply_action = project.update(cx, |project, cx| {
17391 project.apply_code_action_kind(buffers, kind, true, cx)
17392 });
17393 cx.spawn_in(window, async move |_, cx| {
17394 let transaction = futures::select_biased! {
17395 () = timeout => {
17396 log::warn!("timed out waiting for executing code action");
17397 None
17398 }
17399 transaction = apply_action.log_err().fuse() => transaction,
17400 };
17401 buffer
17402 .update(cx, |buffer, cx| {
17403 // check if we need this
17404 if let Some(transaction) = transaction
17405 && !buffer.is_singleton()
17406 {
17407 buffer.push_transaction(&transaction.0, cx);
17408 }
17409 cx.notify();
17410 })
17411 .ok();
17412 Ok(())
17413 })
17414 }
17415
17416 pub fn restart_language_server(
17417 &mut self,
17418 _: &RestartLanguageServer,
17419 _: &mut Window,
17420 cx: &mut Context<Self>,
17421 ) {
17422 if let Some(project) = self.project.clone() {
17423 self.buffer.update(cx, |multi_buffer, cx| {
17424 project.update(cx, |project, cx| {
17425 project.restart_language_servers_for_buffers(
17426 multi_buffer.all_buffers().into_iter().collect(),
17427 HashSet::default(),
17428 cx,
17429 );
17430 });
17431 })
17432 }
17433 }
17434
17435 pub fn stop_language_server(
17436 &mut self,
17437 _: &StopLanguageServer,
17438 _: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) {
17441 if let Some(project) = self.project.clone() {
17442 self.buffer.update(cx, |multi_buffer, cx| {
17443 project.update(cx, |project, cx| {
17444 project.stop_language_servers_for_buffers(
17445 multi_buffer.all_buffers().into_iter().collect(),
17446 HashSet::default(),
17447 cx,
17448 );
17449 cx.emit(project::Event::RefreshInlayHints);
17450 });
17451 });
17452 }
17453 }
17454
17455 fn cancel_language_server_work(
17456 workspace: &mut Workspace,
17457 _: &actions::CancelLanguageServerWork,
17458 _: &mut Window,
17459 cx: &mut Context<Workspace>,
17460 ) {
17461 let project = workspace.project();
17462 let buffers = workspace
17463 .active_item(cx)
17464 .and_then(|item| item.act_as::<Editor>(cx))
17465 .map_or(HashSet::default(), |editor| {
17466 editor.read(cx).buffer.read(cx).all_buffers()
17467 });
17468 project.update(cx, |project, cx| {
17469 project.cancel_language_server_work_for_buffers(buffers, cx);
17470 });
17471 }
17472
17473 fn show_character_palette(
17474 &mut self,
17475 _: &ShowCharacterPalette,
17476 window: &mut Window,
17477 _: &mut Context<Self>,
17478 ) {
17479 window.show_character_palette();
17480 }
17481
17482 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17483 if !self.diagnostics_enabled() {
17484 return;
17485 }
17486
17487 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17488 let buffer = self.buffer.read(cx).snapshot(cx);
17489 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17490 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17491 let is_valid = buffer
17492 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17493 .any(|entry| {
17494 entry.diagnostic.is_primary
17495 && !entry.range.is_empty()
17496 && entry.range.start == primary_range_start
17497 && entry.diagnostic.message == active_diagnostics.active_message
17498 });
17499
17500 if !is_valid {
17501 self.dismiss_diagnostics(cx);
17502 }
17503 }
17504 }
17505
17506 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17507 match &self.active_diagnostics {
17508 ActiveDiagnostic::Group(group) => Some(group),
17509 _ => None,
17510 }
17511 }
17512
17513 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17514 if !self.diagnostics_enabled() {
17515 return;
17516 }
17517 self.dismiss_diagnostics(cx);
17518 self.active_diagnostics = ActiveDiagnostic::All;
17519 }
17520
17521 fn activate_diagnostics(
17522 &mut self,
17523 buffer_id: BufferId,
17524 diagnostic: DiagnosticEntry<usize>,
17525 window: &mut Window,
17526 cx: &mut Context<Self>,
17527 ) {
17528 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17529 return;
17530 }
17531 self.dismiss_diagnostics(cx);
17532 let snapshot = self.snapshot(window, cx);
17533 let buffer = self.buffer.read(cx).snapshot(cx);
17534 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17535 return;
17536 };
17537
17538 let diagnostic_group = buffer
17539 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17540 .collect::<Vec<_>>();
17541
17542 let blocks =
17543 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17544
17545 let blocks = self.display_map.update(cx, |display_map, cx| {
17546 display_map.insert_blocks(blocks, cx).into_iter().collect()
17547 });
17548 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17549 active_range: buffer.anchor_before(diagnostic.range.start)
17550 ..buffer.anchor_after(diagnostic.range.end),
17551 active_message: diagnostic.diagnostic.message.clone(),
17552 group_id: diagnostic.diagnostic.group_id,
17553 blocks,
17554 });
17555 cx.notify();
17556 }
17557
17558 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17559 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17560 return;
17561 };
17562
17563 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17564 if let ActiveDiagnostic::Group(group) = prev {
17565 self.display_map.update(cx, |display_map, cx| {
17566 display_map.remove_blocks(group.blocks, cx);
17567 });
17568 cx.notify();
17569 }
17570 }
17571
17572 /// Disable inline diagnostics rendering for this editor.
17573 pub fn disable_inline_diagnostics(&mut self) {
17574 self.inline_diagnostics_enabled = false;
17575 self.inline_diagnostics_update = Task::ready(());
17576 self.inline_diagnostics.clear();
17577 }
17578
17579 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17580 self.diagnostics_enabled = false;
17581 self.dismiss_diagnostics(cx);
17582 self.inline_diagnostics_update = Task::ready(());
17583 self.inline_diagnostics.clear();
17584 }
17585
17586 pub fn disable_word_completions(&mut self) {
17587 self.word_completions_enabled = false;
17588 }
17589
17590 pub fn diagnostics_enabled(&self) -> bool {
17591 self.diagnostics_enabled && self.mode.is_full()
17592 }
17593
17594 pub fn inline_diagnostics_enabled(&self) -> bool {
17595 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17596 }
17597
17598 pub fn show_inline_diagnostics(&self) -> bool {
17599 self.show_inline_diagnostics
17600 }
17601
17602 pub fn toggle_inline_diagnostics(
17603 &mut self,
17604 _: &ToggleInlineDiagnostics,
17605 window: &mut Window,
17606 cx: &mut Context<Editor>,
17607 ) {
17608 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17609 self.refresh_inline_diagnostics(false, window, cx);
17610 }
17611
17612 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17613 self.diagnostics_max_severity = severity;
17614 self.display_map.update(cx, |display_map, _| {
17615 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17616 });
17617 }
17618
17619 pub fn toggle_diagnostics(
17620 &mut self,
17621 _: &ToggleDiagnostics,
17622 window: &mut Window,
17623 cx: &mut Context<Editor>,
17624 ) {
17625 if !self.diagnostics_enabled() {
17626 return;
17627 }
17628
17629 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17630 EditorSettings::get_global(cx)
17631 .diagnostics_max_severity
17632 .filter(|severity| severity != &DiagnosticSeverity::Off)
17633 .unwrap_or(DiagnosticSeverity::Hint)
17634 } else {
17635 DiagnosticSeverity::Off
17636 };
17637 self.set_max_diagnostics_severity(new_severity, cx);
17638 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17639 self.active_diagnostics = ActiveDiagnostic::None;
17640 self.inline_diagnostics_update = Task::ready(());
17641 self.inline_diagnostics.clear();
17642 } else {
17643 self.refresh_inline_diagnostics(false, window, cx);
17644 }
17645
17646 cx.notify();
17647 }
17648
17649 pub fn toggle_minimap(
17650 &mut self,
17651 _: &ToggleMinimap,
17652 window: &mut Window,
17653 cx: &mut Context<Editor>,
17654 ) {
17655 if self.supports_minimap(cx) {
17656 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17657 }
17658 }
17659
17660 fn refresh_inline_diagnostics(
17661 &mut self,
17662 debounce: bool,
17663 window: &mut Window,
17664 cx: &mut Context<Self>,
17665 ) {
17666 let max_severity = ProjectSettings::get_global(cx)
17667 .diagnostics
17668 .inline
17669 .max_severity
17670 .unwrap_or(self.diagnostics_max_severity);
17671
17672 if !self.inline_diagnostics_enabled()
17673 || !self.show_inline_diagnostics
17674 || max_severity == DiagnosticSeverity::Off
17675 {
17676 self.inline_diagnostics_update = Task::ready(());
17677 self.inline_diagnostics.clear();
17678 return;
17679 }
17680
17681 let debounce_ms = ProjectSettings::get_global(cx)
17682 .diagnostics
17683 .inline
17684 .update_debounce_ms;
17685 let debounce = if debounce && debounce_ms > 0 {
17686 Some(Duration::from_millis(debounce_ms))
17687 } else {
17688 None
17689 };
17690 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17691 if let Some(debounce) = debounce {
17692 cx.background_executor().timer(debounce).await;
17693 }
17694 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17695 editor
17696 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17697 .ok()
17698 }) else {
17699 return;
17700 };
17701
17702 let new_inline_diagnostics = cx
17703 .background_spawn(async move {
17704 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17705 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17706 let message = diagnostic_entry
17707 .diagnostic
17708 .message
17709 .split_once('\n')
17710 .map(|(line, _)| line)
17711 .map(SharedString::new)
17712 .unwrap_or_else(|| {
17713 SharedString::from(diagnostic_entry.diagnostic.message)
17714 });
17715 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17716 let (Ok(i) | Err(i)) = inline_diagnostics
17717 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17718 inline_diagnostics.insert(
17719 i,
17720 (
17721 start_anchor,
17722 InlineDiagnostic {
17723 message,
17724 group_id: diagnostic_entry.diagnostic.group_id,
17725 start: diagnostic_entry.range.start.to_point(&snapshot),
17726 is_primary: diagnostic_entry.diagnostic.is_primary,
17727 severity: diagnostic_entry.diagnostic.severity,
17728 },
17729 ),
17730 );
17731 }
17732 inline_diagnostics
17733 })
17734 .await;
17735
17736 editor
17737 .update(cx, |editor, cx| {
17738 editor.inline_diagnostics = new_inline_diagnostics;
17739 cx.notify();
17740 })
17741 .ok();
17742 });
17743 }
17744
17745 fn pull_diagnostics(
17746 &mut self,
17747 buffer_id: Option<BufferId>,
17748 window: &Window,
17749 cx: &mut Context<Self>,
17750 ) -> Option<()> {
17751 if !self.mode().is_full() {
17752 return None;
17753 }
17754 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17755 .diagnostics
17756 .lsp_pull_diagnostics;
17757 if !pull_diagnostics_settings.enabled {
17758 return None;
17759 }
17760 let project = self.project()?.downgrade();
17761 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17762 let mut buffers = self.buffer.read(cx).all_buffers();
17763 if let Some(buffer_id) = buffer_id {
17764 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17765 }
17766
17767 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17768 cx.background_executor().timer(debounce).await;
17769
17770 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17771 buffers
17772 .into_iter()
17773 .filter_map(|buffer| {
17774 project
17775 .update(cx, |project, cx| {
17776 project.lsp_store().update(cx, |lsp_store, cx| {
17777 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17778 })
17779 })
17780 .ok()
17781 })
17782 .collect::<FuturesUnordered<_>>()
17783 }) else {
17784 return;
17785 };
17786
17787 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17788 match pull_task {
17789 Ok(()) => {
17790 if editor
17791 .update_in(cx, |editor, window, cx| {
17792 editor.update_diagnostics_state(window, cx);
17793 })
17794 .is_err()
17795 {
17796 return;
17797 }
17798 }
17799 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17800 }
17801 }
17802 });
17803
17804 Some(())
17805 }
17806
17807 pub fn set_selections_from_remote(
17808 &mut self,
17809 selections: Vec<Selection<Anchor>>,
17810 pending_selection: Option<Selection<Anchor>>,
17811 window: &mut Window,
17812 cx: &mut Context<Self>,
17813 ) {
17814 let old_cursor_position = self.selections.newest_anchor().head();
17815 self.selections.change_with(cx, |s| {
17816 s.select_anchors(selections);
17817 if let Some(pending_selection) = pending_selection {
17818 s.set_pending(pending_selection, SelectMode::Character);
17819 } else {
17820 s.clear_pending();
17821 }
17822 });
17823 self.selections_did_change(
17824 false,
17825 &old_cursor_position,
17826 SelectionEffects::default(),
17827 window,
17828 cx,
17829 );
17830 }
17831
17832 pub fn transact(
17833 &mut self,
17834 window: &mut Window,
17835 cx: &mut Context<Self>,
17836 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17837 ) -> Option<TransactionId> {
17838 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17839 this.start_transaction_at(Instant::now(), window, cx);
17840 update(this, window, cx);
17841 this.end_transaction_at(Instant::now(), cx)
17842 })
17843 }
17844
17845 pub fn start_transaction_at(
17846 &mut self,
17847 now: Instant,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) -> Option<TransactionId> {
17851 self.end_selection(window, cx);
17852 if let Some(tx_id) = self
17853 .buffer
17854 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17855 {
17856 self.selection_history
17857 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17858 cx.emit(EditorEvent::TransactionBegun {
17859 transaction_id: tx_id,
17860 });
17861 Some(tx_id)
17862 } else {
17863 None
17864 }
17865 }
17866
17867 pub fn end_transaction_at(
17868 &mut self,
17869 now: Instant,
17870 cx: &mut Context<Self>,
17871 ) -> Option<TransactionId> {
17872 if let Some(transaction_id) = self
17873 .buffer
17874 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17875 {
17876 if let Some((_, end_selections)) =
17877 self.selection_history.transaction_mut(transaction_id)
17878 {
17879 *end_selections = Some(self.selections.disjoint_anchors_arc());
17880 } else {
17881 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17882 }
17883
17884 cx.emit(EditorEvent::Edited { transaction_id });
17885 Some(transaction_id)
17886 } else {
17887 None
17888 }
17889 }
17890
17891 pub fn modify_transaction_selection_history(
17892 &mut self,
17893 transaction_id: TransactionId,
17894 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17895 ) -> bool {
17896 self.selection_history
17897 .transaction_mut(transaction_id)
17898 .map(modify)
17899 .is_some()
17900 }
17901
17902 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17903 if self.selection_mark_mode {
17904 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17905 s.move_with(|_, sel| {
17906 sel.collapse_to(sel.head(), SelectionGoal::None);
17907 });
17908 })
17909 }
17910 self.selection_mark_mode = true;
17911 cx.notify();
17912 }
17913
17914 pub fn swap_selection_ends(
17915 &mut self,
17916 _: &actions::SwapSelectionEnds,
17917 window: &mut Window,
17918 cx: &mut Context<Self>,
17919 ) {
17920 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17921 s.move_with(|_, sel| {
17922 if sel.start != sel.end {
17923 sel.reversed = !sel.reversed
17924 }
17925 });
17926 });
17927 self.request_autoscroll(Autoscroll::newest(), cx);
17928 cx.notify();
17929 }
17930
17931 pub fn toggle_focus(
17932 workspace: &mut Workspace,
17933 _: &actions::ToggleFocus,
17934 window: &mut Window,
17935 cx: &mut Context<Workspace>,
17936 ) {
17937 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17938 return;
17939 };
17940 workspace.activate_item(&item, true, true, window, cx);
17941 }
17942
17943 pub fn toggle_fold(
17944 &mut self,
17945 _: &actions::ToggleFold,
17946 window: &mut Window,
17947 cx: &mut Context<Self>,
17948 ) {
17949 if self.is_singleton(cx) {
17950 let selection = self.selections.newest::<Point>(cx);
17951
17952 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17953 let range = if selection.is_empty() {
17954 let point = selection.head().to_display_point(&display_map);
17955 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17956 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17957 .to_point(&display_map);
17958 start..end
17959 } else {
17960 selection.range()
17961 };
17962 if display_map.folds_in_range(range).next().is_some() {
17963 self.unfold_lines(&Default::default(), window, cx)
17964 } else {
17965 self.fold(&Default::default(), window, cx)
17966 }
17967 } else {
17968 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17969 let buffer_ids: HashSet<_> = self
17970 .selections
17971 .disjoint_anchor_ranges()
17972 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17973 .collect();
17974
17975 let should_unfold = buffer_ids
17976 .iter()
17977 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17978
17979 for buffer_id in buffer_ids {
17980 if should_unfold {
17981 self.unfold_buffer(buffer_id, cx);
17982 } else {
17983 self.fold_buffer(buffer_id, cx);
17984 }
17985 }
17986 }
17987 }
17988
17989 pub fn toggle_fold_recursive(
17990 &mut self,
17991 _: &actions::ToggleFoldRecursive,
17992 window: &mut Window,
17993 cx: &mut Context<Self>,
17994 ) {
17995 let selection = self.selections.newest::<Point>(cx);
17996
17997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17998 let range = if selection.is_empty() {
17999 let point = selection.head().to_display_point(&display_map);
18000 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18001 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18002 .to_point(&display_map);
18003 start..end
18004 } else {
18005 selection.range()
18006 };
18007 if display_map.folds_in_range(range).next().is_some() {
18008 self.unfold_recursive(&Default::default(), window, cx)
18009 } else {
18010 self.fold_recursive(&Default::default(), window, cx)
18011 }
18012 }
18013
18014 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18015 if self.is_singleton(cx) {
18016 let mut to_fold = Vec::new();
18017 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18018 let selections = self.selections.all_adjusted(cx);
18019
18020 for selection in selections {
18021 let range = selection.range().sorted();
18022 let buffer_start_row = range.start.row;
18023
18024 if range.start.row != range.end.row {
18025 let mut found = false;
18026 let mut row = range.start.row;
18027 while row <= range.end.row {
18028 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18029 {
18030 found = true;
18031 row = crease.range().end.row + 1;
18032 to_fold.push(crease);
18033 } else {
18034 row += 1
18035 }
18036 }
18037 if found {
18038 continue;
18039 }
18040 }
18041
18042 for row in (0..=range.start.row).rev() {
18043 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18044 && crease.range().end.row >= buffer_start_row
18045 {
18046 to_fold.push(crease);
18047 if row <= range.start.row {
18048 break;
18049 }
18050 }
18051 }
18052 }
18053
18054 self.fold_creases(to_fold, true, window, cx);
18055 } else {
18056 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18057 let buffer_ids = self
18058 .selections
18059 .disjoint_anchor_ranges()
18060 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18061 .collect::<HashSet<_>>();
18062 for buffer_id in buffer_ids {
18063 self.fold_buffer(buffer_id, cx);
18064 }
18065 }
18066 }
18067
18068 pub fn toggle_fold_all(
18069 &mut self,
18070 _: &actions::ToggleFoldAll,
18071 window: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) {
18074 if self.buffer.read(cx).is_singleton() {
18075 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18076 let has_folds = display_map
18077 .folds_in_range(0..display_map.buffer_snapshot.len())
18078 .next()
18079 .is_some();
18080
18081 if has_folds {
18082 self.unfold_all(&actions::UnfoldAll, window, cx);
18083 } else {
18084 self.fold_all(&actions::FoldAll, window, cx);
18085 }
18086 } else {
18087 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18088 let should_unfold = buffer_ids
18089 .iter()
18090 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18091
18092 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18093 editor
18094 .update_in(cx, |editor, _, cx| {
18095 for buffer_id in buffer_ids {
18096 if should_unfold {
18097 editor.unfold_buffer(buffer_id, cx);
18098 } else {
18099 editor.fold_buffer(buffer_id, cx);
18100 }
18101 }
18102 })
18103 .ok();
18104 });
18105 }
18106 }
18107
18108 fn fold_at_level(
18109 &mut self,
18110 fold_at: &FoldAtLevel,
18111 window: &mut Window,
18112 cx: &mut Context<Self>,
18113 ) {
18114 if !self.buffer.read(cx).is_singleton() {
18115 return;
18116 }
18117
18118 let fold_at_level = fold_at.0;
18119 let snapshot = self.buffer.read(cx).snapshot(cx);
18120 let mut to_fold = Vec::new();
18121 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18122
18123 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18124 while start_row < end_row {
18125 match self
18126 .snapshot(window, cx)
18127 .crease_for_buffer_row(MultiBufferRow(start_row))
18128 {
18129 Some(crease) => {
18130 let nested_start_row = crease.range().start.row + 1;
18131 let nested_end_row = crease.range().end.row;
18132
18133 if current_level < fold_at_level {
18134 stack.push((nested_start_row, nested_end_row, current_level + 1));
18135 } else if current_level == fold_at_level {
18136 to_fold.push(crease);
18137 }
18138
18139 start_row = nested_end_row + 1;
18140 }
18141 None => start_row += 1,
18142 }
18143 }
18144 }
18145
18146 self.fold_creases(to_fold, true, window, cx);
18147 }
18148
18149 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18150 if self.buffer.read(cx).is_singleton() {
18151 let mut fold_ranges = Vec::new();
18152 let snapshot = self.buffer.read(cx).snapshot(cx);
18153
18154 for row in 0..snapshot.max_row().0 {
18155 if let Some(foldable_range) = self
18156 .snapshot(window, cx)
18157 .crease_for_buffer_row(MultiBufferRow(row))
18158 {
18159 fold_ranges.push(foldable_range);
18160 }
18161 }
18162
18163 self.fold_creases(fold_ranges, true, window, cx);
18164 } else {
18165 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18166 editor
18167 .update_in(cx, |editor, _, cx| {
18168 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18169 editor.fold_buffer(buffer_id, cx);
18170 }
18171 })
18172 .ok();
18173 });
18174 }
18175 }
18176
18177 pub fn fold_function_bodies(
18178 &mut self,
18179 _: &actions::FoldFunctionBodies,
18180 window: &mut Window,
18181 cx: &mut Context<Self>,
18182 ) {
18183 let snapshot = self.buffer.read(cx).snapshot(cx);
18184
18185 let ranges = snapshot
18186 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18187 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18188 .collect::<Vec<_>>();
18189
18190 let creases = ranges
18191 .into_iter()
18192 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18193 .collect();
18194
18195 self.fold_creases(creases, true, window, cx);
18196 }
18197
18198 pub fn fold_recursive(
18199 &mut self,
18200 _: &actions::FoldRecursive,
18201 window: &mut Window,
18202 cx: &mut Context<Self>,
18203 ) {
18204 let mut to_fold = Vec::new();
18205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18206 let selections = self.selections.all_adjusted(cx);
18207
18208 for selection in selections {
18209 let range = selection.range().sorted();
18210 let buffer_start_row = range.start.row;
18211
18212 if range.start.row != range.end.row {
18213 let mut found = false;
18214 for row in range.start.row..=range.end.row {
18215 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18216 found = true;
18217 to_fold.push(crease);
18218 }
18219 }
18220 if found {
18221 continue;
18222 }
18223 }
18224
18225 for row in (0..=range.start.row).rev() {
18226 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18227 if crease.range().end.row >= buffer_start_row {
18228 to_fold.push(crease);
18229 } else {
18230 break;
18231 }
18232 }
18233 }
18234 }
18235
18236 self.fold_creases(to_fold, true, window, cx);
18237 }
18238
18239 pub fn fold_at(
18240 &mut self,
18241 buffer_row: MultiBufferRow,
18242 window: &mut Window,
18243 cx: &mut Context<Self>,
18244 ) {
18245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18246
18247 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18248 let autoscroll = self
18249 .selections
18250 .all::<Point>(cx)
18251 .iter()
18252 .any(|selection| crease.range().overlaps(&selection.range()));
18253
18254 self.fold_creases(vec![crease], autoscroll, window, cx);
18255 }
18256 }
18257
18258 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18259 if self.is_singleton(cx) {
18260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18261 let buffer = &display_map.buffer_snapshot;
18262 let selections = self.selections.all::<Point>(cx);
18263 let ranges = selections
18264 .iter()
18265 .map(|s| {
18266 let range = s.display_range(&display_map).sorted();
18267 let mut start = range.start.to_point(&display_map);
18268 let mut end = range.end.to_point(&display_map);
18269 start.column = 0;
18270 end.column = buffer.line_len(MultiBufferRow(end.row));
18271 start..end
18272 })
18273 .collect::<Vec<_>>();
18274
18275 self.unfold_ranges(&ranges, true, true, cx);
18276 } else {
18277 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18278 let buffer_ids = self
18279 .selections
18280 .disjoint_anchor_ranges()
18281 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18282 .collect::<HashSet<_>>();
18283 for buffer_id in buffer_ids {
18284 self.unfold_buffer(buffer_id, cx);
18285 }
18286 }
18287 }
18288
18289 pub fn unfold_recursive(
18290 &mut self,
18291 _: &UnfoldRecursive,
18292 _window: &mut Window,
18293 cx: &mut Context<Self>,
18294 ) {
18295 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18296 let selections = self.selections.all::<Point>(cx);
18297 let ranges = selections
18298 .iter()
18299 .map(|s| {
18300 let mut range = s.display_range(&display_map).sorted();
18301 *range.start.column_mut() = 0;
18302 *range.end.column_mut() = display_map.line_len(range.end.row());
18303 let start = range.start.to_point(&display_map);
18304 let end = range.end.to_point(&display_map);
18305 start..end
18306 })
18307 .collect::<Vec<_>>();
18308
18309 self.unfold_ranges(&ranges, true, true, cx);
18310 }
18311
18312 pub fn unfold_at(
18313 &mut self,
18314 buffer_row: MultiBufferRow,
18315 _window: &mut Window,
18316 cx: &mut Context<Self>,
18317 ) {
18318 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18319
18320 let intersection_range = Point::new(buffer_row.0, 0)
18321 ..Point::new(
18322 buffer_row.0,
18323 display_map.buffer_snapshot.line_len(buffer_row),
18324 );
18325
18326 let autoscroll = self
18327 .selections
18328 .all::<Point>(cx)
18329 .iter()
18330 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18331
18332 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18333 }
18334
18335 pub fn unfold_all(
18336 &mut self,
18337 _: &actions::UnfoldAll,
18338 _window: &mut Window,
18339 cx: &mut Context<Self>,
18340 ) {
18341 if self.buffer.read(cx).is_singleton() {
18342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18343 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18344 } else {
18345 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18346 editor
18347 .update(cx, |editor, cx| {
18348 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18349 editor.unfold_buffer(buffer_id, cx);
18350 }
18351 })
18352 .ok();
18353 });
18354 }
18355 }
18356
18357 pub fn fold_selected_ranges(
18358 &mut self,
18359 _: &FoldSelectedRanges,
18360 window: &mut Window,
18361 cx: &mut Context<Self>,
18362 ) {
18363 let selections = self.selections.all_adjusted(cx);
18364 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18365 let ranges = selections
18366 .into_iter()
18367 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18368 .collect::<Vec<_>>();
18369 self.fold_creases(ranges, true, window, cx);
18370 }
18371
18372 pub fn fold_ranges<T: ToOffset + Clone>(
18373 &mut self,
18374 ranges: Vec<Range<T>>,
18375 auto_scroll: bool,
18376 window: &mut Window,
18377 cx: &mut Context<Self>,
18378 ) {
18379 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18380 let ranges = ranges
18381 .into_iter()
18382 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18383 .collect::<Vec<_>>();
18384 self.fold_creases(ranges, auto_scroll, window, cx);
18385 }
18386
18387 pub fn fold_creases<T: ToOffset + Clone>(
18388 &mut self,
18389 creases: Vec<Crease<T>>,
18390 auto_scroll: bool,
18391 _window: &mut Window,
18392 cx: &mut Context<Self>,
18393 ) {
18394 if creases.is_empty() {
18395 return;
18396 }
18397
18398 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18399
18400 if auto_scroll {
18401 self.request_autoscroll(Autoscroll::fit(), cx);
18402 }
18403
18404 cx.notify();
18405
18406 self.scrollbar_marker_state.dirty = true;
18407 self.folds_did_change(cx);
18408 }
18409
18410 /// Removes any folds whose ranges intersect any of the given ranges.
18411 pub fn unfold_ranges<T: ToOffset + Clone>(
18412 &mut self,
18413 ranges: &[Range<T>],
18414 inclusive: bool,
18415 auto_scroll: bool,
18416 cx: &mut Context<Self>,
18417 ) {
18418 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18419 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18420 });
18421 self.folds_did_change(cx);
18422 }
18423
18424 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18425 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18426 return;
18427 }
18428 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18429 self.display_map.update(cx, |display_map, cx| {
18430 display_map.fold_buffers([buffer_id], cx)
18431 });
18432 cx.emit(EditorEvent::BufferFoldToggled {
18433 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18434 folded: true,
18435 });
18436 cx.notify();
18437 }
18438
18439 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18440 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18441 return;
18442 }
18443 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18444 self.display_map.update(cx, |display_map, cx| {
18445 display_map.unfold_buffers([buffer_id], cx);
18446 });
18447 cx.emit(EditorEvent::BufferFoldToggled {
18448 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18449 folded: false,
18450 });
18451 cx.notify();
18452 }
18453
18454 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18455 self.display_map.read(cx).is_buffer_folded(buffer)
18456 }
18457
18458 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18459 self.display_map.read(cx).folded_buffers()
18460 }
18461
18462 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18463 self.display_map.update(cx, |display_map, cx| {
18464 display_map.disable_header_for_buffer(buffer_id, cx);
18465 });
18466 cx.notify();
18467 }
18468
18469 /// Removes any folds with the given ranges.
18470 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18471 &mut self,
18472 ranges: &[Range<T>],
18473 type_id: TypeId,
18474 auto_scroll: bool,
18475 cx: &mut Context<Self>,
18476 ) {
18477 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18478 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18479 });
18480 self.folds_did_change(cx);
18481 }
18482
18483 fn remove_folds_with<T: ToOffset + Clone>(
18484 &mut self,
18485 ranges: &[Range<T>],
18486 auto_scroll: bool,
18487 cx: &mut Context<Self>,
18488 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18489 ) {
18490 if ranges.is_empty() {
18491 return;
18492 }
18493
18494 let mut buffers_affected = HashSet::default();
18495 let multi_buffer = self.buffer().read(cx);
18496 for range in ranges {
18497 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18498 buffers_affected.insert(buffer.read(cx).remote_id());
18499 };
18500 }
18501
18502 self.display_map.update(cx, update);
18503
18504 if auto_scroll {
18505 self.request_autoscroll(Autoscroll::fit(), cx);
18506 }
18507
18508 cx.notify();
18509 self.scrollbar_marker_state.dirty = true;
18510 self.active_indent_guides_state.dirty = true;
18511 }
18512
18513 pub fn update_renderer_widths(
18514 &mut self,
18515 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18516 cx: &mut Context<Self>,
18517 ) -> bool {
18518 self.display_map
18519 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18520 }
18521
18522 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18523 self.display_map.read(cx).fold_placeholder.clone()
18524 }
18525
18526 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18527 self.buffer.update(cx, |buffer, cx| {
18528 buffer.set_all_diff_hunks_expanded(cx);
18529 });
18530 }
18531
18532 pub fn expand_all_diff_hunks(
18533 &mut self,
18534 _: &ExpandAllDiffHunks,
18535 _window: &mut Window,
18536 cx: &mut Context<Self>,
18537 ) {
18538 self.buffer.update(cx, |buffer, cx| {
18539 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18540 });
18541 }
18542
18543 pub fn toggle_selected_diff_hunks(
18544 &mut self,
18545 _: &ToggleSelectedDiffHunks,
18546 _window: &mut Window,
18547 cx: &mut Context<Self>,
18548 ) {
18549 let ranges: Vec<_> = self
18550 .selections
18551 .disjoint_anchors()
18552 .iter()
18553 .map(|s| s.range())
18554 .collect();
18555 self.toggle_diff_hunks_in_ranges(ranges, cx);
18556 }
18557
18558 pub fn diff_hunks_in_ranges<'a>(
18559 &'a self,
18560 ranges: &'a [Range<Anchor>],
18561 buffer: &'a MultiBufferSnapshot,
18562 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18563 ranges.iter().flat_map(move |range| {
18564 let end_excerpt_id = range.end.excerpt_id;
18565 let range = range.to_point(buffer);
18566 let mut peek_end = range.end;
18567 if range.end.row < buffer.max_row().0 {
18568 peek_end = Point::new(range.end.row + 1, 0);
18569 }
18570 buffer
18571 .diff_hunks_in_range(range.start..peek_end)
18572 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18573 })
18574 }
18575
18576 pub fn has_stageable_diff_hunks_in_ranges(
18577 &self,
18578 ranges: &[Range<Anchor>],
18579 snapshot: &MultiBufferSnapshot,
18580 ) -> bool {
18581 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18582 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18583 }
18584
18585 pub fn toggle_staged_selected_diff_hunks(
18586 &mut self,
18587 _: &::git::ToggleStaged,
18588 _: &mut Window,
18589 cx: &mut Context<Self>,
18590 ) {
18591 let snapshot = self.buffer.read(cx).snapshot(cx);
18592 let ranges: Vec<_> = self
18593 .selections
18594 .disjoint_anchors()
18595 .iter()
18596 .map(|s| s.range())
18597 .collect();
18598 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18599 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18600 }
18601
18602 pub fn set_render_diff_hunk_controls(
18603 &mut self,
18604 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18605 cx: &mut Context<Self>,
18606 ) {
18607 self.render_diff_hunk_controls = render_diff_hunk_controls;
18608 cx.notify();
18609 }
18610
18611 pub fn stage_and_next(
18612 &mut self,
18613 _: &::git::StageAndNext,
18614 window: &mut Window,
18615 cx: &mut Context<Self>,
18616 ) {
18617 self.do_stage_or_unstage_and_next(true, window, cx);
18618 }
18619
18620 pub fn unstage_and_next(
18621 &mut self,
18622 _: &::git::UnstageAndNext,
18623 window: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 self.do_stage_or_unstage_and_next(false, window, cx);
18627 }
18628
18629 pub fn stage_or_unstage_diff_hunks(
18630 &mut self,
18631 stage: bool,
18632 ranges: Vec<Range<Anchor>>,
18633 cx: &mut Context<Self>,
18634 ) {
18635 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18636 cx.spawn(async move |this, cx| {
18637 task.await?;
18638 this.update(cx, |this, cx| {
18639 let snapshot = this.buffer.read(cx).snapshot(cx);
18640 let chunk_by = this
18641 .diff_hunks_in_ranges(&ranges, &snapshot)
18642 .chunk_by(|hunk| hunk.buffer_id);
18643 for (buffer_id, hunks) in &chunk_by {
18644 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18645 }
18646 })
18647 })
18648 .detach_and_log_err(cx);
18649 }
18650
18651 fn save_buffers_for_ranges_if_needed(
18652 &mut self,
18653 ranges: &[Range<Anchor>],
18654 cx: &mut Context<Editor>,
18655 ) -> Task<Result<()>> {
18656 let multibuffer = self.buffer.read(cx);
18657 let snapshot = multibuffer.read(cx);
18658 let buffer_ids: HashSet<_> = ranges
18659 .iter()
18660 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18661 .collect();
18662 drop(snapshot);
18663
18664 let mut buffers = HashSet::default();
18665 for buffer_id in buffer_ids {
18666 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18667 let buffer = buffer_entity.read(cx);
18668 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18669 {
18670 buffers.insert(buffer_entity);
18671 }
18672 }
18673 }
18674
18675 if let Some(project) = &self.project {
18676 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18677 } else {
18678 Task::ready(Ok(()))
18679 }
18680 }
18681
18682 fn do_stage_or_unstage_and_next(
18683 &mut self,
18684 stage: bool,
18685 window: &mut Window,
18686 cx: &mut Context<Self>,
18687 ) {
18688 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18689
18690 if ranges.iter().any(|range| range.start != range.end) {
18691 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18692 return;
18693 }
18694
18695 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18696 let snapshot = self.snapshot(window, cx);
18697 let position = self.selections.newest::<Point>(cx).head();
18698 let mut row = snapshot
18699 .buffer_snapshot
18700 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18701 .find(|hunk| hunk.row_range.start.0 > position.row)
18702 .map(|hunk| hunk.row_range.start);
18703
18704 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18705 // Outside of the project diff editor, wrap around to the beginning.
18706 if !all_diff_hunks_expanded {
18707 row = row.or_else(|| {
18708 snapshot
18709 .buffer_snapshot
18710 .diff_hunks_in_range(Point::zero()..position)
18711 .find(|hunk| hunk.row_range.end.0 < position.row)
18712 .map(|hunk| hunk.row_range.start)
18713 });
18714 }
18715
18716 if let Some(row) = row {
18717 let destination = Point::new(row.0, 0);
18718 let autoscroll = Autoscroll::center();
18719
18720 self.unfold_ranges(&[destination..destination], false, false, cx);
18721 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18722 s.select_ranges([destination..destination]);
18723 });
18724 }
18725 }
18726
18727 fn do_stage_or_unstage(
18728 &self,
18729 stage: bool,
18730 buffer_id: BufferId,
18731 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18732 cx: &mut App,
18733 ) -> Option<()> {
18734 let project = self.project()?;
18735 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18736 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18737 let buffer_snapshot = buffer.read(cx).snapshot();
18738 let file_exists = buffer_snapshot
18739 .file()
18740 .is_some_and(|file| file.disk_state().exists());
18741 diff.update(cx, |diff, cx| {
18742 diff.stage_or_unstage_hunks(
18743 stage,
18744 &hunks
18745 .map(|hunk| buffer_diff::DiffHunk {
18746 buffer_range: hunk.buffer_range,
18747 diff_base_byte_range: hunk.diff_base_byte_range,
18748 secondary_status: hunk.secondary_status,
18749 range: Point::zero()..Point::zero(), // unused
18750 })
18751 .collect::<Vec<_>>(),
18752 &buffer_snapshot,
18753 file_exists,
18754 cx,
18755 )
18756 });
18757 None
18758 }
18759
18760 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18761 let ranges: Vec<_> = self
18762 .selections
18763 .disjoint_anchors()
18764 .iter()
18765 .map(|s| s.range())
18766 .collect();
18767 self.buffer
18768 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18769 }
18770
18771 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18772 self.buffer.update(cx, |buffer, cx| {
18773 let ranges = vec![Anchor::min()..Anchor::max()];
18774 if !buffer.all_diff_hunks_expanded()
18775 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18776 {
18777 buffer.collapse_diff_hunks(ranges, cx);
18778 true
18779 } else {
18780 false
18781 }
18782 })
18783 }
18784
18785 fn toggle_diff_hunks_in_ranges(
18786 &mut self,
18787 ranges: Vec<Range<Anchor>>,
18788 cx: &mut Context<Editor>,
18789 ) {
18790 self.buffer.update(cx, |buffer, cx| {
18791 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18792 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18793 })
18794 }
18795
18796 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18797 self.buffer.update(cx, |buffer, cx| {
18798 let snapshot = buffer.snapshot(cx);
18799 let excerpt_id = range.end.excerpt_id;
18800 let point_range = range.to_point(&snapshot);
18801 let expand = !buffer.single_hunk_is_expanded(range, cx);
18802 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18803 })
18804 }
18805
18806 pub(crate) fn apply_all_diff_hunks(
18807 &mut self,
18808 _: &ApplyAllDiffHunks,
18809 window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) {
18812 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18813
18814 let buffers = self.buffer.read(cx).all_buffers();
18815 for branch_buffer in buffers {
18816 branch_buffer.update(cx, |branch_buffer, cx| {
18817 branch_buffer.merge_into_base(Vec::new(), cx);
18818 });
18819 }
18820
18821 if let Some(project) = self.project.clone() {
18822 self.save(
18823 SaveOptions {
18824 format: true,
18825 autosave: false,
18826 },
18827 project,
18828 window,
18829 cx,
18830 )
18831 .detach_and_log_err(cx);
18832 }
18833 }
18834
18835 pub(crate) fn apply_selected_diff_hunks(
18836 &mut self,
18837 _: &ApplyDiffHunk,
18838 window: &mut Window,
18839 cx: &mut Context<Self>,
18840 ) {
18841 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18842 let snapshot = self.snapshot(window, cx);
18843 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18844 let mut ranges_by_buffer = HashMap::default();
18845 self.transact(window, cx, |editor, _window, cx| {
18846 for hunk in hunks {
18847 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18848 ranges_by_buffer
18849 .entry(buffer.clone())
18850 .or_insert_with(Vec::new)
18851 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18852 }
18853 }
18854
18855 for (buffer, ranges) in ranges_by_buffer {
18856 buffer.update(cx, |buffer, cx| {
18857 buffer.merge_into_base(ranges, cx);
18858 });
18859 }
18860 });
18861
18862 if let Some(project) = self.project.clone() {
18863 self.save(
18864 SaveOptions {
18865 format: true,
18866 autosave: false,
18867 },
18868 project,
18869 window,
18870 cx,
18871 )
18872 .detach_and_log_err(cx);
18873 }
18874 }
18875
18876 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18877 if hovered != self.gutter_hovered {
18878 self.gutter_hovered = hovered;
18879 cx.notify();
18880 }
18881 }
18882
18883 pub fn insert_blocks(
18884 &mut self,
18885 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18886 autoscroll: Option<Autoscroll>,
18887 cx: &mut Context<Self>,
18888 ) -> Vec<CustomBlockId> {
18889 let blocks = self
18890 .display_map
18891 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18892 if let Some(autoscroll) = autoscroll {
18893 self.request_autoscroll(autoscroll, cx);
18894 }
18895 cx.notify();
18896 blocks
18897 }
18898
18899 pub fn resize_blocks(
18900 &mut self,
18901 heights: HashMap<CustomBlockId, u32>,
18902 autoscroll: Option<Autoscroll>,
18903 cx: &mut Context<Self>,
18904 ) {
18905 self.display_map
18906 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18907 if let Some(autoscroll) = autoscroll {
18908 self.request_autoscroll(autoscroll, cx);
18909 }
18910 cx.notify();
18911 }
18912
18913 pub fn replace_blocks(
18914 &mut self,
18915 renderers: HashMap<CustomBlockId, RenderBlock>,
18916 autoscroll: Option<Autoscroll>,
18917 cx: &mut Context<Self>,
18918 ) {
18919 self.display_map
18920 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18921 if let Some(autoscroll) = autoscroll {
18922 self.request_autoscroll(autoscroll, cx);
18923 }
18924 cx.notify();
18925 }
18926
18927 pub fn remove_blocks(
18928 &mut self,
18929 block_ids: HashSet<CustomBlockId>,
18930 autoscroll: Option<Autoscroll>,
18931 cx: &mut Context<Self>,
18932 ) {
18933 self.display_map.update(cx, |display_map, cx| {
18934 display_map.remove_blocks(block_ids, cx)
18935 });
18936 if let Some(autoscroll) = autoscroll {
18937 self.request_autoscroll(autoscroll, cx);
18938 }
18939 cx.notify();
18940 }
18941
18942 pub fn row_for_block(
18943 &self,
18944 block_id: CustomBlockId,
18945 cx: &mut Context<Self>,
18946 ) -> Option<DisplayRow> {
18947 self.display_map
18948 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18949 }
18950
18951 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18952 self.focused_block = Some(focused_block);
18953 }
18954
18955 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18956 self.focused_block.take()
18957 }
18958
18959 pub fn insert_creases(
18960 &mut self,
18961 creases: impl IntoIterator<Item = Crease<Anchor>>,
18962 cx: &mut Context<Self>,
18963 ) -> Vec<CreaseId> {
18964 self.display_map
18965 .update(cx, |map, cx| map.insert_creases(creases, cx))
18966 }
18967
18968 pub fn remove_creases(
18969 &mut self,
18970 ids: impl IntoIterator<Item = CreaseId>,
18971 cx: &mut Context<Self>,
18972 ) -> Vec<(CreaseId, Range<Anchor>)> {
18973 self.display_map
18974 .update(cx, |map, cx| map.remove_creases(ids, cx))
18975 }
18976
18977 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18978 self.display_map
18979 .update(cx, |map, cx| map.snapshot(cx))
18980 .longest_row()
18981 }
18982
18983 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18984 self.display_map
18985 .update(cx, |map, cx| map.snapshot(cx))
18986 .max_point()
18987 }
18988
18989 pub fn text(&self, cx: &App) -> String {
18990 self.buffer.read(cx).read(cx).text()
18991 }
18992
18993 pub fn is_empty(&self, cx: &App) -> bool {
18994 self.buffer.read(cx).read(cx).is_empty()
18995 }
18996
18997 pub fn text_option(&self, cx: &App) -> Option<String> {
18998 let text = self.text(cx);
18999 let text = text.trim();
19000
19001 if text.is_empty() {
19002 return None;
19003 }
19004
19005 Some(text.to_string())
19006 }
19007
19008 pub fn set_text(
19009 &mut self,
19010 text: impl Into<Arc<str>>,
19011 window: &mut Window,
19012 cx: &mut Context<Self>,
19013 ) {
19014 self.transact(window, cx, |this, _, cx| {
19015 this.buffer
19016 .read(cx)
19017 .as_singleton()
19018 .expect("you can only call set_text on editors for singleton buffers")
19019 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19020 });
19021 }
19022
19023 pub fn display_text(&self, cx: &mut App) -> String {
19024 self.display_map
19025 .update(cx, |map, cx| map.snapshot(cx))
19026 .text()
19027 }
19028
19029 fn create_minimap(
19030 &self,
19031 minimap_settings: MinimapSettings,
19032 window: &mut Window,
19033 cx: &mut Context<Self>,
19034 ) -> Option<Entity<Self>> {
19035 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
19036 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19037 }
19038
19039 fn initialize_new_minimap(
19040 &self,
19041 minimap_settings: MinimapSettings,
19042 window: &mut Window,
19043 cx: &mut Context<Self>,
19044 ) -> Entity<Self> {
19045 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19046
19047 let mut minimap = Editor::new_internal(
19048 EditorMode::Minimap {
19049 parent: cx.weak_entity(),
19050 },
19051 self.buffer.clone(),
19052 None,
19053 Some(self.display_map.clone()),
19054 window,
19055 cx,
19056 );
19057 minimap.scroll_manager.clone_state(&self.scroll_manager);
19058 minimap.set_text_style_refinement(TextStyleRefinement {
19059 font_size: Some(MINIMAP_FONT_SIZE),
19060 font_weight: Some(MINIMAP_FONT_WEIGHT),
19061 ..Default::default()
19062 });
19063 minimap.update_minimap_configuration(minimap_settings, cx);
19064 cx.new(|_| minimap)
19065 }
19066
19067 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19068 let current_line_highlight = minimap_settings
19069 .current_line_highlight
19070 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19071 self.set_current_line_highlight(Some(current_line_highlight));
19072 }
19073
19074 pub fn minimap(&self) -> Option<&Entity<Self>> {
19075 self.minimap
19076 .as_ref()
19077 .filter(|_| self.minimap_visibility.visible())
19078 }
19079
19080 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19081 let mut wrap_guides = smallvec![];
19082
19083 if self.show_wrap_guides == Some(false) {
19084 return wrap_guides;
19085 }
19086
19087 let settings = self.buffer.read(cx).language_settings(cx);
19088 if settings.show_wrap_guides {
19089 match self.soft_wrap_mode(cx) {
19090 SoftWrap::Column(soft_wrap) => {
19091 wrap_guides.push((soft_wrap as usize, true));
19092 }
19093 SoftWrap::Bounded(soft_wrap) => {
19094 wrap_guides.push((soft_wrap as usize, true));
19095 }
19096 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19097 }
19098 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19099 }
19100
19101 wrap_guides
19102 }
19103
19104 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19105 let settings = self.buffer.read(cx).language_settings(cx);
19106 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19107 match mode {
19108 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19109 SoftWrap::None
19110 }
19111 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19112 language_settings::SoftWrap::PreferredLineLength => {
19113 SoftWrap::Column(settings.preferred_line_length)
19114 }
19115 language_settings::SoftWrap::Bounded => {
19116 SoftWrap::Bounded(settings.preferred_line_length)
19117 }
19118 }
19119 }
19120
19121 pub fn set_soft_wrap_mode(
19122 &mut self,
19123 mode: language_settings::SoftWrap,
19124
19125 cx: &mut Context<Self>,
19126 ) {
19127 self.soft_wrap_mode_override = Some(mode);
19128 cx.notify();
19129 }
19130
19131 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19132 self.hard_wrap = hard_wrap;
19133 cx.notify();
19134 }
19135
19136 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19137 self.text_style_refinement = Some(style);
19138 }
19139
19140 /// called by the Element so we know what style we were most recently rendered with.
19141 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19142 // We intentionally do not inform the display map about the minimap style
19143 // so that wrapping is not recalculated and stays consistent for the editor
19144 // and its linked minimap.
19145 if !self.mode.is_minimap() {
19146 let font = style.text.font();
19147 let font_size = style.text.font_size.to_pixels(window.rem_size());
19148 let display_map = self
19149 .placeholder_display_map
19150 .as_ref()
19151 .filter(|_| self.is_empty(cx))
19152 .unwrap_or(&self.display_map);
19153
19154 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19155 }
19156 self.style = Some(style);
19157 }
19158
19159 pub fn style(&self) -> Option<&EditorStyle> {
19160 self.style.as_ref()
19161 }
19162
19163 // Called by the element. This method is not designed to be called outside of the editor
19164 // element's layout code because it does not notify when rewrapping is computed synchronously.
19165 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19166 if self.is_empty(cx) {
19167 self.placeholder_display_map
19168 .as_ref()
19169 .map_or(false, |display_map| {
19170 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19171 })
19172 } else {
19173 self.display_map
19174 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19175 }
19176 }
19177
19178 pub fn set_soft_wrap(&mut self) {
19179 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19180 }
19181
19182 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19183 if self.soft_wrap_mode_override.is_some() {
19184 self.soft_wrap_mode_override.take();
19185 } else {
19186 let soft_wrap = match self.soft_wrap_mode(cx) {
19187 SoftWrap::GitDiff => return,
19188 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19189 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19190 language_settings::SoftWrap::None
19191 }
19192 };
19193 self.soft_wrap_mode_override = Some(soft_wrap);
19194 }
19195 cx.notify();
19196 }
19197
19198 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19199 let Some(workspace) = self.workspace() else {
19200 return;
19201 };
19202 let fs = workspace.read(cx).app_state().fs.clone();
19203 let current_show = TabBarSettings::get_global(cx).show;
19204 update_settings_file(fs, cx, move |setting, _| {
19205 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19206 });
19207 }
19208
19209 pub fn toggle_indent_guides(
19210 &mut self,
19211 _: &ToggleIndentGuides,
19212 _: &mut Window,
19213 cx: &mut Context<Self>,
19214 ) {
19215 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19216 self.buffer
19217 .read(cx)
19218 .language_settings(cx)
19219 .indent_guides
19220 .enabled
19221 });
19222 self.show_indent_guides = Some(!currently_enabled);
19223 cx.notify();
19224 }
19225
19226 fn should_show_indent_guides(&self) -> Option<bool> {
19227 self.show_indent_guides
19228 }
19229
19230 pub fn toggle_line_numbers(
19231 &mut self,
19232 _: &ToggleLineNumbers,
19233 _: &mut Window,
19234 cx: &mut Context<Self>,
19235 ) {
19236 let mut editor_settings = EditorSettings::get_global(cx).clone();
19237 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19238 EditorSettings::override_global(editor_settings, cx);
19239 }
19240
19241 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19242 if let Some(show_line_numbers) = self.show_line_numbers {
19243 return show_line_numbers;
19244 }
19245 EditorSettings::get_global(cx).gutter.line_numbers
19246 }
19247
19248 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19249 self.use_relative_line_numbers
19250 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19251 }
19252
19253 pub fn toggle_relative_line_numbers(
19254 &mut self,
19255 _: &ToggleRelativeLineNumbers,
19256 _: &mut Window,
19257 cx: &mut Context<Self>,
19258 ) {
19259 let is_relative = self.should_use_relative_line_numbers(cx);
19260 self.set_relative_line_number(Some(!is_relative), cx)
19261 }
19262
19263 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19264 self.use_relative_line_numbers = is_relative;
19265 cx.notify();
19266 }
19267
19268 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19269 self.show_gutter = show_gutter;
19270 cx.notify();
19271 }
19272
19273 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19274 self.show_scrollbars = ScrollbarAxes {
19275 horizontal: show,
19276 vertical: show,
19277 };
19278 cx.notify();
19279 }
19280
19281 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19282 self.show_scrollbars.vertical = show;
19283 cx.notify();
19284 }
19285
19286 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19287 self.show_scrollbars.horizontal = show;
19288 cx.notify();
19289 }
19290
19291 pub fn set_minimap_visibility(
19292 &mut self,
19293 minimap_visibility: MinimapVisibility,
19294 window: &mut Window,
19295 cx: &mut Context<Self>,
19296 ) {
19297 if self.minimap_visibility != minimap_visibility {
19298 if minimap_visibility.visible() && self.minimap.is_none() {
19299 let minimap_settings = EditorSettings::get_global(cx).minimap;
19300 self.minimap =
19301 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19302 }
19303 self.minimap_visibility = minimap_visibility;
19304 cx.notify();
19305 }
19306 }
19307
19308 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19309 self.set_show_scrollbars(false, cx);
19310 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19311 }
19312
19313 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19314 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19315 }
19316
19317 /// Normally the text in full mode and auto height editors is padded on the
19318 /// left side by roughly half a character width for improved hit testing.
19319 ///
19320 /// Use this method to disable this for cases where this is not wanted (e.g.
19321 /// if you want to align the editor text with some other text above or below)
19322 /// or if you want to add this padding to single-line editors.
19323 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19324 self.offset_content = offset_content;
19325 cx.notify();
19326 }
19327
19328 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19329 self.show_line_numbers = Some(show_line_numbers);
19330 cx.notify();
19331 }
19332
19333 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19334 self.disable_expand_excerpt_buttons = true;
19335 cx.notify();
19336 }
19337
19338 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19339 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19340 cx.notify();
19341 }
19342
19343 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19344 self.show_code_actions = Some(show_code_actions);
19345 cx.notify();
19346 }
19347
19348 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19349 self.show_runnables = Some(show_runnables);
19350 cx.notify();
19351 }
19352
19353 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19354 self.show_breakpoints = Some(show_breakpoints);
19355 cx.notify();
19356 }
19357
19358 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19359 if self.display_map.read(cx).masked != masked {
19360 self.display_map.update(cx, |map, _| map.masked = masked);
19361 }
19362 cx.notify()
19363 }
19364
19365 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19366 self.show_wrap_guides = Some(show_wrap_guides);
19367 cx.notify();
19368 }
19369
19370 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19371 self.show_indent_guides = Some(show_indent_guides);
19372 cx.notify();
19373 }
19374
19375 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19376 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19377 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19378 && let Some(dir) = file.abs_path(cx).parent()
19379 {
19380 return Some(dir.to_owned());
19381 }
19382 }
19383
19384 None
19385 }
19386
19387 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19388 self.active_excerpt(cx)?
19389 .1
19390 .read(cx)
19391 .file()
19392 .and_then(|f| f.as_local())
19393 }
19394
19395 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19396 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19397 let buffer = buffer.read(cx);
19398 if let Some(project_path) = buffer.project_path(cx) {
19399 let project = self.project()?.read(cx);
19400 project.absolute_path(&project_path, cx)
19401 } else {
19402 buffer
19403 .file()
19404 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19405 }
19406 })
19407 }
19408
19409 pub fn reveal_in_finder(
19410 &mut self,
19411 _: &RevealInFileManager,
19412 _window: &mut Window,
19413 cx: &mut Context<Self>,
19414 ) {
19415 if let Some(target) = self.target_file(cx) {
19416 cx.reveal_path(&target.abs_path(cx));
19417 }
19418 }
19419
19420 pub fn copy_path(
19421 &mut self,
19422 _: &zed_actions::workspace::CopyPath,
19423 _window: &mut Window,
19424 cx: &mut Context<Self>,
19425 ) {
19426 if let Some(path) = self.target_file_abs_path(cx)
19427 && let Some(path) = path.to_str()
19428 {
19429 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19430 } else {
19431 cx.propagate();
19432 }
19433 }
19434
19435 pub fn copy_relative_path(
19436 &mut self,
19437 _: &zed_actions::workspace::CopyRelativePath,
19438 _window: &mut Window,
19439 cx: &mut Context<Self>,
19440 ) {
19441 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19442 let project = self.project()?.read(cx);
19443 let path = buffer.read(cx).file()?.path();
19444 let path = path.display(project.path_style(cx));
19445 Some(path)
19446 }) {
19447 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19448 } else {
19449 cx.propagate();
19450 }
19451 }
19452
19453 /// Returns the project path for the editor's buffer, if any buffer is
19454 /// opened in the editor.
19455 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19456 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19457 buffer.read(cx).project_path(cx)
19458 } else {
19459 None
19460 }
19461 }
19462
19463 // Returns true if the editor handled a go-to-line request
19464 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19465 maybe!({
19466 let breakpoint_store = self.breakpoint_store.as_ref()?;
19467
19468 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19469 else {
19470 self.clear_row_highlights::<ActiveDebugLine>();
19471 return None;
19472 };
19473
19474 let position = active_stack_frame.position;
19475 let buffer_id = position.buffer_id?;
19476 let snapshot = self
19477 .project
19478 .as_ref()?
19479 .read(cx)
19480 .buffer_for_id(buffer_id, cx)?
19481 .read(cx)
19482 .snapshot();
19483
19484 let mut handled = false;
19485 for (id, ExcerptRange { context, .. }) in
19486 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19487 {
19488 if context.start.cmp(&position, &snapshot).is_ge()
19489 || context.end.cmp(&position, &snapshot).is_lt()
19490 {
19491 continue;
19492 }
19493 let snapshot = self.buffer.read(cx).snapshot(cx);
19494 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19495
19496 handled = true;
19497 self.clear_row_highlights::<ActiveDebugLine>();
19498
19499 self.go_to_line::<ActiveDebugLine>(
19500 multibuffer_anchor,
19501 Some(cx.theme().colors().editor_debugger_active_line_background),
19502 window,
19503 cx,
19504 );
19505
19506 cx.notify();
19507 }
19508
19509 handled.then_some(())
19510 })
19511 .is_some()
19512 }
19513
19514 pub fn copy_file_name_without_extension(
19515 &mut self,
19516 _: &CopyFileNameWithoutExtension,
19517 _: &mut Window,
19518 cx: &mut Context<Self>,
19519 ) {
19520 if let Some(file) = self.target_file(cx)
19521 && let Some(file_stem) = file.path().file_stem()
19522 {
19523 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19524 }
19525 }
19526
19527 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19528 if let Some(file) = self.target_file(cx)
19529 && let Some(name) = file.path().file_name()
19530 {
19531 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19532 }
19533 }
19534
19535 pub fn toggle_git_blame(
19536 &mut self,
19537 _: &::git::Blame,
19538 window: &mut Window,
19539 cx: &mut Context<Self>,
19540 ) {
19541 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19542
19543 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19544 self.start_git_blame(true, window, cx);
19545 }
19546
19547 cx.notify();
19548 }
19549
19550 pub fn toggle_git_blame_inline(
19551 &mut self,
19552 _: &ToggleGitBlameInline,
19553 window: &mut Window,
19554 cx: &mut Context<Self>,
19555 ) {
19556 self.toggle_git_blame_inline_internal(true, window, cx);
19557 cx.notify();
19558 }
19559
19560 pub fn open_git_blame_commit(
19561 &mut self,
19562 _: &OpenGitBlameCommit,
19563 window: &mut Window,
19564 cx: &mut Context<Self>,
19565 ) {
19566 self.open_git_blame_commit_internal(window, cx);
19567 }
19568
19569 fn open_git_blame_commit_internal(
19570 &mut self,
19571 window: &mut Window,
19572 cx: &mut Context<Self>,
19573 ) -> Option<()> {
19574 let blame = self.blame.as_ref()?;
19575 let snapshot = self.snapshot(window, cx);
19576 let cursor = self.selections.newest::<Point>(cx).head();
19577 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19578 let (_, blame_entry) = blame
19579 .update(cx, |blame, cx| {
19580 blame
19581 .blame_for_rows(
19582 &[RowInfo {
19583 buffer_id: Some(buffer.remote_id()),
19584 buffer_row: Some(point.row),
19585 ..Default::default()
19586 }],
19587 cx,
19588 )
19589 .next()
19590 })
19591 .flatten()?;
19592 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19593 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19594 let workspace = self.workspace()?.downgrade();
19595 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19596 None
19597 }
19598
19599 pub fn git_blame_inline_enabled(&self) -> bool {
19600 self.git_blame_inline_enabled
19601 }
19602
19603 pub fn toggle_selection_menu(
19604 &mut self,
19605 _: &ToggleSelectionMenu,
19606 _: &mut Window,
19607 cx: &mut Context<Self>,
19608 ) {
19609 self.show_selection_menu = self
19610 .show_selection_menu
19611 .map(|show_selections_menu| !show_selections_menu)
19612 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19613
19614 cx.notify();
19615 }
19616
19617 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19618 self.show_selection_menu
19619 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19620 }
19621
19622 fn start_git_blame(
19623 &mut self,
19624 user_triggered: bool,
19625 window: &mut Window,
19626 cx: &mut Context<Self>,
19627 ) {
19628 if let Some(project) = self.project() {
19629 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19630 && buffer.read(cx).file().is_none()
19631 {
19632 return;
19633 }
19634
19635 let focused = self.focus_handle(cx).contains_focused(window, cx);
19636
19637 let project = project.clone();
19638 let blame = cx
19639 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19640 self.blame_subscription =
19641 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19642 self.blame = Some(blame);
19643 }
19644 }
19645
19646 fn toggle_git_blame_inline_internal(
19647 &mut self,
19648 user_triggered: bool,
19649 window: &mut Window,
19650 cx: &mut Context<Self>,
19651 ) {
19652 if self.git_blame_inline_enabled {
19653 self.git_blame_inline_enabled = false;
19654 self.show_git_blame_inline = false;
19655 self.show_git_blame_inline_delay_task.take();
19656 } else {
19657 self.git_blame_inline_enabled = true;
19658 self.start_git_blame_inline(user_triggered, window, cx);
19659 }
19660
19661 cx.notify();
19662 }
19663
19664 fn start_git_blame_inline(
19665 &mut self,
19666 user_triggered: bool,
19667 window: &mut Window,
19668 cx: &mut Context<Self>,
19669 ) {
19670 self.start_git_blame(user_triggered, window, cx);
19671
19672 if ProjectSettings::get_global(cx)
19673 .git
19674 .inline_blame_delay()
19675 .is_some()
19676 {
19677 self.start_inline_blame_timer(window, cx);
19678 } else {
19679 self.show_git_blame_inline = true
19680 }
19681 }
19682
19683 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19684 self.blame.as_ref()
19685 }
19686
19687 pub fn show_git_blame_gutter(&self) -> bool {
19688 self.show_git_blame_gutter
19689 }
19690
19691 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19692 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19693 }
19694
19695 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19696 self.show_git_blame_inline
19697 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19698 && !self.newest_selection_head_on_empty_line(cx)
19699 && self.has_blame_entries(cx)
19700 }
19701
19702 fn has_blame_entries(&self, cx: &App) -> bool {
19703 self.blame()
19704 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19705 }
19706
19707 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19708 let cursor_anchor = self.selections.newest_anchor().head();
19709
19710 let snapshot = self.buffer.read(cx).snapshot(cx);
19711 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19712
19713 snapshot.line_len(buffer_row) == 0
19714 }
19715
19716 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19717 let buffer_and_selection = maybe!({
19718 let selection = self.selections.newest::<Point>(cx);
19719 let selection_range = selection.range();
19720
19721 let multi_buffer = self.buffer().read(cx);
19722 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19723 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19724
19725 let (buffer, range, _) = if selection.reversed {
19726 buffer_ranges.first()
19727 } else {
19728 buffer_ranges.last()
19729 }?;
19730
19731 let selection = text::ToPoint::to_point(&range.start, buffer).row
19732 ..text::ToPoint::to_point(&range.end, buffer).row;
19733 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19734 });
19735
19736 let Some((buffer, selection)) = buffer_and_selection else {
19737 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19738 };
19739
19740 let Some(project) = self.project() else {
19741 return Task::ready(Err(anyhow!("editor does not have project")));
19742 };
19743
19744 project.update(cx, |project, cx| {
19745 project.get_permalink_to_line(&buffer, selection, cx)
19746 })
19747 }
19748
19749 pub fn copy_permalink_to_line(
19750 &mut self,
19751 _: &CopyPermalinkToLine,
19752 window: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 let permalink_task = self.get_permalink_to_line(cx);
19756 let workspace = self.workspace();
19757
19758 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19759 Ok(permalink) => {
19760 cx.update(|_, cx| {
19761 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19762 })
19763 .ok();
19764 }
19765 Err(err) => {
19766 let message = format!("Failed to copy permalink: {err}");
19767
19768 anyhow::Result::<()>::Err(err).log_err();
19769
19770 if let Some(workspace) = workspace {
19771 workspace
19772 .update_in(cx, |workspace, _, cx| {
19773 struct CopyPermalinkToLine;
19774
19775 workspace.show_toast(
19776 Toast::new(
19777 NotificationId::unique::<CopyPermalinkToLine>(),
19778 message,
19779 ),
19780 cx,
19781 )
19782 })
19783 .ok();
19784 }
19785 }
19786 })
19787 .detach();
19788 }
19789
19790 pub fn copy_file_location(
19791 &mut self,
19792 _: &CopyFileLocation,
19793 _: &mut Window,
19794 cx: &mut Context<Self>,
19795 ) {
19796 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19797 if let Some(file) = self.target_file(cx) {
19798 let path = file.path().display(file.path_style(cx));
19799 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19800 }
19801 }
19802
19803 pub fn open_permalink_to_line(
19804 &mut self,
19805 _: &OpenPermalinkToLine,
19806 window: &mut Window,
19807 cx: &mut Context<Self>,
19808 ) {
19809 let permalink_task = self.get_permalink_to_line(cx);
19810 let workspace = self.workspace();
19811
19812 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19813 Ok(permalink) => {
19814 cx.update(|_, cx| {
19815 cx.open_url(permalink.as_ref());
19816 })
19817 .ok();
19818 }
19819 Err(err) => {
19820 let message = format!("Failed to open permalink: {err}");
19821
19822 anyhow::Result::<()>::Err(err).log_err();
19823
19824 if let Some(workspace) = workspace {
19825 workspace
19826 .update(cx, |workspace, cx| {
19827 struct OpenPermalinkToLine;
19828
19829 workspace.show_toast(
19830 Toast::new(
19831 NotificationId::unique::<OpenPermalinkToLine>(),
19832 message,
19833 ),
19834 cx,
19835 )
19836 })
19837 .ok();
19838 }
19839 }
19840 })
19841 .detach();
19842 }
19843
19844 pub fn insert_uuid_v4(
19845 &mut self,
19846 _: &InsertUuidV4,
19847 window: &mut Window,
19848 cx: &mut Context<Self>,
19849 ) {
19850 self.insert_uuid(UuidVersion::V4, window, cx);
19851 }
19852
19853 pub fn insert_uuid_v7(
19854 &mut self,
19855 _: &InsertUuidV7,
19856 window: &mut Window,
19857 cx: &mut Context<Self>,
19858 ) {
19859 self.insert_uuid(UuidVersion::V7, window, cx);
19860 }
19861
19862 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19864 self.transact(window, cx, |this, window, cx| {
19865 let edits = this
19866 .selections
19867 .all::<Point>(cx)
19868 .into_iter()
19869 .map(|selection| {
19870 let uuid = match version {
19871 UuidVersion::V4 => uuid::Uuid::new_v4(),
19872 UuidVersion::V7 => uuid::Uuid::now_v7(),
19873 };
19874
19875 (selection.range(), uuid.to_string())
19876 });
19877 this.edit(edits, cx);
19878 this.refresh_edit_prediction(true, false, window, cx);
19879 });
19880 }
19881
19882 pub fn open_selections_in_multibuffer(
19883 &mut self,
19884 _: &OpenSelectionsInMultibuffer,
19885 window: &mut Window,
19886 cx: &mut Context<Self>,
19887 ) {
19888 let multibuffer = self.buffer.read(cx);
19889
19890 let Some(buffer) = multibuffer.as_singleton() else {
19891 return;
19892 };
19893
19894 let Some(workspace) = self.workspace() else {
19895 return;
19896 };
19897
19898 let title = multibuffer.title(cx).to_string();
19899
19900 let locations = self
19901 .selections
19902 .all_anchors(cx)
19903 .iter()
19904 .map(|selection| {
19905 (
19906 buffer.clone(),
19907 (selection.start.text_anchor..selection.end.text_anchor)
19908 .to_point(buffer.read(cx)),
19909 )
19910 })
19911 .into_group_map();
19912
19913 cx.spawn_in(window, async move |_, cx| {
19914 workspace.update_in(cx, |workspace, window, cx| {
19915 Self::open_locations_in_multibuffer(
19916 workspace,
19917 locations,
19918 format!("Selections for '{title}'"),
19919 false,
19920 MultibufferSelectionMode::All,
19921 window,
19922 cx,
19923 );
19924 })
19925 })
19926 .detach();
19927 }
19928
19929 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19930 /// last highlight added will be used.
19931 ///
19932 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19933 pub fn highlight_rows<T: 'static>(
19934 &mut self,
19935 range: Range<Anchor>,
19936 color: Hsla,
19937 options: RowHighlightOptions,
19938 cx: &mut Context<Self>,
19939 ) {
19940 let snapshot = self.buffer().read(cx).snapshot(cx);
19941 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19942 let ix = row_highlights.binary_search_by(|highlight| {
19943 Ordering::Equal
19944 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19945 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19946 });
19947
19948 if let Err(mut ix) = ix {
19949 let index = post_inc(&mut self.highlight_order);
19950
19951 // If this range intersects with the preceding highlight, then merge it with
19952 // the preceding highlight. Otherwise insert a new highlight.
19953 let mut merged = false;
19954 if ix > 0 {
19955 let prev_highlight = &mut row_highlights[ix - 1];
19956 if prev_highlight
19957 .range
19958 .end
19959 .cmp(&range.start, &snapshot)
19960 .is_ge()
19961 {
19962 ix -= 1;
19963 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19964 prev_highlight.range.end = range.end;
19965 }
19966 merged = true;
19967 prev_highlight.index = index;
19968 prev_highlight.color = color;
19969 prev_highlight.options = options;
19970 }
19971 }
19972
19973 if !merged {
19974 row_highlights.insert(
19975 ix,
19976 RowHighlight {
19977 range,
19978 index,
19979 color,
19980 options,
19981 type_id: TypeId::of::<T>(),
19982 },
19983 );
19984 }
19985
19986 // If any of the following highlights intersect with this one, merge them.
19987 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19988 let highlight = &row_highlights[ix];
19989 if next_highlight
19990 .range
19991 .start
19992 .cmp(&highlight.range.end, &snapshot)
19993 .is_le()
19994 {
19995 if next_highlight
19996 .range
19997 .end
19998 .cmp(&highlight.range.end, &snapshot)
19999 .is_gt()
20000 {
20001 row_highlights[ix].range.end = next_highlight.range.end;
20002 }
20003 row_highlights.remove(ix + 1);
20004 } else {
20005 break;
20006 }
20007 }
20008 }
20009 }
20010
20011 /// Remove any highlighted row ranges of the given type that intersect the
20012 /// given ranges.
20013 pub fn remove_highlighted_rows<T: 'static>(
20014 &mut self,
20015 ranges_to_remove: Vec<Range<Anchor>>,
20016 cx: &mut Context<Self>,
20017 ) {
20018 let snapshot = self.buffer().read(cx).snapshot(cx);
20019 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20020 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20021 row_highlights.retain(|highlight| {
20022 while let Some(range_to_remove) = ranges_to_remove.peek() {
20023 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20024 Ordering::Less | Ordering::Equal => {
20025 ranges_to_remove.next();
20026 }
20027 Ordering::Greater => {
20028 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20029 Ordering::Less | Ordering::Equal => {
20030 return false;
20031 }
20032 Ordering::Greater => break,
20033 }
20034 }
20035 }
20036 }
20037
20038 true
20039 })
20040 }
20041
20042 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20043 pub fn clear_row_highlights<T: 'static>(&mut self) {
20044 self.highlighted_rows.remove(&TypeId::of::<T>());
20045 }
20046
20047 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20048 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20049 self.highlighted_rows
20050 .get(&TypeId::of::<T>())
20051 .map_or(&[] as &[_], |vec| vec.as_slice())
20052 .iter()
20053 .map(|highlight| (highlight.range.clone(), highlight.color))
20054 }
20055
20056 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20057 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20058 /// Allows to ignore certain kinds of highlights.
20059 pub fn highlighted_display_rows(
20060 &self,
20061 window: &mut Window,
20062 cx: &mut App,
20063 ) -> BTreeMap<DisplayRow, LineHighlight> {
20064 let snapshot = self.snapshot(window, cx);
20065 let mut used_highlight_orders = HashMap::default();
20066 self.highlighted_rows
20067 .iter()
20068 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20069 .fold(
20070 BTreeMap::<DisplayRow, LineHighlight>::new(),
20071 |mut unique_rows, highlight| {
20072 let start = highlight.range.start.to_display_point(&snapshot);
20073 let end = highlight.range.end.to_display_point(&snapshot);
20074 let start_row = start.row().0;
20075 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20076 && end.column() == 0
20077 {
20078 end.row().0.saturating_sub(1)
20079 } else {
20080 end.row().0
20081 };
20082 for row in start_row..=end_row {
20083 let used_index =
20084 used_highlight_orders.entry(row).or_insert(highlight.index);
20085 if highlight.index >= *used_index {
20086 *used_index = highlight.index;
20087 unique_rows.insert(
20088 DisplayRow(row),
20089 LineHighlight {
20090 include_gutter: highlight.options.include_gutter,
20091 border: None,
20092 background: highlight.color.into(),
20093 type_id: Some(highlight.type_id),
20094 },
20095 );
20096 }
20097 }
20098 unique_rows
20099 },
20100 )
20101 }
20102
20103 pub fn highlighted_display_row_for_autoscroll(
20104 &self,
20105 snapshot: &DisplaySnapshot,
20106 ) -> Option<DisplayRow> {
20107 self.highlighted_rows
20108 .values()
20109 .flat_map(|highlighted_rows| highlighted_rows.iter())
20110 .filter_map(|highlight| {
20111 if highlight.options.autoscroll {
20112 Some(highlight.range.start.to_display_point(snapshot).row())
20113 } else {
20114 None
20115 }
20116 })
20117 .min()
20118 }
20119
20120 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20121 self.highlight_background::<SearchWithinRange>(
20122 ranges,
20123 |colors| colors.colors().editor_document_highlight_read_background,
20124 cx,
20125 )
20126 }
20127
20128 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20129 self.breadcrumb_header = Some(new_header);
20130 }
20131
20132 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20133 self.clear_background_highlights::<SearchWithinRange>(cx);
20134 }
20135
20136 pub fn highlight_background<T: 'static>(
20137 &mut self,
20138 ranges: &[Range<Anchor>],
20139 color_fetcher: fn(&Theme) -> Hsla,
20140 cx: &mut Context<Self>,
20141 ) {
20142 self.background_highlights.insert(
20143 HighlightKey::Type(TypeId::of::<T>()),
20144 (color_fetcher, Arc::from(ranges)),
20145 );
20146 self.scrollbar_marker_state.dirty = true;
20147 cx.notify();
20148 }
20149
20150 pub fn highlight_background_key<T: 'static>(
20151 &mut self,
20152 key: usize,
20153 ranges: &[Range<Anchor>],
20154 color_fetcher: fn(&Theme) -> Hsla,
20155 cx: &mut Context<Self>,
20156 ) {
20157 self.background_highlights.insert(
20158 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20159 (color_fetcher, Arc::from(ranges)),
20160 );
20161 self.scrollbar_marker_state.dirty = true;
20162 cx.notify();
20163 }
20164
20165 pub fn clear_background_highlights<T: 'static>(
20166 &mut self,
20167 cx: &mut Context<Self>,
20168 ) -> Option<BackgroundHighlight> {
20169 let text_highlights = self
20170 .background_highlights
20171 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20172 if !text_highlights.1.is_empty() {
20173 self.scrollbar_marker_state.dirty = true;
20174 cx.notify();
20175 }
20176 Some(text_highlights)
20177 }
20178
20179 pub fn highlight_gutter<T: 'static>(
20180 &mut self,
20181 ranges: impl Into<Vec<Range<Anchor>>>,
20182 color_fetcher: fn(&App) -> Hsla,
20183 cx: &mut Context<Self>,
20184 ) {
20185 self.gutter_highlights
20186 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20187 cx.notify();
20188 }
20189
20190 pub fn clear_gutter_highlights<T: 'static>(
20191 &mut self,
20192 cx: &mut Context<Self>,
20193 ) -> Option<GutterHighlight> {
20194 cx.notify();
20195 self.gutter_highlights.remove(&TypeId::of::<T>())
20196 }
20197
20198 pub fn insert_gutter_highlight<T: 'static>(
20199 &mut self,
20200 range: Range<Anchor>,
20201 color_fetcher: fn(&App) -> Hsla,
20202 cx: &mut Context<Self>,
20203 ) {
20204 let snapshot = self.buffer().read(cx).snapshot(cx);
20205 let mut highlights = self
20206 .gutter_highlights
20207 .remove(&TypeId::of::<T>())
20208 .map(|(_, highlights)| highlights)
20209 .unwrap_or_default();
20210 let ix = highlights.binary_search_by(|highlight| {
20211 Ordering::Equal
20212 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20213 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20214 });
20215 if let Err(ix) = ix {
20216 highlights.insert(ix, range);
20217 }
20218 self.gutter_highlights
20219 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20220 }
20221
20222 pub fn remove_gutter_highlights<T: 'static>(
20223 &mut self,
20224 ranges_to_remove: Vec<Range<Anchor>>,
20225 cx: &mut Context<Self>,
20226 ) {
20227 let snapshot = self.buffer().read(cx).snapshot(cx);
20228 let Some((color_fetcher, mut gutter_highlights)) =
20229 self.gutter_highlights.remove(&TypeId::of::<T>())
20230 else {
20231 return;
20232 };
20233 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20234 gutter_highlights.retain(|highlight| {
20235 while let Some(range_to_remove) = ranges_to_remove.peek() {
20236 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20237 Ordering::Less | Ordering::Equal => {
20238 ranges_to_remove.next();
20239 }
20240 Ordering::Greater => {
20241 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20242 Ordering::Less | Ordering::Equal => {
20243 return false;
20244 }
20245 Ordering::Greater => break,
20246 }
20247 }
20248 }
20249 }
20250
20251 true
20252 });
20253 self.gutter_highlights
20254 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20255 }
20256
20257 #[cfg(feature = "test-support")]
20258 pub fn all_text_highlights(
20259 &self,
20260 window: &mut Window,
20261 cx: &mut Context<Self>,
20262 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20263 let snapshot = self.snapshot(window, cx);
20264 self.display_map.update(cx, |display_map, _| {
20265 display_map
20266 .all_text_highlights()
20267 .map(|highlight| {
20268 let (style, ranges) = highlight.as_ref();
20269 (
20270 *style,
20271 ranges
20272 .iter()
20273 .map(|range| range.clone().to_display_points(&snapshot))
20274 .collect(),
20275 )
20276 })
20277 .collect()
20278 })
20279 }
20280
20281 #[cfg(feature = "test-support")]
20282 pub fn all_text_background_highlights(
20283 &self,
20284 window: &mut Window,
20285 cx: &mut Context<Self>,
20286 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20287 let snapshot = self.snapshot(window, cx);
20288 let buffer = &snapshot.buffer_snapshot;
20289 let start = buffer.anchor_before(0);
20290 let end = buffer.anchor_after(buffer.len());
20291 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20292 }
20293
20294 #[cfg(any(test, feature = "test-support"))]
20295 pub fn sorted_background_highlights_in_range(
20296 &self,
20297 search_range: Range<Anchor>,
20298 display_snapshot: &DisplaySnapshot,
20299 theme: &Theme,
20300 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20301 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20302 res.sort_by(|a, b| {
20303 a.0.start
20304 .cmp(&b.0.start)
20305 .then_with(|| a.0.end.cmp(&b.0.end))
20306 .then_with(|| a.1.cmp(&b.1))
20307 });
20308 res
20309 }
20310
20311 #[cfg(feature = "test-support")]
20312 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20313 let snapshot = self.buffer().read(cx).snapshot(cx);
20314
20315 let highlights = self
20316 .background_highlights
20317 .get(&HighlightKey::Type(TypeId::of::<
20318 items::BufferSearchHighlights,
20319 >()));
20320
20321 if let Some((_color, ranges)) = highlights {
20322 ranges
20323 .iter()
20324 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20325 .collect_vec()
20326 } else {
20327 vec![]
20328 }
20329 }
20330
20331 fn document_highlights_for_position<'a>(
20332 &'a self,
20333 position: Anchor,
20334 buffer: &'a MultiBufferSnapshot,
20335 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20336 let read_highlights = self
20337 .background_highlights
20338 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20339 .map(|h| &h.1);
20340 let write_highlights = self
20341 .background_highlights
20342 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20343 .map(|h| &h.1);
20344 let left_position = position.bias_left(buffer);
20345 let right_position = position.bias_right(buffer);
20346 read_highlights
20347 .into_iter()
20348 .chain(write_highlights)
20349 .flat_map(move |ranges| {
20350 let start_ix = match ranges.binary_search_by(|probe| {
20351 let cmp = probe.end.cmp(&left_position, buffer);
20352 if cmp.is_ge() {
20353 Ordering::Greater
20354 } else {
20355 Ordering::Less
20356 }
20357 }) {
20358 Ok(i) | Err(i) => i,
20359 };
20360
20361 ranges[start_ix..]
20362 .iter()
20363 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20364 })
20365 }
20366
20367 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20368 self.background_highlights
20369 .get(&HighlightKey::Type(TypeId::of::<T>()))
20370 .is_some_and(|(_, highlights)| !highlights.is_empty())
20371 }
20372
20373 /// Returns all background highlights for a given range.
20374 ///
20375 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20376 pub fn background_highlights_in_range(
20377 &self,
20378 search_range: Range<Anchor>,
20379 display_snapshot: &DisplaySnapshot,
20380 theme: &Theme,
20381 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20382 let mut results = Vec::new();
20383 for (color_fetcher, ranges) in self.background_highlights.values() {
20384 let color = color_fetcher(theme);
20385 let start_ix = match ranges.binary_search_by(|probe| {
20386 let cmp = probe
20387 .end
20388 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20389 if cmp.is_gt() {
20390 Ordering::Greater
20391 } else {
20392 Ordering::Less
20393 }
20394 }) {
20395 Ok(i) | Err(i) => i,
20396 };
20397 for range in &ranges[start_ix..] {
20398 if range
20399 .start
20400 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20401 .is_ge()
20402 {
20403 break;
20404 }
20405
20406 let start = range.start.to_display_point(display_snapshot);
20407 let end = range.end.to_display_point(display_snapshot);
20408 results.push((start..end, color))
20409 }
20410 }
20411 results
20412 }
20413
20414 pub fn gutter_highlights_in_range(
20415 &self,
20416 search_range: Range<Anchor>,
20417 display_snapshot: &DisplaySnapshot,
20418 cx: &App,
20419 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20420 let mut results = Vec::new();
20421 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20422 let color = color_fetcher(cx);
20423 let start_ix = match ranges.binary_search_by(|probe| {
20424 let cmp = probe
20425 .end
20426 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20427 if cmp.is_gt() {
20428 Ordering::Greater
20429 } else {
20430 Ordering::Less
20431 }
20432 }) {
20433 Ok(i) | Err(i) => i,
20434 };
20435 for range in &ranges[start_ix..] {
20436 if range
20437 .start
20438 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20439 .is_ge()
20440 {
20441 break;
20442 }
20443
20444 let start = range.start.to_display_point(display_snapshot);
20445 let end = range.end.to_display_point(display_snapshot);
20446 results.push((start..end, color))
20447 }
20448 }
20449 results
20450 }
20451
20452 /// Get the text ranges corresponding to the redaction query
20453 pub fn redacted_ranges(
20454 &self,
20455 search_range: Range<Anchor>,
20456 display_snapshot: &DisplaySnapshot,
20457 cx: &App,
20458 ) -> Vec<Range<DisplayPoint>> {
20459 display_snapshot
20460 .buffer_snapshot
20461 .redacted_ranges(search_range, |file| {
20462 if let Some(file) = file {
20463 file.is_private()
20464 && EditorSettings::get(
20465 Some(SettingsLocation {
20466 worktree_id: file.worktree_id(cx),
20467 path: file.path().as_ref(),
20468 }),
20469 cx,
20470 )
20471 .redact_private_values
20472 } else {
20473 false
20474 }
20475 })
20476 .map(|range| {
20477 range.start.to_display_point(display_snapshot)
20478 ..range.end.to_display_point(display_snapshot)
20479 })
20480 .collect()
20481 }
20482
20483 pub fn highlight_text_key<T: 'static>(
20484 &mut self,
20485 key: usize,
20486 ranges: Vec<Range<Anchor>>,
20487 style: HighlightStyle,
20488 cx: &mut Context<Self>,
20489 ) {
20490 self.display_map.update(cx, |map, _| {
20491 map.highlight_text(
20492 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20493 ranges,
20494 style,
20495 );
20496 });
20497 cx.notify();
20498 }
20499
20500 pub fn highlight_text<T: 'static>(
20501 &mut self,
20502 ranges: Vec<Range<Anchor>>,
20503 style: HighlightStyle,
20504 cx: &mut Context<Self>,
20505 ) {
20506 self.display_map.update(cx, |map, _| {
20507 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20508 });
20509 cx.notify();
20510 }
20511
20512 pub(crate) fn highlight_inlays<T: 'static>(
20513 &mut self,
20514 highlights: Vec<InlayHighlight>,
20515 style: HighlightStyle,
20516 cx: &mut Context<Self>,
20517 ) {
20518 self.display_map.update(cx, |map, _| {
20519 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20520 });
20521 cx.notify();
20522 }
20523
20524 pub fn text_highlights<'a, T: 'static>(
20525 &'a self,
20526 cx: &'a App,
20527 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20528 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20529 }
20530
20531 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20532 let cleared = self
20533 .display_map
20534 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20535 if cleared {
20536 cx.notify();
20537 }
20538 }
20539
20540 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20541 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20542 && self.focus_handle.is_focused(window)
20543 }
20544
20545 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20546 self.show_cursor_when_unfocused = is_enabled;
20547 cx.notify();
20548 }
20549
20550 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20551 cx.notify();
20552 }
20553
20554 fn on_debug_session_event(
20555 &mut self,
20556 _session: Entity<Session>,
20557 event: &SessionEvent,
20558 cx: &mut Context<Self>,
20559 ) {
20560 if let SessionEvent::InvalidateInlineValue = event {
20561 self.refresh_inline_values(cx);
20562 }
20563 }
20564
20565 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20566 let Some(project) = self.project.clone() else {
20567 return;
20568 };
20569
20570 if !self.inline_value_cache.enabled {
20571 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20572 self.splice_inlays(&inlays, Vec::new(), cx);
20573 return;
20574 }
20575
20576 let current_execution_position = self
20577 .highlighted_rows
20578 .get(&TypeId::of::<ActiveDebugLine>())
20579 .and_then(|lines| lines.last().map(|line| line.range.end));
20580
20581 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20582 let inline_values = editor
20583 .update(cx, |editor, cx| {
20584 let Some(current_execution_position) = current_execution_position else {
20585 return Some(Task::ready(Ok(Vec::new())));
20586 };
20587
20588 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20589 let snapshot = buffer.snapshot(cx);
20590
20591 let excerpt = snapshot.excerpt_containing(
20592 current_execution_position..current_execution_position,
20593 )?;
20594
20595 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20596 })?;
20597
20598 let range =
20599 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20600
20601 project.inline_values(buffer, range, cx)
20602 })
20603 .ok()
20604 .flatten()?
20605 .await
20606 .context("refreshing debugger inlays")
20607 .log_err()?;
20608
20609 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20610
20611 for (buffer_id, inline_value) in inline_values
20612 .into_iter()
20613 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20614 {
20615 buffer_inline_values
20616 .entry(buffer_id)
20617 .or_default()
20618 .push(inline_value);
20619 }
20620
20621 editor
20622 .update(cx, |editor, cx| {
20623 let snapshot = editor.buffer.read(cx).snapshot(cx);
20624 let mut new_inlays = Vec::default();
20625
20626 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20627 let buffer_id = buffer_snapshot.remote_id();
20628 buffer_inline_values
20629 .get(&buffer_id)
20630 .into_iter()
20631 .flatten()
20632 .for_each(|hint| {
20633 let inlay = Inlay::debugger(
20634 post_inc(&mut editor.next_inlay_id),
20635 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20636 hint.text(),
20637 );
20638 if !inlay.text().chars().contains(&'\n') {
20639 new_inlays.push(inlay);
20640 }
20641 });
20642 }
20643
20644 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20645 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20646
20647 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20648 })
20649 .ok()?;
20650 Some(())
20651 });
20652 }
20653
20654 fn on_buffer_event(
20655 &mut self,
20656 multibuffer: &Entity<MultiBuffer>,
20657 event: &multi_buffer::Event,
20658 window: &mut Window,
20659 cx: &mut Context<Self>,
20660 ) {
20661 match event {
20662 multi_buffer::Event::Edited {
20663 singleton_buffer_edited,
20664 edited_buffer,
20665 } => {
20666 self.scrollbar_marker_state.dirty = true;
20667 self.active_indent_guides_state.dirty = true;
20668 self.refresh_active_diagnostics(cx);
20669 self.refresh_code_actions(window, cx);
20670 self.refresh_selected_text_highlights(true, window, cx);
20671 self.refresh_single_line_folds(window, cx);
20672 refresh_matching_bracket_highlights(self, window, cx);
20673 if self.has_active_edit_prediction() {
20674 self.update_visible_edit_prediction(window, cx);
20675 }
20676 if let Some(project) = self.project.as_ref()
20677 && let Some(edited_buffer) = edited_buffer
20678 {
20679 project.update(cx, |project, cx| {
20680 self.registered_buffers
20681 .entry(edited_buffer.read(cx).remote_id())
20682 .or_insert_with(|| {
20683 project.register_buffer_with_language_servers(edited_buffer, cx)
20684 });
20685 });
20686 }
20687 cx.emit(EditorEvent::BufferEdited);
20688 cx.emit(SearchEvent::MatchesInvalidated);
20689
20690 if let Some(buffer) = edited_buffer {
20691 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20692 }
20693
20694 if *singleton_buffer_edited {
20695 if let Some(buffer) = edited_buffer
20696 && buffer.read(cx).file().is_none()
20697 {
20698 cx.emit(EditorEvent::TitleChanged);
20699 }
20700 if let Some(project) = &self.project {
20701 #[allow(clippy::mutable_key_type)]
20702 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20703 multibuffer
20704 .all_buffers()
20705 .into_iter()
20706 .filter_map(|buffer| {
20707 buffer.update(cx, |buffer, cx| {
20708 let language = buffer.language()?;
20709 let should_discard = project.update(cx, |project, cx| {
20710 project.is_local()
20711 && !project.has_language_servers_for(buffer, cx)
20712 });
20713 should_discard.not().then_some(language.clone())
20714 })
20715 })
20716 .collect::<HashSet<_>>()
20717 });
20718 if !languages_affected.is_empty() {
20719 self.refresh_inlay_hints(
20720 InlayHintRefreshReason::BufferEdited(languages_affected),
20721 cx,
20722 );
20723 }
20724 }
20725 }
20726
20727 let Some(project) = &self.project else { return };
20728 let (telemetry, is_via_ssh) = {
20729 let project = project.read(cx);
20730 let telemetry = project.client().telemetry().clone();
20731 let is_via_ssh = project.is_via_remote_server();
20732 (telemetry, is_via_ssh)
20733 };
20734 refresh_linked_ranges(self, window, cx);
20735 telemetry.log_edit_event("editor", is_via_ssh);
20736 }
20737 multi_buffer::Event::ExcerptsAdded {
20738 buffer,
20739 predecessor,
20740 excerpts,
20741 } => {
20742 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20743 let buffer_id = buffer.read(cx).remote_id();
20744 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20745 && let Some(project) = &self.project
20746 {
20747 update_uncommitted_diff_for_buffer(
20748 cx.entity(),
20749 project,
20750 [buffer.clone()],
20751 self.buffer.clone(),
20752 cx,
20753 )
20754 .detach();
20755 }
20756 if self.active_diagnostics != ActiveDiagnostic::All {
20757 self.update_lsp_data(false, Some(buffer_id), window, cx);
20758 }
20759 cx.emit(EditorEvent::ExcerptsAdded {
20760 buffer: buffer.clone(),
20761 predecessor: *predecessor,
20762 excerpts: excerpts.clone(),
20763 });
20764 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20765 }
20766 multi_buffer::Event::ExcerptsRemoved {
20767 ids,
20768 removed_buffer_ids,
20769 } => {
20770 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20771 let buffer = self.buffer.read(cx);
20772 self.registered_buffers
20773 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20774 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20775 cx.emit(EditorEvent::ExcerptsRemoved {
20776 ids: ids.clone(),
20777 removed_buffer_ids: removed_buffer_ids.clone(),
20778 });
20779 }
20780 multi_buffer::Event::ExcerptsEdited {
20781 excerpt_ids,
20782 buffer_ids,
20783 } => {
20784 self.display_map.update(cx, |map, cx| {
20785 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20786 });
20787 cx.emit(EditorEvent::ExcerptsEdited {
20788 ids: excerpt_ids.clone(),
20789 });
20790 }
20791 multi_buffer::Event::ExcerptsExpanded { ids } => {
20792 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20793 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20794 }
20795 multi_buffer::Event::Reparsed(buffer_id) => {
20796 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20797 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20798
20799 cx.emit(EditorEvent::Reparsed(*buffer_id));
20800 }
20801 multi_buffer::Event::DiffHunksToggled => {
20802 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20803 }
20804 multi_buffer::Event::LanguageChanged(buffer_id) => {
20805 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20806 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20807 cx.emit(EditorEvent::Reparsed(*buffer_id));
20808 cx.notify();
20809 }
20810 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20811 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20812 multi_buffer::Event::FileHandleChanged
20813 | multi_buffer::Event::Reloaded
20814 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20815 multi_buffer::Event::DiagnosticsUpdated => {
20816 self.update_diagnostics_state(window, cx);
20817 }
20818 _ => {}
20819 };
20820 }
20821
20822 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20823 if !self.diagnostics_enabled() {
20824 return;
20825 }
20826 self.refresh_active_diagnostics(cx);
20827 self.refresh_inline_diagnostics(true, window, cx);
20828 self.scrollbar_marker_state.dirty = true;
20829 cx.notify();
20830 }
20831
20832 pub fn start_temporary_diff_override(&mut self) {
20833 self.load_diff_task.take();
20834 self.temporary_diff_override = true;
20835 }
20836
20837 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20838 self.temporary_diff_override = false;
20839 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20840 self.buffer.update(cx, |buffer, cx| {
20841 buffer.set_all_diff_hunks_collapsed(cx);
20842 });
20843
20844 if let Some(project) = self.project.clone() {
20845 self.load_diff_task = Some(
20846 update_uncommitted_diff_for_buffer(
20847 cx.entity(),
20848 &project,
20849 self.buffer.read(cx).all_buffers(),
20850 self.buffer.clone(),
20851 cx,
20852 )
20853 .shared(),
20854 );
20855 }
20856 }
20857
20858 fn on_display_map_changed(
20859 &mut self,
20860 _: Entity<DisplayMap>,
20861 _: &mut Window,
20862 cx: &mut Context<Self>,
20863 ) {
20864 cx.notify();
20865 }
20866
20867 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20868 if self.diagnostics_enabled() {
20869 let new_severity = EditorSettings::get_global(cx)
20870 .diagnostics_max_severity
20871 .unwrap_or(DiagnosticSeverity::Hint);
20872 self.set_max_diagnostics_severity(new_severity, cx);
20873 }
20874 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20875 self.update_edit_prediction_settings(cx);
20876 self.refresh_edit_prediction(true, false, window, cx);
20877 self.refresh_inline_values(cx);
20878 self.refresh_inlay_hints(
20879 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20880 self.selections.newest_anchor().head(),
20881 &self.buffer.read(cx).snapshot(cx),
20882 cx,
20883 )),
20884 cx,
20885 );
20886
20887 let old_cursor_shape = self.cursor_shape;
20888 let old_show_breadcrumbs = self.show_breadcrumbs;
20889
20890 {
20891 let editor_settings = EditorSettings::get_global(cx);
20892 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20893 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20894 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20895 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20896 }
20897
20898 if old_cursor_shape != self.cursor_shape {
20899 cx.emit(EditorEvent::CursorShapeChanged);
20900 }
20901
20902 if old_show_breadcrumbs != self.show_breadcrumbs {
20903 cx.emit(EditorEvent::BreadcrumbsChanged);
20904 }
20905
20906 let project_settings = ProjectSettings::get_global(cx);
20907 self.serialize_dirty_buffers =
20908 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20909
20910 if self.mode.is_full() {
20911 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20912 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20913 if self.show_inline_diagnostics != show_inline_diagnostics {
20914 self.show_inline_diagnostics = show_inline_diagnostics;
20915 self.refresh_inline_diagnostics(false, window, cx);
20916 }
20917
20918 if self.git_blame_inline_enabled != inline_blame_enabled {
20919 self.toggle_git_blame_inline_internal(false, window, cx);
20920 }
20921
20922 let minimap_settings = EditorSettings::get_global(cx).minimap;
20923 if self.minimap_visibility != MinimapVisibility::Disabled {
20924 if self.minimap_visibility.settings_visibility()
20925 != minimap_settings.minimap_enabled()
20926 {
20927 self.set_minimap_visibility(
20928 MinimapVisibility::for_mode(self.mode(), cx),
20929 window,
20930 cx,
20931 );
20932 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20933 minimap_entity.update(cx, |minimap_editor, cx| {
20934 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20935 })
20936 }
20937 }
20938 }
20939
20940 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20941 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20942 }) {
20943 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20944 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20945 }
20946 self.refresh_colors(false, None, window, cx);
20947 }
20948
20949 cx.notify();
20950 }
20951
20952 pub fn set_searchable(&mut self, searchable: bool) {
20953 self.searchable = searchable;
20954 }
20955
20956 pub fn searchable(&self) -> bool {
20957 self.searchable
20958 }
20959
20960 fn open_proposed_changes_editor(
20961 &mut self,
20962 _: &OpenProposedChangesEditor,
20963 window: &mut Window,
20964 cx: &mut Context<Self>,
20965 ) {
20966 let Some(workspace) = self.workspace() else {
20967 cx.propagate();
20968 return;
20969 };
20970
20971 let selections = self.selections.all::<usize>(cx);
20972 let multi_buffer = self.buffer.read(cx);
20973 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20974 let mut new_selections_by_buffer = HashMap::default();
20975 for selection in selections {
20976 for (buffer, range, _) in
20977 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20978 {
20979 let mut range = range.to_point(buffer);
20980 range.start.column = 0;
20981 range.end.column = buffer.line_len(range.end.row);
20982 new_selections_by_buffer
20983 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20984 .or_insert(Vec::new())
20985 .push(range)
20986 }
20987 }
20988
20989 let proposed_changes_buffers = new_selections_by_buffer
20990 .into_iter()
20991 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20992 .collect::<Vec<_>>();
20993 let proposed_changes_editor = cx.new(|cx| {
20994 ProposedChangesEditor::new(
20995 "Proposed changes",
20996 proposed_changes_buffers,
20997 self.project.clone(),
20998 window,
20999 cx,
21000 )
21001 });
21002
21003 window.defer(cx, move |window, cx| {
21004 workspace.update(cx, |workspace, cx| {
21005 workspace.active_pane().update(cx, |pane, cx| {
21006 pane.add_item(
21007 Box::new(proposed_changes_editor),
21008 true,
21009 true,
21010 None,
21011 window,
21012 cx,
21013 );
21014 });
21015 });
21016 });
21017 }
21018
21019 pub fn open_excerpts_in_split(
21020 &mut self,
21021 _: &OpenExcerptsSplit,
21022 window: &mut Window,
21023 cx: &mut Context<Self>,
21024 ) {
21025 self.open_excerpts_common(None, true, window, cx)
21026 }
21027
21028 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21029 self.open_excerpts_common(None, false, window, cx)
21030 }
21031
21032 fn open_excerpts_common(
21033 &mut self,
21034 jump_data: Option<JumpData>,
21035 split: bool,
21036 window: &mut Window,
21037 cx: &mut Context<Self>,
21038 ) {
21039 let Some(workspace) = self.workspace() else {
21040 cx.propagate();
21041 return;
21042 };
21043
21044 if self.buffer.read(cx).is_singleton() {
21045 cx.propagate();
21046 return;
21047 }
21048
21049 let mut new_selections_by_buffer = HashMap::default();
21050 match &jump_data {
21051 Some(JumpData::MultiBufferPoint {
21052 excerpt_id,
21053 position,
21054 anchor,
21055 line_offset_from_top,
21056 }) => {
21057 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21058 if let Some(buffer) = multi_buffer_snapshot
21059 .buffer_id_for_excerpt(*excerpt_id)
21060 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21061 {
21062 let buffer_snapshot = buffer.read(cx).snapshot();
21063 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21064 language::ToPoint::to_point(anchor, &buffer_snapshot)
21065 } else {
21066 buffer_snapshot.clip_point(*position, Bias::Left)
21067 };
21068 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21069 new_selections_by_buffer.insert(
21070 buffer,
21071 (
21072 vec![jump_to_offset..jump_to_offset],
21073 Some(*line_offset_from_top),
21074 ),
21075 );
21076 }
21077 }
21078 Some(JumpData::MultiBufferRow {
21079 row,
21080 line_offset_from_top,
21081 }) => {
21082 let point = MultiBufferPoint::new(row.0, 0);
21083 if let Some((buffer, buffer_point, _)) =
21084 self.buffer.read(cx).point_to_buffer_point(point, cx)
21085 {
21086 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21087 new_selections_by_buffer
21088 .entry(buffer)
21089 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21090 .0
21091 .push(buffer_offset..buffer_offset)
21092 }
21093 }
21094 None => {
21095 let selections = self.selections.all::<usize>(cx);
21096 let multi_buffer = self.buffer.read(cx);
21097 for selection in selections {
21098 for (snapshot, range, _, anchor) in multi_buffer
21099 .snapshot(cx)
21100 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21101 {
21102 if let Some(anchor) = anchor {
21103 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21104 else {
21105 continue;
21106 };
21107 let offset = text::ToOffset::to_offset(
21108 &anchor.text_anchor,
21109 &buffer_handle.read(cx).snapshot(),
21110 );
21111 let range = offset..offset;
21112 new_selections_by_buffer
21113 .entry(buffer_handle)
21114 .or_insert((Vec::new(), None))
21115 .0
21116 .push(range)
21117 } else {
21118 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21119 else {
21120 continue;
21121 };
21122 new_selections_by_buffer
21123 .entry(buffer_handle)
21124 .or_insert((Vec::new(), None))
21125 .0
21126 .push(range)
21127 }
21128 }
21129 }
21130 }
21131 }
21132
21133 new_selections_by_buffer
21134 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21135
21136 if new_selections_by_buffer.is_empty() {
21137 return;
21138 }
21139
21140 // We defer the pane interaction because we ourselves are a workspace item
21141 // and activating a new item causes the pane to call a method on us reentrantly,
21142 // which panics if we're on the stack.
21143 window.defer(cx, move |window, cx| {
21144 workspace.update(cx, |workspace, cx| {
21145 let pane = if split {
21146 workspace.adjacent_pane(window, cx)
21147 } else {
21148 workspace.active_pane().clone()
21149 };
21150
21151 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21152 let editor = buffer
21153 .read(cx)
21154 .file()
21155 .is_none()
21156 .then(|| {
21157 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21158 // so `workspace.open_project_item` will never find them, always opening a new editor.
21159 // Instead, we try to activate the existing editor in the pane first.
21160 let (editor, pane_item_index) =
21161 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21162 let editor = item.downcast::<Editor>()?;
21163 let singleton_buffer =
21164 editor.read(cx).buffer().read(cx).as_singleton()?;
21165 if singleton_buffer == buffer {
21166 Some((editor, i))
21167 } else {
21168 None
21169 }
21170 })?;
21171 pane.update(cx, |pane, cx| {
21172 pane.activate_item(pane_item_index, true, true, window, cx)
21173 });
21174 Some(editor)
21175 })
21176 .flatten()
21177 .unwrap_or_else(|| {
21178 workspace.open_project_item::<Self>(
21179 pane.clone(),
21180 buffer,
21181 true,
21182 true,
21183 window,
21184 cx,
21185 )
21186 });
21187
21188 editor.update(cx, |editor, cx| {
21189 let autoscroll = match scroll_offset {
21190 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21191 None => Autoscroll::newest(),
21192 };
21193 let nav_history = editor.nav_history.take();
21194 editor.change_selections(
21195 SelectionEffects::scroll(autoscroll),
21196 window,
21197 cx,
21198 |s| {
21199 s.select_ranges(ranges);
21200 },
21201 );
21202 editor.nav_history = nav_history;
21203 });
21204 }
21205 })
21206 });
21207 }
21208
21209 // For now, don't allow opening excerpts in buffers that aren't backed by
21210 // regular project files.
21211 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21212 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21213 }
21214
21215 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21216 let snapshot = self.buffer.read(cx).read(cx);
21217 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21218 Some(
21219 ranges
21220 .iter()
21221 .map(move |range| {
21222 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21223 })
21224 .collect(),
21225 )
21226 }
21227
21228 fn selection_replacement_ranges(
21229 &self,
21230 range: Range<OffsetUtf16>,
21231 cx: &mut App,
21232 ) -> Vec<Range<OffsetUtf16>> {
21233 let selections = self.selections.all::<OffsetUtf16>(cx);
21234 let newest_selection = selections
21235 .iter()
21236 .max_by_key(|selection| selection.id)
21237 .unwrap();
21238 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21239 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21240 let snapshot = self.buffer.read(cx).read(cx);
21241 selections
21242 .into_iter()
21243 .map(|mut selection| {
21244 selection.start.0 =
21245 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21246 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21247 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21248 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21249 })
21250 .collect()
21251 }
21252
21253 fn report_editor_event(
21254 &self,
21255 reported_event: ReportEditorEvent,
21256 file_extension: Option<String>,
21257 cx: &App,
21258 ) {
21259 if cfg!(any(test, feature = "test-support")) {
21260 return;
21261 }
21262
21263 let Some(project) = &self.project else { return };
21264
21265 // If None, we are in a file without an extension
21266 let file = self
21267 .buffer
21268 .read(cx)
21269 .as_singleton()
21270 .and_then(|b| b.read(cx).file());
21271 let file_extension = file_extension.or(file
21272 .as_ref()
21273 .and_then(|file| Path::new(file.file_name(cx)).extension())
21274 .and_then(|e| e.to_str())
21275 .map(|a| a.to_string()));
21276
21277 let vim_mode = vim_enabled(cx);
21278
21279 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21280 let copilot_enabled = edit_predictions_provider
21281 == language::language_settings::EditPredictionProvider::Copilot;
21282 let copilot_enabled_for_language = self
21283 .buffer
21284 .read(cx)
21285 .language_settings(cx)
21286 .show_edit_predictions;
21287
21288 let project = project.read(cx);
21289 let event_type = reported_event.event_type();
21290
21291 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21292 telemetry::event!(
21293 event_type,
21294 type = if auto_saved {"autosave"} else {"manual"},
21295 file_extension,
21296 vim_mode,
21297 copilot_enabled,
21298 copilot_enabled_for_language,
21299 edit_predictions_provider,
21300 is_via_ssh = project.is_via_remote_server(),
21301 );
21302 } else {
21303 telemetry::event!(
21304 event_type,
21305 file_extension,
21306 vim_mode,
21307 copilot_enabled,
21308 copilot_enabled_for_language,
21309 edit_predictions_provider,
21310 is_via_ssh = project.is_via_remote_server(),
21311 );
21312 };
21313 }
21314
21315 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21316 /// with each line being an array of {text, highlight} objects.
21317 fn copy_highlight_json(
21318 &mut self,
21319 _: &CopyHighlightJson,
21320 window: &mut Window,
21321 cx: &mut Context<Self>,
21322 ) {
21323 #[derive(Serialize)]
21324 struct Chunk<'a> {
21325 text: String,
21326 highlight: Option<&'a str>,
21327 }
21328
21329 let snapshot = self.buffer.read(cx).snapshot(cx);
21330 let range = self
21331 .selected_text_range(false, window, cx)
21332 .and_then(|selection| {
21333 if selection.range.is_empty() {
21334 None
21335 } else {
21336 Some(selection.range)
21337 }
21338 })
21339 .unwrap_or_else(|| 0..snapshot.len());
21340
21341 let chunks = snapshot.chunks(range, true);
21342 let mut lines = Vec::new();
21343 let mut line: VecDeque<Chunk> = VecDeque::new();
21344
21345 let Some(style) = self.style.as_ref() else {
21346 return;
21347 };
21348
21349 for chunk in chunks {
21350 let highlight = chunk
21351 .syntax_highlight_id
21352 .and_then(|id| id.name(&style.syntax));
21353 let mut chunk_lines = chunk.text.split('\n').peekable();
21354 while let Some(text) = chunk_lines.next() {
21355 let mut merged_with_last_token = false;
21356 if let Some(last_token) = line.back_mut()
21357 && last_token.highlight == highlight
21358 {
21359 last_token.text.push_str(text);
21360 merged_with_last_token = true;
21361 }
21362
21363 if !merged_with_last_token {
21364 line.push_back(Chunk {
21365 text: text.into(),
21366 highlight,
21367 });
21368 }
21369
21370 if chunk_lines.peek().is_some() {
21371 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21372 line.pop_front();
21373 }
21374 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21375 line.pop_back();
21376 }
21377
21378 lines.push(mem::take(&mut line));
21379 }
21380 }
21381 }
21382
21383 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21384 return;
21385 };
21386 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21387 }
21388
21389 pub fn open_context_menu(
21390 &mut self,
21391 _: &OpenContextMenu,
21392 window: &mut Window,
21393 cx: &mut Context<Self>,
21394 ) {
21395 self.request_autoscroll(Autoscroll::newest(), cx);
21396 let position = self.selections.newest_display(cx).start;
21397 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21398 }
21399
21400 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21401 &self.inlay_hint_cache
21402 }
21403
21404 pub fn replay_insert_event(
21405 &mut self,
21406 text: &str,
21407 relative_utf16_range: Option<Range<isize>>,
21408 window: &mut Window,
21409 cx: &mut Context<Self>,
21410 ) {
21411 if !self.input_enabled {
21412 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21413 return;
21414 }
21415 if let Some(relative_utf16_range) = relative_utf16_range {
21416 let selections = self.selections.all::<OffsetUtf16>(cx);
21417 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21418 let new_ranges = selections.into_iter().map(|range| {
21419 let start = OffsetUtf16(
21420 range
21421 .head()
21422 .0
21423 .saturating_add_signed(relative_utf16_range.start),
21424 );
21425 let end = OffsetUtf16(
21426 range
21427 .head()
21428 .0
21429 .saturating_add_signed(relative_utf16_range.end),
21430 );
21431 start..end
21432 });
21433 s.select_ranges(new_ranges);
21434 });
21435 }
21436
21437 self.handle_input(text, window, cx);
21438 }
21439
21440 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21441 let Some(provider) = self.semantics_provider.as_ref() else {
21442 return false;
21443 };
21444
21445 let mut supports = false;
21446 self.buffer().update(cx, |this, cx| {
21447 this.for_each_buffer(|buffer| {
21448 supports |= provider.supports_inlay_hints(buffer, cx);
21449 });
21450 });
21451
21452 supports
21453 }
21454
21455 pub fn is_focused(&self, window: &Window) -> bool {
21456 self.focus_handle.is_focused(window)
21457 }
21458
21459 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21460 cx.emit(EditorEvent::Focused);
21461
21462 if let Some(descendant) = self
21463 .last_focused_descendant
21464 .take()
21465 .and_then(|descendant| descendant.upgrade())
21466 {
21467 window.focus(&descendant);
21468 } else {
21469 if let Some(blame) = self.blame.as_ref() {
21470 blame.update(cx, GitBlame::focus)
21471 }
21472
21473 self.blink_manager.update(cx, BlinkManager::enable);
21474 self.show_cursor_names(window, cx);
21475 self.buffer.update(cx, |buffer, cx| {
21476 buffer.finalize_last_transaction(cx);
21477 if self.leader_id.is_none() {
21478 buffer.set_active_selections(
21479 &self.selections.disjoint_anchors_arc(),
21480 self.selections.line_mode(),
21481 self.cursor_shape,
21482 cx,
21483 );
21484 }
21485 });
21486 }
21487 }
21488
21489 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21490 cx.emit(EditorEvent::FocusedIn)
21491 }
21492
21493 fn handle_focus_out(
21494 &mut self,
21495 event: FocusOutEvent,
21496 _window: &mut Window,
21497 cx: &mut Context<Self>,
21498 ) {
21499 if event.blurred != self.focus_handle {
21500 self.last_focused_descendant = Some(event.blurred);
21501 }
21502 self.selection_drag_state = SelectionDragState::None;
21503 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21504 }
21505
21506 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21507 self.blink_manager.update(cx, BlinkManager::disable);
21508 self.buffer
21509 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21510
21511 if let Some(blame) = self.blame.as_ref() {
21512 blame.update(cx, GitBlame::blur)
21513 }
21514 if !self.hover_state.focused(window, cx) {
21515 hide_hover(self, cx);
21516 }
21517 if !self
21518 .context_menu
21519 .borrow()
21520 .as_ref()
21521 .is_some_and(|context_menu| context_menu.focused(window, cx))
21522 {
21523 self.hide_context_menu(window, cx);
21524 }
21525 self.take_active_edit_prediction(cx);
21526 cx.emit(EditorEvent::Blurred);
21527 cx.notify();
21528 }
21529
21530 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21531 let mut pending: String = window
21532 .pending_input_keystrokes()
21533 .into_iter()
21534 .flatten()
21535 .filter_map(|keystroke| {
21536 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21537 keystroke.key_char.clone()
21538 } else {
21539 None
21540 }
21541 })
21542 .collect();
21543
21544 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21545 pending = "".to_string();
21546 }
21547
21548 let existing_pending = self
21549 .text_highlights::<PendingInput>(cx)
21550 .map(|(_, ranges)| ranges.to_vec());
21551 if existing_pending.is_none() && pending.is_empty() {
21552 return;
21553 }
21554 let transaction =
21555 self.transact(window, cx, |this, window, cx| {
21556 let selections = this.selections.all::<usize>(cx);
21557 let edits = selections
21558 .iter()
21559 .map(|selection| (selection.end..selection.end, pending.clone()));
21560 this.edit(edits, cx);
21561 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21562 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21563 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21564 }));
21565 });
21566 if let Some(existing_ranges) = existing_pending {
21567 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21568 this.edit(edits, cx);
21569 }
21570 });
21571
21572 let snapshot = self.snapshot(window, cx);
21573 let ranges = self
21574 .selections
21575 .all::<usize>(cx)
21576 .into_iter()
21577 .map(|selection| {
21578 snapshot.buffer_snapshot.anchor_after(selection.end)
21579 ..snapshot
21580 .buffer_snapshot
21581 .anchor_before(selection.end + pending.len())
21582 })
21583 .collect();
21584
21585 if pending.is_empty() {
21586 self.clear_highlights::<PendingInput>(cx);
21587 } else {
21588 self.highlight_text::<PendingInput>(
21589 ranges,
21590 HighlightStyle {
21591 underline: Some(UnderlineStyle {
21592 thickness: px(1.),
21593 color: None,
21594 wavy: false,
21595 }),
21596 ..Default::default()
21597 },
21598 cx,
21599 );
21600 }
21601
21602 self.ime_transaction = self.ime_transaction.or(transaction);
21603 if let Some(transaction) = self.ime_transaction {
21604 self.buffer.update(cx, |buffer, cx| {
21605 buffer.group_until_transaction(transaction, cx);
21606 });
21607 }
21608
21609 if self.text_highlights::<PendingInput>(cx).is_none() {
21610 self.ime_transaction.take();
21611 }
21612 }
21613
21614 pub fn register_action_renderer(
21615 &mut self,
21616 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21617 ) -> Subscription {
21618 let id = self.next_editor_action_id.post_inc();
21619 self.editor_actions
21620 .borrow_mut()
21621 .insert(id, Box::new(listener));
21622
21623 let editor_actions = self.editor_actions.clone();
21624 Subscription::new(move || {
21625 editor_actions.borrow_mut().remove(&id);
21626 })
21627 }
21628
21629 pub fn register_action<A: Action>(
21630 &mut self,
21631 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21632 ) -> Subscription {
21633 let id = self.next_editor_action_id.post_inc();
21634 let listener = Arc::new(listener);
21635 self.editor_actions.borrow_mut().insert(
21636 id,
21637 Box::new(move |_, window, _| {
21638 let listener = listener.clone();
21639 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21640 let action = action.downcast_ref().unwrap();
21641 if phase == DispatchPhase::Bubble {
21642 listener(action, window, cx)
21643 }
21644 })
21645 }),
21646 );
21647
21648 let editor_actions = self.editor_actions.clone();
21649 Subscription::new(move || {
21650 editor_actions.borrow_mut().remove(&id);
21651 })
21652 }
21653
21654 pub fn file_header_size(&self) -> u32 {
21655 FILE_HEADER_HEIGHT
21656 }
21657
21658 pub fn restore(
21659 &mut self,
21660 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21661 window: &mut Window,
21662 cx: &mut Context<Self>,
21663 ) {
21664 let workspace = self.workspace();
21665 let project = self.project();
21666 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21667 let mut tasks = Vec::new();
21668 for (buffer_id, changes) in revert_changes {
21669 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21670 buffer.update(cx, |buffer, cx| {
21671 buffer.edit(
21672 changes
21673 .into_iter()
21674 .map(|(range, text)| (range, text.to_string())),
21675 None,
21676 cx,
21677 );
21678 });
21679
21680 if let Some(project) =
21681 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21682 {
21683 project.update(cx, |project, cx| {
21684 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21685 })
21686 }
21687 }
21688 }
21689 tasks
21690 });
21691 cx.spawn_in(window, async move |_, cx| {
21692 for (buffer, task) in save_tasks {
21693 let result = task.await;
21694 if result.is_err() {
21695 let Some(path) = buffer
21696 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21697 .ok()
21698 else {
21699 continue;
21700 };
21701 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21702 let Some(task) = cx
21703 .update_window_entity(workspace, |workspace, window, cx| {
21704 workspace
21705 .open_path_preview(path, None, false, false, false, window, cx)
21706 })
21707 .ok()
21708 else {
21709 continue;
21710 };
21711 task.await.log_err();
21712 }
21713 }
21714 }
21715 })
21716 .detach();
21717 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21718 selections.refresh()
21719 });
21720 }
21721
21722 pub fn to_pixel_point(
21723 &self,
21724 source: multi_buffer::Anchor,
21725 editor_snapshot: &EditorSnapshot,
21726 window: &mut Window,
21727 ) -> Option<gpui::Point<Pixels>> {
21728 let source_point = source.to_display_point(editor_snapshot);
21729 self.display_to_pixel_point(source_point, editor_snapshot, window)
21730 }
21731
21732 pub fn display_to_pixel_point(
21733 &self,
21734 source: DisplayPoint,
21735 editor_snapshot: &EditorSnapshot,
21736 window: &mut Window,
21737 ) -> Option<gpui::Point<Pixels>> {
21738 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21739 let text_layout_details = self.text_layout_details(window);
21740 let scroll_top = text_layout_details
21741 .scroll_anchor
21742 .scroll_position(editor_snapshot)
21743 .y;
21744
21745 if source.row().as_f32() < scroll_top.floor() {
21746 return None;
21747 }
21748 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21749 let source_y = line_height * (source.row().as_f32() - scroll_top);
21750 Some(gpui::Point::new(source_x, source_y))
21751 }
21752
21753 pub fn has_visible_completions_menu(&self) -> bool {
21754 !self.edit_prediction_preview_is_active()
21755 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21756 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21757 })
21758 }
21759
21760 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21761 if self.mode.is_minimap() {
21762 return;
21763 }
21764 self.addons
21765 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21766 }
21767
21768 pub fn unregister_addon<T: Addon>(&mut self) {
21769 self.addons.remove(&std::any::TypeId::of::<T>());
21770 }
21771
21772 pub fn addon<T: Addon>(&self) -> Option<&T> {
21773 let type_id = std::any::TypeId::of::<T>();
21774 self.addons
21775 .get(&type_id)
21776 .and_then(|item| item.to_any().downcast_ref::<T>())
21777 }
21778
21779 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21780 let type_id = std::any::TypeId::of::<T>();
21781 self.addons
21782 .get_mut(&type_id)
21783 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21784 }
21785
21786 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21787 let text_layout_details = self.text_layout_details(window);
21788 let style = &text_layout_details.editor_style;
21789 let font_id = window.text_system().resolve_font(&style.text.font());
21790 let font_size = style.text.font_size.to_pixels(window.rem_size());
21791 let line_height = style.text.line_height_in_pixels(window.rem_size());
21792 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21793 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21794
21795 CharacterDimensions {
21796 em_width,
21797 em_advance,
21798 line_height,
21799 }
21800 }
21801
21802 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21803 self.load_diff_task.clone()
21804 }
21805
21806 fn read_metadata_from_db(
21807 &mut self,
21808 item_id: u64,
21809 workspace_id: WorkspaceId,
21810 window: &mut Window,
21811 cx: &mut Context<Editor>,
21812 ) {
21813 if self.is_singleton(cx)
21814 && !self.mode.is_minimap()
21815 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21816 {
21817 let buffer_snapshot = OnceCell::new();
21818
21819 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21820 && !folds.is_empty()
21821 {
21822 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21823 self.fold_ranges(
21824 folds
21825 .into_iter()
21826 .map(|(start, end)| {
21827 snapshot.clip_offset(start, Bias::Left)
21828 ..snapshot.clip_offset(end, Bias::Right)
21829 })
21830 .collect(),
21831 false,
21832 window,
21833 cx,
21834 );
21835 }
21836
21837 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21838 && !selections.is_empty()
21839 {
21840 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21841 // skip adding the initial selection to selection history
21842 self.selection_history.mode = SelectionHistoryMode::Skipping;
21843 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21844 s.select_ranges(selections.into_iter().map(|(start, end)| {
21845 snapshot.clip_offset(start, Bias::Left)
21846 ..snapshot.clip_offset(end, Bias::Right)
21847 }));
21848 });
21849 self.selection_history.mode = SelectionHistoryMode::Normal;
21850 };
21851 }
21852
21853 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21854 }
21855
21856 fn update_lsp_data(
21857 &mut self,
21858 ignore_cache: bool,
21859 for_buffer: Option<BufferId>,
21860 window: &mut Window,
21861 cx: &mut Context<'_, Self>,
21862 ) {
21863 self.pull_diagnostics(for_buffer, window, cx);
21864 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21865 }
21866}
21867
21868fn edit_for_markdown_paste<'a>(
21869 buffer: &MultiBufferSnapshot,
21870 range: Range<usize>,
21871 to_insert: &'a str,
21872 url: Option<url::Url>,
21873) -> (Range<usize>, Cow<'a, str>) {
21874 if url.is_none() {
21875 return (range, Cow::Borrowed(to_insert));
21876 };
21877
21878 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21879
21880 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21881 Cow::Borrowed(to_insert)
21882 } else {
21883 Cow::Owned(format!("[{old_text}]({to_insert})"))
21884 };
21885 (range, new_text)
21886}
21887
21888fn vim_enabled(cx: &App) -> bool {
21889 vim_mode_setting::VimModeSetting::try_get(cx)
21890 .map(|vim_mode| vim_mode.0)
21891 .unwrap_or(false)
21892}
21893
21894fn process_completion_for_edit(
21895 completion: &Completion,
21896 intent: CompletionIntent,
21897 buffer: &Entity<Buffer>,
21898 cursor_position: &text::Anchor,
21899 cx: &mut Context<Editor>,
21900) -> CompletionEdit {
21901 let buffer = buffer.read(cx);
21902 let buffer_snapshot = buffer.snapshot();
21903 let (snippet, new_text) = if completion.is_snippet() {
21904 // Workaround for typescript language server issues so that methods don't expand within
21905 // strings and functions with type expressions. The previous point is used because the query
21906 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21907 let mut snippet_source = completion.new_text.clone();
21908 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21909 previous_point.column = previous_point.column.saturating_sub(1);
21910 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21911 && scope.prefers_label_for_snippet_in_completion()
21912 && let Some(label) = completion.label()
21913 && matches!(
21914 completion.kind(),
21915 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21916 )
21917 {
21918 snippet_source = label;
21919 }
21920 match Snippet::parse(&snippet_source).log_err() {
21921 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21922 None => (None, completion.new_text.clone()),
21923 }
21924 } else {
21925 (None, completion.new_text.clone())
21926 };
21927
21928 let mut range_to_replace = {
21929 let replace_range = &completion.replace_range;
21930 if let CompletionSource::Lsp {
21931 insert_range: Some(insert_range),
21932 ..
21933 } = &completion.source
21934 {
21935 debug_assert_eq!(
21936 insert_range.start, replace_range.start,
21937 "insert_range and replace_range should start at the same position"
21938 );
21939 debug_assert!(
21940 insert_range
21941 .start
21942 .cmp(cursor_position, &buffer_snapshot)
21943 .is_le(),
21944 "insert_range should start before or at cursor position"
21945 );
21946 debug_assert!(
21947 replace_range
21948 .start
21949 .cmp(cursor_position, &buffer_snapshot)
21950 .is_le(),
21951 "replace_range should start before or at cursor position"
21952 );
21953
21954 let should_replace = match intent {
21955 CompletionIntent::CompleteWithInsert => false,
21956 CompletionIntent::CompleteWithReplace => true,
21957 CompletionIntent::Complete | CompletionIntent::Compose => {
21958 let insert_mode =
21959 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21960 .completions
21961 .lsp_insert_mode;
21962 match insert_mode {
21963 LspInsertMode::Insert => false,
21964 LspInsertMode::Replace => true,
21965 LspInsertMode::ReplaceSubsequence => {
21966 let mut text_to_replace = buffer.chars_for_range(
21967 buffer.anchor_before(replace_range.start)
21968 ..buffer.anchor_after(replace_range.end),
21969 );
21970 let mut current_needle = text_to_replace.next();
21971 for haystack_ch in completion.label.text.chars() {
21972 if let Some(needle_ch) = current_needle
21973 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21974 {
21975 current_needle = text_to_replace.next();
21976 }
21977 }
21978 current_needle.is_none()
21979 }
21980 LspInsertMode::ReplaceSuffix => {
21981 if replace_range
21982 .end
21983 .cmp(cursor_position, &buffer_snapshot)
21984 .is_gt()
21985 {
21986 let range_after_cursor = *cursor_position..replace_range.end;
21987 let text_after_cursor = buffer
21988 .text_for_range(
21989 buffer.anchor_before(range_after_cursor.start)
21990 ..buffer.anchor_after(range_after_cursor.end),
21991 )
21992 .collect::<String>()
21993 .to_ascii_lowercase();
21994 completion
21995 .label
21996 .text
21997 .to_ascii_lowercase()
21998 .ends_with(&text_after_cursor)
21999 } else {
22000 true
22001 }
22002 }
22003 }
22004 }
22005 };
22006
22007 if should_replace {
22008 replace_range.clone()
22009 } else {
22010 insert_range.clone()
22011 }
22012 } else {
22013 replace_range.clone()
22014 }
22015 };
22016
22017 if range_to_replace
22018 .end
22019 .cmp(cursor_position, &buffer_snapshot)
22020 .is_lt()
22021 {
22022 range_to_replace.end = *cursor_position;
22023 }
22024
22025 CompletionEdit {
22026 new_text,
22027 replace_range: range_to_replace.to_offset(buffer),
22028 snippet,
22029 }
22030}
22031
22032struct CompletionEdit {
22033 new_text: String,
22034 replace_range: Range<usize>,
22035 snippet: Option<Snippet>,
22036}
22037
22038fn insert_extra_newline_brackets(
22039 buffer: &MultiBufferSnapshot,
22040 range: Range<usize>,
22041 language: &language::LanguageScope,
22042) -> bool {
22043 let leading_whitespace_len = buffer
22044 .reversed_chars_at(range.start)
22045 .take_while(|c| c.is_whitespace() && *c != '\n')
22046 .map(|c| c.len_utf8())
22047 .sum::<usize>();
22048 let trailing_whitespace_len = buffer
22049 .chars_at(range.end)
22050 .take_while(|c| c.is_whitespace() && *c != '\n')
22051 .map(|c| c.len_utf8())
22052 .sum::<usize>();
22053 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22054
22055 language.brackets().any(|(pair, enabled)| {
22056 let pair_start = pair.start.trim_end();
22057 let pair_end = pair.end.trim_start();
22058
22059 enabled
22060 && pair.newline
22061 && buffer.contains_str_at(range.end, pair_end)
22062 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22063 })
22064}
22065
22066fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22067 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22068 [(buffer, range, _)] => (*buffer, range.clone()),
22069 _ => return false,
22070 };
22071 let pair = {
22072 let mut result: Option<BracketMatch> = None;
22073
22074 for pair in buffer
22075 .all_bracket_ranges(range.clone())
22076 .filter(move |pair| {
22077 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22078 })
22079 {
22080 let len = pair.close_range.end - pair.open_range.start;
22081
22082 if let Some(existing) = &result {
22083 let existing_len = existing.close_range.end - existing.open_range.start;
22084 if len > existing_len {
22085 continue;
22086 }
22087 }
22088
22089 result = Some(pair);
22090 }
22091
22092 result
22093 };
22094 let Some(pair) = pair else {
22095 return false;
22096 };
22097 pair.newline_only
22098 && buffer
22099 .chars_for_range(pair.open_range.end..range.start)
22100 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22101 .all(|c| c.is_whitespace() && c != '\n')
22102}
22103
22104fn update_uncommitted_diff_for_buffer(
22105 editor: Entity<Editor>,
22106 project: &Entity<Project>,
22107 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22108 buffer: Entity<MultiBuffer>,
22109 cx: &mut App,
22110) -> Task<()> {
22111 let mut tasks = Vec::new();
22112 project.update(cx, |project, cx| {
22113 for buffer in buffers {
22114 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22115 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22116 }
22117 }
22118 });
22119 cx.spawn(async move |cx| {
22120 let diffs = future::join_all(tasks).await;
22121 if editor
22122 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22123 .unwrap_or(false)
22124 {
22125 return;
22126 }
22127
22128 buffer
22129 .update(cx, |buffer, cx| {
22130 for diff in diffs.into_iter().flatten() {
22131 buffer.add_diff(diff, cx);
22132 }
22133 })
22134 .ok();
22135 })
22136}
22137
22138fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22139 let tab_size = tab_size.get() as usize;
22140 let mut width = offset;
22141
22142 for ch in text.chars() {
22143 width += if ch == '\t' {
22144 tab_size - (width % tab_size)
22145 } else {
22146 1
22147 };
22148 }
22149
22150 width - offset
22151}
22152
22153#[cfg(test)]
22154mod tests {
22155 use super::*;
22156
22157 #[test]
22158 fn test_string_size_with_expanded_tabs() {
22159 let nz = |val| NonZeroU32::new(val).unwrap();
22160 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22161 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22162 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22163 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22164 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22165 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22166 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22167 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22168 }
22169}
22170
22171/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22172struct WordBreakingTokenizer<'a> {
22173 input: &'a str,
22174}
22175
22176impl<'a> WordBreakingTokenizer<'a> {
22177 fn new(input: &'a str) -> Self {
22178 Self { input }
22179 }
22180}
22181
22182fn is_char_ideographic(ch: char) -> bool {
22183 use unicode_script::Script::*;
22184 use unicode_script::UnicodeScript;
22185 matches!(ch.script(), Han | Tangut | Yi)
22186}
22187
22188fn is_grapheme_ideographic(text: &str) -> bool {
22189 text.chars().any(is_char_ideographic)
22190}
22191
22192fn is_grapheme_whitespace(text: &str) -> bool {
22193 text.chars().any(|x| x.is_whitespace())
22194}
22195
22196fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22197 text.chars()
22198 .next()
22199 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22200}
22201
22202#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22203enum WordBreakToken<'a> {
22204 Word { token: &'a str, grapheme_len: usize },
22205 InlineWhitespace { token: &'a str, grapheme_len: usize },
22206 Newline,
22207}
22208
22209impl<'a> Iterator for WordBreakingTokenizer<'a> {
22210 /// Yields a span, the count of graphemes in the token, and whether it was
22211 /// whitespace. Note that it also breaks at word boundaries.
22212 type Item = WordBreakToken<'a>;
22213
22214 fn next(&mut self) -> Option<Self::Item> {
22215 use unicode_segmentation::UnicodeSegmentation;
22216 if self.input.is_empty() {
22217 return None;
22218 }
22219
22220 let mut iter = self.input.graphemes(true).peekable();
22221 let mut offset = 0;
22222 let mut grapheme_len = 0;
22223 if let Some(first_grapheme) = iter.next() {
22224 let is_newline = first_grapheme == "\n";
22225 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22226 offset += first_grapheme.len();
22227 grapheme_len += 1;
22228 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22229 if let Some(grapheme) = iter.peek().copied()
22230 && should_stay_with_preceding_ideograph(grapheme)
22231 {
22232 offset += grapheme.len();
22233 grapheme_len += 1;
22234 }
22235 } else {
22236 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22237 let mut next_word_bound = words.peek().copied();
22238 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22239 next_word_bound = words.next();
22240 }
22241 while let Some(grapheme) = iter.peek().copied() {
22242 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22243 break;
22244 };
22245 if is_grapheme_whitespace(grapheme) != is_whitespace
22246 || (grapheme == "\n") != is_newline
22247 {
22248 break;
22249 };
22250 offset += grapheme.len();
22251 grapheme_len += 1;
22252 iter.next();
22253 }
22254 }
22255 let token = &self.input[..offset];
22256 self.input = &self.input[offset..];
22257 if token == "\n" {
22258 Some(WordBreakToken::Newline)
22259 } else if is_whitespace {
22260 Some(WordBreakToken::InlineWhitespace {
22261 token,
22262 grapheme_len,
22263 })
22264 } else {
22265 Some(WordBreakToken::Word {
22266 token,
22267 grapheme_len,
22268 })
22269 }
22270 } else {
22271 None
22272 }
22273 }
22274}
22275
22276#[test]
22277fn test_word_breaking_tokenizer() {
22278 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22279 ("", &[]),
22280 (" ", &[whitespace(" ", 2)]),
22281 ("Ʒ", &[word("Ʒ", 1)]),
22282 ("Ǽ", &[word("Ǽ", 1)]),
22283 ("⋑", &[word("⋑", 1)]),
22284 ("⋑⋑", &[word("⋑⋑", 2)]),
22285 (
22286 "原理,进而",
22287 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22288 ),
22289 (
22290 "hello world",
22291 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22292 ),
22293 (
22294 "hello, world",
22295 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22296 ),
22297 (
22298 " hello world",
22299 &[
22300 whitespace(" ", 2),
22301 word("hello", 5),
22302 whitespace(" ", 1),
22303 word("world", 5),
22304 ],
22305 ),
22306 (
22307 "这是什么 \n 钢笔",
22308 &[
22309 word("这", 1),
22310 word("是", 1),
22311 word("什", 1),
22312 word("么", 1),
22313 whitespace(" ", 1),
22314 newline(),
22315 whitespace(" ", 1),
22316 word("钢", 1),
22317 word("笔", 1),
22318 ],
22319 ),
22320 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22321 ];
22322
22323 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22324 WordBreakToken::Word {
22325 token,
22326 grapheme_len,
22327 }
22328 }
22329
22330 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22331 WordBreakToken::InlineWhitespace {
22332 token,
22333 grapheme_len,
22334 }
22335 }
22336
22337 fn newline() -> WordBreakToken<'static> {
22338 WordBreakToken::Newline
22339 }
22340
22341 for (input, result) in tests {
22342 assert_eq!(
22343 WordBreakingTokenizer::new(input)
22344 .collect::<Vec<_>>()
22345 .as_slice(),
22346 *result,
22347 );
22348 }
22349}
22350
22351fn wrap_with_prefix(
22352 first_line_prefix: String,
22353 subsequent_lines_prefix: String,
22354 unwrapped_text: String,
22355 wrap_column: usize,
22356 tab_size: NonZeroU32,
22357 preserve_existing_whitespace: bool,
22358) -> String {
22359 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22360 let subsequent_lines_prefix_len =
22361 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22362 let mut wrapped_text = String::new();
22363 let mut current_line = first_line_prefix;
22364 let mut is_first_line = true;
22365
22366 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22367 let mut current_line_len = first_line_prefix_len;
22368 let mut in_whitespace = false;
22369 for token in tokenizer {
22370 let have_preceding_whitespace = in_whitespace;
22371 match token {
22372 WordBreakToken::Word {
22373 token,
22374 grapheme_len,
22375 } => {
22376 in_whitespace = false;
22377 let current_prefix_len = if is_first_line {
22378 first_line_prefix_len
22379 } else {
22380 subsequent_lines_prefix_len
22381 };
22382 if current_line_len + grapheme_len > wrap_column
22383 && current_line_len != current_prefix_len
22384 {
22385 wrapped_text.push_str(current_line.trim_end());
22386 wrapped_text.push('\n');
22387 is_first_line = false;
22388 current_line = subsequent_lines_prefix.clone();
22389 current_line_len = subsequent_lines_prefix_len;
22390 }
22391 current_line.push_str(token);
22392 current_line_len += grapheme_len;
22393 }
22394 WordBreakToken::InlineWhitespace {
22395 mut token,
22396 mut grapheme_len,
22397 } => {
22398 in_whitespace = true;
22399 if have_preceding_whitespace && !preserve_existing_whitespace {
22400 continue;
22401 }
22402 if !preserve_existing_whitespace {
22403 token = " ";
22404 grapheme_len = 1;
22405 }
22406 let current_prefix_len = if is_first_line {
22407 first_line_prefix_len
22408 } else {
22409 subsequent_lines_prefix_len
22410 };
22411 if current_line_len + grapheme_len > wrap_column {
22412 wrapped_text.push_str(current_line.trim_end());
22413 wrapped_text.push('\n');
22414 is_first_line = false;
22415 current_line = subsequent_lines_prefix.clone();
22416 current_line_len = subsequent_lines_prefix_len;
22417 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22418 current_line.push_str(token);
22419 current_line_len += grapheme_len;
22420 }
22421 }
22422 WordBreakToken::Newline => {
22423 in_whitespace = true;
22424 let current_prefix_len = if is_first_line {
22425 first_line_prefix_len
22426 } else {
22427 subsequent_lines_prefix_len
22428 };
22429 if preserve_existing_whitespace {
22430 wrapped_text.push_str(current_line.trim_end());
22431 wrapped_text.push('\n');
22432 is_first_line = false;
22433 current_line = subsequent_lines_prefix.clone();
22434 current_line_len = subsequent_lines_prefix_len;
22435 } else if have_preceding_whitespace {
22436 continue;
22437 } else if current_line_len + 1 > wrap_column
22438 && current_line_len != current_prefix_len
22439 {
22440 wrapped_text.push_str(current_line.trim_end());
22441 wrapped_text.push('\n');
22442 is_first_line = false;
22443 current_line = subsequent_lines_prefix.clone();
22444 current_line_len = subsequent_lines_prefix_len;
22445 } else if current_line_len != current_prefix_len {
22446 current_line.push(' ');
22447 current_line_len += 1;
22448 }
22449 }
22450 }
22451 }
22452
22453 if !current_line.is_empty() {
22454 wrapped_text.push_str(¤t_line);
22455 }
22456 wrapped_text
22457}
22458
22459#[test]
22460fn test_wrap_with_prefix() {
22461 assert_eq!(
22462 wrap_with_prefix(
22463 "# ".to_string(),
22464 "# ".to_string(),
22465 "abcdefg".to_string(),
22466 4,
22467 NonZeroU32::new(4).unwrap(),
22468 false,
22469 ),
22470 "# abcdefg"
22471 );
22472 assert_eq!(
22473 wrap_with_prefix(
22474 "".to_string(),
22475 "".to_string(),
22476 "\thello world".to_string(),
22477 8,
22478 NonZeroU32::new(4).unwrap(),
22479 false,
22480 ),
22481 "hello\nworld"
22482 );
22483 assert_eq!(
22484 wrap_with_prefix(
22485 "// ".to_string(),
22486 "// ".to_string(),
22487 "xx \nyy zz aa bb cc".to_string(),
22488 12,
22489 NonZeroU32::new(4).unwrap(),
22490 false,
22491 ),
22492 "// xx yy zz\n// aa bb cc"
22493 );
22494 assert_eq!(
22495 wrap_with_prefix(
22496 String::new(),
22497 String::new(),
22498 "这是什么 \n 钢笔".to_string(),
22499 3,
22500 NonZeroU32::new(4).unwrap(),
22501 false,
22502 ),
22503 "这是什\n么 钢\n笔"
22504 );
22505}
22506
22507pub trait CollaborationHub {
22508 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22509 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22510 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22511}
22512
22513impl CollaborationHub for Entity<Project> {
22514 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22515 self.read(cx).collaborators()
22516 }
22517
22518 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22519 self.read(cx).user_store().read(cx).participant_indices()
22520 }
22521
22522 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22523 let this = self.read(cx);
22524 let user_ids = this.collaborators().values().map(|c| c.user_id);
22525 this.user_store().read(cx).participant_names(user_ids, cx)
22526 }
22527}
22528
22529pub trait SemanticsProvider {
22530 fn hover(
22531 &self,
22532 buffer: &Entity<Buffer>,
22533 position: text::Anchor,
22534 cx: &mut App,
22535 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22536
22537 fn inline_values(
22538 &self,
22539 buffer_handle: Entity<Buffer>,
22540 range: Range<text::Anchor>,
22541 cx: &mut App,
22542 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22543
22544 fn inlay_hints(
22545 &self,
22546 buffer_handle: Entity<Buffer>,
22547 range: Range<text::Anchor>,
22548 cx: &mut App,
22549 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22550
22551 fn resolve_inlay_hint(
22552 &self,
22553 hint: InlayHint,
22554 buffer_handle: Entity<Buffer>,
22555 server_id: LanguageServerId,
22556 cx: &mut App,
22557 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22558
22559 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22560
22561 fn document_highlights(
22562 &self,
22563 buffer: &Entity<Buffer>,
22564 position: text::Anchor,
22565 cx: &mut App,
22566 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22567
22568 fn definitions(
22569 &self,
22570 buffer: &Entity<Buffer>,
22571 position: text::Anchor,
22572 kind: GotoDefinitionKind,
22573 cx: &mut App,
22574 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22575
22576 fn range_for_rename(
22577 &self,
22578 buffer: &Entity<Buffer>,
22579 position: text::Anchor,
22580 cx: &mut App,
22581 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22582
22583 fn perform_rename(
22584 &self,
22585 buffer: &Entity<Buffer>,
22586 position: text::Anchor,
22587 new_name: String,
22588 cx: &mut App,
22589 ) -> Option<Task<Result<ProjectTransaction>>>;
22590}
22591
22592pub trait CompletionProvider {
22593 fn completions(
22594 &self,
22595 excerpt_id: ExcerptId,
22596 buffer: &Entity<Buffer>,
22597 buffer_position: text::Anchor,
22598 trigger: CompletionContext,
22599 window: &mut Window,
22600 cx: &mut Context<Editor>,
22601 ) -> Task<Result<Vec<CompletionResponse>>>;
22602
22603 fn resolve_completions(
22604 &self,
22605 _buffer: Entity<Buffer>,
22606 _completion_indices: Vec<usize>,
22607 _completions: Rc<RefCell<Box<[Completion]>>>,
22608 _cx: &mut Context<Editor>,
22609 ) -> Task<Result<bool>> {
22610 Task::ready(Ok(false))
22611 }
22612
22613 fn apply_additional_edits_for_completion(
22614 &self,
22615 _buffer: Entity<Buffer>,
22616 _completions: Rc<RefCell<Box<[Completion]>>>,
22617 _completion_index: usize,
22618 _push_to_history: bool,
22619 _cx: &mut Context<Editor>,
22620 ) -> Task<Result<Option<language::Transaction>>> {
22621 Task::ready(Ok(None))
22622 }
22623
22624 fn is_completion_trigger(
22625 &self,
22626 buffer: &Entity<Buffer>,
22627 position: language::Anchor,
22628 text: &str,
22629 trigger_in_words: bool,
22630 menu_is_open: bool,
22631 cx: &mut Context<Editor>,
22632 ) -> bool;
22633
22634 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22635
22636 fn sort_completions(&self) -> bool {
22637 true
22638 }
22639
22640 fn filter_completions(&self) -> bool {
22641 true
22642 }
22643}
22644
22645pub trait CodeActionProvider {
22646 fn id(&self) -> Arc<str>;
22647
22648 fn code_actions(
22649 &self,
22650 buffer: &Entity<Buffer>,
22651 range: Range<text::Anchor>,
22652 window: &mut Window,
22653 cx: &mut App,
22654 ) -> Task<Result<Vec<CodeAction>>>;
22655
22656 fn apply_code_action(
22657 &self,
22658 buffer_handle: Entity<Buffer>,
22659 action: CodeAction,
22660 excerpt_id: ExcerptId,
22661 push_to_history: bool,
22662 window: &mut Window,
22663 cx: &mut App,
22664 ) -> Task<Result<ProjectTransaction>>;
22665}
22666
22667impl CodeActionProvider for Entity<Project> {
22668 fn id(&self) -> Arc<str> {
22669 "project".into()
22670 }
22671
22672 fn code_actions(
22673 &self,
22674 buffer: &Entity<Buffer>,
22675 range: Range<text::Anchor>,
22676 _window: &mut Window,
22677 cx: &mut App,
22678 ) -> Task<Result<Vec<CodeAction>>> {
22679 self.update(cx, |project, cx| {
22680 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22681 let code_actions = project.code_actions(buffer, range, None, cx);
22682 cx.background_spawn(async move {
22683 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22684 Ok(code_lens_actions
22685 .context("code lens fetch")?
22686 .into_iter()
22687 .flatten()
22688 .chain(
22689 code_actions
22690 .context("code action fetch")?
22691 .into_iter()
22692 .flatten(),
22693 )
22694 .collect())
22695 })
22696 })
22697 }
22698
22699 fn apply_code_action(
22700 &self,
22701 buffer_handle: Entity<Buffer>,
22702 action: CodeAction,
22703 _excerpt_id: ExcerptId,
22704 push_to_history: bool,
22705 _window: &mut Window,
22706 cx: &mut App,
22707 ) -> Task<Result<ProjectTransaction>> {
22708 self.update(cx, |project, cx| {
22709 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22710 })
22711 }
22712}
22713
22714fn snippet_completions(
22715 project: &Project,
22716 buffer: &Entity<Buffer>,
22717 buffer_position: text::Anchor,
22718 cx: &mut App,
22719) -> Task<Result<CompletionResponse>> {
22720 let languages = buffer.read(cx).languages_at(buffer_position);
22721 let snippet_store = project.snippets().read(cx);
22722
22723 let scopes: Vec<_> = languages
22724 .iter()
22725 .filter_map(|language| {
22726 let language_name = language.lsp_id();
22727 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22728
22729 if snippets.is_empty() {
22730 None
22731 } else {
22732 Some((language.default_scope(), snippets))
22733 }
22734 })
22735 .collect();
22736
22737 if scopes.is_empty() {
22738 return Task::ready(Ok(CompletionResponse {
22739 completions: vec![],
22740 display_options: CompletionDisplayOptions::default(),
22741 is_incomplete: false,
22742 }));
22743 }
22744
22745 let snapshot = buffer.read(cx).text_snapshot();
22746 let chars: String = snapshot
22747 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22748 .collect();
22749 let executor = cx.background_executor().clone();
22750
22751 cx.background_spawn(async move {
22752 let mut is_incomplete = false;
22753 let mut completions: Vec<Completion> = Vec::new();
22754 for (scope, snippets) in scopes.into_iter() {
22755 let classifier =
22756 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22757 let mut last_word = chars
22758 .chars()
22759 .take_while(|c| classifier.is_word(*c))
22760 .collect::<String>();
22761 last_word = last_word.chars().rev().collect();
22762
22763 if last_word.is_empty() {
22764 return Ok(CompletionResponse {
22765 completions: vec![],
22766 display_options: CompletionDisplayOptions::default(),
22767 is_incomplete: true,
22768 });
22769 }
22770
22771 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22772 let to_lsp = |point: &text::Anchor| {
22773 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22774 point_to_lsp(end)
22775 };
22776 let lsp_end = to_lsp(&buffer_position);
22777
22778 let candidates = snippets
22779 .iter()
22780 .enumerate()
22781 .flat_map(|(ix, snippet)| {
22782 snippet
22783 .prefix
22784 .iter()
22785 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22786 })
22787 .collect::<Vec<StringMatchCandidate>>();
22788
22789 const MAX_RESULTS: usize = 100;
22790 let mut matches = fuzzy::match_strings(
22791 &candidates,
22792 &last_word,
22793 last_word.chars().any(|c| c.is_uppercase()),
22794 true,
22795 MAX_RESULTS,
22796 &Default::default(),
22797 executor.clone(),
22798 )
22799 .await;
22800
22801 if matches.len() >= MAX_RESULTS {
22802 is_incomplete = true;
22803 }
22804
22805 // Remove all candidates where the query's start does not match the start of any word in the candidate
22806 if let Some(query_start) = last_word.chars().next() {
22807 matches.retain(|string_match| {
22808 split_words(&string_match.string).any(|word| {
22809 // Check that the first codepoint of the word as lowercase matches the first
22810 // codepoint of the query as lowercase
22811 word.chars()
22812 .flat_map(|codepoint| codepoint.to_lowercase())
22813 .zip(query_start.to_lowercase())
22814 .all(|(word_cp, query_cp)| word_cp == query_cp)
22815 })
22816 });
22817 }
22818
22819 let matched_strings = matches
22820 .into_iter()
22821 .map(|m| m.string)
22822 .collect::<HashSet<_>>();
22823
22824 completions.extend(snippets.iter().filter_map(|snippet| {
22825 let matching_prefix = snippet
22826 .prefix
22827 .iter()
22828 .find(|prefix| matched_strings.contains(*prefix))?;
22829 let start = as_offset - last_word.len();
22830 let start = snapshot.anchor_before(start);
22831 let range = start..buffer_position;
22832 let lsp_start = to_lsp(&start);
22833 let lsp_range = lsp::Range {
22834 start: lsp_start,
22835 end: lsp_end,
22836 };
22837 Some(Completion {
22838 replace_range: range,
22839 new_text: snippet.body.clone(),
22840 source: CompletionSource::Lsp {
22841 insert_range: None,
22842 server_id: LanguageServerId(usize::MAX),
22843 resolved: true,
22844 lsp_completion: Box::new(lsp::CompletionItem {
22845 label: snippet.prefix.first().unwrap().clone(),
22846 kind: Some(CompletionItemKind::SNIPPET),
22847 label_details: snippet.description.as_ref().map(|description| {
22848 lsp::CompletionItemLabelDetails {
22849 detail: Some(description.clone()),
22850 description: None,
22851 }
22852 }),
22853 insert_text_format: Some(InsertTextFormat::SNIPPET),
22854 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22855 lsp::InsertReplaceEdit {
22856 new_text: snippet.body.clone(),
22857 insert: lsp_range,
22858 replace: lsp_range,
22859 },
22860 )),
22861 filter_text: Some(snippet.body.clone()),
22862 sort_text: Some(char::MAX.to_string()),
22863 ..lsp::CompletionItem::default()
22864 }),
22865 lsp_defaults: None,
22866 },
22867 label: CodeLabel {
22868 text: matching_prefix.clone(),
22869 runs: Vec::new(),
22870 filter_range: 0..matching_prefix.len(),
22871 },
22872 icon_path: None,
22873 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22874 single_line: snippet.name.clone().into(),
22875 plain_text: snippet
22876 .description
22877 .clone()
22878 .map(|description| description.into()),
22879 }),
22880 insert_text_mode: None,
22881 confirm: None,
22882 })
22883 }))
22884 }
22885
22886 Ok(CompletionResponse {
22887 completions,
22888 display_options: CompletionDisplayOptions::default(),
22889 is_incomplete,
22890 })
22891 })
22892}
22893
22894impl CompletionProvider for Entity<Project> {
22895 fn completions(
22896 &self,
22897 _excerpt_id: ExcerptId,
22898 buffer: &Entity<Buffer>,
22899 buffer_position: text::Anchor,
22900 options: CompletionContext,
22901 _window: &mut Window,
22902 cx: &mut Context<Editor>,
22903 ) -> Task<Result<Vec<CompletionResponse>>> {
22904 self.update(cx, |project, cx| {
22905 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22906 let project_completions = project.completions(buffer, buffer_position, options, cx);
22907 cx.background_spawn(async move {
22908 let mut responses = project_completions.await?;
22909 let snippets = snippets.await?;
22910 if !snippets.completions.is_empty() {
22911 responses.push(snippets);
22912 }
22913 Ok(responses)
22914 })
22915 })
22916 }
22917
22918 fn resolve_completions(
22919 &self,
22920 buffer: Entity<Buffer>,
22921 completion_indices: Vec<usize>,
22922 completions: Rc<RefCell<Box<[Completion]>>>,
22923 cx: &mut Context<Editor>,
22924 ) -> Task<Result<bool>> {
22925 self.update(cx, |project, cx| {
22926 project.lsp_store().update(cx, |lsp_store, cx| {
22927 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22928 })
22929 })
22930 }
22931
22932 fn apply_additional_edits_for_completion(
22933 &self,
22934 buffer: Entity<Buffer>,
22935 completions: Rc<RefCell<Box<[Completion]>>>,
22936 completion_index: usize,
22937 push_to_history: bool,
22938 cx: &mut Context<Editor>,
22939 ) -> Task<Result<Option<language::Transaction>>> {
22940 self.update(cx, |project, cx| {
22941 project.lsp_store().update(cx, |lsp_store, cx| {
22942 lsp_store.apply_additional_edits_for_completion(
22943 buffer,
22944 completions,
22945 completion_index,
22946 push_to_history,
22947 cx,
22948 )
22949 })
22950 })
22951 }
22952
22953 fn is_completion_trigger(
22954 &self,
22955 buffer: &Entity<Buffer>,
22956 position: language::Anchor,
22957 text: &str,
22958 trigger_in_words: bool,
22959 menu_is_open: bool,
22960 cx: &mut Context<Editor>,
22961 ) -> bool {
22962 let mut chars = text.chars();
22963 let char = if let Some(char) = chars.next() {
22964 char
22965 } else {
22966 return false;
22967 };
22968 if chars.next().is_some() {
22969 return false;
22970 }
22971
22972 let buffer = buffer.read(cx);
22973 let snapshot = buffer.snapshot();
22974 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22975 return false;
22976 }
22977 let classifier = snapshot
22978 .char_classifier_at(position)
22979 .scope_context(Some(CharScopeContext::Completion));
22980 if trigger_in_words && classifier.is_word(char) {
22981 return true;
22982 }
22983
22984 buffer.completion_triggers().contains(text)
22985 }
22986}
22987
22988impl SemanticsProvider for Entity<Project> {
22989 fn hover(
22990 &self,
22991 buffer: &Entity<Buffer>,
22992 position: text::Anchor,
22993 cx: &mut App,
22994 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22995 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22996 }
22997
22998 fn document_highlights(
22999 &self,
23000 buffer: &Entity<Buffer>,
23001 position: text::Anchor,
23002 cx: &mut App,
23003 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23004 Some(self.update(cx, |project, cx| {
23005 project.document_highlights(buffer, position, cx)
23006 }))
23007 }
23008
23009 fn definitions(
23010 &self,
23011 buffer: &Entity<Buffer>,
23012 position: text::Anchor,
23013 kind: GotoDefinitionKind,
23014 cx: &mut App,
23015 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23016 Some(self.update(cx, |project, cx| match kind {
23017 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23018 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23019 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23020 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23021 }))
23022 }
23023
23024 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23025 self.update(cx, |project, cx| {
23026 if project
23027 .active_debug_session(cx)
23028 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23029 {
23030 return true;
23031 }
23032
23033 buffer.update(cx, |buffer, cx| {
23034 project.any_language_server_supports_inlay_hints(buffer, cx)
23035 })
23036 })
23037 }
23038
23039 fn inline_values(
23040 &self,
23041 buffer_handle: Entity<Buffer>,
23042 range: Range<text::Anchor>,
23043 cx: &mut App,
23044 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23045 self.update(cx, |project, cx| {
23046 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23047
23048 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23049 })
23050 }
23051
23052 fn inlay_hints(
23053 &self,
23054 buffer_handle: Entity<Buffer>,
23055 range: Range<text::Anchor>,
23056 cx: &mut App,
23057 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23058 Some(self.update(cx, |project, cx| {
23059 project.inlay_hints(buffer_handle, range, cx)
23060 }))
23061 }
23062
23063 fn resolve_inlay_hint(
23064 &self,
23065 hint: InlayHint,
23066 buffer_handle: Entity<Buffer>,
23067 server_id: LanguageServerId,
23068 cx: &mut App,
23069 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23070 Some(self.update(cx, |project, cx| {
23071 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23072 }))
23073 }
23074
23075 fn range_for_rename(
23076 &self,
23077 buffer: &Entity<Buffer>,
23078 position: text::Anchor,
23079 cx: &mut App,
23080 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23081 Some(self.update(cx, |project, cx| {
23082 let buffer = buffer.clone();
23083 let task = project.prepare_rename(buffer.clone(), position, cx);
23084 cx.spawn(async move |_, cx| {
23085 Ok(match task.await? {
23086 PrepareRenameResponse::Success(range) => Some(range),
23087 PrepareRenameResponse::InvalidPosition => None,
23088 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23089 // Fallback on using TreeSitter info to determine identifier range
23090 buffer.read_with(cx, |buffer, _| {
23091 let snapshot = buffer.snapshot();
23092 let (range, kind) = snapshot.surrounding_word(position, None);
23093 if kind != Some(CharKind::Word) {
23094 return None;
23095 }
23096 Some(
23097 snapshot.anchor_before(range.start)
23098 ..snapshot.anchor_after(range.end),
23099 )
23100 })?
23101 }
23102 })
23103 })
23104 }))
23105 }
23106
23107 fn perform_rename(
23108 &self,
23109 buffer: &Entity<Buffer>,
23110 position: text::Anchor,
23111 new_name: String,
23112 cx: &mut App,
23113 ) -> Option<Task<Result<ProjectTransaction>>> {
23114 Some(self.update(cx, |project, cx| {
23115 project.perform_rename(buffer.clone(), position, new_name, cx)
23116 }))
23117 }
23118}
23119
23120fn inlay_hint_settings(
23121 location: Anchor,
23122 snapshot: &MultiBufferSnapshot,
23123 cx: &mut Context<Editor>,
23124) -> InlayHintSettings {
23125 let file = snapshot.file_at(location);
23126 let language = snapshot.language_at(location).map(|l| l.name());
23127 language_settings(language, file, cx).inlay_hints
23128}
23129
23130fn consume_contiguous_rows(
23131 contiguous_row_selections: &mut Vec<Selection<Point>>,
23132 selection: &Selection<Point>,
23133 display_map: &DisplaySnapshot,
23134 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23135) -> (MultiBufferRow, MultiBufferRow) {
23136 contiguous_row_selections.push(selection.clone());
23137 let start_row = starting_row(selection, display_map);
23138 let mut end_row = ending_row(selection, display_map);
23139
23140 while let Some(next_selection) = selections.peek() {
23141 if next_selection.start.row <= end_row.0 {
23142 end_row = ending_row(next_selection, display_map);
23143 contiguous_row_selections.push(selections.next().unwrap().clone());
23144 } else {
23145 break;
23146 }
23147 }
23148 (start_row, end_row)
23149}
23150
23151fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23152 if selection.start.column > 0 {
23153 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23154 } else {
23155 MultiBufferRow(selection.start.row)
23156 }
23157}
23158
23159fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23160 if next_selection.end.column > 0 || next_selection.is_empty() {
23161 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23162 } else {
23163 MultiBufferRow(next_selection.end.row)
23164 }
23165}
23166
23167impl EditorSnapshot {
23168 pub fn remote_selections_in_range<'a>(
23169 &'a self,
23170 range: &'a Range<Anchor>,
23171 collaboration_hub: &dyn CollaborationHub,
23172 cx: &'a App,
23173 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23174 let participant_names = collaboration_hub.user_names(cx);
23175 let participant_indices = collaboration_hub.user_participant_indices(cx);
23176 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23177 let collaborators_by_replica_id = collaborators_by_peer_id
23178 .values()
23179 .map(|collaborator| (collaborator.replica_id, collaborator))
23180 .collect::<HashMap<_, _>>();
23181 self.buffer_snapshot
23182 .selections_in_range(range, false)
23183 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23184 if replica_id == AGENT_REPLICA_ID {
23185 Some(RemoteSelection {
23186 replica_id,
23187 selection,
23188 cursor_shape,
23189 line_mode,
23190 collaborator_id: CollaboratorId::Agent,
23191 user_name: Some("Agent".into()),
23192 color: cx.theme().players().agent(),
23193 })
23194 } else {
23195 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23196 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23197 let user_name = participant_names.get(&collaborator.user_id).cloned();
23198 Some(RemoteSelection {
23199 replica_id,
23200 selection,
23201 cursor_shape,
23202 line_mode,
23203 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23204 user_name,
23205 color: if let Some(index) = participant_index {
23206 cx.theme().players().color_for_participant(index.0)
23207 } else {
23208 cx.theme().players().absent()
23209 },
23210 })
23211 }
23212 })
23213 }
23214
23215 pub fn hunks_for_ranges(
23216 &self,
23217 ranges: impl IntoIterator<Item = Range<Point>>,
23218 ) -> Vec<MultiBufferDiffHunk> {
23219 let mut hunks = Vec::new();
23220 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23221 HashMap::default();
23222 for query_range in ranges {
23223 let query_rows =
23224 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23225 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23226 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23227 ) {
23228 // Include deleted hunks that are adjacent to the query range, because
23229 // otherwise they would be missed.
23230 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23231 if hunk.status().is_deleted() {
23232 intersects_range |= hunk.row_range.start == query_rows.end;
23233 intersects_range |= hunk.row_range.end == query_rows.start;
23234 }
23235 if intersects_range {
23236 if !processed_buffer_rows
23237 .entry(hunk.buffer_id)
23238 .or_default()
23239 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23240 {
23241 continue;
23242 }
23243 hunks.push(hunk);
23244 }
23245 }
23246 }
23247
23248 hunks
23249 }
23250
23251 fn display_diff_hunks_for_rows<'a>(
23252 &'a self,
23253 display_rows: Range<DisplayRow>,
23254 folded_buffers: &'a HashSet<BufferId>,
23255 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23256 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23257 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23258
23259 self.buffer_snapshot
23260 .diff_hunks_in_range(buffer_start..buffer_end)
23261 .filter_map(|hunk| {
23262 if folded_buffers.contains(&hunk.buffer_id) {
23263 return None;
23264 }
23265
23266 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23267 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23268
23269 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23270 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23271
23272 let display_hunk = if hunk_display_start.column() != 0 {
23273 DisplayDiffHunk::Folded {
23274 display_row: hunk_display_start.row(),
23275 }
23276 } else {
23277 let mut end_row = hunk_display_end.row();
23278 if hunk_display_end.column() > 0 {
23279 end_row.0 += 1;
23280 }
23281 let is_created_file = hunk.is_created_file();
23282 DisplayDiffHunk::Unfolded {
23283 status: hunk.status(),
23284 diff_base_byte_range: hunk.diff_base_byte_range,
23285 display_row_range: hunk_display_start.row()..end_row,
23286 multi_buffer_range: Anchor::range_in_buffer(
23287 hunk.excerpt_id,
23288 hunk.buffer_id,
23289 hunk.buffer_range,
23290 ),
23291 is_created_file,
23292 }
23293 };
23294
23295 Some(display_hunk)
23296 })
23297 }
23298
23299 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23300 self.display_snapshot.buffer_snapshot.language_at(position)
23301 }
23302
23303 pub fn is_focused(&self) -> bool {
23304 self.is_focused
23305 }
23306
23307 pub fn placeholder_text(&self) -> Option<String> {
23308 self.placeholder_display_snapshot
23309 .as_ref()
23310 .map(|display_map| display_map.text())
23311 }
23312
23313 pub fn scroll_position(&self) -> gpui::Point<f32> {
23314 self.scroll_anchor.scroll_position(&self.display_snapshot)
23315 }
23316
23317 fn gutter_dimensions(
23318 &self,
23319 font_id: FontId,
23320 font_size: Pixels,
23321 max_line_number_width: Pixels,
23322 cx: &App,
23323 ) -> Option<GutterDimensions> {
23324 if !self.show_gutter {
23325 return None;
23326 }
23327
23328 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23329 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23330
23331 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23332 matches!(
23333 ProjectSettings::get_global(cx).git.git_gutter,
23334 GitGutterSetting::TrackedFiles
23335 )
23336 });
23337 let gutter_settings = EditorSettings::get_global(cx).gutter;
23338 let show_line_numbers = self
23339 .show_line_numbers
23340 .unwrap_or(gutter_settings.line_numbers);
23341 let line_gutter_width = if show_line_numbers {
23342 // Avoid flicker-like gutter resizes when the line number gains another digit by
23343 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23344 let min_width_for_number_on_gutter =
23345 ch_advance * gutter_settings.min_line_number_digits as f32;
23346 max_line_number_width.max(min_width_for_number_on_gutter)
23347 } else {
23348 0.0.into()
23349 };
23350
23351 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23352 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23353
23354 let git_blame_entries_width =
23355 self.git_blame_gutter_max_author_length
23356 .map(|max_author_length| {
23357 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23358 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23359
23360 /// The number of characters to dedicate to gaps and margins.
23361 const SPACING_WIDTH: usize = 4;
23362
23363 let max_char_count = max_author_length.min(renderer.max_author_length())
23364 + ::git::SHORT_SHA_LENGTH
23365 + MAX_RELATIVE_TIMESTAMP.len()
23366 + SPACING_WIDTH;
23367
23368 ch_advance * max_char_count
23369 });
23370
23371 let is_singleton = self.buffer_snapshot.is_singleton();
23372
23373 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23374 left_padding += if !is_singleton {
23375 ch_width * 4.0
23376 } else if show_runnables || show_breakpoints {
23377 ch_width * 3.0
23378 } else if show_git_gutter && show_line_numbers {
23379 ch_width * 2.0
23380 } else if show_git_gutter || show_line_numbers {
23381 ch_width
23382 } else {
23383 px(0.)
23384 };
23385
23386 let shows_folds = is_singleton && gutter_settings.folds;
23387
23388 let right_padding = if shows_folds && show_line_numbers {
23389 ch_width * 4.0
23390 } else if shows_folds || (!is_singleton && show_line_numbers) {
23391 ch_width * 3.0
23392 } else if show_line_numbers {
23393 ch_width
23394 } else {
23395 px(0.)
23396 };
23397
23398 Some(GutterDimensions {
23399 left_padding,
23400 right_padding,
23401 width: line_gutter_width + left_padding + right_padding,
23402 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23403 git_blame_entries_width,
23404 })
23405 }
23406
23407 pub fn render_crease_toggle(
23408 &self,
23409 buffer_row: MultiBufferRow,
23410 row_contains_cursor: bool,
23411 editor: Entity<Editor>,
23412 window: &mut Window,
23413 cx: &mut App,
23414 ) -> Option<AnyElement> {
23415 let folded = self.is_line_folded(buffer_row);
23416 let mut is_foldable = false;
23417
23418 if let Some(crease) = self
23419 .crease_snapshot
23420 .query_row(buffer_row, &self.buffer_snapshot)
23421 {
23422 is_foldable = true;
23423 match crease {
23424 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23425 if let Some(render_toggle) = render_toggle {
23426 let toggle_callback =
23427 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23428 if folded {
23429 editor.update(cx, |editor, cx| {
23430 editor.fold_at(buffer_row, window, cx)
23431 });
23432 } else {
23433 editor.update(cx, |editor, cx| {
23434 editor.unfold_at(buffer_row, window, cx)
23435 });
23436 }
23437 });
23438 return Some((render_toggle)(
23439 buffer_row,
23440 folded,
23441 toggle_callback,
23442 window,
23443 cx,
23444 ));
23445 }
23446 }
23447 }
23448 }
23449
23450 is_foldable |= self.starts_indent(buffer_row);
23451
23452 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23453 Some(
23454 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23455 .toggle_state(folded)
23456 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23457 if folded {
23458 this.unfold_at(buffer_row, window, cx);
23459 } else {
23460 this.fold_at(buffer_row, window, cx);
23461 }
23462 }))
23463 .into_any_element(),
23464 )
23465 } else {
23466 None
23467 }
23468 }
23469
23470 pub fn render_crease_trailer(
23471 &self,
23472 buffer_row: MultiBufferRow,
23473 window: &mut Window,
23474 cx: &mut App,
23475 ) -> Option<AnyElement> {
23476 let folded = self.is_line_folded(buffer_row);
23477 if let Crease::Inline { render_trailer, .. } = self
23478 .crease_snapshot
23479 .query_row(buffer_row, &self.buffer_snapshot)?
23480 {
23481 let render_trailer = render_trailer.as_ref()?;
23482 Some(render_trailer(buffer_row, folded, window, cx))
23483 } else {
23484 None
23485 }
23486 }
23487}
23488
23489impl Deref for EditorSnapshot {
23490 type Target = DisplaySnapshot;
23491
23492 fn deref(&self) -> &Self::Target {
23493 &self.display_snapshot
23494 }
23495}
23496
23497#[derive(Clone, Debug, PartialEq, Eq)]
23498pub enum EditorEvent {
23499 InputIgnored {
23500 text: Arc<str>,
23501 },
23502 InputHandled {
23503 utf16_range_to_replace: Option<Range<isize>>,
23504 text: Arc<str>,
23505 },
23506 ExcerptsAdded {
23507 buffer: Entity<Buffer>,
23508 predecessor: ExcerptId,
23509 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23510 },
23511 ExcerptsRemoved {
23512 ids: Vec<ExcerptId>,
23513 removed_buffer_ids: Vec<BufferId>,
23514 },
23515 BufferFoldToggled {
23516 ids: Vec<ExcerptId>,
23517 folded: bool,
23518 },
23519 ExcerptsEdited {
23520 ids: Vec<ExcerptId>,
23521 },
23522 ExcerptsExpanded {
23523 ids: Vec<ExcerptId>,
23524 },
23525 BufferEdited,
23526 Edited {
23527 transaction_id: clock::Lamport,
23528 },
23529 Reparsed(BufferId),
23530 Focused,
23531 FocusedIn,
23532 Blurred,
23533 DirtyChanged,
23534 Saved,
23535 TitleChanged,
23536 SelectionsChanged {
23537 local: bool,
23538 },
23539 ScrollPositionChanged {
23540 local: bool,
23541 autoscroll: bool,
23542 },
23543 TransactionUndone {
23544 transaction_id: clock::Lamport,
23545 },
23546 TransactionBegun {
23547 transaction_id: clock::Lamport,
23548 },
23549 CursorShapeChanged,
23550 BreadcrumbsChanged,
23551 PushedToNavHistory {
23552 anchor: Anchor,
23553 is_deactivate: bool,
23554 },
23555}
23556
23557impl EventEmitter<EditorEvent> for Editor {}
23558
23559impl Focusable for Editor {
23560 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23561 self.focus_handle.clone()
23562 }
23563}
23564
23565impl Render for Editor {
23566 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23567 let settings = ThemeSettings::get_global(cx);
23568
23569 let mut text_style = match self.mode {
23570 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23571 color: cx.theme().colors().editor_foreground,
23572 font_family: settings.ui_font.family.clone(),
23573 font_features: settings.ui_font.features.clone(),
23574 font_fallbacks: settings.ui_font.fallbacks.clone(),
23575 font_size: rems(0.875).into(),
23576 font_weight: settings.ui_font.weight,
23577 line_height: relative(settings.buffer_line_height.value()),
23578 ..Default::default()
23579 },
23580 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23581 color: cx.theme().colors().editor_foreground,
23582 font_family: settings.buffer_font.family.clone(),
23583 font_features: settings.buffer_font.features.clone(),
23584 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23585 font_size: settings.buffer_font_size(cx).into(),
23586 font_weight: settings.buffer_font.weight,
23587 line_height: relative(settings.buffer_line_height.value()),
23588 ..Default::default()
23589 },
23590 };
23591 if let Some(text_style_refinement) = &self.text_style_refinement {
23592 text_style.refine(text_style_refinement)
23593 }
23594
23595 let background = match self.mode {
23596 EditorMode::SingleLine => cx.theme().system().transparent,
23597 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23598 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23599 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23600 };
23601
23602 EditorElement::new(
23603 &cx.entity(),
23604 EditorStyle {
23605 background,
23606 border: cx.theme().colors().border,
23607 local_player: cx.theme().players().local(),
23608 text: text_style,
23609 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23610 syntax: cx.theme().syntax().clone(),
23611 status: cx.theme().status().clone(),
23612 inlay_hints_style: make_inlay_hints_style(cx),
23613 edit_prediction_styles: make_suggestion_styles(cx),
23614 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23615 show_underlines: self.diagnostics_enabled(),
23616 },
23617 )
23618 }
23619}
23620
23621impl EntityInputHandler for Editor {
23622 fn text_for_range(
23623 &mut self,
23624 range_utf16: Range<usize>,
23625 adjusted_range: &mut Option<Range<usize>>,
23626 _: &mut Window,
23627 cx: &mut Context<Self>,
23628 ) -> Option<String> {
23629 let snapshot = self.buffer.read(cx).read(cx);
23630 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23631 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23632 if (start.0..end.0) != range_utf16 {
23633 adjusted_range.replace(start.0..end.0);
23634 }
23635 Some(snapshot.text_for_range(start..end).collect())
23636 }
23637
23638 fn selected_text_range(
23639 &mut self,
23640 ignore_disabled_input: bool,
23641 _: &mut Window,
23642 cx: &mut Context<Self>,
23643 ) -> Option<UTF16Selection> {
23644 // Prevent the IME menu from appearing when holding down an alphabetic key
23645 // while input is disabled.
23646 if !ignore_disabled_input && !self.input_enabled {
23647 return None;
23648 }
23649
23650 let selection = self.selections.newest::<OffsetUtf16>(cx);
23651 let range = selection.range();
23652
23653 Some(UTF16Selection {
23654 range: range.start.0..range.end.0,
23655 reversed: selection.reversed,
23656 })
23657 }
23658
23659 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23660 let snapshot = self.buffer.read(cx).read(cx);
23661 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23662 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23663 }
23664
23665 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23666 self.clear_highlights::<InputComposition>(cx);
23667 self.ime_transaction.take();
23668 }
23669
23670 fn replace_text_in_range(
23671 &mut self,
23672 range_utf16: Option<Range<usize>>,
23673 text: &str,
23674 window: &mut Window,
23675 cx: &mut Context<Self>,
23676 ) {
23677 if !self.input_enabled {
23678 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23679 return;
23680 }
23681
23682 self.transact(window, cx, |this, window, cx| {
23683 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23684 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23685 Some(this.selection_replacement_ranges(range_utf16, cx))
23686 } else {
23687 this.marked_text_ranges(cx)
23688 };
23689
23690 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23691 let newest_selection_id = this.selections.newest_anchor().id;
23692 this.selections
23693 .all::<OffsetUtf16>(cx)
23694 .iter()
23695 .zip(ranges_to_replace.iter())
23696 .find_map(|(selection, range)| {
23697 if selection.id == newest_selection_id {
23698 Some(
23699 (range.start.0 as isize - selection.head().0 as isize)
23700 ..(range.end.0 as isize - selection.head().0 as isize),
23701 )
23702 } else {
23703 None
23704 }
23705 })
23706 });
23707
23708 cx.emit(EditorEvent::InputHandled {
23709 utf16_range_to_replace: range_to_replace,
23710 text: text.into(),
23711 });
23712
23713 if let Some(new_selected_ranges) = new_selected_ranges {
23714 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23715 selections.select_ranges(new_selected_ranges)
23716 });
23717 this.backspace(&Default::default(), window, cx);
23718 }
23719
23720 this.handle_input(text, window, cx);
23721 });
23722
23723 if let Some(transaction) = self.ime_transaction {
23724 self.buffer.update(cx, |buffer, cx| {
23725 buffer.group_until_transaction(transaction, cx);
23726 });
23727 }
23728
23729 self.unmark_text(window, cx);
23730 }
23731
23732 fn replace_and_mark_text_in_range(
23733 &mut self,
23734 range_utf16: Option<Range<usize>>,
23735 text: &str,
23736 new_selected_range_utf16: Option<Range<usize>>,
23737 window: &mut Window,
23738 cx: &mut Context<Self>,
23739 ) {
23740 if !self.input_enabled {
23741 return;
23742 }
23743
23744 let transaction = self.transact(window, cx, |this, window, cx| {
23745 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23746 let snapshot = this.buffer.read(cx).read(cx);
23747 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23748 for marked_range in &mut marked_ranges {
23749 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23750 marked_range.start.0 += relative_range_utf16.start;
23751 marked_range.start =
23752 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23753 marked_range.end =
23754 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23755 }
23756 }
23757 Some(marked_ranges)
23758 } else if let Some(range_utf16) = range_utf16 {
23759 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23760 Some(this.selection_replacement_ranges(range_utf16, cx))
23761 } else {
23762 None
23763 };
23764
23765 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23766 let newest_selection_id = this.selections.newest_anchor().id;
23767 this.selections
23768 .all::<OffsetUtf16>(cx)
23769 .iter()
23770 .zip(ranges_to_replace.iter())
23771 .find_map(|(selection, range)| {
23772 if selection.id == newest_selection_id {
23773 Some(
23774 (range.start.0 as isize - selection.head().0 as isize)
23775 ..(range.end.0 as isize - selection.head().0 as isize),
23776 )
23777 } else {
23778 None
23779 }
23780 })
23781 });
23782
23783 cx.emit(EditorEvent::InputHandled {
23784 utf16_range_to_replace: range_to_replace,
23785 text: text.into(),
23786 });
23787
23788 if let Some(ranges) = ranges_to_replace {
23789 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23790 s.select_ranges(ranges)
23791 });
23792 }
23793
23794 let marked_ranges = {
23795 let snapshot = this.buffer.read(cx).read(cx);
23796 this.selections
23797 .disjoint_anchors_arc()
23798 .iter()
23799 .map(|selection| {
23800 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23801 })
23802 .collect::<Vec<_>>()
23803 };
23804
23805 if text.is_empty() {
23806 this.unmark_text(window, cx);
23807 } else {
23808 this.highlight_text::<InputComposition>(
23809 marked_ranges.clone(),
23810 HighlightStyle {
23811 underline: Some(UnderlineStyle {
23812 thickness: px(1.),
23813 color: None,
23814 wavy: false,
23815 }),
23816 ..Default::default()
23817 },
23818 cx,
23819 );
23820 }
23821
23822 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23823 let use_autoclose = this.use_autoclose;
23824 let use_auto_surround = this.use_auto_surround;
23825 this.set_use_autoclose(false);
23826 this.set_use_auto_surround(false);
23827 this.handle_input(text, window, cx);
23828 this.set_use_autoclose(use_autoclose);
23829 this.set_use_auto_surround(use_auto_surround);
23830
23831 if let Some(new_selected_range) = new_selected_range_utf16 {
23832 let snapshot = this.buffer.read(cx).read(cx);
23833 let new_selected_ranges = marked_ranges
23834 .into_iter()
23835 .map(|marked_range| {
23836 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23837 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23838 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23839 snapshot.clip_offset_utf16(new_start, Bias::Left)
23840 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23841 })
23842 .collect::<Vec<_>>();
23843
23844 drop(snapshot);
23845 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23846 selections.select_ranges(new_selected_ranges)
23847 });
23848 }
23849 });
23850
23851 self.ime_transaction = self.ime_transaction.or(transaction);
23852 if let Some(transaction) = self.ime_transaction {
23853 self.buffer.update(cx, |buffer, cx| {
23854 buffer.group_until_transaction(transaction, cx);
23855 });
23856 }
23857
23858 if self.text_highlights::<InputComposition>(cx).is_none() {
23859 self.ime_transaction.take();
23860 }
23861 }
23862
23863 fn bounds_for_range(
23864 &mut self,
23865 range_utf16: Range<usize>,
23866 element_bounds: gpui::Bounds<Pixels>,
23867 window: &mut Window,
23868 cx: &mut Context<Self>,
23869 ) -> Option<gpui::Bounds<Pixels>> {
23870 let text_layout_details = self.text_layout_details(window);
23871 let CharacterDimensions {
23872 em_width,
23873 em_advance,
23874 line_height,
23875 } = self.character_dimensions(window);
23876
23877 let snapshot = self.snapshot(window, cx);
23878 let scroll_position = snapshot.scroll_position();
23879 let scroll_left = scroll_position.x * em_advance;
23880
23881 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23882 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23883 + self.gutter_dimensions.full_width();
23884 let y = line_height * (start.row().as_f32() - scroll_position.y);
23885
23886 Some(Bounds {
23887 origin: element_bounds.origin + point(x, y),
23888 size: size(em_width, line_height),
23889 })
23890 }
23891
23892 fn character_index_for_point(
23893 &mut self,
23894 point: gpui::Point<Pixels>,
23895 _window: &mut Window,
23896 _cx: &mut Context<Self>,
23897 ) -> Option<usize> {
23898 let position_map = self.last_position_map.as_ref()?;
23899 if !position_map.text_hitbox.contains(&point) {
23900 return None;
23901 }
23902 let display_point = position_map.point_for_position(point).previous_valid;
23903 let anchor = position_map
23904 .snapshot
23905 .display_point_to_anchor(display_point, Bias::Left);
23906 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23907 Some(utf16_offset.0)
23908 }
23909}
23910
23911trait SelectionExt {
23912 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23913 fn spanned_rows(
23914 &self,
23915 include_end_if_at_line_start: bool,
23916 map: &DisplaySnapshot,
23917 ) -> Range<MultiBufferRow>;
23918}
23919
23920impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23921 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23922 let start = self
23923 .start
23924 .to_point(&map.buffer_snapshot)
23925 .to_display_point(map);
23926 let end = self
23927 .end
23928 .to_point(&map.buffer_snapshot)
23929 .to_display_point(map);
23930 if self.reversed {
23931 end..start
23932 } else {
23933 start..end
23934 }
23935 }
23936
23937 fn spanned_rows(
23938 &self,
23939 include_end_if_at_line_start: bool,
23940 map: &DisplaySnapshot,
23941 ) -> Range<MultiBufferRow> {
23942 let start = self.start.to_point(&map.buffer_snapshot);
23943 let mut end = self.end.to_point(&map.buffer_snapshot);
23944 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23945 end.row -= 1;
23946 }
23947
23948 let buffer_start = map.prev_line_boundary(start).0;
23949 let buffer_end = map.next_line_boundary(end).0;
23950 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23951 }
23952}
23953
23954impl<T: InvalidationRegion> InvalidationStack<T> {
23955 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23956 where
23957 S: Clone + ToOffset,
23958 {
23959 while let Some(region) = self.last() {
23960 let all_selections_inside_invalidation_ranges =
23961 if selections.len() == region.ranges().len() {
23962 selections
23963 .iter()
23964 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23965 .all(|(selection, invalidation_range)| {
23966 let head = selection.head().to_offset(buffer);
23967 invalidation_range.start <= head && invalidation_range.end >= head
23968 })
23969 } else {
23970 false
23971 };
23972
23973 if all_selections_inside_invalidation_ranges {
23974 break;
23975 } else {
23976 self.pop();
23977 }
23978 }
23979 }
23980}
23981
23982impl<T> Default for InvalidationStack<T> {
23983 fn default() -> Self {
23984 Self(Default::default())
23985 }
23986}
23987
23988impl<T> Deref for InvalidationStack<T> {
23989 type Target = Vec<T>;
23990
23991 fn deref(&self) -> &Self::Target {
23992 &self.0
23993 }
23994}
23995
23996impl<T> DerefMut for InvalidationStack<T> {
23997 fn deref_mut(&mut self) -> &mut Self::Target {
23998 &mut self.0
23999 }
24000}
24001
24002impl InvalidationRegion for SnippetState {
24003 fn ranges(&self) -> &[Range<Anchor>] {
24004 &self.ranges[self.active_index]
24005 }
24006}
24007
24008fn edit_prediction_edit_text(
24009 current_snapshot: &BufferSnapshot,
24010 edits: &[(Range<Anchor>, String)],
24011 edit_preview: &EditPreview,
24012 include_deletions: bool,
24013 cx: &App,
24014) -> HighlightedText {
24015 let edits = edits
24016 .iter()
24017 .map(|(anchor, text)| {
24018 (
24019 anchor.start.text_anchor..anchor.end.text_anchor,
24020 text.clone(),
24021 )
24022 })
24023 .collect::<Vec<_>>();
24024
24025 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24026}
24027
24028fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24029 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24030 // Just show the raw edit text with basic styling
24031 let mut text = String::new();
24032 let mut highlights = Vec::new();
24033
24034 let insertion_highlight_style = HighlightStyle {
24035 color: Some(cx.theme().colors().text),
24036 ..Default::default()
24037 };
24038
24039 for (_, edit_text) in edits {
24040 let start_offset = text.len();
24041 text.push_str(edit_text);
24042 let end_offset = text.len();
24043
24044 if start_offset < end_offset {
24045 highlights.push((start_offset..end_offset, insertion_highlight_style));
24046 }
24047 }
24048
24049 HighlightedText {
24050 text: text.into(),
24051 highlights,
24052 }
24053}
24054
24055pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24056 match severity {
24057 lsp::DiagnosticSeverity::ERROR => colors.error,
24058 lsp::DiagnosticSeverity::WARNING => colors.warning,
24059 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24060 lsp::DiagnosticSeverity::HINT => colors.info,
24061 _ => colors.ignored,
24062 }
24063}
24064
24065pub fn styled_runs_for_code_label<'a>(
24066 label: &'a CodeLabel,
24067 syntax_theme: &'a theme::SyntaxTheme,
24068) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24069 let fade_out = HighlightStyle {
24070 fade_out: Some(0.35),
24071 ..Default::default()
24072 };
24073
24074 let mut prev_end = label.filter_range.end;
24075 label
24076 .runs
24077 .iter()
24078 .enumerate()
24079 .flat_map(move |(ix, (range, highlight_id))| {
24080 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24081 style
24082 } else {
24083 return Default::default();
24084 };
24085 let muted_style = style.highlight(fade_out);
24086
24087 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24088 if range.start >= label.filter_range.end {
24089 if range.start > prev_end {
24090 runs.push((prev_end..range.start, fade_out));
24091 }
24092 runs.push((range.clone(), muted_style));
24093 } else if range.end <= label.filter_range.end {
24094 runs.push((range.clone(), style));
24095 } else {
24096 runs.push((range.start..label.filter_range.end, style));
24097 runs.push((label.filter_range.end..range.end, muted_style));
24098 }
24099 prev_end = cmp::max(prev_end, range.end);
24100
24101 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24102 runs.push((prev_end..label.text.len(), fade_out));
24103 }
24104
24105 runs
24106 })
24107}
24108
24109pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24110 let mut prev_index = 0;
24111 let mut prev_codepoint: Option<char> = None;
24112 text.char_indices()
24113 .chain([(text.len(), '\0')])
24114 .filter_map(move |(index, codepoint)| {
24115 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24116 let is_boundary = index == text.len()
24117 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24118 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24119 if is_boundary {
24120 let chunk = &text[prev_index..index];
24121 prev_index = index;
24122 Some(chunk)
24123 } else {
24124 None
24125 }
24126 })
24127}
24128
24129pub trait RangeToAnchorExt: Sized {
24130 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24131
24132 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24133 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24134 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24135 }
24136}
24137
24138impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24139 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24140 let start_offset = self.start.to_offset(snapshot);
24141 let end_offset = self.end.to_offset(snapshot);
24142 if start_offset == end_offset {
24143 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24144 } else {
24145 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24146 }
24147 }
24148}
24149
24150pub trait RowExt {
24151 fn as_f32(&self) -> f32;
24152
24153 fn next_row(&self) -> Self;
24154
24155 fn previous_row(&self) -> Self;
24156
24157 fn minus(&self, other: Self) -> u32;
24158}
24159
24160impl RowExt for DisplayRow {
24161 fn as_f32(&self) -> f32 {
24162 self.0 as f32
24163 }
24164
24165 fn next_row(&self) -> Self {
24166 Self(self.0 + 1)
24167 }
24168
24169 fn previous_row(&self) -> Self {
24170 Self(self.0.saturating_sub(1))
24171 }
24172
24173 fn minus(&self, other: Self) -> u32 {
24174 self.0 - other.0
24175 }
24176}
24177
24178impl RowExt for MultiBufferRow {
24179 fn as_f32(&self) -> f32 {
24180 self.0 as f32
24181 }
24182
24183 fn next_row(&self) -> Self {
24184 Self(self.0 + 1)
24185 }
24186
24187 fn previous_row(&self) -> Self {
24188 Self(self.0.saturating_sub(1))
24189 }
24190
24191 fn minus(&self, other: Self) -> u32 {
24192 self.0 - other.0
24193 }
24194}
24195
24196trait RowRangeExt {
24197 type Row;
24198
24199 fn len(&self) -> usize;
24200
24201 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24202}
24203
24204impl RowRangeExt for Range<MultiBufferRow> {
24205 type Row = MultiBufferRow;
24206
24207 fn len(&self) -> usize {
24208 (self.end.0 - self.start.0) as usize
24209 }
24210
24211 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24212 (self.start.0..self.end.0).map(MultiBufferRow)
24213 }
24214}
24215
24216impl RowRangeExt for Range<DisplayRow> {
24217 type Row = DisplayRow;
24218
24219 fn len(&self) -> usize {
24220 (self.end.0 - self.start.0) as usize
24221 }
24222
24223 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24224 (self.start.0..self.end.0).map(DisplayRow)
24225 }
24226}
24227
24228/// If select range has more than one line, we
24229/// just point the cursor to range.start.
24230fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24231 if range.start.row == range.end.row {
24232 range
24233 } else {
24234 range.start..range.start
24235 }
24236}
24237pub struct KillRing(ClipboardItem);
24238impl Global for KillRing {}
24239
24240const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24241
24242enum BreakpointPromptEditAction {
24243 Log,
24244 Condition,
24245 HitCondition,
24246}
24247
24248struct BreakpointPromptEditor {
24249 pub(crate) prompt: Entity<Editor>,
24250 editor: WeakEntity<Editor>,
24251 breakpoint_anchor: Anchor,
24252 breakpoint: Breakpoint,
24253 edit_action: BreakpointPromptEditAction,
24254 block_ids: HashSet<CustomBlockId>,
24255 editor_margins: Arc<Mutex<EditorMargins>>,
24256 _subscriptions: Vec<Subscription>,
24257}
24258
24259impl BreakpointPromptEditor {
24260 const MAX_LINES: u8 = 4;
24261
24262 fn new(
24263 editor: WeakEntity<Editor>,
24264 breakpoint_anchor: Anchor,
24265 breakpoint: Breakpoint,
24266 edit_action: BreakpointPromptEditAction,
24267 window: &mut Window,
24268 cx: &mut Context<Self>,
24269 ) -> Self {
24270 let base_text = match edit_action {
24271 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24272 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24273 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24274 }
24275 .map(|msg| msg.to_string())
24276 .unwrap_or_default();
24277
24278 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24279 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24280
24281 let prompt = cx.new(|cx| {
24282 let mut prompt = Editor::new(
24283 EditorMode::AutoHeight {
24284 min_lines: 1,
24285 max_lines: Some(Self::MAX_LINES as usize),
24286 },
24287 buffer,
24288 None,
24289 window,
24290 cx,
24291 );
24292 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24293 prompt.set_show_cursor_when_unfocused(false, cx);
24294 prompt.set_placeholder_text(
24295 match edit_action {
24296 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24297 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24298 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24299 },
24300 window,
24301 cx,
24302 );
24303
24304 prompt
24305 });
24306
24307 Self {
24308 prompt,
24309 editor,
24310 breakpoint_anchor,
24311 breakpoint,
24312 edit_action,
24313 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24314 block_ids: Default::default(),
24315 _subscriptions: vec![],
24316 }
24317 }
24318
24319 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24320 self.block_ids.extend(block_ids)
24321 }
24322
24323 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24324 if let Some(editor) = self.editor.upgrade() {
24325 let message = self
24326 .prompt
24327 .read(cx)
24328 .buffer
24329 .read(cx)
24330 .as_singleton()
24331 .expect("A multi buffer in breakpoint prompt isn't possible")
24332 .read(cx)
24333 .as_rope()
24334 .to_string();
24335
24336 editor.update(cx, |editor, cx| {
24337 editor.edit_breakpoint_at_anchor(
24338 self.breakpoint_anchor,
24339 self.breakpoint.clone(),
24340 match self.edit_action {
24341 BreakpointPromptEditAction::Log => {
24342 BreakpointEditAction::EditLogMessage(message.into())
24343 }
24344 BreakpointPromptEditAction::Condition => {
24345 BreakpointEditAction::EditCondition(message.into())
24346 }
24347 BreakpointPromptEditAction::HitCondition => {
24348 BreakpointEditAction::EditHitCondition(message.into())
24349 }
24350 },
24351 cx,
24352 );
24353
24354 editor.remove_blocks(self.block_ids.clone(), None, cx);
24355 cx.focus_self(window);
24356 });
24357 }
24358 }
24359
24360 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24361 self.editor
24362 .update(cx, |editor, cx| {
24363 editor.remove_blocks(self.block_ids.clone(), None, cx);
24364 window.focus(&editor.focus_handle);
24365 })
24366 .log_err();
24367 }
24368
24369 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24370 let settings = ThemeSettings::get_global(cx);
24371 let text_style = TextStyle {
24372 color: if self.prompt.read(cx).read_only(cx) {
24373 cx.theme().colors().text_disabled
24374 } else {
24375 cx.theme().colors().text
24376 },
24377 font_family: settings.buffer_font.family.clone(),
24378 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24379 font_size: settings.buffer_font_size(cx).into(),
24380 font_weight: settings.buffer_font.weight,
24381 line_height: relative(settings.buffer_line_height.value()),
24382 ..Default::default()
24383 };
24384 EditorElement::new(
24385 &self.prompt,
24386 EditorStyle {
24387 background: cx.theme().colors().editor_background,
24388 local_player: cx.theme().players().local(),
24389 text: text_style,
24390 ..Default::default()
24391 },
24392 )
24393 }
24394}
24395
24396impl Render for BreakpointPromptEditor {
24397 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24398 let editor_margins = *self.editor_margins.lock();
24399 let gutter_dimensions = editor_margins.gutter;
24400 h_flex()
24401 .key_context("Editor")
24402 .bg(cx.theme().colors().editor_background)
24403 .border_y_1()
24404 .border_color(cx.theme().status().info_border)
24405 .size_full()
24406 .py(window.line_height() / 2.5)
24407 .on_action(cx.listener(Self::confirm))
24408 .on_action(cx.listener(Self::cancel))
24409 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24410 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24411 }
24412}
24413
24414impl Focusable for BreakpointPromptEditor {
24415 fn focus_handle(&self, cx: &App) -> FocusHandle {
24416 self.prompt.focus_handle(cx)
24417 }
24418}
24419
24420fn all_edits_insertions_or_deletions(
24421 edits: &Vec<(Range<Anchor>, String)>,
24422 snapshot: &MultiBufferSnapshot,
24423) -> bool {
24424 let mut all_insertions = true;
24425 let mut all_deletions = true;
24426
24427 for (range, new_text) in edits.iter() {
24428 let range_is_empty = range.to_offset(snapshot).is_empty();
24429 let text_is_empty = new_text.is_empty();
24430
24431 if range_is_empty != text_is_empty {
24432 if range_is_empty {
24433 all_deletions = false;
24434 } else {
24435 all_insertions = false;
24436 }
24437 } else {
24438 return false;
24439 }
24440
24441 if !all_insertions && !all_deletions {
24442 return false;
24443 }
24444 }
24445 all_insertions || all_deletions
24446}
24447
24448struct MissingEditPredictionKeybindingTooltip;
24449
24450impl Render for MissingEditPredictionKeybindingTooltip {
24451 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24452 ui::tooltip_container(cx, |container, cx| {
24453 container
24454 .flex_shrink_0()
24455 .max_w_80()
24456 .min_h(rems_from_px(124.))
24457 .justify_between()
24458 .child(
24459 v_flex()
24460 .flex_1()
24461 .text_ui_sm(cx)
24462 .child(Label::new("Conflict with Accept Keybinding"))
24463 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24464 )
24465 .child(
24466 h_flex()
24467 .pb_1()
24468 .gap_1()
24469 .items_end()
24470 .w_full()
24471 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24472 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24473 }))
24474 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24475 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24476 })),
24477 )
24478 })
24479 }
24480}
24481
24482#[derive(Debug, Clone, Copy, PartialEq)]
24483pub struct LineHighlight {
24484 pub background: Background,
24485 pub border: Option<gpui::Hsla>,
24486 pub include_gutter: bool,
24487 pub type_id: Option<TypeId>,
24488}
24489
24490struct LineManipulationResult {
24491 pub new_text: String,
24492 pub line_count_before: usize,
24493 pub line_count_after: usize,
24494}
24495
24496fn render_diff_hunk_controls(
24497 row: u32,
24498 status: &DiffHunkStatus,
24499 hunk_range: Range<Anchor>,
24500 is_created_file: bool,
24501 line_height: Pixels,
24502 editor: &Entity<Editor>,
24503 _window: &mut Window,
24504 cx: &mut App,
24505) -> AnyElement {
24506 h_flex()
24507 .h(line_height)
24508 .mr_1()
24509 .gap_1()
24510 .px_0p5()
24511 .pb_1()
24512 .border_x_1()
24513 .border_b_1()
24514 .border_color(cx.theme().colors().border_variant)
24515 .rounded_b_lg()
24516 .bg(cx.theme().colors().editor_background)
24517 .gap_1()
24518 .block_mouse_except_scroll()
24519 .shadow_md()
24520 .child(if status.has_secondary_hunk() {
24521 Button::new(("stage", row as u64), "Stage")
24522 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24523 .tooltip({
24524 let focus_handle = editor.focus_handle(cx);
24525 move |window, cx| {
24526 Tooltip::for_action_in(
24527 "Stage Hunk",
24528 &::git::ToggleStaged,
24529 &focus_handle,
24530 window,
24531 cx,
24532 )
24533 }
24534 })
24535 .on_click({
24536 let editor = editor.clone();
24537 move |_event, _window, cx| {
24538 editor.update(cx, |editor, cx| {
24539 editor.stage_or_unstage_diff_hunks(
24540 true,
24541 vec![hunk_range.start..hunk_range.start],
24542 cx,
24543 );
24544 });
24545 }
24546 })
24547 } else {
24548 Button::new(("unstage", row as u64), "Unstage")
24549 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24550 .tooltip({
24551 let focus_handle = editor.focus_handle(cx);
24552 move |window, cx| {
24553 Tooltip::for_action_in(
24554 "Unstage Hunk",
24555 &::git::ToggleStaged,
24556 &focus_handle,
24557 window,
24558 cx,
24559 )
24560 }
24561 })
24562 .on_click({
24563 let editor = editor.clone();
24564 move |_event, _window, cx| {
24565 editor.update(cx, |editor, cx| {
24566 editor.stage_or_unstage_diff_hunks(
24567 false,
24568 vec![hunk_range.start..hunk_range.start],
24569 cx,
24570 );
24571 });
24572 }
24573 })
24574 })
24575 .child(
24576 Button::new(("restore", row as u64), "Restore")
24577 .tooltip({
24578 let focus_handle = editor.focus_handle(cx);
24579 move |window, cx| {
24580 Tooltip::for_action_in(
24581 "Restore Hunk",
24582 &::git::Restore,
24583 &focus_handle,
24584 window,
24585 cx,
24586 )
24587 }
24588 })
24589 .on_click({
24590 let editor = editor.clone();
24591 move |_event, window, cx| {
24592 editor.update(cx, |editor, cx| {
24593 let snapshot = editor.snapshot(window, cx);
24594 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24595 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24596 });
24597 }
24598 })
24599 .disabled(is_created_file),
24600 )
24601 .when(
24602 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24603 |el| {
24604 el.child(
24605 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24606 .shape(IconButtonShape::Square)
24607 .icon_size(IconSize::Small)
24608 // .disabled(!has_multiple_hunks)
24609 .tooltip({
24610 let focus_handle = editor.focus_handle(cx);
24611 move |window, cx| {
24612 Tooltip::for_action_in(
24613 "Next Hunk",
24614 &GoToHunk,
24615 &focus_handle,
24616 window,
24617 cx,
24618 )
24619 }
24620 })
24621 .on_click({
24622 let editor = editor.clone();
24623 move |_event, window, cx| {
24624 editor.update(cx, |editor, cx| {
24625 let snapshot = editor.snapshot(window, cx);
24626 let position =
24627 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24628 editor.go_to_hunk_before_or_after_position(
24629 &snapshot,
24630 position,
24631 Direction::Next,
24632 window,
24633 cx,
24634 );
24635 editor.expand_selected_diff_hunks(cx);
24636 });
24637 }
24638 }),
24639 )
24640 .child(
24641 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24642 .shape(IconButtonShape::Square)
24643 .icon_size(IconSize::Small)
24644 // .disabled(!has_multiple_hunks)
24645 .tooltip({
24646 let focus_handle = editor.focus_handle(cx);
24647 move |window, cx| {
24648 Tooltip::for_action_in(
24649 "Previous Hunk",
24650 &GoToPreviousHunk,
24651 &focus_handle,
24652 window,
24653 cx,
24654 )
24655 }
24656 })
24657 .on_click({
24658 let editor = editor.clone();
24659 move |_event, window, cx| {
24660 editor.update(cx, |editor, cx| {
24661 let snapshot = editor.snapshot(window, cx);
24662 let point =
24663 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24664 editor.go_to_hunk_before_or_after_position(
24665 &snapshot,
24666 point,
24667 Direction::Prev,
24668 window,
24669 cx,
24670 );
24671 editor.expand_selected_diff_hunks(cx);
24672 });
24673 }
24674 }),
24675 )
24676 },
24677 )
24678 .into_any_element()
24679}
24680
24681pub fn multibuffer_context_lines(cx: &App) -> u32 {
24682 EditorSettings::try_get(cx)
24683 .map(|settings| settings.excerpt_context_lines)
24684 .unwrap_or(2)
24685 .min(32)
24686}