1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::{AGENT_REPLICA_ID, ReplicaId};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 /// Move to a specific location in the active editor
642 MoveWithin {
643 target: Anchor,
644 snapshot: BufferSnapshot,
645 },
646 /// Move to a specific location in a different editor (not the active one)
647 MoveOutside {
648 target: language::Anchor,
649 snapshot: BufferSnapshot,
650 },
651}
652
653struct EditPredictionState {
654 inlay_ids: Vec<InlayId>,
655 completion: EditPrediction,
656 completion_id: Option<SharedString>,
657 invalidation_range: Option<Range<Anchor>>,
658}
659
660enum EditPredictionSettings {
661 Disabled,
662 Enabled {
663 show_in_menu: bool,
664 preview_requires_modifier: bool,
665 },
666}
667
668enum EditPredictionHighlight {}
669
670#[derive(Debug, Clone)]
671struct InlineDiagnostic {
672 message: SharedString,
673 group_id: usize,
674 is_primary: bool,
675 start: Point,
676 severity: lsp::DiagnosticSeverity,
677}
678
679pub enum MenuEditPredictionsPolicy {
680 Never,
681 ByProvider,
682}
683
684pub enum EditPredictionPreview {
685 /// Modifier is not pressed
686 Inactive { released_too_fast: bool },
687 /// Modifier pressed
688 Active {
689 since: Instant,
690 previous_scroll_position: Option<ScrollAnchor>,
691 },
692}
693
694impl EditPredictionPreview {
695 pub fn released_too_fast(&self) -> bool {
696 match self {
697 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
698 EditPredictionPreview::Active { .. } => false,
699 }
700 }
701
702 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
703 if let EditPredictionPreview::Active {
704 previous_scroll_position,
705 ..
706 } = self
707 {
708 *previous_scroll_position = scroll_position;
709 }
710 }
711}
712
713pub struct ContextMenuOptions {
714 pub min_entries_visible: usize,
715 pub max_entries_visible: usize,
716 pub placement: Option<ContextMenuPlacement>,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ContextMenuPlacement {
721 Above,
722 Below,
723}
724
725#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
726struct EditorActionId(usize);
727
728impl EditorActionId {
729 pub fn post_inc(&mut self) -> Self {
730 let answer = self.0;
731
732 *self = Self(answer + 1);
733
734 Self(answer)
735 }
736}
737
738// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
739// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
740
741type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
742type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
743
744#[derive(Default)]
745struct ScrollbarMarkerState {
746 scrollbar_size: Size<Pixels>,
747 dirty: bool,
748 markers: Arc<[PaintQuad]>,
749 pending_refresh: Option<Task<Result<()>>>,
750}
751
752impl ScrollbarMarkerState {
753 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
754 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
755 }
756}
757
758#[derive(Clone, Copy, PartialEq, Eq)]
759pub enum MinimapVisibility {
760 Disabled,
761 Enabled {
762 /// The configuration currently present in the users settings.
763 setting_configuration: bool,
764 /// Whether to override the currently set visibility from the users setting.
765 toggle_override: bool,
766 },
767}
768
769impl MinimapVisibility {
770 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
771 if mode.is_full() {
772 Self::Enabled {
773 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
774 toggle_override: false,
775 }
776 } else {
777 Self::Disabled
778 }
779 }
780
781 fn hidden(&self) -> Self {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => Self::Enabled {
787 setting_configuration,
788 toggle_override: setting_configuration,
789 },
790 Self::Disabled => Self::Disabled,
791 }
792 }
793
794 fn disabled(&self) -> bool {
795 matches!(*self, Self::Disabled)
796 }
797
798 fn settings_visibility(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 ..
803 } => setting_configuration,
804 _ => false,
805 }
806 }
807
808 fn visible(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 toggle_override,
813 } => setting_configuration ^ toggle_override,
814 _ => false,
815 }
816 }
817
818 fn toggle_visibility(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 toggle_override,
822 setting_configuration,
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: !toggle_override,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn to_any(&self) -> &dyn std::any::Any;
880
881 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
882 None
883 }
884}
885
886struct ChangeLocation {
887 current: Option<Vec<Anchor>>,
888 original: Vec<Anchor>,
889}
890impl ChangeLocation {
891 fn locations(&self) -> &[Anchor] {
892 self.current.as_ref().unwrap_or(&self.original)
893 }
894}
895
896/// A set of caret positions, registered when the editor was edited.
897pub struct ChangeList {
898 changes: Vec<ChangeLocation>,
899 /// Currently "selected" change.
900 position: Option<usize>,
901}
902
903impl ChangeList {
904 pub fn new() -> Self {
905 Self {
906 changes: Vec::new(),
907 position: None,
908 }
909 }
910
911 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
912 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
913 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
914 if self.changes.is_empty() {
915 return None;
916 }
917
918 let prev = self.position.unwrap_or(self.changes.len());
919 let next = if direction == Direction::Prev {
920 prev.saturating_sub(count)
921 } else {
922 (prev + count).min(self.changes.len() - 1)
923 };
924 self.position = Some(next);
925 self.changes.get(next).map(|change| change.locations())
926 }
927
928 /// Adds a new change to the list, resetting the change list position.
929 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
930 self.position.take();
931 if let Some(last) = self.changes.last_mut()
932 && group
933 {
934 last.current = Some(new_positions)
935 } else {
936 self.changes.push(ChangeLocation {
937 original: new_positions,
938 current: None,
939 });
940 }
941 }
942
943 pub fn last(&self) -> Option<&[Anchor]> {
944 self.changes.last().map(|change| change.locations())
945 }
946
947 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.original.as_slice())
949 }
950
951 pub fn invert_last_group(&mut self) {
952 if let Some(last) = self.changes.last_mut()
953 && let Some(current) = last.current.as_mut()
954 {
955 mem::swap(&mut last.original, current);
956 }
957 }
958}
959
960#[derive(Clone)]
961struct InlineBlamePopoverState {
962 scroll_handle: ScrollHandle,
963 commit_message: Option<ParsedCommitMessage>,
964 markdown: Entity<Markdown>,
965}
966
967struct InlineBlamePopover {
968 position: gpui::Point<Pixels>,
969 hide_task: Option<Task<()>>,
970 popover_bounds: Option<Bounds<Pixels>>,
971 popover_state: InlineBlamePopoverState,
972 keyboard_grace: bool,
973}
974
975enum SelectionDragState {
976 /// State when no drag related activity is detected.
977 None,
978 /// State when the mouse is down on a selection that is about to be dragged.
979 ReadyToDrag {
980 selection: Selection<Anchor>,
981 click_position: gpui::Point<Pixels>,
982 mouse_down_time: Instant,
983 },
984 /// State when the mouse is dragging the selection in the editor.
985 Dragging {
986 selection: Selection<Anchor>,
987 drop_cursor: Selection<Anchor>,
988 hide_drop_cursor: bool,
989 },
990}
991
992enum ColumnarSelectionState {
993 FromMouse {
994 selection_tail: Anchor,
995 display_point: Option<DisplayPoint>,
996 },
997 FromSelection {
998 selection_tail: Anchor,
999 },
1000}
1001
1002/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1003/// a breakpoint on them.
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1005struct PhantomBreakpointIndicator {
1006 display_row: DisplayRow,
1007 /// There's a small debounce between hovering over the line and showing the indicator.
1008 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1009 is_active: bool,
1010 collides_with_existing_breakpoint: bool,
1011}
1012
1013/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1014///
1015/// See the [module level documentation](self) for more information.
1016pub struct Editor {
1017 focus_handle: FocusHandle,
1018 last_focused_descendant: Option<WeakFocusHandle>,
1019 /// The text buffer being edited
1020 buffer: Entity<MultiBuffer>,
1021 /// Map of how text in the buffer should be displayed.
1022 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1023 pub display_map: Entity<DisplayMap>,
1024 placeholder_display_map: Option<Entity<DisplayMap>>,
1025 pub selections: SelectionsCollection,
1026 pub scroll_manager: ScrollManager,
1027 /// When inline assist editors are linked, they all render cursors because
1028 /// typing enters text into each of them, even the ones that aren't focused.
1029 pub(crate) show_cursor_when_unfocused: bool,
1030 columnar_selection_state: Option<ColumnarSelectionState>,
1031 add_selections_state: Option<AddSelectionsState>,
1032 select_next_state: Option<SelectNextState>,
1033 select_prev_state: Option<SelectNextState>,
1034 selection_history: SelectionHistory,
1035 defer_selection_effects: bool,
1036 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1037 autoclose_regions: Vec<AutocloseRegion>,
1038 snippet_stack: InvalidationStack<SnippetState>,
1039 select_syntax_node_history: SelectSyntaxNodeHistory,
1040 ime_transaction: Option<TransactionId>,
1041 pub diagnostics_max_severity: DiagnosticSeverity,
1042 active_diagnostics: ActiveDiagnostic,
1043 show_inline_diagnostics: bool,
1044 inline_diagnostics_update: Task<()>,
1045 inline_diagnostics_enabled: bool,
1046 diagnostics_enabled: bool,
1047 word_completions_enabled: bool,
1048 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1049 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1050 hard_wrap: Option<usize>,
1051 project: Option<Entity<Project>>,
1052 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1053 completion_provider: Option<Rc<dyn CompletionProvider>>,
1054 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1055 blink_manager: Entity<BlinkManager>,
1056 show_cursor_names: bool,
1057 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1058 pub show_local_selections: bool,
1059 mode: EditorMode,
1060 show_breadcrumbs: bool,
1061 show_gutter: bool,
1062 show_scrollbars: ScrollbarAxes,
1063 minimap_visibility: MinimapVisibility,
1064 offset_content: bool,
1065 disable_expand_excerpt_buttons: bool,
1066 show_line_numbers: Option<bool>,
1067 use_relative_line_numbers: Option<bool>,
1068 show_git_diff_gutter: Option<bool>,
1069 show_code_actions: Option<bool>,
1070 show_runnables: Option<bool>,
1071 show_breakpoints: Option<bool>,
1072 show_wrap_guides: Option<bool>,
1073 show_indent_guides: Option<bool>,
1074 highlight_order: usize,
1075 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1076 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1077 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1078 scrollbar_marker_state: ScrollbarMarkerState,
1079 active_indent_guides_state: ActiveIndentGuidesState,
1080 nav_history: Option<ItemNavHistory>,
1081 context_menu: RefCell<Option<CodeContextMenu>>,
1082 context_menu_options: Option<ContextMenuOptions>,
1083 mouse_context_menu: Option<MouseContextMenu>,
1084 completion_tasks: Vec<(CompletionId, Task<()>)>,
1085 inline_blame_popover: Option<InlineBlamePopover>,
1086 inline_blame_popover_show_task: Option<Task<()>>,
1087 signature_help_state: SignatureHelpState,
1088 auto_signature_help: Option<bool>,
1089 find_all_references_task_sources: Vec<Anchor>,
1090 next_completion_id: CompletionId,
1091 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1092 code_actions_task: Option<Task<Result<()>>>,
1093 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 document_highlights_task: Option<Task<()>>,
1096 linked_editing_range_task: Option<Task<Option<()>>>,
1097 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1098 pending_rename: Option<RenameState>,
1099 searchable: bool,
1100 cursor_shape: CursorShape,
1101 current_line_highlight: Option<CurrentLineHighlight>,
1102 collapse_matches: bool,
1103 autoindent_mode: Option<AutoindentMode>,
1104 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1105 input_enabled: bool,
1106 use_modal_editing: bool,
1107 read_only: bool,
1108 leader_id: Option<CollaboratorId>,
1109 remote_id: Option<ViewId>,
1110 pub hover_state: HoverState,
1111 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1112 gutter_hovered: bool,
1113 hovered_link_state: Option<HoveredLinkState>,
1114 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1115 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1116 active_edit_prediction: Option<EditPredictionState>,
1117 /// Used to prevent flickering as the user types while the menu is open
1118 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1119 edit_prediction_settings: EditPredictionSettings,
1120 edit_predictions_hidden_for_vim_mode: bool,
1121 show_edit_predictions_override: Option<bool>,
1122 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1123 edit_prediction_preview: EditPredictionPreview,
1124 edit_prediction_indent_conflict: bool,
1125 edit_prediction_requires_modifier_in_indent_conflict: bool,
1126 inlay_hint_cache: InlayHintCache,
1127 next_inlay_id: u32,
1128 next_color_inlay_id: u32,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 serialize_dirty_buffers: bool,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191 selection_drag_state: SelectionDragState,
1192 colors: Option<LspColorData>,
1193 refresh_colors_task: Task<()>,
1194 folding_newlines: Task<()>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1199enum NextScrollCursorCenterTopBottom {
1200 #[default]
1201 Center,
1202 Top,
1203 Bottom,
1204}
1205
1206impl NextScrollCursorCenterTopBottom {
1207 fn next(&self) -> Self {
1208 match self {
1209 Self::Center => Self::Top,
1210 Self::Top => Self::Bottom,
1211 Self::Bottom => Self::Center,
1212 }
1213 }
1214}
1215
1216#[derive(Clone)]
1217pub struct EditorSnapshot {
1218 pub mode: EditorMode,
1219 show_gutter: bool,
1220 show_line_numbers: Option<bool>,
1221 show_git_diff_gutter: Option<bool>,
1222 show_code_actions: Option<bool>,
1223 show_runnables: Option<bool>,
1224 show_breakpoints: Option<bool>,
1225 git_blame_gutter_max_author_length: Option<usize>,
1226 pub display_snapshot: DisplaySnapshot,
1227 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1228 is_focused: bool,
1229 scroll_anchor: ScrollAnchor,
1230 ongoing_scroll: OngoingScroll,
1231 current_line_highlight: CurrentLineHighlight,
1232 gutter_hovered: bool,
1233}
1234
1235#[derive(Default, Debug, Clone, Copy)]
1236pub struct GutterDimensions {
1237 pub left_padding: Pixels,
1238 pub right_padding: Pixels,
1239 pub width: Pixels,
1240 pub margin: Pixels,
1241 pub git_blame_entries_width: Option<Pixels>,
1242}
1243
1244impl GutterDimensions {
1245 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1246 Self {
1247 margin: Self::default_gutter_margin(font_id, font_size, cx),
1248 ..Default::default()
1249 }
1250 }
1251
1252 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1253 -cx.text_system().descent(font_id, font_size)
1254 }
1255 /// The full width of the space taken up by the gutter.
1256 pub fn full_width(&self) -> Pixels {
1257 self.margin + self.width
1258 }
1259
1260 /// The width of the space reserved for the fold indicators,
1261 /// use alongside 'justify_end' and `gutter_width` to
1262 /// right align content with the line numbers
1263 pub fn fold_area_width(&self) -> Pixels {
1264 self.margin + self.right_padding
1265 }
1266}
1267
1268struct CharacterDimensions {
1269 em_width: Pixels,
1270 em_advance: Pixels,
1271 line_height: Pixels,
1272}
1273
1274#[derive(Debug)]
1275pub struct RemoteSelection {
1276 pub replica_id: ReplicaId,
1277 pub selection: Selection<Anchor>,
1278 pub cursor_shape: CursorShape,
1279 pub collaborator_id: CollaboratorId,
1280 pub line_mode: bool,
1281 pub user_name: Option<SharedString>,
1282 pub color: PlayerColor,
1283}
1284
1285#[derive(Clone, Debug)]
1286struct SelectionHistoryEntry {
1287 selections: Arc<[Selection<Anchor>]>,
1288 select_next_state: Option<SelectNextState>,
1289 select_prev_state: Option<SelectNextState>,
1290 add_selections_state: Option<AddSelectionsState>,
1291}
1292
1293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1294enum SelectionHistoryMode {
1295 Normal,
1296 Undoing,
1297 Redoing,
1298 Skipping,
1299}
1300
1301#[derive(Clone, PartialEq, Eq, Hash)]
1302struct HoveredCursor {
1303 replica_id: u16,
1304 selection_id: usize,
1305}
1306
1307impl Default for SelectionHistoryMode {
1308 fn default() -> Self {
1309 Self::Normal
1310 }
1311}
1312
1313#[derive(Debug)]
1314/// SelectionEffects controls the side-effects of updating the selection.
1315///
1316/// The default behaviour does "what you mostly want":
1317/// - it pushes to the nav history if the cursor moved by >10 lines
1318/// - it re-triggers completion requests
1319/// - it scrolls to fit
1320///
1321/// You might want to modify these behaviours. For example when doing a "jump"
1322/// like go to definition, we always want to add to nav history; but when scrolling
1323/// in vim mode we never do.
1324///
1325/// Similarly, you might want to disable scrolling if you don't want the viewport to
1326/// move.
1327#[derive(Clone)]
1328pub struct SelectionEffects {
1329 nav_history: Option<bool>,
1330 completions: bool,
1331 scroll: Option<Autoscroll>,
1332}
1333
1334impl Default for SelectionEffects {
1335 fn default() -> Self {
1336 Self {
1337 nav_history: None,
1338 completions: true,
1339 scroll: Some(Autoscroll::fit()),
1340 }
1341 }
1342}
1343impl SelectionEffects {
1344 pub fn scroll(scroll: Autoscroll) -> Self {
1345 Self {
1346 scroll: Some(scroll),
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn no_scroll() -> Self {
1352 Self {
1353 scroll: None,
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn completions(self, completions: bool) -> Self {
1359 Self {
1360 completions,
1361 ..self
1362 }
1363 }
1364
1365 pub fn nav_history(self, nav_history: bool) -> Self {
1366 Self {
1367 nav_history: Some(nav_history),
1368 ..self
1369 }
1370 }
1371}
1372
1373struct DeferredSelectionEffectsState {
1374 changed: bool,
1375 effects: SelectionEffects,
1376 old_cursor_position: Anchor,
1377 history_entry: SelectionHistoryEntry,
1378}
1379
1380#[derive(Default)]
1381struct SelectionHistory {
1382 #[allow(clippy::type_complexity)]
1383 selections_by_transaction:
1384 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1385 mode: SelectionHistoryMode,
1386 undo_stack: VecDeque<SelectionHistoryEntry>,
1387 redo_stack: VecDeque<SelectionHistoryEntry>,
1388}
1389
1390impl SelectionHistory {
1391 #[track_caller]
1392 fn insert_transaction(
1393 &mut self,
1394 transaction_id: TransactionId,
1395 selections: Arc<[Selection<Anchor>]>,
1396 ) {
1397 if selections.is_empty() {
1398 log::error!(
1399 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1400 std::panic::Location::caller()
1401 );
1402 return;
1403 }
1404 self.selections_by_transaction
1405 .insert(transaction_id, (selections, None));
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction(
1410 &self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get(&transaction_id)
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction_mut(
1418 &mut self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get_mut(&transaction_id)
1422 }
1423
1424 fn push(&mut self, entry: SelectionHistoryEntry) {
1425 if !entry.selections.is_empty() {
1426 match self.mode {
1427 SelectionHistoryMode::Normal => {
1428 self.push_undo(entry);
1429 self.redo_stack.clear();
1430 }
1431 SelectionHistoryMode::Undoing => self.push_redo(entry),
1432 SelectionHistoryMode::Redoing => self.push_undo(entry),
1433 SelectionHistoryMode::Skipping => {}
1434 }
1435 }
1436 }
1437
1438 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .undo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.undo_stack.push_back(entry);
1445 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.undo_stack.pop_front();
1447 }
1448 }
1449 }
1450
1451 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1452 if self
1453 .redo_stack
1454 .back()
1455 .is_none_or(|e| e.selections != entry.selections)
1456 {
1457 self.redo_stack.push_back(entry);
1458 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1459 self.redo_stack.pop_front();
1460 }
1461 }
1462 }
1463}
1464
1465#[derive(Clone, Copy)]
1466pub struct RowHighlightOptions {
1467 pub autoscroll: bool,
1468 pub include_gutter: bool,
1469}
1470
1471impl Default for RowHighlightOptions {
1472 fn default() -> Self {
1473 Self {
1474 autoscroll: Default::default(),
1475 include_gutter: true,
1476 }
1477 }
1478}
1479
1480struct RowHighlight {
1481 index: usize,
1482 range: Range<Anchor>,
1483 color: Hsla,
1484 options: RowHighlightOptions,
1485 type_id: TypeId,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsState {
1490 groups: Vec<AddSelectionsGroup>,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsGroup {
1495 above: bool,
1496 stack: Vec<usize>,
1497}
1498
1499#[derive(Clone)]
1500struct SelectNextState {
1501 query: AhoCorasick,
1502 wordwise: bool,
1503 done: bool,
1504}
1505
1506impl std::fmt::Debug for SelectNextState {
1507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508 f.debug_struct(std::any::type_name::<Self>())
1509 .field("wordwise", &self.wordwise)
1510 .field("done", &self.done)
1511 .finish()
1512 }
1513}
1514
1515#[derive(Debug)]
1516struct AutocloseRegion {
1517 selection_id: usize,
1518 range: Range<Anchor>,
1519 pair: BracketPair,
1520}
1521
1522#[derive(Debug)]
1523struct SnippetState {
1524 ranges: Vec<Vec<Range<Anchor>>>,
1525 active_index: usize,
1526 choices: Vec<Option<Vec<String>>>,
1527}
1528
1529#[doc(hidden)]
1530pub struct RenameState {
1531 pub range: Range<Anchor>,
1532 pub old_name: Arc<str>,
1533 pub editor: Entity<Editor>,
1534 block_id: CustomBlockId,
1535}
1536
1537struct InvalidationStack<T>(Vec<T>);
1538
1539struct RegisteredEditPredictionProvider {
1540 provider: Arc<dyn EditPredictionProviderHandle>,
1541 _subscription: Subscription,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545pub struct ActiveDiagnosticGroup {
1546 pub active_range: Range<Anchor>,
1547 pub active_message: String,
1548 pub group_id: usize,
1549 pub blocks: HashSet<CustomBlockId>,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553
1554pub(crate) enum ActiveDiagnostic {
1555 None,
1556 All,
1557 Group(ActiveDiagnosticGroup),
1558}
1559
1560#[derive(Serialize, Deserialize, Clone, Debug)]
1561pub struct ClipboardSelection {
1562 /// The number of bytes in this selection.
1563 pub len: usize,
1564 /// Whether this was a full-line selection.
1565 pub is_entire_line: bool,
1566 /// The indentation of the first line when this content was originally copied.
1567 pub first_line_indent: u32,
1568}
1569
1570// selections, scroll behavior, was newest selection reversed
1571type SelectSyntaxNodeHistoryState = (
1572 Box<[Selection<usize>]>,
1573 SelectSyntaxNodeScrollBehavior,
1574 bool,
1575);
1576
1577#[derive(Default)]
1578struct SelectSyntaxNodeHistory {
1579 stack: Vec<SelectSyntaxNodeHistoryState>,
1580 // disable temporarily to allow changing selections without losing the stack
1581 pub disable_clearing: bool,
1582}
1583
1584impl SelectSyntaxNodeHistory {
1585 pub fn try_clear(&mut self) {
1586 if !self.disable_clearing {
1587 self.stack.clear();
1588 }
1589 }
1590
1591 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1592 self.stack.push(selection);
1593 }
1594
1595 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1596 self.stack.pop()
1597 }
1598}
1599
1600enum SelectSyntaxNodeScrollBehavior {
1601 CursorTop,
1602 FitSelection,
1603 CursorBottom,
1604}
1605
1606#[derive(Debug)]
1607pub(crate) struct NavigationData {
1608 cursor_anchor: Anchor,
1609 cursor_position: Point,
1610 scroll_anchor: ScrollAnchor,
1611 scroll_top_row: u32,
1612}
1613
1614#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1615pub enum GotoDefinitionKind {
1616 Symbol,
1617 Declaration,
1618 Type,
1619 Implementation,
1620}
1621
1622#[derive(Debug, Clone)]
1623enum InlayHintRefreshReason {
1624 ModifiersChanged(bool),
1625 Toggle(bool),
1626 SettingsChange(InlayHintSettings),
1627 NewLinesShown,
1628 BufferEdited(HashSet<Arc<Language>>),
1629 RefreshRequested,
1630 ExcerptsRemoved(Vec<ExcerptId>),
1631}
1632
1633impl InlayHintRefreshReason {
1634 fn description(&self) -> &'static str {
1635 match self {
1636 Self::ModifiersChanged(_) => "modifiers changed",
1637 Self::Toggle(_) => "toggle",
1638 Self::SettingsChange(_) => "settings change",
1639 Self::NewLinesShown => "new lines shown",
1640 Self::BufferEdited(_) => "buffer edited",
1641 Self::RefreshRequested => "refresh requested",
1642 Self::ExcerptsRemoved(_) => "excerpts removed",
1643 }
1644 }
1645}
1646
1647pub enum FormatTarget {
1648 Buffers(HashSet<Entity<Buffer>>),
1649 Ranges(Vec<Range<MultiBufferPoint>>),
1650}
1651
1652pub(crate) struct FocusedBlock {
1653 id: BlockId,
1654 focus_handle: WeakFocusHandle,
1655}
1656
1657#[derive(Clone)]
1658enum JumpData {
1659 MultiBufferRow {
1660 row: MultiBufferRow,
1661 line_offset_from_top: u32,
1662 },
1663 MultiBufferPoint {
1664 excerpt_id: ExcerptId,
1665 position: Point,
1666 anchor: text::Anchor,
1667 line_offset_from_top: u32,
1668 },
1669}
1670
1671pub enum MultibufferSelectionMode {
1672 First,
1673 All,
1674}
1675
1676#[derive(Clone, Copy, Debug, Default)]
1677pub struct RewrapOptions {
1678 pub override_language_settings: bool,
1679 pub preserve_existing_whitespace: bool,
1680}
1681
1682impl Editor {
1683 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1687 }
1688
1689 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1690 let buffer = cx.new(|cx| Buffer::local("", cx));
1691 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1692 Self::new(EditorMode::full(), buffer, None, window, cx)
1693 }
1694
1695 pub fn auto_height(
1696 min_lines: usize,
1697 max_lines: usize,
1698 window: &mut Window,
1699 cx: &mut Context<Self>,
1700 ) -> Self {
1701 let buffer = cx.new(|cx| Buffer::local("", cx));
1702 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1703 Self::new(
1704 EditorMode::AutoHeight {
1705 min_lines,
1706 max_lines: Some(max_lines),
1707 },
1708 buffer,
1709 None,
1710 window,
1711 cx,
1712 )
1713 }
1714
1715 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1716 /// The editor grows as tall as needed to fit its content.
1717 pub fn auto_height_unbounded(
1718 min_lines: usize,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| Buffer::local("", cx));
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(
1725 EditorMode::AutoHeight {
1726 min_lines,
1727 max_lines: None,
1728 },
1729 buffer,
1730 None,
1731 window,
1732 cx,
1733 )
1734 }
1735
1736 pub fn for_buffer(
1737 buffer: Entity<Buffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1743 Self::new(EditorMode::full(), buffer, project, window, cx)
1744 }
1745
1746 pub fn for_multibuffer(
1747 buffer: Entity<MultiBuffer>,
1748 project: Option<Entity<Project>>,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 Self::new(EditorMode::full(), buffer, project, window, cx)
1753 }
1754
1755 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1756 let mut clone = Self::new(
1757 self.mode.clone(),
1758 self.buffer.clone(),
1759 self.project.clone(),
1760 window,
1761 cx,
1762 );
1763 self.display_map.update(cx, |display_map, cx| {
1764 let snapshot = display_map.snapshot(cx);
1765 clone.display_map.update(cx, |display_map, cx| {
1766 display_map.set_state(&snapshot, cx);
1767 });
1768 });
1769 clone.folds_did_change(cx);
1770 clone.selections.clone_state(&self.selections);
1771 clone.scroll_manager.clone_state(&self.scroll_manager);
1772 clone.searchable = self.searchable;
1773 clone.read_only = self.read_only;
1774 clone
1775 }
1776
1777 pub fn new(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 Editor::new_internal(mode, buffer, project, None, window, cx)
1785 }
1786
1787 fn new_internal(
1788 mode: EditorMode,
1789 buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 display_map: Option<Entity<DisplayMap>>,
1792 window: &mut Window,
1793 cx: &mut Context<Self>,
1794 ) -> Self {
1795 debug_assert!(
1796 display_map.is_none() || mode.is_minimap(),
1797 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1798 );
1799
1800 let full_mode = mode.is_full();
1801 let is_minimap = mode.is_minimap();
1802 let diagnostics_max_severity = if full_mode {
1803 EditorSettings::get_global(cx)
1804 .diagnostics_max_severity
1805 .unwrap_or(DiagnosticSeverity::Hint)
1806 } else {
1807 DiagnosticSeverity::Off
1808 };
1809 let style = window.text_style();
1810 let font_size = style.font_size.to_pixels(window.rem_size());
1811 let editor = cx.entity().downgrade();
1812 let fold_placeholder = FoldPlaceholder {
1813 constrain_width: false,
1814 render: Arc::new(move |fold_id, fold_range, cx| {
1815 let editor = editor.clone();
1816 div()
1817 .id(fold_id)
1818 .bg(cx.theme().colors().ghost_element_background)
1819 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1820 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1821 .rounded_xs()
1822 .size_full()
1823 .cursor_pointer()
1824 .child("⋯")
1825 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1826 .on_click(move |_, _window, cx| {
1827 editor
1828 .update(cx, |editor, cx| {
1829 editor.unfold_ranges(
1830 &[fold_range.start..fold_range.end],
1831 true,
1832 false,
1833 cx,
1834 );
1835 cx.stop_propagation();
1836 })
1837 .ok();
1838 })
1839 .into_any()
1840 }),
1841 merge_adjacent: true,
1842 ..FoldPlaceholder::default()
1843 };
1844 let display_map = display_map.unwrap_or_else(|| {
1845 cx.new(|cx| {
1846 DisplayMap::new(
1847 buffer.clone(),
1848 style.font(),
1849 font_size,
1850 None,
1851 FILE_HEADER_HEIGHT,
1852 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1853 fold_placeholder,
1854 diagnostics_max_severity,
1855 cx,
1856 )
1857 })
1858 });
1859
1860 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1861
1862 let blink_manager = cx.new(|cx| {
1863 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1864 if is_minimap {
1865 blink_manager.disable(cx);
1866 }
1867 blink_manager
1868 });
1869
1870 let soft_wrap_mode_override =
1871 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1872
1873 let mut project_subscriptions = Vec::new();
1874 if full_mode && let Some(project) = project.as_ref() {
1875 project_subscriptions.push(cx.subscribe_in(
1876 project,
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 project::Event::RefreshCodeLens => {
1880 // we always query lens with actions, without storing them, always refreshing them
1881 }
1882 project::Event::RefreshInlayHints => {
1883 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1884 }
1885 project::Event::LanguageServerAdded(..)
1886 | project::Event::LanguageServerRemoved(..) => {
1887 if editor.tasks_update_task.is_none() {
1888 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1889 }
1890 }
1891 project::Event::SnippetEdit(id, snippet_edits) => {
1892 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1893 let focus_handle = editor.focus_handle(cx);
1894 if focus_handle.is_focused(window) {
1895 let snapshot = buffer.read(cx).snapshot();
1896 for (range, snippet) in snippet_edits {
1897 let editor_range =
1898 language::range_from_lsp(*range).to_offset(&snapshot);
1899 editor
1900 .insert_snippet(
1901 &[editor_range],
1902 snippet.clone(),
1903 window,
1904 cx,
1905 )
1906 .ok();
1907 }
1908 }
1909 }
1910 }
1911 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1912 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1913 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1914 }
1915 }
1916
1917 project::Event::EntryRenamed(transaction) => {
1918 let Some(workspace) = editor.workspace() else {
1919 return;
1920 };
1921 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1922 else {
1923 return;
1924 };
1925 if active_editor.entity_id() == cx.entity_id() {
1926 let edited_buffers_already_open = {
1927 let other_editors: Vec<Entity<Editor>> = workspace
1928 .read(cx)
1929 .panes()
1930 .iter()
1931 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1932 .filter(|editor| editor.entity_id() != cx.entity_id())
1933 .collect();
1934
1935 transaction.0.keys().all(|buffer| {
1936 other_editors.iter().any(|editor| {
1937 let multi_buffer = editor.read(cx).buffer();
1938 multi_buffer.read(cx).is_singleton()
1939 && multi_buffer.read(cx).as_singleton().map_or(
1940 false,
1941 |singleton| {
1942 singleton.entity_id() == buffer.entity_id()
1943 },
1944 )
1945 })
1946 })
1947 };
1948
1949 if !edited_buffers_already_open {
1950 let workspace = workspace.downgrade();
1951 let transaction = transaction.clone();
1952 cx.defer_in(window, move |_, window, cx| {
1953 cx.spawn_in(window, async move |editor, cx| {
1954 Self::open_project_transaction(
1955 &editor,
1956 workspace,
1957 transaction,
1958 "Rename".to_string(),
1959 cx,
1960 )
1961 .await
1962 .ok()
1963 })
1964 .detach();
1965 });
1966 }
1967 }
1968 }
1969
1970 _ => {}
1971 },
1972 ));
1973 if let Some(task_inventory) = project
1974 .read(cx)
1975 .task_store()
1976 .read(cx)
1977 .task_inventory()
1978 .cloned()
1979 {
1980 project_subscriptions.push(cx.observe_in(
1981 &task_inventory,
1982 window,
1983 |editor, _, window, cx| {
1984 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1985 },
1986 ));
1987 };
1988
1989 project_subscriptions.push(cx.subscribe_in(
1990 &project.read(cx).breakpoint_store(),
1991 window,
1992 |editor, _, event, window, cx| match event {
1993 BreakpointStoreEvent::ClearDebugLines => {
1994 editor.clear_row_highlights::<ActiveDebugLine>();
1995 editor.refresh_inline_values(cx);
1996 }
1997 BreakpointStoreEvent::SetDebugLine => {
1998 if editor.go_to_active_debug_line(window, cx) {
1999 cx.stop_propagation();
2000 }
2001
2002 editor.refresh_inline_values(cx);
2003 }
2004 _ => {}
2005 },
2006 ));
2007 let git_store = project.read(cx).git_store().clone();
2008 let project = project.clone();
2009 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2010 if let GitStoreEvent::RepositoryUpdated(
2011 _,
2012 RepositoryEvent::Updated {
2013 new_instance: true, ..
2014 },
2015 _,
2016 ) = event
2017 {
2018 this.load_diff_task = Some(
2019 update_uncommitted_diff_for_buffer(
2020 cx.entity(),
2021 &project,
2022 this.buffer.read(cx).all_buffers(),
2023 this.buffer.clone(),
2024 cx,
2025 )
2026 .shared(),
2027 );
2028 }
2029 }));
2030 }
2031
2032 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2033
2034 let inlay_hint_settings =
2035 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2036 let focus_handle = cx.focus_handle();
2037 if !is_minimap {
2038 cx.on_focus(&focus_handle, window, Self::handle_focus)
2039 .detach();
2040 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2041 .detach();
2042 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2043 .detach();
2044 cx.on_blur(&focus_handle, window, Self::handle_blur)
2045 .detach();
2046 cx.observe_pending_input(window, Self::observe_pending_input)
2047 .detach();
2048 }
2049
2050 let show_indent_guides =
2051 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2052 Some(false)
2053 } else {
2054 None
2055 };
2056
2057 let breakpoint_store = match (&mode, project.as_ref()) {
2058 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2059 _ => None,
2060 };
2061
2062 let mut code_action_providers = Vec::new();
2063 let mut load_uncommitted_diff = None;
2064 if let Some(project) = project.clone() {
2065 load_uncommitted_diff = Some(
2066 update_uncommitted_diff_for_buffer(
2067 cx.entity(),
2068 &project,
2069 buffer.read(cx).all_buffers(),
2070 buffer.clone(),
2071 cx,
2072 )
2073 .shared(),
2074 );
2075 code_action_providers.push(Rc::new(project) as Rc<_>);
2076 }
2077
2078 let mut editor = Self {
2079 focus_handle,
2080 show_cursor_when_unfocused: false,
2081 last_focused_descendant: None,
2082 buffer: buffer.clone(),
2083 display_map: display_map.clone(),
2084 placeholder_display_map: None,
2085 selections,
2086 scroll_manager: ScrollManager::new(cx),
2087 columnar_selection_state: None,
2088 add_selections_state: None,
2089 select_next_state: None,
2090 select_prev_state: None,
2091 selection_history: SelectionHistory::default(),
2092 defer_selection_effects: false,
2093 deferred_selection_effects_state: None,
2094 autoclose_regions: Vec::new(),
2095 snippet_stack: InvalidationStack::default(),
2096 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2097 ime_transaction: None,
2098 active_diagnostics: ActiveDiagnostic::None,
2099 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2100 inline_diagnostics_update: Task::ready(()),
2101 inline_diagnostics: Vec::new(),
2102 soft_wrap_mode_override,
2103 diagnostics_max_severity,
2104 hard_wrap: None,
2105 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2106 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2107 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2108 project,
2109 blink_manager: blink_manager.clone(),
2110 show_local_selections: true,
2111 show_scrollbars: ScrollbarAxes {
2112 horizontal: full_mode,
2113 vertical: full_mode,
2114 },
2115 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2116 offset_content: !matches!(mode, EditorMode::SingleLine),
2117 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2118 show_gutter: full_mode,
2119 show_line_numbers: (!full_mode).then_some(false),
2120 use_relative_line_numbers: None,
2121 disable_expand_excerpt_buttons: !full_mode,
2122 show_git_diff_gutter: None,
2123 show_code_actions: None,
2124 show_runnables: None,
2125 show_breakpoints: None,
2126 show_wrap_guides: None,
2127 show_indent_guides,
2128 highlight_order: 0,
2129 highlighted_rows: HashMap::default(),
2130 background_highlights: HashMap::default(),
2131 gutter_highlights: HashMap::default(),
2132 scrollbar_marker_state: ScrollbarMarkerState::default(),
2133 active_indent_guides_state: ActiveIndentGuidesState::default(),
2134 nav_history: None,
2135 context_menu: RefCell::new(None),
2136 context_menu_options: None,
2137 mouse_context_menu: None,
2138 completion_tasks: Vec::new(),
2139 inline_blame_popover: None,
2140 inline_blame_popover_show_task: None,
2141 signature_help_state: SignatureHelpState::default(),
2142 auto_signature_help: None,
2143 find_all_references_task_sources: Vec::new(),
2144 next_completion_id: 0,
2145 next_inlay_id: 0,
2146 code_action_providers,
2147 available_code_actions: None,
2148 code_actions_task: None,
2149 quick_selection_highlight_task: None,
2150 debounced_selection_highlight_task: None,
2151 document_highlights_task: None,
2152 linked_editing_range_task: None,
2153 pending_rename: None,
2154 searchable: !is_minimap,
2155 cursor_shape: EditorSettings::get_global(cx)
2156 .cursor_shape
2157 .unwrap_or_default(),
2158 current_line_highlight: None,
2159 autoindent_mode: Some(AutoindentMode::EachLine),
2160 collapse_matches: false,
2161 workspace: None,
2162 input_enabled: !is_minimap,
2163 use_modal_editing: full_mode,
2164 read_only: is_minimap,
2165 use_autoclose: true,
2166 use_auto_surround: true,
2167 auto_replace_emoji_shortcode: false,
2168 jsx_tag_auto_close_enabled_in_any_buffer: false,
2169 leader_id: None,
2170 remote_id: None,
2171 hover_state: HoverState::default(),
2172 pending_mouse_down: None,
2173 hovered_link_state: None,
2174 edit_prediction_provider: None,
2175 active_edit_prediction: None,
2176 stale_edit_prediction_in_menu: None,
2177 edit_prediction_preview: EditPredictionPreview::Inactive {
2178 released_too_fast: false,
2179 },
2180 inline_diagnostics_enabled: full_mode,
2181 diagnostics_enabled: full_mode,
2182 word_completions_enabled: full_mode,
2183 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2184 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2185 gutter_hovered: false,
2186 pixel_position_of_newest_cursor: None,
2187 last_bounds: None,
2188 last_position_map: None,
2189 expect_bounds_change: None,
2190 gutter_dimensions: GutterDimensions::default(),
2191 style: None,
2192 show_cursor_names: false,
2193 hovered_cursors: HashMap::default(),
2194 next_editor_action_id: EditorActionId::default(),
2195 editor_actions: Rc::default(),
2196 edit_predictions_hidden_for_vim_mode: false,
2197 show_edit_predictions_override: None,
2198 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2199 edit_prediction_settings: EditPredictionSettings::Disabled,
2200 edit_prediction_indent_conflict: false,
2201 edit_prediction_requires_modifier_in_indent_conflict: true,
2202 custom_context_menu: None,
2203 show_git_blame_gutter: false,
2204 show_git_blame_inline: false,
2205 show_selection_menu: None,
2206 show_git_blame_inline_delay_task: None,
2207 git_blame_inline_enabled: full_mode
2208 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2209 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2210 serialize_dirty_buffers: !is_minimap
2211 && ProjectSettings::get_global(cx)
2212 .session
2213 .restore_unsaved_buffers,
2214 blame: None,
2215 blame_subscription: None,
2216 tasks: BTreeMap::default(),
2217
2218 breakpoint_store,
2219 gutter_breakpoint_indicator: (None, None),
2220 hovered_diff_hunk_row: None,
2221 _subscriptions: (!is_minimap)
2222 .then(|| {
2223 vec![
2224 cx.observe(&buffer, Self::on_buffer_changed),
2225 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2226 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2227 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2228 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2229 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2230 cx.observe_window_activation(window, |editor, window, cx| {
2231 let active = window.is_window_active();
2232 editor.blink_manager.update(cx, |blink_manager, cx| {
2233 if active {
2234 blink_manager.enable(cx);
2235 } else {
2236 blink_manager.disable(cx);
2237 }
2238 });
2239 if active {
2240 editor.show_mouse_cursor(cx);
2241 }
2242 }),
2243 ]
2244 })
2245 .unwrap_or_default(),
2246 tasks_update_task: None,
2247 pull_diagnostics_task: Task::ready(()),
2248 colors: None,
2249 refresh_colors_task: Task::ready(()),
2250 next_color_inlay_id: 0,
2251 linked_edit_ranges: Default::default(),
2252 in_project_search: false,
2253 previous_search_ranges: None,
2254 breadcrumb_header: None,
2255 focused_block: None,
2256 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2257 addons: HashMap::default(),
2258 registered_buffers: HashMap::default(),
2259 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2260 selection_mark_mode: false,
2261 toggle_fold_multiple_buffers: Task::ready(()),
2262 serialize_selections: Task::ready(()),
2263 serialize_folds: Task::ready(()),
2264 text_style_refinement: None,
2265 load_diff_task: load_uncommitted_diff,
2266 temporary_diff_override: false,
2267 mouse_cursor_hidden: false,
2268 minimap: None,
2269 hide_mouse_mode: EditorSettings::get_global(cx)
2270 .hide_mouse
2271 .unwrap_or_default(),
2272 change_list: ChangeList::new(),
2273 mode,
2274 selection_drag_state: SelectionDragState::None,
2275 folding_newlines: Task::ready(()),
2276 lookup_key: None,
2277 };
2278
2279 if is_minimap {
2280 return editor;
2281 }
2282
2283 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2284 editor
2285 ._subscriptions
2286 .push(cx.observe(breakpoints, |_, _, cx| {
2287 cx.notify();
2288 }));
2289 }
2290 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2291 editor._subscriptions.extend(project_subscriptions);
2292
2293 editor._subscriptions.push(cx.subscribe_in(
2294 &cx.entity(),
2295 window,
2296 |editor, _, e: &EditorEvent, window, cx| match e {
2297 EditorEvent::ScrollPositionChanged { local, .. } => {
2298 if *local {
2299 let new_anchor = editor.scroll_manager.anchor();
2300 let snapshot = editor.snapshot(window, cx);
2301 editor.update_restoration_data(cx, move |data| {
2302 data.scroll_position = (
2303 new_anchor.top_row(snapshot.buffer_snapshot()),
2304 new_anchor.offset,
2305 );
2306 });
2307 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2308 editor.inline_blame_popover.take();
2309 }
2310 }
2311 EditorEvent::Edited { .. } => {
2312 if !vim_enabled(cx) {
2313 let (map, selections) = editor.selections.all_adjusted_display(cx);
2314 let pop_state = editor
2315 .change_list
2316 .last()
2317 .map(|previous| {
2318 previous.len() == selections.len()
2319 && previous.iter().enumerate().all(|(ix, p)| {
2320 p.to_display_point(&map).row()
2321 == selections[ix].head().row()
2322 })
2323 })
2324 .unwrap_or(false);
2325 let new_positions = selections
2326 .into_iter()
2327 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2328 .collect();
2329 editor
2330 .change_list
2331 .push_to_change_list(pop_state, new_positions);
2332 }
2333 }
2334 _ => (),
2335 },
2336 ));
2337
2338 if let Some(dap_store) = editor
2339 .project
2340 .as_ref()
2341 .map(|project| project.read(cx).dap_store())
2342 {
2343 let weak_editor = cx.weak_entity();
2344
2345 editor
2346 ._subscriptions
2347 .push(
2348 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2349 let session_entity = cx.entity();
2350 weak_editor
2351 .update(cx, |editor, cx| {
2352 editor._subscriptions.push(
2353 cx.subscribe(&session_entity, Self::on_debug_session_event),
2354 );
2355 })
2356 .ok();
2357 }),
2358 );
2359
2360 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2361 editor
2362 ._subscriptions
2363 .push(cx.subscribe(&session, Self::on_debug_session_event));
2364 }
2365 }
2366
2367 // skip adding the initial selection to selection history
2368 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2369 editor.end_selection(window, cx);
2370 editor.selection_history.mode = SelectionHistoryMode::Normal;
2371
2372 editor.scroll_manager.show_scrollbars(window, cx);
2373 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2374
2375 if full_mode {
2376 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2377 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2378
2379 if editor.git_blame_inline_enabled {
2380 editor.start_git_blame_inline(false, window, cx);
2381 }
2382
2383 editor.go_to_active_debug_line(window, cx);
2384
2385 if let Some(buffer) = buffer.read(cx).as_singleton()
2386 && let Some(project) = editor.project()
2387 {
2388 let handle = project.update(cx, |project, cx| {
2389 project.register_buffer_with_language_servers(&buffer, cx)
2390 });
2391 editor
2392 .registered_buffers
2393 .insert(buffer.read(cx).remote_id(), handle);
2394 }
2395
2396 editor.minimap =
2397 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2398 editor.colors = Some(LspColorData::new(cx));
2399 editor.update_lsp_data(false, None, window, cx);
2400 }
2401
2402 if editor.mode.is_full() {
2403 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2404 }
2405
2406 editor
2407 }
2408
2409 pub fn deploy_mouse_context_menu(
2410 &mut self,
2411 position: gpui::Point<Pixels>,
2412 context_menu: Entity<ContextMenu>,
2413 window: &mut Window,
2414 cx: &mut Context<Self>,
2415 ) {
2416 self.mouse_context_menu = Some(MouseContextMenu::new(
2417 self,
2418 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2419 context_menu,
2420 window,
2421 cx,
2422 ));
2423 }
2424
2425 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2426 self.mouse_context_menu
2427 .as_ref()
2428 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2429 }
2430
2431 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2432 if self
2433 .selections
2434 .pending_anchor()
2435 .is_some_and(|pending_selection| {
2436 let snapshot = self.buffer().read(cx).snapshot(cx);
2437 pending_selection.range().includes(range, &snapshot)
2438 })
2439 {
2440 return true;
2441 }
2442
2443 self.selections
2444 .disjoint_in_range::<usize>(range.clone(), cx)
2445 .into_iter()
2446 .any(|selection| {
2447 // This is needed to cover a corner case, if we just check for an existing
2448 // selection in the fold range, having a cursor at the start of the fold
2449 // marks it as selected. Non-empty selections don't cause this.
2450 let length = selection.end - selection.start;
2451 length > 0
2452 })
2453 }
2454
2455 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2456 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2457 }
2458
2459 fn key_context_internal(
2460 &self,
2461 has_active_edit_prediction: bool,
2462 window: &Window,
2463 cx: &App,
2464 ) -> KeyContext {
2465 let mut key_context = KeyContext::new_with_defaults();
2466 key_context.add("Editor");
2467 let mode = match self.mode {
2468 EditorMode::SingleLine => "single_line",
2469 EditorMode::AutoHeight { .. } => "auto_height",
2470 EditorMode::Minimap { .. } => "minimap",
2471 EditorMode::Full { .. } => "full",
2472 };
2473
2474 if EditorSettings::jupyter_enabled(cx) {
2475 key_context.add("jupyter");
2476 }
2477
2478 key_context.set("mode", mode);
2479 if self.pending_rename.is_some() {
2480 key_context.add("renaming");
2481 }
2482
2483 match self.context_menu.borrow().as_ref() {
2484 Some(CodeContextMenu::Completions(menu)) => {
2485 if menu.visible() {
2486 key_context.add("menu");
2487 key_context.add("showing_completions");
2488 }
2489 }
2490 Some(CodeContextMenu::CodeActions(menu)) => {
2491 if menu.visible() {
2492 key_context.add("menu");
2493 key_context.add("showing_code_actions")
2494 }
2495 }
2496 None => {}
2497 }
2498
2499 if self.signature_help_state.has_multiple_signatures() {
2500 key_context.add("showing_signature_help");
2501 }
2502
2503 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2504 if !self.focus_handle(cx).contains_focused(window, cx)
2505 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2506 {
2507 for addon in self.addons.values() {
2508 addon.extend_key_context(&mut key_context, cx)
2509 }
2510 }
2511
2512 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2513 if let Some(extension) = singleton_buffer
2514 .read(cx)
2515 .file()
2516 .and_then(|file| file.path().extension())
2517 {
2518 key_context.set("extension", extension.to_string());
2519 }
2520 } else {
2521 key_context.add("multibuffer");
2522 }
2523
2524 if has_active_edit_prediction {
2525 if self.edit_prediction_in_conflict() {
2526 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2527 } else {
2528 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2529 key_context.add("copilot_suggestion");
2530 }
2531 }
2532
2533 if self.selection_mark_mode {
2534 key_context.add("selection_mode");
2535 }
2536
2537 key_context
2538 }
2539
2540 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2541 self.last_bounds.as_ref()
2542 }
2543
2544 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2545 if self.mouse_cursor_hidden {
2546 self.mouse_cursor_hidden = false;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2552 let hide_mouse_cursor = match origin {
2553 HideMouseCursorOrigin::TypingAction => {
2554 matches!(
2555 self.hide_mouse_mode,
2556 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2557 )
2558 }
2559 HideMouseCursorOrigin::MovementAction => {
2560 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2561 }
2562 };
2563 if self.mouse_cursor_hidden != hide_mouse_cursor {
2564 self.mouse_cursor_hidden = hide_mouse_cursor;
2565 cx.notify();
2566 }
2567 }
2568
2569 pub fn edit_prediction_in_conflict(&self) -> bool {
2570 if !self.show_edit_predictions_in_menu() {
2571 return false;
2572 }
2573
2574 let showing_completions = self
2575 .context_menu
2576 .borrow()
2577 .as_ref()
2578 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2579
2580 showing_completions
2581 || self.edit_prediction_requires_modifier()
2582 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2583 // bindings to insert tab characters.
2584 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2585 }
2586
2587 pub fn accept_edit_prediction_keybind(
2588 &self,
2589 accept_partial: bool,
2590 window: &Window,
2591 cx: &App,
2592 ) -> AcceptEditPredictionBinding {
2593 let key_context = self.key_context_internal(true, window, cx);
2594 let in_conflict = self.edit_prediction_in_conflict();
2595
2596 let bindings = if accept_partial {
2597 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2598 } else {
2599 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2600 };
2601
2602 // TODO: if the binding contains multiple keystrokes, display all of them, not
2603 // just the first one.
2604 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2605 !in_conflict
2606 || binding
2607 .keystrokes()
2608 .first()
2609 .is_some_and(|keystroke| keystroke.modifiers().modified())
2610 }))
2611 }
2612
2613 pub fn new_file(
2614 workspace: &mut Workspace,
2615 _: &workspace::NewFile,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) {
2619 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2620 "Failed to create buffer",
2621 window,
2622 cx,
2623 |e, _, _| match e.error_code() {
2624 ErrorCode::RemoteUpgradeRequired => Some(format!(
2625 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2626 e.error_tag("required").unwrap_or("the latest version")
2627 )),
2628 _ => None,
2629 },
2630 );
2631 }
2632
2633 pub fn new_in_workspace(
2634 workspace: &mut Workspace,
2635 window: &mut Window,
2636 cx: &mut Context<Workspace>,
2637 ) -> Task<Result<Entity<Editor>>> {
2638 let project = workspace.project().clone();
2639 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2640
2641 cx.spawn_in(window, async move |workspace, cx| {
2642 let buffer = create.await?;
2643 workspace.update_in(cx, |workspace, window, cx| {
2644 let editor =
2645 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2646 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2647 editor
2648 })
2649 })
2650 }
2651
2652 fn new_file_vertical(
2653 workspace: &mut Workspace,
2654 _: &workspace::NewFileSplitVertical,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2659 }
2660
2661 fn new_file_horizontal(
2662 workspace: &mut Workspace,
2663 _: &workspace::NewFileSplitHorizontal,
2664 window: &mut Window,
2665 cx: &mut Context<Workspace>,
2666 ) {
2667 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2668 }
2669
2670 fn new_file_in_direction(
2671 workspace: &mut Workspace,
2672 direction: SplitDirection,
2673 window: &mut Window,
2674 cx: &mut Context<Workspace>,
2675 ) {
2676 let project = workspace.project().clone();
2677 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2678
2679 cx.spawn_in(window, async move |workspace, cx| {
2680 let buffer = create.await?;
2681 workspace.update_in(cx, move |workspace, window, cx| {
2682 workspace.split_item(
2683 direction,
2684 Box::new(
2685 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2686 ),
2687 window,
2688 cx,
2689 )
2690 })?;
2691 anyhow::Ok(())
2692 })
2693 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2694 match e.error_code() {
2695 ErrorCode::RemoteUpgradeRequired => Some(format!(
2696 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2697 e.error_tag("required").unwrap_or("the latest version")
2698 )),
2699 _ => None,
2700 }
2701 });
2702 }
2703
2704 pub fn leader_id(&self) -> Option<CollaboratorId> {
2705 self.leader_id
2706 }
2707
2708 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2709 &self.buffer
2710 }
2711
2712 pub fn project(&self) -> Option<&Entity<Project>> {
2713 self.project.as_ref()
2714 }
2715
2716 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2717 self.workspace.as_ref()?.0.upgrade()
2718 }
2719
2720 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2721 self.buffer().read(cx).title(cx)
2722 }
2723
2724 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2725 let git_blame_gutter_max_author_length = self
2726 .render_git_blame_gutter(cx)
2727 .then(|| {
2728 if let Some(blame) = self.blame.as_ref() {
2729 let max_author_length =
2730 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2731 Some(max_author_length)
2732 } else {
2733 None
2734 }
2735 })
2736 .flatten();
2737
2738 EditorSnapshot {
2739 mode: self.mode.clone(),
2740 show_gutter: self.show_gutter,
2741 show_line_numbers: self.show_line_numbers,
2742 show_git_diff_gutter: self.show_git_diff_gutter,
2743 show_code_actions: self.show_code_actions,
2744 show_runnables: self.show_runnables,
2745 show_breakpoints: self.show_breakpoints,
2746 git_blame_gutter_max_author_length,
2747 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2748 placeholder_display_snapshot: self
2749 .placeholder_display_map
2750 .as_ref()
2751 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2752 scroll_anchor: self.scroll_manager.anchor(),
2753 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2754 is_focused: self.focus_handle.is_focused(window),
2755 current_line_highlight: self
2756 .current_line_highlight
2757 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2758 gutter_hovered: self.gutter_hovered,
2759 }
2760 }
2761
2762 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2763 self.buffer.read(cx).language_at(point, cx)
2764 }
2765
2766 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2767 self.buffer.read(cx).read(cx).file_at(point).cloned()
2768 }
2769
2770 pub fn active_excerpt(
2771 &self,
2772 cx: &App,
2773 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2774 self.buffer
2775 .read(cx)
2776 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2777 }
2778
2779 pub fn mode(&self) -> &EditorMode {
2780 &self.mode
2781 }
2782
2783 pub fn set_mode(&mut self, mode: EditorMode) {
2784 self.mode = mode;
2785 }
2786
2787 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2788 self.collaboration_hub.as_deref()
2789 }
2790
2791 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2792 self.collaboration_hub = Some(hub);
2793 }
2794
2795 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2796 self.in_project_search = in_project_search;
2797 }
2798
2799 pub fn set_custom_context_menu(
2800 &mut self,
2801 f: impl 'static
2802 + Fn(
2803 &mut Self,
2804 DisplayPoint,
2805 &mut Window,
2806 &mut Context<Self>,
2807 ) -> Option<Entity<ui::ContextMenu>>,
2808 ) {
2809 self.custom_context_menu = Some(Box::new(f))
2810 }
2811
2812 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2813 self.completion_provider = provider;
2814 }
2815
2816 #[cfg(any(test, feature = "test-support"))]
2817 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2818 self.completion_provider.clone()
2819 }
2820
2821 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2822 self.semantics_provider.clone()
2823 }
2824
2825 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2826 self.semantics_provider = provider;
2827 }
2828
2829 pub fn set_edit_prediction_provider<T>(
2830 &mut self,
2831 provider: Option<Entity<T>>,
2832 window: &mut Window,
2833 cx: &mut Context<Self>,
2834 ) where
2835 T: EditPredictionProvider,
2836 {
2837 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2838 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2839 if this.focus_handle.is_focused(window) {
2840 this.update_visible_edit_prediction(window, cx);
2841 }
2842 }),
2843 provider: Arc::new(provider),
2844 });
2845 self.update_edit_prediction_settings(cx);
2846 self.refresh_edit_prediction(false, false, window, cx);
2847 }
2848
2849 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2850 self.placeholder_display_map
2851 .as_ref()
2852 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2853 }
2854
2855 pub fn set_placeholder_text(
2856 &mut self,
2857 placeholder_text: &str,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) {
2861 let multibuffer = cx
2862 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2863
2864 let style = window.text_style();
2865
2866 self.placeholder_display_map = Some(cx.new(|cx| {
2867 DisplayMap::new(
2868 multibuffer,
2869 style.font(),
2870 style.font_size.to_pixels(window.rem_size()),
2871 None,
2872 FILE_HEADER_HEIGHT,
2873 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2874 Default::default(),
2875 DiagnosticSeverity::Off,
2876 cx,
2877 )
2878 }));
2879 cx.notify();
2880 }
2881
2882 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2883 self.cursor_shape = cursor_shape;
2884
2885 // Disrupt blink for immediate user feedback that the cursor shape has changed
2886 self.blink_manager.update(cx, BlinkManager::show_cursor);
2887
2888 cx.notify();
2889 }
2890
2891 pub fn set_current_line_highlight(
2892 &mut self,
2893 current_line_highlight: Option<CurrentLineHighlight>,
2894 ) {
2895 self.current_line_highlight = current_line_highlight;
2896 }
2897
2898 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2899 self.collapse_matches = collapse_matches;
2900 }
2901
2902 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2903 let buffers = self.buffer.read(cx).all_buffers();
2904 let Some(project) = self.project.as_ref() else {
2905 return;
2906 };
2907 project.update(cx, |project, cx| {
2908 for buffer in buffers {
2909 self.registered_buffers
2910 .entry(buffer.read(cx).remote_id())
2911 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2912 }
2913 })
2914 }
2915
2916 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2917 if self.collapse_matches {
2918 return range.start..range.start;
2919 }
2920 range.clone()
2921 }
2922
2923 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2924 if self.display_map.read(cx).clip_at_line_ends != clip {
2925 self.display_map
2926 .update(cx, |map, _| map.clip_at_line_ends = clip);
2927 }
2928 }
2929
2930 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2931 self.input_enabled = input_enabled;
2932 }
2933
2934 pub fn set_edit_predictions_hidden_for_vim_mode(
2935 &mut self,
2936 hidden: bool,
2937 window: &mut Window,
2938 cx: &mut Context<Self>,
2939 ) {
2940 if hidden != self.edit_predictions_hidden_for_vim_mode {
2941 self.edit_predictions_hidden_for_vim_mode = hidden;
2942 if hidden {
2943 self.update_visible_edit_prediction(window, cx);
2944 } else {
2945 self.refresh_edit_prediction(true, false, window, cx);
2946 }
2947 }
2948 }
2949
2950 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2951 self.menu_edit_predictions_policy = value;
2952 }
2953
2954 pub fn set_autoindent(&mut self, autoindent: bool) {
2955 if autoindent {
2956 self.autoindent_mode = Some(AutoindentMode::EachLine);
2957 } else {
2958 self.autoindent_mode = None;
2959 }
2960 }
2961
2962 pub fn read_only(&self, cx: &App) -> bool {
2963 self.read_only || self.buffer.read(cx).read_only()
2964 }
2965
2966 pub fn set_read_only(&mut self, read_only: bool) {
2967 self.read_only = read_only;
2968 }
2969
2970 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2971 self.use_autoclose = autoclose;
2972 }
2973
2974 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2975 self.use_auto_surround = auto_surround;
2976 }
2977
2978 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2979 self.auto_replace_emoji_shortcode = auto_replace;
2980 }
2981
2982 pub fn toggle_edit_predictions(
2983 &mut self,
2984 _: &ToggleEditPrediction,
2985 window: &mut Window,
2986 cx: &mut Context<Self>,
2987 ) {
2988 if self.show_edit_predictions_override.is_some() {
2989 self.set_show_edit_predictions(None, window, cx);
2990 } else {
2991 let show_edit_predictions = !self.edit_predictions_enabled();
2992 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2993 }
2994 }
2995
2996 pub fn set_show_edit_predictions(
2997 &mut self,
2998 show_edit_predictions: Option<bool>,
2999 window: &mut Window,
3000 cx: &mut Context<Self>,
3001 ) {
3002 self.show_edit_predictions_override = show_edit_predictions;
3003 self.update_edit_prediction_settings(cx);
3004
3005 if let Some(false) = show_edit_predictions {
3006 self.discard_edit_prediction(false, cx);
3007 } else {
3008 self.refresh_edit_prediction(false, true, window, cx);
3009 }
3010 }
3011
3012 fn edit_predictions_disabled_in_scope(
3013 &self,
3014 buffer: &Entity<Buffer>,
3015 buffer_position: language::Anchor,
3016 cx: &App,
3017 ) -> bool {
3018 let snapshot = buffer.read(cx).snapshot();
3019 let settings = snapshot.settings_at(buffer_position, cx);
3020
3021 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3022 return false;
3023 };
3024
3025 scope.override_name().is_some_and(|scope_name| {
3026 settings
3027 .edit_predictions_disabled_in
3028 .iter()
3029 .any(|s| s == scope_name)
3030 })
3031 }
3032
3033 pub fn set_use_modal_editing(&mut self, to: bool) {
3034 self.use_modal_editing = to;
3035 }
3036
3037 pub fn use_modal_editing(&self) -> bool {
3038 self.use_modal_editing
3039 }
3040
3041 fn selections_did_change(
3042 &mut self,
3043 local: bool,
3044 old_cursor_position: &Anchor,
3045 effects: SelectionEffects,
3046 window: &mut Window,
3047 cx: &mut Context<Self>,
3048 ) {
3049 window.invalidate_character_coordinates();
3050
3051 // Copy selections to primary selection buffer
3052 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3053 if local {
3054 let selections = self.selections.all::<usize>(cx);
3055 let buffer_handle = self.buffer.read(cx).read(cx);
3056
3057 let mut text = String::new();
3058 for (index, selection) in selections.iter().enumerate() {
3059 let text_for_selection = buffer_handle
3060 .text_for_range(selection.start..selection.end)
3061 .collect::<String>();
3062
3063 text.push_str(&text_for_selection);
3064 if index != selections.len() - 1 {
3065 text.push('\n');
3066 }
3067 }
3068
3069 if !text.is_empty() {
3070 cx.write_to_primary(ClipboardItem::new_string(text));
3071 }
3072 }
3073
3074 let selection_anchors = self.selections.disjoint_anchors_arc();
3075
3076 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3077 self.buffer.update(cx, |buffer, cx| {
3078 buffer.set_active_selections(
3079 &selection_anchors,
3080 self.selections.line_mode(),
3081 self.cursor_shape,
3082 cx,
3083 )
3084 });
3085 }
3086 let display_map = self
3087 .display_map
3088 .update(cx, |display_map, cx| display_map.snapshot(cx));
3089 let buffer = display_map.buffer_snapshot();
3090 if self.selections.count() == 1 {
3091 self.add_selections_state = None;
3092 }
3093 self.select_next_state = None;
3094 self.select_prev_state = None;
3095 self.select_syntax_node_history.try_clear();
3096 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3097 self.snippet_stack.invalidate(&selection_anchors, buffer);
3098 self.take_rename(false, window, cx);
3099
3100 let newest_selection = self.selections.newest_anchor();
3101 let new_cursor_position = newest_selection.head();
3102 let selection_start = newest_selection.start;
3103
3104 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3105 self.push_to_nav_history(
3106 *old_cursor_position,
3107 Some(new_cursor_position.to_point(buffer)),
3108 false,
3109 effects.nav_history == Some(true),
3110 cx,
3111 );
3112 }
3113
3114 if local {
3115 if let Some(buffer_id) = new_cursor_position.buffer_id
3116 && !self.registered_buffers.contains_key(&buffer_id)
3117 && let Some(project) = self.project.as_ref()
3118 {
3119 project.update(cx, |project, cx| {
3120 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3121 return;
3122 };
3123 self.registered_buffers.insert(
3124 buffer_id,
3125 project.register_buffer_with_language_servers(&buffer, cx),
3126 );
3127 })
3128 }
3129
3130 let mut context_menu = self.context_menu.borrow_mut();
3131 let completion_menu = match context_menu.as_ref() {
3132 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3133 Some(CodeContextMenu::CodeActions(_)) => {
3134 *context_menu = None;
3135 None
3136 }
3137 None => None,
3138 };
3139 let completion_position = completion_menu.map(|menu| menu.initial_position);
3140 drop(context_menu);
3141
3142 if effects.completions
3143 && let Some(completion_position) = completion_position
3144 {
3145 let start_offset = selection_start.to_offset(buffer);
3146 let position_matches = start_offset == completion_position.to_offset(buffer);
3147 let continue_showing = if position_matches {
3148 if self.snippet_stack.is_empty() {
3149 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3150 == Some(CharKind::Word)
3151 } else {
3152 // Snippet choices can be shown even when the cursor is in whitespace.
3153 // Dismissing the menu with actions like backspace is handled by
3154 // invalidation regions.
3155 true
3156 }
3157 } else {
3158 false
3159 };
3160
3161 if continue_showing {
3162 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3163 } else {
3164 self.hide_context_menu(window, cx);
3165 }
3166 }
3167
3168 hide_hover(self, cx);
3169
3170 if old_cursor_position.to_display_point(&display_map).row()
3171 != new_cursor_position.to_display_point(&display_map).row()
3172 {
3173 self.available_code_actions.take();
3174 }
3175 self.refresh_code_actions(window, cx);
3176 self.refresh_document_highlights(cx);
3177 self.refresh_selected_text_highlights(false, window, cx);
3178 refresh_matching_bracket_highlights(self, window, cx);
3179 self.update_visible_edit_prediction(window, cx);
3180 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3181 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3182 self.inline_blame_popover.take();
3183 if self.git_blame_inline_enabled {
3184 self.start_inline_blame_timer(window, cx);
3185 }
3186 }
3187
3188 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3189 cx.emit(EditorEvent::SelectionsChanged { local });
3190
3191 let selections = &self.selections.disjoint_anchors_arc();
3192 if selections.len() == 1 {
3193 cx.emit(SearchEvent::ActiveMatchChanged)
3194 }
3195 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3196 let inmemory_selections = selections
3197 .iter()
3198 .map(|s| {
3199 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3200 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3201 })
3202 .collect();
3203 self.update_restoration_data(cx, |data| {
3204 data.selections = inmemory_selections;
3205 });
3206
3207 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3208 && let Some(workspace_id) =
3209 self.workspace.as_ref().and_then(|workspace| workspace.1)
3210 {
3211 let snapshot = self.buffer().read(cx).snapshot(cx);
3212 let selections = selections.clone();
3213 let background_executor = cx.background_executor().clone();
3214 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3215 self.serialize_selections = cx.background_spawn(async move {
3216 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3217 let db_selections = selections
3218 .iter()
3219 .map(|selection| {
3220 (
3221 selection.start.to_offset(&snapshot),
3222 selection.end.to_offset(&snapshot),
3223 )
3224 })
3225 .collect();
3226
3227 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3228 .await
3229 .with_context(|| {
3230 format!(
3231 "persisting editor selections for editor {editor_id}, \
3232 workspace {workspace_id:?}"
3233 )
3234 })
3235 .log_err();
3236 });
3237 }
3238 }
3239
3240 cx.notify();
3241 }
3242
3243 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3244 use text::ToOffset as _;
3245 use text::ToPoint as _;
3246
3247 if self.mode.is_minimap()
3248 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3249 {
3250 return;
3251 }
3252
3253 if !self.buffer().read(cx).is_singleton() {
3254 return;
3255 }
3256
3257 let display_snapshot = self
3258 .display_map
3259 .update(cx, |display_map, cx| display_map.snapshot(cx));
3260 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3261 return;
3262 };
3263 let inmemory_folds = display_snapshot
3264 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3265 .map(|fold| {
3266 fold.range.start.text_anchor.to_point(&snapshot)
3267 ..fold.range.end.text_anchor.to_point(&snapshot)
3268 })
3269 .collect();
3270 self.update_restoration_data(cx, |data| {
3271 data.folds = inmemory_folds;
3272 });
3273
3274 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3275 return;
3276 };
3277 let background_executor = cx.background_executor().clone();
3278 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3279 let db_folds = display_snapshot
3280 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3281 .map(|fold| {
3282 (
3283 fold.range.start.text_anchor.to_offset(&snapshot),
3284 fold.range.end.text_anchor.to_offset(&snapshot),
3285 )
3286 })
3287 .collect();
3288 self.serialize_folds = cx.background_spawn(async move {
3289 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3290 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3291 .await
3292 .with_context(|| {
3293 format!(
3294 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3295 )
3296 })
3297 .log_err();
3298 });
3299 }
3300
3301 pub fn sync_selections(
3302 &mut self,
3303 other: Entity<Editor>,
3304 cx: &mut Context<Self>,
3305 ) -> gpui::Subscription {
3306 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3307 if !other_selections.is_empty() {
3308 self.selections.change_with(cx, |selections| {
3309 selections.select_anchors(other_selections);
3310 });
3311 }
3312
3313 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3314 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3315 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3316 if other_selections.is_empty() {
3317 return;
3318 }
3319 this.selections.change_with(cx, |selections| {
3320 selections.select_anchors(other_selections);
3321 });
3322 }
3323 });
3324
3325 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3326 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3327 let these_selections = this.selections.disjoint_anchors().to_vec();
3328 if these_selections.is_empty() {
3329 return;
3330 }
3331 other.update(cx, |other_editor, cx| {
3332 other_editor.selections.change_with(cx, |selections| {
3333 selections.select_anchors(these_selections);
3334 })
3335 });
3336 }
3337 });
3338
3339 Subscription::join(other_subscription, this_subscription)
3340 }
3341
3342 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3343 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3344 /// effects of selection change occur at the end of the transaction.
3345 pub fn change_selections<R>(
3346 &mut self,
3347 effects: SelectionEffects,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3351 ) -> R {
3352 if let Some(state) = &mut self.deferred_selection_effects_state {
3353 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3354 state.effects.completions = effects.completions;
3355 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3356 let (changed, result) = self.selections.change_with(cx, change);
3357 state.changed |= changed;
3358 return result;
3359 }
3360 let mut state = DeferredSelectionEffectsState {
3361 changed: false,
3362 effects,
3363 old_cursor_position: self.selections.newest_anchor().head(),
3364 history_entry: SelectionHistoryEntry {
3365 selections: self.selections.disjoint_anchors_arc(),
3366 select_next_state: self.select_next_state.clone(),
3367 select_prev_state: self.select_prev_state.clone(),
3368 add_selections_state: self.add_selections_state.clone(),
3369 },
3370 };
3371 let (changed, result) = self.selections.change_with(cx, change);
3372 state.changed = state.changed || changed;
3373 if self.defer_selection_effects {
3374 self.deferred_selection_effects_state = Some(state);
3375 } else {
3376 self.apply_selection_effects(state, window, cx);
3377 }
3378 result
3379 }
3380
3381 /// Defers the effects of selection change, so that the effects of multiple calls to
3382 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3383 /// to selection history and the state of popovers based on selection position aren't
3384 /// erroneously updated.
3385 pub fn with_selection_effects_deferred<R>(
3386 &mut self,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3390 ) -> R {
3391 let already_deferred = self.defer_selection_effects;
3392 self.defer_selection_effects = true;
3393 let result = update(self, window, cx);
3394 if !already_deferred {
3395 self.defer_selection_effects = false;
3396 if let Some(state) = self.deferred_selection_effects_state.take() {
3397 self.apply_selection_effects(state, window, cx);
3398 }
3399 }
3400 result
3401 }
3402
3403 fn apply_selection_effects(
3404 &mut self,
3405 state: DeferredSelectionEffectsState,
3406 window: &mut Window,
3407 cx: &mut Context<Self>,
3408 ) {
3409 if state.changed {
3410 self.selection_history.push(state.history_entry);
3411
3412 if let Some(autoscroll) = state.effects.scroll {
3413 self.request_autoscroll(autoscroll, cx);
3414 }
3415
3416 let old_cursor_position = &state.old_cursor_position;
3417
3418 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3419
3420 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3421 self.show_signature_help(&ShowSignatureHelp, window, cx);
3422 }
3423 }
3424 }
3425
3426 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3427 where
3428 I: IntoIterator<Item = (Range<S>, T)>,
3429 S: ToOffset,
3430 T: Into<Arc<str>>,
3431 {
3432 if self.read_only(cx) {
3433 return;
3434 }
3435
3436 self.buffer
3437 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3438 }
3439
3440 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3441 where
3442 I: IntoIterator<Item = (Range<S>, T)>,
3443 S: ToOffset,
3444 T: Into<Arc<str>>,
3445 {
3446 if self.read_only(cx) {
3447 return;
3448 }
3449
3450 self.buffer.update(cx, |buffer, cx| {
3451 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3452 });
3453 }
3454
3455 pub fn edit_with_block_indent<I, S, T>(
3456 &mut self,
3457 edits: I,
3458 original_indent_columns: Vec<Option<u32>>,
3459 cx: &mut Context<Self>,
3460 ) where
3461 I: IntoIterator<Item = (Range<S>, T)>,
3462 S: ToOffset,
3463 T: Into<Arc<str>>,
3464 {
3465 if self.read_only(cx) {
3466 return;
3467 }
3468
3469 self.buffer.update(cx, |buffer, cx| {
3470 buffer.edit(
3471 edits,
3472 Some(AutoindentMode::Block {
3473 original_indent_columns,
3474 }),
3475 cx,
3476 )
3477 });
3478 }
3479
3480 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3481 self.hide_context_menu(window, cx);
3482
3483 match phase {
3484 SelectPhase::Begin {
3485 position,
3486 add,
3487 click_count,
3488 } => self.begin_selection(position, add, click_count, window, cx),
3489 SelectPhase::BeginColumnar {
3490 position,
3491 goal_column,
3492 reset,
3493 mode,
3494 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3495 SelectPhase::Extend {
3496 position,
3497 click_count,
3498 } => self.extend_selection(position, click_count, window, cx),
3499 SelectPhase::Update {
3500 position,
3501 goal_column,
3502 scroll_delta,
3503 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3504 SelectPhase::End => self.end_selection(window, cx),
3505 }
3506 }
3507
3508 fn extend_selection(
3509 &mut self,
3510 position: DisplayPoint,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3516 let tail = self.selections.newest::<usize>(cx).tail();
3517 self.begin_selection(position, false, click_count, window, cx);
3518
3519 let position = position.to_offset(&display_map, Bias::Left);
3520 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3521
3522 let mut pending_selection = self
3523 .selections
3524 .pending_anchor()
3525 .cloned()
3526 .expect("extend_selection not called with pending selection");
3527 if position >= tail {
3528 pending_selection.start = tail_anchor;
3529 } else {
3530 pending_selection.end = tail_anchor;
3531 pending_selection.reversed = true;
3532 }
3533
3534 let mut pending_mode = self.selections.pending_mode().unwrap();
3535 match &mut pending_mode {
3536 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3537 _ => {}
3538 }
3539
3540 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3541 SelectionEffects::scroll(Autoscroll::fit())
3542 } else {
3543 SelectionEffects::no_scroll()
3544 };
3545
3546 self.change_selections(effects, window, cx, |s| {
3547 s.set_pending(pending_selection.clone(), pending_mode)
3548 });
3549 }
3550
3551 fn begin_selection(
3552 &mut self,
3553 position: DisplayPoint,
3554 add: bool,
3555 click_count: usize,
3556 window: &mut Window,
3557 cx: &mut Context<Self>,
3558 ) {
3559 if !self.focus_handle.is_focused(window) {
3560 self.last_focused_descendant = None;
3561 window.focus(&self.focus_handle);
3562 }
3563
3564 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3565 let buffer = display_map.buffer_snapshot();
3566 let position = display_map.clip_point(position, Bias::Left);
3567
3568 let start;
3569 let end;
3570 let mode;
3571 let mut auto_scroll;
3572 match click_count {
3573 1 => {
3574 start = buffer.anchor_before(position.to_point(&display_map));
3575 end = start;
3576 mode = SelectMode::Character;
3577 auto_scroll = true;
3578 }
3579 2 => {
3580 let position = display_map
3581 .clip_point(position, Bias::Left)
3582 .to_offset(&display_map, Bias::Left);
3583 let (range, _) = buffer.surrounding_word(position, None);
3584 start = buffer.anchor_before(range.start);
3585 end = buffer.anchor_before(range.end);
3586 mode = SelectMode::Word(start..end);
3587 auto_scroll = true;
3588 }
3589 3 => {
3590 let position = display_map
3591 .clip_point(position, Bias::Left)
3592 .to_point(&display_map);
3593 let line_start = display_map.prev_line_boundary(position).0;
3594 let next_line_start = buffer.clip_point(
3595 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3596 Bias::Left,
3597 );
3598 start = buffer.anchor_before(line_start);
3599 end = buffer.anchor_before(next_line_start);
3600 mode = SelectMode::Line(start..end);
3601 auto_scroll = true;
3602 }
3603 _ => {
3604 start = buffer.anchor_before(0);
3605 end = buffer.anchor_before(buffer.len());
3606 mode = SelectMode::All;
3607 auto_scroll = false;
3608 }
3609 }
3610 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3611
3612 let point_to_delete: Option<usize> = {
3613 let selected_points: Vec<Selection<Point>> =
3614 self.selections.disjoint_in_range(start..end, cx);
3615
3616 if !add || click_count > 1 {
3617 None
3618 } else if !selected_points.is_empty() {
3619 Some(selected_points[0].id)
3620 } else {
3621 let clicked_point_already_selected =
3622 self.selections.disjoint_anchors().iter().find(|selection| {
3623 selection.start.to_point(buffer) == start.to_point(buffer)
3624 || selection.end.to_point(buffer) == end.to_point(buffer)
3625 });
3626
3627 clicked_point_already_selected.map(|selection| selection.id)
3628 }
3629 };
3630
3631 let selections_count = self.selections.count();
3632 let effects = if auto_scroll {
3633 SelectionEffects::default()
3634 } else {
3635 SelectionEffects::no_scroll()
3636 };
3637
3638 self.change_selections(effects, window, cx, |s| {
3639 if let Some(point_to_delete) = point_to_delete {
3640 s.delete(point_to_delete);
3641
3642 if selections_count == 1 {
3643 s.set_pending_anchor_range(start..end, mode);
3644 }
3645 } else {
3646 if !add {
3647 s.clear_disjoint();
3648 }
3649
3650 s.set_pending_anchor_range(start..end, mode);
3651 }
3652 });
3653 }
3654
3655 fn begin_columnar_selection(
3656 &mut self,
3657 position: DisplayPoint,
3658 goal_column: u32,
3659 reset: bool,
3660 mode: ColumnarMode,
3661 window: &mut Window,
3662 cx: &mut Context<Self>,
3663 ) {
3664 if !self.focus_handle.is_focused(window) {
3665 self.last_focused_descendant = None;
3666 window.focus(&self.focus_handle);
3667 }
3668
3669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3670
3671 if reset {
3672 let pointer_position = display_map
3673 .buffer_snapshot()
3674 .anchor_before(position.to_point(&display_map));
3675
3676 self.change_selections(
3677 SelectionEffects::scroll(Autoscroll::newest()),
3678 window,
3679 cx,
3680 |s| {
3681 s.clear_disjoint();
3682 s.set_pending_anchor_range(
3683 pointer_position..pointer_position,
3684 SelectMode::Character,
3685 );
3686 },
3687 );
3688 };
3689
3690 let tail = self.selections.newest::<Point>(cx).tail();
3691 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3692 self.columnar_selection_state = match mode {
3693 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3694 selection_tail: selection_anchor,
3695 display_point: if reset {
3696 if position.column() != goal_column {
3697 Some(DisplayPoint::new(position.row(), goal_column))
3698 } else {
3699 None
3700 }
3701 } else {
3702 None
3703 },
3704 }),
3705 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3706 selection_tail: selection_anchor,
3707 }),
3708 };
3709
3710 if !reset {
3711 self.select_columns(position, goal_column, &display_map, window, cx);
3712 }
3713 }
3714
3715 fn update_selection(
3716 &mut self,
3717 position: DisplayPoint,
3718 goal_column: u32,
3719 scroll_delta: gpui::Point<f32>,
3720 window: &mut Window,
3721 cx: &mut Context<Self>,
3722 ) {
3723 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3724
3725 if self.columnar_selection_state.is_some() {
3726 self.select_columns(position, goal_column, &display_map, window, cx);
3727 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3728 let buffer = display_map.buffer_snapshot();
3729 let head;
3730 let tail;
3731 let mode = self.selections.pending_mode().unwrap();
3732 match &mode {
3733 SelectMode::Character => {
3734 head = position.to_point(&display_map);
3735 tail = pending.tail().to_point(buffer);
3736 }
3737 SelectMode::Word(original_range) => {
3738 let offset = display_map
3739 .clip_point(position, Bias::Left)
3740 .to_offset(&display_map, Bias::Left);
3741 let original_range = original_range.to_offset(buffer);
3742
3743 let head_offset = if buffer.is_inside_word(offset, None)
3744 || original_range.contains(&offset)
3745 {
3746 let (word_range, _) = buffer.surrounding_word(offset, None);
3747 if word_range.start < original_range.start {
3748 word_range.start
3749 } else {
3750 word_range.end
3751 }
3752 } else {
3753 offset
3754 };
3755
3756 head = head_offset.to_point(buffer);
3757 if head_offset <= original_range.start {
3758 tail = original_range.end.to_point(buffer);
3759 } else {
3760 tail = original_range.start.to_point(buffer);
3761 }
3762 }
3763 SelectMode::Line(original_range) => {
3764 let original_range = original_range.to_point(display_map.buffer_snapshot());
3765
3766 let position = display_map
3767 .clip_point(position, Bias::Left)
3768 .to_point(&display_map);
3769 let line_start = display_map.prev_line_boundary(position).0;
3770 let next_line_start = buffer.clip_point(
3771 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3772 Bias::Left,
3773 );
3774
3775 if line_start < original_range.start {
3776 head = line_start
3777 } else {
3778 head = next_line_start
3779 }
3780
3781 if head <= original_range.start {
3782 tail = original_range.end;
3783 } else {
3784 tail = original_range.start;
3785 }
3786 }
3787 SelectMode::All => {
3788 return;
3789 }
3790 };
3791
3792 if head < tail {
3793 pending.start = buffer.anchor_before(head);
3794 pending.end = buffer.anchor_before(tail);
3795 pending.reversed = true;
3796 } else {
3797 pending.start = buffer.anchor_before(tail);
3798 pending.end = buffer.anchor_before(head);
3799 pending.reversed = false;
3800 }
3801
3802 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3803 s.set_pending(pending.clone(), mode);
3804 });
3805 } else {
3806 log::error!("update_selection dispatched with no pending selection");
3807 return;
3808 }
3809
3810 self.apply_scroll_delta(scroll_delta, window, cx);
3811 cx.notify();
3812 }
3813
3814 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3815 self.columnar_selection_state.take();
3816 if self.selections.pending_anchor().is_some() {
3817 let selections = self.selections.all::<usize>(cx);
3818 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3819 s.select(selections);
3820 s.clear_pending();
3821 });
3822 }
3823 }
3824
3825 fn select_columns(
3826 &mut self,
3827 head: DisplayPoint,
3828 goal_column: u32,
3829 display_map: &DisplaySnapshot,
3830 window: &mut Window,
3831 cx: &mut Context<Self>,
3832 ) {
3833 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3834 return;
3835 };
3836
3837 let tail = match columnar_state {
3838 ColumnarSelectionState::FromMouse {
3839 selection_tail,
3840 display_point,
3841 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3842 ColumnarSelectionState::FromSelection { selection_tail } => {
3843 selection_tail.to_display_point(display_map)
3844 }
3845 };
3846
3847 let start_row = cmp::min(tail.row(), head.row());
3848 let end_row = cmp::max(tail.row(), head.row());
3849 let start_column = cmp::min(tail.column(), goal_column);
3850 let end_column = cmp::max(tail.column(), goal_column);
3851 let reversed = start_column < tail.column();
3852
3853 let selection_ranges = (start_row.0..=end_row.0)
3854 .map(DisplayRow)
3855 .filter_map(|row| {
3856 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3857 || start_column <= display_map.line_len(row))
3858 && !display_map.is_block_line(row)
3859 {
3860 let start = display_map
3861 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3862 .to_point(display_map);
3863 let end = display_map
3864 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3865 .to_point(display_map);
3866 if reversed {
3867 Some(end..start)
3868 } else {
3869 Some(start..end)
3870 }
3871 } else {
3872 None
3873 }
3874 })
3875 .collect::<Vec<_>>();
3876
3877 let ranges = match columnar_state {
3878 ColumnarSelectionState::FromMouse { .. } => {
3879 let mut non_empty_ranges = selection_ranges
3880 .iter()
3881 .filter(|selection_range| selection_range.start != selection_range.end)
3882 .peekable();
3883 if non_empty_ranges.peek().is_some() {
3884 non_empty_ranges.cloned().collect()
3885 } else {
3886 selection_ranges
3887 }
3888 }
3889 _ => selection_ranges,
3890 };
3891
3892 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3893 s.select_ranges(ranges);
3894 });
3895 cx.notify();
3896 }
3897
3898 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3899 self.selections
3900 .all_adjusted(cx)
3901 .iter()
3902 .any(|selection| !selection.is_empty())
3903 }
3904
3905 pub fn has_pending_nonempty_selection(&self) -> bool {
3906 let pending_nonempty_selection = match self.selections.pending_anchor() {
3907 Some(Selection { start, end, .. }) => start != end,
3908 None => false,
3909 };
3910
3911 pending_nonempty_selection
3912 || (self.columnar_selection_state.is_some()
3913 && self.selections.disjoint_anchors().len() > 1)
3914 }
3915
3916 pub fn has_pending_selection(&self) -> bool {
3917 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3918 }
3919
3920 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3921 self.selection_mark_mode = false;
3922 self.selection_drag_state = SelectionDragState::None;
3923
3924 if self.clear_expanded_diff_hunks(cx) {
3925 cx.notify();
3926 return;
3927 }
3928 if self.dismiss_menus_and_popups(true, window, cx) {
3929 return;
3930 }
3931
3932 if self.mode.is_full()
3933 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3934 {
3935 return;
3936 }
3937
3938 cx.propagate();
3939 }
3940
3941 pub fn dismiss_menus_and_popups(
3942 &mut self,
3943 is_user_requested: bool,
3944 window: &mut Window,
3945 cx: &mut Context<Self>,
3946 ) -> bool {
3947 if self.take_rename(false, window, cx).is_some() {
3948 return true;
3949 }
3950
3951 if hide_hover(self, cx) {
3952 return true;
3953 }
3954
3955 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3956 return true;
3957 }
3958
3959 if self.hide_context_menu(window, cx).is_some() {
3960 return true;
3961 }
3962
3963 if self.mouse_context_menu.take().is_some() {
3964 return true;
3965 }
3966
3967 if is_user_requested && self.discard_edit_prediction(true, cx) {
3968 return true;
3969 }
3970
3971 if self.snippet_stack.pop().is_some() {
3972 return true;
3973 }
3974
3975 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3976 self.dismiss_diagnostics(cx);
3977 return true;
3978 }
3979
3980 false
3981 }
3982
3983 fn linked_editing_ranges_for(
3984 &self,
3985 selection: Range<text::Anchor>,
3986 cx: &App,
3987 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3988 if self.linked_edit_ranges.is_empty() {
3989 return None;
3990 }
3991 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3992 selection.end.buffer_id.and_then(|end_buffer_id| {
3993 if selection.start.buffer_id != Some(end_buffer_id) {
3994 return None;
3995 }
3996 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3997 let snapshot = buffer.read(cx).snapshot();
3998 self.linked_edit_ranges
3999 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4000 .map(|ranges| (ranges, snapshot, buffer))
4001 })?;
4002 use text::ToOffset as TO;
4003 // find offset from the start of current range to current cursor position
4004 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4005
4006 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4007 let start_difference = start_offset - start_byte_offset;
4008 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4009 let end_difference = end_offset - start_byte_offset;
4010 // Current range has associated linked ranges.
4011 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4012 for range in linked_ranges.iter() {
4013 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4014 let end_offset = start_offset + end_difference;
4015 let start_offset = start_offset + start_difference;
4016 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4017 continue;
4018 }
4019 if self.selections.disjoint_anchor_ranges().any(|s| {
4020 if s.start.buffer_id != selection.start.buffer_id
4021 || s.end.buffer_id != selection.end.buffer_id
4022 {
4023 return false;
4024 }
4025 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4026 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4027 }) {
4028 continue;
4029 }
4030 let start = buffer_snapshot.anchor_after(start_offset);
4031 let end = buffer_snapshot.anchor_after(end_offset);
4032 linked_edits
4033 .entry(buffer.clone())
4034 .or_default()
4035 .push(start..end);
4036 }
4037 Some(linked_edits)
4038 }
4039
4040 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4041 let text: Arc<str> = text.into();
4042
4043 if self.read_only(cx) {
4044 return;
4045 }
4046
4047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4048
4049 let selections = self.selections.all_adjusted(cx);
4050 let mut bracket_inserted = false;
4051 let mut edits = Vec::new();
4052 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4053 let mut new_selections = Vec::with_capacity(selections.len());
4054 let mut new_autoclose_regions = Vec::new();
4055 let snapshot = self.buffer.read(cx).read(cx);
4056 let mut clear_linked_edit_ranges = false;
4057
4058 for (selection, autoclose_region) in
4059 self.selections_with_autoclose_regions(selections, &snapshot)
4060 {
4061 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4062 // Determine if the inserted text matches the opening or closing
4063 // bracket of any of this language's bracket pairs.
4064 let mut bracket_pair = None;
4065 let mut is_bracket_pair_start = false;
4066 let mut is_bracket_pair_end = false;
4067 if !text.is_empty() {
4068 let mut bracket_pair_matching_end = None;
4069 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4070 // and they are removing the character that triggered IME popup.
4071 for (pair, enabled) in scope.brackets() {
4072 if !pair.close && !pair.surround {
4073 continue;
4074 }
4075
4076 if enabled && pair.start.ends_with(text.as_ref()) {
4077 let prefix_len = pair.start.len() - text.len();
4078 let preceding_text_matches_prefix = prefix_len == 0
4079 || (selection.start.column >= (prefix_len as u32)
4080 && snapshot.contains_str_at(
4081 Point::new(
4082 selection.start.row,
4083 selection.start.column - (prefix_len as u32),
4084 ),
4085 &pair.start[..prefix_len],
4086 ));
4087 if preceding_text_matches_prefix {
4088 bracket_pair = Some(pair.clone());
4089 is_bracket_pair_start = true;
4090 break;
4091 }
4092 }
4093 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4094 {
4095 // take first bracket pair matching end, but don't break in case a later bracket
4096 // pair matches start
4097 bracket_pair_matching_end = Some(pair.clone());
4098 }
4099 }
4100 if let Some(end) = bracket_pair_matching_end
4101 && bracket_pair.is_none()
4102 {
4103 bracket_pair = Some(end);
4104 is_bracket_pair_end = true;
4105 }
4106 }
4107
4108 if let Some(bracket_pair) = bracket_pair {
4109 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4110 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4111 let auto_surround =
4112 self.use_auto_surround && snapshot_settings.use_auto_surround;
4113 if selection.is_empty() {
4114 if is_bracket_pair_start {
4115 // If the inserted text is a suffix of an opening bracket and the
4116 // selection is preceded by the rest of the opening bracket, then
4117 // insert the closing bracket.
4118 let following_text_allows_autoclose = snapshot
4119 .chars_at(selection.start)
4120 .next()
4121 .is_none_or(|c| scope.should_autoclose_before(c));
4122
4123 let preceding_text_allows_autoclose = selection.start.column == 0
4124 || snapshot
4125 .reversed_chars_at(selection.start)
4126 .next()
4127 .is_none_or(|c| {
4128 bracket_pair.start != bracket_pair.end
4129 || !snapshot
4130 .char_classifier_at(selection.start)
4131 .is_word(c)
4132 });
4133
4134 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4135 && bracket_pair.start.len() == 1
4136 {
4137 let target = bracket_pair.start.chars().next().unwrap();
4138 let current_line_count = snapshot
4139 .reversed_chars_at(selection.start)
4140 .take_while(|&c| c != '\n')
4141 .filter(|&c| c == target)
4142 .count();
4143 current_line_count % 2 == 1
4144 } else {
4145 false
4146 };
4147
4148 if autoclose
4149 && bracket_pair.close
4150 && following_text_allows_autoclose
4151 && preceding_text_allows_autoclose
4152 && !is_closing_quote
4153 {
4154 let anchor = snapshot.anchor_before(selection.end);
4155 new_selections.push((selection.map(|_| anchor), text.len()));
4156 new_autoclose_regions.push((
4157 anchor,
4158 text.len(),
4159 selection.id,
4160 bracket_pair.clone(),
4161 ));
4162 edits.push((
4163 selection.range(),
4164 format!("{}{}", text, bracket_pair.end).into(),
4165 ));
4166 bracket_inserted = true;
4167 continue;
4168 }
4169 }
4170
4171 if let Some(region) = autoclose_region {
4172 // If the selection is followed by an auto-inserted closing bracket,
4173 // then don't insert that closing bracket again; just move the selection
4174 // past the closing bracket.
4175 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4176 && text.as_ref() == region.pair.end.as_str()
4177 && snapshot.contains_str_at(region.range.end, text.as_ref());
4178 if should_skip {
4179 let anchor = snapshot.anchor_after(selection.end);
4180 new_selections
4181 .push((selection.map(|_| anchor), region.pair.end.len()));
4182 continue;
4183 }
4184 }
4185
4186 let always_treat_brackets_as_autoclosed = snapshot
4187 .language_settings_at(selection.start, cx)
4188 .always_treat_brackets_as_autoclosed;
4189 if always_treat_brackets_as_autoclosed
4190 && is_bracket_pair_end
4191 && snapshot.contains_str_at(selection.end, text.as_ref())
4192 {
4193 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4194 // and the inserted text is a closing bracket and the selection is followed
4195 // by the closing bracket then move the selection past the closing bracket.
4196 let anchor = snapshot.anchor_after(selection.end);
4197 new_selections.push((selection.map(|_| anchor), text.len()));
4198 continue;
4199 }
4200 }
4201 // If an opening bracket is 1 character long and is typed while
4202 // text is selected, then surround that text with the bracket pair.
4203 else if auto_surround
4204 && bracket_pair.surround
4205 && is_bracket_pair_start
4206 && bracket_pair.start.chars().count() == 1
4207 {
4208 edits.push((selection.start..selection.start, text.clone()));
4209 edits.push((
4210 selection.end..selection.end,
4211 bracket_pair.end.as_str().into(),
4212 ));
4213 bracket_inserted = true;
4214 new_selections.push((
4215 Selection {
4216 id: selection.id,
4217 start: snapshot.anchor_after(selection.start),
4218 end: snapshot.anchor_before(selection.end),
4219 reversed: selection.reversed,
4220 goal: selection.goal,
4221 },
4222 0,
4223 ));
4224 continue;
4225 }
4226 }
4227 }
4228
4229 if self.auto_replace_emoji_shortcode
4230 && selection.is_empty()
4231 && text.as_ref().ends_with(':')
4232 && let Some(possible_emoji_short_code) =
4233 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4234 && !possible_emoji_short_code.is_empty()
4235 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4236 {
4237 let emoji_shortcode_start = Point::new(
4238 selection.start.row,
4239 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4240 );
4241
4242 // Remove shortcode from buffer
4243 edits.push((
4244 emoji_shortcode_start..selection.start,
4245 "".to_string().into(),
4246 ));
4247 new_selections.push((
4248 Selection {
4249 id: selection.id,
4250 start: snapshot.anchor_after(emoji_shortcode_start),
4251 end: snapshot.anchor_before(selection.start),
4252 reversed: selection.reversed,
4253 goal: selection.goal,
4254 },
4255 0,
4256 ));
4257
4258 // Insert emoji
4259 let selection_start_anchor = snapshot.anchor_after(selection.start);
4260 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4261 edits.push((selection.start..selection.end, emoji.to_string().into()));
4262
4263 continue;
4264 }
4265
4266 // If not handling any auto-close operation, then just replace the selected
4267 // text with the given input and move the selection to the end of the
4268 // newly inserted text.
4269 let anchor = snapshot.anchor_after(selection.end);
4270 if !self.linked_edit_ranges.is_empty() {
4271 let start_anchor = snapshot.anchor_before(selection.start);
4272
4273 let is_word_char = text.chars().next().is_none_or(|char| {
4274 let classifier = snapshot
4275 .char_classifier_at(start_anchor.to_offset(&snapshot))
4276 .scope_context(Some(CharScopeContext::LinkedEdit));
4277 classifier.is_word(char)
4278 });
4279
4280 if is_word_char {
4281 if let Some(ranges) = self
4282 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4283 {
4284 for (buffer, edits) in ranges {
4285 linked_edits
4286 .entry(buffer.clone())
4287 .or_default()
4288 .extend(edits.into_iter().map(|range| (range, text.clone())));
4289 }
4290 }
4291 } else {
4292 clear_linked_edit_ranges = true;
4293 }
4294 }
4295
4296 new_selections.push((selection.map(|_| anchor), 0));
4297 edits.push((selection.start..selection.end, text.clone()));
4298 }
4299
4300 drop(snapshot);
4301
4302 self.transact(window, cx, |this, window, cx| {
4303 if clear_linked_edit_ranges {
4304 this.linked_edit_ranges.clear();
4305 }
4306 let initial_buffer_versions =
4307 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4308
4309 this.buffer.update(cx, |buffer, cx| {
4310 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4311 });
4312 for (buffer, edits) in linked_edits {
4313 buffer.update(cx, |buffer, cx| {
4314 let snapshot = buffer.snapshot();
4315 let edits = edits
4316 .into_iter()
4317 .map(|(range, text)| {
4318 use text::ToPoint as TP;
4319 let end_point = TP::to_point(&range.end, &snapshot);
4320 let start_point = TP::to_point(&range.start, &snapshot);
4321 (start_point..end_point, text)
4322 })
4323 .sorted_by_key(|(range, _)| range.start);
4324 buffer.edit(edits, None, cx);
4325 })
4326 }
4327 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4328 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4329 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4330 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4331 .zip(new_selection_deltas)
4332 .map(|(selection, delta)| Selection {
4333 id: selection.id,
4334 start: selection.start + delta,
4335 end: selection.end + delta,
4336 reversed: selection.reversed,
4337 goal: SelectionGoal::None,
4338 })
4339 .collect::<Vec<_>>();
4340
4341 let mut i = 0;
4342 for (position, delta, selection_id, pair) in new_autoclose_regions {
4343 let position = position.to_offset(map.buffer_snapshot()) + delta;
4344 let start = map.buffer_snapshot().anchor_before(position);
4345 let end = map.buffer_snapshot().anchor_after(position);
4346 while let Some(existing_state) = this.autoclose_regions.get(i) {
4347 match existing_state
4348 .range
4349 .start
4350 .cmp(&start, map.buffer_snapshot())
4351 {
4352 Ordering::Less => i += 1,
4353 Ordering::Greater => break,
4354 Ordering::Equal => {
4355 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4356 Ordering::Less => i += 1,
4357 Ordering::Equal => break,
4358 Ordering::Greater => break,
4359 }
4360 }
4361 }
4362 }
4363 this.autoclose_regions.insert(
4364 i,
4365 AutocloseRegion {
4366 selection_id,
4367 range: start..end,
4368 pair,
4369 },
4370 );
4371 }
4372
4373 let had_active_edit_prediction = this.has_active_edit_prediction();
4374 this.change_selections(
4375 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4376 window,
4377 cx,
4378 |s| s.select(new_selections),
4379 );
4380
4381 if !bracket_inserted
4382 && let Some(on_type_format_task) =
4383 this.trigger_on_type_formatting(text.to_string(), window, cx)
4384 {
4385 on_type_format_task.detach_and_log_err(cx);
4386 }
4387
4388 let editor_settings = EditorSettings::get_global(cx);
4389 if bracket_inserted
4390 && (editor_settings.auto_signature_help
4391 || editor_settings.show_signature_help_after_edits)
4392 {
4393 this.show_signature_help(&ShowSignatureHelp, window, cx);
4394 }
4395
4396 let trigger_in_words =
4397 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4398 if this.hard_wrap.is_some() {
4399 let latest: Range<Point> = this.selections.newest(cx).range();
4400 if latest.is_empty()
4401 && this
4402 .buffer()
4403 .read(cx)
4404 .snapshot(cx)
4405 .line_len(MultiBufferRow(latest.start.row))
4406 == latest.start.column
4407 {
4408 this.rewrap_impl(
4409 RewrapOptions {
4410 override_language_settings: true,
4411 preserve_existing_whitespace: true,
4412 },
4413 cx,
4414 )
4415 }
4416 }
4417 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4418 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4419 this.refresh_edit_prediction(true, false, window, cx);
4420 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4421 });
4422 }
4423
4424 fn find_possible_emoji_shortcode_at_position(
4425 snapshot: &MultiBufferSnapshot,
4426 position: Point,
4427 ) -> Option<String> {
4428 let mut chars = Vec::new();
4429 let mut found_colon = false;
4430 for char in snapshot.reversed_chars_at(position).take(100) {
4431 // Found a possible emoji shortcode in the middle of the buffer
4432 if found_colon {
4433 if char.is_whitespace() {
4434 chars.reverse();
4435 return Some(chars.iter().collect());
4436 }
4437 // If the previous character is not a whitespace, we are in the middle of a word
4438 // and we only want to complete the shortcode if the word is made up of other emojis
4439 let mut containing_word = String::new();
4440 for ch in snapshot
4441 .reversed_chars_at(position)
4442 .skip(chars.len() + 1)
4443 .take(100)
4444 {
4445 if ch.is_whitespace() {
4446 break;
4447 }
4448 containing_word.push(ch);
4449 }
4450 let containing_word = containing_word.chars().rev().collect::<String>();
4451 if util::word_consists_of_emojis(containing_word.as_str()) {
4452 chars.reverse();
4453 return Some(chars.iter().collect());
4454 }
4455 }
4456
4457 if char.is_whitespace() || !char.is_ascii() {
4458 return None;
4459 }
4460 if char == ':' {
4461 found_colon = true;
4462 } else {
4463 chars.push(char);
4464 }
4465 }
4466 // Found a possible emoji shortcode at the beginning of the buffer
4467 chars.reverse();
4468 Some(chars.iter().collect())
4469 }
4470
4471 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4473 self.transact(window, cx, |this, window, cx| {
4474 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4475 let selections = this.selections.all::<usize>(cx);
4476 let multi_buffer = this.buffer.read(cx);
4477 let buffer = multi_buffer.snapshot(cx);
4478 selections
4479 .iter()
4480 .map(|selection| {
4481 let start_point = selection.start.to_point(&buffer);
4482 let mut existing_indent =
4483 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4484 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4485 let start = selection.start;
4486 let end = selection.end;
4487 let selection_is_empty = start == end;
4488 let language_scope = buffer.language_scope_at(start);
4489 let (
4490 comment_delimiter,
4491 doc_delimiter,
4492 insert_extra_newline,
4493 indent_on_newline,
4494 indent_on_extra_newline,
4495 ) = if let Some(language) = &language_scope {
4496 let mut insert_extra_newline =
4497 insert_extra_newline_brackets(&buffer, start..end, language)
4498 || insert_extra_newline_tree_sitter(&buffer, start..end);
4499
4500 // Comment extension on newline is allowed only for cursor selections
4501 let comment_delimiter = maybe!({
4502 if !selection_is_empty {
4503 return None;
4504 }
4505
4506 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4507 return None;
4508 }
4509
4510 let delimiters = language.line_comment_prefixes();
4511 let max_len_of_delimiter =
4512 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4513 let (snapshot, range) =
4514 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4515
4516 let num_of_whitespaces = snapshot
4517 .chars_for_range(range.clone())
4518 .take_while(|c| c.is_whitespace())
4519 .count();
4520 let comment_candidate = snapshot
4521 .chars_for_range(range.clone())
4522 .skip(num_of_whitespaces)
4523 .take(max_len_of_delimiter)
4524 .collect::<String>();
4525 let (delimiter, trimmed_len) = delimiters
4526 .iter()
4527 .filter_map(|delimiter| {
4528 let prefix = delimiter.trim_end();
4529 if comment_candidate.starts_with(prefix) {
4530 Some((delimiter, prefix.len()))
4531 } else {
4532 None
4533 }
4534 })
4535 .max_by_key(|(_, len)| *len)?;
4536
4537 if let Some(BlockCommentConfig {
4538 start: block_start, ..
4539 }) = language.block_comment()
4540 {
4541 let block_start_trimmed = block_start.trim_end();
4542 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4543 let line_content = snapshot
4544 .chars_for_range(range)
4545 .skip(num_of_whitespaces)
4546 .take(block_start_trimmed.len())
4547 .collect::<String>();
4548
4549 if line_content.starts_with(block_start_trimmed) {
4550 return None;
4551 }
4552 }
4553 }
4554
4555 let cursor_is_placed_after_comment_marker =
4556 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4557 if cursor_is_placed_after_comment_marker {
4558 Some(delimiter.clone())
4559 } else {
4560 None
4561 }
4562 });
4563
4564 let mut indent_on_newline = IndentSize::spaces(0);
4565 let mut indent_on_extra_newline = IndentSize::spaces(0);
4566
4567 let doc_delimiter = maybe!({
4568 if !selection_is_empty {
4569 return None;
4570 }
4571
4572 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4573 return None;
4574 }
4575
4576 let BlockCommentConfig {
4577 start: start_tag,
4578 end: end_tag,
4579 prefix: delimiter,
4580 tab_size: len,
4581 } = language.documentation_comment()?;
4582 let is_within_block_comment = buffer
4583 .language_scope_at(start_point)
4584 .is_some_and(|scope| scope.override_name() == Some("comment"));
4585 if !is_within_block_comment {
4586 return None;
4587 }
4588
4589 let (snapshot, range) =
4590 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4591
4592 let num_of_whitespaces = snapshot
4593 .chars_for_range(range.clone())
4594 .take_while(|c| c.is_whitespace())
4595 .count();
4596
4597 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4598 let column = start_point.column;
4599 let cursor_is_after_start_tag = {
4600 let start_tag_len = start_tag.len();
4601 let start_tag_line = snapshot
4602 .chars_for_range(range.clone())
4603 .skip(num_of_whitespaces)
4604 .take(start_tag_len)
4605 .collect::<String>();
4606 if start_tag_line.starts_with(start_tag.as_ref()) {
4607 num_of_whitespaces + start_tag_len <= column as usize
4608 } else {
4609 false
4610 }
4611 };
4612
4613 let cursor_is_after_delimiter = {
4614 let delimiter_trim = delimiter.trim_end();
4615 let delimiter_line = snapshot
4616 .chars_for_range(range.clone())
4617 .skip(num_of_whitespaces)
4618 .take(delimiter_trim.len())
4619 .collect::<String>();
4620 if delimiter_line.starts_with(delimiter_trim) {
4621 num_of_whitespaces + delimiter_trim.len() <= column as usize
4622 } else {
4623 false
4624 }
4625 };
4626
4627 let cursor_is_before_end_tag_if_exists = {
4628 let mut char_position = 0u32;
4629 let mut end_tag_offset = None;
4630
4631 'outer: for chunk in snapshot.text_for_range(range) {
4632 if let Some(byte_pos) = chunk.find(&**end_tag) {
4633 let chars_before_match =
4634 chunk[..byte_pos].chars().count() as u32;
4635 end_tag_offset =
4636 Some(char_position + chars_before_match);
4637 break 'outer;
4638 }
4639 char_position += chunk.chars().count() as u32;
4640 }
4641
4642 if let Some(end_tag_offset) = end_tag_offset {
4643 let cursor_is_before_end_tag = column <= end_tag_offset;
4644 if cursor_is_after_start_tag {
4645 if cursor_is_before_end_tag {
4646 insert_extra_newline = true;
4647 }
4648 let cursor_is_at_start_of_end_tag =
4649 column == end_tag_offset;
4650 if cursor_is_at_start_of_end_tag {
4651 indent_on_extra_newline.len = *len;
4652 }
4653 }
4654 cursor_is_before_end_tag
4655 } else {
4656 true
4657 }
4658 };
4659
4660 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4661 && cursor_is_before_end_tag_if_exists
4662 {
4663 if cursor_is_after_start_tag {
4664 indent_on_newline.len = *len;
4665 }
4666 Some(delimiter.clone())
4667 } else {
4668 None
4669 }
4670 });
4671
4672 (
4673 comment_delimiter,
4674 doc_delimiter,
4675 insert_extra_newline,
4676 indent_on_newline,
4677 indent_on_extra_newline,
4678 )
4679 } else {
4680 (
4681 None,
4682 None,
4683 false,
4684 IndentSize::default(),
4685 IndentSize::default(),
4686 )
4687 };
4688
4689 let prevent_auto_indent = doc_delimiter.is_some();
4690 let delimiter = comment_delimiter.or(doc_delimiter);
4691
4692 let capacity_for_delimiter =
4693 delimiter.as_deref().map(str::len).unwrap_or_default();
4694 let mut new_text = String::with_capacity(
4695 1 + capacity_for_delimiter
4696 + existing_indent.len as usize
4697 + indent_on_newline.len as usize
4698 + indent_on_extra_newline.len as usize,
4699 );
4700 new_text.push('\n');
4701 new_text.extend(existing_indent.chars());
4702 new_text.extend(indent_on_newline.chars());
4703
4704 if let Some(delimiter) = &delimiter {
4705 new_text.push_str(delimiter);
4706 }
4707
4708 if insert_extra_newline {
4709 new_text.push('\n');
4710 new_text.extend(existing_indent.chars());
4711 new_text.extend(indent_on_extra_newline.chars());
4712 }
4713
4714 let anchor = buffer.anchor_after(end);
4715 let new_selection = selection.map(|_| anchor);
4716 (
4717 ((start..end, new_text), prevent_auto_indent),
4718 (insert_extra_newline, new_selection),
4719 )
4720 })
4721 .unzip()
4722 };
4723
4724 let mut auto_indent_edits = Vec::new();
4725 let mut edits = Vec::new();
4726 for (edit, prevent_auto_indent) in edits_with_flags {
4727 if prevent_auto_indent {
4728 edits.push(edit);
4729 } else {
4730 auto_indent_edits.push(edit);
4731 }
4732 }
4733 if !edits.is_empty() {
4734 this.edit(edits, cx);
4735 }
4736 if !auto_indent_edits.is_empty() {
4737 this.edit_with_autoindent(auto_indent_edits, cx);
4738 }
4739
4740 let buffer = this.buffer.read(cx).snapshot(cx);
4741 let new_selections = selection_info
4742 .into_iter()
4743 .map(|(extra_newline_inserted, new_selection)| {
4744 let mut cursor = new_selection.end.to_point(&buffer);
4745 if extra_newline_inserted {
4746 cursor.row -= 1;
4747 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4748 }
4749 new_selection.map(|_| cursor)
4750 })
4751 .collect();
4752
4753 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4754 this.refresh_edit_prediction(true, false, window, cx);
4755 });
4756 }
4757
4758 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4760
4761 let buffer = self.buffer.read(cx);
4762 let snapshot = buffer.snapshot(cx);
4763
4764 let mut edits = Vec::new();
4765 let mut rows = Vec::new();
4766
4767 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4768 let cursor = selection.head();
4769 let row = cursor.row;
4770
4771 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4772
4773 let newline = "\n".to_string();
4774 edits.push((start_of_line..start_of_line, newline));
4775
4776 rows.push(row + rows_inserted as u32);
4777 }
4778
4779 self.transact(window, cx, |editor, window, cx| {
4780 editor.edit(edits, cx);
4781
4782 editor.change_selections(Default::default(), window, cx, |s| {
4783 let mut index = 0;
4784 s.move_cursors_with(|map, _, _| {
4785 let row = rows[index];
4786 index += 1;
4787
4788 let point = Point::new(row, 0);
4789 let boundary = map.next_line_boundary(point).1;
4790 let clipped = map.clip_point(boundary, Bias::Left);
4791
4792 (clipped, SelectionGoal::None)
4793 });
4794 });
4795
4796 let mut indent_edits = Vec::new();
4797 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4798 for row in rows {
4799 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4800 for (row, indent) in indents {
4801 if indent.len == 0 {
4802 continue;
4803 }
4804
4805 let text = match indent.kind {
4806 IndentKind::Space => " ".repeat(indent.len as usize),
4807 IndentKind::Tab => "\t".repeat(indent.len as usize),
4808 };
4809 let point = Point::new(row.0, 0);
4810 indent_edits.push((point..point, text));
4811 }
4812 }
4813 editor.edit(indent_edits, cx);
4814 });
4815 }
4816
4817 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4819
4820 let buffer = self.buffer.read(cx);
4821 let snapshot = buffer.snapshot(cx);
4822
4823 let mut edits = Vec::new();
4824 let mut rows = Vec::new();
4825 let mut rows_inserted = 0;
4826
4827 for selection in self.selections.all_adjusted(cx) {
4828 let cursor = selection.head();
4829 let row = cursor.row;
4830
4831 let point = Point::new(row + 1, 0);
4832 let start_of_line = snapshot.clip_point(point, Bias::Left);
4833
4834 let newline = "\n".to_string();
4835 edits.push((start_of_line..start_of_line, newline));
4836
4837 rows_inserted += 1;
4838 rows.push(row + rows_inserted);
4839 }
4840
4841 self.transact(window, cx, |editor, window, cx| {
4842 editor.edit(edits, cx);
4843
4844 editor.change_selections(Default::default(), window, cx, |s| {
4845 let mut index = 0;
4846 s.move_cursors_with(|map, _, _| {
4847 let row = rows[index];
4848 index += 1;
4849
4850 let point = Point::new(row, 0);
4851 let boundary = map.next_line_boundary(point).1;
4852 let clipped = map.clip_point(boundary, Bias::Left);
4853
4854 (clipped, SelectionGoal::None)
4855 });
4856 });
4857
4858 let mut indent_edits = Vec::new();
4859 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4860 for row in rows {
4861 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4862 for (row, indent) in indents {
4863 if indent.len == 0 {
4864 continue;
4865 }
4866
4867 let text = match indent.kind {
4868 IndentKind::Space => " ".repeat(indent.len as usize),
4869 IndentKind::Tab => "\t".repeat(indent.len as usize),
4870 };
4871 let point = Point::new(row.0, 0);
4872 indent_edits.push((point..point, text));
4873 }
4874 }
4875 editor.edit(indent_edits, cx);
4876 });
4877 }
4878
4879 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4880 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4881 original_indent_columns: Vec::new(),
4882 });
4883 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4884 }
4885
4886 fn insert_with_autoindent_mode(
4887 &mut self,
4888 text: &str,
4889 autoindent_mode: Option<AutoindentMode>,
4890 window: &mut Window,
4891 cx: &mut Context<Self>,
4892 ) {
4893 if self.read_only(cx) {
4894 return;
4895 }
4896
4897 let text: Arc<str> = text.into();
4898 self.transact(window, cx, |this, window, cx| {
4899 let old_selections = this.selections.all_adjusted(cx);
4900 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4901 let anchors = {
4902 let snapshot = buffer.read(cx);
4903 old_selections
4904 .iter()
4905 .map(|s| {
4906 let anchor = snapshot.anchor_after(s.head());
4907 s.map(|_| anchor)
4908 })
4909 .collect::<Vec<_>>()
4910 };
4911 buffer.edit(
4912 old_selections
4913 .iter()
4914 .map(|s| (s.start..s.end, text.clone())),
4915 autoindent_mode,
4916 cx,
4917 );
4918 anchors
4919 });
4920
4921 this.change_selections(Default::default(), window, cx, |s| {
4922 s.select_anchors(selection_anchors);
4923 });
4924
4925 cx.notify();
4926 });
4927 }
4928
4929 fn trigger_completion_on_input(
4930 &mut self,
4931 text: &str,
4932 trigger_in_words: bool,
4933 window: &mut Window,
4934 cx: &mut Context<Self>,
4935 ) {
4936 let completions_source = self
4937 .context_menu
4938 .borrow()
4939 .as_ref()
4940 .and_then(|menu| match menu {
4941 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4942 CodeContextMenu::CodeActions(_) => None,
4943 });
4944
4945 match completions_source {
4946 Some(CompletionsMenuSource::Words { .. }) => {
4947 self.open_or_update_completions_menu(
4948 Some(CompletionsMenuSource::Words {
4949 ignore_threshold: false,
4950 }),
4951 None,
4952 window,
4953 cx,
4954 );
4955 }
4956 Some(CompletionsMenuSource::Normal)
4957 | Some(CompletionsMenuSource::SnippetChoices)
4958 | None
4959 if self.is_completion_trigger(
4960 text,
4961 trigger_in_words,
4962 completions_source.is_some(),
4963 cx,
4964 ) =>
4965 {
4966 self.show_completions(
4967 &ShowCompletions {
4968 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4969 },
4970 window,
4971 cx,
4972 )
4973 }
4974 _ => {
4975 self.hide_context_menu(window, cx);
4976 }
4977 }
4978 }
4979
4980 fn is_completion_trigger(
4981 &self,
4982 text: &str,
4983 trigger_in_words: bool,
4984 menu_is_open: bool,
4985 cx: &mut Context<Self>,
4986 ) -> bool {
4987 let position = self.selections.newest_anchor().head();
4988 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4989 return false;
4990 };
4991
4992 if let Some(completion_provider) = &self.completion_provider {
4993 completion_provider.is_completion_trigger(
4994 &buffer,
4995 position.text_anchor,
4996 text,
4997 trigger_in_words,
4998 menu_is_open,
4999 cx,
5000 )
5001 } else {
5002 false
5003 }
5004 }
5005
5006 /// If any empty selections is touching the start of its innermost containing autoclose
5007 /// region, expand it to select the brackets.
5008 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5009 let selections = self.selections.all::<usize>(cx);
5010 let buffer = self.buffer.read(cx).read(cx);
5011 let new_selections = self
5012 .selections_with_autoclose_regions(selections, &buffer)
5013 .map(|(mut selection, region)| {
5014 if !selection.is_empty() {
5015 return selection;
5016 }
5017
5018 if let Some(region) = region {
5019 let mut range = region.range.to_offset(&buffer);
5020 if selection.start == range.start && range.start >= region.pair.start.len() {
5021 range.start -= region.pair.start.len();
5022 if buffer.contains_str_at(range.start, ®ion.pair.start)
5023 && buffer.contains_str_at(range.end, ®ion.pair.end)
5024 {
5025 range.end += region.pair.end.len();
5026 selection.start = range.start;
5027 selection.end = range.end;
5028
5029 return selection;
5030 }
5031 }
5032 }
5033
5034 let always_treat_brackets_as_autoclosed = buffer
5035 .language_settings_at(selection.start, cx)
5036 .always_treat_brackets_as_autoclosed;
5037
5038 if !always_treat_brackets_as_autoclosed {
5039 return selection;
5040 }
5041
5042 if let Some(scope) = buffer.language_scope_at(selection.start) {
5043 for (pair, enabled) in scope.brackets() {
5044 if !enabled || !pair.close {
5045 continue;
5046 }
5047
5048 if buffer.contains_str_at(selection.start, &pair.end) {
5049 let pair_start_len = pair.start.len();
5050 if buffer.contains_str_at(
5051 selection.start.saturating_sub(pair_start_len),
5052 &pair.start,
5053 ) {
5054 selection.start -= pair_start_len;
5055 selection.end += pair.end.len();
5056
5057 return selection;
5058 }
5059 }
5060 }
5061 }
5062
5063 selection
5064 })
5065 .collect();
5066
5067 drop(buffer);
5068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5069 selections.select(new_selections)
5070 });
5071 }
5072
5073 /// Iterate the given selections, and for each one, find the smallest surrounding
5074 /// autoclose region. This uses the ordering of the selections and the autoclose
5075 /// regions to avoid repeated comparisons.
5076 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5077 &'a self,
5078 selections: impl IntoIterator<Item = Selection<D>>,
5079 buffer: &'a MultiBufferSnapshot,
5080 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5081 let mut i = 0;
5082 let mut regions = self.autoclose_regions.as_slice();
5083 selections.into_iter().map(move |selection| {
5084 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5085
5086 let mut enclosing = None;
5087 while let Some(pair_state) = regions.get(i) {
5088 if pair_state.range.end.to_offset(buffer) < range.start {
5089 regions = ®ions[i + 1..];
5090 i = 0;
5091 } else if pair_state.range.start.to_offset(buffer) > range.end {
5092 break;
5093 } else {
5094 if pair_state.selection_id == selection.id {
5095 enclosing = Some(pair_state);
5096 }
5097 i += 1;
5098 }
5099 }
5100
5101 (selection, enclosing)
5102 })
5103 }
5104
5105 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5106 fn invalidate_autoclose_regions(
5107 &mut self,
5108 mut selections: &[Selection<Anchor>],
5109 buffer: &MultiBufferSnapshot,
5110 ) {
5111 self.autoclose_regions.retain(|state| {
5112 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5113 return false;
5114 }
5115
5116 let mut i = 0;
5117 while let Some(selection) = selections.get(i) {
5118 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5119 selections = &selections[1..];
5120 continue;
5121 }
5122 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5123 break;
5124 }
5125 if selection.id == state.selection_id {
5126 return true;
5127 } else {
5128 i += 1;
5129 }
5130 }
5131 false
5132 });
5133 }
5134
5135 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5136 let offset = position.to_offset(buffer);
5137 let (word_range, kind) =
5138 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5139 if offset > word_range.start && kind == Some(CharKind::Word) {
5140 Some(
5141 buffer
5142 .text_for_range(word_range.start..offset)
5143 .collect::<String>(),
5144 )
5145 } else {
5146 None
5147 }
5148 }
5149
5150 pub fn toggle_inline_values(
5151 &mut self,
5152 _: &ToggleInlineValues,
5153 _: &mut Window,
5154 cx: &mut Context<Self>,
5155 ) {
5156 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5157
5158 self.refresh_inline_values(cx);
5159 }
5160
5161 pub fn toggle_inlay_hints(
5162 &mut self,
5163 _: &ToggleInlayHints,
5164 _: &mut Window,
5165 cx: &mut Context<Self>,
5166 ) {
5167 self.refresh_inlay_hints(
5168 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5169 cx,
5170 );
5171 }
5172
5173 pub fn inlay_hints_enabled(&self) -> bool {
5174 self.inlay_hint_cache.enabled
5175 }
5176
5177 pub fn inline_values_enabled(&self) -> bool {
5178 self.inline_value_cache.enabled
5179 }
5180
5181 #[cfg(any(test, feature = "test-support"))]
5182 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5183 self.display_map
5184 .read(cx)
5185 .current_inlays()
5186 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5187 .cloned()
5188 .collect()
5189 }
5190
5191 #[cfg(any(test, feature = "test-support"))]
5192 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5193 self.display_map
5194 .read(cx)
5195 .current_inlays()
5196 .cloned()
5197 .collect()
5198 }
5199
5200 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5201 if self.semantics_provider.is_none() || !self.mode.is_full() {
5202 return;
5203 }
5204
5205 let reason_description = reason.description();
5206 let ignore_debounce = matches!(
5207 reason,
5208 InlayHintRefreshReason::SettingsChange(_)
5209 | InlayHintRefreshReason::Toggle(_)
5210 | InlayHintRefreshReason::ExcerptsRemoved(_)
5211 | InlayHintRefreshReason::ModifiersChanged(_)
5212 );
5213 let (invalidate_cache, required_languages) = match reason {
5214 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5215 match self.inlay_hint_cache.modifiers_override(enabled) {
5216 Some(enabled) => {
5217 if enabled {
5218 (InvalidationStrategy::RefreshRequested, None)
5219 } else {
5220 self.clear_inlay_hints(cx);
5221 return;
5222 }
5223 }
5224 None => return,
5225 }
5226 }
5227 InlayHintRefreshReason::Toggle(enabled) => {
5228 if self.inlay_hint_cache.toggle(enabled) {
5229 if enabled {
5230 (InvalidationStrategy::RefreshRequested, None)
5231 } else {
5232 self.clear_inlay_hints(cx);
5233 return;
5234 }
5235 } else {
5236 return;
5237 }
5238 }
5239 InlayHintRefreshReason::SettingsChange(new_settings) => {
5240 match self.inlay_hint_cache.update_settings(
5241 &self.buffer,
5242 new_settings,
5243 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5244 cx,
5245 ) {
5246 ControlFlow::Break(Some(InlaySplice {
5247 to_remove,
5248 to_insert,
5249 })) => {
5250 self.splice_inlays(&to_remove, to_insert, cx);
5251 return;
5252 }
5253 ControlFlow::Break(None) => return,
5254 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5255 }
5256 }
5257 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5258 if let Some(InlaySplice {
5259 to_remove,
5260 to_insert,
5261 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5262 {
5263 self.splice_inlays(&to_remove, to_insert, cx);
5264 }
5265 self.display_map.update(cx, |display_map, _| {
5266 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5267 });
5268 return;
5269 }
5270 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5271 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5272 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5273 }
5274 InlayHintRefreshReason::RefreshRequested => {
5275 (InvalidationStrategy::RefreshRequested, None)
5276 }
5277 };
5278
5279 if let Some(InlaySplice {
5280 to_remove,
5281 to_insert,
5282 }) = self.inlay_hint_cache.spawn_hint_refresh(
5283 reason_description,
5284 self.visible_excerpts(required_languages.as_ref(), cx),
5285 invalidate_cache,
5286 ignore_debounce,
5287 cx,
5288 ) {
5289 self.splice_inlays(&to_remove, to_insert, cx);
5290 }
5291 }
5292
5293 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5294 self.splice_inlays(
5295 &self
5296 .visible_inlay_hints(cx)
5297 .map(|inlay| inlay.id)
5298 .collect::<Vec<_>>(),
5299 Vec::new(),
5300 cx,
5301 );
5302 }
5303
5304 fn visible_inlay_hints<'a>(
5305 &'a self,
5306 cx: &'a Context<Editor>,
5307 ) -> impl Iterator<Item = &'a Inlay> {
5308 self.display_map
5309 .read(cx)
5310 .current_inlays()
5311 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5312 }
5313
5314 pub fn visible_excerpts(
5315 &self,
5316 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5317 cx: &mut Context<Editor>,
5318 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5319 let Some(project) = self.project() else {
5320 return HashMap::default();
5321 };
5322 let project = project.read(cx);
5323 let multi_buffer = self.buffer().read(cx);
5324 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5325 let multi_buffer_visible_start = self
5326 .scroll_manager
5327 .anchor()
5328 .anchor
5329 .to_point(&multi_buffer_snapshot);
5330 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5331 multi_buffer_visible_start
5332 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5333 Bias::Left,
5334 );
5335 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5336 multi_buffer_snapshot
5337 .range_to_buffer_ranges(multi_buffer_visible_range)
5338 .into_iter()
5339 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5340 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5341 let buffer_file = project::File::from_dyn(buffer.file())?;
5342 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5343 let worktree_entry = buffer_worktree
5344 .read(cx)
5345 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5346 if worktree_entry.is_ignored {
5347 return None;
5348 }
5349
5350 let language = buffer.language()?;
5351 if let Some(restrict_to_languages) = restrict_to_languages
5352 && !restrict_to_languages.contains(language)
5353 {
5354 return None;
5355 }
5356 Some((
5357 excerpt_id,
5358 (
5359 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5360 buffer.version().clone(),
5361 excerpt_visible_range,
5362 ),
5363 ))
5364 })
5365 .collect()
5366 }
5367
5368 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5369 TextLayoutDetails {
5370 text_system: window.text_system().clone(),
5371 editor_style: self.style.clone().unwrap(),
5372 rem_size: window.rem_size(),
5373 scroll_anchor: self.scroll_manager.anchor(),
5374 visible_rows: self.visible_line_count(),
5375 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5376 }
5377 }
5378
5379 pub fn splice_inlays(
5380 &self,
5381 to_remove: &[InlayId],
5382 to_insert: Vec<Inlay>,
5383 cx: &mut Context<Self>,
5384 ) {
5385 self.display_map.update(cx, |display_map, cx| {
5386 display_map.splice_inlays(to_remove, to_insert, cx)
5387 });
5388 cx.notify();
5389 }
5390
5391 fn trigger_on_type_formatting(
5392 &self,
5393 input: String,
5394 window: &mut Window,
5395 cx: &mut Context<Self>,
5396 ) -> Option<Task<Result<()>>> {
5397 if input.len() != 1 {
5398 return None;
5399 }
5400
5401 let project = self.project()?;
5402 let position = self.selections.newest_anchor().head();
5403 let (buffer, buffer_position) = self
5404 .buffer
5405 .read(cx)
5406 .text_anchor_for_position(position, cx)?;
5407
5408 let settings = language_settings::language_settings(
5409 buffer
5410 .read(cx)
5411 .language_at(buffer_position)
5412 .map(|l| l.name()),
5413 buffer.read(cx).file(),
5414 cx,
5415 );
5416 if !settings.use_on_type_format {
5417 return None;
5418 }
5419
5420 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5421 // hence we do LSP request & edit on host side only — add formats to host's history.
5422 let push_to_lsp_host_history = true;
5423 // If this is not the host, append its history with new edits.
5424 let push_to_client_history = project.read(cx).is_via_collab();
5425
5426 let on_type_formatting = project.update(cx, |project, cx| {
5427 project.on_type_format(
5428 buffer.clone(),
5429 buffer_position,
5430 input,
5431 push_to_lsp_host_history,
5432 cx,
5433 )
5434 });
5435 Some(cx.spawn_in(window, async move |editor, cx| {
5436 if let Some(transaction) = on_type_formatting.await? {
5437 if push_to_client_history {
5438 buffer
5439 .update(cx, |buffer, _| {
5440 buffer.push_transaction(transaction, Instant::now());
5441 buffer.finalize_last_transaction();
5442 })
5443 .ok();
5444 }
5445 editor.update(cx, |editor, cx| {
5446 editor.refresh_document_highlights(cx);
5447 })?;
5448 }
5449 Ok(())
5450 }))
5451 }
5452
5453 pub fn show_word_completions(
5454 &mut self,
5455 _: &ShowWordCompletions,
5456 window: &mut Window,
5457 cx: &mut Context<Self>,
5458 ) {
5459 self.open_or_update_completions_menu(
5460 Some(CompletionsMenuSource::Words {
5461 ignore_threshold: true,
5462 }),
5463 None,
5464 window,
5465 cx,
5466 );
5467 }
5468
5469 pub fn show_completions(
5470 &mut self,
5471 options: &ShowCompletions,
5472 window: &mut Window,
5473 cx: &mut Context<Self>,
5474 ) {
5475 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5476 }
5477
5478 fn open_or_update_completions_menu(
5479 &mut self,
5480 requested_source: Option<CompletionsMenuSource>,
5481 trigger: Option<&str>,
5482 window: &mut Window,
5483 cx: &mut Context<Self>,
5484 ) {
5485 if self.pending_rename.is_some() {
5486 return;
5487 }
5488
5489 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5490
5491 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5492 // inserted and selected. To handle that case, the start of the selection is used so that
5493 // the menu starts with all choices.
5494 let position = self
5495 .selections
5496 .newest_anchor()
5497 .start
5498 .bias_right(&multibuffer_snapshot);
5499 if position.diff_base_anchor.is_some() {
5500 return;
5501 }
5502 let buffer_position = multibuffer_snapshot.anchor_before(position);
5503 let Some(buffer) = buffer_position
5504 .buffer_id
5505 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5506 else {
5507 return;
5508 };
5509 let buffer_snapshot = buffer.read(cx).snapshot();
5510
5511 let query: Option<Arc<String>> =
5512 Self::completion_query(&multibuffer_snapshot, buffer_position)
5513 .map(|query| query.into());
5514
5515 drop(multibuffer_snapshot);
5516
5517 // Hide the current completions menu when query is empty. Without this, cached
5518 // completions from before the trigger char may be reused (#32774).
5519 if query.is_none() {
5520 let menu_is_open = matches!(
5521 self.context_menu.borrow().as_ref(),
5522 Some(CodeContextMenu::Completions(_))
5523 );
5524 if menu_is_open {
5525 self.hide_context_menu(window, cx);
5526 }
5527 }
5528
5529 let mut ignore_word_threshold = false;
5530 let provider = match requested_source {
5531 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5532 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5533 ignore_word_threshold = ignore_threshold;
5534 None
5535 }
5536 Some(CompletionsMenuSource::SnippetChoices) => {
5537 log::error!("bug: SnippetChoices requested_source is not handled");
5538 None
5539 }
5540 };
5541
5542 let sort_completions = provider
5543 .as_ref()
5544 .is_some_and(|provider| provider.sort_completions());
5545
5546 let filter_completions = provider
5547 .as_ref()
5548 .is_none_or(|provider| provider.filter_completions());
5549
5550 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5551 if filter_completions {
5552 menu.filter(query.clone(), provider.clone(), window, cx);
5553 }
5554 // When `is_incomplete` is false, no need to re-query completions when the current query
5555 // is a suffix of the initial query.
5556 if !menu.is_incomplete {
5557 // If the new query is a suffix of the old query (typing more characters) and
5558 // the previous result was complete, the existing completions can be filtered.
5559 //
5560 // Note that this is always true for snippet completions.
5561 let query_matches = match (&menu.initial_query, &query) {
5562 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5563 (None, _) => true,
5564 _ => false,
5565 };
5566 if query_matches {
5567 let position_matches = if menu.initial_position == position {
5568 true
5569 } else {
5570 let snapshot = self.buffer.read(cx).read(cx);
5571 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5572 };
5573 if position_matches {
5574 return;
5575 }
5576 }
5577 }
5578 };
5579
5580 let trigger_kind = match trigger {
5581 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5582 CompletionTriggerKind::TRIGGER_CHARACTER
5583 }
5584 _ => CompletionTriggerKind::INVOKED,
5585 };
5586 let completion_context = CompletionContext {
5587 trigger_character: trigger.and_then(|trigger| {
5588 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5589 Some(String::from(trigger))
5590 } else {
5591 None
5592 }
5593 }),
5594 trigger_kind,
5595 };
5596
5597 let Anchor {
5598 excerpt_id: buffer_excerpt_id,
5599 text_anchor: buffer_position,
5600 ..
5601 } = buffer_position;
5602
5603 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5604 buffer_snapshot.surrounding_word(buffer_position, None)
5605 {
5606 let word_to_exclude = buffer_snapshot
5607 .text_for_range(word_range.clone())
5608 .collect::<String>();
5609 (
5610 buffer_snapshot.anchor_before(word_range.start)
5611 ..buffer_snapshot.anchor_after(buffer_position),
5612 Some(word_to_exclude),
5613 )
5614 } else {
5615 (buffer_position..buffer_position, None)
5616 };
5617
5618 let language = buffer_snapshot
5619 .language_at(buffer_position)
5620 .map(|language| language.name());
5621
5622 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5623 .completions
5624 .clone();
5625
5626 let show_completion_documentation = buffer_snapshot
5627 .settings_at(buffer_position, cx)
5628 .show_completion_documentation;
5629
5630 // The document can be large, so stay in reasonable bounds when searching for words,
5631 // otherwise completion pop-up might be slow to appear.
5632 const WORD_LOOKUP_ROWS: u32 = 5_000;
5633 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5634 let min_word_search = buffer_snapshot.clip_point(
5635 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5636 Bias::Left,
5637 );
5638 let max_word_search = buffer_snapshot.clip_point(
5639 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5640 Bias::Right,
5641 );
5642 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5643 ..buffer_snapshot.point_to_offset(max_word_search);
5644
5645 let skip_digits = query
5646 .as_ref()
5647 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5648
5649 let omit_word_completions = !self.word_completions_enabled
5650 || (!ignore_word_threshold
5651 && match &query {
5652 Some(query) => query.chars().count() < completion_settings.words_min_length,
5653 None => completion_settings.words_min_length != 0,
5654 });
5655
5656 let (mut words, provider_responses) = match &provider {
5657 Some(provider) => {
5658 let provider_responses = provider.completions(
5659 buffer_excerpt_id,
5660 &buffer,
5661 buffer_position,
5662 completion_context,
5663 window,
5664 cx,
5665 );
5666
5667 let words = match (omit_word_completions, completion_settings.words) {
5668 (true, _) | (_, WordsCompletionMode::Disabled) => {
5669 Task::ready(BTreeMap::default())
5670 }
5671 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5672 .background_spawn(async move {
5673 buffer_snapshot.words_in_range(WordsQuery {
5674 fuzzy_contents: None,
5675 range: word_search_range,
5676 skip_digits,
5677 })
5678 }),
5679 };
5680
5681 (words, provider_responses)
5682 }
5683 None => {
5684 let words = if omit_word_completions {
5685 Task::ready(BTreeMap::default())
5686 } else {
5687 cx.background_spawn(async move {
5688 buffer_snapshot.words_in_range(WordsQuery {
5689 fuzzy_contents: None,
5690 range: word_search_range,
5691 skip_digits,
5692 })
5693 })
5694 };
5695 (words, Task::ready(Ok(Vec::new())))
5696 }
5697 };
5698
5699 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5700
5701 let id = post_inc(&mut self.next_completion_id);
5702 let task = cx.spawn_in(window, async move |editor, cx| {
5703 let Ok(()) = editor.update(cx, |this, _| {
5704 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5705 }) else {
5706 return;
5707 };
5708
5709 // TODO: Ideally completions from different sources would be selectively re-queried, so
5710 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5711 let mut completions = Vec::new();
5712 let mut is_incomplete = false;
5713 let mut display_options: Option<CompletionDisplayOptions> = None;
5714 if let Some(provider_responses) = provider_responses.await.log_err()
5715 && !provider_responses.is_empty()
5716 {
5717 for response in provider_responses {
5718 completions.extend(response.completions);
5719 is_incomplete = is_incomplete || response.is_incomplete;
5720 match display_options.as_mut() {
5721 None => {
5722 display_options = Some(response.display_options);
5723 }
5724 Some(options) => options.merge(&response.display_options),
5725 }
5726 }
5727 if completion_settings.words == WordsCompletionMode::Fallback {
5728 words = Task::ready(BTreeMap::default());
5729 }
5730 }
5731 let display_options = display_options.unwrap_or_default();
5732
5733 let mut words = words.await;
5734 if let Some(word_to_exclude) = &word_to_exclude {
5735 words.remove(word_to_exclude);
5736 }
5737 for lsp_completion in &completions {
5738 words.remove(&lsp_completion.new_text);
5739 }
5740 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5741 replace_range: word_replace_range.clone(),
5742 new_text: word.clone(),
5743 label: CodeLabel::plain(word, None),
5744 icon_path: None,
5745 documentation: None,
5746 source: CompletionSource::BufferWord {
5747 word_range,
5748 resolved: false,
5749 },
5750 insert_text_mode: Some(InsertTextMode::AS_IS),
5751 confirm: None,
5752 }));
5753
5754 let menu = if completions.is_empty() {
5755 None
5756 } else {
5757 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5758 let languages = editor
5759 .workspace
5760 .as_ref()
5761 .and_then(|(workspace, _)| workspace.upgrade())
5762 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5763 let menu = CompletionsMenu::new(
5764 id,
5765 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5766 sort_completions,
5767 show_completion_documentation,
5768 position,
5769 query.clone(),
5770 is_incomplete,
5771 buffer.clone(),
5772 completions.into(),
5773 display_options,
5774 snippet_sort_order,
5775 languages,
5776 language,
5777 cx,
5778 );
5779
5780 let query = if filter_completions { query } else { None };
5781 let matches_task = if let Some(query) = query {
5782 menu.do_async_filtering(query, cx)
5783 } else {
5784 Task::ready(menu.unfiltered_matches())
5785 };
5786 (menu, matches_task)
5787 }) else {
5788 return;
5789 };
5790
5791 let matches = matches_task.await;
5792
5793 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5794 // Newer menu already set, so exit.
5795 if let Some(CodeContextMenu::Completions(prev_menu)) =
5796 editor.context_menu.borrow().as_ref()
5797 && prev_menu.id > id
5798 {
5799 return;
5800 };
5801
5802 // Only valid to take prev_menu because it the new menu is immediately set
5803 // below, or the menu is hidden.
5804 if let Some(CodeContextMenu::Completions(prev_menu)) =
5805 editor.context_menu.borrow_mut().take()
5806 {
5807 let position_matches =
5808 if prev_menu.initial_position == menu.initial_position {
5809 true
5810 } else {
5811 let snapshot = editor.buffer.read(cx).read(cx);
5812 prev_menu.initial_position.to_offset(&snapshot)
5813 == menu.initial_position.to_offset(&snapshot)
5814 };
5815 if position_matches {
5816 // Preserve markdown cache before `set_filter_results` because it will
5817 // try to populate the documentation cache.
5818 menu.preserve_markdown_cache(prev_menu);
5819 }
5820 };
5821
5822 menu.set_filter_results(matches, provider, window, cx);
5823 }) else {
5824 return;
5825 };
5826
5827 menu.visible().then_some(menu)
5828 };
5829
5830 editor
5831 .update_in(cx, |editor, window, cx| {
5832 if editor.focus_handle.is_focused(window)
5833 && let Some(menu) = menu
5834 {
5835 *editor.context_menu.borrow_mut() =
5836 Some(CodeContextMenu::Completions(menu));
5837
5838 crate::hover_popover::hide_hover(editor, cx);
5839 if editor.show_edit_predictions_in_menu() {
5840 editor.update_visible_edit_prediction(window, cx);
5841 } else {
5842 editor.discard_edit_prediction(false, cx);
5843 }
5844
5845 cx.notify();
5846 return;
5847 }
5848
5849 if editor.completion_tasks.len() <= 1 {
5850 // If there are no more completion tasks and the last menu was empty, we should hide it.
5851 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5852 // If it was already hidden and we don't show edit predictions in the menu,
5853 // we should also show the edit prediction when available.
5854 if was_hidden && editor.show_edit_predictions_in_menu() {
5855 editor.update_visible_edit_prediction(window, cx);
5856 }
5857 }
5858 })
5859 .ok();
5860 });
5861
5862 self.completion_tasks.push((id, task));
5863 }
5864
5865 #[cfg(feature = "test-support")]
5866 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5867 let menu = self.context_menu.borrow();
5868 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5869 let completions = menu.completions.borrow();
5870 Some(completions.to_vec())
5871 } else {
5872 None
5873 }
5874 }
5875
5876 pub fn with_completions_menu_matching_id<R>(
5877 &self,
5878 id: CompletionId,
5879 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5880 ) -> R {
5881 let mut context_menu = self.context_menu.borrow_mut();
5882 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5883 return f(None);
5884 };
5885 if completions_menu.id != id {
5886 return f(None);
5887 }
5888 f(Some(completions_menu))
5889 }
5890
5891 pub fn confirm_completion(
5892 &mut self,
5893 action: &ConfirmCompletion,
5894 window: &mut Window,
5895 cx: &mut Context<Self>,
5896 ) -> Option<Task<Result<()>>> {
5897 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5898 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5899 }
5900
5901 pub fn confirm_completion_insert(
5902 &mut self,
5903 _: &ConfirmCompletionInsert,
5904 window: &mut Window,
5905 cx: &mut Context<Self>,
5906 ) -> Option<Task<Result<()>>> {
5907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5908 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5909 }
5910
5911 pub fn confirm_completion_replace(
5912 &mut self,
5913 _: &ConfirmCompletionReplace,
5914 window: &mut Window,
5915 cx: &mut Context<Self>,
5916 ) -> Option<Task<Result<()>>> {
5917 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5918 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5919 }
5920
5921 pub fn compose_completion(
5922 &mut self,
5923 action: &ComposeCompletion,
5924 window: &mut Window,
5925 cx: &mut Context<Self>,
5926 ) -> Option<Task<Result<()>>> {
5927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5928 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5929 }
5930
5931 fn do_completion(
5932 &mut self,
5933 item_ix: Option<usize>,
5934 intent: CompletionIntent,
5935 window: &mut Window,
5936 cx: &mut Context<Editor>,
5937 ) -> Option<Task<Result<()>>> {
5938 use language::ToOffset as _;
5939
5940 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5941 else {
5942 return None;
5943 };
5944
5945 let candidate_id = {
5946 let entries = completions_menu.entries.borrow();
5947 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5948 if self.show_edit_predictions_in_menu() {
5949 self.discard_edit_prediction(true, cx);
5950 }
5951 mat.candidate_id
5952 };
5953
5954 let completion = completions_menu
5955 .completions
5956 .borrow()
5957 .get(candidate_id)?
5958 .clone();
5959 cx.stop_propagation();
5960
5961 let buffer_handle = completions_menu.buffer.clone();
5962
5963 let CompletionEdit {
5964 new_text,
5965 snippet,
5966 replace_range,
5967 } = process_completion_for_edit(
5968 &completion,
5969 intent,
5970 &buffer_handle,
5971 &completions_menu.initial_position.text_anchor,
5972 cx,
5973 );
5974
5975 let buffer = buffer_handle.read(cx);
5976 let snapshot = self.buffer.read(cx).snapshot(cx);
5977 let newest_anchor = self.selections.newest_anchor();
5978 let replace_range_multibuffer = {
5979 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5980 let multibuffer_anchor = snapshot
5981 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5982 .unwrap()
5983 ..snapshot
5984 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5985 .unwrap();
5986 multibuffer_anchor.start.to_offset(&snapshot)
5987 ..multibuffer_anchor.end.to_offset(&snapshot)
5988 };
5989 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5990 return None;
5991 }
5992
5993 let old_text = buffer
5994 .text_for_range(replace_range.clone())
5995 .collect::<String>();
5996 let lookbehind = newest_anchor
5997 .start
5998 .text_anchor
5999 .to_offset(buffer)
6000 .saturating_sub(replace_range.start);
6001 let lookahead = replace_range
6002 .end
6003 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6004 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6005 let suffix = &old_text[lookbehind.min(old_text.len())..];
6006
6007 let selections = self.selections.all::<usize>(cx);
6008 let mut ranges = Vec::new();
6009 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6010
6011 for selection in &selections {
6012 let range = if selection.id == newest_anchor.id {
6013 replace_range_multibuffer.clone()
6014 } else {
6015 let mut range = selection.range();
6016
6017 // if prefix is present, don't duplicate it
6018 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6019 range.start = range.start.saturating_sub(lookbehind);
6020
6021 // if suffix is also present, mimic the newest cursor and replace it
6022 if selection.id != newest_anchor.id
6023 && snapshot.contains_str_at(range.end, suffix)
6024 {
6025 range.end += lookahead;
6026 }
6027 }
6028 range
6029 };
6030
6031 ranges.push(range.clone());
6032
6033 if !self.linked_edit_ranges.is_empty() {
6034 let start_anchor = snapshot.anchor_before(range.start);
6035 let end_anchor = snapshot.anchor_after(range.end);
6036 if let Some(ranges) = self
6037 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6038 {
6039 for (buffer, edits) in ranges {
6040 linked_edits
6041 .entry(buffer.clone())
6042 .or_default()
6043 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6044 }
6045 }
6046 }
6047 }
6048
6049 let common_prefix_len = old_text
6050 .chars()
6051 .zip(new_text.chars())
6052 .take_while(|(a, b)| a == b)
6053 .map(|(a, _)| a.len_utf8())
6054 .sum::<usize>();
6055
6056 cx.emit(EditorEvent::InputHandled {
6057 utf16_range_to_replace: None,
6058 text: new_text[common_prefix_len..].into(),
6059 });
6060
6061 self.transact(window, cx, |editor, window, cx| {
6062 if let Some(mut snippet) = snippet {
6063 snippet.text = new_text.to_string();
6064 editor
6065 .insert_snippet(&ranges, snippet, window, cx)
6066 .log_err();
6067 } else {
6068 editor.buffer.update(cx, |multi_buffer, cx| {
6069 let auto_indent = match completion.insert_text_mode {
6070 Some(InsertTextMode::AS_IS) => None,
6071 _ => editor.autoindent_mode.clone(),
6072 };
6073 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6074 multi_buffer.edit(edits, auto_indent, cx);
6075 });
6076 }
6077 for (buffer, edits) in linked_edits {
6078 buffer.update(cx, |buffer, cx| {
6079 let snapshot = buffer.snapshot();
6080 let edits = edits
6081 .into_iter()
6082 .map(|(range, text)| {
6083 use text::ToPoint as TP;
6084 let end_point = TP::to_point(&range.end, &snapshot);
6085 let start_point = TP::to_point(&range.start, &snapshot);
6086 (start_point..end_point, text)
6087 })
6088 .sorted_by_key(|(range, _)| range.start);
6089 buffer.edit(edits, None, cx);
6090 })
6091 }
6092
6093 editor.refresh_edit_prediction(true, false, window, cx);
6094 });
6095 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6096
6097 let show_new_completions_on_confirm = completion
6098 .confirm
6099 .as_ref()
6100 .is_some_and(|confirm| confirm(intent, window, cx));
6101 if show_new_completions_on_confirm {
6102 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6103 }
6104
6105 let provider = self.completion_provider.as_ref()?;
6106 drop(completion);
6107 let apply_edits = provider.apply_additional_edits_for_completion(
6108 buffer_handle,
6109 completions_menu.completions.clone(),
6110 candidate_id,
6111 true,
6112 cx,
6113 );
6114
6115 let editor_settings = EditorSettings::get_global(cx);
6116 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6117 // After the code completion is finished, users often want to know what signatures are needed.
6118 // so we should automatically call signature_help
6119 self.show_signature_help(&ShowSignatureHelp, window, cx);
6120 }
6121
6122 Some(cx.foreground_executor().spawn(async move {
6123 apply_edits.await?;
6124 Ok(())
6125 }))
6126 }
6127
6128 pub fn toggle_code_actions(
6129 &mut self,
6130 action: &ToggleCodeActions,
6131 window: &mut Window,
6132 cx: &mut Context<Self>,
6133 ) {
6134 let quick_launch = action.quick_launch;
6135 let mut context_menu = self.context_menu.borrow_mut();
6136 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6137 if code_actions.deployed_from == action.deployed_from {
6138 // Toggle if we're selecting the same one
6139 *context_menu = None;
6140 cx.notify();
6141 return;
6142 } else {
6143 // Otherwise, clear it and start a new one
6144 *context_menu = None;
6145 cx.notify();
6146 }
6147 }
6148 drop(context_menu);
6149 let snapshot = self.snapshot(window, cx);
6150 let deployed_from = action.deployed_from.clone();
6151 let action = action.clone();
6152 self.completion_tasks.clear();
6153 self.discard_edit_prediction(false, cx);
6154
6155 let multibuffer_point = match &action.deployed_from {
6156 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6157 DisplayPoint::new(*row, 0).to_point(&snapshot)
6158 }
6159 _ => self.selections.newest::<Point>(cx).head(),
6160 };
6161 let Some((buffer, buffer_row)) = snapshot
6162 .buffer_snapshot()
6163 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6164 .and_then(|(buffer_snapshot, range)| {
6165 self.buffer()
6166 .read(cx)
6167 .buffer(buffer_snapshot.remote_id())
6168 .map(|buffer| (buffer, range.start.row))
6169 })
6170 else {
6171 return;
6172 };
6173 let buffer_id = buffer.read(cx).remote_id();
6174 let tasks = self
6175 .tasks
6176 .get(&(buffer_id, buffer_row))
6177 .map(|t| Arc::new(t.to_owned()));
6178
6179 if !self.focus_handle.is_focused(window) {
6180 return;
6181 }
6182 let project = self.project.clone();
6183
6184 let code_actions_task = match deployed_from {
6185 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6186 _ => self.code_actions(buffer_row, window, cx),
6187 };
6188
6189 let runnable_task = match deployed_from {
6190 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6191 _ => {
6192 let mut task_context_task = Task::ready(None);
6193 if let Some(tasks) = &tasks
6194 && let Some(project) = project
6195 {
6196 task_context_task =
6197 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6198 }
6199
6200 cx.spawn_in(window, {
6201 let buffer = buffer.clone();
6202 async move |editor, cx| {
6203 let task_context = task_context_task.await;
6204
6205 let resolved_tasks =
6206 tasks
6207 .zip(task_context.clone())
6208 .map(|(tasks, task_context)| ResolvedTasks {
6209 templates: tasks.resolve(&task_context).collect(),
6210 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6211 multibuffer_point.row,
6212 tasks.column,
6213 )),
6214 });
6215 let debug_scenarios = editor
6216 .update(cx, |editor, cx| {
6217 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6218 })?
6219 .await;
6220 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6221 }
6222 })
6223 }
6224 };
6225
6226 cx.spawn_in(window, async move |editor, cx| {
6227 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6228 let code_actions = code_actions_task.await;
6229 let spawn_straight_away = quick_launch
6230 && resolved_tasks
6231 .as_ref()
6232 .is_some_and(|tasks| tasks.templates.len() == 1)
6233 && code_actions
6234 .as_ref()
6235 .is_none_or(|actions| actions.is_empty())
6236 && debug_scenarios.is_empty();
6237
6238 editor.update_in(cx, |editor, window, cx| {
6239 crate::hover_popover::hide_hover(editor, cx);
6240 let actions = CodeActionContents::new(
6241 resolved_tasks,
6242 code_actions,
6243 debug_scenarios,
6244 task_context.unwrap_or_default(),
6245 );
6246
6247 // Don't show the menu if there are no actions available
6248 if actions.is_empty() {
6249 cx.notify();
6250 return Task::ready(Ok(()));
6251 }
6252
6253 *editor.context_menu.borrow_mut() =
6254 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6255 buffer,
6256 actions,
6257 selected_item: Default::default(),
6258 scroll_handle: UniformListScrollHandle::default(),
6259 deployed_from,
6260 }));
6261 cx.notify();
6262 if spawn_straight_away
6263 && let Some(task) = editor.confirm_code_action(
6264 &ConfirmCodeAction { item_ix: Some(0) },
6265 window,
6266 cx,
6267 )
6268 {
6269 return task;
6270 }
6271
6272 Task::ready(Ok(()))
6273 })
6274 })
6275 .detach_and_log_err(cx);
6276 }
6277
6278 fn debug_scenarios(
6279 &mut self,
6280 resolved_tasks: &Option<ResolvedTasks>,
6281 buffer: &Entity<Buffer>,
6282 cx: &mut App,
6283 ) -> Task<Vec<task::DebugScenario>> {
6284 maybe!({
6285 let project = self.project()?;
6286 let dap_store = project.read(cx).dap_store();
6287 let mut scenarios = vec![];
6288 let resolved_tasks = resolved_tasks.as_ref()?;
6289 let buffer = buffer.read(cx);
6290 let language = buffer.language()?;
6291 let file = buffer.file();
6292 let debug_adapter = language_settings(language.name().into(), file, cx)
6293 .debuggers
6294 .first()
6295 .map(SharedString::from)
6296 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6297
6298 dap_store.update(cx, |dap_store, cx| {
6299 for (_, task) in &resolved_tasks.templates {
6300 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6301 task.original_task().clone(),
6302 debug_adapter.clone().into(),
6303 task.display_label().to_owned().into(),
6304 cx,
6305 );
6306 scenarios.push(maybe_scenario);
6307 }
6308 });
6309 Some(cx.background_spawn(async move {
6310 futures::future::join_all(scenarios)
6311 .await
6312 .into_iter()
6313 .flatten()
6314 .collect::<Vec<_>>()
6315 }))
6316 })
6317 .unwrap_or_else(|| Task::ready(vec![]))
6318 }
6319
6320 fn code_actions(
6321 &mut self,
6322 buffer_row: u32,
6323 window: &mut Window,
6324 cx: &mut Context<Self>,
6325 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6326 let mut task = self.code_actions_task.take();
6327 cx.spawn_in(window, async move |editor, cx| {
6328 while let Some(prev_task) = task {
6329 prev_task.await.log_err();
6330 task = editor
6331 .update(cx, |this, _| this.code_actions_task.take())
6332 .ok()?;
6333 }
6334
6335 editor
6336 .update(cx, |editor, cx| {
6337 editor
6338 .available_code_actions
6339 .clone()
6340 .and_then(|(location, code_actions)| {
6341 let snapshot = location.buffer.read(cx).snapshot();
6342 let point_range = location.range.to_point(&snapshot);
6343 let point_range = point_range.start.row..=point_range.end.row;
6344 if point_range.contains(&buffer_row) {
6345 Some(code_actions)
6346 } else {
6347 None
6348 }
6349 })
6350 })
6351 .ok()
6352 .flatten()
6353 })
6354 }
6355
6356 pub fn confirm_code_action(
6357 &mut self,
6358 action: &ConfirmCodeAction,
6359 window: &mut Window,
6360 cx: &mut Context<Self>,
6361 ) -> Option<Task<Result<()>>> {
6362 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6363
6364 let actions_menu =
6365 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6366 menu
6367 } else {
6368 return None;
6369 };
6370
6371 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6372 let action = actions_menu.actions.get(action_ix)?;
6373 let title = action.label();
6374 let buffer = actions_menu.buffer;
6375 let workspace = self.workspace()?;
6376
6377 match action {
6378 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6379 workspace.update(cx, |workspace, cx| {
6380 workspace.schedule_resolved_task(
6381 task_source_kind,
6382 resolved_task,
6383 false,
6384 window,
6385 cx,
6386 );
6387
6388 Some(Task::ready(Ok(())))
6389 })
6390 }
6391 CodeActionsItem::CodeAction {
6392 excerpt_id,
6393 action,
6394 provider,
6395 } => {
6396 let apply_code_action =
6397 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6398 let workspace = workspace.downgrade();
6399 Some(cx.spawn_in(window, async move |editor, cx| {
6400 let project_transaction = apply_code_action.await?;
6401 Self::open_project_transaction(
6402 &editor,
6403 workspace,
6404 project_transaction,
6405 title,
6406 cx,
6407 )
6408 .await
6409 }))
6410 }
6411 CodeActionsItem::DebugScenario(scenario) => {
6412 let context = actions_menu.actions.context;
6413
6414 workspace.update(cx, |workspace, cx| {
6415 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6416 workspace.start_debug_session(
6417 scenario,
6418 context,
6419 Some(buffer),
6420 None,
6421 window,
6422 cx,
6423 );
6424 });
6425 Some(Task::ready(Ok(())))
6426 }
6427 }
6428 }
6429
6430 pub async fn open_project_transaction(
6431 editor: &WeakEntity<Editor>,
6432 workspace: WeakEntity<Workspace>,
6433 transaction: ProjectTransaction,
6434 title: String,
6435 cx: &mut AsyncWindowContext,
6436 ) -> Result<()> {
6437 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6438 cx.update(|_, cx| {
6439 entries.sort_unstable_by_key(|(buffer, _)| {
6440 buffer.read(cx).file().map(|f| f.path().clone())
6441 });
6442 })?;
6443 if entries.is_empty() {
6444 return Ok(());
6445 }
6446
6447 // If the project transaction's edits are all contained within this editor, then
6448 // avoid opening a new editor to display them.
6449
6450 if let [(buffer, transaction)] = &*entries {
6451 let excerpt = editor.update(cx, |editor, cx| {
6452 editor
6453 .buffer()
6454 .read(cx)
6455 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6456 })?;
6457 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6458 && excerpted_buffer == *buffer
6459 {
6460 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6461 let excerpt_range = excerpt_range.to_offset(buffer);
6462 buffer
6463 .edited_ranges_for_transaction::<usize>(transaction)
6464 .all(|range| {
6465 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6466 })
6467 })?;
6468
6469 if all_edits_within_excerpt {
6470 return Ok(());
6471 }
6472 }
6473 }
6474
6475 let mut ranges_to_highlight = Vec::new();
6476 let excerpt_buffer = cx.new(|cx| {
6477 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6478 for (buffer_handle, transaction) in &entries {
6479 let edited_ranges = buffer_handle
6480 .read(cx)
6481 .edited_ranges_for_transaction::<Point>(transaction)
6482 .collect::<Vec<_>>();
6483 let (ranges, _) = multibuffer.set_excerpts_for_path(
6484 PathKey::for_buffer(buffer_handle, cx),
6485 buffer_handle.clone(),
6486 edited_ranges,
6487 multibuffer_context_lines(cx),
6488 cx,
6489 );
6490
6491 ranges_to_highlight.extend(ranges);
6492 }
6493 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6494 multibuffer
6495 })?;
6496
6497 workspace.update_in(cx, |workspace, window, cx| {
6498 let project = workspace.project().clone();
6499 let editor =
6500 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6501 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6502 editor.update(cx, |editor, cx| {
6503 editor.highlight_background::<Self>(
6504 &ranges_to_highlight,
6505 |theme| theme.colors().editor_highlighted_line_background,
6506 cx,
6507 );
6508 });
6509 })?;
6510
6511 Ok(())
6512 }
6513
6514 pub fn clear_code_action_providers(&mut self) {
6515 self.code_action_providers.clear();
6516 self.available_code_actions.take();
6517 }
6518
6519 pub fn add_code_action_provider(
6520 &mut self,
6521 provider: Rc<dyn CodeActionProvider>,
6522 window: &mut Window,
6523 cx: &mut Context<Self>,
6524 ) {
6525 if self
6526 .code_action_providers
6527 .iter()
6528 .any(|existing_provider| existing_provider.id() == provider.id())
6529 {
6530 return;
6531 }
6532
6533 self.code_action_providers.push(provider);
6534 self.refresh_code_actions(window, cx);
6535 }
6536
6537 pub fn remove_code_action_provider(
6538 &mut self,
6539 id: Arc<str>,
6540 window: &mut Window,
6541 cx: &mut Context<Self>,
6542 ) {
6543 self.code_action_providers
6544 .retain(|provider| provider.id() != id);
6545 self.refresh_code_actions(window, cx);
6546 }
6547
6548 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6549 !self.code_action_providers.is_empty()
6550 && EditorSettings::get_global(cx).toolbar.code_actions
6551 }
6552
6553 pub fn has_available_code_actions(&self) -> bool {
6554 self.available_code_actions
6555 .as_ref()
6556 .is_some_and(|(_, actions)| !actions.is_empty())
6557 }
6558
6559 fn render_inline_code_actions(
6560 &self,
6561 icon_size: ui::IconSize,
6562 display_row: DisplayRow,
6563 is_active: bool,
6564 cx: &mut Context<Self>,
6565 ) -> AnyElement {
6566 let show_tooltip = !self.context_menu_visible();
6567 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6568 .icon_size(icon_size)
6569 .shape(ui::IconButtonShape::Square)
6570 .icon_color(ui::Color::Hidden)
6571 .toggle_state(is_active)
6572 .when(show_tooltip, |this| {
6573 this.tooltip({
6574 let focus_handle = self.focus_handle.clone();
6575 move |window, cx| {
6576 Tooltip::for_action_in(
6577 "Toggle Code Actions",
6578 &ToggleCodeActions {
6579 deployed_from: None,
6580 quick_launch: false,
6581 },
6582 &focus_handle,
6583 window,
6584 cx,
6585 )
6586 }
6587 })
6588 })
6589 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6590 window.focus(&editor.focus_handle(cx));
6591 editor.toggle_code_actions(
6592 &crate::actions::ToggleCodeActions {
6593 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6594 display_row,
6595 )),
6596 quick_launch: false,
6597 },
6598 window,
6599 cx,
6600 );
6601 }))
6602 .into_any_element()
6603 }
6604
6605 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6606 &self.context_menu
6607 }
6608
6609 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6610 let newest_selection = self.selections.newest_anchor().clone();
6611 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6612 let buffer = self.buffer.read(cx);
6613 if newest_selection.head().diff_base_anchor.is_some() {
6614 return None;
6615 }
6616 let (start_buffer, start) =
6617 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6618 let (end_buffer, end) =
6619 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6620 if start_buffer != end_buffer {
6621 return None;
6622 }
6623
6624 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6625 cx.background_executor()
6626 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6627 .await;
6628
6629 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6630 let providers = this.code_action_providers.clone();
6631 let tasks = this
6632 .code_action_providers
6633 .iter()
6634 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6635 .collect::<Vec<_>>();
6636 (providers, tasks)
6637 })?;
6638
6639 let mut actions = Vec::new();
6640 for (provider, provider_actions) in
6641 providers.into_iter().zip(future::join_all(tasks).await)
6642 {
6643 if let Some(provider_actions) = provider_actions.log_err() {
6644 actions.extend(provider_actions.into_iter().map(|action| {
6645 AvailableCodeAction {
6646 excerpt_id: newest_selection.start.excerpt_id,
6647 action,
6648 provider: provider.clone(),
6649 }
6650 }));
6651 }
6652 }
6653
6654 this.update(cx, |this, cx| {
6655 this.available_code_actions = if actions.is_empty() {
6656 None
6657 } else {
6658 Some((
6659 Location {
6660 buffer: start_buffer,
6661 range: start..end,
6662 },
6663 actions.into(),
6664 ))
6665 };
6666 cx.notify();
6667 })
6668 }));
6669 None
6670 }
6671
6672 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6673 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6674 self.show_git_blame_inline = false;
6675
6676 self.show_git_blame_inline_delay_task =
6677 Some(cx.spawn_in(window, async move |this, cx| {
6678 cx.background_executor().timer(delay).await;
6679
6680 this.update(cx, |this, cx| {
6681 this.show_git_blame_inline = true;
6682 cx.notify();
6683 })
6684 .log_err();
6685 }));
6686 }
6687 }
6688
6689 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6690 let snapshot = self.snapshot(window, cx);
6691 let cursor = self.selections.newest::<Point>(cx).head();
6692 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6693 else {
6694 return;
6695 };
6696
6697 let Some(blame) = self.blame.as_ref() else {
6698 return;
6699 };
6700
6701 let row_info = RowInfo {
6702 buffer_id: Some(buffer.remote_id()),
6703 buffer_row: Some(point.row),
6704 ..Default::default()
6705 };
6706 let Some((buffer, blame_entry)) = blame
6707 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6708 .flatten()
6709 else {
6710 return;
6711 };
6712
6713 let anchor = self.selections.newest_anchor().head();
6714 let position = self.to_pixel_point(anchor, &snapshot, window);
6715 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6716 self.show_blame_popover(
6717 buffer,
6718 &blame_entry,
6719 position + last_bounds.origin,
6720 true,
6721 cx,
6722 );
6723 };
6724 }
6725
6726 fn show_blame_popover(
6727 &mut self,
6728 buffer: BufferId,
6729 blame_entry: &BlameEntry,
6730 position: gpui::Point<Pixels>,
6731 ignore_timeout: bool,
6732 cx: &mut Context<Self>,
6733 ) {
6734 if let Some(state) = &mut self.inline_blame_popover {
6735 state.hide_task.take();
6736 } else {
6737 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6738 let blame_entry = blame_entry.clone();
6739 let show_task = cx.spawn(async move |editor, cx| {
6740 if !ignore_timeout {
6741 cx.background_executor()
6742 .timer(std::time::Duration::from_millis(blame_popover_delay))
6743 .await;
6744 }
6745 editor
6746 .update(cx, |editor, cx| {
6747 editor.inline_blame_popover_show_task.take();
6748 let Some(blame) = editor.blame.as_ref() else {
6749 return;
6750 };
6751 let blame = blame.read(cx);
6752 let details = blame.details_for_entry(buffer, &blame_entry);
6753 let markdown = cx.new(|cx| {
6754 Markdown::new(
6755 details
6756 .as_ref()
6757 .map(|message| message.message.clone())
6758 .unwrap_or_default(),
6759 None,
6760 None,
6761 cx,
6762 )
6763 });
6764 editor.inline_blame_popover = Some(InlineBlamePopover {
6765 position,
6766 hide_task: None,
6767 popover_bounds: None,
6768 popover_state: InlineBlamePopoverState {
6769 scroll_handle: ScrollHandle::new(),
6770 commit_message: details,
6771 markdown,
6772 },
6773 keyboard_grace: ignore_timeout,
6774 });
6775 cx.notify();
6776 })
6777 .ok();
6778 });
6779 self.inline_blame_popover_show_task = Some(show_task);
6780 }
6781 }
6782
6783 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6784 self.inline_blame_popover_show_task.take();
6785 if let Some(state) = &mut self.inline_blame_popover {
6786 let hide_task = cx.spawn(async move |editor, cx| {
6787 cx.background_executor()
6788 .timer(std::time::Duration::from_millis(100))
6789 .await;
6790 editor
6791 .update(cx, |editor, cx| {
6792 editor.inline_blame_popover.take();
6793 cx.notify();
6794 })
6795 .ok();
6796 });
6797 state.hide_task = Some(hide_task);
6798 }
6799 }
6800
6801 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6802 if self.pending_rename.is_some() {
6803 return None;
6804 }
6805
6806 let provider = self.semantics_provider.clone()?;
6807 let buffer = self.buffer.read(cx);
6808 let newest_selection = self.selections.newest_anchor().clone();
6809 let cursor_position = newest_selection.head();
6810 let (cursor_buffer, cursor_buffer_position) =
6811 buffer.text_anchor_for_position(cursor_position, cx)?;
6812 let (tail_buffer, tail_buffer_position) =
6813 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6814 if cursor_buffer != tail_buffer {
6815 return None;
6816 }
6817
6818 let snapshot = cursor_buffer.read(cx).snapshot();
6819 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6820 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6821 if start_word_range != end_word_range {
6822 self.document_highlights_task.take();
6823 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6824 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6825 return None;
6826 }
6827
6828 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6829 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6830 cx.background_executor()
6831 .timer(Duration::from_millis(debounce))
6832 .await;
6833
6834 let highlights = if let Some(highlights) = cx
6835 .update(|cx| {
6836 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6837 })
6838 .ok()
6839 .flatten()
6840 {
6841 highlights.await.log_err()
6842 } else {
6843 None
6844 };
6845
6846 if let Some(highlights) = highlights {
6847 this.update(cx, |this, cx| {
6848 if this.pending_rename.is_some() {
6849 return;
6850 }
6851
6852 let buffer = this.buffer.read(cx);
6853 if buffer
6854 .text_anchor_for_position(cursor_position, cx)
6855 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6856 {
6857 return;
6858 }
6859
6860 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6861 let mut write_ranges = Vec::new();
6862 let mut read_ranges = Vec::new();
6863 for highlight in highlights {
6864 let buffer_id = cursor_buffer.read(cx).remote_id();
6865 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6866 {
6867 let start = highlight
6868 .range
6869 .start
6870 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6871 let end = highlight
6872 .range
6873 .end
6874 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6875 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6876 continue;
6877 }
6878
6879 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6880 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6881 write_ranges.push(range);
6882 } else {
6883 read_ranges.push(range);
6884 }
6885 }
6886 }
6887
6888 this.highlight_background::<DocumentHighlightRead>(
6889 &read_ranges,
6890 |theme| theme.colors().editor_document_highlight_read_background,
6891 cx,
6892 );
6893 this.highlight_background::<DocumentHighlightWrite>(
6894 &write_ranges,
6895 |theme| theme.colors().editor_document_highlight_write_background,
6896 cx,
6897 );
6898 cx.notify();
6899 })
6900 .log_err();
6901 }
6902 }));
6903 None
6904 }
6905
6906 fn prepare_highlight_query_from_selection(
6907 &mut self,
6908 cx: &mut Context<Editor>,
6909 ) -> Option<(String, Range<Anchor>)> {
6910 if matches!(self.mode, EditorMode::SingleLine) {
6911 return None;
6912 }
6913 if !EditorSettings::get_global(cx).selection_highlight {
6914 return None;
6915 }
6916 if self.selections.count() != 1 || self.selections.line_mode() {
6917 return None;
6918 }
6919 let selection = self.selections.newest::<Point>(cx);
6920 if selection.is_empty() || selection.start.row != selection.end.row {
6921 return None;
6922 }
6923 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6924 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6925 let query = multi_buffer_snapshot
6926 .text_for_range(selection_anchor_range.clone())
6927 .collect::<String>();
6928 if query.trim().is_empty() {
6929 return None;
6930 }
6931 Some((query, selection_anchor_range))
6932 }
6933
6934 fn update_selection_occurrence_highlights(
6935 &mut self,
6936 query_text: String,
6937 query_range: Range<Anchor>,
6938 multi_buffer_range_to_query: Range<Point>,
6939 use_debounce: bool,
6940 window: &mut Window,
6941 cx: &mut Context<Editor>,
6942 ) -> Task<()> {
6943 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6944 cx.spawn_in(window, async move |editor, cx| {
6945 if use_debounce {
6946 cx.background_executor()
6947 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6948 .await;
6949 }
6950 let match_task = cx.background_spawn(async move {
6951 let buffer_ranges = multi_buffer_snapshot
6952 .range_to_buffer_ranges(multi_buffer_range_to_query)
6953 .into_iter()
6954 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6955 let mut match_ranges = Vec::new();
6956 let Ok(regex) = project::search::SearchQuery::text(
6957 query_text.clone(),
6958 false,
6959 false,
6960 false,
6961 Default::default(),
6962 Default::default(),
6963 false,
6964 None,
6965 ) else {
6966 return Vec::default();
6967 };
6968 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6969 match_ranges.extend(
6970 regex
6971 .search(buffer_snapshot, Some(search_range.clone()))
6972 .await
6973 .into_iter()
6974 .filter_map(|match_range| {
6975 let match_start = buffer_snapshot
6976 .anchor_after(search_range.start + match_range.start);
6977 let match_end = buffer_snapshot
6978 .anchor_before(search_range.start + match_range.end);
6979 let match_anchor_range = Anchor::range_in_buffer(
6980 excerpt_id,
6981 buffer_snapshot.remote_id(),
6982 match_start..match_end,
6983 );
6984 (match_anchor_range != query_range).then_some(match_anchor_range)
6985 }),
6986 );
6987 }
6988 match_ranges
6989 });
6990 let match_ranges = match_task.await;
6991 editor
6992 .update_in(cx, |editor, _, cx| {
6993 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6994 if !match_ranges.is_empty() {
6995 editor.highlight_background::<SelectedTextHighlight>(
6996 &match_ranges,
6997 |theme| theme.colors().editor_document_highlight_bracket_background,
6998 cx,
6999 )
7000 }
7001 })
7002 .log_err();
7003 })
7004 }
7005
7006 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7007 struct NewlineFold;
7008 let type_id = std::any::TypeId::of::<NewlineFold>();
7009 if !self.mode.is_single_line() {
7010 return;
7011 }
7012 let snapshot = self.snapshot(window, cx);
7013 if snapshot.buffer_snapshot().max_point().row == 0 {
7014 return;
7015 }
7016 let task = cx.background_spawn(async move {
7017 let new_newlines = snapshot
7018 .buffer_chars_at(0)
7019 .filter_map(|(c, i)| {
7020 if c == '\n' {
7021 Some(
7022 snapshot.buffer_snapshot().anchor_after(i)
7023 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7024 )
7025 } else {
7026 None
7027 }
7028 })
7029 .collect::<Vec<_>>();
7030 let existing_newlines = snapshot
7031 .folds_in_range(0..snapshot.buffer_snapshot().len())
7032 .filter_map(|fold| {
7033 if fold.placeholder.type_tag == Some(type_id) {
7034 Some(fold.range.start..fold.range.end)
7035 } else {
7036 None
7037 }
7038 })
7039 .collect::<Vec<_>>();
7040
7041 (new_newlines, existing_newlines)
7042 });
7043 self.folding_newlines = cx.spawn(async move |this, cx| {
7044 let (new_newlines, existing_newlines) = task.await;
7045 if new_newlines == existing_newlines {
7046 return;
7047 }
7048 let placeholder = FoldPlaceholder {
7049 render: Arc::new(move |_, _, cx| {
7050 div()
7051 .bg(cx.theme().status().hint_background)
7052 .border_b_1()
7053 .size_full()
7054 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7055 .border_color(cx.theme().status().hint)
7056 .child("\\n")
7057 .into_any()
7058 }),
7059 constrain_width: false,
7060 merge_adjacent: false,
7061 type_tag: Some(type_id),
7062 };
7063 let creases = new_newlines
7064 .into_iter()
7065 .map(|range| Crease::simple(range, placeholder.clone()))
7066 .collect();
7067 this.update(cx, |this, cx| {
7068 this.display_map.update(cx, |display_map, cx| {
7069 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7070 display_map.fold(creases, cx);
7071 });
7072 })
7073 .ok();
7074 });
7075 }
7076
7077 fn refresh_selected_text_highlights(
7078 &mut self,
7079 on_buffer_edit: bool,
7080 window: &mut Window,
7081 cx: &mut Context<Editor>,
7082 ) {
7083 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7084 else {
7085 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7086 self.quick_selection_highlight_task.take();
7087 self.debounced_selection_highlight_task.take();
7088 return;
7089 };
7090 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7091 if on_buffer_edit
7092 || self
7093 .quick_selection_highlight_task
7094 .as_ref()
7095 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7096 {
7097 let multi_buffer_visible_start = self
7098 .scroll_manager
7099 .anchor()
7100 .anchor
7101 .to_point(&multi_buffer_snapshot);
7102 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7103 multi_buffer_visible_start
7104 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7105 Bias::Left,
7106 );
7107 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7108 self.quick_selection_highlight_task = Some((
7109 query_range.clone(),
7110 self.update_selection_occurrence_highlights(
7111 query_text.clone(),
7112 query_range.clone(),
7113 multi_buffer_visible_range,
7114 false,
7115 window,
7116 cx,
7117 ),
7118 ));
7119 }
7120 if on_buffer_edit
7121 || self
7122 .debounced_selection_highlight_task
7123 .as_ref()
7124 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7125 {
7126 let multi_buffer_start = multi_buffer_snapshot
7127 .anchor_before(0)
7128 .to_point(&multi_buffer_snapshot);
7129 let multi_buffer_end = multi_buffer_snapshot
7130 .anchor_after(multi_buffer_snapshot.len())
7131 .to_point(&multi_buffer_snapshot);
7132 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7133 self.debounced_selection_highlight_task = Some((
7134 query_range.clone(),
7135 self.update_selection_occurrence_highlights(
7136 query_text,
7137 query_range,
7138 multi_buffer_full_range,
7139 true,
7140 window,
7141 cx,
7142 ),
7143 ));
7144 }
7145 }
7146
7147 pub fn refresh_edit_prediction(
7148 &mut self,
7149 debounce: bool,
7150 user_requested: bool,
7151 window: &mut Window,
7152 cx: &mut Context<Self>,
7153 ) -> Option<()> {
7154 if DisableAiSettings::get_global(cx).disable_ai {
7155 return None;
7156 }
7157
7158 let provider = self.edit_prediction_provider()?;
7159 let cursor = self.selections.newest_anchor().head();
7160 let (buffer, cursor_buffer_position) =
7161 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7162
7163 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7164 self.discard_edit_prediction(false, cx);
7165 return None;
7166 }
7167
7168 self.update_visible_edit_prediction(window, cx);
7169
7170 if !user_requested
7171 && (!self.should_show_edit_predictions()
7172 || !self.is_focused(window)
7173 || buffer.read(cx).is_empty())
7174 {
7175 self.discard_edit_prediction(false, cx);
7176 return None;
7177 }
7178
7179 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7180 Some(())
7181 }
7182
7183 fn show_edit_predictions_in_menu(&self) -> bool {
7184 match self.edit_prediction_settings {
7185 EditPredictionSettings::Disabled => false,
7186 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7187 }
7188 }
7189
7190 pub fn edit_predictions_enabled(&self) -> bool {
7191 match self.edit_prediction_settings {
7192 EditPredictionSettings::Disabled => false,
7193 EditPredictionSettings::Enabled { .. } => true,
7194 }
7195 }
7196
7197 fn edit_prediction_requires_modifier(&self) -> bool {
7198 match self.edit_prediction_settings {
7199 EditPredictionSettings::Disabled => false,
7200 EditPredictionSettings::Enabled {
7201 preview_requires_modifier,
7202 ..
7203 } => preview_requires_modifier,
7204 }
7205 }
7206
7207 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7208 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7209 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7210 self.discard_edit_prediction(false, cx);
7211 } else {
7212 let selection = self.selections.newest_anchor();
7213 let cursor = selection.head();
7214
7215 if let Some((buffer, cursor_buffer_position)) =
7216 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7217 {
7218 self.edit_prediction_settings =
7219 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7220 }
7221 }
7222 }
7223
7224 fn edit_prediction_settings_at_position(
7225 &self,
7226 buffer: &Entity<Buffer>,
7227 buffer_position: language::Anchor,
7228 cx: &App,
7229 ) -> EditPredictionSettings {
7230 if !self.mode.is_full()
7231 || !self.show_edit_predictions_override.unwrap_or(true)
7232 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7233 {
7234 return EditPredictionSettings::Disabled;
7235 }
7236
7237 let buffer = buffer.read(cx);
7238
7239 let file = buffer.file();
7240
7241 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7242 return EditPredictionSettings::Disabled;
7243 };
7244
7245 let by_provider = matches!(
7246 self.menu_edit_predictions_policy,
7247 MenuEditPredictionsPolicy::ByProvider
7248 );
7249
7250 let show_in_menu = by_provider
7251 && self
7252 .edit_prediction_provider
7253 .as_ref()
7254 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7255
7256 let preview_requires_modifier =
7257 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7258
7259 EditPredictionSettings::Enabled {
7260 show_in_menu,
7261 preview_requires_modifier,
7262 }
7263 }
7264
7265 fn should_show_edit_predictions(&self) -> bool {
7266 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7267 }
7268
7269 pub fn edit_prediction_preview_is_active(&self) -> bool {
7270 matches!(
7271 self.edit_prediction_preview,
7272 EditPredictionPreview::Active { .. }
7273 )
7274 }
7275
7276 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7277 let cursor = self.selections.newest_anchor().head();
7278 if let Some((buffer, cursor_position)) =
7279 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7280 {
7281 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7282 } else {
7283 false
7284 }
7285 }
7286
7287 pub fn supports_minimap(&self, cx: &App) -> bool {
7288 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7289 }
7290
7291 fn edit_predictions_enabled_in_buffer(
7292 &self,
7293 buffer: &Entity<Buffer>,
7294 buffer_position: language::Anchor,
7295 cx: &App,
7296 ) -> bool {
7297 maybe!({
7298 if self.read_only(cx) {
7299 return Some(false);
7300 }
7301 let provider = self.edit_prediction_provider()?;
7302 if !provider.is_enabled(buffer, buffer_position, cx) {
7303 return Some(false);
7304 }
7305 let buffer = buffer.read(cx);
7306 let Some(file) = buffer.file() else {
7307 return Some(true);
7308 };
7309 let settings = all_language_settings(Some(file), cx);
7310 Some(settings.edit_predictions_enabled_for_file(file, cx))
7311 })
7312 .unwrap_or(false)
7313 }
7314
7315 fn cycle_edit_prediction(
7316 &mut self,
7317 direction: Direction,
7318 window: &mut Window,
7319 cx: &mut Context<Self>,
7320 ) -> Option<()> {
7321 let provider = self.edit_prediction_provider()?;
7322 let cursor = self.selections.newest_anchor().head();
7323 let (buffer, cursor_buffer_position) =
7324 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7325 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7326 return None;
7327 }
7328
7329 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7330 self.update_visible_edit_prediction(window, cx);
7331
7332 Some(())
7333 }
7334
7335 pub fn show_edit_prediction(
7336 &mut self,
7337 _: &ShowEditPrediction,
7338 window: &mut Window,
7339 cx: &mut Context<Self>,
7340 ) {
7341 if !self.has_active_edit_prediction() {
7342 self.refresh_edit_prediction(false, true, window, cx);
7343 return;
7344 }
7345
7346 self.update_visible_edit_prediction(window, cx);
7347 }
7348
7349 pub fn display_cursor_names(
7350 &mut self,
7351 _: &DisplayCursorNames,
7352 window: &mut Window,
7353 cx: &mut Context<Self>,
7354 ) {
7355 self.show_cursor_names(window, cx);
7356 }
7357
7358 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7359 self.show_cursor_names = true;
7360 cx.notify();
7361 cx.spawn_in(window, async move |this, cx| {
7362 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7363 this.update(cx, |this, cx| {
7364 this.show_cursor_names = false;
7365 cx.notify()
7366 })
7367 .ok()
7368 })
7369 .detach();
7370 }
7371
7372 pub fn next_edit_prediction(
7373 &mut self,
7374 _: &NextEditPrediction,
7375 window: &mut Window,
7376 cx: &mut Context<Self>,
7377 ) {
7378 if self.has_active_edit_prediction() {
7379 self.cycle_edit_prediction(Direction::Next, window, cx);
7380 } else {
7381 let is_copilot_disabled = self
7382 .refresh_edit_prediction(false, true, window, cx)
7383 .is_none();
7384 if is_copilot_disabled {
7385 cx.propagate();
7386 }
7387 }
7388 }
7389
7390 pub fn previous_edit_prediction(
7391 &mut self,
7392 _: &PreviousEditPrediction,
7393 window: &mut Window,
7394 cx: &mut Context<Self>,
7395 ) {
7396 if self.has_active_edit_prediction() {
7397 self.cycle_edit_prediction(Direction::Prev, window, cx);
7398 } else {
7399 let is_copilot_disabled = self
7400 .refresh_edit_prediction(false, true, window, cx)
7401 .is_none();
7402 if is_copilot_disabled {
7403 cx.propagate();
7404 }
7405 }
7406 }
7407
7408 pub fn accept_edit_prediction(
7409 &mut self,
7410 _: &AcceptEditPrediction,
7411 window: &mut Window,
7412 cx: &mut Context<Self>,
7413 ) {
7414 if self.show_edit_predictions_in_menu() {
7415 self.hide_context_menu(window, cx);
7416 }
7417
7418 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7419 return;
7420 };
7421
7422 match &active_edit_prediction.completion {
7423 EditPrediction::MoveWithin { target, .. } => {
7424 let target = *target;
7425
7426 if let Some(position_map) = &self.last_position_map {
7427 if position_map
7428 .visible_row_range
7429 .contains(&target.to_display_point(&position_map.snapshot).row())
7430 || !self.edit_prediction_requires_modifier()
7431 {
7432 self.unfold_ranges(&[target..target], true, false, cx);
7433 // Note that this is also done in vim's handler of the Tab action.
7434 self.change_selections(
7435 SelectionEffects::scroll(Autoscroll::newest()),
7436 window,
7437 cx,
7438 |selections| {
7439 selections.select_anchor_ranges([target..target]);
7440 },
7441 );
7442 self.clear_row_highlights::<EditPredictionPreview>();
7443
7444 self.edit_prediction_preview
7445 .set_previous_scroll_position(None);
7446 } else {
7447 self.edit_prediction_preview
7448 .set_previous_scroll_position(Some(
7449 position_map.snapshot.scroll_anchor,
7450 ));
7451
7452 self.highlight_rows::<EditPredictionPreview>(
7453 target..target,
7454 cx.theme().colors().editor_highlighted_line_background,
7455 RowHighlightOptions {
7456 autoscroll: true,
7457 ..Default::default()
7458 },
7459 cx,
7460 );
7461 self.request_autoscroll(Autoscroll::fit(), cx);
7462 }
7463 }
7464 }
7465 EditPrediction::MoveOutside { snapshot, target } => {
7466 if let Some(workspace) = self.workspace() {
7467 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7468 .detach_and_log_err(cx);
7469 }
7470 }
7471 EditPrediction::Edit { edits, .. } => {
7472 self.report_edit_prediction_event(
7473 active_edit_prediction.completion_id.clone(),
7474 true,
7475 cx,
7476 );
7477
7478 if let Some(provider) = self.edit_prediction_provider() {
7479 provider.accept(cx);
7480 }
7481
7482 // Store the transaction ID and selections before applying the edit
7483 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7484
7485 let snapshot = self.buffer.read(cx).snapshot(cx);
7486 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7487
7488 self.buffer.update(cx, |buffer, cx| {
7489 buffer.edit(edits.iter().cloned(), None, cx)
7490 });
7491
7492 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7493 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7494 });
7495
7496 let selections = self.selections.disjoint_anchors_arc();
7497 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7498 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7499 if has_new_transaction {
7500 self.selection_history
7501 .insert_transaction(transaction_id_now, selections);
7502 }
7503 }
7504
7505 self.update_visible_edit_prediction(window, cx);
7506 if self.active_edit_prediction.is_none() {
7507 self.refresh_edit_prediction(true, true, window, cx);
7508 }
7509
7510 cx.notify();
7511 }
7512 }
7513
7514 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7515 }
7516
7517 pub fn accept_partial_edit_prediction(
7518 &mut self,
7519 _: &AcceptPartialEditPrediction,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) {
7523 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7524 return;
7525 };
7526 if self.selections.count() != 1 {
7527 return;
7528 }
7529
7530 match &active_edit_prediction.completion {
7531 EditPrediction::MoveWithin { target, .. } => {
7532 let target = *target;
7533 self.change_selections(
7534 SelectionEffects::scroll(Autoscroll::newest()),
7535 window,
7536 cx,
7537 |selections| {
7538 selections.select_anchor_ranges([target..target]);
7539 },
7540 );
7541 }
7542 EditPrediction::MoveOutside { snapshot, target } => {
7543 if let Some(workspace) = self.workspace() {
7544 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7545 .detach_and_log_err(cx);
7546 }
7547 }
7548 EditPrediction::Edit { edits, .. } => {
7549 self.report_edit_prediction_event(
7550 active_edit_prediction.completion_id.clone(),
7551 true,
7552 cx,
7553 );
7554
7555 // Find an insertion that starts at the cursor position.
7556 let snapshot = self.buffer.read(cx).snapshot(cx);
7557 let cursor_offset = self.selections.newest::<usize>(cx).head();
7558 let insertion = edits.iter().find_map(|(range, text)| {
7559 let range = range.to_offset(&snapshot);
7560 if range.is_empty() && range.start == cursor_offset {
7561 Some(text)
7562 } else {
7563 None
7564 }
7565 });
7566
7567 if let Some(text) = insertion {
7568 let mut partial_completion = text
7569 .chars()
7570 .by_ref()
7571 .take_while(|c| c.is_alphabetic())
7572 .collect::<String>();
7573 if partial_completion.is_empty() {
7574 partial_completion = text
7575 .chars()
7576 .by_ref()
7577 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7578 .collect::<String>();
7579 }
7580
7581 cx.emit(EditorEvent::InputHandled {
7582 utf16_range_to_replace: None,
7583 text: partial_completion.clone().into(),
7584 });
7585
7586 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7587
7588 self.refresh_edit_prediction(true, true, window, cx);
7589 cx.notify();
7590 } else {
7591 self.accept_edit_prediction(&Default::default(), window, cx);
7592 }
7593 }
7594 }
7595 }
7596
7597 fn discard_edit_prediction(
7598 &mut self,
7599 should_report_edit_prediction_event: bool,
7600 cx: &mut Context<Self>,
7601 ) -> bool {
7602 if should_report_edit_prediction_event {
7603 let completion_id = self
7604 .active_edit_prediction
7605 .as_ref()
7606 .and_then(|active_completion| active_completion.completion_id.clone());
7607
7608 self.report_edit_prediction_event(completion_id, false, cx);
7609 }
7610
7611 if let Some(provider) = self.edit_prediction_provider() {
7612 provider.discard(cx);
7613 }
7614
7615 self.take_active_edit_prediction(cx)
7616 }
7617
7618 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7619 let Some(provider) = self.edit_prediction_provider() else {
7620 return;
7621 };
7622
7623 let Some((_, buffer, _)) = self
7624 .buffer
7625 .read(cx)
7626 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7627 else {
7628 return;
7629 };
7630
7631 let extension = buffer
7632 .read(cx)
7633 .file()
7634 .and_then(|file| Some(file.path().extension()?.to_string()));
7635
7636 let event_type = match accepted {
7637 true => "Edit Prediction Accepted",
7638 false => "Edit Prediction Discarded",
7639 };
7640 telemetry::event!(
7641 event_type,
7642 provider = provider.name(),
7643 prediction_id = id,
7644 suggestion_accepted = accepted,
7645 file_extension = extension,
7646 );
7647 }
7648
7649 fn open_editor_at_anchor(
7650 snapshot: &language::BufferSnapshot,
7651 target: language::Anchor,
7652 workspace: &Entity<Workspace>,
7653 window: &mut Window,
7654 cx: &mut App,
7655 ) -> Task<Result<()>> {
7656 workspace.update(cx, |workspace, cx| {
7657 let path = snapshot.file().map(|file| file.full_path(cx));
7658 let Some(path) =
7659 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7660 else {
7661 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7662 };
7663 let target = text::ToPoint::to_point(&target, snapshot);
7664 let item = workspace.open_path(path, None, true, window, cx);
7665 window.spawn(cx, async move |cx| {
7666 let Some(editor) = item.await?.downcast::<Editor>() else {
7667 return Ok(());
7668 };
7669 editor
7670 .update_in(cx, |editor, window, cx| {
7671 editor.go_to_singleton_buffer_point(target, window, cx);
7672 })
7673 .ok();
7674 anyhow::Ok(())
7675 })
7676 })
7677 }
7678
7679 pub fn has_active_edit_prediction(&self) -> bool {
7680 self.active_edit_prediction.is_some()
7681 }
7682
7683 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7684 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7685 return false;
7686 };
7687
7688 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7689 self.clear_highlights::<EditPredictionHighlight>(cx);
7690 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7691 true
7692 }
7693
7694 /// Returns true when we're displaying the edit prediction popover below the cursor
7695 /// like we are not previewing and the LSP autocomplete menu is visible
7696 /// or we are in `when_holding_modifier` mode.
7697 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7698 if self.edit_prediction_preview_is_active()
7699 || !self.show_edit_predictions_in_menu()
7700 || !self.edit_predictions_enabled()
7701 {
7702 return false;
7703 }
7704
7705 if self.has_visible_completions_menu() {
7706 return true;
7707 }
7708
7709 has_completion && self.edit_prediction_requires_modifier()
7710 }
7711
7712 fn handle_modifiers_changed(
7713 &mut self,
7714 modifiers: Modifiers,
7715 position_map: &PositionMap,
7716 window: &mut Window,
7717 cx: &mut Context<Self>,
7718 ) {
7719 if self.show_edit_predictions_in_menu() {
7720 self.update_edit_prediction_preview(&modifiers, window, cx);
7721 }
7722
7723 self.update_selection_mode(&modifiers, position_map, window, cx);
7724
7725 let mouse_position = window.mouse_position();
7726 if !position_map.text_hitbox.is_hovered(window) {
7727 return;
7728 }
7729
7730 self.update_hovered_link(
7731 position_map.point_for_position(mouse_position),
7732 &position_map.snapshot,
7733 modifiers,
7734 window,
7735 cx,
7736 )
7737 }
7738
7739 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7740 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7741 if invert {
7742 match multi_cursor_setting {
7743 MultiCursorModifier::Alt => modifiers.alt,
7744 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7745 }
7746 } else {
7747 match multi_cursor_setting {
7748 MultiCursorModifier::Alt => modifiers.secondary(),
7749 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7750 }
7751 }
7752 }
7753
7754 fn columnar_selection_mode(
7755 modifiers: &Modifiers,
7756 cx: &mut Context<Self>,
7757 ) -> Option<ColumnarMode> {
7758 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7759 if Self::multi_cursor_modifier(false, modifiers, cx) {
7760 Some(ColumnarMode::FromMouse)
7761 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7762 Some(ColumnarMode::FromSelection)
7763 } else {
7764 None
7765 }
7766 } else {
7767 None
7768 }
7769 }
7770
7771 fn update_selection_mode(
7772 &mut self,
7773 modifiers: &Modifiers,
7774 position_map: &PositionMap,
7775 window: &mut Window,
7776 cx: &mut Context<Self>,
7777 ) {
7778 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7779 return;
7780 };
7781 if self.selections.pending_anchor().is_none() {
7782 return;
7783 }
7784
7785 let mouse_position = window.mouse_position();
7786 let point_for_position = position_map.point_for_position(mouse_position);
7787 let position = point_for_position.previous_valid;
7788
7789 self.select(
7790 SelectPhase::BeginColumnar {
7791 position,
7792 reset: false,
7793 mode,
7794 goal_column: point_for_position.exact_unclipped.column(),
7795 },
7796 window,
7797 cx,
7798 );
7799 }
7800
7801 fn update_edit_prediction_preview(
7802 &mut self,
7803 modifiers: &Modifiers,
7804 window: &mut Window,
7805 cx: &mut Context<Self>,
7806 ) {
7807 let mut modifiers_held = false;
7808 if let Some(accept_keystroke) = self
7809 .accept_edit_prediction_keybind(false, window, cx)
7810 .keystroke()
7811 {
7812 modifiers_held = modifiers_held
7813 || (accept_keystroke.modifiers() == modifiers
7814 && accept_keystroke.modifiers().modified());
7815 };
7816 if let Some(accept_partial_keystroke) = self
7817 .accept_edit_prediction_keybind(true, window, cx)
7818 .keystroke()
7819 {
7820 modifiers_held = modifiers_held
7821 || (accept_partial_keystroke.modifiers() == modifiers
7822 && accept_partial_keystroke.modifiers().modified());
7823 }
7824
7825 if modifiers_held {
7826 if matches!(
7827 self.edit_prediction_preview,
7828 EditPredictionPreview::Inactive { .. }
7829 ) {
7830 self.edit_prediction_preview = EditPredictionPreview::Active {
7831 previous_scroll_position: None,
7832 since: Instant::now(),
7833 };
7834
7835 self.update_visible_edit_prediction(window, cx);
7836 cx.notify();
7837 }
7838 } else if let EditPredictionPreview::Active {
7839 previous_scroll_position,
7840 since,
7841 } = self.edit_prediction_preview
7842 {
7843 if let (Some(previous_scroll_position), Some(position_map)) =
7844 (previous_scroll_position, self.last_position_map.as_ref())
7845 {
7846 self.set_scroll_position(
7847 previous_scroll_position
7848 .scroll_position(&position_map.snapshot.display_snapshot),
7849 window,
7850 cx,
7851 );
7852 }
7853
7854 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7855 released_too_fast: since.elapsed() < Duration::from_millis(200),
7856 };
7857 self.clear_row_highlights::<EditPredictionPreview>();
7858 self.update_visible_edit_prediction(window, cx);
7859 cx.notify();
7860 }
7861 }
7862
7863 fn update_visible_edit_prediction(
7864 &mut self,
7865 _window: &mut Window,
7866 cx: &mut Context<Self>,
7867 ) -> Option<()> {
7868 if DisableAiSettings::get_global(cx).disable_ai {
7869 return None;
7870 }
7871
7872 if self.ime_transaction.is_some() {
7873 self.discard_edit_prediction(false, cx);
7874 return None;
7875 }
7876
7877 let selection = self.selections.newest_anchor();
7878 let cursor = selection.head();
7879 let multibuffer = self.buffer.read(cx).snapshot(cx);
7880 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7881 let excerpt_id = cursor.excerpt_id;
7882
7883 let show_in_menu = self.show_edit_predictions_in_menu();
7884 let completions_menu_has_precedence = !show_in_menu
7885 && (self.context_menu.borrow().is_some()
7886 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7887
7888 if completions_menu_has_precedence
7889 || !offset_selection.is_empty()
7890 || self
7891 .active_edit_prediction
7892 .as_ref()
7893 .is_some_and(|completion| {
7894 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7895 return false;
7896 };
7897 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7898 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7899 !invalidation_range.contains(&offset_selection.head())
7900 })
7901 {
7902 self.discard_edit_prediction(false, cx);
7903 return None;
7904 }
7905
7906 self.take_active_edit_prediction(cx);
7907 let Some(provider) = self.edit_prediction_provider() else {
7908 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7909 return None;
7910 };
7911
7912 let (buffer, cursor_buffer_position) =
7913 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7914
7915 self.edit_prediction_settings =
7916 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7917
7918 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7919
7920 if self.edit_prediction_indent_conflict {
7921 let cursor_point = cursor.to_point(&multibuffer);
7922
7923 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7924
7925 if let Some((_, indent)) = indents.iter().next()
7926 && indent.len == cursor_point.column
7927 {
7928 self.edit_prediction_indent_conflict = false;
7929 }
7930 }
7931
7932 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7933
7934 let (completion_id, edits, edit_preview) = match edit_prediction {
7935 edit_prediction::EditPrediction::Local {
7936 id,
7937 edits,
7938 edit_preview,
7939 } => (id, edits, edit_preview),
7940 edit_prediction::EditPrediction::Jump {
7941 id,
7942 snapshot,
7943 target,
7944 } => {
7945 self.stale_edit_prediction_in_menu = None;
7946 self.active_edit_prediction = Some(EditPredictionState {
7947 inlay_ids: vec![],
7948 completion: EditPrediction::MoveOutside { snapshot, target },
7949 completion_id: id,
7950 invalidation_range: None,
7951 });
7952 cx.notify();
7953 return Some(());
7954 }
7955 };
7956
7957 let edits = edits
7958 .into_iter()
7959 .flat_map(|(range, new_text)| {
7960 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7961 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7962 Some((start..end, new_text))
7963 })
7964 .collect::<Vec<_>>();
7965 if edits.is_empty() {
7966 return None;
7967 }
7968
7969 let first_edit_start = edits.first().unwrap().0.start;
7970 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7971 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7972
7973 let last_edit_end = edits.last().unwrap().0.end;
7974 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7975 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7976
7977 let cursor_row = cursor.to_point(&multibuffer).row;
7978
7979 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7980
7981 let mut inlay_ids = Vec::new();
7982 let invalidation_row_range;
7983 let move_invalidation_row_range = if cursor_row < edit_start_row {
7984 Some(cursor_row..edit_end_row)
7985 } else if cursor_row > edit_end_row {
7986 Some(edit_start_row..cursor_row)
7987 } else {
7988 None
7989 };
7990 let supports_jump = self
7991 .edit_prediction_provider
7992 .as_ref()
7993 .map(|provider| provider.provider.supports_jump_to_edit())
7994 .unwrap_or(true);
7995
7996 let is_move = supports_jump
7997 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7998 let completion = if is_move {
7999 invalidation_row_range =
8000 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8001 let target = first_edit_start;
8002 EditPrediction::MoveWithin { target, snapshot }
8003 } else {
8004 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8005 && !self.edit_predictions_hidden_for_vim_mode;
8006
8007 if show_completions_in_buffer {
8008 if edits
8009 .iter()
8010 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8011 {
8012 let mut inlays = Vec::new();
8013 for (range, new_text) in &edits {
8014 let inlay = Inlay::edit_prediction(
8015 post_inc(&mut self.next_inlay_id),
8016 range.start,
8017 new_text.as_str(),
8018 );
8019 inlay_ids.push(inlay.id);
8020 inlays.push(inlay);
8021 }
8022
8023 self.splice_inlays(&[], inlays, cx);
8024 } else {
8025 let background_color = cx.theme().status().deleted_background;
8026 self.highlight_text::<EditPredictionHighlight>(
8027 edits.iter().map(|(range, _)| range.clone()).collect(),
8028 HighlightStyle {
8029 background_color: Some(background_color),
8030 ..Default::default()
8031 },
8032 cx,
8033 );
8034 }
8035 }
8036
8037 invalidation_row_range = edit_start_row..edit_end_row;
8038
8039 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8040 if provider.show_tab_accept_marker() {
8041 EditDisplayMode::TabAccept
8042 } else {
8043 EditDisplayMode::Inline
8044 }
8045 } else {
8046 EditDisplayMode::DiffPopover
8047 };
8048
8049 EditPrediction::Edit {
8050 edits,
8051 edit_preview,
8052 display_mode,
8053 snapshot,
8054 }
8055 };
8056
8057 let invalidation_range = multibuffer
8058 .anchor_before(Point::new(invalidation_row_range.start, 0))
8059 ..multibuffer.anchor_after(Point::new(
8060 invalidation_row_range.end,
8061 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8062 ));
8063
8064 self.stale_edit_prediction_in_menu = None;
8065 self.active_edit_prediction = Some(EditPredictionState {
8066 inlay_ids,
8067 completion,
8068 completion_id,
8069 invalidation_range: Some(invalidation_range),
8070 });
8071
8072 cx.notify();
8073
8074 Some(())
8075 }
8076
8077 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8078 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8079 }
8080
8081 fn clear_tasks(&mut self) {
8082 self.tasks.clear()
8083 }
8084
8085 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8086 if self.tasks.insert(key, value).is_some() {
8087 // This case should hopefully be rare, but just in case...
8088 log::error!(
8089 "multiple different run targets found on a single line, only the last target will be rendered"
8090 )
8091 }
8092 }
8093
8094 /// Get all display points of breakpoints that will be rendered within editor
8095 ///
8096 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8097 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8098 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8099 fn active_breakpoints(
8100 &self,
8101 range: Range<DisplayRow>,
8102 window: &mut Window,
8103 cx: &mut Context<Self>,
8104 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8105 let mut breakpoint_display_points = HashMap::default();
8106
8107 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8108 return breakpoint_display_points;
8109 };
8110
8111 let snapshot = self.snapshot(window, cx);
8112
8113 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8114 let Some(project) = self.project() else {
8115 return breakpoint_display_points;
8116 };
8117
8118 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8119 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8120
8121 for (buffer_snapshot, range, excerpt_id) in
8122 multi_buffer_snapshot.range_to_buffer_ranges(range)
8123 {
8124 let Some(buffer) = project
8125 .read(cx)
8126 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8127 else {
8128 continue;
8129 };
8130 let breakpoints = breakpoint_store.read(cx).breakpoints(
8131 &buffer,
8132 Some(
8133 buffer_snapshot.anchor_before(range.start)
8134 ..buffer_snapshot.anchor_after(range.end),
8135 ),
8136 buffer_snapshot,
8137 cx,
8138 );
8139 for (breakpoint, state) in breakpoints {
8140 let multi_buffer_anchor =
8141 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8142 let position = multi_buffer_anchor
8143 .to_point(multi_buffer_snapshot)
8144 .to_display_point(&snapshot);
8145
8146 breakpoint_display_points.insert(
8147 position.row(),
8148 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8149 );
8150 }
8151 }
8152
8153 breakpoint_display_points
8154 }
8155
8156 fn breakpoint_context_menu(
8157 &self,
8158 anchor: Anchor,
8159 window: &mut Window,
8160 cx: &mut Context<Self>,
8161 ) -> Entity<ui::ContextMenu> {
8162 let weak_editor = cx.weak_entity();
8163 let focus_handle = self.focus_handle(cx);
8164
8165 let row = self
8166 .buffer
8167 .read(cx)
8168 .snapshot(cx)
8169 .summary_for_anchor::<Point>(&anchor)
8170 .row;
8171
8172 let breakpoint = self
8173 .breakpoint_at_row(row, window, cx)
8174 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8175
8176 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8177 "Edit Log Breakpoint"
8178 } else {
8179 "Set Log Breakpoint"
8180 };
8181
8182 let condition_breakpoint_msg = if breakpoint
8183 .as_ref()
8184 .is_some_and(|bp| bp.1.condition.is_some())
8185 {
8186 "Edit Condition Breakpoint"
8187 } else {
8188 "Set Condition Breakpoint"
8189 };
8190
8191 let hit_condition_breakpoint_msg = if breakpoint
8192 .as_ref()
8193 .is_some_and(|bp| bp.1.hit_condition.is_some())
8194 {
8195 "Edit Hit Condition Breakpoint"
8196 } else {
8197 "Set Hit Condition Breakpoint"
8198 };
8199
8200 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8201 "Unset Breakpoint"
8202 } else {
8203 "Set Breakpoint"
8204 };
8205
8206 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8207
8208 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8209 BreakpointState::Enabled => Some("Disable"),
8210 BreakpointState::Disabled => Some("Enable"),
8211 });
8212
8213 let (anchor, breakpoint) =
8214 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8215
8216 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8217 menu.on_blur_subscription(Subscription::new(|| {}))
8218 .context(focus_handle)
8219 .when(run_to_cursor, |this| {
8220 let weak_editor = weak_editor.clone();
8221 this.entry("Run to cursor", None, move |window, cx| {
8222 weak_editor
8223 .update(cx, |editor, cx| {
8224 editor.change_selections(
8225 SelectionEffects::no_scroll(),
8226 window,
8227 cx,
8228 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8229 );
8230 })
8231 .ok();
8232
8233 window.dispatch_action(Box::new(RunToCursor), cx);
8234 })
8235 .separator()
8236 })
8237 .when_some(toggle_state_msg, |this, msg| {
8238 this.entry(msg, None, {
8239 let weak_editor = weak_editor.clone();
8240 let breakpoint = breakpoint.clone();
8241 move |_window, cx| {
8242 weak_editor
8243 .update(cx, |this, cx| {
8244 this.edit_breakpoint_at_anchor(
8245 anchor,
8246 breakpoint.as_ref().clone(),
8247 BreakpointEditAction::InvertState,
8248 cx,
8249 );
8250 })
8251 .log_err();
8252 }
8253 })
8254 })
8255 .entry(set_breakpoint_msg, None, {
8256 let weak_editor = weak_editor.clone();
8257 let breakpoint = breakpoint.clone();
8258 move |_window, cx| {
8259 weak_editor
8260 .update(cx, |this, cx| {
8261 this.edit_breakpoint_at_anchor(
8262 anchor,
8263 breakpoint.as_ref().clone(),
8264 BreakpointEditAction::Toggle,
8265 cx,
8266 );
8267 })
8268 .log_err();
8269 }
8270 })
8271 .entry(log_breakpoint_msg, None, {
8272 let breakpoint = breakpoint.clone();
8273 let weak_editor = weak_editor.clone();
8274 move |window, cx| {
8275 weak_editor
8276 .update(cx, |this, cx| {
8277 this.add_edit_breakpoint_block(
8278 anchor,
8279 breakpoint.as_ref(),
8280 BreakpointPromptEditAction::Log,
8281 window,
8282 cx,
8283 );
8284 })
8285 .log_err();
8286 }
8287 })
8288 .entry(condition_breakpoint_msg, None, {
8289 let breakpoint = breakpoint.clone();
8290 let weak_editor = weak_editor.clone();
8291 move |window, cx| {
8292 weak_editor
8293 .update(cx, |this, cx| {
8294 this.add_edit_breakpoint_block(
8295 anchor,
8296 breakpoint.as_ref(),
8297 BreakpointPromptEditAction::Condition,
8298 window,
8299 cx,
8300 );
8301 })
8302 .log_err();
8303 }
8304 })
8305 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8306 weak_editor
8307 .update(cx, |this, cx| {
8308 this.add_edit_breakpoint_block(
8309 anchor,
8310 breakpoint.as_ref(),
8311 BreakpointPromptEditAction::HitCondition,
8312 window,
8313 cx,
8314 );
8315 })
8316 .log_err();
8317 })
8318 })
8319 }
8320
8321 fn render_breakpoint(
8322 &self,
8323 position: Anchor,
8324 row: DisplayRow,
8325 breakpoint: &Breakpoint,
8326 state: Option<BreakpointSessionState>,
8327 cx: &mut Context<Self>,
8328 ) -> IconButton {
8329 let is_rejected = state.is_some_and(|s| !s.verified);
8330 // Is it a breakpoint that shows up when hovering over gutter?
8331 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8332 (false, false),
8333 |PhantomBreakpointIndicator {
8334 is_active,
8335 display_row,
8336 collides_with_existing_breakpoint,
8337 }| {
8338 (
8339 is_active && display_row == row,
8340 collides_with_existing_breakpoint,
8341 )
8342 },
8343 );
8344
8345 let (color, icon) = {
8346 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8347 (false, false) => ui::IconName::DebugBreakpoint,
8348 (true, false) => ui::IconName::DebugLogBreakpoint,
8349 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8350 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8351 };
8352
8353 let color = if is_phantom {
8354 Color::Hint
8355 } else if is_rejected {
8356 Color::Disabled
8357 } else {
8358 Color::Debugger
8359 };
8360
8361 (color, icon)
8362 };
8363
8364 let breakpoint = Arc::from(breakpoint.clone());
8365
8366 let alt_as_text = gpui::Keystroke {
8367 modifiers: Modifiers::secondary_key(),
8368 ..Default::default()
8369 };
8370 let primary_action_text = if breakpoint.is_disabled() {
8371 "Enable breakpoint"
8372 } else if is_phantom && !collides_with_existing {
8373 "Set breakpoint"
8374 } else {
8375 "Unset breakpoint"
8376 };
8377 let focus_handle = self.focus_handle.clone();
8378
8379 let meta = if is_rejected {
8380 SharedString::from("No executable code is associated with this line.")
8381 } else if collides_with_existing && !breakpoint.is_disabled() {
8382 SharedString::from(format!(
8383 "{alt_as_text}-click to disable,\nright-click for more options."
8384 ))
8385 } else {
8386 SharedString::from("Right-click for more options.")
8387 };
8388 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8389 .icon_size(IconSize::XSmall)
8390 .size(ui::ButtonSize::None)
8391 .when(is_rejected, |this| {
8392 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8393 })
8394 .icon_color(color)
8395 .style(ButtonStyle::Transparent)
8396 .on_click(cx.listener({
8397 move |editor, event: &ClickEvent, window, cx| {
8398 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8399 BreakpointEditAction::InvertState
8400 } else {
8401 BreakpointEditAction::Toggle
8402 };
8403
8404 window.focus(&editor.focus_handle(cx));
8405 editor.edit_breakpoint_at_anchor(
8406 position,
8407 breakpoint.as_ref().clone(),
8408 edit_action,
8409 cx,
8410 );
8411 }
8412 }))
8413 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8414 editor.set_breakpoint_context_menu(
8415 row,
8416 Some(position),
8417 event.position(),
8418 window,
8419 cx,
8420 );
8421 }))
8422 .tooltip(move |window, cx| {
8423 Tooltip::with_meta_in(
8424 primary_action_text,
8425 Some(&ToggleBreakpoint),
8426 meta.clone(),
8427 &focus_handle,
8428 window,
8429 cx,
8430 )
8431 })
8432 }
8433
8434 fn build_tasks_context(
8435 project: &Entity<Project>,
8436 buffer: &Entity<Buffer>,
8437 buffer_row: u32,
8438 tasks: &Arc<RunnableTasks>,
8439 cx: &mut Context<Self>,
8440 ) -> Task<Option<task::TaskContext>> {
8441 let position = Point::new(buffer_row, tasks.column);
8442 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8443 let location = Location {
8444 buffer: buffer.clone(),
8445 range: range_start..range_start,
8446 };
8447 // Fill in the environmental variables from the tree-sitter captures
8448 let mut captured_task_variables = TaskVariables::default();
8449 for (capture_name, value) in tasks.extra_variables.clone() {
8450 captured_task_variables.insert(
8451 task::VariableName::Custom(capture_name.into()),
8452 value.clone(),
8453 );
8454 }
8455 project.update(cx, |project, cx| {
8456 project.task_store().update(cx, |task_store, cx| {
8457 task_store.task_context_for_location(captured_task_variables, location, cx)
8458 })
8459 })
8460 }
8461
8462 pub fn spawn_nearest_task(
8463 &mut self,
8464 action: &SpawnNearestTask,
8465 window: &mut Window,
8466 cx: &mut Context<Self>,
8467 ) {
8468 let Some((workspace, _)) = self.workspace.clone() else {
8469 return;
8470 };
8471 let Some(project) = self.project.clone() else {
8472 return;
8473 };
8474
8475 // Try to find a closest, enclosing node using tree-sitter that has a task
8476 let Some((buffer, buffer_row, tasks)) = self
8477 .find_enclosing_node_task(cx)
8478 // Or find the task that's closest in row-distance.
8479 .or_else(|| self.find_closest_task(cx))
8480 else {
8481 return;
8482 };
8483
8484 let reveal_strategy = action.reveal;
8485 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8486 cx.spawn_in(window, async move |_, cx| {
8487 let context = task_context.await?;
8488 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8489
8490 let resolved = &mut resolved_task.resolved;
8491 resolved.reveal = reveal_strategy;
8492
8493 workspace
8494 .update_in(cx, |workspace, window, cx| {
8495 workspace.schedule_resolved_task(
8496 task_source_kind,
8497 resolved_task,
8498 false,
8499 window,
8500 cx,
8501 );
8502 })
8503 .ok()
8504 })
8505 .detach();
8506 }
8507
8508 fn find_closest_task(
8509 &mut self,
8510 cx: &mut Context<Self>,
8511 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8512 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8513
8514 let ((buffer_id, row), tasks) = self
8515 .tasks
8516 .iter()
8517 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8518
8519 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8520 let tasks = Arc::new(tasks.to_owned());
8521 Some((buffer, *row, tasks))
8522 }
8523
8524 fn find_enclosing_node_task(
8525 &mut self,
8526 cx: &mut Context<Self>,
8527 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8528 let snapshot = self.buffer.read(cx).snapshot(cx);
8529 let offset = self.selections.newest::<usize>(cx).head();
8530 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8531 let buffer_id = excerpt.buffer().remote_id();
8532
8533 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8534 let mut cursor = layer.node().walk();
8535
8536 while cursor.goto_first_child_for_byte(offset).is_some() {
8537 if cursor.node().end_byte() == offset {
8538 cursor.goto_next_sibling();
8539 }
8540 }
8541
8542 // Ascend to the smallest ancestor that contains the range and has a task.
8543 loop {
8544 let node = cursor.node();
8545 let node_range = node.byte_range();
8546 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8547
8548 // Check if this node contains our offset
8549 if node_range.start <= offset && node_range.end >= offset {
8550 // If it contains offset, check for task
8551 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8552 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8553 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8554 }
8555 }
8556
8557 if !cursor.goto_parent() {
8558 break;
8559 }
8560 }
8561 None
8562 }
8563
8564 fn render_run_indicator(
8565 &self,
8566 _style: &EditorStyle,
8567 is_active: bool,
8568 row: DisplayRow,
8569 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8570 cx: &mut Context<Self>,
8571 ) -> IconButton {
8572 let color = Color::Muted;
8573 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8574
8575 IconButton::new(
8576 ("run_indicator", row.0 as usize),
8577 ui::IconName::PlayOutlined,
8578 )
8579 .shape(ui::IconButtonShape::Square)
8580 .icon_size(IconSize::XSmall)
8581 .icon_color(color)
8582 .toggle_state(is_active)
8583 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8584 let quick_launch = match e {
8585 ClickEvent::Keyboard(_) => true,
8586 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8587 };
8588
8589 window.focus(&editor.focus_handle(cx));
8590 editor.toggle_code_actions(
8591 &ToggleCodeActions {
8592 deployed_from: Some(CodeActionSource::RunMenu(row)),
8593 quick_launch,
8594 },
8595 window,
8596 cx,
8597 );
8598 }))
8599 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8600 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8601 }))
8602 }
8603
8604 pub fn context_menu_visible(&self) -> bool {
8605 !self.edit_prediction_preview_is_active()
8606 && self
8607 .context_menu
8608 .borrow()
8609 .as_ref()
8610 .is_some_and(|menu| menu.visible())
8611 }
8612
8613 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8614 self.context_menu
8615 .borrow()
8616 .as_ref()
8617 .map(|menu| menu.origin())
8618 }
8619
8620 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8621 self.context_menu_options = Some(options);
8622 }
8623
8624 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8625 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8626
8627 fn render_edit_prediction_popover(
8628 &mut self,
8629 text_bounds: &Bounds<Pixels>,
8630 content_origin: gpui::Point<Pixels>,
8631 right_margin: Pixels,
8632 editor_snapshot: &EditorSnapshot,
8633 visible_row_range: Range<DisplayRow>,
8634 scroll_top: ScrollOffset,
8635 scroll_bottom: ScrollOffset,
8636 line_layouts: &[LineWithInvisibles],
8637 line_height: Pixels,
8638 scroll_position: gpui::Point<ScrollOffset>,
8639 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8640 newest_selection_head: Option<DisplayPoint>,
8641 editor_width: Pixels,
8642 style: &EditorStyle,
8643 window: &mut Window,
8644 cx: &mut App,
8645 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8646 if self.mode().is_minimap() {
8647 return None;
8648 }
8649 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8650
8651 if self.edit_prediction_visible_in_cursor_popover(true) {
8652 return None;
8653 }
8654
8655 match &active_edit_prediction.completion {
8656 EditPrediction::MoveWithin { target, .. } => {
8657 let target_display_point = target.to_display_point(editor_snapshot);
8658
8659 if self.edit_prediction_requires_modifier() {
8660 if !self.edit_prediction_preview_is_active() {
8661 return None;
8662 }
8663
8664 self.render_edit_prediction_modifier_jump_popover(
8665 text_bounds,
8666 content_origin,
8667 visible_row_range,
8668 line_layouts,
8669 line_height,
8670 scroll_pixel_position,
8671 newest_selection_head,
8672 target_display_point,
8673 window,
8674 cx,
8675 )
8676 } else {
8677 self.render_edit_prediction_eager_jump_popover(
8678 text_bounds,
8679 content_origin,
8680 editor_snapshot,
8681 visible_row_range,
8682 scroll_top,
8683 scroll_bottom,
8684 line_height,
8685 scroll_pixel_position,
8686 target_display_point,
8687 editor_width,
8688 window,
8689 cx,
8690 )
8691 }
8692 }
8693 EditPrediction::Edit {
8694 display_mode: EditDisplayMode::Inline,
8695 ..
8696 } => None,
8697 EditPrediction::Edit {
8698 display_mode: EditDisplayMode::TabAccept,
8699 edits,
8700 ..
8701 } => {
8702 let range = &edits.first()?.0;
8703 let target_display_point = range.end.to_display_point(editor_snapshot);
8704
8705 self.render_edit_prediction_end_of_line_popover(
8706 "Accept",
8707 editor_snapshot,
8708 visible_row_range,
8709 target_display_point,
8710 line_height,
8711 scroll_pixel_position,
8712 content_origin,
8713 editor_width,
8714 window,
8715 cx,
8716 )
8717 }
8718 EditPrediction::Edit {
8719 edits,
8720 edit_preview,
8721 display_mode: EditDisplayMode::DiffPopover,
8722 snapshot,
8723 } => self.render_edit_prediction_diff_popover(
8724 text_bounds,
8725 content_origin,
8726 right_margin,
8727 editor_snapshot,
8728 visible_row_range,
8729 line_layouts,
8730 line_height,
8731 scroll_position,
8732 scroll_pixel_position,
8733 newest_selection_head,
8734 editor_width,
8735 style,
8736 edits,
8737 edit_preview,
8738 snapshot,
8739 window,
8740 cx,
8741 ),
8742 EditPrediction::MoveOutside { snapshot, .. } => {
8743 let file_name = snapshot
8744 .file()
8745 .map(|file| file.file_name(cx))
8746 .unwrap_or("untitled");
8747 let mut element = self
8748 .render_edit_prediction_line_popover(
8749 format!("Jump to {file_name}"),
8750 Some(IconName::ZedPredict),
8751 window,
8752 cx,
8753 )
8754 .into_any();
8755
8756 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8757 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8758 let origin_y = text_bounds.size.height - size.height - px(30.);
8759 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8760 element.prepaint_at(origin, window, cx);
8761
8762 Some((element, origin))
8763 }
8764 }
8765 }
8766
8767 fn render_edit_prediction_modifier_jump_popover(
8768 &mut self,
8769 text_bounds: &Bounds<Pixels>,
8770 content_origin: gpui::Point<Pixels>,
8771 visible_row_range: Range<DisplayRow>,
8772 line_layouts: &[LineWithInvisibles],
8773 line_height: Pixels,
8774 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8775 newest_selection_head: Option<DisplayPoint>,
8776 target_display_point: DisplayPoint,
8777 window: &mut Window,
8778 cx: &mut App,
8779 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8780 let scrolled_content_origin =
8781 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8782
8783 const SCROLL_PADDING_Y: Pixels = px(12.);
8784
8785 if target_display_point.row() < visible_row_range.start {
8786 return self.render_edit_prediction_scroll_popover(
8787 |_| SCROLL_PADDING_Y,
8788 IconName::ArrowUp,
8789 visible_row_range,
8790 line_layouts,
8791 newest_selection_head,
8792 scrolled_content_origin,
8793 window,
8794 cx,
8795 );
8796 } else if target_display_point.row() >= visible_row_range.end {
8797 return self.render_edit_prediction_scroll_popover(
8798 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8799 IconName::ArrowDown,
8800 visible_row_range,
8801 line_layouts,
8802 newest_selection_head,
8803 scrolled_content_origin,
8804 window,
8805 cx,
8806 );
8807 }
8808
8809 const POLE_WIDTH: Pixels = px(2.);
8810
8811 let line_layout =
8812 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8813 let target_column = target_display_point.column() as usize;
8814
8815 let target_x = line_layout.x_for_index(target_column);
8816 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8817 - scroll_pixel_position.y;
8818
8819 let flag_on_right = target_x < text_bounds.size.width / 2.;
8820
8821 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8822 border_color.l += 0.001;
8823
8824 let mut element = v_flex()
8825 .items_end()
8826 .when(flag_on_right, |el| el.items_start())
8827 .child(if flag_on_right {
8828 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8829 .rounded_bl(px(0.))
8830 .rounded_tl(px(0.))
8831 .border_l_2()
8832 .border_color(border_color)
8833 } else {
8834 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8835 .rounded_br(px(0.))
8836 .rounded_tr(px(0.))
8837 .border_r_2()
8838 .border_color(border_color)
8839 })
8840 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8841 .into_any();
8842
8843 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8844
8845 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8846 - point(
8847 if flag_on_right {
8848 POLE_WIDTH
8849 } else {
8850 size.width - POLE_WIDTH
8851 },
8852 size.height - line_height,
8853 );
8854
8855 origin.x = origin.x.max(content_origin.x);
8856
8857 element.prepaint_at(origin, window, cx);
8858
8859 Some((element, origin))
8860 }
8861
8862 fn render_edit_prediction_scroll_popover(
8863 &mut self,
8864 to_y: impl Fn(Size<Pixels>) -> Pixels,
8865 scroll_icon: IconName,
8866 visible_row_range: Range<DisplayRow>,
8867 line_layouts: &[LineWithInvisibles],
8868 newest_selection_head: Option<DisplayPoint>,
8869 scrolled_content_origin: gpui::Point<Pixels>,
8870 window: &mut Window,
8871 cx: &mut App,
8872 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8873 let mut element = self
8874 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8875 .into_any();
8876
8877 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8878
8879 let cursor = newest_selection_head?;
8880 let cursor_row_layout =
8881 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8882 let cursor_column = cursor.column() as usize;
8883
8884 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8885
8886 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8887
8888 element.prepaint_at(origin, window, cx);
8889 Some((element, origin))
8890 }
8891
8892 fn render_edit_prediction_eager_jump_popover(
8893 &mut self,
8894 text_bounds: &Bounds<Pixels>,
8895 content_origin: gpui::Point<Pixels>,
8896 editor_snapshot: &EditorSnapshot,
8897 visible_row_range: Range<DisplayRow>,
8898 scroll_top: ScrollOffset,
8899 scroll_bottom: ScrollOffset,
8900 line_height: Pixels,
8901 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8902 target_display_point: DisplayPoint,
8903 editor_width: Pixels,
8904 window: &mut Window,
8905 cx: &mut App,
8906 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8907 if target_display_point.row().as_f64() < scroll_top {
8908 let mut element = self
8909 .render_edit_prediction_line_popover(
8910 "Jump to Edit",
8911 Some(IconName::ArrowUp),
8912 window,
8913 cx,
8914 )
8915 .into_any();
8916
8917 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8918 let offset = point(
8919 (text_bounds.size.width - size.width) / 2.,
8920 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8921 );
8922
8923 let origin = text_bounds.origin + offset;
8924 element.prepaint_at(origin, window, cx);
8925 Some((element, origin))
8926 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8927 let mut element = self
8928 .render_edit_prediction_line_popover(
8929 "Jump to Edit",
8930 Some(IconName::ArrowDown),
8931 window,
8932 cx,
8933 )
8934 .into_any();
8935
8936 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8937 let offset = point(
8938 (text_bounds.size.width - size.width) / 2.,
8939 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8940 );
8941
8942 let origin = text_bounds.origin + offset;
8943 element.prepaint_at(origin, window, cx);
8944 Some((element, origin))
8945 } else {
8946 self.render_edit_prediction_end_of_line_popover(
8947 "Jump to Edit",
8948 editor_snapshot,
8949 visible_row_range,
8950 target_display_point,
8951 line_height,
8952 scroll_pixel_position,
8953 content_origin,
8954 editor_width,
8955 window,
8956 cx,
8957 )
8958 }
8959 }
8960
8961 fn render_edit_prediction_end_of_line_popover(
8962 self: &mut Editor,
8963 label: &'static str,
8964 editor_snapshot: &EditorSnapshot,
8965 visible_row_range: Range<DisplayRow>,
8966 target_display_point: DisplayPoint,
8967 line_height: Pixels,
8968 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8969 content_origin: gpui::Point<Pixels>,
8970 editor_width: Pixels,
8971 window: &mut Window,
8972 cx: &mut App,
8973 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8974 let target_line_end = DisplayPoint::new(
8975 target_display_point.row(),
8976 editor_snapshot.line_len(target_display_point.row()),
8977 );
8978
8979 let mut element = self
8980 .render_edit_prediction_line_popover(label, None, window, cx)
8981 .into_any();
8982
8983 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8984
8985 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8986
8987 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8988 let mut origin = start_point
8989 + line_origin
8990 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8991 origin.x = origin.x.max(content_origin.x);
8992
8993 let max_x = content_origin.x + editor_width - size.width;
8994
8995 if origin.x > max_x {
8996 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8997
8998 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8999 origin.y += offset;
9000 IconName::ArrowUp
9001 } else {
9002 origin.y -= offset;
9003 IconName::ArrowDown
9004 };
9005
9006 element = self
9007 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9008 .into_any();
9009
9010 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9011
9012 origin.x = content_origin.x + editor_width - size.width - px(2.);
9013 }
9014
9015 element.prepaint_at(origin, window, cx);
9016 Some((element, origin))
9017 }
9018
9019 fn render_edit_prediction_diff_popover(
9020 self: &Editor,
9021 text_bounds: &Bounds<Pixels>,
9022 content_origin: gpui::Point<Pixels>,
9023 right_margin: Pixels,
9024 editor_snapshot: &EditorSnapshot,
9025 visible_row_range: Range<DisplayRow>,
9026 line_layouts: &[LineWithInvisibles],
9027 line_height: Pixels,
9028 scroll_position: gpui::Point<ScrollOffset>,
9029 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9030 newest_selection_head: Option<DisplayPoint>,
9031 editor_width: Pixels,
9032 style: &EditorStyle,
9033 edits: &Vec<(Range<Anchor>, String)>,
9034 edit_preview: &Option<language::EditPreview>,
9035 snapshot: &language::BufferSnapshot,
9036 window: &mut Window,
9037 cx: &mut App,
9038 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9039 let edit_start = edits
9040 .first()
9041 .unwrap()
9042 .0
9043 .start
9044 .to_display_point(editor_snapshot);
9045 let edit_end = edits
9046 .last()
9047 .unwrap()
9048 .0
9049 .end
9050 .to_display_point(editor_snapshot);
9051
9052 let is_visible = visible_row_range.contains(&edit_start.row())
9053 || visible_row_range.contains(&edit_end.row());
9054 if !is_visible {
9055 return None;
9056 }
9057
9058 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9059 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9060 } else {
9061 // Fallback for providers without edit_preview
9062 crate::edit_prediction_fallback_text(edits, cx)
9063 };
9064
9065 let styled_text = highlighted_edits.to_styled_text(&style.text);
9066 let line_count = highlighted_edits.text.lines().count();
9067
9068 const BORDER_WIDTH: Pixels = px(1.);
9069
9070 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9071 let has_keybind = keybind.is_some();
9072
9073 let mut element = h_flex()
9074 .items_start()
9075 .child(
9076 h_flex()
9077 .bg(cx.theme().colors().editor_background)
9078 .border(BORDER_WIDTH)
9079 .shadow_xs()
9080 .border_color(cx.theme().colors().border)
9081 .rounded_l_lg()
9082 .when(line_count > 1, |el| el.rounded_br_lg())
9083 .pr_1()
9084 .child(styled_text),
9085 )
9086 .child(
9087 h_flex()
9088 .h(line_height + BORDER_WIDTH * 2.)
9089 .px_1p5()
9090 .gap_1()
9091 // Workaround: For some reason, there's a gap if we don't do this
9092 .ml(-BORDER_WIDTH)
9093 .shadow(vec![gpui::BoxShadow {
9094 color: gpui::black().opacity(0.05),
9095 offset: point(px(1.), px(1.)),
9096 blur_radius: px(2.),
9097 spread_radius: px(0.),
9098 }])
9099 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9100 .border(BORDER_WIDTH)
9101 .border_color(cx.theme().colors().border)
9102 .rounded_r_lg()
9103 .id("edit_prediction_diff_popover_keybind")
9104 .when(!has_keybind, |el| {
9105 let status_colors = cx.theme().status();
9106
9107 el.bg(status_colors.error_background)
9108 .border_color(status_colors.error.opacity(0.6))
9109 .child(Icon::new(IconName::Info).color(Color::Error))
9110 .cursor_default()
9111 .hoverable_tooltip(move |_window, cx| {
9112 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9113 })
9114 })
9115 .children(keybind),
9116 )
9117 .into_any();
9118
9119 let longest_row =
9120 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9121 let longest_line_width = if visible_row_range.contains(&longest_row) {
9122 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9123 } else {
9124 layout_line(
9125 longest_row,
9126 editor_snapshot,
9127 style,
9128 editor_width,
9129 |_| false,
9130 window,
9131 cx,
9132 )
9133 .width
9134 };
9135
9136 let viewport_bounds =
9137 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9138 right: -right_margin,
9139 ..Default::default()
9140 });
9141
9142 let x_after_longest = Pixels::from(
9143 ScrollPixelOffset::from(
9144 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9145 ) - scroll_pixel_position.x,
9146 );
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
9159 + Pixels::from(
9160 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9161 - scroll_pixel_position.y,
9162 ),
9163 )
9164 } else {
9165 let cursor_row = newest_selection_head.map(|head| head.row());
9166 let above_edit = edit_start
9167 .row()
9168 .0
9169 .checked_sub(line_count as u32)
9170 .map(DisplayRow);
9171 let below_edit = Some(edit_end.row() + 1);
9172 let above_cursor =
9173 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9174 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9175
9176 // Place the edit popover adjacent to the edit if there is a location
9177 // available that is onscreen and does not obscure the cursor. Otherwise,
9178 // place it adjacent to the cursor.
9179 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9180 .into_iter()
9181 .flatten()
9182 .find(|&start_row| {
9183 let end_row = start_row + line_count as u32;
9184 visible_row_range.contains(&start_row)
9185 && visible_row_range.contains(&end_row)
9186 && cursor_row
9187 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9188 })?;
9189
9190 content_origin
9191 + point(
9192 Pixels::from(-scroll_pixel_position.x),
9193 Pixels::from(
9194 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9195 ),
9196 )
9197 };
9198
9199 origin.x -= BORDER_WIDTH;
9200
9201 window.defer_draw(element, origin, 1);
9202
9203 // Do not return an element, since it will already be drawn due to defer_draw.
9204 None
9205 }
9206
9207 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9208 px(30.)
9209 }
9210
9211 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9212 if self.read_only(cx) {
9213 cx.theme().players().read_only()
9214 } else {
9215 self.style.as_ref().unwrap().local_player
9216 }
9217 }
9218
9219 fn render_edit_prediction_accept_keybind(
9220 &self,
9221 window: &mut Window,
9222 cx: &App,
9223 ) -> Option<AnyElement> {
9224 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9225 let accept_keystroke = accept_binding.keystroke()?;
9226
9227 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9228
9229 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9230 Color::Accent
9231 } else {
9232 Color::Muted
9233 };
9234
9235 h_flex()
9236 .px_0p5()
9237 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9238 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9239 .text_size(TextSize::XSmall.rems(cx))
9240 .child(h_flex().children(ui::render_modifiers(
9241 accept_keystroke.modifiers(),
9242 PlatformStyle::platform(),
9243 Some(modifiers_color),
9244 Some(IconSize::XSmall.rems().into()),
9245 true,
9246 )))
9247 .when(is_platform_style_mac, |parent| {
9248 parent.child(accept_keystroke.key().to_string())
9249 })
9250 .when(!is_platform_style_mac, |parent| {
9251 parent.child(
9252 Key::new(
9253 util::capitalize(accept_keystroke.key()),
9254 Some(Color::Default),
9255 )
9256 .size(Some(IconSize::XSmall.rems().into())),
9257 )
9258 })
9259 .into_any()
9260 .into()
9261 }
9262
9263 fn render_edit_prediction_line_popover(
9264 &self,
9265 label: impl Into<SharedString>,
9266 icon: Option<IconName>,
9267 window: &mut Window,
9268 cx: &App,
9269 ) -> Stateful<Div> {
9270 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9271
9272 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9273 let has_keybind = keybind.is_some();
9274
9275 h_flex()
9276 .id("ep-line-popover")
9277 .py_0p5()
9278 .pl_1()
9279 .pr(padding_right)
9280 .gap_1()
9281 .rounded_md()
9282 .border_1()
9283 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9284 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9285 .shadow_xs()
9286 .when(!has_keybind, |el| {
9287 let status_colors = cx.theme().status();
9288
9289 el.bg(status_colors.error_background)
9290 .border_color(status_colors.error.opacity(0.6))
9291 .pl_2()
9292 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9293 .cursor_default()
9294 .hoverable_tooltip(move |_window, cx| {
9295 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9296 })
9297 })
9298 .children(keybind)
9299 .child(
9300 Label::new(label)
9301 .size(LabelSize::Small)
9302 .when(!has_keybind, |el| {
9303 el.color(cx.theme().status().error.into()).strikethrough()
9304 }),
9305 )
9306 .when(!has_keybind, |el| {
9307 el.child(
9308 h_flex().ml_1().child(
9309 Icon::new(IconName::Info)
9310 .size(IconSize::Small)
9311 .color(cx.theme().status().error.into()),
9312 ),
9313 )
9314 })
9315 .when_some(icon, |element, icon| {
9316 element.child(
9317 div()
9318 .mt(px(1.5))
9319 .child(Icon::new(icon).size(IconSize::Small)),
9320 )
9321 })
9322 }
9323
9324 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9325 let accent_color = cx.theme().colors().text_accent;
9326 let editor_bg_color = cx.theme().colors().editor_background;
9327 editor_bg_color.blend(accent_color.opacity(0.1))
9328 }
9329
9330 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9331 let accent_color = cx.theme().colors().text_accent;
9332 let editor_bg_color = cx.theme().colors().editor_background;
9333 editor_bg_color.blend(accent_color.opacity(0.6))
9334 }
9335 fn get_prediction_provider_icon_name(
9336 provider: &Option<RegisteredEditPredictionProvider>,
9337 ) -> IconName {
9338 match provider {
9339 Some(provider) => match provider.provider.name() {
9340 "copilot" => IconName::Copilot,
9341 "supermaven" => IconName::Supermaven,
9342 _ => IconName::ZedPredict,
9343 },
9344 None => IconName::ZedPredict,
9345 }
9346 }
9347
9348 fn render_edit_prediction_cursor_popover(
9349 &self,
9350 min_width: Pixels,
9351 max_width: Pixels,
9352 cursor_point: Point,
9353 style: &EditorStyle,
9354 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9355 _window: &Window,
9356 cx: &mut Context<Editor>,
9357 ) -> Option<AnyElement> {
9358 let provider = self.edit_prediction_provider.as_ref()?;
9359 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9360
9361 let is_refreshing = provider.provider.is_refreshing(cx);
9362
9363 fn pending_completion_container(icon: IconName) -> Div {
9364 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9365 }
9366
9367 let completion = match &self.active_edit_prediction {
9368 Some(prediction) => {
9369 if !self.has_visible_completions_menu() {
9370 const RADIUS: Pixels = px(6.);
9371 const BORDER_WIDTH: Pixels = px(1.);
9372
9373 return Some(
9374 h_flex()
9375 .elevation_2(cx)
9376 .border(BORDER_WIDTH)
9377 .border_color(cx.theme().colors().border)
9378 .when(accept_keystroke.is_none(), |el| {
9379 el.border_color(cx.theme().status().error)
9380 })
9381 .rounded(RADIUS)
9382 .rounded_tl(px(0.))
9383 .overflow_hidden()
9384 .child(div().px_1p5().child(match &prediction.completion {
9385 EditPrediction::MoveWithin { target, snapshot } => {
9386 use text::ToPoint as _;
9387 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9388 {
9389 Icon::new(IconName::ZedPredictDown)
9390 } else {
9391 Icon::new(IconName::ZedPredictUp)
9392 }
9393 }
9394 EditPrediction::MoveOutside { .. } => {
9395 // TODO [zeta2] custom icon for external jump?
9396 Icon::new(provider_icon)
9397 }
9398 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9399 }))
9400 .child(
9401 h_flex()
9402 .gap_1()
9403 .py_1()
9404 .px_2()
9405 .rounded_r(RADIUS - BORDER_WIDTH)
9406 .border_l_1()
9407 .border_color(cx.theme().colors().border)
9408 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9409 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9410 el.child(
9411 Label::new("Hold")
9412 .size(LabelSize::Small)
9413 .when(accept_keystroke.is_none(), |el| {
9414 el.strikethrough()
9415 })
9416 .line_height_style(LineHeightStyle::UiLabel),
9417 )
9418 })
9419 .id("edit_prediction_cursor_popover_keybind")
9420 .when(accept_keystroke.is_none(), |el| {
9421 let status_colors = cx.theme().status();
9422
9423 el.bg(status_colors.error_background)
9424 .border_color(status_colors.error.opacity(0.6))
9425 .child(Icon::new(IconName::Info).color(Color::Error))
9426 .cursor_default()
9427 .hoverable_tooltip(move |_window, cx| {
9428 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9429 .into()
9430 })
9431 })
9432 .when_some(
9433 accept_keystroke.as_ref(),
9434 |el, accept_keystroke| {
9435 el.child(h_flex().children(ui::render_modifiers(
9436 accept_keystroke.modifiers(),
9437 PlatformStyle::platform(),
9438 Some(Color::Default),
9439 Some(IconSize::XSmall.rems().into()),
9440 false,
9441 )))
9442 },
9443 ),
9444 )
9445 .into_any(),
9446 );
9447 }
9448
9449 self.render_edit_prediction_cursor_popover_preview(
9450 prediction,
9451 cursor_point,
9452 style,
9453 cx,
9454 )?
9455 }
9456
9457 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9458 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9459 stale_completion,
9460 cursor_point,
9461 style,
9462 cx,
9463 )?,
9464
9465 None => pending_completion_container(provider_icon)
9466 .child(Label::new("...").size(LabelSize::Small)),
9467 },
9468
9469 None => pending_completion_container(provider_icon)
9470 .child(Label::new("...").size(LabelSize::Small)),
9471 };
9472
9473 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9474 completion
9475 .with_animation(
9476 "loading-completion",
9477 Animation::new(Duration::from_secs(2))
9478 .repeat()
9479 .with_easing(pulsating_between(0.4, 0.8)),
9480 |label, delta| label.opacity(delta),
9481 )
9482 .into_any_element()
9483 } else {
9484 completion.into_any_element()
9485 };
9486
9487 let has_completion = self.active_edit_prediction.is_some();
9488
9489 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9490 Some(
9491 h_flex()
9492 .min_w(min_width)
9493 .max_w(max_width)
9494 .flex_1()
9495 .elevation_2(cx)
9496 .border_color(cx.theme().colors().border)
9497 .child(
9498 div()
9499 .flex_1()
9500 .py_1()
9501 .px_2()
9502 .overflow_hidden()
9503 .child(completion),
9504 )
9505 .when_some(accept_keystroke, |el, accept_keystroke| {
9506 if !accept_keystroke.modifiers().modified() {
9507 return el;
9508 }
9509
9510 el.child(
9511 h_flex()
9512 .h_full()
9513 .border_l_1()
9514 .rounded_r_lg()
9515 .border_color(cx.theme().colors().border)
9516 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9517 .gap_1()
9518 .py_1()
9519 .px_2()
9520 .child(
9521 h_flex()
9522 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9523 .when(is_platform_style_mac, |parent| parent.gap_1())
9524 .child(h_flex().children(ui::render_modifiers(
9525 accept_keystroke.modifiers(),
9526 PlatformStyle::platform(),
9527 Some(if !has_completion {
9528 Color::Muted
9529 } else {
9530 Color::Default
9531 }),
9532 None,
9533 false,
9534 ))),
9535 )
9536 .child(Label::new("Preview").into_any_element())
9537 .opacity(if has_completion { 1.0 } else { 0.4 }),
9538 )
9539 })
9540 .into_any(),
9541 )
9542 }
9543
9544 fn render_edit_prediction_cursor_popover_preview(
9545 &self,
9546 completion: &EditPredictionState,
9547 cursor_point: Point,
9548 style: &EditorStyle,
9549 cx: &mut Context<Editor>,
9550 ) -> Option<Div> {
9551 use text::ToPoint as _;
9552
9553 fn render_relative_row_jump(
9554 prefix: impl Into<String>,
9555 current_row: u32,
9556 target_row: u32,
9557 ) -> Div {
9558 let (row_diff, arrow) = if target_row < current_row {
9559 (current_row - target_row, IconName::ArrowUp)
9560 } else {
9561 (target_row - current_row, IconName::ArrowDown)
9562 };
9563
9564 h_flex()
9565 .child(
9566 Label::new(format!("{}{}", prefix.into(), row_diff))
9567 .color(Color::Muted)
9568 .size(LabelSize::Small),
9569 )
9570 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9571 }
9572
9573 let supports_jump = self
9574 .edit_prediction_provider
9575 .as_ref()
9576 .map(|provider| provider.provider.supports_jump_to_edit())
9577 .unwrap_or(true);
9578
9579 match &completion.completion {
9580 EditPrediction::MoveWithin {
9581 target, snapshot, ..
9582 } => {
9583 if !supports_jump {
9584 return None;
9585 }
9586
9587 Some(
9588 h_flex()
9589 .px_2()
9590 .gap_2()
9591 .flex_1()
9592 .child(
9593 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9594 Icon::new(IconName::ZedPredictDown)
9595 } else {
9596 Icon::new(IconName::ZedPredictUp)
9597 },
9598 )
9599 .child(Label::new("Jump to Edit")),
9600 )
9601 }
9602 EditPrediction::MoveOutside { snapshot, .. } => {
9603 let file_name = snapshot
9604 .file()
9605 .map(|file| file.file_name(cx))
9606 .unwrap_or("untitled");
9607 Some(
9608 h_flex()
9609 .px_2()
9610 .gap_2()
9611 .flex_1()
9612 .child(Icon::new(IconName::ZedPredict))
9613 .child(Label::new(format!("Jump to {file_name}"))),
9614 )
9615 }
9616 EditPrediction::Edit {
9617 edits,
9618 edit_preview,
9619 snapshot,
9620 display_mode: _,
9621 } => {
9622 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9623
9624 let (highlighted_edits, has_more_lines) =
9625 if let Some(edit_preview) = edit_preview.as_ref() {
9626 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9627 .first_line_preview()
9628 } else {
9629 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9630 };
9631
9632 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9633 .with_default_highlights(&style.text, highlighted_edits.highlights);
9634
9635 let preview = h_flex()
9636 .gap_1()
9637 .min_w_16()
9638 .child(styled_text)
9639 .when(has_more_lines, |parent| parent.child("…"));
9640
9641 let left = if supports_jump && first_edit_row != cursor_point.row {
9642 render_relative_row_jump("", cursor_point.row, first_edit_row)
9643 .into_any_element()
9644 } else {
9645 let icon_name =
9646 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9647 Icon::new(icon_name).into_any_element()
9648 };
9649
9650 Some(
9651 h_flex()
9652 .h_full()
9653 .flex_1()
9654 .gap_2()
9655 .pr_1()
9656 .overflow_x_hidden()
9657 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9658 .child(left)
9659 .child(preview),
9660 )
9661 }
9662 }
9663 }
9664
9665 pub fn render_context_menu(
9666 &self,
9667 style: &EditorStyle,
9668 max_height_in_lines: u32,
9669 window: &mut Window,
9670 cx: &mut Context<Editor>,
9671 ) -> Option<AnyElement> {
9672 let menu = self.context_menu.borrow();
9673 let menu = menu.as_ref()?;
9674 if !menu.visible() {
9675 return None;
9676 };
9677 Some(menu.render(style, max_height_in_lines, window, cx))
9678 }
9679
9680 fn render_context_menu_aside(
9681 &mut self,
9682 max_size: Size<Pixels>,
9683 window: &mut Window,
9684 cx: &mut Context<Editor>,
9685 ) -> Option<AnyElement> {
9686 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9687 if menu.visible() {
9688 menu.render_aside(max_size, window, cx)
9689 } else {
9690 None
9691 }
9692 })
9693 }
9694
9695 fn hide_context_menu(
9696 &mut self,
9697 window: &mut Window,
9698 cx: &mut Context<Self>,
9699 ) -> Option<CodeContextMenu> {
9700 cx.notify();
9701 self.completion_tasks.clear();
9702 let context_menu = self.context_menu.borrow_mut().take();
9703 self.stale_edit_prediction_in_menu.take();
9704 self.update_visible_edit_prediction(window, cx);
9705 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9706 && let Some(completion_provider) = &self.completion_provider
9707 {
9708 completion_provider.selection_changed(None, window, cx);
9709 }
9710 context_menu
9711 }
9712
9713 fn show_snippet_choices(
9714 &mut self,
9715 choices: &Vec<String>,
9716 selection: Range<Anchor>,
9717 cx: &mut Context<Self>,
9718 ) {
9719 let Some((_, buffer, _)) = self
9720 .buffer()
9721 .read(cx)
9722 .excerpt_containing(selection.start, cx)
9723 else {
9724 return;
9725 };
9726 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9727 else {
9728 return;
9729 };
9730 if buffer != end_buffer {
9731 log::error!("expected anchor range to have matching buffer IDs");
9732 return;
9733 }
9734
9735 let id = post_inc(&mut self.next_completion_id);
9736 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9737 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9738 CompletionsMenu::new_snippet_choices(
9739 id,
9740 true,
9741 choices,
9742 selection,
9743 buffer,
9744 snippet_sort_order,
9745 ),
9746 ));
9747 }
9748
9749 pub fn insert_snippet(
9750 &mut self,
9751 insertion_ranges: &[Range<usize>],
9752 snippet: Snippet,
9753 window: &mut Window,
9754 cx: &mut Context<Self>,
9755 ) -> Result<()> {
9756 struct Tabstop<T> {
9757 is_end_tabstop: bool,
9758 ranges: Vec<Range<T>>,
9759 choices: Option<Vec<String>>,
9760 }
9761
9762 let tabstops = self.buffer.update(cx, |buffer, cx| {
9763 let snippet_text: Arc<str> = snippet.text.clone().into();
9764 let edits = insertion_ranges
9765 .iter()
9766 .cloned()
9767 .map(|range| (range, snippet_text.clone()));
9768 let autoindent_mode = AutoindentMode::Block {
9769 original_indent_columns: Vec::new(),
9770 };
9771 buffer.edit(edits, Some(autoindent_mode), cx);
9772
9773 let snapshot = &*buffer.read(cx);
9774 let snippet = &snippet;
9775 snippet
9776 .tabstops
9777 .iter()
9778 .map(|tabstop| {
9779 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9780 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9781 });
9782 let mut tabstop_ranges = tabstop
9783 .ranges
9784 .iter()
9785 .flat_map(|tabstop_range| {
9786 let mut delta = 0_isize;
9787 insertion_ranges.iter().map(move |insertion_range| {
9788 let insertion_start = insertion_range.start as isize + delta;
9789 delta +=
9790 snippet.text.len() as isize - insertion_range.len() as isize;
9791
9792 let start = ((insertion_start + tabstop_range.start) as usize)
9793 .min(snapshot.len());
9794 let end = ((insertion_start + tabstop_range.end) as usize)
9795 .min(snapshot.len());
9796 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9797 })
9798 })
9799 .collect::<Vec<_>>();
9800 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9801
9802 Tabstop {
9803 is_end_tabstop,
9804 ranges: tabstop_ranges,
9805 choices: tabstop.choices.clone(),
9806 }
9807 })
9808 .collect::<Vec<_>>()
9809 });
9810 if let Some(tabstop) = tabstops.first() {
9811 self.change_selections(Default::default(), window, cx, |s| {
9812 // Reverse order so that the first range is the newest created selection.
9813 // Completions will use it and autoscroll will prioritize it.
9814 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9815 });
9816
9817 if let Some(choices) = &tabstop.choices
9818 && let Some(selection) = tabstop.ranges.first()
9819 {
9820 self.show_snippet_choices(choices, selection.clone(), cx)
9821 }
9822
9823 // If we're already at the last tabstop and it's at the end of the snippet,
9824 // we're done, we don't need to keep the state around.
9825 if !tabstop.is_end_tabstop {
9826 let choices = tabstops
9827 .iter()
9828 .map(|tabstop| tabstop.choices.clone())
9829 .collect();
9830
9831 let ranges = tabstops
9832 .into_iter()
9833 .map(|tabstop| tabstop.ranges)
9834 .collect::<Vec<_>>();
9835
9836 self.snippet_stack.push(SnippetState {
9837 active_index: 0,
9838 ranges,
9839 choices,
9840 });
9841 }
9842
9843 // Check whether the just-entered snippet ends with an auto-closable bracket.
9844 if self.autoclose_regions.is_empty() {
9845 let snapshot = self.buffer.read(cx).snapshot(cx);
9846 let mut all_selections = self.selections.all::<Point>(cx);
9847 for selection in &mut all_selections {
9848 let selection_head = selection.head();
9849 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9850 continue;
9851 };
9852
9853 let mut bracket_pair = None;
9854 let max_lookup_length = scope
9855 .brackets()
9856 .map(|(pair, _)| {
9857 pair.start
9858 .as_str()
9859 .chars()
9860 .count()
9861 .max(pair.end.as_str().chars().count())
9862 })
9863 .max();
9864 if let Some(max_lookup_length) = max_lookup_length {
9865 let next_text = snapshot
9866 .chars_at(selection_head)
9867 .take(max_lookup_length)
9868 .collect::<String>();
9869 let prev_text = snapshot
9870 .reversed_chars_at(selection_head)
9871 .take(max_lookup_length)
9872 .collect::<String>();
9873
9874 for (pair, enabled) in scope.brackets() {
9875 if enabled
9876 && pair.close
9877 && prev_text.starts_with(pair.start.as_str())
9878 && next_text.starts_with(pair.end.as_str())
9879 {
9880 bracket_pair = Some(pair.clone());
9881 break;
9882 }
9883 }
9884 }
9885
9886 if let Some(pair) = bracket_pair {
9887 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9888 let autoclose_enabled =
9889 self.use_autoclose && snapshot_settings.use_autoclose;
9890 if autoclose_enabled {
9891 let start = snapshot.anchor_after(selection_head);
9892 let end = snapshot.anchor_after(selection_head);
9893 self.autoclose_regions.push(AutocloseRegion {
9894 selection_id: selection.id,
9895 range: start..end,
9896 pair,
9897 });
9898 }
9899 }
9900 }
9901 }
9902 }
9903 Ok(())
9904 }
9905
9906 pub fn move_to_next_snippet_tabstop(
9907 &mut self,
9908 window: &mut Window,
9909 cx: &mut Context<Self>,
9910 ) -> bool {
9911 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9912 }
9913
9914 pub fn move_to_prev_snippet_tabstop(
9915 &mut self,
9916 window: &mut Window,
9917 cx: &mut Context<Self>,
9918 ) -> bool {
9919 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9920 }
9921
9922 pub fn move_to_snippet_tabstop(
9923 &mut self,
9924 bias: Bias,
9925 window: &mut Window,
9926 cx: &mut Context<Self>,
9927 ) -> bool {
9928 if let Some(mut snippet) = self.snippet_stack.pop() {
9929 match bias {
9930 Bias::Left => {
9931 if snippet.active_index > 0 {
9932 snippet.active_index -= 1;
9933 } else {
9934 self.snippet_stack.push(snippet);
9935 return false;
9936 }
9937 }
9938 Bias::Right => {
9939 if snippet.active_index + 1 < snippet.ranges.len() {
9940 snippet.active_index += 1;
9941 } else {
9942 self.snippet_stack.push(snippet);
9943 return false;
9944 }
9945 }
9946 }
9947 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9948 self.change_selections(Default::default(), window, cx, |s| {
9949 // Reverse order so that the first range is the newest created selection.
9950 // Completions will use it and autoscroll will prioritize it.
9951 s.select_ranges(current_ranges.iter().rev().cloned())
9952 });
9953
9954 if let Some(choices) = &snippet.choices[snippet.active_index]
9955 && let Some(selection) = current_ranges.first()
9956 {
9957 self.show_snippet_choices(choices, selection.clone(), cx);
9958 }
9959
9960 // If snippet state is not at the last tabstop, push it back on the stack
9961 if snippet.active_index + 1 < snippet.ranges.len() {
9962 self.snippet_stack.push(snippet);
9963 }
9964 return true;
9965 }
9966 }
9967
9968 false
9969 }
9970
9971 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9972 self.transact(window, cx, |this, window, cx| {
9973 this.select_all(&SelectAll, window, cx);
9974 this.insert("", window, cx);
9975 });
9976 }
9977
9978 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9979 if self.read_only(cx) {
9980 return;
9981 }
9982 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9983 self.transact(window, cx, |this, window, cx| {
9984 this.select_autoclose_pair(window, cx);
9985 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9986 if !this.linked_edit_ranges.is_empty() {
9987 let selections = this.selections.all::<MultiBufferPoint>(cx);
9988 let snapshot = this.buffer.read(cx).snapshot(cx);
9989
9990 for selection in selections.iter() {
9991 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9992 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9993 if selection_start.buffer_id != selection_end.buffer_id {
9994 continue;
9995 }
9996 if let Some(ranges) =
9997 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9998 {
9999 for (buffer, entries) in ranges {
10000 linked_ranges.entry(buffer).or_default().extend(entries);
10001 }
10002 }
10003 }
10004 }
10005
10006 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10007 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10008 for selection in &mut selections {
10009 if selection.is_empty() {
10010 let old_head = selection.head();
10011 let mut new_head =
10012 movement::left(&display_map, old_head.to_display_point(&display_map))
10013 .to_point(&display_map);
10014 if let Some((buffer, line_buffer_range)) = display_map
10015 .buffer_snapshot()
10016 .buffer_line_for_row(MultiBufferRow(old_head.row))
10017 {
10018 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10019 let indent_len = match indent_size.kind {
10020 IndentKind::Space => {
10021 buffer.settings_at(line_buffer_range.start, cx).tab_size
10022 }
10023 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10024 };
10025 if old_head.column <= indent_size.len && old_head.column > 0 {
10026 let indent_len = indent_len.get();
10027 new_head = cmp::min(
10028 new_head,
10029 MultiBufferPoint::new(
10030 old_head.row,
10031 ((old_head.column - 1) / indent_len) * indent_len,
10032 ),
10033 );
10034 }
10035 }
10036
10037 selection.set_head(new_head, SelectionGoal::None);
10038 }
10039 }
10040
10041 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10042 this.insert("", window, cx);
10043 let empty_str: Arc<str> = Arc::from("");
10044 for (buffer, edits) in linked_ranges {
10045 let snapshot = buffer.read(cx).snapshot();
10046 use text::ToPoint as TP;
10047
10048 let edits = edits
10049 .into_iter()
10050 .map(|range| {
10051 let end_point = TP::to_point(&range.end, &snapshot);
10052 let mut start_point = TP::to_point(&range.start, &snapshot);
10053
10054 if end_point == start_point {
10055 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10056 .saturating_sub(1);
10057 start_point =
10058 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10059 };
10060
10061 (start_point..end_point, empty_str.clone())
10062 })
10063 .sorted_by_key(|(range, _)| range.start)
10064 .collect::<Vec<_>>();
10065 buffer.update(cx, |this, cx| {
10066 this.edit(edits, None, cx);
10067 })
10068 }
10069 this.refresh_edit_prediction(true, false, window, cx);
10070 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10071 });
10072 }
10073
10074 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10075 if self.read_only(cx) {
10076 return;
10077 }
10078 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10079 self.transact(window, cx, |this, window, cx| {
10080 this.change_selections(Default::default(), window, cx, |s| {
10081 s.move_with(|map, selection| {
10082 if selection.is_empty() {
10083 let cursor = movement::right(map, selection.head());
10084 selection.end = cursor;
10085 selection.reversed = true;
10086 selection.goal = SelectionGoal::None;
10087 }
10088 })
10089 });
10090 this.insert("", window, cx);
10091 this.refresh_edit_prediction(true, false, window, cx);
10092 });
10093 }
10094
10095 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10096 if self.mode.is_single_line() {
10097 cx.propagate();
10098 return;
10099 }
10100
10101 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10102 if self.move_to_prev_snippet_tabstop(window, cx) {
10103 return;
10104 }
10105 self.outdent(&Outdent, window, cx);
10106 }
10107
10108 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10109 if self.mode.is_single_line() {
10110 cx.propagate();
10111 return;
10112 }
10113
10114 if self.move_to_next_snippet_tabstop(window, cx) {
10115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10116 return;
10117 }
10118 if self.read_only(cx) {
10119 return;
10120 }
10121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10122 let mut selections = self.selections.all_adjusted(cx);
10123 let buffer = self.buffer.read(cx);
10124 let snapshot = buffer.snapshot(cx);
10125 let rows_iter = selections.iter().map(|s| s.head().row);
10126 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10127
10128 let has_some_cursor_in_whitespace = selections
10129 .iter()
10130 .filter(|selection| selection.is_empty())
10131 .any(|selection| {
10132 let cursor = selection.head();
10133 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10134 cursor.column < current_indent.len
10135 });
10136
10137 let mut edits = Vec::new();
10138 let mut prev_edited_row = 0;
10139 let mut row_delta = 0;
10140 for selection in &mut selections {
10141 if selection.start.row != prev_edited_row {
10142 row_delta = 0;
10143 }
10144 prev_edited_row = selection.end.row;
10145
10146 // If the selection is non-empty, then increase the indentation of the selected lines.
10147 if !selection.is_empty() {
10148 row_delta =
10149 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10150 continue;
10151 }
10152
10153 let cursor = selection.head();
10154 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10155 if let Some(suggested_indent) =
10156 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10157 {
10158 // Don't do anything if already at suggested indent
10159 // and there is any other cursor which is not
10160 if has_some_cursor_in_whitespace
10161 && cursor.column == current_indent.len
10162 && current_indent.len == suggested_indent.len
10163 {
10164 continue;
10165 }
10166
10167 // Adjust line and move cursor to suggested indent
10168 // if cursor is not at suggested indent
10169 if cursor.column < suggested_indent.len
10170 && cursor.column <= current_indent.len
10171 && current_indent.len <= suggested_indent.len
10172 {
10173 selection.start = Point::new(cursor.row, suggested_indent.len);
10174 selection.end = selection.start;
10175 if row_delta == 0 {
10176 edits.extend(Buffer::edit_for_indent_size_adjustment(
10177 cursor.row,
10178 current_indent,
10179 suggested_indent,
10180 ));
10181 row_delta = suggested_indent.len - current_indent.len;
10182 }
10183 continue;
10184 }
10185
10186 // If current indent is more than suggested indent
10187 // only move cursor to current indent and skip indent
10188 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10189 selection.start = Point::new(cursor.row, current_indent.len);
10190 selection.end = selection.start;
10191 continue;
10192 }
10193 }
10194
10195 // Otherwise, insert a hard or soft tab.
10196 let settings = buffer.language_settings_at(cursor, cx);
10197 let tab_size = if settings.hard_tabs {
10198 IndentSize::tab()
10199 } else {
10200 let tab_size = settings.tab_size.get();
10201 let indent_remainder = snapshot
10202 .text_for_range(Point::new(cursor.row, 0)..cursor)
10203 .flat_map(str::chars)
10204 .fold(row_delta % tab_size, |counter: u32, c| {
10205 if c == '\t' {
10206 0
10207 } else {
10208 (counter + 1) % tab_size
10209 }
10210 });
10211
10212 let chars_to_next_tab_stop = tab_size - indent_remainder;
10213 IndentSize::spaces(chars_to_next_tab_stop)
10214 };
10215 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10216 selection.end = selection.start;
10217 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10218 row_delta += tab_size.len;
10219 }
10220
10221 self.transact(window, cx, |this, window, cx| {
10222 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10223 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10224 this.refresh_edit_prediction(true, false, window, cx);
10225 });
10226 }
10227
10228 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10229 if self.read_only(cx) {
10230 return;
10231 }
10232 if self.mode.is_single_line() {
10233 cx.propagate();
10234 return;
10235 }
10236
10237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10238 let mut selections = self.selections.all::<Point>(cx);
10239 let mut prev_edited_row = 0;
10240 let mut row_delta = 0;
10241 let mut edits = Vec::new();
10242 let buffer = self.buffer.read(cx);
10243 let snapshot = buffer.snapshot(cx);
10244 for selection in &mut selections {
10245 if selection.start.row != prev_edited_row {
10246 row_delta = 0;
10247 }
10248 prev_edited_row = selection.end.row;
10249
10250 row_delta =
10251 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10252 }
10253
10254 self.transact(window, cx, |this, window, cx| {
10255 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10256 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10257 });
10258 }
10259
10260 fn indent_selection(
10261 buffer: &MultiBuffer,
10262 snapshot: &MultiBufferSnapshot,
10263 selection: &mut Selection<Point>,
10264 edits: &mut Vec<(Range<Point>, String)>,
10265 delta_for_start_row: u32,
10266 cx: &App,
10267 ) -> u32 {
10268 let settings = buffer.language_settings_at(selection.start, cx);
10269 let tab_size = settings.tab_size.get();
10270 let indent_kind = if settings.hard_tabs {
10271 IndentKind::Tab
10272 } else {
10273 IndentKind::Space
10274 };
10275 let mut start_row = selection.start.row;
10276 let mut end_row = selection.end.row + 1;
10277
10278 // If a selection ends at the beginning of a line, don't indent
10279 // that last line.
10280 if selection.end.column == 0 && selection.end.row > selection.start.row {
10281 end_row -= 1;
10282 }
10283
10284 // Avoid re-indenting a row that has already been indented by a
10285 // previous selection, but still update this selection's column
10286 // to reflect that indentation.
10287 if delta_for_start_row > 0 {
10288 start_row += 1;
10289 selection.start.column += delta_for_start_row;
10290 if selection.end.row == selection.start.row {
10291 selection.end.column += delta_for_start_row;
10292 }
10293 }
10294
10295 let mut delta_for_end_row = 0;
10296 let has_multiple_rows = start_row + 1 != end_row;
10297 for row in start_row..end_row {
10298 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10299 let indent_delta = match (current_indent.kind, indent_kind) {
10300 (IndentKind::Space, IndentKind::Space) => {
10301 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10302 IndentSize::spaces(columns_to_next_tab_stop)
10303 }
10304 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10305 (_, IndentKind::Tab) => IndentSize::tab(),
10306 };
10307
10308 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10309 0
10310 } else {
10311 selection.start.column
10312 };
10313 let row_start = Point::new(row, start);
10314 edits.push((
10315 row_start..row_start,
10316 indent_delta.chars().collect::<String>(),
10317 ));
10318
10319 // Update this selection's endpoints to reflect the indentation.
10320 if row == selection.start.row {
10321 selection.start.column += indent_delta.len;
10322 }
10323 if row == selection.end.row {
10324 selection.end.column += indent_delta.len;
10325 delta_for_end_row = indent_delta.len;
10326 }
10327 }
10328
10329 if selection.start.row == selection.end.row {
10330 delta_for_start_row + delta_for_end_row
10331 } else {
10332 delta_for_end_row
10333 }
10334 }
10335
10336 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10337 if self.read_only(cx) {
10338 return;
10339 }
10340 if self.mode.is_single_line() {
10341 cx.propagate();
10342 return;
10343 }
10344
10345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10347 let selections = self.selections.all::<Point>(cx);
10348 let mut deletion_ranges = Vec::new();
10349 let mut last_outdent = None;
10350 {
10351 let buffer = self.buffer.read(cx);
10352 let snapshot = buffer.snapshot(cx);
10353 for selection in &selections {
10354 let settings = buffer.language_settings_at(selection.start, cx);
10355 let tab_size = settings.tab_size.get();
10356 let mut rows = selection.spanned_rows(false, &display_map);
10357
10358 // Avoid re-outdenting a row that has already been outdented by a
10359 // previous selection.
10360 if let Some(last_row) = last_outdent
10361 && last_row == rows.start
10362 {
10363 rows.start = rows.start.next_row();
10364 }
10365 let has_multiple_rows = rows.len() > 1;
10366 for row in rows.iter_rows() {
10367 let indent_size = snapshot.indent_size_for_line(row);
10368 if indent_size.len > 0 {
10369 let deletion_len = match indent_size.kind {
10370 IndentKind::Space => {
10371 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10372 if columns_to_prev_tab_stop == 0 {
10373 tab_size
10374 } else {
10375 columns_to_prev_tab_stop
10376 }
10377 }
10378 IndentKind::Tab => 1,
10379 };
10380 let start = if has_multiple_rows
10381 || deletion_len > selection.start.column
10382 || indent_size.len < selection.start.column
10383 {
10384 0
10385 } else {
10386 selection.start.column - deletion_len
10387 };
10388 deletion_ranges.push(
10389 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10390 );
10391 last_outdent = Some(row);
10392 }
10393 }
10394 }
10395 }
10396
10397 self.transact(window, cx, |this, window, cx| {
10398 this.buffer.update(cx, |buffer, cx| {
10399 let empty_str: Arc<str> = Arc::default();
10400 buffer.edit(
10401 deletion_ranges
10402 .into_iter()
10403 .map(|range| (range, empty_str.clone())),
10404 None,
10405 cx,
10406 );
10407 });
10408 let selections = this.selections.all::<usize>(cx);
10409 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10410 });
10411 }
10412
10413 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10414 if self.read_only(cx) {
10415 return;
10416 }
10417 if self.mode.is_single_line() {
10418 cx.propagate();
10419 return;
10420 }
10421
10422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10423 let selections = self
10424 .selections
10425 .all::<usize>(cx)
10426 .into_iter()
10427 .map(|s| s.range());
10428
10429 self.transact(window, cx, |this, window, cx| {
10430 this.buffer.update(cx, |buffer, cx| {
10431 buffer.autoindent_ranges(selections, cx);
10432 });
10433 let selections = this.selections.all::<usize>(cx);
10434 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10435 });
10436 }
10437
10438 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10439 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10440 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10441 let selections = self.selections.all::<Point>(cx);
10442
10443 let mut new_cursors = Vec::new();
10444 let mut edit_ranges = Vec::new();
10445 let mut selections = selections.iter().peekable();
10446 while let Some(selection) = selections.next() {
10447 let mut rows = selection.spanned_rows(false, &display_map);
10448
10449 // Accumulate contiguous regions of rows that we want to delete.
10450 while let Some(next_selection) = selections.peek() {
10451 let next_rows = next_selection.spanned_rows(false, &display_map);
10452 if next_rows.start <= rows.end {
10453 rows.end = next_rows.end;
10454 selections.next().unwrap();
10455 } else {
10456 break;
10457 }
10458 }
10459
10460 let buffer = display_map.buffer_snapshot();
10461 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10462 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10463 // If there's a line after the range, delete the \n from the end of the row range
10464 (
10465 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10466 rows.end,
10467 )
10468 } else {
10469 // If there isn't a line after the range, delete the \n from the line before the
10470 // start of the row range
10471 edit_start = edit_start.saturating_sub(1);
10472 (buffer.len(), rows.start.previous_row())
10473 };
10474
10475 let text_layout_details = self.text_layout_details(window);
10476 let x = display_map.x_for_display_point(
10477 selection.head().to_display_point(&display_map),
10478 &text_layout_details,
10479 );
10480 let row = Point::new(target_row.0, 0)
10481 .to_display_point(&display_map)
10482 .row();
10483 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10484
10485 new_cursors.push((
10486 selection.id,
10487 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10488 SelectionGoal::None,
10489 ));
10490 edit_ranges.push(edit_start..edit_end);
10491 }
10492
10493 self.transact(window, cx, |this, window, cx| {
10494 let buffer = this.buffer.update(cx, |buffer, cx| {
10495 let empty_str: Arc<str> = Arc::default();
10496 buffer.edit(
10497 edit_ranges
10498 .into_iter()
10499 .map(|range| (range, empty_str.clone())),
10500 None,
10501 cx,
10502 );
10503 buffer.snapshot(cx)
10504 });
10505 let new_selections = new_cursors
10506 .into_iter()
10507 .map(|(id, cursor, goal)| {
10508 let cursor = cursor.to_point(&buffer);
10509 Selection {
10510 id,
10511 start: cursor,
10512 end: cursor,
10513 reversed: false,
10514 goal,
10515 }
10516 })
10517 .collect();
10518
10519 this.change_selections(Default::default(), window, cx, |s| {
10520 s.select(new_selections);
10521 });
10522 });
10523 }
10524
10525 pub fn join_lines_impl(
10526 &mut self,
10527 insert_whitespace: bool,
10528 window: &mut Window,
10529 cx: &mut Context<Self>,
10530 ) {
10531 if self.read_only(cx) {
10532 return;
10533 }
10534 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10535 for selection in self.selections.all::<Point>(cx) {
10536 let start = MultiBufferRow(selection.start.row);
10537 // Treat single line selections as if they include the next line. Otherwise this action
10538 // would do nothing for single line selections individual cursors.
10539 let end = if selection.start.row == selection.end.row {
10540 MultiBufferRow(selection.start.row + 1)
10541 } else {
10542 MultiBufferRow(selection.end.row)
10543 };
10544
10545 if let Some(last_row_range) = row_ranges.last_mut()
10546 && start <= last_row_range.end
10547 {
10548 last_row_range.end = end;
10549 continue;
10550 }
10551 row_ranges.push(start..end);
10552 }
10553
10554 let snapshot = self.buffer.read(cx).snapshot(cx);
10555 let mut cursor_positions = Vec::new();
10556 for row_range in &row_ranges {
10557 let anchor = snapshot.anchor_before(Point::new(
10558 row_range.end.previous_row().0,
10559 snapshot.line_len(row_range.end.previous_row()),
10560 ));
10561 cursor_positions.push(anchor..anchor);
10562 }
10563
10564 self.transact(window, cx, |this, window, cx| {
10565 for row_range in row_ranges.into_iter().rev() {
10566 for row in row_range.iter_rows().rev() {
10567 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10568 let next_line_row = row.next_row();
10569 let indent = snapshot.indent_size_for_line(next_line_row);
10570 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10571
10572 let replace =
10573 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10574 " "
10575 } else {
10576 ""
10577 };
10578
10579 this.buffer.update(cx, |buffer, cx| {
10580 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10581 });
10582 }
10583 }
10584
10585 this.change_selections(Default::default(), window, cx, |s| {
10586 s.select_anchor_ranges(cursor_positions)
10587 });
10588 });
10589 }
10590
10591 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10592 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10593 self.join_lines_impl(true, window, cx);
10594 }
10595
10596 pub fn sort_lines_case_sensitive(
10597 &mut self,
10598 _: &SortLinesCaseSensitive,
10599 window: &mut Window,
10600 cx: &mut Context<Self>,
10601 ) {
10602 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10603 }
10604
10605 pub fn sort_lines_by_length(
10606 &mut self,
10607 _: &SortLinesByLength,
10608 window: &mut Window,
10609 cx: &mut Context<Self>,
10610 ) {
10611 self.manipulate_immutable_lines(window, cx, |lines| {
10612 lines.sort_by_key(|&line| line.chars().count())
10613 })
10614 }
10615
10616 pub fn sort_lines_case_insensitive(
10617 &mut self,
10618 _: &SortLinesCaseInsensitive,
10619 window: &mut Window,
10620 cx: &mut Context<Self>,
10621 ) {
10622 self.manipulate_immutable_lines(window, cx, |lines| {
10623 lines.sort_by_key(|line| line.to_lowercase())
10624 })
10625 }
10626
10627 pub fn unique_lines_case_insensitive(
10628 &mut self,
10629 _: &UniqueLinesCaseInsensitive,
10630 window: &mut Window,
10631 cx: &mut Context<Self>,
10632 ) {
10633 self.manipulate_immutable_lines(window, cx, |lines| {
10634 let mut seen = HashSet::default();
10635 lines.retain(|line| seen.insert(line.to_lowercase()));
10636 })
10637 }
10638
10639 pub fn unique_lines_case_sensitive(
10640 &mut self,
10641 _: &UniqueLinesCaseSensitive,
10642 window: &mut Window,
10643 cx: &mut Context<Self>,
10644 ) {
10645 self.manipulate_immutable_lines(window, cx, |lines| {
10646 let mut seen = HashSet::default();
10647 lines.retain(|line| seen.insert(*line));
10648 })
10649 }
10650
10651 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10652 let snapshot = self.buffer.read(cx).snapshot(cx);
10653 for selection in self.selections.disjoint_anchors_arc().iter() {
10654 if snapshot
10655 .language_at(selection.start)
10656 .and_then(|lang| lang.config().wrap_characters.as_ref())
10657 .is_some()
10658 {
10659 return true;
10660 }
10661 }
10662 false
10663 }
10664
10665 fn wrap_selections_in_tag(
10666 &mut self,
10667 _: &WrapSelectionsInTag,
10668 window: &mut Window,
10669 cx: &mut Context<Self>,
10670 ) {
10671 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10672
10673 let snapshot = self.buffer.read(cx).snapshot(cx);
10674
10675 let mut edits = Vec::new();
10676 let mut boundaries = Vec::new();
10677
10678 for selection in self.selections.all::<Point>(cx).iter() {
10679 let Some(wrap_config) = snapshot
10680 .language_at(selection.start)
10681 .and_then(|lang| lang.config().wrap_characters.clone())
10682 else {
10683 continue;
10684 };
10685
10686 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10687 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10688
10689 let start_before = snapshot.anchor_before(selection.start);
10690 let end_after = snapshot.anchor_after(selection.end);
10691
10692 edits.push((start_before..start_before, open_tag));
10693 edits.push((end_after..end_after, close_tag));
10694
10695 boundaries.push((
10696 start_before,
10697 end_after,
10698 wrap_config.start_prefix.len(),
10699 wrap_config.end_suffix.len(),
10700 ));
10701 }
10702
10703 if edits.is_empty() {
10704 return;
10705 }
10706
10707 self.transact(window, cx, |this, window, cx| {
10708 let buffer = this.buffer.update(cx, |buffer, cx| {
10709 buffer.edit(edits, None, cx);
10710 buffer.snapshot(cx)
10711 });
10712
10713 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10714 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10715 boundaries.into_iter()
10716 {
10717 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10718 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10719 new_selections.push(open_offset..open_offset);
10720 new_selections.push(close_offset..close_offset);
10721 }
10722
10723 this.change_selections(Default::default(), window, cx, |s| {
10724 s.select_ranges(new_selections);
10725 });
10726
10727 this.request_autoscroll(Autoscroll::fit(), cx);
10728 });
10729 }
10730
10731 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10732 let Some(project) = self.project.clone() else {
10733 return;
10734 };
10735 self.reload(project, window, cx)
10736 .detach_and_notify_err(window, cx);
10737 }
10738
10739 pub fn restore_file(
10740 &mut self,
10741 _: &::git::RestoreFile,
10742 window: &mut Window,
10743 cx: &mut Context<Self>,
10744 ) {
10745 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10746 let mut buffer_ids = HashSet::default();
10747 let snapshot = self.buffer().read(cx).snapshot(cx);
10748 for selection in self.selections.all::<usize>(cx) {
10749 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10750 }
10751
10752 let buffer = self.buffer().read(cx);
10753 let ranges = buffer_ids
10754 .into_iter()
10755 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10756 .collect::<Vec<_>>();
10757
10758 self.restore_hunks_in_ranges(ranges, window, cx);
10759 }
10760
10761 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10762 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10763 let selections = self
10764 .selections
10765 .all(cx)
10766 .into_iter()
10767 .map(|s| s.range())
10768 .collect();
10769 self.restore_hunks_in_ranges(selections, window, cx);
10770 }
10771
10772 pub fn restore_hunks_in_ranges(
10773 &mut self,
10774 ranges: Vec<Range<Point>>,
10775 window: &mut Window,
10776 cx: &mut Context<Editor>,
10777 ) {
10778 let mut revert_changes = HashMap::default();
10779 let chunk_by = self
10780 .snapshot(window, cx)
10781 .hunks_for_ranges(ranges)
10782 .into_iter()
10783 .chunk_by(|hunk| hunk.buffer_id);
10784 for (buffer_id, hunks) in &chunk_by {
10785 let hunks = hunks.collect::<Vec<_>>();
10786 for hunk in &hunks {
10787 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10788 }
10789 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10790 }
10791 drop(chunk_by);
10792 if !revert_changes.is_empty() {
10793 self.transact(window, cx, |editor, window, cx| {
10794 editor.restore(revert_changes, window, cx);
10795 });
10796 }
10797 }
10798
10799 pub fn open_active_item_in_terminal(
10800 &mut self,
10801 _: &OpenInTerminal,
10802 window: &mut Window,
10803 cx: &mut Context<Self>,
10804 ) {
10805 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10806 let project_path = buffer.read(cx).project_path(cx)?;
10807 let project = self.project()?.read(cx);
10808 let entry = project.entry_for_path(&project_path, cx)?;
10809 let parent = match &entry.canonical_path {
10810 Some(canonical_path) => canonical_path.to_path_buf(),
10811 None => project.absolute_path(&project_path, cx)?,
10812 }
10813 .parent()?
10814 .to_path_buf();
10815 Some(parent)
10816 }) {
10817 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10818 }
10819 }
10820
10821 fn set_breakpoint_context_menu(
10822 &mut self,
10823 display_row: DisplayRow,
10824 position: Option<Anchor>,
10825 clicked_point: gpui::Point<Pixels>,
10826 window: &mut Window,
10827 cx: &mut Context<Self>,
10828 ) {
10829 let source = self
10830 .buffer
10831 .read(cx)
10832 .snapshot(cx)
10833 .anchor_before(Point::new(display_row.0, 0u32));
10834
10835 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10836
10837 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10838 self,
10839 source,
10840 clicked_point,
10841 context_menu,
10842 window,
10843 cx,
10844 );
10845 }
10846
10847 fn add_edit_breakpoint_block(
10848 &mut self,
10849 anchor: Anchor,
10850 breakpoint: &Breakpoint,
10851 edit_action: BreakpointPromptEditAction,
10852 window: &mut Window,
10853 cx: &mut Context<Self>,
10854 ) {
10855 let weak_editor = cx.weak_entity();
10856 let bp_prompt = cx.new(|cx| {
10857 BreakpointPromptEditor::new(
10858 weak_editor,
10859 anchor,
10860 breakpoint.clone(),
10861 edit_action,
10862 window,
10863 cx,
10864 )
10865 });
10866
10867 let height = bp_prompt.update(cx, |this, cx| {
10868 this.prompt
10869 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10870 });
10871 let cloned_prompt = bp_prompt.clone();
10872 let blocks = vec![BlockProperties {
10873 style: BlockStyle::Sticky,
10874 placement: BlockPlacement::Above(anchor),
10875 height: Some(height),
10876 render: Arc::new(move |cx| {
10877 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10878 cloned_prompt.clone().into_any_element()
10879 }),
10880 priority: 0,
10881 }];
10882
10883 let focus_handle = bp_prompt.focus_handle(cx);
10884 window.focus(&focus_handle);
10885
10886 let block_ids = self.insert_blocks(blocks, None, cx);
10887 bp_prompt.update(cx, |prompt, _| {
10888 prompt.add_block_ids(block_ids);
10889 });
10890 }
10891
10892 pub(crate) fn breakpoint_at_row(
10893 &self,
10894 row: u32,
10895 window: &mut Window,
10896 cx: &mut Context<Self>,
10897 ) -> Option<(Anchor, Breakpoint)> {
10898 let snapshot = self.snapshot(window, cx);
10899 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10900
10901 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10902 }
10903
10904 pub(crate) fn breakpoint_at_anchor(
10905 &self,
10906 breakpoint_position: Anchor,
10907 snapshot: &EditorSnapshot,
10908 cx: &mut Context<Self>,
10909 ) -> Option<(Anchor, Breakpoint)> {
10910 let buffer = self
10911 .buffer
10912 .read(cx)
10913 .buffer_for_anchor(breakpoint_position, cx)?;
10914
10915 let enclosing_excerpt = breakpoint_position.excerpt_id;
10916 let buffer_snapshot = buffer.read(cx).snapshot();
10917
10918 let row = buffer_snapshot
10919 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10920 .row;
10921
10922 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10923 let anchor_end = snapshot
10924 .buffer_snapshot()
10925 .anchor_after(Point::new(row, line_len));
10926
10927 self.breakpoint_store
10928 .as_ref()?
10929 .read_with(cx, |breakpoint_store, cx| {
10930 breakpoint_store
10931 .breakpoints(
10932 &buffer,
10933 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10934 &buffer_snapshot,
10935 cx,
10936 )
10937 .next()
10938 .and_then(|(bp, _)| {
10939 let breakpoint_row = buffer_snapshot
10940 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10941 .row;
10942
10943 if breakpoint_row == row {
10944 snapshot
10945 .buffer_snapshot()
10946 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10947 .map(|position| (position, bp.bp.clone()))
10948 } else {
10949 None
10950 }
10951 })
10952 })
10953 }
10954
10955 pub fn edit_log_breakpoint(
10956 &mut self,
10957 _: &EditLogBreakpoint,
10958 window: &mut Window,
10959 cx: &mut Context<Self>,
10960 ) {
10961 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10962 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10963 message: None,
10964 state: BreakpointState::Enabled,
10965 condition: None,
10966 hit_condition: None,
10967 });
10968
10969 self.add_edit_breakpoint_block(
10970 anchor,
10971 &breakpoint,
10972 BreakpointPromptEditAction::Log,
10973 window,
10974 cx,
10975 );
10976 }
10977 }
10978
10979 fn breakpoints_at_cursors(
10980 &self,
10981 window: &mut Window,
10982 cx: &mut Context<Self>,
10983 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10984 let snapshot = self.snapshot(window, cx);
10985 let cursors = self
10986 .selections
10987 .disjoint_anchors_arc()
10988 .iter()
10989 .map(|selection| {
10990 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10991
10992 let breakpoint_position = self
10993 .breakpoint_at_row(cursor_position.row, window, cx)
10994 .map(|bp| bp.0)
10995 .unwrap_or_else(|| {
10996 snapshot
10997 .display_snapshot
10998 .buffer_snapshot()
10999 .anchor_after(Point::new(cursor_position.row, 0))
11000 });
11001
11002 let breakpoint = self
11003 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11004 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11005
11006 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11007 })
11008 // 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.
11009 .collect::<HashMap<Anchor, _>>();
11010
11011 cursors.into_iter().collect()
11012 }
11013
11014 pub fn enable_breakpoint(
11015 &mut self,
11016 _: &crate::actions::EnableBreakpoint,
11017 window: &mut Window,
11018 cx: &mut Context<Self>,
11019 ) {
11020 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11021 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11022 continue;
11023 };
11024 self.edit_breakpoint_at_anchor(
11025 anchor,
11026 breakpoint,
11027 BreakpointEditAction::InvertState,
11028 cx,
11029 );
11030 }
11031 }
11032
11033 pub fn disable_breakpoint(
11034 &mut self,
11035 _: &crate::actions::DisableBreakpoint,
11036 window: &mut Window,
11037 cx: &mut Context<Self>,
11038 ) {
11039 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11040 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11041 continue;
11042 };
11043 self.edit_breakpoint_at_anchor(
11044 anchor,
11045 breakpoint,
11046 BreakpointEditAction::InvertState,
11047 cx,
11048 );
11049 }
11050 }
11051
11052 pub fn toggle_breakpoint(
11053 &mut self,
11054 _: &crate::actions::ToggleBreakpoint,
11055 window: &mut Window,
11056 cx: &mut Context<Self>,
11057 ) {
11058 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11059 if let Some(breakpoint) = breakpoint {
11060 self.edit_breakpoint_at_anchor(
11061 anchor,
11062 breakpoint,
11063 BreakpointEditAction::Toggle,
11064 cx,
11065 );
11066 } else {
11067 self.edit_breakpoint_at_anchor(
11068 anchor,
11069 Breakpoint::new_standard(),
11070 BreakpointEditAction::Toggle,
11071 cx,
11072 );
11073 }
11074 }
11075 }
11076
11077 pub fn edit_breakpoint_at_anchor(
11078 &mut self,
11079 breakpoint_position: Anchor,
11080 breakpoint: Breakpoint,
11081 edit_action: BreakpointEditAction,
11082 cx: &mut Context<Self>,
11083 ) {
11084 let Some(breakpoint_store) = &self.breakpoint_store else {
11085 return;
11086 };
11087
11088 let Some(buffer) = self
11089 .buffer
11090 .read(cx)
11091 .buffer_for_anchor(breakpoint_position, cx)
11092 else {
11093 return;
11094 };
11095
11096 breakpoint_store.update(cx, |breakpoint_store, cx| {
11097 breakpoint_store.toggle_breakpoint(
11098 buffer,
11099 BreakpointWithPosition {
11100 position: breakpoint_position.text_anchor,
11101 bp: breakpoint,
11102 },
11103 edit_action,
11104 cx,
11105 );
11106 });
11107
11108 cx.notify();
11109 }
11110
11111 #[cfg(any(test, feature = "test-support"))]
11112 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11113 self.breakpoint_store.clone()
11114 }
11115
11116 pub fn prepare_restore_change(
11117 &self,
11118 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11119 hunk: &MultiBufferDiffHunk,
11120 cx: &mut App,
11121 ) -> Option<()> {
11122 if hunk.is_created_file() {
11123 return None;
11124 }
11125 let buffer = self.buffer.read(cx);
11126 let diff = buffer.diff_for(hunk.buffer_id)?;
11127 let buffer = buffer.buffer(hunk.buffer_id)?;
11128 let buffer = buffer.read(cx);
11129 let original_text = diff
11130 .read(cx)
11131 .base_text()
11132 .as_rope()
11133 .slice(hunk.diff_base_byte_range.clone());
11134 let buffer_snapshot = buffer.snapshot();
11135 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11136 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11137 probe
11138 .0
11139 .start
11140 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11141 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11142 }) {
11143 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11144 Some(())
11145 } else {
11146 None
11147 }
11148 }
11149
11150 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11151 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11152 }
11153
11154 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11155 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11156 }
11157
11158 fn manipulate_lines<M>(
11159 &mut self,
11160 window: &mut Window,
11161 cx: &mut Context<Self>,
11162 mut manipulate: M,
11163 ) where
11164 M: FnMut(&str) -> LineManipulationResult,
11165 {
11166 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11167
11168 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11169 let buffer = self.buffer.read(cx).snapshot(cx);
11170
11171 let mut edits = Vec::new();
11172
11173 let selections = self.selections.all::<Point>(cx);
11174 let mut selections = selections.iter().peekable();
11175 let mut contiguous_row_selections = Vec::new();
11176 let mut new_selections = Vec::new();
11177 let mut added_lines = 0;
11178 let mut removed_lines = 0;
11179
11180 while let Some(selection) = selections.next() {
11181 let (start_row, end_row) = consume_contiguous_rows(
11182 &mut contiguous_row_selections,
11183 selection,
11184 &display_map,
11185 &mut selections,
11186 );
11187
11188 let start_point = Point::new(start_row.0, 0);
11189 let end_point = Point::new(
11190 end_row.previous_row().0,
11191 buffer.line_len(end_row.previous_row()),
11192 );
11193 let text = buffer
11194 .text_for_range(start_point..end_point)
11195 .collect::<String>();
11196
11197 let LineManipulationResult {
11198 new_text,
11199 line_count_before,
11200 line_count_after,
11201 } = manipulate(&text);
11202
11203 edits.push((start_point..end_point, new_text));
11204
11205 // Selections must change based on added and removed line count
11206 let start_row =
11207 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11208 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11209 new_selections.push(Selection {
11210 id: selection.id,
11211 start: start_row,
11212 end: end_row,
11213 goal: SelectionGoal::None,
11214 reversed: selection.reversed,
11215 });
11216
11217 if line_count_after > line_count_before {
11218 added_lines += line_count_after - line_count_before;
11219 } else if line_count_before > line_count_after {
11220 removed_lines += line_count_before - line_count_after;
11221 }
11222 }
11223
11224 self.transact(window, cx, |this, window, cx| {
11225 let buffer = this.buffer.update(cx, |buffer, cx| {
11226 buffer.edit(edits, None, cx);
11227 buffer.snapshot(cx)
11228 });
11229
11230 // Recalculate offsets on newly edited buffer
11231 let new_selections = new_selections
11232 .iter()
11233 .map(|s| {
11234 let start_point = Point::new(s.start.0, 0);
11235 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11236 Selection {
11237 id: s.id,
11238 start: buffer.point_to_offset(start_point),
11239 end: buffer.point_to_offset(end_point),
11240 goal: s.goal,
11241 reversed: s.reversed,
11242 }
11243 })
11244 .collect();
11245
11246 this.change_selections(Default::default(), window, cx, |s| {
11247 s.select(new_selections);
11248 });
11249
11250 this.request_autoscroll(Autoscroll::fit(), cx);
11251 });
11252 }
11253
11254 fn manipulate_immutable_lines<Fn>(
11255 &mut self,
11256 window: &mut Window,
11257 cx: &mut Context<Self>,
11258 mut callback: Fn,
11259 ) where
11260 Fn: FnMut(&mut Vec<&str>),
11261 {
11262 self.manipulate_lines(window, cx, |text| {
11263 let mut lines: Vec<&str> = text.split('\n').collect();
11264 let line_count_before = lines.len();
11265
11266 callback(&mut lines);
11267
11268 LineManipulationResult {
11269 new_text: lines.join("\n"),
11270 line_count_before,
11271 line_count_after: lines.len(),
11272 }
11273 });
11274 }
11275
11276 fn manipulate_mutable_lines<Fn>(
11277 &mut self,
11278 window: &mut Window,
11279 cx: &mut Context<Self>,
11280 mut callback: Fn,
11281 ) where
11282 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11283 {
11284 self.manipulate_lines(window, cx, |text| {
11285 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11286 let line_count_before = lines.len();
11287
11288 callback(&mut lines);
11289
11290 LineManipulationResult {
11291 new_text: lines.join("\n"),
11292 line_count_before,
11293 line_count_after: lines.len(),
11294 }
11295 });
11296 }
11297
11298 pub fn convert_indentation_to_spaces(
11299 &mut self,
11300 _: &ConvertIndentationToSpaces,
11301 window: &mut Window,
11302 cx: &mut Context<Self>,
11303 ) {
11304 let settings = self.buffer.read(cx).language_settings(cx);
11305 let tab_size = settings.tab_size.get() as usize;
11306
11307 self.manipulate_mutable_lines(window, cx, |lines| {
11308 // Allocates a reasonably sized scratch buffer once for the whole loop
11309 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11310 // Avoids recomputing spaces that could be inserted many times
11311 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11312 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11313 .collect();
11314
11315 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11316 let mut chars = line.as_ref().chars();
11317 let mut col = 0;
11318 let mut changed = false;
11319
11320 for ch in chars.by_ref() {
11321 match ch {
11322 ' ' => {
11323 reindented_line.push(' ');
11324 col += 1;
11325 }
11326 '\t' => {
11327 // \t are converted to spaces depending on the current column
11328 let spaces_len = tab_size - (col % tab_size);
11329 reindented_line.extend(&space_cache[spaces_len - 1]);
11330 col += spaces_len;
11331 changed = true;
11332 }
11333 _ => {
11334 // If we dont append before break, the character is consumed
11335 reindented_line.push(ch);
11336 break;
11337 }
11338 }
11339 }
11340
11341 if !changed {
11342 reindented_line.clear();
11343 continue;
11344 }
11345 // Append the rest of the line and replace old reference with new one
11346 reindented_line.extend(chars);
11347 *line = Cow::Owned(reindented_line.clone());
11348 reindented_line.clear();
11349 }
11350 });
11351 }
11352
11353 pub fn convert_indentation_to_tabs(
11354 &mut self,
11355 _: &ConvertIndentationToTabs,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 let settings = self.buffer.read(cx).language_settings(cx);
11360 let tab_size = settings.tab_size.get() as usize;
11361
11362 self.manipulate_mutable_lines(window, cx, |lines| {
11363 // Allocates a reasonably sized buffer once for the whole loop
11364 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11365 // Avoids recomputing spaces that could be inserted many times
11366 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11367 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11368 .collect();
11369
11370 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11371 let mut chars = line.chars();
11372 let mut spaces_count = 0;
11373 let mut first_non_indent_char = None;
11374 let mut changed = false;
11375
11376 for ch in chars.by_ref() {
11377 match ch {
11378 ' ' => {
11379 // Keep track of spaces. Append \t when we reach tab_size
11380 spaces_count += 1;
11381 changed = true;
11382 if spaces_count == tab_size {
11383 reindented_line.push('\t');
11384 spaces_count = 0;
11385 }
11386 }
11387 '\t' => {
11388 reindented_line.push('\t');
11389 spaces_count = 0;
11390 }
11391 _ => {
11392 // Dont append it yet, we might have remaining spaces
11393 first_non_indent_char = Some(ch);
11394 break;
11395 }
11396 }
11397 }
11398
11399 if !changed {
11400 reindented_line.clear();
11401 continue;
11402 }
11403 // Remaining spaces that didn't make a full tab stop
11404 if spaces_count > 0 {
11405 reindented_line.extend(&space_cache[spaces_count - 1]);
11406 }
11407 // If we consume an extra character that was not indentation, add it back
11408 if let Some(extra_char) = first_non_indent_char {
11409 reindented_line.push(extra_char);
11410 }
11411 // Append the rest of the line and replace old reference with new one
11412 reindented_line.extend(chars);
11413 *line = Cow::Owned(reindented_line.clone());
11414 reindented_line.clear();
11415 }
11416 });
11417 }
11418
11419 pub fn convert_to_upper_case(
11420 &mut self,
11421 _: &ConvertToUpperCase,
11422 window: &mut Window,
11423 cx: &mut Context<Self>,
11424 ) {
11425 self.manipulate_text(window, cx, |text| text.to_uppercase())
11426 }
11427
11428 pub fn convert_to_lower_case(
11429 &mut self,
11430 _: &ConvertToLowerCase,
11431 window: &mut Window,
11432 cx: &mut Context<Self>,
11433 ) {
11434 self.manipulate_text(window, cx, |text| text.to_lowercase())
11435 }
11436
11437 pub fn convert_to_title_case(
11438 &mut self,
11439 _: &ConvertToTitleCase,
11440 window: &mut Window,
11441 cx: &mut Context<Self>,
11442 ) {
11443 self.manipulate_text(window, cx, |text| {
11444 text.split('\n')
11445 .map(|line| line.to_case(Case::Title))
11446 .join("\n")
11447 })
11448 }
11449
11450 pub fn convert_to_snake_case(
11451 &mut self,
11452 _: &ConvertToSnakeCase,
11453 window: &mut Window,
11454 cx: &mut Context<Self>,
11455 ) {
11456 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11457 }
11458
11459 pub fn convert_to_kebab_case(
11460 &mut self,
11461 _: &ConvertToKebabCase,
11462 window: &mut Window,
11463 cx: &mut Context<Self>,
11464 ) {
11465 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11466 }
11467
11468 pub fn convert_to_upper_camel_case(
11469 &mut self,
11470 _: &ConvertToUpperCamelCase,
11471 window: &mut Window,
11472 cx: &mut Context<Self>,
11473 ) {
11474 self.manipulate_text(window, cx, |text| {
11475 text.split('\n')
11476 .map(|line| line.to_case(Case::UpperCamel))
11477 .join("\n")
11478 })
11479 }
11480
11481 pub fn convert_to_lower_camel_case(
11482 &mut self,
11483 _: &ConvertToLowerCamelCase,
11484 window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11488 }
11489
11490 pub fn convert_to_opposite_case(
11491 &mut self,
11492 _: &ConvertToOppositeCase,
11493 window: &mut Window,
11494 cx: &mut Context<Self>,
11495 ) {
11496 self.manipulate_text(window, cx, |text| {
11497 text.chars()
11498 .fold(String::with_capacity(text.len()), |mut t, c| {
11499 if c.is_uppercase() {
11500 t.extend(c.to_lowercase());
11501 } else {
11502 t.extend(c.to_uppercase());
11503 }
11504 t
11505 })
11506 })
11507 }
11508
11509 pub fn convert_to_sentence_case(
11510 &mut self,
11511 _: &ConvertToSentenceCase,
11512 window: &mut Window,
11513 cx: &mut Context<Self>,
11514 ) {
11515 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11516 }
11517
11518 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11519 self.manipulate_text(window, cx, |text| {
11520 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11521 if has_upper_case_characters {
11522 text.to_lowercase()
11523 } else {
11524 text.to_uppercase()
11525 }
11526 })
11527 }
11528
11529 pub fn convert_to_rot13(
11530 &mut self,
11531 _: &ConvertToRot13,
11532 window: &mut Window,
11533 cx: &mut Context<Self>,
11534 ) {
11535 self.manipulate_text(window, cx, |text| {
11536 text.chars()
11537 .map(|c| match c {
11538 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11539 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11540 _ => c,
11541 })
11542 .collect()
11543 })
11544 }
11545
11546 pub fn convert_to_rot47(
11547 &mut self,
11548 _: &ConvertToRot47,
11549 window: &mut Window,
11550 cx: &mut Context<Self>,
11551 ) {
11552 self.manipulate_text(window, cx, |text| {
11553 text.chars()
11554 .map(|c| {
11555 let code_point = c as u32;
11556 if code_point >= 33 && code_point <= 126 {
11557 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11558 }
11559 c
11560 })
11561 .collect()
11562 })
11563 }
11564
11565 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11566 where
11567 Fn: FnMut(&str) -> String,
11568 {
11569 let buffer = self.buffer.read(cx).snapshot(cx);
11570
11571 let mut new_selections = Vec::new();
11572 let mut edits = Vec::new();
11573 let mut selection_adjustment = 0i32;
11574
11575 for selection in self.selections.all_adjusted(cx) {
11576 let selection_is_empty = selection.is_empty();
11577
11578 let (start, end) = if selection_is_empty {
11579 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11580 (word_range.start, word_range.end)
11581 } else {
11582 (
11583 buffer.point_to_offset(selection.start),
11584 buffer.point_to_offset(selection.end),
11585 )
11586 };
11587
11588 let text = buffer.text_for_range(start..end).collect::<String>();
11589 let old_length = text.len() as i32;
11590 let text = callback(&text);
11591
11592 new_selections.push(Selection {
11593 start: (start as i32 - selection_adjustment) as usize,
11594 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11595 goal: SelectionGoal::None,
11596 id: selection.id,
11597 reversed: selection.reversed,
11598 });
11599
11600 selection_adjustment += old_length - text.len() as i32;
11601
11602 edits.push((start..end, text));
11603 }
11604
11605 self.transact(window, cx, |this, window, cx| {
11606 this.buffer.update(cx, |buffer, cx| {
11607 buffer.edit(edits, None, cx);
11608 });
11609
11610 this.change_selections(Default::default(), window, cx, |s| {
11611 s.select(new_selections);
11612 });
11613
11614 this.request_autoscroll(Autoscroll::fit(), cx);
11615 });
11616 }
11617
11618 pub fn move_selection_on_drop(
11619 &mut self,
11620 selection: &Selection<Anchor>,
11621 target: DisplayPoint,
11622 is_cut: bool,
11623 window: &mut Window,
11624 cx: &mut Context<Self>,
11625 ) {
11626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11627 let buffer = display_map.buffer_snapshot();
11628 let mut edits = Vec::new();
11629 let insert_point = display_map
11630 .clip_point(target, Bias::Left)
11631 .to_point(&display_map);
11632 let text = buffer
11633 .text_for_range(selection.start..selection.end)
11634 .collect::<String>();
11635 if is_cut {
11636 edits.push(((selection.start..selection.end), String::new()));
11637 }
11638 let insert_anchor = buffer.anchor_before(insert_point);
11639 edits.push(((insert_anchor..insert_anchor), text));
11640 let last_edit_start = insert_anchor.bias_left(buffer);
11641 let last_edit_end = insert_anchor.bias_right(buffer);
11642 self.transact(window, cx, |this, window, cx| {
11643 this.buffer.update(cx, |buffer, cx| {
11644 buffer.edit(edits, None, cx);
11645 });
11646 this.change_selections(Default::default(), window, cx, |s| {
11647 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11648 });
11649 });
11650 }
11651
11652 pub fn clear_selection_drag_state(&mut self) {
11653 self.selection_drag_state = SelectionDragState::None;
11654 }
11655
11656 pub fn duplicate(
11657 &mut self,
11658 upwards: bool,
11659 whole_lines: bool,
11660 window: &mut Window,
11661 cx: &mut Context<Self>,
11662 ) {
11663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11664
11665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11666 let buffer = display_map.buffer_snapshot();
11667 let selections = self.selections.all::<Point>(cx);
11668
11669 let mut edits = Vec::new();
11670 let mut selections_iter = selections.iter().peekable();
11671 while let Some(selection) = selections_iter.next() {
11672 let mut rows = selection.spanned_rows(false, &display_map);
11673 // duplicate line-wise
11674 if whole_lines || selection.start == selection.end {
11675 // Avoid duplicating the same lines twice.
11676 while let Some(next_selection) = selections_iter.peek() {
11677 let next_rows = next_selection.spanned_rows(false, &display_map);
11678 if next_rows.start < rows.end {
11679 rows.end = next_rows.end;
11680 selections_iter.next().unwrap();
11681 } else {
11682 break;
11683 }
11684 }
11685
11686 // Copy the text from the selected row region and splice it either at the start
11687 // or end of the region.
11688 let start = Point::new(rows.start.0, 0);
11689 let end = Point::new(
11690 rows.end.previous_row().0,
11691 buffer.line_len(rows.end.previous_row()),
11692 );
11693 let text = buffer
11694 .text_for_range(start..end)
11695 .chain(Some("\n"))
11696 .collect::<String>();
11697 let insert_location = if upwards {
11698 Point::new(rows.end.0, 0)
11699 } else {
11700 start
11701 };
11702 edits.push((insert_location..insert_location, text));
11703 } else {
11704 // duplicate character-wise
11705 let start = selection.start;
11706 let end = selection.end;
11707 let text = buffer.text_for_range(start..end).collect::<String>();
11708 edits.push((selection.end..selection.end, text));
11709 }
11710 }
11711
11712 self.transact(window, cx, |this, _, cx| {
11713 this.buffer.update(cx, |buffer, cx| {
11714 buffer.edit(edits, None, cx);
11715 });
11716
11717 this.request_autoscroll(Autoscroll::fit(), cx);
11718 });
11719 }
11720
11721 pub fn duplicate_line_up(
11722 &mut self,
11723 _: &DuplicateLineUp,
11724 window: &mut Window,
11725 cx: &mut Context<Self>,
11726 ) {
11727 self.duplicate(true, true, window, cx);
11728 }
11729
11730 pub fn duplicate_line_down(
11731 &mut self,
11732 _: &DuplicateLineDown,
11733 window: &mut Window,
11734 cx: &mut Context<Self>,
11735 ) {
11736 self.duplicate(false, true, window, cx);
11737 }
11738
11739 pub fn duplicate_selection(
11740 &mut self,
11741 _: &DuplicateSelection,
11742 window: &mut Window,
11743 cx: &mut Context<Self>,
11744 ) {
11745 self.duplicate(false, false, window, cx);
11746 }
11747
11748 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11750 if self.mode.is_single_line() {
11751 cx.propagate();
11752 return;
11753 }
11754
11755 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11756 let buffer = self.buffer.read(cx).snapshot(cx);
11757
11758 let mut edits = Vec::new();
11759 let mut unfold_ranges = Vec::new();
11760 let mut refold_creases = Vec::new();
11761
11762 let selections = self.selections.all::<Point>(cx);
11763 let mut selections = selections.iter().peekable();
11764 let mut contiguous_row_selections = Vec::new();
11765 let mut new_selections = Vec::new();
11766
11767 while let Some(selection) = selections.next() {
11768 // Find all the selections that span a contiguous row range
11769 let (start_row, end_row) = consume_contiguous_rows(
11770 &mut contiguous_row_selections,
11771 selection,
11772 &display_map,
11773 &mut selections,
11774 );
11775
11776 // Move the text spanned by the row range to be before the line preceding the row range
11777 if start_row.0 > 0 {
11778 let range_to_move = Point::new(
11779 start_row.previous_row().0,
11780 buffer.line_len(start_row.previous_row()),
11781 )
11782 ..Point::new(
11783 end_row.previous_row().0,
11784 buffer.line_len(end_row.previous_row()),
11785 );
11786 let insertion_point = display_map
11787 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11788 .0;
11789
11790 // Don't move lines across excerpts
11791 if buffer
11792 .excerpt_containing(insertion_point..range_to_move.end)
11793 .is_some()
11794 {
11795 let text = buffer
11796 .text_for_range(range_to_move.clone())
11797 .flat_map(|s| s.chars())
11798 .skip(1)
11799 .chain(['\n'])
11800 .collect::<String>();
11801
11802 edits.push((
11803 buffer.anchor_after(range_to_move.start)
11804 ..buffer.anchor_before(range_to_move.end),
11805 String::new(),
11806 ));
11807 let insertion_anchor = buffer.anchor_after(insertion_point);
11808 edits.push((insertion_anchor..insertion_anchor, text));
11809
11810 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11811
11812 // Move selections up
11813 new_selections.extend(contiguous_row_selections.drain(..).map(
11814 |mut selection| {
11815 selection.start.row -= row_delta;
11816 selection.end.row -= row_delta;
11817 selection
11818 },
11819 ));
11820
11821 // Move folds up
11822 unfold_ranges.push(range_to_move.clone());
11823 for fold in display_map.folds_in_range(
11824 buffer.anchor_before(range_to_move.start)
11825 ..buffer.anchor_after(range_to_move.end),
11826 ) {
11827 let mut start = fold.range.start.to_point(&buffer);
11828 let mut end = fold.range.end.to_point(&buffer);
11829 start.row -= row_delta;
11830 end.row -= row_delta;
11831 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11832 }
11833 }
11834 }
11835
11836 // If we didn't move line(s), preserve the existing selections
11837 new_selections.append(&mut contiguous_row_selections);
11838 }
11839
11840 self.transact(window, cx, |this, window, cx| {
11841 this.unfold_ranges(&unfold_ranges, true, true, cx);
11842 this.buffer.update(cx, |buffer, cx| {
11843 for (range, text) in edits {
11844 buffer.edit([(range, text)], None, cx);
11845 }
11846 });
11847 this.fold_creases(refold_creases, true, window, cx);
11848 this.change_selections(Default::default(), window, cx, |s| {
11849 s.select(new_selections);
11850 })
11851 });
11852 }
11853
11854 pub fn move_line_down(
11855 &mut self,
11856 _: &MoveLineDown,
11857 window: &mut Window,
11858 cx: &mut Context<Self>,
11859 ) {
11860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11861 if self.mode.is_single_line() {
11862 cx.propagate();
11863 return;
11864 }
11865
11866 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11867 let buffer = self.buffer.read(cx).snapshot(cx);
11868
11869 let mut edits = Vec::new();
11870 let mut unfold_ranges = Vec::new();
11871 let mut refold_creases = Vec::new();
11872
11873 let selections = self.selections.all::<Point>(cx);
11874 let mut selections = selections.iter().peekable();
11875 let mut contiguous_row_selections = Vec::new();
11876 let mut new_selections = Vec::new();
11877
11878 while let Some(selection) = selections.next() {
11879 // Find all the selections that span a contiguous row range
11880 let (start_row, end_row) = consume_contiguous_rows(
11881 &mut contiguous_row_selections,
11882 selection,
11883 &display_map,
11884 &mut selections,
11885 );
11886
11887 // Move the text spanned by the row range to be after the last line of the row range
11888 if end_row.0 <= buffer.max_point().row {
11889 let range_to_move =
11890 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11891 let insertion_point = display_map
11892 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11893 .0;
11894
11895 // Don't move lines across excerpt boundaries
11896 if buffer
11897 .excerpt_containing(range_to_move.start..insertion_point)
11898 .is_some()
11899 {
11900 let mut text = String::from("\n");
11901 text.extend(buffer.text_for_range(range_to_move.clone()));
11902 text.pop(); // Drop trailing newline
11903 edits.push((
11904 buffer.anchor_after(range_to_move.start)
11905 ..buffer.anchor_before(range_to_move.end),
11906 String::new(),
11907 ));
11908 let insertion_anchor = buffer.anchor_after(insertion_point);
11909 edits.push((insertion_anchor..insertion_anchor, text));
11910
11911 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11912
11913 // Move selections down
11914 new_selections.extend(contiguous_row_selections.drain(..).map(
11915 |mut selection| {
11916 selection.start.row += row_delta;
11917 selection.end.row += row_delta;
11918 selection
11919 },
11920 ));
11921
11922 // Move folds down
11923 unfold_ranges.push(range_to_move.clone());
11924 for fold in display_map.folds_in_range(
11925 buffer.anchor_before(range_to_move.start)
11926 ..buffer.anchor_after(range_to_move.end),
11927 ) {
11928 let mut start = fold.range.start.to_point(&buffer);
11929 let mut end = fold.range.end.to_point(&buffer);
11930 start.row += row_delta;
11931 end.row += row_delta;
11932 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11933 }
11934 }
11935 }
11936
11937 // If we didn't move line(s), preserve the existing selections
11938 new_selections.append(&mut contiguous_row_selections);
11939 }
11940
11941 self.transact(window, cx, |this, window, cx| {
11942 this.unfold_ranges(&unfold_ranges, true, true, cx);
11943 this.buffer.update(cx, |buffer, cx| {
11944 for (range, text) in edits {
11945 buffer.edit([(range, text)], None, cx);
11946 }
11947 });
11948 this.fold_creases(refold_creases, true, window, cx);
11949 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11950 });
11951 }
11952
11953 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11955 let text_layout_details = &self.text_layout_details(window);
11956 self.transact(window, cx, |this, window, cx| {
11957 let edits = this.change_selections(Default::default(), window, cx, |s| {
11958 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11959 s.move_with(|display_map, selection| {
11960 if !selection.is_empty() {
11961 return;
11962 }
11963
11964 let mut head = selection.head();
11965 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11966 if head.column() == display_map.line_len(head.row()) {
11967 transpose_offset = display_map
11968 .buffer_snapshot()
11969 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11970 }
11971
11972 if transpose_offset == 0 {
11973 return;
11974 }
11975
11976 *head.column_mut() += 1;
11977 head = display_map.clip_point(head, Bias::Right);
11978 let goal = SelectionGoal::HorizontalPosition(
11979 display_map
11980 .x_for_display_point(head, text_layout_details)
11981 .into(),
11982 );
11983 selection.collapse_to(head, goal);
11984
11985 let transpose_start = display_map
11986 .buffer_snapshot()
11987 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11988 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11989 let transpose_end = display_map
11990 .buffer_snapshot()
11991 .clip_offset(transpose_offset + 1, Bias::Right);
11992 if let Some(ch) = display_map
11993 .buffer_snapshot()
11994 .chars_at(transpose_start)
11995 .next()
11996 {
11997 edits.push((transpose_start..transpose_offset, String::new()));
11998 edits.push((transpose_end..transpose_end, ch.to_string()));
11999 }
12000 }
12001 });
12002 edits
12003 });
12004 this.buffer
12005 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12006 let selections = this.selections.all::<usize>(cx);
12007 this.change_selections(Default::default(), window, cx, |s| {
12008 s.select(selections);
12009 });
12010 });
12011 }
12012
12013 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12014 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12015 if self.mode.is_single_line() {
12016 cx.propagate();
12017 return;
12018 }
12019
12020 self.rewrap_impl(RewrapOptions::default(), cx)
12021 }
12022
12023 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12024 let buffer = self.buffer.read(cx).snapshot(cx);
12025 let selections = self.selections.all::<Point>(cx);
12026
12027 #[derive(Clone, Debug, PartialEq)]
12028 enum CommentFormat {
12029 /// single line comment, with prefix for line
12030 Line(String),
12031 /// single line within a block comment, with prefix for line
12032 BlockLine(String),
12033 /// a single line of a block comment that includes the initial delimiter
12034 BlockCommentWithStart(BlockCommentConfig),
12035 /// a single line of a block comment that includes the ending delimiter
12036 BlockCommentWithEnd(BlockCommentConfig),
12037 }
12038
12039 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12040 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12041 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12042 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12043 .peekable();
12044
12045 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12046 row
12047 } else {
12048 return Vec::new();
12049 };
12050
12051 let language_settings = buffer.language_settings_at(selection.head(), cx);
12052 let language_scope = buffer.language_scope_at(selection.head());
12053
12054 let indent_and_prefix_for_row =
12055 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12056 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12057 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12058 &language_scope
12059 {
12060 let indent_end = Point::new(row, indent.len);
12061 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12062 let line_text_after_indent = buffer
12063 .text_for_range(indent_end..line_end)
12064 .collect::<String>();
12065
12066 let is_within_comment_override = buffer
12067 .language_scope_at(indent_end)
12068 .is_some_and(|scope| scope.override_name() == Some("comment"));
12069 let comment_delimiters = if is_within_comment_override {
12070 // we are within a comment syntax node, but we don't
12071 // yet know what kind of comment: block, doc or line
12072 match (
12073 language_scope.documentation_comment(),
12074 language_scope.block_comment(),
12075 ) {
12076 (Some(config), _) | (_, Some(config))
12077 if buffer.contains_str_at(indent_end, &config.start) =>
12078 {
12079 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12080 }
12081 (Some(config), _) | (_, Some(config))
12082 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12083 {
12084 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12085 }
12086 (Some(config), _) | (_, Some(config))
12087 if buffer.contains_str_at(indent_end, &config.prefix) =>
12088 {
12089 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12090 }
12091 (_, _) => language_scope
12092 .line_comment_prefixes()
12093 .iter()
12094 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12095 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12096 }
12097 } else {
12098 // we not in an overridden comment node, but we may
12099 // be within a non-overridden line comment node
12100 language_scope
12101 .line_comment_prefixes()
12102 .iter()
12103 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12104 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12105 };
12106
12107 let rewrap_prefix = language_scope
12108 .rewrap_prefixes()
12109 .iter()
12110 .find_map(|prefix_regex| {
12111 prefix_regex.find(&line_text_after_indent).map(|mat| {
12112 if mat.start() == 0 {
12113 Some(mat.as_str().to_string())
12114 } else {
12115 None
12116 }
12117 })
12118 })
12119 .flatten();
12120 (comment_delimiters, rewrap_prefix)
12121 } else {
12122 (None, None)
12123 };
12124 (indent, comment_prefix, rewrap_prefix)
12125 };
12126
12127 let mut ranges = Vec::new();
12128 let from_empty_selection = selection.is_empty();
12129
12130 let mut current_range_start = first_row;
12131 let mut prev_row = first_row;
12132 let (
12133 mut current_range_indent,
12134 mut current_range_comment_delimiters,
12135 mut current_range_rewrap_prefix,
12136 ) = indent_and_prefix_for_row(first_row);
12137
12138 for row in non_blank_rows_iter.skip(1) {
12139 let has_paragraph_break = row > prev_row + 1;
12140
12141 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12142 indent_and_prefix_for_row(row);
12143
12144 let has_indent_change = row_indent != current_range_indent;
12145 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12146
12147 let has_boundary_change = has_comment_change
12148 || row_rewrap_prefix.is_some()
12149 || (has_indent_change && current_range_comment_delimiters.is_some());
12150
12151 if has_paragraph_break || has_boundary_change {
12152 ranges.push((
12153 language_settings.clone(),
12154 Point::new(current_range_start, 0)
12155 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12156 current_range_indent,
12157 current_range_comment_delimiters.clone(),
12158 current_range_rewrap_prefix.clone(),
12159 from_empty_selection,
12160 ));
12161 current_range_start = row;
12162 current_range_indent = row_indent;
12163 current_range_comment_delimiters = row_comment_delimiters;
12164 current_range_rewrap_prefix = row_rewrap_prefix;
12165 }
12166 prev_row = row;
12167 }
12168
12169 ranges.push((
12170 language_settings.clone(),
12171 Point::new(current_range_start, 0)
12172 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12173 current_range_indent,
12174 current_range_comment_delimiters,
12175 current_range_rewrap_prefix,
12176 from_empty_selection,
12177 ));
12178
12179 ranges
12180 });
12181
12182 let mut edits = Vec::new();
12183 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12184
12185 for (
12186 language_settings,
12187 wrap_range,
12188 mut indent_size,
12189 comment_prefix,
12190 rewrap_prefix,
12191 from_empty_selection,
12192 ) in wrap_ranges
12193 {
12194 let mut start_row = wrap_range.start.row;
12195 let mut end_row = wrap_range.end.row;
12196
12197 // Skip selections that overlap with a range that has already been rewrapped.
12198 let selection_range = start_row..end_row;
12199 if rewrapped_row_ranges
12200 .iter()
12201 .any(|range| range.overlaps(&selection_range))
12202 {
12203 continue;
12204 }
12205
12206 let tab_size = language_settings.tab_size;
12207
12208 let (line_prefix, inside_comment) = match &comment_prefix {
12209 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12210 (Some(prefix.as_str()), true)
12211 }
12212 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12213 (Some(prefix.as_ref()), true)
12214 }
12215 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12216 start: _,
12217 end: _,
12218 prefix,
12219 tab_size,
12220 })) => {
12221 indent_size.len += tab_size;
12222 (Some(prefix.as_ref()), true)
12223 }
12224 None => (None, false),
12225 };
12226 let indent_prefix = indent_size.chars().collect::<String>();
12227 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12228
12229 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12230 RewrapBehavior::InComments => inside_comment,
12231 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12232 RewrapBehavior::Anywhere => true,
12233 };
12234
12235 let should_rewrap = options.override_language_settings
12236 || allow_rewrap_based_on_language
12237 || self.hard_wrap.is_some();
12238 if !should_rewrap {
12239 continue;
12240 }
12241
12242 if from_empty_selection {
12243 'expand_upwards: while start_row > 0 {
12244 let prev_row = start_row - 1;
12245 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12246 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12247 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12248 {
12249 start_row = prev_row;
12250 } else {
12251 break 'expand_upwards;
12252 }
12253 }
12254
12255 'expand_downwards: while end_row < buffer.max_point().row {
12256 let next_row = end_row + 1;
12257 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12258 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12259 && !buffer.is_line_blank(MultiBufferRow(next_row))
12260 {
12261 end_row = next_row;
12262 } else {
12263 break 'expand_downwards;
12264 }
12265 }
12266 }
12267
12268 let start = Point::new(start_row, 0);
12269 let start_offset = ToOffset::to_offset(&start, &buffer);
12270 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12271 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12272 let mut first_line_delimiter = None;
12273 let mut last_line_delimiter = None;
12274 let Some(lines_without_prefixes) = selection_text
12275 .lines()
12276 .enumerate()
12277 .map(|(ix, line)| {
12278 let line_trimmed = line.trim_start();
12279 if rewrap_prefix.is_some() && ix > 0 {
12280 Ok(line_trimmed)
12281 } else if let Some(
12282 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12283 start,
12284 prefix,
12285 end,
12286 tab_size,
12287 })
12288 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12289 start,
12290 prefix,
12291 end,
12292 tab_size,
12293 }),
12294 ) = &comment_prefix
12295 {
12296 let line_trimmed = line_trimmed
12297 .strip_prefix(start.as_ref())
12298 .map(|s| {
12299 let mut indent_size = indent_size;
12300 indent_size.len -= tab_size;
12301 let indent_prefix: String = indent_size.chars().collect();
12302 first_line_delimiter = Some((indent_prefix, start));
12303 s.trim_start()
12304 })
12305 .unwrap_or(line_trimmed);
12306 let line_trimmed = line_trimmed
12307 .strip_suffix(end.as_ref())
12308 .map(|s| {
12309 last_line_delimiter = Some(end);
12310 s.trim_end()
12311 })
12312 .unwrap_or(line_trimmed);
12313 let line_trimmed = line_trimmed
12314 .strip_prefix(prefix.as_ref())
12315 .unwrap_or(line_trimmed);
12316 Ok(line_trimmed)
12317 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12318 line_trimmed.strip_prefix(prefix).with_context(|| {
12319 format!("line did not start with prefix {prefix:?}: {line:?}")
12320 })
12321 } else {
12322 line_trimmed
12323 .strip_prefix(&line_prefix.trim_start())
12324 .with_context(|| {
12325 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12326 })
12327 }
12328 })
12329 .collect::<Result<Vec<_>, _>>()
12330 .log_err()
12331 else {
12332 continue;
12333 };
12334
12335 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12336 buffer
12337 .language_settings_at(Point::new(start_row, 0), cx)
12338 .preferred_line_length as usize
12339 });
12340
12341 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12342 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12343 } else {
12344 line_prefix.clone()
12345 };
12346
12347 let wrapped_text = {
12348 let mut wrapped_text = wrap_with_prefix(
12349 line_prefix,
12350 subsequent_lines_prefix,
12351 lines_without_prefixes.join("\n"),
12352 wrap_column,
12353 tab_size,
12354 options.preserve_existing_whitespace,
12355 );
12356
12357 if let Some((indent, delimiter)) = first_line_delimiter {
12358 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12359 }
12360 if let Some(last_line) = last_line_delimiter {
12361 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12362 }
12363
12364 wrapped_text
12365 };
12366
12367 // TODO: should always use char-based diff while still supporting cursor behavior that
12368 // matches vim.
12369 let mut diff_options = DiffOptions::default();
12370 if options.override_language_settings {
12371 diff_options.max_word_diff_len = 0;
12372 diff_options.max_word_diff_line_count = 0;
12373 } else {
12374 diff_options.max_word_diff_len = usize::MAX;
12375 diff_options.max_word_diff_line_count = usize::MAX;
12376 }
12377
12378 for (old_range, new_text) in
12379 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12380 {
12381 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12382 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12383 edits.push((edit_start..edit_end, new_text));
12384 }
12385
12386 rewrapped_row_ranges.push(start_row..=end_row);
12387 }
12388
12389 self.buffer
12390 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12391 }
12392
12393 pub fn cut_common(
12394 &mut self,
12395 cut_no_selection_line: bool,
12396 window: &mut Window,
12397 cx: &mut Context<Self>,
12398 ) -> ClipboardItem {
12399 let mut text = String::new();
12400 let buffer = self.buffer.read(cx).snapshot(cx);
12401 let mut selections = self.selections.all::<Point>(cx);
12402 let mut clipboard_selections = Vec::with_capacity(selections.len());
12403 {
12404 let max_point = buffer.max_point();
12405 let mut is_first = true;
12406 for selection in &mut selections {
12407 let is_entire_line =
12408 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12409 if is_entire_line {
12410 selection.start = Point::new(selection.start.row, 0);
12411 if !selection.is_empty() && selection.end.column == 0 {
12412 selection.end = cmp::min(max_point, selection.end);
12413 } else {
12414 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12415 }
12416 selection.goal = SelectionGoal::None;
12417 }
12418 if is_first {
12419 is_first = false;
12420 } else {
12421 text += "\n";
12422 }
12423 let mut len = 0;
12424 for chunk in buffer.text_for_range(selection.start..selection.end) {
12425 text.push_str(chunk);
12426 len += chunk.len();
12427 }
12428 clipboard_selections.push(ClipboardSelection {
12429 len,
12430 is_entire_line,
12431 first_line_indent: buffer
12432 .indent_size_for_line(MultiBufferRow(selection.start.row))
12433 .len,
12434 });
12435 }
12436 }
12437
12438 self.transact(window, cx, |this, window, cx| {
12439 this.change_selections(Default::default(), window, cx, |s| {
12440 s.select(selections);
12441 });
12442 this.insert("", window, cx);
12443 });
12444 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12445 }
12446
12447 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12448 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12449 let item = self.cut_common(true, window, cx);
12450 cx.write_to_clipboard(item);
12451 }
12452
12453 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12454 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12455 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12456 s.move_with(|snapshot, sel| {
12457 if sel.is_empty() {
12458 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12459 }
12460 if sel.is_empty() {
12461 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12462 }
12463 });
12464 });
12465 let item = self.cut_common(false, window, cx);
12466 cx.set_global(KillRing(item))
12467 }
12468
12469 pub fn kill_ring_yank(
12470 &mut self,
12471 _: &KillRingYank,
12472 window: &mut Window,
12473 cx: &mut Context<Self>,
12474 ) {
12475 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12476 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12477 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12478 (kill_ring.text().to_string(), kill_ring.metadata_json())
12479 } else {
12480 return;
12481 }
12482 } else {
12483 return;
12484 };
12485 self.do_paste(&text, metadata, false, window, cx);
12486 }
12487
12488 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12489 self.do_copy(true, cx);
12490 }
12491
12492 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12493 self.do_copy(false, cx);
12494 }
12495
12496 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12497 let selections = self.selections.all::<Point>(cx);
12498 let buffer = self.buffer.read(cx).read(cx);
12499 let mut text = String::new();
12500
12501 let mut clipboard_selections = Vec::with_capacity(selections.len());
12502 {
12503 let max_point = buffer.max_point();
12504 let mut is_first = true;
12505 for selection in &selections {
12506 let mut start = selection.start;
12507 let mut end = selection.end;
12508 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12509 if is_entire_line {
12510 start = Point::new(start.row, 0);
12511 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12512 }
12513
12514 let mut trimmed_selections = Vec::new();
12515 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12516 let row = MultiBufferRow(start.row);
12517 let first_indent = buffer.indent_size_for_line(row);
12518 if first_indent.len == 0 || start.column > first_indent.len {
12519 trimmed_selections.push(start..end);
12520 } else {
12521 trimmed_selections.push(
12522 Point::new(row.0, first_indent.len)
12523 ..Point::new(row.0, buffer.line_len(row)),
12524 );
12525 for row in start.row + 1..=end.row {
12526 let mut line_len = buffer.line_len(MultiBufferRow(row));
12527 if row == end.row {
12528 line_len = end.column;
12529 }
12530 if line_len == 0 {
12531 trimmed_selections
12532 .push(Point::new(row, 0)..Point::new(row, line_len));
12533 continue;
12534 }
12535 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12536 if row_indent_size.len >= first_indent.len {
12537 trimmed_selections.push(
12538 Point::new(row, first_indent.len)..Point::new(row, line_len),
12539 );
12540 } else {
12541 trimmed_selections.clear();
12542 trimmed_selections.push(start..end);
12543 break;
12544 }
12545 }
12546 }
12547 } else {
12548 trimmed_selections.push(start..end);
12549 }
12550
12551 for trimmed_range in trimmed_selections {
12552 if is_first {
12553 is_first = false;
12554 } else {
12555 text += "\n";
12556 }
12557 let mut len = 0;
12558 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12559 text.push_str(chunk);
12560 len += chunk.len();
12561 }
12562 clipboard_selections.push(ClipboardSelection {
12563 len,
12564 is_entire_line,
12565 first_line_indent: buffer
12566 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12567 .len,
12568 });
12569 }
12570 }
12571 }
12572
12573 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12574 text,
12575 clipboard_selections,
12576 ));
12577 }
12578
12579 pub fn do_paste(
12580 &mut self,
12581 text: &String,
12582 clipboard_selections: Option<Vec<ClipboardSelection>>,
12583 handle_entire_lines: bool,
12584 window: &mut Window,
12585 cx: &mut Context<Self>,
12586 ) {
12587 if self.read_only(cx) {
12588 return;
12589 }
12590
12591 let clipboard_text = Cow::Borrowed(text.as_str());
12592
12593 self.transact(window, cx, |this, window, cx| {
12594 let had_active_edit_prediction = this.has_active_edit_prediction();
12595 let old_selections = this.selections.all::<usize>(cx);
12596 let cursor_offset = this.selections.last::<usize>(cx).head();
12597
12598 if let Some(mut clipboard_selections) = clipboard_selections {
12599 let all_selections_were_entire_line =
12600 clipboard_selections.iter().all(|s| s.is_entire_line);
12601 let first_selection_indent_column =
12602 clipboard_selections.first().map(|s| s.first_line_indent);
12603 if clipboard_selections.len() != old_selections.len() {
12604 clipboard_selections.drain(..);
12605 }
12606 let mut auto_indent_on_paste = true;
12607
12608 this.buffer.update(cx, |buffer, cx| {
12609 let snapshot = buffer.read(cx);
12610 auto_indent_on_paste = snapshot
12611 .language_settings_at(cursor_offset, cx)
12612 .auto_indent_on_paste;
12613
12614 let mut start_offset = 0;
12615 let mut edits = Vec::new();
12616 let mut original_indent_columns = Vec::new();
12617 for (ix, selection) in old_selections.iter().enumerate() {
12618 let to_insert;
12619 let entire_line;
12620 let original_indent_column;
12621 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12622 let end_offset = start_offset + clipboard_selection.len;
12623 to_insert = &clipboard_text[start_offset..end_offset];
12624 entire_line = clipboard_selection.is_entire_line;
12625 start_offset = end_offset + 1;
12626 original_indent_column = Some(clipboard_selection.first_line_indent);
12627 } else {
12628 to_insert = &*clipboard_text;
12629 entire_line = all_selections_were_entire_line;
12630 original_indent_column = first_selection_indent_column
12631 }
12632
12633 let (range, to_insert) =
12634 if selection.is_empty() && handle_entire_lines && entire_line {
12635 // If the corresponding selection was empty when this slice of the
12636 // clipboard text was written, then the entire line containing the
12637 // selection was copied. If this selection is also currently empty,
12638 // then paste the line before the current line of the buffer.
12639 let column = selection.start.to_point(&snapshot).column as usize;
12640 let line_start = selection.start - column;
12641 (line_start..line_start, Cow::Borrowed(to_insert))
12642 } else {
12643 let language = snapshot.language_at(selection.head());
12644 let range = selection.range();
12645 if let Some(language) = language
12646 && language.name() == "Markdown".into()
12647 {
12648 edit_for_markdown_paste(
12649 &snapshot,
12650 range,
12651 to_insert,
12652 url::Url::parse(to_insert).ok(),
12653 )
12654 } else {
12655 (range, Cow::Borrowed(to_insert))
12656 }
12657 };
12658
12659 edits.push((range, to_insert));
12660 original_indent_columns.push(original_indent_column);
12661 }
12662 drop(snapshot);
12663
12664 buffer.edit(
12665 edits,
12666 if auto_indent_on_paste {
12667 Some(AutoindentMode::Block {
12668 original_indent_columns,
12669 })
12670 } else {
12671 None
12672 },
12673 cx,
12674 );
12675 });
12676
12677 let selections = this.selections.all::<usize>(cx);
12678 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12679 } else {
12680 let url = url::Url::parse(&clipboard_text).ok();
12681
12682 let auto_indent_mode = if !clipboard_text.is_empty() {
12683 Some(AutoindentMode::Block {
12684 original_indent_columns: Vec::new(),
12685 })
12686 } else {
12687 None
12688 };
12689
12690 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12691 let snapshot = buffer.snapshot(cx);
12692
12693 let anchors = old_selections
12694 .iter()
12695 .map(|s| {
12696 let anchor = snapshot.anchor_after(s.head());
12697 s.map(|_| anchor)
12698 })
12699 .collect::<Vec<_>>();
12700
12701 let mut edits = Vec::new();
12702
12703 for selection in old_selections.iter() {
12704 let language = snapshot.language_at(selection.head());
12705 let range = selection.range();
12706
12707 let (edit_range, edit_text) = if let Some(language) = language
12708 && language.name() == "Markdown".into()
12709 {
12710 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12711 } else {
12712 (range, clipboard_text.clone())
12713 };
12714
12715 edits.push((edit_range, edit_text));
12716 }
12717
12718 drop(snapshot);
12719 buffer.edit(edits, auto_indent_mode, cx);
12720
12721 anchors
12722 });
12723
12724 this.change_selections(Default::default(), window, cx, |s| {
12725 s.select_anchors(selection_anchors);
12726 });
12727 }
12728
12729 let trigger_in_words =
12730 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12731
12732 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12733 });
12734 }
12735
12736 pub fn diff_clipboard_with_selection(
12737 &mut self,
12738 _: &DiffClipboardWithSelection,
12739 window: &mut Window,
12740 cx: &mut Context<Self>,
12741 ) {
12742 let selections = self.selections.all::<usize>(cx);
12743
12744 if selections.is_empty() {
12745 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12746 return;
12747 };
12748
12749 let clipboard_text = match cx.read_from_clipboard() {
12750 Some(item) => match item.entries().first() {
12751 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12752 _ => None,
12753 },
12754 None => None,
12755 };
12756
12757 let Some(clipboard_text) = clipboard_text else {
12758 log::warn!("Clipboard doesn't contain text.");
12759 return;
12760 };
12761
12762 window.dispatch_action(
12763 Box::new(DiffClipboardWithSelectionData {
12764 clipboard_text,
12765 editor: cx.entity(),
12766 }),
12767 cx,
12768 );
12769 }
12770
12771 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12773 if let Some(item) = cx.read_from_clipboard() {
12774 let entries = item.entries();
12775
12776 match entries.first() {
12777 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12778 // of all the pasted entries.
12779 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12780 .do_paste(
12781 clipboard_string.text(),
12782 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12783 true,
12784 window,
12785 cx,
12786 ),
12787 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12788 }
12789 }
12790 }
12791
12792 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12793 if self.read_only(cx) {
12794 return;
12795 }
12796
12797 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12798
12799 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12800 if let Some((selections, _)) =
12801 self.selection_history.transaction(transaction_id).cloned()
12802 {
12803 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12804 s.select_anchors(selections.to_vec());
12805 });
12806 } else {
12807 log::error!(
12808 "No entry in selection_history found for undo. \
12809 This may correspond to a bug where undo does not update the selection. \
12810 If this is occurring, please add details to \
12811 https://github.com/zed-industries/zed/issues/22692"
12812 );
12813 }
12814 self.request_autoscroll(Autoscroll::fit(), cx);
12815 self.unmark_text(window, cx);
12816 self.refresh_edit_prediction(true, false, window, cx);
12817 cx.emit(EditorEvent::Edited { transaction_id });
12818 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12819 }
12820 }
12821
12822 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12823 if self.read_only(cx) {
12824 return;
12825 }
12826
12827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12828
12829 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12830 if let Some((_, Some(selections))) =
12831 self.selection_history.transaction(transaction_id).cloned()
12832 {
12833 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12834 s.select_anchors(selections.to_vec());
12835 });
12836 } else {
12837 log::error!(
12838 "No entry in selection_history found for redo. \
12839 This may correspond to a bug where undo does not update the selection. \
12840 If this is occurring, please add details to \
12841 https://github.com/zed-industries/zed/issues/22692"
12842 );
12843 }
12844 self.request_autoscroll(Autoscroll::fit(), cx);
12845 self.unmark_text(window, cx);
12846 self.refresh_edit_prediction(true, false, window, cx);
12847 cx.emit(EditorEvent::Edited { transaction_id });
12848 }
12849 }
12850
12851 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12852 self.buffer
12853 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12854 }
12855
12856 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12857 self.buffer
12858 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12859 }
12860
12861 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12863 self.change_selections(Default::default(), window, cx, |s| {
12864 s.move_with(|map, selection| {
12865 let cursor = if selection.is_empty() {
12866 movement::left(map, selection.start)
12867 } else {
12868 selection.start
12869 };
12870 selection.collapse_to(cursor, SelectionGoal::None);
12871 });
12872 })
12873 }
12874
12875 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12877 self.change_selections(Default::default(), window, cx, |s| {
12878 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12879 })
12880 }
12881
12882 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12884 self.change_selections(Default::default(), window, cx, |s| {
12885 s.move_with(|map, selection| {
12886 let cursor = if selection.is_empty() {
12887 movement::right(map, selection.end)
12888 } else {
12889 selection.end
12890 };
12891 selection.collapse_to(cursor, SelectionGoal::None)
12892 });
12893 })
12894 }
12895
12896 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12898 self.change_selections(Default::default(), window, cx, |s| {
12899 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12900 });
12901 }
12902
12903 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12904 if self.take_rename(true, window, cx).is_some() {
12905 return;
12906 }
12907
12908 if self.mode.is_single_line() {
12909 cx.propagate();
12910 return;
12911 }
12912
12913 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12914
12915 let text_layout_details = &self.text_layout_details(window);
12916 let selection_count = self.selections.count();
12917 let first_selection = self.selections.first_anchor();
12918
12919 self.change_selections(Default::default(), window, cx, |s| {
12920 s.move_with(|map, selection| {
12921 if !selection.is_empty() {
12922 selection.goal = SelectionGoal::None;
12923 }
12924 let (cursor, goal) = movement::up(
12925 map,
12926 selection.start,
12927 selection.goal,
12928 false,
12929 text_layout_details,
12930 );
12931 selection.collapse_to(cursor, goal);
12932 });
12933 });
12934
12935 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12936 {
12937 cx.propagate();
12938 }
12939 }
12940
12941 pub fn move_up_by_lines(
12942 &mut self,
12943 action: &MoveUpByLines,
12944 window: &mut Window,
12945 cx: &mut Context<Self>,
12946 ) {
12947 if self.take_rename(true, window, cx).is_some() {
12948 return;
12949 }
12950
12951 if self.mode.is_single_line() {
12952 cx.propagate();
12953 return;
12954 }
12955
12956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12957
12958 let text_layout_details = &self.text_layout_details(window);
12959
12960 self.change_selections(Default::default(), window, cx, |s| {
12961 s.move_with(|map, selection| {
12962 if !selection.is_empty() {
12963 selection.goal = SelectionGoal::None;
12964 }
12965 let (cursor, goal) = movement::up_by_rows(
12966 map,
12967 selection.start,
12968 action.lines,
12969 selection.goal,
12970 false,
12971 text_layout_details,
12972 );
12973 selection.collapse_to(cursor, goal);
12974 });
12975 })
12976 }
12977
12978 pub fn move_down_by_lines(
12979 &mut self,
12980 action: &MoveDownByLines,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) {
12984 if self.take_rename(true, window, cx).is_some() {
12985 return;
12986 }
12987
12988 if self.mode.is_single_line() {
12989 cx.propagate();
12990 return;
12991 }
12992
12993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12994
12995 let text_layout_details = &self.text_layout_details(window);
12996
12997 self.change_selections(Default::default(), window, cx, |s| {
12998 s.move_with(|map, selection| {
12999 if !selection.is_empty() {
13000 selection.goal = SelectionGoal::None;
13001 }
13002 let (cursor, goal) = movement::down_by_rows(
13003 map,
13004 selection.start,
13005 action.lines,
13006 selection.goal,
13007 false,
13008 text_layout_details,
13009 );
13010 selection.collapse_to(cursor, goal);
13011 });
13012 })
13013 }
13014
13015 pub fn select_down_by_lines(
13016 &mut self,
13017 action: &SelectDownByLines,
13018 window: &mut Window,
13019 cx: &mut Context<Self>,
13020 ) {
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13022 let text_layout_details = &self.text_layout_details(window);
13023 self.change_selections(Default::default(), window, cx, |s| {
13024 s.move_heads_with(|map, head, goal| {
13025 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13026 })
13027 })
13028 }
13029
13030 pub fn select_up_by_lines(
13031 &mut self,
13032 action: &SelectUpByLines,
13033 window: &mut Window,
13034 cx: &mut Context<Self>,
13035 ) {
13036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13037 let text_layout_details = &self.text_layout_details(window);
13038 self.change_selections(Default::default(), window, cx, |s| {
13039 s.move_heads_with(|map, head, goal| {
13040 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13041 })
13042 })
13043 }
13044
13045 pub fn select_page_up(
13046 &mut self,
13047 _: &SelectPageUp,
13048 window: &mut Window,
13049 cx: &mut Context<Self>,
13050 ) {
13051 let Some(row_count) = self.visible_row_count() else {
13052 return;
13053 };
13054
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13056
13057 let text_layout_details = &self.text_layout_details(window);
13058
13059 self.change_selections(Default::default(), window, cx, |s| {
13060 s.move_heads_with(|map, head, goal| {
13061 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13062 })
13063 })
13064 }
13065
13066 pub fn move_page_up(
13067 &mut self,
13068 action: &MovePageUp,
13069 window: &mut Window,
13070 cx: &mut Context<Self>,
13071 ) {
13072 if self.take_rename(true, window, cx).is_some() {
13073 return;
13074 }
13075
13076 if self
13077 .context_menu
13078 .borrow_mut()
13079 .as_mut()
13080 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13081 .unwrap_or(false)
13082 {
13083 return;
13084 }
13085
13086 if matches!(self.mode, EditorMode::SingleLine) {
13087 cx.propagate();
13088 return;
13089 }
13090
13091 let Some(row_count) = self.visible_row_count() else {
13092 return;
13093 };
13094
13095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13096
13097 let effects = if action.center_cursor {
13098 SelectionEffects::scroll(Autoscroll::center())
13099 } else {
13100 SelectionEffects::default()
13101 };
13102
13103 let text_layout_details = &self.text_layout_details(window);
13104
13105 self.change_selections(effects, window, cx, |s| {
13106 s.move_with(|map, selection| {
13107 if !selection.is_empty() {
13108 selection.goal = SelectionGoal::None;
13109 }
13110 let (cursor, goal) = movement::up_by_rows(
13111 map,
13112 selection.end,
13113 row_count,
13114 selection.goal,
13115 false,
13116 text_layout_details,
13117 );
13118 selection.collapse_to(cursor, goal);
13119 });
13120 });
13121 }
13122
13123 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13124 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13125 let text_layout_details = &self.text_layout_details(window);
13126 self.change_selections(Default::default(), window, cx, |s| {
13127 s.move_heads_with(|map, head, goal| {
13128 movement::up(map, head, goal, false, text_layout_details)
13129 })
13130 })
13131 }
13132
13133 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13134 self.take_rename(true, window, cx);
13135
13136 if self.mode.is_single_line() {
13137 cx.propagate();
13138 return;
13139 }
13140
13141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13142
13143 let text_layout_details = &self.text_layout_details(window);
13144 let selection_count = self.selections.count();
13145 let first_selection = self.selections.first_anchor();
13146
13147 self.change_selections(Default::default(), window, cx, |s| {
13148 s.move_with(|map, selection| {
13149 if !selection.is_empty() {
13150 selection.goal = SelectionGoal::None;
13151 }
13152 let (cursor, goal) = movement::down(
13153 map,
13154 selection.end,
13155 selection.goal,
13156 false,
13157 text_layout_details,
13158 );
13159 selection.collapse_to(cursor, goal);
13160 });
13161 });
13162
13163 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13164 {
13165 cx.propagate();
13166 }
13167 }
13168
13169 pub fn select_page_down(
13170 &mut self,
13171 _: &SelectPageDown,
13172 window: &mut Window,
13173 cx: &mut Context<Self>,
13174 ) {
13175 let Some(row_count) = self.visible_row_count() else {
13176 return;
13177 };
13178
13179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13180
13181 let text_layout_details = &self.text_layout_details(window);
13182
13183 self.change_selections(Default::default(), window, cx, |s| {
13184 s.move_heads_with(|map, head, goal| {
13185 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13186 })
13187 })
13188 }
13189
13190 pub fn move_page_down(
13191 &mut self,
13192 action: &MovePageDown,
13193 window: &mut Window,
13194 cx: &mut Context<Self>,
13195 ) {
13196 if self.take_rename(true, window, cx).is_some() {
13197 return;
13198 }
13199
13200 if self
13201 .context_menu
13202 .borrow_mut()
13203 .as_mut()
13204 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13205 .unwrap_or(false)
13206 {
13207 return;
13208 }
13209
13210 if matches!(self.mode, EditorMode::SingleLine) {
13211 cx.propagate();
13212 return;
13213 }
13214
13215 let Some(row_count) = self.visible_row_count() else {
13216 return;
13217 };
13218
13219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13220
13221 let effects = if action.center_cursor {
13222 SelectionEffects::scroll(Autoscroll::center())
13223 } else {
13224 SelectionEffects::default()
13225 };
13226
13227 let text_layout_details = &self.text_layout_details(window);
13228 self.change_selections(effects, window, cx, |s| {
13229 s.move_with(|map, selection| {
13230 if !selection.is_empty() {
13231 selection.goal = SelectionGoal::None;
13232 }
13233 let (cursor, goal) = movement::down_by_rows(
13234 map,
13235 selection.end,
13236 row_count,
13237 selection.goal,
13238 false,
13239 text_layout_details,
13240 );
13241 selection.collapse_to(cursor, goal);
13242 });
13243 });
13244 }
13245
13246 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13248 let text_layout_details = &self.text_layout_details(window);
13249 self.change_selections(Default::default(), window, cx, |s| {
13250 s.move_heads_with(|map, head, goal| {
13251 movement::down(map, head, goal, false, text_layout_details)
13252 })
13253 });
13254 }
13255
13256 pub fn context_menu_first(
13257 &mut self,
13258 _: &ContextMenuFirst,
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_first(self.completion_provider.as_deref(), window, cx);
13264 }
13265 }
13266
13267 pub fn context_menu_prev(
13268 &mut self,
13269 _: &ContextMenuPrevious,
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_prev(self.completion_provider.as_deref(), window, cx);
13275 }
13276 }
13277
13278 pub fn context_menu_next(
13279 &mut self,
13280 _: &ContextMenuNext,
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_next(self.completion_provider.as_deref(), window, cx);
13286 }
13287 }
13288
13289 pub fn context_menu_last(
13290 &mut self,
13291 _: &ContextMenuLast,
13292 window: &mut Window,
13293 cx: &mut Context<Self>,
13294 ) {
13295 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13296 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13297 }
13298 }
13299
13300 pub fn signature_help_prev(
13301 &mut self,
13302 _: &SignatureHelpPrevious,
13303 _: &mut Window,
13304 cx: &mut Context<Self>,
13305 ) {
13306 if let Some(popover) = self.signature_help_state.popover_mut() {
13307 if popover.current_signature == 0 {
13308 popover.current_signature = popover.signatures.len() - 1;
13309 } else {
13310 popover.current_signature -= 1;
13311 }
13312 cx.notify();
13313 }
13314 }
13315
13316 pub fn signature_help_next(
13317 &mut self,
13318 _: &SignatureHelpNext,
13319 _: &mut Window,
13320 cx: &mut Context<Self>,
13321 ) {
13322 if let Some(popover) = self.signature_help_state.popover_mut() {
13323 if popover.current_signature + 1 == popover.signatures.len() {
13324 popover.current_signature = 0;
13325 } else {
13326 popover.current_signature += 1;
13327 }
13328 cx.notify();
13329 }
13330 }
13331
13332 pub fn move_to_previous_word_start(
13333 &mut self,
13334 _: &MoveToPreviousWordStart,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13339 self.change_selections(Default::default(), window, cx, |s| {
13340 s.move_cursors_with(|map, head, _| {
13341 (
13342 movement::previous_word_start(map, head),
13343 SelectionGoal::None,
13344 )
13345 });
13346 })
13347 }
13348
13349 pub fn move_to_previous_subword_start(
13350 &mut self,
13351 _: &MoveToPreviousSubwordStart,
13352 window: &mut Window,
13353 cx: &mut Context<Self>,
13354 ) {
13355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13356 self.change_selections(Default::default(), window, cx, |s| {
13357 s.move_cursors_with(|map, head, _| {
13358 (
13359 movement::previous_subword_start(map, head),
13360 SelectionGoal::None,
13361 )
13362 });
13363 })
13364 }
13365
13366 pub fn select_to_previous_word_start(
13367 &mut self,
13368 _: &SelectToPreviousWordStart,
13369 window: &mut Window,
13370 cx: &mut Context<Self>,
13371 ) {
13372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13373 self.change_selections(Default::default(), window, cx, |s| {
13374 s.move_heads_with(|map, head, _| {
13375 (
13376 movement::previous_word_start(map, head),
13377 SelectionGoal::None,
13378 )
13379 });
13380 })
13381 }
13382
13383 pub fn select_to_previous_subword_start(
13384 &mut self,
13385 _: &SelectToPreviousSubwordStart,
13386 window: &mut Window,
13387 cx: &mut Context<Self>,
13388 ) {
13389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13390 self.change_selections(Default::default(), window, cx, |s| {
13391 s.move_heads_with(|map, head, _| {
13392 (
13393 movement::previous_subword_start(map, head),
13394 SelectionGoal::None,
13395 )
13396 });
13397 })
13398 }
13399
13400 pub fn delete_to_previous_word_start(
13401 &mut self,
13402 action: &DeleteToPreviousWordStart,
13403 window: &mut Window,
13404 cx: &mut Context<Self>,
13405 ) {
13406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13407 self.transact(window, cx, |this, window, cx| {
13408 this.select_autoclose_pair(window, cx);
13409 this.change_selections(Default::default(), window, cx, |s| {
13410 s.move_with(|map, selection| {
13411 if selection.is_empty() {
13412 let mut cursor = if action.ignore_newlines {
13413 movement::previous_word_start(map, selection.head())
13414 } else {
13415 movement::previous_word_start_or_newline(map, selection.head())
13416 };
13417 cursor = movement::adjust_greedy_deletion(
13418 map,
13419 selection.head(),
13420 cursor,
13421 action.ignore_brackets,
13422 );
13423 selection.set_head(cursor, SelectionGoal::None);
13424 }
13425 });
13426 });
13427 this.insert("", window, cx);
13428 });
13429 }
13430
13431 pub fn delete_to_previous_subword_start(
13432 &mut self,
13433 _: &DeleteToPreviousSubwordStart,
13434 window: &mut Window,
13435 cx: &mut Context<Self>,
13436 ) {
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13438 self.transact(window, cx, |this, window, cx| {
13439 this.select_autoclose_pair(window, cx);
13440 this.change_selections(Default::default(), window, cx, |s| {
13441 s.move_with(|map, selection| {
13442 if selection.is_empty() {
13443 let mut cursor = movement::previous_subword_start(map, selection.head());
13444 cursor =
13445 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13446 selection.set_head(cursor, SelectionGoal::None);
13447 }
13448 });
13449 });
13450 this.insert("", window, cx);
13451 });
13452 }
13453
13454 pub fn move_to_next_word_end(
13455 &mut self,
13456 _: &MoveToNextWordEnd,
13457 window: &mut Window,
13458 cx: &mut Context<Self>,
13459 ) {
13460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13461 self.change_selections(Default::default(), window, cx, |s| {
13462 s.move_cursors_with(|map, head, _| {
13463 (movement::next_word_end(map, head), SelectionGoal::None)
13464 });
13465 })
13466 }
13467
13468 pub fn move_to_next_subword_end(
13469 &mut self,
13470 _: &MoveToNextSubwordEnd,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) {
13474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13475 self.change_selections(Default::default(), window, cx, |s| {
13476 s.move_cursors_with(|map, head, _| {
13477 (movement::next_subword_end(map, head), SelectionGoal::None)
13478 });
13479 })
13480 }
13481
13482 pub fn select_to_next_word_end(
13483 &mut self,
13484 _: &SelectToNextWordEnd,
13485 window: &mut Window,
13486 cx: &mut Context<Self>,
13487 ) {
13488 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13489 self.change_selections(Default::default(), window, cx, |s| {
13490 s.move_heads_with(|map, head, _| {
13491 (movement::next_word_end(map, head), SelectionGoal::None)
13492 });
13493 })
13494 }
13495
13496 pub fn select_to_next_subword_end(
13497 &mut self,
13498 _: &SelectToNextSubwordEnd,
13499 window: &mut Window,
13500 cx: &mut Context<Self>,
13501 ) {
13502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13503 self.change_selections(Default::default(), window, cx, |s| {
13504 s.move_heads_with(|map, head, _| {
13505 (movement::next_subword_end(map, head), SelectionGoal::None)
13506 });
13507 })
13508 }
13509
13510 pub fn delete_to_next_word_end(
13511 &mut self,
13512 action: &DeleteToNextWordEnd,
13513 window: &mut Window,
13514 cx: &mut Context<Self>,
13515 ) {
13516 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13517 self.transact(window, cx, |this, window, cx| {
13518 this.change_selections(Default::default(), window, cx, |s| {
13519 s.move_with(|map, selection| {
13520 if selection.is_empty() {
13521 let mut cursor = if action.ignore_newlines {
13522 movement::next_word_end(map, selection.head())
13523 } else {
13524 movement::next_word_end_or_newline(map, selection.head())
13525 };
13526 cursor = movement::adjust_greedy_deletion(
13527 map,
13528 selection.head(),
13529 cursor,
13530 action.ignore_brackets,
13531 );
13532 selection.set_head(cursor, SelectionGoal::None);
13533 }
13534 });
13535 });
13536 this.insert("", window, cx);
13537 });
13538 }
13539
13540 pub fn delete_to_next_subword_end(
13541 &mut self,
13542 _: &DeleteToNextSubwordEnd,
13543 window: &mut Window,
13544 cx: &mut Context<Self>,
13545 ) {
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13547 self.transact(window, cx, |this, window, cx| {
13548 this.change_selections(Default::default(), window, cx, |s| {
13549 s.move_with(|map, selection| {
13550 if selection.is_empty() {
13551 let mut cursor = movement::next_subword_end(map, selection.head());
13552 cursor =
13553 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13554 selection.set_head(cursor, SelectionGoal::None);
13555 }
13556 });
13557 });
13558 this.insert("", window, cx);
13559 });
13560 }
13561
13562 pub fn move_to_beginning_of_line(
13563 &mut self,
13564 action: &MoveToBeginningOfLine,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) {
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_cursors_with(|map, head, _| {
13571 (
13572 movement::indented_line_beginning(
13573 map,
13574 head,
13575 action.stop_at_soft_wraps,
13576 action.stop_at_indent,
13577 ),
13578 SelectionGoal::None,
13579 )
13580 });
13581 })
13582 }
13583
13584 pub fn select_to_beginning_of_line(
13585 &mut self,
13586 action: &SelectToBeginningOfLine,
13587 window: &mut Window,
13588 cx: &mut Context<Self>,
13589 ) {
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13591 self.change_selections(Default::default(), window, cx, |s| {
13592 s.move_heads_with(|map, head, _| {
13593 (
13594 movement::indented_line_beginning(
13595 map,
13596 head,
13597 action.stop_at_soft_wraps,
13598 action.stop_at_indent,
13599 ),
13600 SelectionGoal::None,
13601 )
13602 });
13603 });
13604 }
13605
13606 pub fn delete_to_beginning_of_line(
13607 &mut self,
13608 action: &DeleteToBeginningOfLine,
13609 window: &mut Window,
13610 cx: &mut Context<Self>,
13611 ) {
13612 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13613 self.transact(window, cx, |this, window, cx| {
13614 this.change_selections(Default::default(), window, cx, |s| {
13615 s.move_with(|_, selection| {
13616 selection.reversed = true;
13617 });
13618 });
13619
13620 this.select_to_beginning_of_line(
13621 &SelectToBeginningOfLine {
13622 stop_at_soft_wraps: false,
13623 stop_at_indent: action.stop_at_indent,
13624 },
13625 window,
13626 cx,
13627 );
13628 this.backspace(&Backspace, window, cx);
13629 });
13630 }
13631
13632 pub fn move_to_end_of_line(
13633 &mut self,
13634 action: &MoveToEndOfLine,
13635 window: &mut Window,
13636 cx: &mut Context<Self>,
13637 ) {
13638 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13639 self.change_selections(Default::default(), window, cx, |s| {
13640 s.move_cursors_with(|map, head, _| {
13641 (
13642 movement::line_end(map, head, action.stop_at_soft_wraps),
13643 SelectionGoal::None,
13644 )
13645 });
13646 })
13647 }
13648
13649 pub fn select_to_end_of_line(
13650 &mut self,
13651 action: &SelectToEndOfLine,
13652 window: &mut Window,
13653 cx: &mut Context<Self>,
13654 ) {
13655 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13656 self.change_selections(Default::default(), window, cx, |s| {
13657 s.move_heads_with(|map, head, _| {
13658 (
13659 movement::line_end(map, head, action.stop_at_soft_wraps),
13660 SelectionGoal::None,
13661 )
13662 });
13663 })
13664 }
13665
13666 pub fn delete_to_end_of_line(
13667 &mut self,
13668 _: &DeleteToEndOfLine,
13669 window: &mut Window,
13670 cx: &mut Context<Self>,
13671 ) {
13672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13673 self.transact(window, cx, |this, window, cx| {
13674 this.select_to_end_of_line(
13675 &SelectToEndOfLine {
13676 stop_at_soft_wraps: false,
13677 },
13678 window,
13679 cx,
13680 );
13681 this.delete(&Delete, window, cx);
13682 });
13683 }
13684
13685 pub fn cut_to_end_of_line(
13686 &mut self,
13687 action: &CutToEndOfLine,
13688 window: &mut Window,
13689 cx: &mut Context<Self>,
13690 ) {
13691 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13692 self.transact(window, cx, |this, window, cx| {
13693 this.select_to_end_of_line(
13694 &SelectToEndOfLine {
13695 stop_at_soft_wraps: false,
13696 },
13697 window,
13698 cx,
13699 );
13700 if !action.stop_at_newlines {
13701 this.change_selections(Default::default(), window, cx, |s| {
13702 s.move_with(|_, sel| {
13703 if sel.is_empty() {
13704 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13705 }
13706 });
13707 });
13708 }
13709 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13710 let item = this.cut_common(false, window, cx);
13711 cx.write_to_clipboard(item);
13712 });
13713 }
13714
13715 pub fn move_to_start_of_paragraph(
13716 &mut self,
13717 _: &MoveToStartOfParagraph,
13718 window: &mut Window,
13719 cx: &mut Context<Self>,
13720 ) {
13721 if matches!(self.mode, EditorMode::SingleLine) {
13722 cx.propagate();
13723 return;
13724 }
13725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13726 self.change_selections(Default::default(), window, cx, |s| {
13727 s.move_with(|map, selection| {
13728 selection.collapse_to(
13729 movement::start_of_paragraph(map, selection.head(), 1),
13730 SelectionGoal::None,
13731 )
13732 });
13733 })
13734 }
13735
13736 pub fn move_to_end_of_paragraph(
13737 &mut self,
13738 _: &MoveToEndOfParagraph,
13739 window: &mut Window,
13740 cx: &mut Context<Self>,
13741 ) {
13742 if matches!(self.mode, EditorMode::SingleLine) {
13743 cx.propagate();
13744 return;
13745 }
13746 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13747 self.change_selections(Default::default(), window, cx, |s| {
13748 s.move_with(|map, selection| {
13749 selection.collapse_to(
13750 movement::end_of_paragraph(map, selection.head(), 1),
13751 SelectionGoal::None,
13752 )
13753 });
13754 })
13755 }
13756
13757 pub fn select_to_start_of_paragraph(
13758 &mut self,
13759 _: &SelectToStartOfParagraph,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 if matches!(self.mode, EditorMode::SingleLine) {
13764 cx.propagate();
13765 return;
13766 }
13767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13768 self.change_selections(Default::default(), window, cx, |s| {
13769 s.move_heads_with(|map, head, _| {
13770 (
13771 movement::start_of_paragraph(map, head, 1),
13772 SelectionGoal::None,
13773 )
13774 });
13775 })
13776 }
13777
13778 pub fn select_to_end_of_paragraph(
13779 &mut self,
13780 _: &SelectToEndOfParagraph,
13781 window: &mut Window,
13782 cx: &mut Context<Self>,
13783 ) {
13784 if matches!(self.mode, EditorMode::SingleLine) {
13785 cx.propagate();
13786 return;
13787 }
13788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13789 self.change_selections(Default::default(), window, cx, |s| {
13790 s.move_heads_with(|map, head, _| {
13791 (
13792 movement::end_of_paragraph(map, head, 1),
13793 SelectionGoal::None,
13794 )
13795 });
13796 })
13797 }
13798
13799 pub fn move_to_start_of_excerpt(
13800 &mut self,
13801 _: &MoveToStartOfExcerpt,
13802 window: &mut Window,
13803 cx: &mut Context<Self>,
13804 ) {
13805 if matches!(self.mode, EditorMode::SingleLine) {
13806 cx.propagate();
13807 return;
13808 }
13809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13810 self.change_selections(Default::default(), window, cx, |s| {
13811 s.move_with(|map, selection| {
13812 selection.collapse_to(
13813 movement::start_of_excerpt(
13814 map,
13815 selection.head(),
13816 workspace::searchable::Direction::Prev,
13817 ),
13818 SelectionGoal::None,
13819 )
13820 });
13821 })
13822 }
13823
13824 pub fn move_to_start_of_next_excerpt(
13825 &mut self,
13826 _: &MoveToStartOfNextExcerpt,
13827 window: &mut Window,
13828 cx: &mut Context<Self>,
13829 ) {
13830 if matches!(self.mode, EditorMode::SingleLine) {
13831 cx.propagate();
13832 return;
13833 }
13834
13835 self.change_selections(Default::default(), window, cx, |s| {
13836 s.move_with(|map, selection| {
13837 selection.collapse_to(
13838 movement::start_of_excerpt(
13839 map,
13840 selection.head(),
13841 workspace::searchable::Direction::Next,
13842 ),
13843 SelectionGoal::None,
13844 )
13845 });
13846 })
13847 }
13848
13849 pub fn move_to_end_of_excerpt(
13850 &mut self,
13851 _: &MoveToEndOfExcerpt,
13852 window: &mut Window,
13853 cx: &mut Context<Self>,
13854 ) {
13855 if matches!(self.mode, EditorMode::SingleLine) {
13856 cx.propagate();
13857 return;
13858 }
13859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13860 self.change_selections(Default::default(), window, cx, |s| {
13861 s.move_with(|map, selection| {
13862 selection.collapse_to(
13863 movement::end_of_excerpt(
13864 map,
13865 selection.head(),
13866 workspace::searchable::Direction::Next,
13867 ),
13868 SelectionGoal::None,
13869 )
13870 });
13871 })
13872 }
13873
13874 pub fn move_to_end_of_previous_excerpt(
13875 &mut self,
13876 _: &MoveToEndOfPreviousExcerpt,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 if matches!(self.mode, EditorMode::SingleLine) {
13881 cx.propagate();
13882 return;
13883 }
13884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13885 self.change_selections(Default::default(), window, cx, |s| {
13886 s.move_with(|map, selection| {
13887 selection.collapse_to(
13888 movement::end_of_excerpt(
13889 map,
13890 selection.head(),
13891 workspace::searchable::Direction::Prev,
13892 ),
13893 SelectionGoal::None,
13894 )
13895 });
13896 })
13897 }
13898
13899 pub fn select_to_start_of_excerpt(
13900 &mut self,
13901 _: &SelectToStartOfExcerpt,
13902 window: &mut Window,
13903 cx: &mut Context<Self>,
13904 ) {
13905 if matches!(self.mode, EditorMode::SingleLine) {
13906 cx.propagate();
13907 return;
13908 }
13909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13910 self.change_selections(Default::default(), window, cx, |s| {
13911 s.move_heads_with(|map, head, _| {
13912 (
13913 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13914 SelectionGoal::None,
13915 )
13916 });
13917 })
13918 }
13919
13920 pub fn select_to_start_of_next_excerpt(
13921 &mut self,
13922 _: &SelectToStartOfNextExcerpt,
13923 window: &mut Window,
13924 cx: &mut Context<Self>,
13925 ) {
13926 if matches!(self.mode, EditorMode::SingleLine) {
13927 cx.propagate();
13928 return;
13929 }
13930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13931 self.change_selections(Default::default(), window, cx, |s| {
13932 s.move_heads_with(|map, head, _| {
13933 (
13934 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13935 SelectionGoal::None,
13936 )
13937 });
13938 })
13939 }
13940
13941 pub fn select_to_end_of_excerpt(
13942 &mut self,
13943 _: &SelectToEndOfExcerpt,
13944 window: &mut Window,
13945 cx: &mut Context<Self>,
13946 ) {
13947 if matches!(self.mode, EditorMode::SingleLine) {
13948 cx.propagate();
13949 return;
13950 }
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13952 self.change_selections(Default::default(), window, cx, |s| {
13953 s.move_heads_with(|map, head, _| {
13954 (
13955 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13956 SelectionGoal::None,
13957 )
13958 });
13959 })
13960 }
13961
13962 pub fn select_to_end_of_previous_excerpt(
13963 &mut self,
13964 _: &SelectToEndOfPreviousExcerpt,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) {
13968 if matches!(self.mode, EditorMode::SingleLine) {
13969 cx.propagate();
13970 return;
13971 }
13972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13973 self.change_selections(Default::default(), window, cx, |s| {
13974 s.move_heads_with(|map, head, _| {
13975 (
13976 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13977 SelectionGoal::None,
13978 )
13979 });
13980 })
13981 }
13982
13983 pub fn move_to_beginning(
13984 &mut self,
13985 _: &MoveToBeginning,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 if matches!(self.mode, EditorMode::SingleLine) {
13990 cx.propagate();
13991 return;
13992 }
13993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13994 self.change_selections(Default::default(), window, cx, |s| {
13995 s.select_ranges(vec![0..0]);
13996 });
13997 }
13998
13999 pub fn select_to_beginning(
14000 &mut self,
14001 _: &SelectToBeginning,
14002 window: &mut Window,
14003 cx: &mut Context<Self>,
14004 ) {
14005 let mut selection = self.selections.last::<Point>(cx);
14006 selection.set_head(Point::zero(), SelectionGoal::None);
14007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14008 self.change_selections(Default::default(), window, cx, |s| {
14009 s.select(vec![selection]);
14010 });
14011 }
14012
14013 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14014 if matches!(self.mode, EditorMode::SingleLine) {
14015 cx.propagate();
14016 return;
14017 }
14018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14019 let cursor = self.buffer.read(cx).read(cx).len();
14020 self.change_selections(Default::default(), window, cx, |s| {
14021 s.select_ranges(vec![cursor..cursor])
14022 });
14023 }
14024
14025 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14026 self.nav_history = nav_history;
14027 }
14028
14029 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14030 self.nav_history.as_ref()
14031 }
14032
14033 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14034 self.push_to_nav_history(
14035 self.selections.newest_anchor().head(),
14036 None,
14037 false,
14038 true,
14039 cx,
14040 );
14041 }
14042
14043 fn push_to_nav_history(
14044 &mut self,
14045 cursor_anchor: Anchor,
14046 new_position: Option<Point>,
14047 is_deactivate: bool,
14048 always: bool,
14049 cx: &mut Context<Self>,
14050 ) {
14051 if let Some(nav_history) = self.nav_history.as_mut() {
14052 let buffer = self.buffer.read(cx).read(cx);
14053 let cursor_position = cursor_anchor.to_point(&buffer);
14054 let scroll_state = self.scroll_manager.anchor();
14055 let scroll_top_row = scroll_state.top_row(&buffer);
14056 drop(buffer);
14057
14058 if let Some(new_position) = new_position {
14059 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14060 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14061 return;
14062 }
14063 }
14064
14065 nav_history.push(
14066 Some(NavigationData {
14067 cursor_anchor,
14068 cursor_position,
14069 scroll_anchor: scroll_state,
14070 scroll_top_row,
14071 }),
14072 cx,
14073 );
14074 cx.emit(EditorEvent::PushedToNavHistory {
14075 anchor: cursor_anchor,
14076 is_deactivate,
14077 })
14078 }
14079 }
14080
14081 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14083 let buffer = self.buffer.read(cx).snapshot(cx);
14084 let mut selection = self.selections.first::<usize>(cx);
14085 selection.set_head(buffer.len(), SelectionGoal::None);
14086 self.change_selections(Default::default(), window, cx, |s| {
14087 s.select(vec![selection]);
14088 });
14089 }
14090
14091 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14093 let end = self.buffer.read(cx).read(cx).len();
14094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14095 s.select_ranges(vec![0..end]);
14096 });
14097 }
14098
14099 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14100 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14101 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14102 let mut selections = self.selections.all::<Point>(cx);
14103 let max_point = display_map.buffer_snapshot().max_point();
14104 for selection in &mut selections {
14105 let rows = selection.spanned_rows(true, &display_map);
14106 selection.start = Point::new(rows.start.0, 0);
14107 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14108 selection.reversed = false;
14109 }
14110 self.change_selections(Default::default(), window, cx, |s| {
14111 s.select(selections);
14112 });
14113 }
14114
14115 pub fn split_selection_into_lines(
14116 &mut self,
14117 action: &SplitSelectionIntoLines,
14118 window: &mut Window,
14119 cx: &mut Context<Self>,
14120 ) {
14121 let selections = self
14122 .selections
14123 .all::<Point>(cx)
14124 .into_iter()
14125 .map(|selection| selection.start..selection.end)
14126 .collect::<Vec<_>>();
14127 self.unfold_ranges(&selections, true, true, cx);
14128
14129 let mut new_selection_ranges = Vec::new();
14130 {
14131 let buffer = self.buffer.read(cx).read(cx);
14132 for selection in selections {
14133 for row in selection.start.row..selection.end.row {
14134 let line_start = Point::new(row, 0);
14135 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14136
14137 if action.keep_selections {
14138 // Keep the selection range for each line
14139 let selection_start = if row == selection.start.row {
14140 selection.start
14141 } else {
14142 line_start
14143 };
14144 new_selection_ranges.push(selection_start..line_end);
14145 } else {
14146 // Collapse to cursor at end of line
14147 new_selection_ranges.push(line_end..line_end);
14148 }
14149 }
14150
14151 let is_multiline_selection = selection.start.row != selection.end.row;
14152 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14153 // so this action feels more ergonomic when paired with other selection operations
14154 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14155 if !should_skip_last {
14156 if action.keep_selections {
14157 if is_multiline_selection {
14158 let line_start = Point::new(selection.end.row, 0);
14159 new_selection_ranges.push(line_start..selection.end);
14160 } else {
14161 new_selection_ranges.push(selection.start..selection.end);
14162 }
14163 } else {
14164 new_selection_ranges.push(selection.end..selection.end);
14165 }
14166 }
14167 }
14168 }
14169 self.change_selections(Default::default(), window, cx, |s| {
14170 s.select_ranges(new_selection_ranges);
14171 });
14172 }
14173
14174 pub fn add_selection_above(
14175 &mut self,
14176 _: &AddSelectionAbove,
14177 window: &mut Window,
14178 cx: &mut Context<Self>,
14179 ) {
14180 self.add_selection(true, window, cx);
14181 }
14182
14183 pub fn add_selection_below(
14184 &mut self,
14185 _: &AddSelectionBelow,
14186 window: &mut Window,
14187 cx: &mut Context<Self>,
14188 ) {
14189 self.add_selection(false, window, cx);
14190 }
14191
14192 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14194
14195 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14196 let all_selections = self.selections.all::<Point>(cx);
14197 let text_layout_details = self.text_layout_details(window);
14198
14199 let (mut columnar_selections, new_selections_to_columnarize) = {
14200 if let Some(state) = self.add_selections_state.as_ref() {
14201 let columnar_selection_ids: HashSet<_> = state
14202 .groups
14203 .iter()
14204 .flat_map(|group| group.stack.iter())
14205 .copied()
14206 .collect();
14207
14208 all_selections
14209 .into_iter()
14210 .partition(|s| columnar_selection_ids.contains(&s.id))
14211 } else {
14212 (Vec::new(), all_selections)
14213 }
14214 };
14215
14216 let mut state = self
14217 .add_selections_state
14218 .take()
14219 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14220
14221 for selection in new_selections_to_columnarize {
14222 let range = selection.display_range(&display_map).sorted();
14223 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14224 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14225 let positions = start_x.min(end_x)..start_x.max(end_x);
14226 let mut stack = Vec::new();
14227 for row in range.start.row().0..=range.end.row().0 {
14228 if let Some(selection) = self.selections.build_columnar_selection(
14229 &display_map,
14230 DisplayRow(row),
14231 &positions,
14232 selection.reversed,
14233 &text_layout_details,
14234 ) {
14235 stack.push(selection.id);
14236 columnar_selections.push(selection);
14237 }
14238 }
14239 if !stack.is_empty() {
14240 if above {
14241 stack.reverse();
14242 }
14243 state.groups.push(AddSelectionsGroup { above, stack });
14244 }
14245 }
14246
14247 let mut final_selections = Vec::new();
14248 let end_row = if above {
14249 DisplayRow(0)
14250 } else {
14251 display_map.max_point().row()
14252 };
14253
14254 let mut last_added_item_per_group = HashMap::default();
14255 for group in state.groups.iter_mut() {
14256 if let Some(last_id) = group.stack.last() {
14257 last_added_item_per_group.insert(*last_id, group);
14258 }
14259 }
14260
14261 for selection in columnar_selections {
14262 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14263 if above == group.above {
14264 let range = selection.display_range(&display_map).sorted();
14265 debug_assert_eq!(range.start.row(), range.end.row());
14266 let mut row = range.start.row();
14267 let positions =
14268 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14269 Pixels::from(start)..Pixels::from(end)
14270 } else {
14271 let start_x =
14272 display_map.x_for_display_point(range.start, &text_layout_details);
14273 let end_x =
14274 display_map.x_for_display_point(range.end, &text_layout_details);
14275 start_x.min(end_x)..start_x.max(end_x)
14276 };
14277
14278 let mut maybe_new_selection = None;
14279 while row != end_row {
14280 if above {
14281 row.0 -= 1;
14282 } else {
14283 row.0 += 1;
14284 }
14285 if let Some(new_selection) = self.selections.build_columnar_selection(
14286 &display_map,
14287 row,
14288 &positions,
14289 selection.reversed,
14290 &text_layout_details,
14291 ) {
14292 maybe_new_selection = Some(new_selection);
14293 break;
14294 }
14295 }
14296
14297 if let Some(new_selection) = maybe_new_selection {
14298 group.stack.push(new_selection.id);
14299 if above {
14300 final_selections.push(new_selection);
14301 final_selections.push(selection);
14302 } else {
14303 final_selections.push(selection);
14304 final_selections.push(new_selection);
14305 }
14306 } else {
14307 final_selections.push(selection);
14308 }
14309 } else {
14310 group.stack.pop();
14311 }
14312 } else {
14313 final_selections.push(selection);
14314 }
14315 }
14316
14317 self.change_selections(Default::default(), window, cx, |s| {
14318 s.select(final_selections);
14319 });
14320
14321 let final_selection_ids: HashSet<_> = self
14322 .selections
14323 .all::<Point>(cx)
14324 .iter()
14325 .map(|s| s.id)
14326 .collect();
14327 state.groups.retain_mut(|group| {
14328 // selections might get merged above so we remove invalid items from stacks
14329 group.stack.retain(|id| final_selection_ids.contains(id));
14330
14331 // single selection in stack can be treated as initial state
14332 group.stack.len() > 1
14333 });
14334
14335 if !state.groups.is_empty() {
14336 self.add_selections_state = Some(state);
14337 }
14338 }
14339
14340 fn select_match_ranges(
14341 &mut self,
14342 range: Range<usize>,
14343 reversed: bool,
14344 replace_newest: bool,
14345 auto_scroll: Option<Autoscroll>,
14346 window: &mut Window,
14347 cx: &mut Context<Editor>,
14348 ) {
14349 self.unfold_ranges(
14350 std::slice::from_ref(&range),
14351 false,
14352 auto_scroll.is_some(),
14353 cx,
14354 );
14355 let effects = if let Some(scroll) = auto_scroll {
14356 SelectionEffects::scroll(scroll)
14357 } else {
14358 SelectionEffects::no_scroll()
14359 };
14360 self.change_selections(effects, window, cx, |s| {
14361 if replace_newest {
14362 s.delete(s.newest_anchor().id);
14363 }
14364 if reversed {
14365 s.insert_range(range.end..range.start);
14366 } else {
14367 s.insert_range(range);
14368 }
14369 });
14370 }
14371
14372 pub fn select_next_match_internal(
14373 &mut self,
14374 display_map: &DisplaySnapshot,
14375 replace_newest: bool,
14376 autoscroll: Option<Autoscroll>,
14377 window: &mut Window,
14378 cx: &mut Context<Self>,
14379 ) -> Result<()> {
14380 let buffer = display_map.buffer_snapshot();
14381 let mut selections = self.selections.all::<usize>(cx);
14382 if let Some(mut select_next_state) = self.select_next_state.take() {
14383 let query = &select_next_state.query;
14384 if !select_next_state.done {
14385 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14386 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14387 let mut next_selected_range = None;
14388
14389 let bytes_after_last_selection =
14390 buffer.bytes_in_range(last_selection.end..buffer.len());
14391 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14392 let query_matches = query
14393 .stream_find_iter(bytes_after_last_selection)
14394 .map(|result| (last_selection.end, result))
14395 .chain(
14396 query
14397 .stream_find_iter(bytes_before_first_selection)
14398 .map(|result| (0, result)),
14399 );
14400
14401 for (start_offset, query_match) in query_matches {
14402 let query_match = query_match.unwrap(); // can only fail due to I/O
14403 let offset_range =
14404 start_offset + query_match.start()..start_offset + query_match.end();
14405
14406 if !select_next_state.wordwise
14407 || (!buffer.is_inside_word(offset_range.start, None)
14408 && !buffer.is_inside_word(offset_range.end, None))
14409 {
14410 // TODO: This is n^2, because we might check all the selections
14411 if !selections
14412 .iter()
14413 .any(|selection| selection.range().overlaps(&offset_range))
14414 {
14415 next_selected_range = Some(offset_range);
14416 break;
14417 }
14418 }
14419 }
14420
14421 if let Some(next_selected_range) = next_selected_range {
14422 self.select_match_ranges(
14423 next_selected_range,
14424 last_selection.reversed,
14425 replace_newest,
14426 autoscroll,
14427 window,
14428 cx,
14429 );
14430 } else {
14431 select_next_state.done = true;
14432 }
14433 }
14434
14435 self.select_next_state = Some(select_next_state);
14436 } else {
14437 let mut only_carets = true;
14438 let mut same_text_selected = true;
14439 let mut selected_text = None;
14440
14441 let mut selections_iter = selections.iter().peekable();
14442 while let Some(selection) = selections_iter.next() {
14443 if selection.start != selection.end {
14444 only_carets = false;
14445 }
14446
14447 if same_text_selected {
14448 if selected_text.is_none() {
14449 selected_text =
14450 Some(buffer.text_for_range(selection.range()).collect::<String>());
14451 }
14452
14453 if let Some(next_selection) = selections_iter.peek() {
14454 if next_selection.range().len() == selection.range().len() {
14455 let next_selected_text = buffer
14456 .text_for_range(next_selection.range())
14457 .collect::<String>();
14458 if Some(next_selected_text) != selected_text {
14459 same_text_selected = false;
14460 selected_text = None;
14461 }
14462 } else {
14463 same_text_selected = false;
14464 selected_text = None;
14465 }
14466 }
14467 }
14468 }
14469
14470 if only_carets {
14471 for selection in &mut selections {
14472 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14473 selection.start = word_range.start;
14474 selection.end = word_range.end;
14475 selection.goal = SelectionGoal::None;
14476 selection.reversed = false;
14477 self.select_match_ranges(
14478 selection.start..selection.end,
14479 selection.reversed,
14480 replace_newest,
14481 autoscroll,
14482 window,
14483 cx,
14484 );
14485 }
14486
14487 if selections.len() == 1 {
14488 let selection = selections
14489 .last()
14490 .expect("ensured that there's only one selection");
14491 let query = buffer
14492 .text_for_range(selection.start..selection.end)
14493 .collect::<String>();
14494 let is_empty = query.is_empty();
14495 let select_state = SelectNextState {
14496 query: AhoCorasick::new(&[query])?,
14497 wordwise: true,
14498 done: is_empty,
14499 };
14500 self.select_next_state = Some(select_state);
14501 } else {
14502 self.select_next_state = None;
14503 }
14504 } else if let Some(selected_text) = selected_text {
14505 self.select_next_state = Some(SelectNextState {
14506 query: AhoCorasick::new(&[selected_text])?,
14507 wordwise: false,
14508 done: false,
14509 });
14510 self.select_next_match_internal(
14511 display_map,
14512 replace_newest,
14513 autoscroll,
14514 window,
14515 cx,
14516 )?;
14517 }
14518 }
14519 Ok(())
14520 }
14521
14522 pub fn select_all_matches(
14523 &mut self,
14524 _action: &SelectAllMatches,
14525 window: &mut Window,
14526 cx: &mut Context<Self>,
14527 ) -> Result<()> {
14528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14529
14530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14531
14532 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14533 let Some(select_next_state) = self.select_next_state.as_mut() else {
14534 return Ok(());
14535 };
14536 if select_next_state.done {
14537 return Ok(());
14538 }
14539
14540 let mut new_selections = Vec::new();
14541
14542 let reversed = self.selections.oldest::<usize>(cx).reversed;
14543 let buffer = display_map.buffer_snapshot();
14544 let query_matches = select_next_state
14545 .query
14546 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14547
14548 for query_match in query_matches.into_iter() {
14549 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14550 let offset_range = if reversed {
14551 query_match.end()..query_match.start()
14552 } else {
14553 query_match.start()..query_match.end()
14554 };
14555
14556 if !select_next_state.wordwise
14557 || (!buffer.is_inside_word(offset_range.start, None)
14558 && !buffer.is_inside_word(offset_range.end, None))
14559 {
14560 new_selections.push(offset_range.start..offset_range.end);
14561 }
14562 }
14563
14564 select_next_state.done = true;
14565
14566 if new_selections.is_empty() {
14567 log::error!("bug: new_selections is empty in select_all_matches");
14568 return Ok(());
14569 }
14570
14571 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14572 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14573 selections.select_ranges(new_selections)
14574 });
14575
14576 Ok(())
14577 }
14578
14579 pub fn select_next(
14580 &mut self,
14581 action: &SelectNext,
14582 window: &mut Window,
14583 cx: &mut Context<Self>,
14584 ) -> Result<()> {
14585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14587 self.select_next_match_internal(
14588 &display_map,
14589 action.replace_newest,
14590 Some(Autoscroll::newest()),
14591 window,
14592 cx,
14593 )?;
14594 Ok(())
14595 }
14596
14597 pub fn select_previous(
14598 &mut self,
14599 action: &SelectPrevious,
14600 window: &mut Window,
14601 cx: &mut Context<Self>,
14602 ) -> Result<()> {
14603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14605 let buffer = display_map.buffer_snapshot();
14606 let mut selections = self.selections.all::<usize>(cx);
14607 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14608 let query = &select_prev_state.query;
14609 if !select_prev_state.done {
14610 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14611 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14612 let mut next_selected_range = None;
14613 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14614 let bytes_before_last_selection =
14615 buffer.reversed_bytes_in_range(0..last_selection.start);
14616 let bytes_after_first_selection =
14617 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14618 let query_matches = query
14619 .stream_find_iter(bytes_before_last_selection)
14620 .map(|result| (last_selection.start, result))
14621 .chain(
14622 query
14623 .stream_find_iter(bytes_after_first_selection)
14624 .map(|result| (buffer.len(), result)),
14625 );
14626 for (end_offset, query_match) in query_matches {
14627 let query_match = query_match.unwrap(); // can only fail due to I/O
14628 let offset_range =
14629 end_offset - query_match.end()..end_offset - query_match.start();
14630
14631 if !select_prev_state.wordwise
14632 || (!buffer.is_inside_word(offset_range.start, None)
14633 && !buffer.is_inside_word(offset_range.end, None))
14634 {
14635 next_selected_range = Some(offset_range);
14636 break;
14637 }
14638 }
14639
14640 if let Some(next_selected_range) = next_selected_range {
14641 self.select_match_ranges(
14642 next_selected_range,
14643 last_selection.reversed,
14644 action.replace_newest,
14645 Some(Autoscroll::newest()),
14646 window,
14647 cx,
14648 );
14649 } else {
14650 select_prev_state.done = true;
14651 }
14652 }
14653
14654 self.select_prev_state = Some(select_prev_state);
14655 } else {
14656 let mut only_carets = true;
14657 let mut same_text_selected = true;
14658 let mut selected_text = None;
14659
14660 let mut selections_iter = selections.iter().peekable();
14661 while let Some(selection) = selections_iter.next() {
14662 if selection.start != selection.end {
14663 only_carets = false;
14664 }
14665
14666 if same_text_selected {
14667 if selected_text.is_none() {
14668 selected_text =
14669 Some(buffer.text_for_range(selection.range()).collect::<String>());
14670 }
14671
14672 if let Some(next_selection) = selections_iter.peek() {
14673 if next_selection.range().len() == selection.range().len() {
14674 let next_selected_text = buffer
14675 .text_for_range(next_selection.range())
14676 .collect::<String>();
14677 if Some(next_selected_text) != selected_text {
14678 same_text_selected = false;
14679 selected_text = None;
14680 }
14681 } else {
14682 same_text_selected = false;
14683 selected_text = None;
14684 }
14685 }
14686 }
14687 }
14688
14689 if only_carets {
14690 for selection in &mut selections {
14691 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14692 selection.start = word_range.start;
14693 selection.end = word_range.end;
14694 selection.goal = SelectionGoal::None;
14695 selection.reversed = false;
14696 self.select_match_ranges(
14697 selection.start..selection.end,
14698 selection.reversed,
14699 action.replace_newest,
14700 Some(Autoscroll::newest()),
14701 window,
14702 cx,
14703 );
14704 }
14705 if selections.len() == 1 {
14706 let selection = selections
14707 .last()
14708 .expect("ensured that there's only one selection");
14709 let query = buffer
14710 .text_for_range(selection.start..selection.end)
14711 .collect::<String>();
14712 let is_empty = query.is_empty();
14713 let select_state = SelectNextState {
14714 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14715 wordwise: true,
14716 done: is_empty,
14717 };
14718 self.select_prev_state = Some(select_state);
14719 } else {
14720 self.select_prev_state = None;
14721 }
14722 } else if let Some(selected_text) = selected_text {
14723 self.select_prev_state = Some(SelectNextState {
14724 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14725 wordwise: false,
14726 done: false,
14727 });
14728 self.select_previous(action, window, cx)?;
14729 }
14730 }
14731 Ok(())
14732 }
14733
14734 pub fn find_next_match(
14735 &mut self,
14736 _: &FindNextMatch,
14737 window: &mut Window,
14738 cx: &mut Context<Self>,
14739 ) -> Result<()> {
14740 let selections = self.selections.disjoint_anchors_arc();
14741 match selections.first() {
14742 Some(first) if selections.len() >= 2 => {
14743 self.change_selections(Default::default(), window, cx, |s| {
14744 s.select_ranges([first.range()]);
14745 });
14746 }
14747 _ => self.select_next(
14748 &SelectNext {
14749 replace_newest: true,
14750 },
14751 window,
14752 cx,
14753 )?,
14754 }
14755 Ok(())
14756 }
14757
14758 pub fn find_previous_match(
14759 &mut self,
14760 _: &FindPreviousMatch,
14761 window: &mut Window,
14762 cx: &mut Context<Self>,
14763 ) -> Result<()> {
14764 let selections = self.selections.disjoint_anchors_arc();
14765 match selections.last() {
14766 Some(last) if selections.len() >= 2 => {
14767 self.change_selections(Default::default(), window, cx, |s| {
14768 s.select_ranges([last.range()]);
14769 });
14770 }
14771 _ => self.select_previous(
14772 &SelectPrevious {
14773 replace_newest: true,
14774 },
14775 window,
14776 cx,
14777 )?,
14778 }
14779 Ok(())
14780 }
14781
14782 pub fn toggle_comments(
14783 &mut self,
14784 action: &ToggleComments,
14785 window: &mut Window,
14786 cx: &mut Context<Self>,
14787 ) {
14788 if self.read_only(cx) {
14789 return;
14790 }
14791 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14792 let text_layout_details = &self.text_layout_details(window);
14793 self.transact(window, cx, |this, window, cx| {
14794 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14795 let mut edits = Vec::new();
14796 let mut selection_edit_ranges = Vec::new();
14797 let mut last_toggled_row = None;
14798 let snapshot = this.buffer.read(cx).read(cx);
14799 let empty_str: Arc<str> = Arc::default();
14800 let mut suffixes_inserted = Vec::new();
14801 let ignore_indent = action.ignore_indent;
14802
14803 fn comment_prefix_range(
14804 snapshot: &MultiBufferSnapshot,
14805 row: MultiBufferRow,
14806 comment_prefix: &str,
14807 comment_prefix_whitespace: &str,
14808 ignore_indent: bool,
14809 ) -> Range<Point> {
14810 let indent_size = if ignore_indent {
14811 0
14812 } else {
14813 snapshot.indent_size_for_line(row).len
14814 };
14815
14816 let start = Point::new(row.0, indent_size);
14817
14818 let mut line_bytes = snapshot
14819 .bytes_in_range(start..snapshot.max_point())
14820 .flatten()
14821 .copied();
14822
14823 // If this line currently begins with the line comment prefix, then record
14824 // the range containing the prefix.
14825 if line_bytes
14826 .by_ref()
14827 .take(comment_prefix.len())
14828 .eq(comment_prefix.bytes())
14829 {
14830 // Include any whitespace that matches the comment prefix.
14831 let matching_whitespace_len = line_bytes
14832 .zip(comment_prefix_whitespace.bytes())
14833 .take_while(|(a, b)| a == b)
14834 .count() as u32;
14835 let end = Point::new(
14836 start.row,
14837 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14838 );
14839 start..end
14840 } else {
14841 start..start
14842 }
14843 }
14844
14845 fn comment_suffix_range(
14846 snapshot: &MultiBufferSnapshot,
14847 row: MultiBufferRow,
14848 comment_suffix: &str,
14849 comment_suffix_has_leading_space: bool,
14850 ) -> Range<Point> {
14851 let end = Point::new(row.0, snapshot.line_len(row));
14852 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14853
14854 let mut line_end_bytes = snapshot
14855 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14856 .flatten()
14857 .copied();
14858
14859 let leading_space_len = if suffix_start_column > 0
14860 && line_end_bytes.next() == Some(b' ')
14861 && comment_suffix_has_leading_space
14862 {
14863 1
14864 } else {
14865 0
14866 };
14867
14868 // If this line currently begins with the line comment prefix, then record
14869 // the range containing the prefix.
14870 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14871 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14872 start..end
14873 } else {
14874 end..end
14875 }
14876 }
14877
14878 // TODO: Handle selections that cross excerpts
14879 for selection in &mut selections {
14880 let start_column = snapshot
14881 .indent_size_for_line(MultiBufferRow(selection.start.row))
14882 .len;
14883 let language = if let Some(language) =
14884 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14885 {
14886 language
14887 } else {
14888 continue;
14889 };
14890
14891 selection_edit_ranges.clear();
14892
14893 // If multiple selections contain a given row, avoid processing that
14894 // row more than once.
14895 let mut start_row = MultiBufferRow(selection.start.row);
14896 if last_toggled_row == Some(start_row) {
14897 start_row = start_row.next_row();
14898 }
14899 let end_row =
14900 if selection.end.row > selection.start.row && selection.end.column == 0 {
14901 MultiBufferRow(selection.end.row - 1)
14902 } else {
14903 MultiBufferRow(selection.end.row)
14904 };
14905 last_toggled_row = Some(end_row);
14906
14907 if start_row > end_row {
14908 continue;
14909 }
14910
14911 // If the language has line comments, toggle those.
14912 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14913
14914 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14915 if ignore_indent {
14916 full_comment_prefixes = full_comment_prefixes
14917 .into_iter()
14918 .map(|s| Arc::from(s.trim_end()))
14919 .collect();
14920 }
14921
14922 if !full_comment_prefixes.is_empty() {
14923 let first_prefix = full_comment_prefixes
14924 .first()
14925 .expect("prefixes is non-empty");
14926 let prefix_trimmed_lengths = full_comment_prefixes
14927 .iter()
14928 .map(|p| p.trim_end_matches(' ').len())
14929 .collect::<SmallVec<[usize; 4]>>();
14930
14931 let mut all_selection_lines_are_comments = true;
14932
14933 for row in start_row.0..=end_row.0 {
14934 let row = MultiBufferRow(row);
14935 if start_row < end_row && snapshot.is_line_blank(row) {
14936 continue;
14937 }
14938
14939 let prefix_range = full_comment_prefixes
14940 .iter()
14941 .zip(prefix_trimmed_lengths.iter().copied())
14942 .map(|(prefix, trimmed_prefix_len)| {
14943 comment_prefix_range(
14944 snapshot.deref(),
14945 row,
14946 &prefix[..trimmed_prefix_len],
14947 &prefix[trimmed_prefix_len..],
14948 ignore_indent,
14949 )
14950 })
14951 .max_by_key(|range| range.end.column - range.start.column)
14952 .expect("prefixes is non-empty");
14953
14954 if prefix_range.is_empty() {
14955 all_selection_lines_are_comments = false;
14956 }
14957
14958 selection_edit_ranges.push(prefix_range);
14959 }
14960
14961 if all_selection_lines_are_comments {
14962 edits.extend(
14963 selection_edit_ranges
14964 .iter()
14965 .cloned()
14966 .map(|range| (range, empty_str.clone())),
14967 );
14968 } else {
14969 let min_column = selection_edit_ranges
14970 .iter()
14971 .map(|range| range.start.column)
14972 .min()
14973 .unwrap_or(0);
14974 edits.extend(selection_edit_ranges.iter().map(|range| {
14975 let position = Point::new(range.start.row, min_column);
14976 (position..position, first_prefix.clone())
14977 }));
14978 }
14979 } else if let Some(BlockCommentConfig {
14980 start: full_comment_prefix,
14981 end: comment_suffix,
14982 ..
14983 }) = language.block_comment()
14984 {
14985 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14986 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14987 let prefix_range = comment_prefix_range(
14988 snapshot.deref(),
14989 start_row,
14990 comment_prefix,
14991 comment_prefix_whitespace,
14992 ignore_indent,
14993 );
14994 let suffix_range = comment_suffix_range(
14995 snapshot.deref(),
14996 end_row,
14997 comment_suffix.trim_start_matches(' '),
14998 comment_suffix.starts_with(' '),
14999 );
15000
15001 if prefix_range.is_empty() || suffix_range.is_empty() {
15002 edits.push((
15003 prefix_range.start..prefix_range.start,
15004 full_comment_prefix.clone(),
15005 ));
15006 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15007 suffixes_inserted.push((end_row, comment_suffix.len()));
15008 } else {
15009 edits.push((prefix_range, empty_str.clone()));
15010 edits.push((suffix_range, empty_str.clone()));
15011 }
15012 } else {
15013 continue;
15014 }
15015 }
15016
15017 drop(snapshot);
15018 this.buffer.update(cx, |buffer, cx| {
15019 buffer.edit(edits, None, cx);
15020 });
15021
15022 // Adjust selections so that they end before any comment suffixes that
15023 // were inserted.
15024 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15025 let mut selections = this.selections.all::<Point>(cx);
15026 let snapshot = this.buffer.read(cx).read(cx);
15027 for selection in &mut selections {
15028 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15029 match row.cmp(&MultiBufferRow(selection.end.row)) {
15030 Ordering::Less => {
15031 suffixes_inserted.next();
15032 continue;
15033 }
15034 Ordering::Greater => break,
15035 Ordering::Equal => {
15036 if selection.end.column == snapshot.line_len(row) {
15037 if selection.is_empty() {
15038 selection.start.column -= suffix_len as u32;
15039 }
15040 selection.end.column -= suffix_len as u32;
15041 }
15042 break;
15043 }
15044 }
15045 }
15046 }
15047
15048 drop(snapshot);
15049 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15050
15051 let selections = this.selections.all::<Point>(cx);
15052 let selections_on_single_row = selections.windows(2).all(|selections| {
15053 selections[0].start.row == selections[1].start.row
15054 && selections[0].end.row == selections[1].end.row
15055 && selections[0].start.row == selections[0].end.row
15056 });
15057 let selections_selecting = selections
15058 .iter()
15059 .any(|selection| selection.start != selection.end);
15060 let advance_downwards = action.advance_downwards
15061 && selections_on_single_row
15062 && !selections_selecting
15063 && !matches!(this.mode, EditorMode::SingleLine);
15064
15065 if advance_downwards {
15066 let snapshot = this.buffer.read(cx).snapshot(cx);
15067
15068 this.change_selections(Default::default(), window, cx, |s| {
15069 s.move_cursors_with(|display_snapshot, display_point, _| {
15070 let mut point = display_point.to_point(display_snapshot);
15071 point.row += 1;
15072 point = snapshot.clip_point(point, Bias::Left);
15073 let display_point = point.to_display_point(display_snapshot);
15074 let goal = SelectionGoal::HorizontalPosition(
15075 display_snapshot
15076 .x_for_display_point(display_point, text_layout_details)
15077 .into(),
15078 );
15079 (display_point, goal)
15080 })
15081 });
15082 }
15083 });
15084 }
15085
15086 pub fn select_enclosing_symbol(
15087 &mut self,
15088 _: &SelectEnclosingSymbol,
15089 window: &mut Window,
15090 cx: &mut Context<Self>,
15091 ) {
15092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15093
15094 let buffer = self.buffer.read(cx).snapshot(cx);
15095 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15096
15097 fn update_selection(
15098 selection: &Selection<usize>,
15099 buffer_snap: &MultiBufferSnapshot,
15100 ) -> Option<Selection<usize>> {
15101 let cursor = selection.head();
15102 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15103 for symbol in symbols.iter().rev() {
15104 let start = symbol.range.start.to_offset(buffer_snap);
15105 let end = symbol.range.end.to_offset(buffer_snap);
15106 let new_range = start..end;
15107 if start < selection.start || end > selection.end {
15108 return Some(Selection {
15109 id: selection.id,
15110 start: new_range.start,
15111 end: new_range.end,
15112 goal: SelectionGoal::None,
15113 reversed: selection.reversed,
15114 });
15115 }
15116 }
15117 None
15118 }
15119
15120 let mut selected_larger_symbol = false;
15121 let new_selections = old_selections
15122 .iter()
15123 .map(|selection| match update_selection(selection, &buffer) {
15124 Some(new_selection) => {
15125 if new_selection.range() != selection.range() {
15126 selected_larger_symbol = true;
15127 }
15128 new_selection
15129 }
15130 None => selection.clone(),
15131 })
15132 .collect::<Vec<_>>();
15133
15134 if selected_larger_symbol {
15135 self.change_selections(Default::default(), window, cx, |s| {
15136 s.select(new_selections);
15137 });
15138 }
15139 }
15140
15141 pub fn select_larger_syntax_node(
15142 &mut self,
15143 _: &SelectLargerSyntaxNode,
15144 window: &mut Window,
15145 cx: &mut Context<Self>,
15146 ) {
15147 let Some(visible_row_count) = self.visible_row_count() else {
15148 return;
15149 };
15150 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15151 if old_selections.is_empty() {
15152 return;
15153 }
15154
15155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15156
15157 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15158 let buffer = self.buffer.read(cx).snapshot(cx);
15159
15160 let mut selected_larger_node = false;
15161 let mut new_selections = old_selections
15162 .iter()
15163 .map(|selection| {
15164 let old_range = selection.start..selection.end;
15165
15166 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15167 // manually select word at selection
15168 if ["string_content", "inline"].contains(&node.kind()) {
15169 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15170 // ignore if word is already selected
15171 if !word_range.is_empty() && old_range != word_range {
15172 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15173 // only select word if start and end point belongs to same word
15174 if word_range == last_word_range {
15175 selected_larger_node = true;
15176 return Selection {
15177 id: selection.id,
15178 start: word_range.start,
15179 end: word_range.end,
15180 goal: SelectionGoal::None,
15181 reversed: selection.reversed,
15182 };
15183 }
15184 }
15185 }
15186 }
15187
15188 let mut new_range = old_range.clone();
15189 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15190 new_range = range;
15191 if !node.is_named() {
15192 continue;
15193 }
15194 if !display_map.intersects_fold(new_range.start)
15195 && !display_map.intersects_fold(new_range.end)
15196 {
15197 break;
15198 }
15199 }
15200
15201 selected_larger_node |= new_range != old_range;
15202 Selection {
15203 id: selection.id,
15204 start: new_range.start,
15205 end: new_range.end,
15206 goal: SelectionGoal::None,
15207 reversed: selection.reversed,
15208 }
15209 })
15210 .collect::<Vec<_>>();
15211
15212 if !selected_larger_node {
15213 return; // don't put this call in the history
15214 }
15215
15216 // scroll based on transformation done to the last selection created by the user
15217 let (last_old, last_new) = old_selections
15218 .last()
15219 .zip(new_selections.last().cloned())
15220 .expect("old_selections isn't empty");
15221
15222 // revert selection
15223 let is_selection_reversed = {
15224 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15225 new_selections.last_mut().expect("checked above").reversed =
15226 should_newest_selection_be_reversed;
15227 should_newest_selection_be_reversed
15228 };
15229
15230 if selected_larger_node {
15231 self.select_syntax_node_history.disable_clearing = true;
15232 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15233 s.select(new_selections.clone());
15234 });
15235 self.select_syntax_node_history.disable_clearing = false;
15236 }
15237
15238 let start_row = last_new.start.to_display_point(&display_map).row().0;
15239 let end_row = last_new.end.to_display_point(&display_map).row().0;
15240 let selection_height = end_row - start_row + 1;
15241 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15242
15243 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15244 let scroll_behavior = if fits_on_the_screen {
15245 self.request_autoscroll(Autoscroll::fit(), cx);
15246 SelectSyntaxNodeScrollBehavior::FitSelection
15247 } else if is_selection_reversed {
15248 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15249 SelectSyntaxNodeScrollBehavior::CursorTop
15250 } else {
15251 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15252 SelectSyntaxNodeScrollBehavior::CursorBottom
15253 };
15254
15255 self.select_syntax_node_history.push((
15256 old_selections,
15257 scroll_behavior,
15258 is_selection_reversed,
15259 ));
15260 }
15261
15262 pub fn select_smaller_syntax_node(
15263 &mut self,
15264 _: &SelectSmallerSyntaxNode,
15265 window: &mut Window,
15266 cx: &mut Context<Self>,
15267 ) {
15268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15269
15270 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15271 self.select_syntax_node_history.pop()
15272 {
15273 if let Some(selection) = selections.last_mut() {
15274 selection.reversed = is_selection_reversed;
15275 }
15276
15277 self.select_syntax_node_history.disable_clearing = true;
15278 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15279 s.select(selections.to_vec());
15280 });
15281 self.select_syntax_node_history.disable_clearing = false;
15282
15283 match scroll_behavior {
15284 SelectSyntaxNodeScrollBehavior::CursorTop => {
15285 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15286 }
15287 SelectSyntaxNodeScrollBehavior::FitSelection => {
15288 self.request_autoscroll(Autoscroll::fit(), cx);
15289 }
15290 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15291 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15292 }
15293 }
15294 }
15295 }
15296
15297 pub fn unwrap_syntax_node(
15298 &mut self,
15299 _: &UnwrapSyntaxNode,
15300 window: &mut Window,
15301 cx: &mut Context<Self>,
15302 ) {
15303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15304
15305 let buffer = self.buffer.read(cx).snapshot(cx);
15306 let selections = self
15307 .selections
15308 .all::<usize>(cx)
15309 .into_iter()
15310 // subtracting the offset requires sorting
15311 .sorted_by_key(|i| i.start);
15312
15313 let full_edits = selections
15314 .into_iter()
15315 .filter_map(|selection| {
15316 let child = if selection.is_empty()
15317 && let Some((_, ancestor_range)) =
15318 buffer.syntax_ancestor(selection.start..selection.end)
15319 {
15320 ancestor_range
15321 } else {
15322 selection.range()
15323 };
15324
15325 let mut parent = child.clone();
15326 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15327 parent = ancestor_range;
15328 if parent.start < child.start || parent.end > child.end {
15329 break;
15330 }
15331 }
15332
15333 if parent == child {
15334 return None;
15335 }
15336 let text = buffer.text_for_range(child).collect::<String>();
15337 Some((selection.id, parent, text))
15338 })
15339 .collect::<Vec<_>>();
15340 if full_edits.is_empty() {
15341 return;
15342 }
15343
15344 self.transact(window, cx, |this, window, cx| {
15345 this.buffer.update(cx, |buffer, cx| {
15346 buffer.edit(
15347 full_edits
15348 .iter()
15349 .map(|(_, p, t)| (p.clone(), t.clone()))
15350 .collect::<Vec<_>>(),
15351 None,
15352 cx,
15353 );
15354 });
15355 this.change_selections(Default::default(), window, cx, |s| {
15356 let mut offset = 0;
15357 let mut selections = vec![];
15358 for (id, parent, text) in full_edits {
15359 let start = parent.start - offset;
15360 offset += parent.len() - text.len();
15361 selections.push(Selection {
15362 id,
15363 start,
15364 end: start + text.len(),
15365 reversed: false,
15366 goal: Default::default(),
15367 });
15368 }
15369 s.select(selections);
15370 });
15371 });
15372 }
15373
15374 pub fn select_next_syntax_node(
15375 &mut self,
15376 _: &SelectNextSyntaxNode,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15381 if old_selections.is_empty() {
15382 return;
15383 }
15384
15385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15386
15387 let buffer = self.buffer.read(cx).snapshot(cx);
15388 let mut selected_sibling = false;
15389
15390 let new_selections = old_selections
15391 .iter()
15392 .map(|selection| {
15393 let old_range = selection.start..selection.end;
15394
15395 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15396 let new_range = node.byte_range();
15397 selected_sibling = true;
15398 Selection {
15399 id: selection.id,
15400 start: new_range.start,
15401 end: new_range.end,
15402 goal: SelectionGoal::None,
15403 reversed: selection.reversed,
15404 }
15405 } else {
15406 selection.clone()
15407 }
15408 })
15409 .collect::<Vec<_>>();
15410
15411 if selected_sibling {
15412 self.change_selections(
15413 SelectionEffects::scroll(Autoscroll::fit()),
15414 window,
15415 cx,
15416 |s| {
15417 s.select(new_selections);
15418 },
15419 );
15420 }
15421 }
15422
15423 pub fn select_prev_syntax_node(
15424 &mut self,
15425 _: &SelectPreviousSyntaxNode,
15426 window: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) {
15429 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15430 if old_selections.is_empty() {
15431 return;
15432 }
15433
15434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15435
15436 let buffer = self.buffer.read(cx).snapshot(cx);
15437 let mut selected_sibling = false;
15438
15439 let new_selections = old_selections
15440 .iter()
15441 .map(|selection| {
15442 let old_range = selection.start..selection.end;
15443
15444 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15445 let new_range = node.byte_range();
15446 selected_sibling = true;
15447 Selection {
15448 id: selection.id,
15449 start: new_range.start,
15450 end: new_range.end,
15451 goal: SelectionGoal::None,
15452 reversed: selection.reversed,
15453 }
15454 } else {
15455 selection.clone()
15456 }
15457 })
15458 .collect::<Vec<_>>();
15459
15460 if selected_sibling {
15461 self.change_selections(
15462 SelectionEffects::scroll(Autoscroll::fit()),
15463 window,
15464 cx,
15465 |s| {
15466 s.select(new_selections);
15467 },
15468 );
15469 }
15470 }
15471
15472 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15473 if !EditorSettings::get_global(cx).gutter.runnables {
15474 self.clear_tasks();
15475 return Task::ready(());
15476 }
15477 let project = self.project().map(Entity::downgrade);
15478 let task_sources = self.lsp_task_sources(cx);
15479 let multi_buffer = self.buffer.downgrade();
15480 cx.spawn_in(window, async move |editor, cx| {
15481 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15482 let Some(project) = project.and_then(|p| p.upgrade()) else {
15483 return;
15484 };
15485 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15486 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15487 }) else {
15488 return;
15489 };
15490
15491 let hide_runnables = project
15492 .update(cx, |project, _| project.is_via_collab())
15493 .unwrap_or(true);
15494 if hide_runnables {
15495 return;
15496 }
15497 let new_rows =
15498 cx.background_spawn({
15499 let snapshot = display_snapshot.clone();
15500 async move {
15501 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15502 }
15503 })
15504 .await;
15505 let Ok(lsp_tasks) =
15506 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15507 else {
15508 return;
15509 };
15510 let lsp_tasks = lsp_tasks.await;
15511
15512 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15513 lsp_tasks
15514 .into_iter()
15515 .flat_map(|(kind, tasks)| {
15516 tasks.into_iter().filter_map(move |(location, task)| {
15517 Some((kind.clone(), location?, task))
15518 })
15519 })
15520 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15521 let buffer = location.target.buffer;
15522 let buffer_snapshot = buffer.read(cx).snapshot();
15523 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15524 |(excerpt_id, snapshot, _)| {
15525 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15526 display_snapshot
15527 .buffer_snapshot()
15528 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15529 } else {
15530 None
15531 }
15532 },
15533 );
15534 if let Some(offset) = offset {
15535 let task_buffer_range =
15536 location.target.range.to_point(&buffer_snapshot);
15537 let context_buffer_range =
15538 task_buffer_range.to_offset(&buffer_snapshot);
15539 let context_range = BufferOffset(context_buffer_range.start)
15540 ..BufferOffset(context_buffer_range.end);
15541
15542 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15543 .or_insert_with(|| RunnableTasks {
15544 templates: Vec::new(),
15545 offset,
15546 column: task_buffer_range.start.column,
15547 extra_variables: HashMap::default(),
15548 context_range,
15549 })
15550 .templates
15551 .push((kind, task.original_task().clone()));
15552 }
15553
15554 acc
15555 })
15556 }) else {
15557 return;
15558 };
15559
15560 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15561 buffer.language_settings(cx).tasks.prefer_lsp
15562 }) else {
15563 return;
15564 };
15565
15566 let rows = Self::runnable_rows(
15567 project,
15568 display_snapshot,
15569 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15570 new_rows,
15571 cx.clone(),
15572 )
15573 .await;
15574 editor
15575 .update(cx, |editor, _| {
15576 editor.clear_tasks();
15577 for (key, mut value) in rows {
15578 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15579 value.templates.extend(lsp_tasks.templates);
15580 }
15581
15582 editor.insert_tasks(key, value);
15583 }
15584 for (key, value) in lsp_tasks_by_rows {
15585 editor.insert_tasks(key, value);
15586 }
15587 })
15588 .ok();
15589 })
15590 }
15591 fn fetch_runnable_ranges(
15592 snapshot: &DisplaySnapshot,
15593 range: Range<Anchor>,
15594 ) -> Vec<language::RunnableRange> {
15595 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15596 }
15597
15598 fn runnable_rows(
15599 project: Entity<Project>,
15600 snapshot: DisplaySnapshot,
15601 prefer_lsp: bool,
15602 runnable_ranges: Vec<RunnableRange>,
15603 cx: AsyncWindowContext,
15604 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15605 cx.spawn(async move |cx| {
15606 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15607 for mut runnable in runnable_ranges {
15608 let Some(tasks) = cx
15609 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15610 .ok()
15611 else {
15612 continue;
15613 };
15614 let mut tasks = tasks.await;
15615
15616 if prefer_lsp {
15617 tasks.retain(|(task_kind, _)| {
15618 !matches!(task_kind, TaskSourceKind::Language { .. })
15619 });
15620 }
15621 if tasks.is_empty() {
15622 continue;
15623 }
15624
15625 let point = runnable
15626 .run_range
15627 .start
15628 .to_point(&snapshot.buffer_snapshot());
15629 let Some(row) = snapshot
15630 .buffer_snapshot()
15631 .buffer_line_for_row(MultiBufferRow(point.row))
15632 .map(|(_, range)| range.start.row)
15633 else {
15634 continue;
15635 };
15636
15637 let context_range =
15638 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15639 runnable_rows.push((
15640 (runnable.buffer_id, row),
15641 RunnableTasks {
15642 templates: tasks,
15643 offset: snapshot
15644 .buffer_snapshot()
15645 .anchor_before(runnable.run_range.start),
15646 context_range,
15647 column: point.column,
15648 extra_variables: runnable.extra_captures,
15649 },
15650 ));
15651 }
15652 runnable_rows
15653 })
15654 }
15655
15656 fn templates_with_tags(
15657 project: &Entity<Project>,
15658 runnable: &mut Runnable,
15659 cx: &mut App,
15660 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15661 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15662 let (worktree_id, file) = project
15663 .buffer_for_id(runnable.buffer, cx)
15664 .and_then(|buffer| buffer.read(cx).file())
15665 .map(|file| (file.worktree_id(cx), file.clone()))
15666 .unzip();
15667
15668 (
15669 project.task_store().read(cx).task_inventory().cloned(),
15670 worktree_id,
15671 file,
15672 )
15673 });
15674
15675 let tags = mem::take(&mut runnable.tags);
15676 let language = runnable.language.clone();
15677 cx.spawn(async move |cx| {
15678 let mut templates_with_tags = Vec::new();
15679 if let Some(inventory) = inventory {
15680 for RunnableTag(tag) in tags {
15681 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15682 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15683 }) else {
15684 return templates_with_tags;
15685 };
15686 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15687 move |(_, template)| {
15688 template.tags.iter().any(|source_tag| source_tag == &tag)
15689 },
15690 ));
15691 }
15692 }
15693 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15694
15695 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15696 // Strongest source wins; if we have worktree tag binding, prefer that to
15697 // global and language bindings;
15698 // if we have a global binding, prefer that to language binding.
15699 let first_mismatch = templates_with_tags
15700 .iter()
15701 .position(|(tag_source, _)| tag_source != leading_tag_source);
15702 if let Some(index) = first_mismatch {
15703 templates_with_tags.truncate(index);
15704 }
15705 }
15706
15707 templates_with_tags
15708 })
15709 }
15710
15711 pub fn move_to_enclosing_bracket(
15712 &mut self,
15713 _: &MoveToEnclosingBracket,
15714 window: &mut Window,
15715 cx: &mut Context<Self>,
15716 ) {
15717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15718 self.change_selections(Default::default(), window, cx, |s| {
15719 s.move_offsets_with(|snapshot, selection| {
15720 let Some(enclosing_bracket_ranges) =
15721 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15722 else {
15723 return;
15724 };
15725
15726 let mut best_length = usize::MAX;
15727 let mut best_inside = false;
15728 let mut best_in_bracket_range = false;
15729 let mut best_destination = None;
15730 for (open, close) in enclosing_bracket_ranges {
15731 let close = close.to_inclusive();
15732 let length = close.end() - open.start;
15733 let inside = selection.start >= open.end && selection.end <= *close.start();
15734 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15735 || close.contains(&selection.head());
15736
15737 // If best is next to a bracket and current isn't, skip
15738 if !in_bracket_range && best_in_bracket_range {
15739 continue;
15740 }
15741
15742 // Prefer smaller lengths unless best is inside and current isn't
15743 if length > best_length && (best_inside || !inside) {
15744 continue;
15745 }
15746
15747 best_length = length;
15748 best_inside = inside;
15749 best_in_bracket_range = in_bracket_range;
15750 best_destination = Some(
15751 if close.contains(&selection.start) && close.contains(&selection.end) {
15752 if inside { open.end } else { open.start }
15753 } else if inside {
15754 *close.start()
15755 } else {
15756 *close.end()
15757 },
15758 );
15759 }
15760
15761 if let Some(destination) = best_destination {
15762 selection.collapse_to(destination, SelectionGoal::None);
15763 }
15764 })
15765 });
15766 }
15767
15768 pub fn undo_selection(
15769 &mut self,
15770 _: &UndoSelection,
15771 window: &mut Window,
15772 cx: &mut Context<Self>,
15773 ) {
15774 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15775 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15776 self.selection_history.mode = SelectionHistoryMode::Undoing;
15777 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15778 this.end_selection(window, cx);
15779 this.change_selections(
15780 SelectionEffects::scroll(Autoscroll::newest()),
15781 window,
15782 cx,
15783 |s| s.select_anchors(entry.selections.to_vec()),
15784 );
15785 });
15786 self.selection_history.mode = SelectionHistoryMode::Normal;
15787
15788 self.select_next_state = entry.select_next_state;
15789 self.select_prev_state = entry.select_prev_state;
15790 self.add_selections_state = entry.add_selections_state;
15791 }
15792 }
15793
15794 pub fn redo_selection(
15795 &mut self,
15796 _: &RedoSelection,
15797 window: &mut Window,
15798 cx: &mut Context<Self>,
15799 ) {
15800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15801 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15802 self.selection_history.mode = SelectionHistoryMode::Redoing;
15803 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15804 this.end_selection(window, cx);
15805 this.change_selections(
15806 SelectionEffects::scroll(Autoscroll::newest()),
15807 window,
15808 cx,
15809 |s| s.select_anchors(entry.selections.to_vec()),
15810 );
15811 });
15812 self.selection_history.mode = SelectionHistoryMode::Normal;
15813
15814 self.select_next_state = entry.select_next_state;
15815 self.select_prev_state = entry.select_prev_state;
15816 self.add_selections_state = entry.add_selections_state;
15817 }
15818 }
15819
15820 pub fn expand_excerpts(
15821 &mut self,
15822 action: &ExpandExcerpts,
15823 _: &mut Window,
15824 cx: &mut Context<Self>,
15825 ) {
15826 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15827 }
15828
15829 pub fn expand_excerpts_down(
15830 &mut self,
15831 action: &ExpandExcerptsDown,
15832 _: &mut Window,
15833 cx: &mut Context<Self>,
15834 ) {
15835 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15836 }
15837
15838 pub fn expand_excerpts_up(
15839 &mut self,
15840 action: &ExpandExcerptsUp,
15841 _: &mut Window,
15842 cx: &mut Context<Self>,
15843 ) {
15844 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15845 }
15846
15847 pub fn expand_excerpts_for_direction(
15848 &mut self,
15849 lines: u32,
15850 direction: ExpandExcerptDirection,
15851
15852 cx: &mut Context<Self>,
15853 ) {
15854 let selections = self.selections.disjoint_anchors_arc();
15855
15856 let lines = if lines == 0 {
15857 EditorSettings::get_global(cx).expand_excerpt_lines
15858 } else {
15859 lines
15860 };
15861
15862 self.buffer.update(cx, |buffer, cx| {
15863 let snapshot = buffer.snapshot(cx);
15864 let mut excerpt_ids = selections
15865 .iter()
15866 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15867 .collect::<Vec<_>>();
15868 excerpt_ids.sort();
15869 excerpt_ids.dedup();
15870 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15871 })
15872 }
15873
15874 pub fn expand_excerpt(
15875 &mut self,
15876 excerpt: ExcerptId,
15877 direction: ExpandExcerptDirection,
15878 window: &mut Window,
15879 cx: &mut Context<Self>,
15880 ) {
15881 let current_scroll_position = self.scroll_position(cx);
15882 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15883 let mut should_scroll_up = false;
15884
15885 if direction == ExpandExcerptDirection::Down {
15886 let multi_buffer = self.buffer.read(cx);
15887 let snapshot = multi_buffer.snapshot(cx);
15888 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15889 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15890 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15891 {
15892 let buffer_snapshot = buffer.read(cx).snapshot();
15893 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15894 let last_row = buffer_snapshot.max_point().row;
15895 let lines_below = last_row.saturating_sub(excerpt_end_row);
15896 should_scroll_up = lines_below >= lines_to_expand;
15897 }
15898 }
15899
15900 self.buffer.update(cx, |buffer, cx| {
15901 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15902 });
15903
15904 if should_scroll_up {
15905 let new_scroll_position =
15906 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15907 self.set_scroll_position(new_scroll_position, window, cx);
15908 }
15909 }
15910
15911 pub fn go_to_singleton_buffer_point(
15912 &mut self,
15913 point: Point,
15914 window: &mut Window,
15915 cx: &mut Context<Self>,
15916 ) {
15917 self.go_to_singleton_buffer_range(point..point, window, cx);
15918 }
15919
15920 pub fn go_to_singleton_buffer_range(
15921 &mut self,
15922 range: Range<Point>,
15923 window: &mut Window,
15924 cx: &mut Context<Self>,
15925 ) {
15926 let multibuffer = self.buffer().read(cx);
15927 let Some(buffer) = multibuffer.as_singleton() else {
15928 return;
15929 };
15930 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15931 return;
15932 };
15933 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15934 return;
15935 };
15936 self.change_selections(
15937 SelectionEffects::default().nav_history(true),
15938 window,
15939 cx,
15940 |s| s.select_anchor_ranges([start..end]),
15941 );
15942 }
15943
15944 pub fn go_to_diagnostic(
15945 &mut self,
15946 action: &GoToDiagnostic,
15947 window: &mut Window,
15948 cx: &mut Context<Self>,
15949 ) {
15950 if !self.diagnostics_enabled() {
15951 return;
15952 }
15953 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15954 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15955 }
15956
15957 pub fn go_to_prev_diagnostic(
15958 &mut self,
15959 action: &GoToPreviousDiagnostic,
15960 window: &mut Window,
15961 cx: &mut Context<Self>,
15962 ) {
15963 if !self.diagnostics_enabled() {
15964 return;
15965 }
15966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15967 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15968 }
15969
15970 pub fn go_to_diagnostic_impl(
15971 &mut self,
15972 direction: Direction,
15973 severity: GoToDiagnosticSeverityFilter,
15974 window: &mut Window,
15975 cx: &mut Context<Self>,
15976 ) {
15977 let buffer = self.buffer.read(cx).snapshot(cx);
15978 let selection = self.selections.newest::<usize>(cx);
15979
15980 let mut active_group_id = None;
15981 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15982 && active_group.active_range.start.to_offset(&buffer) == selection.start
15983 {
15984 active_group_id = Some(active_group.group_id);
15985 }
15986
15987 fn filtered<'a>(
15988 snapshot: EditorSnapshot,
15989 severity: GoToDiagnosticSeverityFilter,
15990 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15991 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15992 diagnostics
15993 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15994 .filter(|entry| entry.range.start != entry.range.end)
15995 .filter(|entry| !entry.diagnostic.is_unnecessary)
15996 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15997 }
15998
15999 let snapshot = self.snapshot(window, cx);
16000 let before = filtered(
16001 snapshot.clone(),
16002 severity,
16003 buffer
16004 .diagnostics_in_range(0..selection.start)
16005 .filter(|entry| entry.range.start <= selection.start),
16006 );
16007 let after = filtered(
16008 snapshot,
16009 severity,
16010 buffer
16011 .diagnostics_in_range(selection.start..buffer.len())
16012 .filter(|entry| entry.range.start >= selection.start),
16013 );
16014
16015 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16016 if direction == Direction::Prev {
16017 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16018 {
16019 for diagnostic in prev_diagnostics.into_iter().rev() {
16020 if diagnostic.range.start != selection.start
16021 || active_group_id
16022 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16023 {
16024 found = Some(diagnostic);
16025 break 'outer;
16026 }
16027 }
16028 }
16029 } else {
16030 for diagnostic in after.chain(before) {
16031 if diagnostic.range.start != selection.start
16032 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16033 {
16034 found = Some(diagnostic);
16035 break;
16036 }
16037 }
16038 }
16039 let Some(next_diagnostic) = found else {
16040 return;
16041 };
16042
16043 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16044 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16045 return;
16046 };
16047 self.change_selections(Default::default(), window, cx, |s| {
16048 s.select_ranges(vec![
16049 next_diagnostic.range.start..next_diagnostic.range.start,
16050 ])
16051 });
16052 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16053 self.refresh_edit_prediction(false, true, window, cx);
16054 }
16055
16056 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16058 let snapshot = self.snapshot(window, cx);
16059 let selection = self.selections.newest::<Point>(cx);
16060 self.go_to_hunk_before_or_after_position(
16061 &snapshot,
16062 selection.head(),
16063 Direction::Next,
16064 window,
16065 cx,
16066 );
16067 }
16068
16069 pub fn go_to_hunk_before_or_after_position(
16070 &mut self,
16071 snapshot: &EditorSnapshot,
16072 position: Point,
16073 direction: Direction,
16074 window: &mut Window,
16075 cx: &mut Context<Editor>,
16076 ) {
16077 let row = if direction == Direction::Next {
16078 self.hunk_after_position(snapshot, position)
16079 .map(|hunk| hunk.row_range.start)
16080 } else {
16081 self.hunk_before_position(snapshot, position)
16082 };
16083
16084 if let Some(row) = row {
16085 let destination = Point::new(row.0, 0);
16086 let autoscroll = Autoscroll::center();
16087
16088 self.unfold_ranges(&[destination..destination], false, false, cx);
16089 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16090 s.select_ranges([destination..destination]);
16091 });
16092 }
16093 }
16094
16095 fn hunk_after_position(
16096 &mut self,
16097 snapshot: &EditorSnapshot,
16098 position: Point,
16099 ) -> Option<MultiBufferDiffHunk> {
16100 snapshot
16101 .buffer_snapshot()
16102 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16103 .find(|hunk| hunk.row_range.start.0 > position.row)
16104 .or_else(|| {
16105 snapshot
16106 .buffer_snapshot()
16107 .diff_hunks_in_range(Point::zero()..position)
16108 .find(|hunk| hunk.row_range.end.0 < position.row)
16109 })
16110 }
16111
16112 fn go_to_prev_hunk(
16113 &mut self,
16114 _: &GoToPreviousHunk,
16115 window: &mut Window,
16116 cx: &mut Context<Self>,
16117 ) {
16118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16119 let snapshot = self.snapshot(window, cx);
16120 let selection = self.selections.newest::<Point>(cx);
16121 self.go_to_hunk_before_or_after_position(
16122 &snapshot,
16123 selection.head(),
16124 Direction::Prev,
16125 window,
16126 cx,
16127 );
16128 }
16129
16130 fn hunk_before_position(
16131 &mut self,
16132 snapshot: &EditorSnapshot,
16133 position: Point,
16134 ) -> Option<MultiBufferRow> {
16135 snapshot
16136 .buffer_snapshot()
16137 .diff_hunk_before(position)
16138 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16139 }
16140
16141 fn go_to_next_change(
16142 &mut self,
16143 _: &GoToNextChange,
16144 window: &mut Window,
16145 cx: &mut Context<Self>,
16146 ) {
16147 if let Some(selections) = self
16148 .change_list
16149 .next_change(1, Direction::Next)
16150 .map(|s| s.to_vec())
16151 {
16152 self.change_selections(Default::default(), window, cx, |s| {
16153 let map = s.display_map();
16154 s.select_display_ranges(selections.iter().map(|a| {
16155 let point = a.to_display_point(&map);
16156 point..point
16157 }))
16158 })
16159 }
16160 }
16161
16162 fn go_to_previous_change(
16163 &mut self,
16164 _: &GoToPreviousChange,
16165 window: &mut Window,
16166 cx: &mut Context<Self>,
16167 ) {
16168 if let Some(selections) = self
16169 .change_list
16170 .next_change(1, Direction::Prev)
16171 .map(|s| s.to_vec())
16172 {
16173 self.change_selections(Default::default(), window, cx, |s| {
16174 let map = s.display_map();
16175 s.select_display_ranges(selections.iter().map(|a| {
16176 let point = a.to_display_point(&map);
16177 point..point
16178 }))
16179 })
16180 }
16181 }
16182
16183 pub fn go_to_next_document_highlight(
16184 &mut self,
16185 _: &GoToNextDocumentHighlight,
16186 window: &mut Window,
16187 cx: &mut Context<Self>,
16188 ) {
16189 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16190 }
16191
16192 pub fn go_to_prev_document_highlight(
16193 &mut self,
16194 _: &GoToPreviousDocumentHighlight,
16195 window: &mut Window,
16196 cx: &mut Context<Self>,
16197 ) {
16198 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16199 }
16200
16201 pub fn go_to_document_highlight_before_or_after_position(
16202 &mut self,
16203 direction: Direction,
16204 window: &mut Window,
16205 cx: &mut Context<Editor>,
16206 ) {
16207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16208 let snapshot = self.snapshot(window, cx);
16209 let buffer = &snapshot.buffer_snapshot();
16210 let position = self.selections.newest::<Point>(cx).head();
16211 let anchor_position = buffer.anchor_after(position);
16212
16213 // Get all document highlights (both read and write)
16214 let mut all_highlights = Vec::new();
16215
16216 if let Some((_, read_highlights)) = self
16217 .background_highlights
16218 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16219 {
16220 all_highlights.extend(read_highlights.iter());
16221 }
16222
16223 if let Some((_, write_highlights)) = self
16224 .background_highlights
16225 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16226 {
16227 all_highlights.extend(write_highlights.iter());
16228 }
16229
16230 if all_highlights.is_empty() {
16231 return;
16232 }
16233
16234 // Sort highlights by position
16235 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16236
16237 let target_highlight = match direction {
16238 Direction::Next => {
16239 // Find the first highlight after the current position
16240 all_highlights
16241 .iter()
16242 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16243 }
16244 Direction::Prev => {
16245 // Find the last highlight before the current position
16246 all_highlights
16247 .iter()
16248 .rev()
16249 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16250 }
16251 };
16252
16253 if let Some(highlight) = target_highlight {
16254 let destination = highlight.start.to_point(buffer);
16255 let autoscroll = Autoscroll::center();
16256
16257 self.unfold_ranges(&[destination..destination], false, false, cx);
16258 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16259 s.select_ranges([destination..destination]);
16260 });
16261 }
16262 }
16263
16264 fn go_to_line<T: 'static>(
16265 &mut self,
16266 position: Anchor,
16267 highlight_color: Option<Hsla>,
16268 window: &mut Window,
16269 cx: &mut Context<Self>,
16270 ) {
16271 let snapshot = self.snapshot(window, cx).display_snapshot;
16272 let position = position.to_point(&snapshot.buffer_snapshot());
16273 let start = snapshot
16274 .buffer_snapshot()
16275 .clip_point(Point::new(position.row, 0), Bias::Left);
16276 let end = start + Point::new(1, 0);
16277 let start = snapshot.buffer_snapshot().anchor_before(start);
16278 let end = snapshot.buffer_snapshot().anchor_before(end);
16279
16280 self.highlight_rows::<T>(
16281 start..end,
16282 highlight_color
16283 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16284 Default::default(),
16285 cx,
16286 );
16287
16288 if self.buffer.read(cx).is_singleton() {
16289 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16290 }
16291 }
16292
16293 pub fn go_to_definition(
16294 &mut self,
16295 _: &GoToDefinition,
16296 window: &mut Window,
16297 cx: &mut Context<Self>,
16298 ) -> Task<Result<Navigated>> {
16299 let definition =
16300 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16301 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16302 cx.spawn_in(window, async move |editor, cx| {
16303 if definition.await? == Navigated::Yes {
16304 return Ok(Navigated::Yes);
16305 }
16306 match fallback_strategy {
16307 GoToDefinitionFallback::None => Ok(Navigated::No),
16308 GoToDefinitionFallback::FindAllReferences => {
16309 match editor.update_in(cx, |editor, window, cx| {
16310 editor.find_all_references(&FindAllReferences, window, cx)
16311 })? {
16312 Some(references) => references.await,
16313 None => Ok(Navigated::No),
16314 }
16315 }
16316 }
16317 })
16318 }
16319
16320 pub fn go_to_declaration(
16321 &mut self,
16322 _: &GoToDeclaration,
16323 window: &mut Window,
16324 cx: &mut Context<Self>,
16325 ) -> Task<Result<Navigated>> {
16326 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16327 }
16328
16329 pub fn go_to_declaration_split(
16330 &mut self,
16331 _: &GoToDeclaration,
16332 window: &mut Window,
16333 cx: &mut Context<Self>,
16334 ) -> Task<Result<Navigated>> {
16335 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16336 }
16337
16338 pub fn go_to_implementation(
16339 &mut self,
16340 _: &GoToImplementation,
16341 window: &mut Window,
16342 cx: &mut Context<Self>,
16343 ) -> Task<Result<Navigated>> {
16344 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16345 }
16346
16347 pub fn go_to_implementation_split(
16348 &mut self,
16349 _: &GoToImplementationSplit,
16350 window: &mut Window,
16351 cx: &mut Context<Self>,
16352 ) -> Task<Result<Navigated>> {
16353 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16354 }
16355
16356 pub fn go_to_type_definition(
16357 &mut self,
16358 _: &GoToTypeDefinition,
16359 window: &mut Window,
16360 cx: &mut Context<Self>,
16361 ) -> Task<Result<Navigated>> {
16362 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16363 }
16364
16365 pub fn go_to_definition_split(
16366 &mut self,
16367 _: &GoToDefinitionSplit,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) -> Task<Result<Navigated>> {
16371 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16372 }
16373
16374 pub fn go_to_type_definition_split(
16375 &mut self,
16376 _: &GoToTypeDefinitionSplit,
16377 window: &mut Window,
16378 cx: &mut Context<Self>,
16379 ) -> Task<Result<Navigated>> {
16380 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16381 }
16382
16383 fn go_to_definition_of_kind(
16384 &mut self,
16385 kind: GotoDefinitionKind,
16386 split: bool,
16387 window: &mut Window,
16388 cx: &mut Context<Self>,
16389 ) -> Task<Result<Navigated>> {
16390 let Some(provider) = self.semantics_provider.clone() else {
16391 return Task::ready(Ok(Navigated::No));
16392 };
16393 let head = self.selections.newest::<usize>(cx).head();
16394 let buffer = self.buffer.read(cx);
16395 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16396 return Task::ready(Ok(Navigated::No));
16397 };
16398 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16399 return Task::ready(Ok(Navigated::No));
16400 };
16401
16402 cx.spawn_in(window, async move |editor, cx| {
16403 let Some(definitions) = definitions.await? else {
16404 return Ok(Navigated::No);
16405 };
16406 let navigated = editor
16407 .update_in(cx, |editor, window, cx| {
16408 editor.navigate_to_hover_links(
16409 Some(kind),
16410 definitions
16411 .into_iter()
16412 .filter(|location| {
16413 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16414 })
16415 .map(HoverLink::Text)
16416 .collect::<Vec<_>>(),
16417 split,
16418 window,
16419 cx,
16420 )
16421 })?
16422 .await?;
16423 anyhow::Ok(navigated)
16424 })
16425 }
16426
16427 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16428 let selection = self.selections.newest_anchor();
16429 let head = selection.head();
16430 let tail = selection.tail();
16431
16432 let Some((buffer, start_position)) =
16433 self.buffer.read(cx).text_anchor_for_position(head, cx)
16434 else {
16435 return;
16436 };
16437
16438 let end_position = if head != tail {
16439 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16440 return;
16441 };
16442 Some(pos)
16443 } else {
16444 None
16445 };
16446
16447 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16448 let url = if let Some(end_pos) = end_position {
16449 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16450 } else {
16451 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16452 };
16453
16454 if let Some(url) = url {
16455 cx.update(|window, cx| {
16456 if parse_zed_link(&url, cx).is_some() {
16457 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16458 } else {
16459 cx.open_url(&url);
16460 }
16461 })?;
16462 }
16463
16464 anyhow::Ok(())
16465 });
16466
16467 url_finder.detach();
16468 }
16469
16470 pub fn open_selected_filename(
16471 &mut self,
16472 _: &OpenSelectedFilename,
16473 window: &mut Window,
16474 cx: &mut Context<Self>,
16475 ) {
16476 let Some(workspace) = self.workspace() else {
16477 return;
16478 };
16479
16480 let position = self.selections.newest_anchor().head();
16481
16482 let Some((buffer, buffer_position)) =
16483 self.buffer.read(cx).text_anchor_for_position(position, cx)
16484 else {
16485 return;
16486 };
16487
16488 let project = self.project.clone();
16489
16490 cx.spawn_in(window, async move |_, cx| {
16491 let result = find_file(&buffer, project, buffer_position, cx).await;
16492
16493 if let Some((_, path)) = result {
16494 workspace
16495 .update_in(cx, |workspace, window, cx| {
16496 workspace.open_resolved_path(path, window, cx)
16497 })?
16498 .await?;
16499 }
16500 anyhow::Ok(())
16501 })
16502 .detach();
16503 }
16504
16505 pub(crate) fn navigate_to_hover_links(
16506 &mut self,
16507 kind: Option<GotoDefinitionKind>,
16508 definitions: Vec<HoverLink>,
16509 split: bool,
16510 window: &mut Window,
16511 cx: &mut Context<Editor>,
16512 ) -> Task<Result<Navigated>> {
16513 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16514 let mut first_url_or_file = None;
16515 let definitions: Vec<_> = definitions
16516 .into_iter()
16517 .filter_map(|def| match def {
16518 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16519 HoverLink::InlayHint(lsp_location, server_id) => {
16520 let computation =
16521 self.compute_target_location(lsp_location, server_id, window, cx);
16522 Some(cx.background_spawn(computation))
16523 }
16524 HoverLink::Url(url) => {
16525 first_url_or_file = Some(Either::Left(url));
16526 None
16527 }
16528 HoverLink::File(path) => {
16529 first_url_or_file = Some(Either::Right(path));
16530 None
16531 }
16532 })
16533 .collect();
16534
16535 let workspace = self.workspace();
16536
16537 cx.spawn_in(window, async move |editor, cx| {
16538 let locations: Vec<Location> = future::join_all(definitions)
16539 .await
16540 .into_iter()
16541 .filter_map(|location| location.transpose())
16542 .collect::<Result<_>>()
16543 .context("location tasks")?;
16544 let mut locations = cx.update(|_, cx| {
16545 locations
16546 .into_iter()
16547 .map(|location| {
16548 let buffer = location.buffer.read(cx);
16549 (location.buffer, location.range.to_point(buffer))
16550 })
16551 .into_group_map()
16552 })?;
16553 let mut num_locations = 0;
16554 for ranges in locations.values_mut() {
16555 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16556 ranges.dedup();
16557 num_locations += ranges.len();
16558 }
16559
16560 if num_locations > 1 {
16561 let Some(workspace) = workspace else {
16562 return Ok(Navigated::No);
16563 };
16564
16565 let tab_kind = match kind {
16566 Some(GotoDefinitionKind::Implementation) => "Implementations",
16567 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16568 Some(GotoDefinitionKind::Declaration) => "Declarations",
16569 Some(GotoDefinitionKind::Type) => "Types",
16570 };
16571 let title = editor
16572 .update_in(cx, |_, _, cx| {
16573 let target = locations
16574 .iter()
16575 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16576 .map(|(buffer, location)| {
16577 buffer
16578 .read(cx)
16579 .text_for_range(location.clone())
16580 .collect::<String>()
16581 })
16582 .filter(|text| !text.contains('\n'))
16583 .unique()
16584 .take(3)
16585 .join(", ");
16586 if target.is_empty() {
16587 tab_kind.to_owned()
16588 } else {
16589 format!("{tab_kind} for {target}")
16590 }
16591 })
16592 .context("buffer title")?;
16593
16594 let opened = workspace
16595 .update_in(cx, |workspace, window, cx| {
16596 Self::open_locations_in_multibuffer(
16597 workspace,
16598 locations,
16599 title,
16600 split,
16601 MultibufferSelectionMode::First,
16602 window,
16603 cx,
16604 )
16605 })
16606 .is_ok();
16607
16608 anyhow::Ok(Navigated::from_bool(opened))
16609 } else if num_locations == 0 {
16610 // If there is one url or file, open it directly
16611 match first_url_or_file {
16612 Some(Either::Left(url)) => {
16613 cx.update(|_, cx| cx.open_url(&url))?;
16614 Ok(Navigated::Yes)
16615 }
16616 Some(Either::Right(path)) => {
16617 let Some(workspace) = workspace else {
16618 return Ok(Navigated::No);
16619 };
16620
16621 workspace
16622 .update_in(cx, |workspace, window, cx| {
16623 workspace.open_resolved_path(path, window, cx)
16624 })?
16625 .await?;
16626 Ok(Navigated::Yes)
16627 }
16628 None => Ok(Navigated::No),
16629 }
16630 } else {
16631 let Some(workspace) = workspace else {
16632 return Ok(Navigated::No);
16633 };
16634
16635 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16636 let target_range = target_ranges.first().unwrap().clone();
16637
16638 editor.update_in(cx, |editor, window, cx| {
16639 let range = target_range.to_point(target_buffer.read(cx));
16640 let range = editor.range_for_match(&range);
16641 let range = collapse_multiline_range(range);
16642
16643 if !split
16644 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16645 {
16646 editor.go_to_singleton_buffer_range(range, window, cx);
16647 } else {
16648 let pane = workspace.read(cx).active_pane().clone();
16649 window.defer(cx, move |window, cx| {
16650 let target_editor: Entity<Self> =
16651 workspace.update(cx, |workspace, cx| {
16652 let pane = if split {
16653 workspace.adjacent_pane(window, cx)
16654 } else {
16655 workspace.active_pane().clone()
16656 };
16657
16658 workspace.open_project_item(
16659 pane,
16660 target_buffer.clone(),
16661 true,
16662 true,
16663 window,
16664 cx,
16665 )
16666 });
16667 target_editor.update(cx, |target_editor, cx| {
16668 // When selecting a definition in a different buffer, disable the nav history
16669 // to avoid creating a history entry at the previous cursor location.
16670 pane.update(cx, |pane, _| pane.disable_history());
16671 target_editor.go_to_singleton_buffer_range(range, window, cx);
16672 pane.update(cx, |pane, _| pane.enable_history());
16673 });
16674 });
16675 }
16676 Navigated::Yes
16677 })
16678 }
16679 })
16680 }
16681
16682 fn compute_target_location(
16683 &self,
16684 lsp_location: lsp::Location,
16685 server_id: LanguageServerId,
16686 window: &mut Window,
16687 cx: &mut Context<Self>,
16688 ) -> Task<anyhow::Result<Option<Location>>> {
16689 let Some(project) = self.project.clone() else {
16690 return Task::ready(Ok(None));
16691 };
16692
16693 cx.spawn_in(window, async move |editor, cx| {
16694 let location_task = editor.update(cx, |_, cx| {
16695 project.update(cx, |project, cx| {
16696 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16697 })
16698 })?;
16699 let location = Some({
16700 let target_buffer_handle = location_task.await.context("open local buffer")?;
16701 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16702 let target_start = target_buffer
16703 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16704 let target_end = target_buffer
16705 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16706 target_buffer.anchor_after(target_start)
16707 ..target_buffer.anchor_before(target_end)
16708 })?;
16709 Location {
16710 buffer: target_buffer_handle,
16711 range,
16712 }
16713 });
16714 Ok(location)
16715 })
16716 }
16717
16718 pub fn find_all_references(
16719 &mut self,
16720 _: &FindAllReferences,
16721 window: &mut Window,
16722 cx: &mut Context<Self>,
16723 ) -> Option<Task<Result<Navigated>>> {
16724 let selection = self.selections.newest::<usize>(cx);
16725 let multi_buffer = self.buffer.read(cx);
16726 let head = selection.head();
16727
16728 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16729 let head_anchor = multi_buffer_snapshot.anchor_at(
16730 head,
16731 if head < selection.tail() {
16732 Bias::Right
16733 } else {
16734 Bias::Left
16735 },
16736 );
16737
16738 match self
16739 .find_all_references_task_sources
16740 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16741 {
16742 Ok(_) => {
16743 log::info!(
16744 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16745 );
16746 return None;
16747 }
16748 Err(i) => {
16749 self.find_all_references_task_sources.insert(i, head_anchor);
16750 }
16751 }
16752
16753 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16754 let workspace = self.workspace()?;
16755 let project = workspace.read(cx).project().clone();
16756 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16757 Some(cx.spawn_in(window, async move |editor, cx| {
16758 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16759 if let Ok(i) = editor
16760 .find_all_references_task_sources
16761 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16762 {
16763 editor.find_all_references_task_sources.remove(i);
16764 }
16765 });
16766
16767 let Some(locations) = references.await? else {
16768 return anyhow::Ok(Navigated::No);
16769 };
16770 let mut locations = cx.update(|_, cx| {
16771 locations
16772 .into_iter()
16773 .map(|location| {
16774 let buffer = location.buffer.read(cx);
16775 (location.buffer, location.range.to_point(buffer))
16776 })
16777 .into_group_map()
16778 })?;
16779 if locations.is_empty() {
16780 return anyhow::Ok(Navigated::No);
16781 }
16782 for ranges in locations.values_mut() {
16783 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16784 ranges.dedup();
16785 }
16786
16787 workspace.update_in(cx, |workspace, window, cx| {
16788 let target = locations
16789 .iter()
16790 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16791 .map(|(buffer, location)| {
16792 buffer
16793 .read(cx)
16794 .text_for_range(location.clone())
16795 .collect::<String>()
16796 })
16797 .filter(|text| !text.contains('\n'))
16798 .unique()
16799 .take(3)
16800 .join(", ");
16801 let title = if target.is_empty() {
16802 "References".to_owned()
16803 } else {
16804 format!("References to {target}")
16805 };
16806 Self::open_locations_in_multibuffer(
16807 workspace,
16808 locations,
16809 title,
16810 false,
16811 MultibufferSelectionMode::First,
16812 window,
16813 cx,
16814 );
16815 Navigated::Yes
16816 })
16817 }))
16818 }
16819
16820 /// Opens a multibuffer with the given project locations in it
16821 pub fn open_locations_in_multibuffer(
16822 workspace: &mut Workspace,
16823 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16824 title: String,
16825 split: bool,
16826 multibuffer_selection_mode: MultibufferSelectionMode,
16827 window: &mut Window,
16828 cx: &mut Context<Workspace>,
16829 ) {
16830 if locations.is_empty() {
16831 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16832 return;
16833 }
16834
16835 let capability = workspace.project().read(cx).capability();
16836 let mut ranges = <Vec<Range<Anchor>>>::new();
16837
16838 // a key to find existing multibuffer editors with the same set of locations
16839 // to prevent us from opening more and more multibuffer tabs for searches and the like
16840 let mut key = (title.clone(), vec![]);
16841 let excerpt_buffer = cx.new(|cx| {
16842 let key = &mut key.1;
16843 let mut multibuffer = MultiBuffer::new(capability);
16844 for (buffer, mut ranges_for_buffer) in locations {
16845 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16846 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16847 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16848 PathKey::for_buffer(&buffer, cx),
16849 buffer.clone(),
16850 ranges_for_buffer,
16851 multibuffer_context_lines(cx),
16852 cx,
16853 );
16854 ranges.extend(new_ranges)
16855 }
16856
16857 multibuffer.with_title(title)
16858 });
16859 let existing = workspace.active_pane().update(cx, |pane, cx| {
16860 pane.items()
16861 .filter_map(|item| item.downcast::<Editor>())
16862 .find(|editor| {
16863 editor
16864 .read(cx)
16865 .lookup_key
16866 .as_ref()
16867 .and_then(|it| {
16868 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16869 })
16870 .is_some_and(|it| *it == key)
16871 })
16872 });
16873 let editor = existing.unwrap_or_else(|| {
16874 cx.new(|cx| {
16875 let mut editor = Editor::for_multibuffer(
16876 excerpt_buffer,
16877 Some(workspace.project().clone()),
16878 window,
16879 cx,
16880 );
16881 editor.lookup_key = Some(Box::new(key));
16882 editor
16883 })
16884 });
16885 editor.update(cx, |editor, cx| {
16886 match multibuffer_selection_mode {
16887 MultibufferSelectionMode::First => {
16888 if let Some(first_range) = ranges.first() {
16889 editor.change_selections(
16890 SelectionEffects::no_scroll(),
16891 window,
16892 cx,
16893 |selections| {
16894 selections.clear_disjoint();
16895 selections
16896 .select_anchor_ranges(std::iter::once(first_range.clone()));
16897 },
16898 );
16899 }
16900 editor.highlight_background::<Self>(
16901 &ranges,
16902 |theme| theme.colors().editor_highlighted_line_background,
16903 cx,
16904 );
16905 }
16906 MultibufferSelectionMode::All => {
16907 editor.change_selections(
16908 SelectionEffects::no_scroll(),
16909 window,
16910 cx,
16911 |selections| {
16912 selections.clear_disjoint();
16913 selections.select_anchor_ranges(ranges);
16914 },
16915 );
16916 }
16917 }
16918 editor.register_buffers_with_language_servers(cx);
16919 });
16920
16921 let item = Box::new(editor);
16922 let item_id = item.item_id();
16923
16924 if split {
16925 let pane = workspace.adjacent_pane(window, cx);
16926 workspace.add_item(pane, item, None, true, true, window, cx);
16927 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16928 let (preview_item_id, preview_item_idx) =
16929 workspace.active_pane().read_with(cx, |pane, _| {
16930 (pane.preview_item_id(), pane.preview_item_idx())
16931 });
16932
16933 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16934
16935 if let Some(preview_item_id) = preview_item_id {
16936 workspace.active_pane().update(cx, |pane, cx| {
16937 pane.remove_item(preview_item_id, false, false, window, cx);
16938 });
16939 }
16940 } else {
16941 workspace.add_item_to_active_pane(item, None, true, window, cx);
16942 }
16943 workspace.active_pane().update(cx, |pane, cx| {
16944 pane.set_preview_item_id(Some(item_id), cx);
16945 });
16946 }
16947
16948 pub fn rename(
16949 &mut self,
16950 _: &Rename,
16951 window: &mut Window,
16952 cx: &mut Context<Self>,
16953 ) -> Option<Task<Result<()>>> {
16954 use language::ToOffset as _;
16955
16956 let provider = self.semantics_provider.clone()?;
16957 let selection = self.selections.newest_anchor().clone();
16958 let (cursor_buffer, cursor_buffer_position) = self
16959 .buffer
16960 .read(cx)
16961 .text_anchor_for_position(selection.head(), cx)?;
16962 let (tail_buffer, cursor_buffer_position_end) = self
16963 .buffer
16964 .read(cx)
16965 .text_anchor_for_position(selection.tail(), cx)?;
16966 if tail_buffer != cursor_buffer {
16967 return None;
16968 }
16969
16970 let snapshot = cursor_buffer.read(cx).snapshot();
16971 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16972 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16973 let prepare_rename = provider
16974 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16975 .unwrap_or_else(|| Task::ready(Ok(None)));
16976 drop(snapshot);
16977
16978 Some(cx.spawn_in(window, async move |this, cx| {
16979 let rename_range = if let Some(range) = prepare_rename.await? {
16980 Some(range)
16981 } else {
16982 this.update(cx, |this, cx| {
16983 let buffer = this.buffer.read(cx).snapshot(cx);
16984 let mut buffer_highlights = this
16985 .document_highlights_for_position(selection.head(), &buffer)
16986 .filter(|highlight| {
16987 highlight.start.excerpt_id == selection.head().excerpt_id
16988 && highlight.end.excerpt_id == selection.head().excerpt_id
16989 });
16990 buffer_highlights
16991 .next()
16992 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16993 })?
16994 };
16995 if let Some(rename_range) = rename_range {
16996 this.update_in(cx, |this, window, cx| {
16997 let snapshot = cursor_buffer.read(cx).snapshot();
16998 let rename_buffer_range = rename_range.to_offset(&snapshot);
16999 let cursor_offset_in_rename_range =
17000 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17001 let cursor_offset_in_rename_range_end =
17002 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17003
17004 this.take_rename(false, window, cx);
17005 let buffer = this.buffer.read(cx).read(cx);
17006 let cursor_offset = selection.head().to_offset(&buffer);
17007 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17008 let rename_end = rename_start + rename_buffer_range.len();
17009 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17010 let mut old_highlight_id = None;
17011 let old_name: Arc<str> = buffer
17012 .chunks(rename_start..rename_end, true)
17013 .map(|chunk| {
17014 if old_highlight_id.is_none() {
17015 old_highlight_id = chunk.syntax_highlight_id;
17016 }
17017 chunk.text
17018 })
17019 .collect::<String>()
17020 .into();
17021
17022 drop(buffer);
17023
17024 // Position the selection in the rename editor so that it matches the current selection.
17025 this.show_local_selections = false;
17026 let rename_editor = cx.new(|cx| {
17027 let mut editor = Editor::single_line(window, cx);
17028 editor.buffer.update(cx, |buffer, cx| {
17029 buffer.edit([(0..0, old_name.clone())], None, cx)
17030 });
17031 let rename_selection_range = match cursor_offset_in_rename_range
17032 .cmp(&cursor_offset_in_rename_range_end)
17033 {
17034 Ordering::Equal => {
17035 editor.select_all(&SelectAll, window, cx);
17036 return editor;
17037 }
17038 Ordering::Less => {
17039 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17040 }
17041 Ordering::Greater => {
17042 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17043 }
17044 };
17045 if rename_selection_range.end > old_name.len() {
17046 editor.select_all(&SelectAll, window, cx);
17047 } else {
17048 editor.change_selections(Default::default(), window, cx, |s| {
17049 s.select_ranges([rename_selection_range]);
17050 });
17051 }
17052 editor
17053 });
17054 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17055 if e == &EditorEvent::Focused {
17056 cx.emit(EditorEvent::FocusedIn)
17057 }
17058 })
17059 .detach();
17060
17061 let write_highlights =
17062 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17063 let read_highlights =
17064 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17065 let ranges = write_highlights
17066 .iter()
17067 .flat_map(|(_, ranges)| ranges.iter())
17068 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17069 .cloned()
17070 .collect();
17071
17072 this.highlight_text::<Rename>(
17073 ranges,
17074 HighlightStyle {
17075 fade_out: Some(0.6),
17076 ..Default::default()
17077 },
17078 cx,
17079 );
17080 let rename_focus_handle = rename_editor.focus_handle(cx);
17081 window.focus(&rename_focus_handle);
17082 let block_id = this.insert_blocks(
17083 [BlockProperties {
17084 style: BlockStyle::Flex,
17085 placement: BlockPlacement::Below(range.start),
17086 height: Some(1),
17087 render: Arc::new({
17088 let rename_editor = rename_editor.clone();
17089 move |cx: &mut BlockContext| {
17090 let mut text_style = cx.editor_style.text.clone();
17091 if let Some(highlight_style) = old_highlight_id
17092 .and_then(|h| h.style(&cx.editor_style.syntax))
17093 {
17094 text_style = text_style.highlight(highlight_style);
17095 }
17096 div()
17097 .block_mouse_except_scroll()
17098 .pl(cx.anchor_x)
17099 .child(EditorElement::new(
17100 &rename_editor,
17101 EditorStyle {
17102 background: cx.theme().system().transparent,
17103 local_player: cx.editor_style.local_player,
17104 text: text_style,
17105 scrollbar_width: cx.editor_style.scrollbar_width,
17106 syntax: cx.editor_style.syntax.clone(),
17107 status: cx.editor_style.status.clone(),
17108 inlay_hints_style: HighlightStyle {
17109 font_weight: Some(FontWeight::BOLD),
17110 ..make_inlay_hints_style(cx.app)
17111 },
17112 edit_prediction_styles: make_suggestion_styles(
17113 cx.app,
17114 ),
17115 ..EditorStyle::default()
17116 },
17117 ))
17118 .into_any_element()
17119 }
17120 }),
17121 priority: 0,
17122 }],
17123 Some(Autoscroll::fit()),
17124 cx,
17125 )[0];
17126 this.pending_rename = Some(RenameState {
17127 range,
17128 old_name,
17129 editor: rename_editor,
17130 block_id,
17131 });
17132 })?;
17133 }
17134
17135 Ok(())
17136 }))
17137 }
17138
17139 pub fn confirm_rename(
17140 &mut self,
17141 _: &ConfirmRename,
17142 window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) -> Option<Task<Result<()>>> {
17145 let rename = self.take_rename(false, window, cx)?;
17146 let workspace = self.workspace()?.downgrade();
17147 let (buffer, start) = self
17148 .buffer
17149 .read(cx)
17150 .text_anchor_for_position(rename.range.start, cx)?;
17151 let (end_buffer, _) = self
17152 .buffer
17153 .read(cx)
17154 .text_anchor_for_position(rename.range.end, cx)?;
17155 if buffer != end_buffer {
17156 return None;
17157 }
17158
17159 let old_name = rename.old_name;
17160 let new_name = rename.editor.read(cx).text(cx);
17161
17162 let rename = self.semantics_provider.as_ref()?.perform_rename(
17163 &buffer,
17164 start,
17165 new_name.clone(),
17166 cx,
17167 )?;
17168
17169 Some(cx.spawn_in(window, async move |editor, cx| {
17170 let project_transaction = rename.await?;
17171 Self::open_project_transaction(
17172 &editor,
17173 workspace,
17174 project_transaction,
17175 format!("Rename: {} → {}", old_name, new_name),
17176 cx,
17177 )
17178 .await?;
17179
17180 editor.update(cx, |editor, cx| {
17181 editor.refresh_document_highlights(cx);
17182 })?;
17183 Ok(())
17184 }))
17185 }
17186
17187 fn take_rename(
17188 &mut self,
17189 moving_cursor: bool,
17190 window: &mut Window,
17191 cx: &mut Context<Self>,
17192 ) -> Option<RenameState> {
17193 let rename = self.pending_rename.take()?;
17194 if rename.editor.focus_handle(cx).is_focused(window) {
17195 window.focus(&self.focus_handle);
17196 }
17197
17198 self.remove_blocks(
17199 [rename.block_id].into_iter().collect(),
17200 Some(Autoscroll::fit()),
17201 cx,
17202 );
17203 self.clear_highlights::<Rename>(cx);
17204 self.show_local_selections = true;
17205
17206 if moving_cursor {
17207 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17208 editor.selections.newest::<usize>(cx).head()
17209 });
17210
17211 // Update the selection to match the position of the selection inside
17212 // the rename editor.
17213 let snapshot = self.buffer.read(cx).read(cx);
17214 let rename_range = rename.range.to_offset(&snapshot);
17215 let cursor_in_editor = snapshot
17216 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17217 .min(rename_range.end);
17218 drop(snapshot);
17219
17220 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17221 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17222 });
17223 } else {
17224 self.refresh_document_highlights(cx);
17225 }
17226
17227 Some(rename)
17228 }
17229
17230 pub fn pending_rename(&self) -> Option<&RenameState> {
17231 self.pending_rename.as_ref()
17232 }
17233
17234 fn format(
17235 &mut self,
17236 _: &Format,
17237 window: &mut Window,
17238 cx: &mut Context<Self>,
17239 ) -> Option<Task<Result<()>>> {
17240 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17241
17242 let project = match &self.project {
17243 Some(project) => project.clone(),
17244 None => return None,
17245 };
17246
17247 Some(self.perform_format(
17248 project,
17249 FormatTrigger::Manual,
17250 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17251 window,
17252 cx,
17253 ))
17254 }
17255
17256 fn format_selections(
17257 &mut self,
17258 _: &FormatSelections,
17259 window: &mut Window,
17260 cx: &mut Context<Self>,
17261 ) -> Option<Task<Result<()>>> {
17262 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17263
17264 let project = match &self.project {
17265 Some(project) => project.clone(),
17266 None => return None,
17267 };
17268
17269 let ranges = self
17270 .selections
17271 .all_adjusted(cx)
17272 .into_iter()
17273 .map(|selection| selection.range())
17274 .collect_vec();
17275
17276 Some(self.perform_format(
17277 project,
17278 FormatTrigger::Manual,
17279 FormatTarget::Ranges(ranges),
17280 window,
17281 cx,
17282 ))
17283 }
17284
17285 fn perform_format(
17286 &mut self,
17287 project: Entity<Project>,
17288 trigger: FormatTrigger,
17289 target: FormatTarget,
17290 window: &mut Window,
17291 cx: &mut Context<Self>,
17292 ) -> Task<Result<()>> {
17293 let buffer = self.buffer.clone();
17294 let (buffers, target) = match target {
17295 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17296 FormatTarget::Ranges(selection_ranges) => {
17297 let multi_buffer = buffer.read(cx);
17298 let snapshot = multi_buffer.read(cx);
17299 let mut buffers = HashSet::default();
17300 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17301 BTreeMap::new();
17302 for selection_range in selection_ranges {
17303 for (buffer, buffer_range, _) in
17304 snapshot.range_to_buffer_ranges(selection_range)
17305 {
17306 let buffer_id = buffer.remote_id();
17307 let start = buffer.anchor_before(buffer_range.start);
17308 let end = buffer.anchor_after(buffer_range.end);
17309 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17310 buffer_id_to_ranges
17311 .entry(buffer_id)
17312 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17313 .or_insert_with(|| vec![start..end]);
17314 }
17315 }
17316 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17317 }
17318 };
17319
17320 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17321 let selections_prev = transaction_id_prev
17322 .and_then(|transaction_id_prev| {
17323 // default to selections as they were after the last edit, if we have them,
17324 // instead of how they are now.
17325 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17326 // will take you back to where you made the last edit, instead of staying where you scrolled
17327 self.selection_history
17328 .transaction(transaction_id_prev)
17329 .map(|t| t.0.clone())
17330 })
17331 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17332
17333 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17334 let format = project.update(cx, |project, cx| {
17335 project.format(buffers, target, true, trigger, cx)
17336 });
17337
17338 cx.spawn_in(window, async move |editor, cx| {
17339 let transaction = futures::select_biased! {
17340 transaction = format.log_err().fuse() => transaction,
17341 () = timeout => {
17342 log::warn!("timed out waiting for formatting");
17343 None
17344 }
17345 };
17346
17347 buffer
17348 .update(cx, |buffer, cx| {
17349 if let Some(transaction) = transaction
17350 && !buffer.is_singleton()
17351 {
17352 buffer.push_transaction(&transaction.0, cx);
17353 }
17354 cx.notify();
17355 })
17356 .ok();
17357
17358 if let Some(transaction_id_now) =
17359 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17360 {
17361 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17362 if has_new_transaction {
17363 _ = editor.update(cx, |editor, _| {
17364 editor
17365 .selection_history
17366 .insert_transaction(transaction_id_now, selections_prev);
17367 });
17368 }
17369 }
17370
17371 Ok(())
17372 })
17373 }
17374
17375 fn organize_imports(
17376 &mut self,
17377 _: &OrganizeImports,
17378 window: &mut Window,
17379 cx: &mut Context<Self>,
17380 ) -> Option<Task<Result<()>>> {
17381 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17382 let project = match &self.project {
17383 Some(project) => project.clone(),
17384 None => return None,
17385 };
17386 Some(self.perform_code_action_kind(
17387 project,
17388 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17389 window,
17390 cx,
17391 ))
17392 }
17393
17394 fn perform_code_action_kind(
17395 &mut self,
17396 project: Entity<Project>,
17397 kind: CodeActionKind,
17398 window: &mut Window,
17399 cx: &mut Context<Self>,
17400 ) -> Task<Result<()>> {
17401 let buffer = self.buffer.clone();
17402 let buffers = buffer.read(cx).all_buffers();
17403 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17404 let apply_action = project.update(cx, |project, cx| {
17405 project.apply_code_action_kind(buffers, kind, true, cx)
17406 });
17407 cx.spawn_in(window, async move |_, cx| {
17408 let transaction = futures::select_biased! {
17409 () = timeout => {
17410 log::warn!("timed out waiting for executing code action");
17411 None
17412 }
17413 transaction = apply_action.log_err().fuse() => transaction,
17414 };
17415 buffer
17416 .update(cx, |buffer, cx| {
17417 // check if we need this
17418 if let Some(transaction) = transaction
17419 && !buffer.is_singleton()
17420 {
17421 buffer.push_transaction(&transaction.0, cx);
17422 }
17423 cx.notify();
17424 })
17425 .ok();
17426 Ok(())
17427 })
17428 }
17429
17430 pub fn restart_language_server(
17431 &mut self,
17432 _: &RestartLanguageServer,
17433 _: &mut Window,
17434 cx: &mut Context<Self>,
17435 ) {
17436 if let Some(project) = self.project.clone() {
17437 self.buffer.update(cx, |multi_buffer, cx| {
17438 project.update(cx, |project, cx| {
17439 project.restart_language_servers_for_buffers(
17440 multi_buffer.all_buffers().into_iter().collect(),
17441 HashSet::default(),
17442 cx,
17443 );
17444 });
17445 })
17446 }
17447 }
17448
17449 pub fn stop_language_server(
17450 &mut self,
17451 _: &StopLanguageServer,
17452 _: &mut Window,
17453 cx: &mut Context<Self>,
17454 ) {
17455 if let Some(project) = self.project.clone() {
17456 self.buffer.update(cx, |multi_buffer, cx| {
17457 project.update(cx, |project, cx| {
17458 project.stop_language_servers_for_buffers(
17459 multi_buffer.all_buffers().into_iter().collect(),
17460 HashSet::default(),
17461 cx,
17462 );
17463 cx.emit(project::Event::RefreshInlayHints);
17464 });
17465 });
17466 }
17467 }
17468
17469 fn cancel_language_server_work(
17470 workspace: &mut Workspace,
17471 _: &actions::CancelLanguageServerWork,
17472 _: &mut Window,
17473 cx: &mut Context<Workspace>,
17474 ) {
17475 let project = workspace.project();
17476 let buffers = workspace
17477 .active_item(cx)
17478 .and_then(|item| item.act_as::<Editor>(cx))
17479 .map_or(HashSet::default(), |editor| {
17480 editor.read(cx).buffer.read(cx).all_buffers()
17481 });
17482 project.update(cx, |project, cx| {
17483 project.cancel_language_server_work_for_buffers(buffers, cx);
17484 });
17485 }
17486
17487 fn show_character_palette(
17488 &mut self,
17489 _: &ShowCharacterPalette,
17490 window: &mut Window,
17491 _: &mut Context<Self>,
17492 ) {
17493 window.show_character_palette();
17494 }
17495
17496 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17497 if !self.diagnostics_enabled() {
17498 return;
17499 }
17500
17501 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17502 let buffer = self.buffer.read(cx).snapshot(cx);
17503 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17504 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17505 let is_valid = buffer
17506 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17507 .any(|entry| {
17508 entry.diagnostic.is_primary
17509 && !entry.range.is_empty()
17510 && entry.range.start == primary_range_start
17511 && entry.diagnostic.message == active_diagnostics.active_message
17512 });
17513
17514 if !is_valid {
17515 self.dismiss_diagnostics(cx);
17516 }
17517 }
17518 }
17519
17520 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17521 match &self.active_diagnostics {
17522 ActiveDiagnostic::Group(group) => Some(group),
17523 _ => None,
17524 }
17525 }
17526
17527 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17528 if !self.diagnostics_enabled() {
17529 return;
17530 }
17531 self.dismiss_diagnostics(cx);
17532 self.active_diagnostics = ActiveDiagnostic::All;
17533 }
17534
17535 fn activate_diagnostics(
17536 &mut self,
17537 buffer_id: BufferId,
17538 diagnostic: DiagnosticEntryRef<'_, usize>,
17539 window: &mut Window,
17540 cx: &mut Context<Self>,
17541 ) {
17542 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17543 return;
17544 }
17545 self.dismiss_diagnostics(cx);
17546 let snapshot = self.snapshot(window, cx);
17547 let buffer = self.buffer.read(cx).snapshot(cx);
17548 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17549 return;
17550 };
17551
17552 let diagnostic_group = buffer
17553 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17554 .collect::<Vec<_>>();
17555
17556 let blocks =
17557 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17558
17559 let blocks = self.display_map.update(cx, |display_map, cx| {
17560 display_map.insert_blocks(blocks, cx).into_iter().collect()
17561 });
17562 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17563 active_range: buffer.anchor_before(diagnostic.range.start)
17564 ..buffer.anchor_after(diagnostic.range.end),
17565 active_message: diagnostic.diagnostic.message.clone(),
17566 group_id: diagnostic.diagnostic.group_id,
17567 blocks,
17568 });
17569 cx.notify();
17570 }
17571
17572 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17573 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17574 return;
17575 };
17576
17577 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17578 if let ActiveDiagnostic::Group(group) = prev {
17579 self.display_map.update(cx, |display_map, cx| {
17580 display_map.remove_blocks(group.blocks, cx);
17581 });
17582 cx.notify();
17583 }
17584 }
17585
17586 /// Disable inline diagnostics rendering for this editor.
17587 pub fn disable_inline_diagnostics(&mut self) {
17588 self.inline_diagnostics_enabled = false;
17589 self.inline_diagnostics_update = Task::ready(());
17590 self.inline_diagnostics.clear();
17591 }
17592
17593 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17594 self.diagnostics_enabled = false;
17595 self.dismiss_diagnostics(cx);
17596 self.inline_diagnostics_update = Task::ready(());
17597 self.inline_diagnostics.clear();
17598 }
17599
17600 pub fn disable_word_completions(&mut self) {
17601 self.word_completions_enabled = false;
17602 }
17603
17604 pub fn diagnostics_enabled(&self) -> bool {
17605 self.diagnostics_enabled && self.mode.is_full()
17606 }
17607
17608 pub fn inline_diagnostics_enabled(&self) -> bool {
17609 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17610 }
17611
17612 pub fn show_inline_diagnostics(&self) -> bool {
17613 self.show_inline_diagnostics
17614 }
17615
17616 pub fn toggle_inline_diagnostics(
17617 &mut self,
17618 _: &ToggleInlineDiagnostics,
17619 window: &mut Window,
17620 cx: &mut Context<Editor>,
17621 ) {
17622 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17623 self.refresh_inline_diagnostics(false, window, cx);
17624 }
17625
17626 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17627 self.diagnostics_max_severity = severity;
17628 self.display_map.update(cx, |display_map, _| {
17629 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17630 });
17631 }
17632
17633 pub fn toggle_diagnostics(
17634 &mut self,
17635 _: &ToggleDiagnostics,
17636 window: &mut Window,
17637 cx: &mut Context<Editor>,
17638 ) {
17639 if !self.diagnostics_enabled() {
17640 return;
17641 }
17642
17643 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17644 EditorSettings::get_global(cx)
17645 .diagnostics_max_severity
17646 .filter(|severity| severity != &DiagnosticSeverity::Off)
17647 .unwrap_or(DiagnosticSeverity::Hint)
17648 } else {
17649 DiagnosticSeverity::Off
17650 };
17651 self.set_max_diagnostics_severity(new_severity, cx);
17652 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17653 self.active_diagnostics = ActiveDiagnostic::None;
17654 self.inline_diagnostics_update = Task::ready(());
17655 self.inline_diagnostics.clear();
17656 } else {
17657 self.refresh_inline_diagnostics(false, window, cx);
17658 }
17659
17660 cx.notify();
17661 }
17662
17663 pub fn toggle_minimap(
17664 &mut self,
17665 _: &ToggleMinimap,
17666 window: &mut Window,
17667 cx: &mut Context<Editor>,
17668 ) {
17669 if self.supports_minimap(cx) {
17670 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17671 }
17672 }
17673
17674 fn refresh_inline_diagnostics(
17675 &mut self,
17676 debounce: bool,
17677 window: &mut Window,
17678 cx: &mut Context<Self>,
17679 ) {
17680 let max_severity = ProjectSettings::get_global(cx)
17681 .diagnostics
17682 .inline
17683 .max_severity
17684 .unwrap_or(self.diagnostics_max_severity);
17685
17686 if !self.inline_diagnostics_enabled()
17687 || !self.show_inline_diagnostics
17688 || max_severity == DiagnosticSeverity::Off
17689 {
17690 self.inline_diagnostics_update = Task::ready(());
17691 self.inline_diagnostics.clear();
17692 return;
17693 }
17694
17695 let debounce_ms = ProjectSettings::get_global(cx)
17696 .diagnostics
17697 .inline
17698 .update_debounce_ms;
17699 let debounce = if debounce && debounce_ms > 0 {
17700 Some(Duration::from_millis(debounce_ms))
17701 } else {
17702 None
17703 };
17704 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17705 if let Some(debounce) = debounce {
17706 cx.background_executor().timer(debounce).await;
17707 }
17708 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17709 editor
17710 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17711 .ok()
17712 }) else {
17713 return;
17714 };
17715
17716 let new_inline_diagnostics = cx
17717 .background_spawn(async move {
17718 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17719 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17720 let message = diagnostic_entry
17721 .diagnostic
17722 .message
17723 .split_once('\n')
17724 .map(|(line, _)| line)
17725 .map(SharedString::new)
17726 .unwrap_or_else(|| {
17727 SharedString::new(&*diagnostic_entry.diagnostic.message)
17728 });
17729 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17730 let (Ok(i) | Err(i)) = inline_diagnostics
17731 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17732 inline_diagnostics.insert(
17733 i,
17734 (
17735 start_anchor,
17736 InlineDiagnostic {
17737 message,
17738 group_id: diagnostic_entry.diagnostic.group_id,
17739 start: diagnostic_entry.range.start.to_point(&snapshot),
17740 is_primary: diagnostic_entry.diagnostic.is_primary,
17741 severity: diagnostic_entry.diagnostic.severity,
17742 },
17743 ),
17744 );
17745 }
17746 inline_diagnostics
17747 })
17748 .await;
17749
17750 editor
17751 .update(cx, |editor, cx| {
17752 editor.inline_diagnostics = new_inline_diagnostics;
17753 cx.notify();
17754 })
17755 .ok();
17756 });
17757 }
17758
17759 fn pull_diagnostics(
17760 &mut self,
17761 buffer_id: Option<BufferId>,
17762 window: &Window,
17763 cx: &mut Context<Self>,
17764 ) -> Option<()> {
17765 if !self.mode().is_full() {
17766 return None;
17767 }
17768 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17769 .diagnostics
17770 .lsp_pull_diagnostics;
17771 if !pull_diagnostics_settings.enabled {
17772 return None;
17773 }
17774 let project = self.project()?.downgrade();
17775 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17776 let mut buffers = self.buffer.read(cx).all_buffers();
17777 if let Some(buffer_id) = buffer_id {
17778 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17779 }
17780
17781 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17782 cx.background_executor().timer(debounce).await;
17783
17784 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17785 buffers
17786 .into_iter()
17787 .filter_map(|buffer| {
17788 project
17789 .update(cx, |project, cx| {
17790 project.lsp_store().update(cx, |lsp_store, cx| {
17791 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17792 })
17793 })
17794 .ok()
17795 })
17796 .collect::<FuturesUnordered<_>>()
17797 }) else {
17798 return;
17799 };
17800
17801 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17802 match pull_task {
17803 Ok(()) => {
17804 if editor
17805 .update_in(cx, |editor, window, cx| {
17806 editor.update_diagnostics_state(window, cx);
17807 })
17808 .is_err()
17809 {
17810 return;
17811 }
17812 }
17813 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17814 }
17815 }
17816 });
17817
17818 Some(())
17819 }
17820
17821 pub fn set_selections_from_remote(
17822 &mut self,
17823 selections: Vec<Selection<Anchor>>,
17824 pending_selection: Option<Selection<Anchor>>,
17825 window: &mut Window,
17826 cx: &mut Context<Self>,
17827 ) {
17828 let old_cursor_position = self.selections.newest_anchor().head();
17829 self.selections.change_with(cx, |s| {
17830 s.select_anchors(selections);
17831 if let Some(pending_selection) = pending_selection {
17832 s.set_pending(pending_selection, SelectMode::Character);
17833 } else {
17834 s.clear_pending();
17835 }
17836 });
17837 self.selections_did_change(
17838 false,
17839 &old_cursor_position,
17840 SelectionEffects::default(),
17841 window,
17842 cx,
17843 );
17844 }
17845
17846 pub fn transact(
17847 &mut self,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17851 ) -> Option<TransactionId> {
17852 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17853 this.start_transaction_at(Instant::now(), window, cx);
17854 update(this, window, cx);
17855 this.end_transaction_at(Instant::now(), cx)
17856 })
17857 }
17858
17859 pub fn start_transaction_at(
17860 &mut self,
17861 now: Instant,
17862 window: &mut Window,
17863 cx: &mut Context<Self>,
17864 ) -> Option<TransactionId> {
17865 self.end_selection(window, cx);
17866 if let Some(tx_id) = self
17867 .buffer
17868 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17869 {
17870 self.selection_history
17871 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17872 cx.emit(EditorEvent::TransactionBegun {
17873 transaction_id: tx_id,
17874 });
17875 Some(tx_id)
17876 } else {
17877 None
17878 }
17879 }
17880
17881 pub fn end_transaction_at(
17882 &mut self,
17883 now: Instant,
17884 cx: &mut Context<Self>,
17885 ) -> Option<TransactionId> {
17886 if let Some(transaction_id) = self
17887 .buffer
17888 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17889 {
17890 if let Some((_, end_selections)) =
17891 self.selection_history.transaction_mut(transaction_id)
17892 {
17893 *end_selections = Some(self.selections.disjoint_anchors_arc());
17894 } else {
17895 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17896 }
17897
17898 cx.emit(EditorEvent::Edited { transaction_id });
17899 Some(transaction_id)
17900 } else {
17901 None
17902 }
17903 }
17904
17905 pub fn modify_transaction_selection_history(
17906 &mut self,
17907 transaction_id: TransactionId,
17908 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17909 ) -> bool {
17910 self.selection_history
17911 .transaction_mut(transaction_id)
17912 .map(modify)
17913 .is_some()
17914 }
17915
17916 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17917 if self.selection_mark_mode {
17918 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17919 s.move_with(|_, sel| {
17920 sel.collapse_to(sel.head(), SelectionGoal::None);
17921 });
17922 })
17923 }
17924 self.selection_mark_mode = true;
17925 cx.notify();
17926 }
17927
17928 pub fn swap_selection_ends(
17929 &mut self,
17930 _: &actions::SwapSelectionEnds,
17931 window: &mut Window,
17932 cx: &mut Context<Self>,
17933 ) {
17934 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17935 s.move_with(|_, sel| {
17936 if sel.start != sel.end {
17937 sel.reversed = !sel.reversed
17938 }
17939 });
17940 });
17941 self.request_autoscroll(Autoscroll::newest(), cx);
17942 cx.notify();
17943 }
17944
17945 pub fn toggle_focus(
17946 workspace: &mut Workspace,
17947 _: &actions::ToggleFocus,
17948 window: &mut Window,
17949 cx: &mut Context<Workspace>,
17950 ) {
17951 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17952 return;
17953 };
17954 workspace.activate_item(&item, true, true, window, cx);
17955 }
17956
17957 pub fn toggle_fold(
17958 &mut self,
17959 _: &actions::ToggleFold,
17960 window: &mut Window,
17961 cx: &mut Context<Self>,
17962 ) {
17963 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17964 let selection = self.selections.newest::<Point>(cx);
17965
17966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17967 let range = if selection.is_empty() {
17968 let point = selection.head().to_display_point(&display_map);
17969 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17970 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17971 .to_point(&display_map);
17972 start..end
17973 } else {
17974 selection.range()
17975 };
17976 if display_map.folds_in_range(range).next().is_some() {
17977 self.unfold_lines(&Default::default(), window, cx)
17978 } else {
17979 self.fold(&Default::default(), window, cx)
17980 }
17981 } else {
17982 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17983 let buffer_ids: HashSet<_> = self
17984 .selections
17985 .disjoint_anchor_ranges()
17986 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17987 .collect();
17988
17989 let should_unfold = buffer_ids
17990 .iter()
17991 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17992
17993 for buffer_id in buffer_ids {
17994 if should_unfold {
17995 self.unfold_buffer(buffer_id, cx);
17996 } else {
17997 self.fold_buffer(buffer_id, cx);
17998 }
17999 }
18000 }
18001 }
18002
18003 pub fn toggle_fold_recursive(
18004 &mut self,
18005 _: &actions::ToggleFoldRecursive,
18006 window: &mut Window,
18007 cx: &mut Context<Self>,
18008 ) {
18009 let selection = self.selections.newest::<Point>(cx);
18010
18011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18012 let range = if selection.is_empty() {
18013 let point = selection.head().to_display_point(&display_map);
18014 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18015 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18016 .to_point(&display_map);
18017 start..end
18018 } else {
18019 selection.range()
18020 };
18021 if display_map.folds_in_range(range).next().is_some() {
18022 self.unfold_recursive(&Default::default(), window, cx)
18023 } else {
18024 self.fold_recursive(&Default::default(), window, cx)
18025 }
18026 }
18027
18028 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18029 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18030 let mut to_fold = Vec::new();
18031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18032 let selections = self.selections.all_adjusted(cx);
18033
18034 for selection in selections {
18035 let range = selection.range().sorted();
18036 let buffer_start_row = range.start.row;
18037
18038 if range.start.row != range.end.row {
18039 let mut found = false;
18040 let mut row = range.start.row;
18041 while row <= range.end.row {
18042 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18043 {
18044 found = true;
18045 row = crease.range().end.row + 1;
18046 to_fold.push(crease);
18047 } else {
18048 row += 1
18049 }
18050 }
18051 if found {
18052 continue;
18053 }
18054 }
18055
18056 for row in (0..=range.start.row).rev() {
18057 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18058 && crease.range().end.row >= buffer_start_row
18059 {
18060 to_fold.push(crease);
18061 if row <= range.start.row {
18062 break;
18063 }
18064 }
18065 }
18066 }
18067
18068 self.fold_creases(to_fold, true, window, cx);
18069 } else {
18070 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18071 let buffer_ids = self
18072 .selections
18073 .disjoint_anchor_ranges()
18074 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18075 .collect::<HashSet<_>>();
18076 for buffer_id in buffer_ids {
18077 self.fold_buffer(buffer_id, cx);
18078 }
18079 }
18080 }
18081
18082 pub fn toggle_fold_all(
18083 &mut self,
18084 _: &actions::ToggleFoldAll,
18085 window: &mut Window,
18086 cx: &mut Context<Self>,
18087 ) {
18088 if self.buffer.read(cx).is_singleton() {
18089 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18090 let has_folds = display_map
18091 .folds_in_range(0..display_map.buffer_snapshot().len())
18092 .next()
18093 .is_some();
18094
18095 if has_folds {
18096 self.unfold_all(&actions::UnfoldAll, window, cx);
18097 } else {
18098 self.fold_all(&actions::FoldAll, window, cx);
18099 }
18100 } else {
18101 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18102 let should_unfold = buffer_ids
18103 .iter()
18104 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18105
18106 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18107 editor
18108 .update_in(cx, |editor, _, cx| {
18109 for buffer_id in buffer_ids {
18110 if should_unfold {
18111 editor.unfold_buffer(buffer_id, cx);
18112 } else {
18113 editor.fold_buffer(buffer_id, cx);
18114 }
18115 }
18116 })
18117 .ok();
18118 });
18119 }
18120 }
18121
18122 fn fold_at_level(
18123 &mut self,
18124 fold_at: &FoldAtLevel,
18125 window: &mut Window,
18126 cx: &mut Context<Self>,
18127 ) {
18128 if !self.buffer.read(cx).is_singleton() {
18129 return;
18130 }
18131
18132 let fold_at_level = fold_at.0;
18133 let snapshot = self.buffer.read(cx).snapshot(cx);
18134 let mut to_fold = Vec::new();
18135 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18136
18137 let row_ranges_to_keep: Vec<Range<u32>> = self
18138 .selections
18139 .all::<Point>(cx)
18140 .into_iter()
18141 .map(|sel| sel.start.row..sel.end.row)
18142 .collect();
18143
18144 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18145 while start_row < end_row {
18146 match self
18147 .snapshot(window, cx)
18148 .crease_for_buffer_row(MultiBufferRow(start_row))
18149 {
18150 Some(crease) => {
18151 let nested_start_row = crease.range().start.row + 1;
18152 let nested_end_row = crease.range().end.row;
18153
18154 if current_level < fold_at_level {
18155 stack.push((nested_start_row, nested_end_row, current_level + 1));
18156 } else if current_level == fold_at_level {
18157 // Fold iff there is no selection completely contained within the fold region
18158 if !row_ranges_to_keep.iter().any(|selection| {
18159 selection.end >= nested_start_row
18160 && selection.start <= nested_end_row
18161 }) {
18162 to_fold.push(crease);
18163 }
18164 }
18165
18166 start_row = nested_end_row + 1;
18167 }
18168 None => start_row += 1,
18169 }
18170 }
18171 }
18172
18173 self.fold_creases(to_fold, true, window, cx);
18174 }
18175
18176 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18177 if self.buffer.read(cx).is_singleton() {
18178 let mut fold_ranges = Vec::new();
18179 let snapshot = self.buffer.read(cx).snapshot(cx);
18180
18181 for row in 0..snapshot.max_row().0 {
18182 if let Some(foldable_range) = self
18183 .snapshot(window, cx)
18184 .crease_for_buffer_row(MultiBufferRow(row))
18185 {
18186 fold_ranges.push(foldable_range);
18187 }
18188 }
18189
18190 self.fold_creases(fold_ranges, true, window, cx);
18191 } else {
18192 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18193 editor
18194 .update_in(cx, |editor, _, cx| {
18195 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18196 editor.fold_buffer(buffer_id, cx);
18197 }
18198 })
18199 .ok();
18200 });
18201 }
18202 }
18203
18204 pub fn fold_function_bodies(
18205 &mut self,
18206 _: &actions::FoldFunctionBodies,
18207 window: &mut Window,
18208 cx: &mut Context<Self>,
18209 ) {
18210 let snapshot = self.buffer.read(cx).snapshot(cx);
18211
18212 let ranges = snapshot
18213 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18214 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18215 .collect::<Vec<_>>();
18216
18217 let creases = ranges
18218 .into_iter()
18219 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18220 .collect();
18221
18222 self.fold_creases(creases, true, window, cx);
18223 }
18224
18225 pub fn fold_recursive(
18226 &mut self,
18227 _: &actions::FoldRecursive,
18228 window: &mut Window,
18229 cx: &mut Context<Self>,
18230 ) {
18231 let mut to_fold = Vec::new();
18232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18233 let selections = self.selections.all_adjusted(cx);
18234
18235 for selection in selections {
18236 let range = selection.range().sorted();
18237 let buffer_start_row = range.start.row;
18238
18239 if range.start.row != range.end.row {
18240 let mut found = false;
18241 for row in range.start.row..=range.end.row {
18242 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18243 found = true;
18244 to_fold.push(crease);
18245 }
18246 }
18247 if found {
18248 continue;
18249 }
18250 }
18251
18252 for row in (0..=range.start.row).rev() {
18253 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18254 if crease.range().end.row >= buffer_start_row {
18255 to_fold.push(crease);
18256 } else {
18257 break;
18258 }
18259 }
18260 }
18261 }
18262
18263 self.fold_creases(to_fold, true, window, cx);
18264 }
18265
18266 pub fn fold_at(
18267 &mut self,
18268 buffer_row: MultiBufferRow,
18269 window: &mut Window,
18270 cx: &mut Context<Self>,
18271 ) {
18272 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18273
18274 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18275 let autoscroll = self
18276 .selections
18277 .all::<Point>(cx)
18278 .iter()
18279 .any(|selection| crease.range().overlaps(&selection.range()));
18280
18281 self.fold_creases(vec![crease], autoscroll, window, cx);
18282 }
18283 }
18284
18285 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18286 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18288 let buffer = display_map.buffer_snapshot();
18289 let selections = self.selections.all::<Point>(cx);
18290 let ranges = selections
18291 .iter()
18292 .map(|s| {
18293 let range = s.display_range(&display_map).sorted();
18294 let mut start = range.start.to_point(&display_map);
18295 let mut end = range.end.to_point(&display_map);
18296 start.column = 0;
18297 end.column = buffer.line_len(MultiBufferRow(end.row));
18298 start..end
18299 })
18300 .collect::<Vec<_>>();
18301
18302 self.unfold_ranges(&ranges, true, true, cx);
18303 } else {
18304 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18305 let buffer_ids = self
18306 .selections
18307 .disjoint_anchor_ranges()
18308 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18309 .collect::<HashSet<_>>();
18310 for buffer_id in buffer_ids {
18311 self.unfold_buffer(buffer_id, cx);
18312 }
18313 }
18314 }
18315
18316 pub fn unfold_recursive(
18317 &mut self,
18318 _: &UnfoldRecursive,
18319 _window: &mut Window,
18320 cx: &mut Context<Self>,
18321 ) {
18322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18323 let selections = self.selections.all::<Point>(cx);
18324 let ranges = selections
18325 .iter()
18326 .map(|s| {
18327 let mut range = s.display_range(&display_map).sorted();
18328 *range.start.column_mut() = 0;
18329 *range.end.column_mut() = display_map.line_len(range.end.row());
18330 let start = range.start.to_point(&display_map);
18331 let end = range.end.to_point(&display_map);
18332 start..end
18333 })
18334 .collect::<Vec<_>>();
18335
18336 self.unfold_ranges(&ranges, true, true, cx);
18337 }
18338
18339 pub fn unfold_at(
18340 &mut self,
18341 buffer_row: MultiBufferRow,
18342 _window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) {
18345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18346
18347 let intersection_range = Point::new(buffer_row.0, 0)
18348 ..Point::new(
18349 buffer_row.0,
18350 display_map.buffer_snapshot().line_len(buffer_row),
18351 );
18352
18353 let autoscroll = self
18354 .selections
18355 .all::<Point>(cx)
18356 .iter()
18357 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18358
18359 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18360 }
18361
18362 pub fn unfold_all(
18363 &mut self,
18364 _: &actions::UnfoldAll,
18365 _window: &mut Window,
18366 cx: &mut Context<Self>,
18367 ) {
18368 if self.buffer.read(cx).is_singleton() {
18369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18370 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18371 } else {
18372 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18373 editor
18374 .update(cx, |editor, cx| {
18375 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18376 editor.unfold_buffer(buffer_id, cx);
18377 }
18378 })
18379 .ok();
18380 });
18381 }
18382 }
18383
18384 pub fn fold_selected_ranges(
18385 &mut self,
18386 _: &FoldSelectedRanges,
18387 window: &mut Window,
18388 cx: &mut Context<Self>,
18389 ) {
18390 let selections = self.selections.all_adjusted(cx);
18391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18392 let ranges = selections
18393 .into_iter()
18394 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18395 .collect::<Vec<_>>();
18396 self.fold_creases(ranges, true, window, cx);
18397 }
18398
18399 pub fn fold_ranges<T: ToOffset + Clone>(
18400 &mut self,
18401 ranges: Vec<Range<T>>,
18402 auto_scroll: bool,
18403 window: &mut Window,
18404 cx: &mut Context<Self>,
18405 ) {
18406 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18407 let ranges = ranges
18408 .into_iter()
18409 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18410 .collect::<Vec<_>>();
18411 self.fold_creases(ranges, auto_scroll, window, cx);
18412 }
18413
18414 pub fn fold_creases<T: ToOffset + Clone>(
18415 &mut self,
18416 creases: Vec<Crease<T>>,
18417 auto_scroll: bool,
18418 _window: &mut Window,
18419 cx: &mut Context<Self>,
18420 ) {
18421 if creases.is_empty() {
18422 return;
18423 }
18424
18425 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18426
18427 if auto_scroll {
18428 self.request_autoscroll(Autoscroll::fit(), cx);
18429 }
18430
18431 cx.notify();
18432
18433 self.scrollbar_marker_state.dirty = true;
18434 self.folds_did_change(cx);
18435 }
18436
18437 /// Removes any folds whose ranges intersect any of the given ranges.
18438 pub fn unfold_ranges<T: ToOffset + Clone>(
18439 &mut self,
18440 ranges: &[Range<T>],
18441 inclusive: bool,
18442 auto_scroll: bool,
18443 cx: &mut Context<Self>,
18444 ) {
18445 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18446 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18447 });
18448 self.folds_did_change(cx);
18449 }
18450
18451 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18452 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18453 return;
18454 }
18455 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18456 self.display_map.update(cx, |display_map, cx| {
18457 display_map.fold_buffers([buffer_id], cx)
18458 });
18459 cx.emit(EditorEvent::BufferFoldToggled {
18460 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18461 folded: true,
18462 });
18463 cx.notify();
18464 }
18465
18466 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18467 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18468 return;
18469 }
18470 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18471 self.display_map.update(cx, |display_map, cx| {
18472 display_map.unfold_buffers([buffer_id], cx);
18473 });
18474 cx.emit(EditorEvent::BufferFoldToggled {
18475 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18476 folded: false,
18477 });
18478 cx.notify();
18479 }
18480
18481 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18482 self.display_map.read(cx).is_buffer_folded(buffer)
18483 }
18484
18485 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18486 self.display_map.read(cx).folded_buffers()
18487 }
18488
18489 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18490 self.display_map.update(cx, |display_map, cx| {
18491 display_map.disable_header_for_buffer(buffer_id, cx);
18492 });
18493 cx.notify();
18494 }
18495
18496 /// Removes any folds with the given ranges.
18497 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18498 &mut self,
18499 ranges: &[Range<T>],
18500 type_id: TypeId,
18501 auto_scroll: bool,
18502 cx: &mut Context<Self>,
18503 ) {
18504 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18505 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18506 });
18507 self.folds_did_change(cx);
18508 }
18509
18510 fn remove_folds_with<T: ToOffset + Clone>(
18511 &mut self,
18512 ranges: &[Range<T>],
18513 auto_scroll: bool,
18514 cx: &mut Context<Self>,
18515 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18516 ) {
18517 if ranges.is_empty() {
18518 return;
18519 }
18520
18521 let mut buffers_affected = HashSet::default();
18522 let multi_buffer = self.buffer().read(cx);
18523 for range in ranges {
18524 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18525 buffers_affected.insert(buffer.read(cx).remote_id());
18526 };
18527 }
18528
18529 self.display_map.update(cx, update);
18530
18531 if auto_scroll {
18532 self.request_autoscroll(Autoscroll::fit(), cx);
18533 }
18534
18535 cx.notify();
18536 self.scrollbar_marker_state.dirty = true;
18537 self.active_indent_guides_state.dirty = true;
18538 }
18539
18540 pub fn update_renderer_widths(
18541 &mut self,
18542 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18543 cx: &mut Context<Self>,
18544 ) -> bool {
18545 self.display_map
18546 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18547 }
18548
18549 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18550 self.display_map.read(cx).fold_placeholder.clone()
18551 }
18552
18553 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18554 self.buffer.update(cx, |buffer, cx| {
18555 buffer.set_all_diff_hunks_expanded(cx);
18556 });
18557 }
18558
18559 pub fn expand_all_diff_hunks(
18560 &mut self,
18561 _: &ExpandAllDiffHunks,
18562 _window: &mut Window,
18563 cx: &mut Context<Self>,
18564 ) {
18565 self.buffer.update(cx, |buffer, cx| {
18566 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18567 });
18568 }
18569
18570 pub fn toggle_selected_diff_hunks(
18571 &mut self,
18572 _: &ToggleSelectedDiffHunks,
18573 _window: &mut Window,
18574 cx: &mut Context<Self>,
18575 ) {
18576 let ranges: Vec<_> = self
18577 .selections
18578 .disjoint_anchors()
18579 .iter()
18580 .map(|s| s.range())
18581 .collect();
18582 self.toggle_diff_hunks_in_ranges(ranges, cx);
18583 }
18584
18585 pub fn diff_hunks_in_ranges<'a>(
18586 &'a self,
18587 ranges: &'a [Range<Anchor>],
18588 buffer: &'a MultiBufferSnapshot,
18589 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18590 ranges.iter().flat_map(move |range| {
18591 let end_excerpt_id = range.end.excerpt_id;
18592 let range = range.to_point(buffer);
18593 let mut peek_end = range.end;
18594 if range.end.row < buffer.max_row().0 {
18595 peek_end = Point::new(range.end.row + 1, 0);
18596 }
18597 buffer
18598 .diff_hunks_in_range(range.start..peek_end)
18599 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18600 })
18601 }
18602
18603 pub fn has_stageable_diff_hunks_in_ranges(
18604 &self,
18605 ranges: &[Range<Anchor>],
18606 snapshot: &MultiBufferSnapshot,
18607 ) -> bool {
18608 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18609 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18610 }
18611
18612 pub fn toggle_staged_selected_diff_hunks(
18613 &mut self,
18614 _: &::git::ToggleStaged,
18615 _: &mut Window,
18616 cx: &mut Context<Self>,
18617 ) {
18618 let snapshot = self.buffer.read(cx).snapshot(cx);
18619 let ranges: Vec<_> = self
18620 .selections
18621 .disjoint_anchors()
18622 .iter()
18623 .map(|s| s.range())
18624 .collect();
18625 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18626 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18627 }
18628
18629 pub fn set_render_diff_hunk_controls(
18630 &mut self,
18631 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18632 cx: &mut Context<Self>,
18633 ) {
18634 self.render_diff_hunk_controls = render_diff_hunk_controls;
18635 cx.notify();
18636 }
18637
18638 pub fn stage_and_next(
18639 &mut self,
18640 _: &::git::StageAndNext,
18641 window: &mut Window,
18642 cx: &mut Context<Self>,
18643 ) {
18644 self.do_stage_or_unstage_and_next(true, window, cx);
18645 }
18646
18647 pub fn unstage_and_next(
18648 &mut self,
18649 _: &::git::UnstageAndNext,
18650 window: &mut Window,
18651 cx: &mut Context<Self>,
18652 ) {
18653 self.do_stage_or_unstage_and_next(false, window, cx);
18654 }
18655
18656 pub fn stage_or_unstage_diff_hunks(
18657 &mut self,
18658 stage: bool,
18659 ranges: Vec<Range<Anchor>>,
18660 cx: &mut Context<Self>,
18661 ) {
18662 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18663 cx.spawn(async move |this, cx| {
18664 task.await?;
18665 this.update(cx, |this, cx| {
18666 let snapshot = this.buffer.read(cx).snapshot(cx);
18667 let chunk_by = this
18668 .diff_hunks_in_ranges(&ranges, &snapshot)
18669 .chunk_by(|hunk| hunk.buffer_id);
18670 for (buffer_id, hunks) in &chunk_by {
18671 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18672 }
18673 })
18674 })
18675 .detach_and_log_err(cx);
18676 }
18677
18678 fn save_buffers_for_ranges_if_needed(
18679 &mut self,
18680 ranges: &[Range<Anchor>],
18681 cx: &mut Context<Editor>,
18682 ) -> Task<Result<()>> {
18683 let multibuffer = self.buffer.read(cx);
18684 let snapshot = multibuffer.read(cx);
18685 let buffer_ids: HashSet<_> = ranges
18686 .iter()
18687 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18688 .collect();
18689 drop(snapshot);
18690
18691 let mut buffers = HashSet::default();
18692 for buffer_id in buffer_ids {
18693 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18694 let buffer = buffer_entity.read(cx);
18695 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18696 {
18697 buffers.insert(buffer_entity);
18698 }
18699 }
18700 }
18701
18702 if let Some(project) = &self.project {
18703 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18704 } else {
18705 Task::ready(Ok(()))
18706 }
18707 }
18708
18709 fn do_stage_or_unstage_and_next(
18710 &mut self,
18711 stage: bool,
18712 window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) {
18715 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18716
18717 if ranges.iter().any(|range| range.start != range.end) {
18718 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18719 return;
18720 }
18721
18722 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18723 let snapshot = self.snapshot(window, cx);
18724 let position = self.selections.newest::<Point>(cx).head();
18725 let mut row = snapshot
18726 .buffer_snapshot()
18727 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18728 .find(|hunk| hunk.row_range.start.0 > position.row)
18729 .map(|hunk| hunk.row_range.start);
18730
18731 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18732 // Outside of the project diff editor, wrap around to the beginning.
18733 if !all_diff_hunks_expanded {
18734 row = row.or_else(|| {
18735 snapshot
18736 .buffer_snapshot()
18737 .diff_hunks_in_range(Point::zero()..position)
18738 .find(|hunk| hunk.row_range.end.0 < position.row)
18739 .map(|hunk| hunk.row_range.start)
18740 });
18741 }
18742
18743 if let Some(row) = row {
18744 let destination = Point::new(row.0, 0);
18745 let autoscroll = Autoscroll::center();
18746
18747 self.unfold_ranges(&[destination..destination], false, false, cx);
18748 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18749 s.select_ranges([destination..destination]);
18750 });
18751 }
18752 }
18753
18754 fn do_stage_or_unstage(
18755 &self,
18756 stage: bool,
18757 buffer_id: BufferId,
18758 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18759 cx: &mut App,
18760 ) -> Option<()> {
18761 let project = self.project()?;
18762 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18763 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18764 let buffer_snapshot = buffer.read(cx).snapshot();
18765 let file_exists = buffer_snapshot
18766 .file()
18767 .is_some_and(|file| file.disk_state().exists());
18768 diff.update(cx, |diff, cx| {
18769 diff.stage_or_unstage_hunks(
18770 stage,
18771 &hunks
18772 .map(|hunk| buffer_diff::DiffHunk {
18773 buffer_range: hunk.buffer_range,
18774 diff_base_byte_range: hunk.diff_base_byte_range,
18775 secondary_status: hunk.secondary_status,
18776 range: Point::zero()..Point::zero(), // unused
18777 })
18778 .collect::<Vec<_>>(),
18779 &buffer_snapshot,
18780 file_exists,
18781 cx,
18782 )
18783 });
18784 None
18785 }
18786
18787 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18788 let ranges: Vec<_> = self
18789 .selections
18790 .disjoint_anchors()
18791 .iter()
18792 .map(|s| s.range())
18793 .collect();
18794 self.buffer
18795 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18796 }
18797
18798 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18799 self.buffer.update(cx, |buffer, cx| {
18800 let ranges = vec![Anchor::min()..Anchor::max()];
18801 if !buffer.all_diff_hunks_expanded()
18802 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18803 {
18804 buffer.collapse_diff_hunks(ranges, cx);
18805 true
18806 } else {
18807 false
18808 }
18809 })
18810 }
18811
18812 fn toggle_diff_hunks_in_ranges(
18813 &mut self,
18814 ranges: Vec<Range<Anchor>>,
18815 cx: &mut Context<Editor>,
18816 ) {
18817 self.buffer.update(cx, |buffer, cx| {
18818 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18819 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18820 })
18821 }
18822
18823 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18824 self.buffer.update(cx, |buffer, cx| {
18825 let snapshot = buffer.snapshot(cx);
18826 let excerpt_id = range.end.excerpt_id;
18827 let point_range = range.to_point(&snapshot);
18828 let expand = !buffer.single_hunk_is_expanded(range, cx);
18829 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18830 })
18831 }
18832
18833 pub(crate) fn apply_all_diff_hunks(
18834 &mut self,
18835 _: &ApplyAllDiffHunks,
18836 window: &mut Window,
18837 cx: &mut Context<Self>,
18838 ) {
18839 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18840
18841 let buffers = self.buffer.read(cx).all_buffers();
18842 for branch_buffer in buffers {
18843 branch_buffer.update(cx, |branch_buffer, cx| {
18844 branch_buffer.merge_into_base(Vec::new(), cx);
18845 });
18846 }
18847
18848 if let Some(project) = self.project.clone() {
18849 self.save(
18850 SaveOptions {
18851 format: true,
18852 autosave: false,
18853 },
18854 project,
18855 window,
18856 cx,
18857 )
18858 .detach_and_log_err(cx);
18859 }
18860 }
18861
18862 pub(crate) fn apply_selected_diff_hunks(
18863 &mut self,
18864 _: &ApplyDiffHunk,
18865 window: &mut Window,
18866 cx: &mut Context<Self>,
18867 ) {
18868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18869 let snapshot = self.snapshot(window, cx);
18870 let hunks = snapshot.hunks_for_ranges(
18871 self.selections
18872 .all(cx)
18873 .into_iter()
18874 .map(|selection| selection.range()),
18875 );
18876 let mut ranges_by_buffer = HashMap::default();
18877 self.transact(window, cx, |editor, _window, cx| {
18878 for hunk in hunks {
18879 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18880 ranges_by_buffer
18881 .entry(buffer.clone())
18882 .or_insert_with(Vec::new)
18883 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18884 }
18885 }
18886
18887 for (buffer, ranges) in ranges_by_buffer {
18888 buffer.update(cx, |buffer, cx| {
18889 buffer.merge_into_base(ranges, cx);
18890 });
18891 }
18892 });
18893
18894 if let Some(project) = self.project.clone() {
18895 self.save(
18896 SaveOptions {
18897 format: true,
18898 autosave: false,
18899 },
18900 project,
18901 window,
18902 cx,
18903 )
18904 .detach_and_log_err(cx);
18905 }
18906 }
18907
18908 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18909 if hovered != self.gutter_hovered {
18910 self.gutter_hovered = hovered;
18911 cx.notify();
18912 }
18913 }
18914
18915 pub fn insert_blocks(
18916 &mut self,
18917 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18918 autoscroll: Option<Autoscroll>,
18919 cx: &mut Context<Self>,
18920 ) -> Vec<CustomBlockId> {
18921 let blocks = self
18922 .display_map
18923 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18924 if let Some(autoscroll) = autoscroll {
18925 self.request_autoscroll(autoscroll, cx);
18926 }
18927 cx.notify();
18928 blocks
18929 }
18930
18931 pub fn resize_blocks(
18932 &mut self,
18933 heights: HashMap<CustomBlockId, u32>,
18934 autoscroll: Option<Autoscroll>,
18935 cx: &mut Context<Self>,
18936 ) {
18937 self.display_map
18938 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18939 if let Some(autoscroll) = autoscroll {
18940 self.request_autoscroll(autoscroll, cx);
18941 }
18942 cx.notify();
18943 }
18944
18945 pub fn replace_blocks(
18946 &mut self,
18947 renderers: HashMap<CustomBlockId, RenderBlock>,
18948 autoscroll: Option<Autoscroll>,
18949 cx: &mut Context<Self>,
18950 ) {
18951 self.display_map
18952 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18953 if let Some(autoscroll) = autoscroll {
18954 self.request_autoscroll(autoscroll, cx);
18955 }
18956 cx.notify();
18957 }
18958
18959 pub fn remove_blocks(
18960 &mut self,
18961 block_ids: HashSet<CustomBlockId>,
18962 autoscroll: Option<Autoscroll>,
18963 cx: &mut Context<Self>,
18964 ) {
18965 self.display_map.update(cx, |display_map, cx| {
18966 display_map.remove_blocks(block_ids, cx)
18967 });
18968 if let Some(autoscroll) = autoscroll {
18969 self.request_autoscroll(autoscroll, cx);
18970 }
18971 cx.notify();
18972 }
18973
18974 pub fn row_for_block(
18975 &self,
18976 block_id: CustomBlockId,
18977 cx: &mut Context<Self>,
18978 ) -> Option<DisplayRow> {
18979 self.display_map
18980 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18981 }
18982
18983 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18984 self.focused_block = Some(focused_block);
18985 }
18986
18987 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18988 self.focused_block.take()
18989 }
18990
18991 pub fn insert_creases(
18992 &mut self,
18993 creases: impl IntoIterator<Item = Crease<Anchor>>,
18994 cx: &mut Context<Self>,
18995 ) -> Vec<CreaseId> {
18996 self.display_map
18997 .update(cx, |map, cx| map.insert_creases(creases, cx))
18998 }
18999
19000 pub fn remove_creases(
19001 &mut self,
19002 ids: impl IntoIterator<Item = CreaseId>,
19003 cx: &mut Context<Self>,
19004 ) -> Vec<(CreaseId, Range<Anchor>)> {
19005 self.display_map
19006 .update(cx, |map, cx| map.remove_creases(ids, cx))
19007 }
19008
19009 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19010 self.display_map
19011 .update(cx, |map, cx| map.snapshot(cx))
19012 .longest_row()
19013 }
19014
19015 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19016 self.display_map
19017 .update(cx, |map, cx| map.snapshot(cx))
19018 .max_point()
19019 }
19020
19021 pub fn text(&self, cx: &App) -> String {
19022 self.buffer.read(cx).read(cx).text()
19023 }
19024
19025 pub fn is_empty(&self, cx: &App) -> bool {
19026 self.buffer.read(cx).read(cx).is_empty()
19027 }
19028
19029 pub fn text_option(&self, cx: &App) -> Option<String> {
19030 let text = self.text(cx);
19031 let text = text.trim();
19032
19033 if text.is_empty() {
19034 return None;
19035 }
19036
19037 Some(text.to_string())
19038 }
19039
19040 pub fn set_text(
19041 &mut self,
19042 text: impl Into<Arc<str>>,
19043 window: &mut Window,
19044 cx: &mut Context<Self>,
19045 ) {
19046 self.transact(window, cx, |this, _, cx| {
19047 this.buffer
19048 .read(cx)
19049 .as_singleton()
19050 .expect("you can only call set_text on editors for singleton buffers")
19051 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19052 });
19053 }
19054
19055 pub fn display_text(&self, cx: &mut App) -> String {
19056 self.display_map
19057 .update(cx, |map, cx| map.snapshot(cx))
19058 .text()
19059 }
19060
19061 fn create_minimap(
19062 &self,
19063 minimap_settings: MinimapSettings,
19064 window: &mut Window,
19065 cx: &mut Context<Self>,
19066 ) -> Option<Entity<Self>> {
19067 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19068 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19069 }
19070
19071 fn initialize_new_minimap(
19072 &self,
19073 minimap_settings: MinimapSettings,
19074 window: &mut Window,
19075 cx: &mut Context<Self>,
19076 ) -> Entity<Self> {
19077 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19078
19079 let mut minimap = Editor::new_internal(
19080 EditorMode::Minimap {
19081 parent: cx.weak_entity(),
19082 },
19083 self.buffer.clone(),
19084 None,
19085 Some(self.display_map.clone()),
19086 window,
19087 cx,
19088 );
19089 minimap.scroll_manager.clone_state(&self.scroll_manager);
19090 minimap.set_text_style_refinement(TextStyleRefinement {
19091 font_size: Some(MINIMAP_FONT_SIZE),
19092 font_weight: Some(MINIMAP_FONT_WEIGHT),
19093 ..Default::default()
19094 });
19095 minimap.update_minimap_configuration(minimap_settings, cx);
19096 cx.new(|_| minimap)
19097 }
19098
19099 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19100 let current_line_highlight = minimap_settings
19101 .current_line_highlight
19102 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19103 self.set_current_line_highlight(Some(current_line_highlight));
19104 }
19105
19106 pub fn minimap(&self) -> Option<&Entity<Self>> {
19107 self.minimap
19108 .as_ref()
19109 .filter(|_| self.minimap_visibility.visible())
19110 }
19111
19112 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19113 let mut wrap_guides = smallvec![];
19114
19115 if self.show_wrap_guides == Some(false) {
19116 return wrap_guides;
19117 }
19118
19119 let settings = self.buffer.read(cx).language_settings(cx);
19120 if settings.show_wrap_guides {
19121 match self.soft_wrap_mode(cx) {
19122 SoftWrap::Column(soft_wrap) => {
19123 wrap_guides.push((soft_wrap as usize, true));
19124 }
19125 SoftWrap::Bounded(soft_wrap) => {
19126 wrap_guides.push((soft_wrap as usize, true));
19127 }
19128 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19129 }
19130 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19131 }
19132
19133 wrap_guides
19134 }
19135
19136 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19137 let settings = self.buffer.read(cx).language_settings(cx);
19138 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19139 match mode {
19140 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19141 SoftWrap::None
19142 }
19143 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19144 language_settings::SoftWrap::PreferredLineLength => {
19145 SoftWrap::Column(settings.preferred_line_length)
19146 }
19147 language_settings::SoftWrap::Bounded => {
19148 SoftWrap::Bounded(settings.preferred_line_length)
19149 }
19150 }
19151 }
19152
19153 pub fn set_soft_wrap_mode(
19154 &mut self,
19155 mode: language_settings::SoftWrap,
19156
19157 cx: &mut Context<Self>,
19158 ) {
19159 self.soft_wrap_mode_override = Some(mode);
19160 cx.notify();
19161 }
19162
19163 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19164 self.hard_wrap = hard_wrap;
19165 cx.notify();
19166 }
19167
19168 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19169 self.text_style_refinement = Some(style);
19170 }
19171
19172 /// called by the Element so we know what style we were most recently rendered with.
19173 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19174 // We intentionally do not inform the display map about the minimap style
19175 // so that wrapping is not recalculated and stays consistent for the editor
19176 // and its linked minimap.
19177 if !self.mode.is_minimap() {
19178 let font = style.text.font();
19179 let font_size = style.text.font_size.to_pixels(window.rem_size());
19180 let display_map = self
19181 .placeholder_display_map
19182 .as_ref()
19183 .filter(|_| self.is_empty(cx))
19184 .unwrap_or(&self.display_map);
19185
19186 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19187 }
19188 self.style = Some(style);
19189 }
19190
19191 pub fn style(&self) -> Option<&EditorStyle> {
19192 self.style.as_ref()
19193 }
19194
19195 // Called by the element. This method is not designed to be called outside of the editor
19196 // element's layout code because it does not notify when rewrapping is computed synchronously.
19197 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19198 if self.is_empty(cx) {
19199 self.placeholder_display_map
19200 .as_ref()
19201 .map_or(false, |display_map| {
19202 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19203 })
19204 } else {
19205 self.display_map
19206 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19207 }
19208 }
19209
19210 pub fn set_soft_wrap(&mut self) {
19211 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19212 }
19213
19214 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19215 if self.soft_wrap_mode_override.is_some() {
19216 self.soft_wrap_mode_override.take();
19217 } else {
19218 let soft_wrap = match self.soft_wrap_mode(cx) {
19219 SoftWrap::GitDiff => return,
19220 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19221 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19222 language_settings::SoftWrap::None
19223 }
19224 };
19225 self.soft_wrap_mode_override = Some(soft_wrap);
19226 }
19227 cx.notify();
19228 }
19229
19230 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19231 let Some(workspace) = self.workspace() else {
19232 return;
19233 };
19234 let fs = workspace.read(cx).app_state().fs.clone();
19235 let current_show = TabBarSettings::get_global(cx).show;
19236 update_settings_file(fs, cx, move |setting, _| {
19237 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19238 });
19239 }
19240
19241 pub fn toggle_indent_guides(
19242 &mut self,
19243 _: &ToggleIndentGuides,
19244 _: &mut Window,
19245 cx: &mut Context<Self>,
19246 ) {
19247 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19248 self.buffer
19249 .read(cx)
19250 .language_settings(cx)
19251 .indent_guides
19252 .enabled
19253 });
19254 self.show_indent_guides = Some(!currently_enabled);
19255 cx.notify();
19256 }
19257
19258 fn should_show_indent_guides(&self) -> Option<bool> {
19259 self.show_indent_guides
19260 }
19261
19262 pub fn toggle_line_numbers(
19263 &mut self,
19264 _: &ToggleLineNumbers,
19265 _: &mut Window,
19266 cx: &mut Context<Self>,
19267 ) {
19268 let mut editor_settings = EditorSettings::get_global(cx).clone();
19269 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19270 EditorSettings::override_global(editor_settings, cx);
19271 }
19272
19273 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19274 if let Some(show_line_numbers) = self.show_line_numbers {
19275 return show_line_numbers;
19276 }
19277 EditorSettings::get_global(cx).gutter.line_numbers
19278 }
19279
19280 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19281 self.use_relative_line_numbers
19282 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19283 }
19284
19285 pub fn toggle_relative_line_numbers(
19286 &mut self,
19287 _: &ToggleRelativeLineNumbers,
19288 _: &mut Window,
19289 cx: &mut Context<Self>,
19290 ) {
19291 let is_relative = self.should_use_relative_line_numbers(cx);
19292 self.set_relative_line_number(Some(!is_relative), cx)
19293 }
19294
19295 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19296 self.use_relative_line_numbers = is_relative;
19297 cx.notify();
19298 }
19299
19300 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19301 self.show_gutter = show_gutter;
19302 cx.notify();
19303 }
19304
19305 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19306 self.show_scrollbars = ScrollbarAxes {
19307 horizontal: show,
19308 vertical: show,
19309 };
19310 cx.notify();
19311 }
19312
19313 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19314 self.show_scrollbars.vertical = show;
19315 cx.notify();
19316 }
19317
19318 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19319 self.show_scrollbars.horizontal = show;
19320 cx.notify();
19321 }
19322
19323 pub fn set_minimap_visibility(
19324 &mut self,
19325 minimap_visibility: MinimapVisibility,
19326 window: &mut Window,
19327 cx: &mut Context<Self>,
19328 ) {
19329 if self.minimap_visibility != minimap_visibility {
19330 if minimap_visibility.visible() && self.minimap.is_none() {
19331 let minimap_settings = EditorSettings::get_global(cx).minimap;
19332 self.minimap =
19333 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19334 }
19335 self.minimap_visibility = minimap_visibility;
19336 cx.notify();
19337 }
19338 }
19339
19340 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19341 self.set_show_scrollbars(false, cx);
19342 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19343 }
19344
19345 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19346 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19347 }
19348
19349 /// Normally the text in full mode and auto height editors is padded on the
19350 /// left side by roughly half a character width for improved hit testing.
19351 ///
19352 /// Use this method to disable this for cases where this is not wanted (e.g.
19353 /// if you want to align the editor text with some other text above or below)
19354 /// or if you want to add this padding to single-line editors.
19355 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19356 self.offset_content = offset_content;
19357 cx.notify();
19358 }
19359
19360 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19361 self.show_line_numbers = Some(show_line_numbers);
19362 cx.notify();
19363 }
19364
19365 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19366 self.disable_expand_excerpt_buttons = true;
19367 cx.notify();
19368 }
19369
19370 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19371 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19372 cx.notify();
19373 }
19374
19375 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19376 self.show_code_actions = Some(show_code_actions);
19377 cx.notify();
19378 }
19379
19380 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19381 self.show_runnables = Some(show_runnables);
19382 cx.notify();
19383 }
19384
19385 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19386 self.show_breakpoints = Some(show_breakpoints);
19387 cx.notify();
19388 }
19389
19390 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19391 if self.display_map.read(cx).masked != masked {
19392 self.display_map.update(cx, |map, _| map.masked = masked);
19393 }
19394 cx.notify()
19395 }
19396
19397 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19398 self.show_wrap_guides = Some(show_wrap_guides);
19399 cx.notify();
19400 }
19401
19402 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19403 self.show_indent_guides = Some(show_indent_guides);
19404 cx.notify();
19405 }
19406
19407 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19408 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19409 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19410 && let Some(dir) = file.abs_path(cx).parent()
19411 {
19412 return Some(dir.to_owned());
19413 }
19414 }
19415
19416 None
19417 }
19418
19419 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19420 self.active_excerpt(cx)?
19421 .1
19422 .read(cx)
19423 .file()
19424 .and_then(|f| f.as_local())
19425 }
19426
19427 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19428 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19429 let buffer = buffer.read(cx);
19430 if let Some(project_path) = buffer.project_path(cx) {
19431 let project = self.project()?.read(cx);
19432 project.absolute_path(&project_path, cx)
19433 } else {
19434 buffer
19435 .file()
19436 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19437 }
19438 })
19439 }
19440
19441 pub fn reveal_in_finder(
19442 &mut self,
19443 _: &RevealInFileManager,
19444 _window: &mut Window,
19445 cx: &mut Context<Self>,
19446 ) {
19447 if let Some(target) = self.target_file(cx) {
19448 cx.reveal_path(&target.abs_path(cx));
19449 }
19450 }
19451
19452 pub fn copy_path(
19453 &mut self,
19454 _: &zed_actions::workspace::CopyPath,
19455 _window: &mut Window,
19456 cx: &mut Context<Self>,
19457 ) {
19458 if let Some(path) = self.target_file_abs_path(cx)
19459 && let Some(path) = path.to_str()
19460 {
19461 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19462 } else {
19463 cx.propagate();
19464 }
19465 }
19466
19467 pub fn copy_relative_path(
19468 &mut self,
19469 _: &zed_actions::workspace::CopyRelativePath,
19470 _window: &mut Window,
19471 cx: &mut Context<Self>,
19472 ) {
19473 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19474 let project = self.project()?.read(cx);
19475 let path = buffer.read(cx).file()?.path();
19476 let path = path.display(project.path_style(cx));
19477 Some(path)
19478 }) {
19479 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19480 } else {
19481 cx.propagate();
19482 }
19483 }
19484
19485 /// Returns the project path for the editor's buffer, if any buffer is
19486 /// opened in the editor.
19487 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19488 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19489 buffer.read(cx).project_path(cx)
19490 } else {
19491 None
19492 }
19493 }
19494
19495 // Returns true if the editor handled a go-to-line request
19496 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19497 maybe!({
19498 let breakpoint_store = self.breakpoint_store.as_ref()?;
19499
19500 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19501 else {
19502 self.clear_row_highlights::<ActiveDebugLine>();
19503 return None;
19504 };
19505
19506 let position = active_stack_frame.position;
19507 let buffer_id = position.buffer_id?;
19508 let snapshot = self
19509 .project
19510 .as_ref()?
19511 .read(cx)
19512 .buffer_for_id(buffer_id, cx)?
19513 .read(cx)
19514 .snapshot();
19515
19516 let mut handled = false;
19517 for (id, ExcerptRange { context, .. }) in
19518 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19519 {
19520 if context.start.cmp(&position, &snapshot).is_ge()
19521 || context.end.cmp(&position, &snapshot).is_lt()
19522 {
19523 continue;
19524 }
19525 let snapshot = self.buffer.read(cx).snapshot(cx);
19526 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19527
19528 handled = true;
19529 self.clear_row_highlights::<ActiveDebugLine>();
19530
19531 self.go_to_line::<ActiveDebugLine>(
19532 multibuffer_anchor,
19533 Some(cx.theme().colors().editor_debugger_active_line_background),
19534 window,
19535 cx,
19536 );
19537
19538 cx.notify();
19539 }
19540
19541 handled.then_some(())
19542 })
19543 .is_some()
19544 }
19545
19546 pub fn copy_file_name_without_extension(
19547 &mut self,
19548 _: &CopyFileNameWithoutExtension,
19549 _: &mut Window,
19550 cx: &mut Context<Self>,
19551 ) {
19552 if let Some(file) = self.target_file(cx)
19553 && let Some(file_stem) = file.path().file_stem()
19554 {
19555 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19556 }
19557 }
19558
19559 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19560 if let Some(file) = self.target_file(cx)
19561 && let Some(name) = file.path().file_name()
19562 {
19563 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19564 }
19565 }
19566
19567 pub fn toggle_git_blame(
19568 &mut self,
19569 _: &::git::Blame,
19570 window: &mut Window,
19571 cx: &mut Context<Self>,
19572 ) {
19573 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19574
19575 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19576 self.start_git_blame(true, window, cx);
19577 }
19578
19579 cx.notify();
19580 }
19581
19582 pub fn toggle_git_blame_inline(
19583 &mut self,
19584 _: &ToggleGitBlameInline,
19585 window: &mut Window,
19586 cx: &mut Context<Self>,
19587 ) {
19588 self.toggle_git_blame_inline_internal(true, window, cx);
19589 cx.notify();
19590 }
19591
19592 pub fn open_git_blame_commit(
19593 &mut self,
19594 _: &OpenGitBlameCommit,
19595 window: &mut Window,
19596 cx: &mut Context<Self>,
19597 ) {
19598 self.open_git_blame_commit_internal(window, cx);
19599 }
19600
19601 fn open_git_blame_commit_internal(
19602 &mut self,
19603 window: &mut Window,
19604 cx: &mut Context<Self>,
19605 ) -> Option<()> {
19606 let blame = self.blame.as_ref()?;
19607 let snapshot = self.snapshot(window, cx);
19608 let cursor = self.selections.newest::<Point>(cx).head();
19609 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19610 let (_, blame_entry) = blame
19611 .update(cx, |blame, cx| {
19612 blame
19613 .blame_for_rows(
19614 &[RowInfo {
19615 buffer_id: Some(buffer.remote_id()),
19616 buffer_row: Some(point.row),
19617 ..Default::default()
19618 }],
19619 cx,
19620 )
19621 .next()
19622 })
19623 .flatten()?;
19624 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19625 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19626 let workspace = self.workspace()?.downgrade();
19627 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19628 None
19629 }
19630
19631 pub fn git_blame_inline_enabled(&self) -> bool {
19632 self.git_blame_inline_enabled
19633 }
19634
19635 pub fn toggle_selection_menu(
19636 &mut self,
19637 _: &ToggleSelectionMenu,
19638 _: &mut Window,
19639 cx: &mut Context<Self>,
19640 ) {
19641 self.show_selection_menu = self
19642 .show_selection_menu
19643 .map(|show_selections_menu| !show_selections_menu)
19644 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19645
19646 cx.notify();
19647 }
19648
19649 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19650 self.show_selection_menu
19651 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19652 }
19653
19654 fn start_git_blame(
19655 &mut self,
19656 user_triggered: bool,
19657 window: &mut Window,
19658 cx: &mut Context<Self>,
19659 ) {
19660 if let Some(project) = self.project() {
19661 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19662 && buffer.read(cx).file().is_none()
19663 {
19664 return;
19665 }
19666
19667 let focused = self.focus_handle(cx).contains_focused(window, cx);
19668
19669 let project = project.clone();
19670 let blame = cx
19671 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19672 self.blame_subscription =
19673 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19674 self.blame = Some(blame);
19675 }
19676 }
19677
19678 fn toggle_git_blame_inline_internal(
19679 &mut self,
19680 user_triggered: bool,
19681 window: &mut Window,
19682 cx: &mut Context<Self>,
19683 ) {
19684 if self.git_blame_inline_enabled {
19685 self.git_blame_inline_enabled = false;
19686 self.show_git_blame_inline = false;
19687 self.show_git_blame_inline_delay_task.take();
19688 } else {
19689 self.git_blame_inline_enabled = true;
19690 self.start_git_blame_inline(user_triggered, window, cx);
19691 }
19692
19693 cx.notify();
19694 }
19695
19696 fn start_git_blame_inline(
19697 &mut self,
19698 user_triggered: bool,
19699 window: &mut Window,
19700 cx: &mut Context<Self>,
19701 ) {
19702 self.start_git_blame(user_triggered, window, cx);
19703
19704 if ProjectSettings::get_global(cx)
19705 .git
19706 .inline_blame_delay()
19707 .is_some()
19708 {
19709 self.start_inline_blame_timer(window, cx);
19710 } else {
19711 self.show_git_blame_inline = true
19712 }
19713 }
19714
19715 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19716 self.blame.as_ref()
19717 }
19718
19719 pub fn show_git_blame_gutter(&self) -> bool {
19720 self.show_git_blame_gutter
19721 }
19722
19723 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19724 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19725 }
19726
19727 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19728 self.show_git_blame_inline
19729 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19730 && !self.newest_selection_head_on_empty_line(cx)
19731 && self.has_blame_entries(cx)
19732 }
19733
19734 fn has_blame_entries(&self, cx: &App) -> bool {
19735 self.blame()
19736 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19737 }
19738
19739 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19740 let cursor_anchor = self.selections.newest_anchor().head();
19741
19742 let snapshot = self.buffer.read(cx).snapshot(cx);
19743 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19744
19745 snapshot.line_len(buffer_row) == 0
19746 }
19747
19748 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19749 let buffer_and_selection = maybe!({
19750 let selection = self.selections.newest::<Point>(cx);
19751 let selection_range = selection.range();
19752
19753 let multi_buffer = self.buffer().read(cx);
19754 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19755 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19756
19757 let (buffer, range, _) = if selection.reversed {
19758 buffer_ranges.first()
19759 } else {
19760 buffer_ranges.last()
19761 }?;
19762
19763 let selection = text::ToPoint::to_point(&range.start, buffer).row
19764 ..text::ToPoint::to_point(&range.end, buffer).row;
19765 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19766 });
19767
19768 let Some((buffer, selection)) = buffer_and_selection else {
19769 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19770 };
19771
19772 let Some(project) = self.project() else {
19773 return Task::ready(Err(anyhow!("editor does not have project")));
19774 };
19775
19776 project.update(cx, |project, cx| {
19777 project.get_permalink_to_line(&buffer, selection, cx)
19778 })
19779 }
19780
19781 pub fn copy_permalink_to_line(
19782 &mut self,
19783 _: &CopyPermalinkToLine,
19784 window: &mut Window,
19785 cx: &mut Context<Self>,
19786 ) {
19787 let permalink_task = self.get_permalink_to_line(cx);
19788 let workspace = self.workspace();
19789
19790 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19791 Ok(permalink) => {
19792 cx.update(|_, cx| {
19793 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19794 })
19795 .ok();
19796 }
19797 Err(err) => {
19798 let message = format!("Failed to copy permalink: {err}");
19799
19800 anyhow::Result::<()>::Err(err).log_err();
19801
19802 if let Some(workspace) = workspace {
19803 workspace
19804 .update_in(cx, |workspace, _, cx| {
19805 struct CopyPermalinkToLine;
19806
19807 workspace.show_toast(
19808 Toast::new(
19809 NotificationId::unique::<CopyPermalinkToLine>(),
19810 message,
19811 ),
19812 cx,
19813 )
19814 })
19815 .ok();
19816 }
19817 }
19818 })
19819 .detach();
19820 }
19821
19822 pub fn copy_file_location(
19823 &mut self,
19824 _: &CopyFileLocation,
19825 _: &mut Window,
19826 cx: &mut Context<Self>,
19827 ) {
19828 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19829 if let Some(file) = self.target_file(cx) {
19830 let path = file.path().display(file.path_style(cx));
19831 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19832 }
19833 }
19834
19835 pub fn open_permalink_to_line(
19836 &mut self,
19837 _: &OpenPermalinkToLine,
19838 window: &mut Window,
19839 cx: &mut Context<Self>,
19840 ) {
19841 let permalink_task = self.get_permalink_to_line(cx);
19842 let workspace = self.workspace();
19843
19844 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19845 Ok(permalink) => {
19846 cx.update(|_, cx| {
19847 cx.open_url(permalink.as_ref());
19848 })
19849 .ok();
19850 }
19851 Err(err) => {
19852 let message = format!("Failed to open permalink: {err}");
19853
19854 anyhow::Result::<()>::Err(err).log_err();
19855
19856 if let Some(workspace) = workspace {
19857 workspace
19858 .update(cx, |workspace, cx| {
19859 struct OpenPermalinkToLine;
19860
19861 workspace.show_toast(
19862 Toast::new(
19863 NotificationId::unique::<OpenPermalinkToLine>(),
19864 message,
19865 ),
19866 cx,
19867 )
19868 })
19869 .ok();
19870 }
19871 }
19872 })
19873 .detach();
19874 }
19875
19876 pub fn insert_uuid_v4(
19877 &mut self,
19878 _: &InsertUuidV4,
19879 window: &mut Window,
19880 cx: &mut Context<Self>,
19881 ) {
19882 self.insert_uuid(UuidVersion::V4, window, cx);
19883 }
19884
19885 pub fn insert_uuid_v7(
19886 &mut self,
19887 _: &InsertUuidV7,
19888 window: &mut Window,
19889 cx: &mut Context<Self>,
19890 ) {
19891 self.insert_uuid(UuidVersion::V7, window, cx);
19892 }
19893
19894 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19895 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19896 self.transact(window, cx, |this, window, cx| {
19897 let edits = this
19898 .selections
19899 .all::<Point>(cx)
19900 .into_iter()
19901 .map(|selection| {
19902 let uuid = match version {
19903 UuidVersion::V4 => uuid::Uuid::new_v4(),
19904 UuidVersion::V7 => uuid::Uuid::now_v7(),
19905 };
19906
19907 (selection.range(), uuid.to_string())
19908 });
19909 this.edit(edits, cx);
19910 this.refresh_edit_prediction(true, false, window, cx);
19911 });
19912 }
19913
19914 pub fn open_selections_in_multibuffer(
19915 &mut self,
19916 _: &OpenSelectionsInMultibuffer,
19917 window: &mut Window,
19918 cx: &mut Context<Self>,
19919 ) {
19920 let multibuffer = self.buffer.read(cx);
19921
19922 let Some(buffer) = multibuffer.as_singleton() else {
19923 return;
19924 };
19925
19926 let Some(workspace) = self.workspace() else {
19927 return;
19928 };
19929
19930 let title = multibuffer.title(cx).to_string();
19931
19932 let locations = self
19933 .selections
19934 .all_anchors(cx)
19935 .iter()
19936 .map(|selection| {
19937 (
19938 buffer.clone(),
19939 (selection.start.text_anchor..selection.end.text_anchor)
19940 .to_point(buffer.read(cx)),
19941 )
19942 })
19943 .into_group_map();
19944
19945 cx.spawn_in(window, async move |_, cx| {
19946 workspace.update_in(cx, |workspace, window, cx| {
19947 Self::open_locations_in_multibuffer(
19948 workspace,
19949 locations,
19950 format!("Selections for '{title}'"),
19951 false,
19952 MultibufferSelectionMode::All,
19953 window,
19954 cx,
19955 );
19956 })
19957 })
19958 .detach();
19959 }
19960
19961 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19962 /// last highlight added will be used.
19963 ///
19964 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19965 pub fn highlight_rows<T: 'static>(
19966 &mut self,
19967 range: Range<Anchor>,
19968 color: Hsla,
19969 options: RowHighlightOptions,
19970 cx: &mut Context<Self>,
19971 ) {
19972 let snapshot = self.buffer().read(cx).snapshot(cx);
19973 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19974 let ix = row_highlights.binary_search_by(|highlight| {
19975 Ordering::Equal
19976 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19977 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19978 });
19979
19980 if let Err(mut ix) = ix {
19981 let index = post_inc(&mut self.highlight_order);
19982
19983 // If this range intersects with the preceding highlight, then merge it with
19984 // the preceding highlight. Otherwise insert a new highlight.
19985 let mut merged = false;
19986 if ix > 0 {
19987 let prev_highlight = &mut row_highlights[ix - 1];
19988 if prev_highlight
19989 .range
19990 .end
19991 .cmp(&range.start, &snapshot)
19992 .is_ge()
19993 {
19994 ix -= 1;
19995 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19996 prev_highlight.range.end = range.end;
19997 }
19998 merged = true;
19999 prev_highlight.index = index;
20000 prev_highlight.color = color;
20001 prev_highlight.options = options;
20002 }
20003 }
20004
20005 if !merged {
20006 row_highlights.insert(
20007 ix,
20008 RowHighlight {
20009 range,
20010 index,
20011 color,
20012 options,
20013 type_id: TypeId::of::<T>(),
20014 },
20015 );
20016 }
20017
20018 // If any of the following highlights intersect with this one, merge them.
20019 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20020 let highlight = &row_highlights[ix];
20021 if next_highlight
20022 .range
20023 .start
20024 .cmp(&highlight.range.end, &snapshot)
20025 .is_le()
20026 {
20027 if next_highlight
20028 .range
20029 .end
20030 .cmp(&highlight.range.end, &snapshot)
20031 .is_gt()
20032 {
20033 row_highlights[ix].range.end = next_highlight.range.end;
20034 }
20035 row_highlights.remove(ix + 1);
20036 } else {
20037 break;
20038 }
20039 }
20040 }
20041 }
20042
20043 /// Remove any highlighted row ranges of the given type that intersect the
20044 /// given ranges.
20045 pub fn remove_highlighted_rows<T: 'static>(
20046 &mut self,
20047 ranges_to_remove: Vec<Range<Anchor>>,
20048 cx: &mut Context<Self>,
20049 ) {
20050 let snapshot = self.buffer().read(cx).snapshot(cx);
20051 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20052 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20053 row_highlights.retain(|highlight| {
20054 while let Some(range_to_remove) = ranges_to_remove.peek() {
20055 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20056 Ordering::Less | Ordering::Equal => {
20057 ranges_to_remove.next();
20058 }
20059 Ordering::Greater => {
20060 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20061 Ordering::Less | Ordering::Equal => {
20062 return false;
20063 }
20064 Ordering::Greater => break,
20065 }
20066 }
20067 }
20068 }
20069
20070 true
20071 })
20072 }
20073
20074 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20075 pub fn clear_row_highlights<T: 'static>(&mut self) {
20076 self.highlighted_rows.remove(&TypeId::of::<T>());
20077 }
20078
20079 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20080 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20081 self.highlighted_rows
20082 .get(&TypeId::of::<T>())
20083 .map_or(&[] as &[_], |vec| vec.as_slice())
20084 .iter()
20085 .map(|highlight| (highlight.range.clone(), highlight.color))
20086 }
20087
20088 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20089 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20090 /// Allows to ignore certain kinds of highlights.
20091 pub fn highlighted_display_rows(
20092 &self,
20093 window: &mut Window,
20094 cx: &mut App,
20095 ) -> BTreeMap<DisplayRow, LineHighlight> {
20096 let snapshot = self.snapshot(window, cx);
20097 let mut used_highlight_orders = HashMap::default();
20098 self.highlighted_rows
20099 .iter()
20100 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20101 .fold(
20102 BTreeMap::<DisplayRow, LineHighlight>::new(),
20103 |mut unique_rows, highlight| {
20104 let start = highlight.range.start.to_display_point(&snapshot);
20105 let end = highlight.range.end.to_display_point(&snapshot);
20106 let start_row = start.row().0;
20107 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20108 && end.column() == 0
20109 {
20110 end.row().0.saturating_sub(1)
20111 } else {
20112 end.row().0
20113 };
20114 for row in start_row..=end_row {
20115 let used_index =
20116 used_highlight_orders.entry(row).or_insert(highlight.index);
20117 if highlight.index >= *used_index {
20118 *used_index = highlight.index;
20119 unique_rows.insert(
20120 DisplayRow(row),
20121 LineHighlight {
20122 include_gutter: highlight.options.include_gutter,
20123 border: None,
20124 background: highlight.color.into(),
20125 type_id: Some(highlight.type_id),
20126 },
20127 );
20128 }
20129 }
20130 unique_rows
20131 },
20132 )
20133 }
20134
20135 pub fn highlighted_display_row_for_autoscroll(
20136 &self,
20137 snapshot: &DisplaySnapshot,
20138 ) -> Option<DisplayRow> {
20139 self.highlighted_rows
20140 .values()
20141 .flat_map(|highlighted_rows| highlighted_rows.iter())
20142 .filter_map(|highlight| {
20143 if highlight.options.autoscroll {
20144 Some(highlight.range.start.to_display_point(snapshot).row())
20145 } else {
20146 None
20147 }
20148 })
20149 .min()
20150 }
20151
20152 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20153 self.highlight_background::<SearchWithinRange>(
20154 ranges,
20155 |colors| colors.colors().editor_document_highlight_read_background,
20156 cx,
20157 )
20158 }
20159
20160 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20161 self.breadcrumb_header = Some(new_header);
20162 }
20163
20164 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20165 self.clear_background_highlights::<SearchWithinRange>(cx);
20166 }
20167
20168 pub fn highlight_background<T: 'static>(
20169 &mut self,
20170 ranges: &[Range<Anchor>],
20171 color_fetcher: fn(&Theme) -> Hsla,
20172 cx: &mut Context<Self>,
20173 ) {
20174 self.background_highlights.insert(
20175 HighlightKey::Type(TypeId::of::<T>()),
20176 (color_fetcher, Arc::from(ranges)),
20177 );
20178 self.scrollbar_marker_state.dirty = true;
20179 cx.notify();
20180 }
20181
20182 pub fn highlight_background_key<T: 'static>(
20183 &mut self,
20184 key: usize,
20185 ranges: &[Range<Anchor>],
20186 color_fetcher: fn(&Theme) -> Hsla,
20187 cx: &mut Context<Self>,
20188 ) {
20189 self.background_highlights.insert(
20190 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20191 (color_fetcher, Arc::from(ranges)),
20192 );
20193 self.scrollbar_marker_state.dirty = true;
20194 cx.notify();
20195 }
20196
20197 pub fn clear_background_highlights<T: 'static>(
20198 &mut self,
20199 cx: &mut Context<Self>,
20200 ) -> Option<BackgroundHighlight> {
20201 let text_highlights = self
20202 .background_highlights
20203 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20204 if !text_highlights.1.is_empty() {
20205 self.scrollbar_marker_state.dirty = true;
20206 cx.notify();
20207 }
20208 Some(text_highlights)
20209 }
20210
20211 pub fn highlight_gutter<T: 'static>(
20212 &mut self,
20213 ranges: impl Into<Vec<Range<Anchor>>>,
20214 color_fetcher: fn(&App) -> Hsla,
20215 cx: &mut Context<Self>,
20216 ) {
20217 self.gutter_highlights
20218 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20219 cx.notify();
20220 }
20221
20222 pub fn clear_gutter_highlights<T: 'static>(
20223 &mut self,
20224 cx: &mut Context<Self>,
20225 ) -> Option<GutterHighlight> {
20226 cx.notify();
20227 self.gutter_highlights.remove(&TypeId::of::<T>())
20228 }
20229
20230 pub fn insert_gutter_highlight<T: 'static>(
20231 &mut self,
20232 range: Range<Anchor>,
20233 color_fetcher: fn(&App) -> Hsla,
20234 cx: &mut Context<Self>,
20235 ) {
20236 let snapshot = self.buffer().read(cx).snapshot(cx);
20237 let mut highlights = self
20238 .gutter_highlights
20239 .remove(&TypeId::of::<T>())
20240 .map(|(_, highlights)| highlights)
20241 .unwrap_or_default();
20242 let ix = highlights.binary_search_by(|highlight| {
20243 Ordering::Equal
20244 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20245 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20246 });
20247 if let Err(ix) = ix {
20248 highlights.insert(ix, range);
20249 }
20250 self.gutter_highlights
20251 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20252 }
20253
20254 pub fn remove_gutter_highlights<T: 'static>(
20255 &mut self,
20256 ranges_to_remove: Vec<Range<Anchor>>,
20257 cx: &mut Context<Self>,
20258 ) {
20259 let snapshot = self.buffer().read(cx).snapshot(cx);
20260 let Some((color_fetcher, mut gutter_highlights)) =
20261 self.gutter_highlights.remove(&TypeId::of::<T>())
20262 else {
20263 return;
20264 };
20265 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20266 gutter_highlights.retain(|highlight| {
20267 while let Some(range_to_remove) = ranges_to_remove.peek() {
20268 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20269 Ordering::Less | Ordering::Equal => {
20270 ranges_to_remove.next();
20271 }
20272 Ordering::Greater => {
20273 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20274 Ordering::Less | Ordering::Equal => {
20275 return false;
20276 }
20277 Ordering::Greater => break,
20278 }
20279 }
20280 }
20281 }
20282
20283 true
20284 });
20285 self.gutter_highlights
20286 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20287 }
20288
20289 #[cfg(feature = "test-support")]
20290 pub fn all_text_highlights(
20291 &self,
20292 window: &mut Window,
20293 cx: &mut Context<Self>,
20294 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20295 let snapshot = self.snapshot(window, cx);
20296 self.display_map.update(cx, |display_map, _| {
20297 display_map
20298 .all_text_highlights()
20299 .map(|highlight| {
20300 let (style, ranges) = highlight.as_ref();
20301 (
20302 *style,
20303 ranges
20304 .iter()
20305 .map(|range| range.clone().to_display_points(&snapshot))
20306 .collect(),
20307 )
20308 })
20309 .collect()
20310 })
20311 }
20312
20313 #[cfg(feature = "test-support")]
20314 pub fn all_text_background_highlights(
20315 &self,
20316 window: &mut Window,
20317 cx: &mut Context<Self>,
20318 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20319 let snapshot = self.snapshot(window, cx);
20320 let buffer = &snapshot.buffer_snapshot();
20321 let start = buffer.anchor_before(0);
20322 let end = buffer.anchor_after(buffer.len());
20323 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20324 }
20325
20326 #[cfg(any(test, feature = "test-support"))]
20327 pub fn sorted_background_highlights_in_range(
20328 &self,
20329 search_range: Range<Anchor>,
20330 display_snapshot: &DisplaySnapshot,
20331 theme: &Theme,
20332 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20333 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20334 res.sort_by(|a, b| {
20335 a.0.start
20336 .cmp(&b.0.start)
20337 .then_with(|| a.0.end.cmp(&b.0.end))
20338 .then_with(|| a.1.cmp(&b.1))
20339 });
20340 res
20341 }
20342
20343 #[cfg(feature = "test-support")]
20344 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20345 let snapshot = self.buffer().read(cx).snapshot(cx);
20346
20347 let highlights = self
20348 .background_highlights
20349 .get(&HighlightKey::Type(TypeId::of::<
20350 items::BufferSearchHighlights,
20351 >()));
20352
20353 if let Some((_color, ranges)) = highlights {
20354 ranges
20355 .iter()
20356 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20357 .collect_vec()
20358 } else {
20359 vec![]
20360 }
20361 }
20362
20363 fn document_highlights_for_position<'a>(
20364 &'a self,
20365 position: Anchor,
20366 buffer: &'a MultiBufferSnapshot,
20367 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20368 let read_highlights = self
20369 .background_highlights
20370 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20371 .map(|h| &h.1);
20372 let write_highlights = self
20373 .background_highlights
20374 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20375 .map(|h| &h.1);
20376 let left_position = position.bias_left(buffer);
20377 let right_position = position.bias_right(buffer);
20378 read_highlights
20379 .into_iter()
20380 .chain(write_highlights)
20381 .flat_map(move |ranges| {
20382 let start_ix = match ranges.binary_search_by(|probe| {
20383 let cmp = probe.end.cmp(&left_position, buffer);
20384 if cmp.is_ge() {
20385 Ordering::Greater
20386 } else {
20387 Ordering::Less
20388 }
20389 }) {
20390 Ok(i) | Err(i) => i,
20391 };
20392
20393 ranges[start_ix..]
20394 .iter()
20395 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20396 })
20397 }
20398
20399 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20400 self.background_highlights
20401 .get(&HighlightKey::Type(TypeId::of::<T>()))
20402 .is_some_and(|(_, highlights)| !highlights.is_empty())
20403 }
20404
20405 /// Returns all background highlights for a given range.
20406 ///
20407 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20408 pub fn background_highlights_in_range(
20409 &self,
20410 search_range: Range<Anchor>,
20411 display_snapshot: &DisplaySnapshot,
20412 theme: &Theme,
20413 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20414 let mut results = Vec::new();
20415 for (color_fetcher, ranges) in self.background_highlights.values() {
20416 let color = color_fetcher(theme);
20417 let start_ix = match ranges.binary_search_by(|probe| {
20418 let cmp = probe
20419 .end
20420 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20421 if cmp.is_gt() {
20422 Ordering::Greater
20423 } else {
20424 Ordering::Less
20425 }
20426 }) {
20427 Ok(i) | Err(i) => i,
20428 };
20429 for range in &ranges[start_ix..] {
20430 if range
20431 .start
20432 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20433 .is_ge()
20434 {
20435 break;
20436 }
20437
20438 let start = range.start.to_display_point(display_snapshot);
20439 let end = range.end.to_display_point(display_snapshot);
20440 results.push((start..end, color))
20441 }
20442 }
20443 results
20444 }
20445
20446 pub fn gutter_highlights_in_range(
20447 &self,
20448 search_range: Range<Anchor>,
20449 display_snapshot: &DisplaySnapshot,
20450 cx: &App,
20451 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20452 let mut results = Vec::new();
20453 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20454 let color = color_fetcher(cx);
20455 let start_ix = match ranges.binary_search_by(|probe| {
20456 let cmp = probe
20457 .end
20458 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20459 if cmp.is_gt() {
20460 Ordering::Greater
20461 } else {
20462 Ordering::Less
20463 }
20464 }) {
20465 Ok(i) | Err(i) => i,
20466 };
20467 for range in &ranges[start_ix..] {
20468 if range
20469 .start
20470 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20471 .is_ge()
20472 {
20473 break;
20474 }
20475
20476 let start = range.start.to_display_point(display_snapshot);
20477 let end = range.end.to_display_point(display_snapshot);
20478 results.push((start..end, color))
20479 }
20480 }
20481 results
20482 }
20483
20484 /// Get the text ranges corresponding to the redaction query
20485 pub fn redacted_ranges(
20486 &self,
20487 search_range: Range<Anchor>,
20488 display_snapshot: &DisplaySnapshot,
20489 cx: &App,
20490 ) -> Vec<Range<DisplayPoint>> {
20491 display_snapshot
20492 .buffer_snapshot()
20493 .redacted_ranges(search_range, |file| {
20494 if let Some(file) = file {
20495 file.is_private()
20496 && EditorSettings::get(
20497 Some(SettingsLocation {
20498 worktree_id: file.worktree_id(cx),
20499 path: file.path().as_ref(),
20500 }),
20501 cx,
20502 )
20503 .redact_private_values
20504 } else {
20505 false
20506 }
20507 })
20508 .map(|range| {
20509 range.start.to_display_point(display_snapshot)
20510 ..range.end.to_display_point(display_snapshot)
20511 })
20512 .collect()
20513 }
20514
20515 pub fn highlight_text_key<T: 'static>(
20516 &mut self,
20517 key: usize,
20518 ranges: Vec<Range<Anchor>>,
20519 style: HighlightStyle,
20520 cx: &mut Context<Self>,
20521 ) {
20522 self.display_map.update(cx, |map, _| {
20523 map.highlight_text(
20524 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20525 ranges,
20526 style,
20527 );
20528 });
20529 cx.notify();
20530 }
20531
20532 pub fn highlight_text<T: 'static>(
20533 &mut self,
20534 ranges: Vec<Range<Anchor>>,
20535 style: HighlightStyle,
20536 cx: &mut Context<Self>,
20537 ) {
20538 self.display_map.update(cx, |map, _| {
20539 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20540 });
20541 cx.notify();
20542 }
20543
20544 pub(crate) fn highlight_inlays<T: 'static>(
20545 &mut self,
20546 highlights: Vec<InlayHighlight>,
20547 style: HighlightStyle,
20548 cx: &mut Context<Self>,
20549 ) {
20550 self.display_map.update(cx, |map, _| {
20551 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20552 });
20553 cx.notify();
20554 }
20555
20556 pub fn text_highlights<'a, T: 'static>(
20557 &'a self,
20558 cx: &'a App,
20559 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20560 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20561 }
20562
20563 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20564 let cleared = self
20565 .display_map
20566 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20567 if cleared {
20568 cx.notify();
20569 }
20570 }
20571
20572 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20573 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20574 && self.focus_handle.is_focused(window)
20575 }
20576
20577 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20578 self.show_cursor_when_unfocused = is_enabled;
20579 cx.notify();
20580 }
20581
20582 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20583 cx.notify();
20584 }
20585
20586 fn on_debug_session_event(
20587 &mut self,
20588 _session: Entity<Session>,
20589 event: &SessionEvent,
20590 cx: &mut Context<Self>,
20591 ) {
20592 if let SessionEvent::InvalidateInlineValue = event {
20593 self.refresh_inline_values(cx);
20594 }
20595 }
20596
20597 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20598 let Some(project) = self.project.clone() else {
20599 return;
20600 };
20601
20602 if !self.inline_value_cache.enabled {
20603 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20604 self.splice_inlays(&inlays, Vec::new(), cx);
20605 return;
20606 }
20607
20608 let current_execution_position = self
20609 .highlighted_rows
20610 .get(&TypeId::of::<ActiveDebugLine>())
20611 .and_then(|lines| lines.last().map(|line| line.range.end));
20612
20613 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20614 let inline_values = editor
20615 .update(cx, |editor, cx| {
20616 let Some(current_execution_position) = current_execution_position else {
20617 return Some(Task::ready(Ok(Vec::new())));
20618 };
20619
20620 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20621 let snapshot = buffer.snapshot(cx);
20622
20623 let excerpt = snapshot.excerpt_containing(
20624 current_execution_position..current_execution_position,
20625 )?;
20626
20627 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20628 })?;
20629
20630 let range =
20631 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20632
20633 project.inline_values(buffer, range, cx)
20634 })
20635 .ok()
20636 .flatten()?
20637 .await
20638 .context("refreshing debugger inlays")
20639 .log_err()?;
20640
20641 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20642
20643 for (buffer_id, inline_value) in inline_values
20644 .into_iter()
20645 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20646 {
20647 buffer_inline_values
20648 .entry(buffer_id)
20649 .or_default()
20650 .push(inline_value);
20651 }
20652
20653 editor
20654 .update(cx, |editor, cx| {
20655 let snapshot = editor.buffer.read(cx).snapshot(cx);
20656 let mut new_inlays = Vec::default();
20657
20658 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20659 let buffer_id = buffer_snapshot.remote_id();
20660 buffer_inline_values
20661 .get(&buffer_id)
20662 .into_iter()
20663 .flatten()
20664 .for_each(|hint| {
20665 let inlay = Inlay::debugger(
20666 post_inc(&mut editor.next_inlay_id),
20667 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20668 hint.text(),
20669 );
20670 if !inlay.text().chars().contains(&'\n') {
20671 new_inlays.push(inlay);
20672 }
20673 });
20674 }
20675
20676 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20677 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20678
20679 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20680 })
20681 .ok()?;
20682 Some(())
20683 });
20684 }
20685
20686 fn on_buffer_event(
20687 &mut self,
20688 multibuffer: &Entity<MultiBuffer>,
20689 event: &multi_buffer::Event,
20690 window: &mut Window,
20691 cx: &mut Context<Self>,
20692 ) {
20693 match event {
20694 multi_buffer::Event::Edited {
20695 singleton_buffer_edited,
20696 edited_buffer,
20697 } => {
20698 self.scrollbar_marker_state.dirty = true;
20699 self.active_indent_guides_state.dirty = true;
20700 self.refresh_active_diagnostics(cx);
20701 self.refresh_code_actions(window, cx);
20702 self.refresh_selected_text_highlights(true, window, cx);
20703 self.refresh_single_line_folds(window, cx);
20704 refresh_matching_bracket_highlights(self, window, cx);
20705 if self.has_active_edit_prediction() {
20706 self.update_visible_edit_prediction(window, cx);
20707 }
20708 if let Some(project) = self.project.as_ref()
20709 && let Some(edited_buffer) = edited_buffer
20710 {
20711 project.update(cx, |project, cx| {
20712 self.registered_buffers
20713 .entry(edited_buffer.read(cx).remote_id())
20714 .or_insert_with(|| {
20715 project.register_buffer_with_language_servers(edited_buffer, cx)
20716 });
20717 });
20718 }
20719 cx.emit(EditorEvent::BufferEdited);
20720 cx.emit(SearchEvent::MatchesInvalidated);
20721
20722 if let Some(buffer) = edited_buffer {
20723 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20724 }
20725
20726 if *singleton_buffer_edited {
20727 if let Some(buffer) = edited_buffer
20728 && buffer.read(cx).file().is_none()
20729 {
20730 cx.emit(EditorEvent::TitleChanged);
20731 }
20732 if let Some(project) = &self.project {
20733 #[allow(clippy::mutable_key_type)]
20734 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20735 multibuffer
20736 .all_buffers()
20737 .into_iter()
20738 .filter_map(|buffer| {
20739 buffer.update(cx, |buffer, cx| {
20740 let language = buffer.language()?;
20741 let should_discard = project.update(cx, |project, cx| {
20742 project.is_local()
20743 && !project.has_language_servers_for(buffer, cx)
20744 });
20745 should_discard.not().then_some(language.clone())
20746 })
20747 })
20748 .collect::<HashSet<_>>()
20749 });
20750 if !languages_affected.is_empty() {
20751 self.refresh_inlay_hints(
20752 InlayHintRefreshReason::BufferEdited(languages_affected),
20753 cx,
20754 );
20755 }
20756 }
20757 }
20758
20759 let Some(project) = &self.project else { return };
20760 let (telemetry, is_via_ssh) = {
20761 let project = project.read(cx);
20762 let telemetry = project.client().telemetry().clone();
20763 let is_via_ssh = project.is_via_remote_server();
20764 (telemetry, is_via_ssh)
20765 };
20766 refresh_linked_ranges(self, window, cx);
20767 telemetry.log_edit_event("editor", is_via_ssh);
20768 }
20769 multi_buffer::Event::ExcerptsAdded {
20770 buffer,
20771 predecessor,
20772 excerpts,
20773 } => {
20774 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20775 let buffer_id = buffer.read(cx).remote_id();
20776 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20777 && let Some(project) = &self.project
20778 {
20779 update_uncommitted_diff_for_buffer(
20780 cx.entity(),
20781 project,
20782 [buffer.clone()],
20783 self.buffer.clone(),
20784 cx,
20785 )
20786 .detach();
20787 }
20788 if self.active_diagnostics != ActiveDiagnostic::All {
20789 self.update_lsp_data(false, Some(buffer_id), window, cx);
20790 }
20791 cx.emit(EditorEvent::ExcerptsAdded {
20792 buffer: buffer.clone(),
20793 predecessor: *predecessor,
20794 excerpts: excerpts.clone(),
20795 });
20796 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20797 }
20798 multi_buffer::Event::ExcerptsRemoved {
20799 ids,
20800 removed_buffer_ids,
20801 } => {
20802 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20803 let buffer = self.buffer.read(cx);
20804 self.registered_buffers
20805 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20806 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20807 cx.emit(EditorEvent::ExcerptsRemoved {
20808 ids: ids.clone(),
20809 removed_buffer_ids: removed_buffer_ids.clone(),
20810 });
20811 }
20812 multi_buffer::Event::ExcerptsEdited {
20813 excerpt_ids,
20814 buffer_ids,
20815 } => {
20816 self.display_map.update(cx, |map, cx| {
20817 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20818 });
20819 cx.emit(EditorEvent::ExcerptsEdited {
20820 ids: excerpt_ids.clone(),
20821 });
20822 }
20823 multi_buffer::Event::ExcerptsExpanded { ids } => {
20824 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20825 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20826 }
20827 multi_buffer::Event::Reparsed(buffer_id) => {
20828 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20829 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20830
20831 cx.emit(EditorEvent::Reparsed(*buffer_id));
20832 }
20833 multi_buffer::Event::DiffHunksToggled => {
20834 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20835 }
20836 multi_buffer::Event::LanguageChanged(buffer_id) => {
20837 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20838 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20839 cx.emit(EditorEvent::Reparsed(*buffer_id));
20840 cx.notify();
20841 }
20842 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20843 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20844 multi_buffer::Event::FileHandleChanged
20845 | multi_buffer::Event::Reloaded
20846 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20847 multi_buffer::Event::DiagnosticsUpdated => {
20848 self.update_diagnostics_state(window, cx);
20849 }
20850 _ => {}
20851 };
20852 }
20853
20854 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20855 if !self.diagnostics_enabled() {
20856 return;
20857 }
20858 self.refresh_active_diagnostics(cx);
20859 self.refresh_inline_diagnostics(true, window, cx);
20860 self.scrollbar_marker_state.dirty = true;
20861 cx.notify();
20862 }
20863
20864 pub fn start_temporary_diff_override(&mut self) {
20865 self.load_diff_task.take();
20866 self.temporary_diff_override = true;
20867 }
20868
20869 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20870 self.temporary_diff_override = false;
20871 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20872 self.buffer.update(cx, |buffer, cx| {
20873 buffer.set_all_diff_hunks_collapsed(cx);
20874 });
20875
20876 if let Some(project) = self.project.clone() {
20877 self.load_diff_task = Some(
20878 update_uncommitted_diff_for_buffer(
20879 cx.entity(),
20880 &project,
20881 self.buffer.read(cx).all_buffers(),
20882 self.buffer.clone(),
20883 cx,
20884 )
20885 .shared(),
20886 );
20887 }
20888 }
20889
20890 fn on_display_map_changed(
20891 &mut self,
20892 _: Entity<DisplayMap>,
20893 _: &mut Window,
20894 cx: &mut Context<Self>,
20895 ) {
20896 cx.notify();
20897 }
20898
20899 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20900 if self.diagnostics_enabled() {
20901 let new_severity = EditorSettings::get_global(cx)
20902 .diagnostics_max_severity
20903 .unwrap_or(DiagnosticSeverity::Hint);
20904 self.set_max_diagnostics_severity(new_severity, cx);
20905 }
20906 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20907 self.update_edit_prediction_settings(cx);
20908 self.refresh_edit_prediction(true, false, window, cx);
20909 self.refresh_inline_values(cx);
20910 self.refresh_inlay_hints(
20911 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20912 self.selections.newest_anchor().head(),
20913 &self.buffer.read(cx).snapshot(cx),
20914 cx,
20915 )),
20916 cx,
20917 );
20918
20919 let old_cursor_shape = self.cursor_shape;
20920 let old_show_breadcrumbs = self.show_breadcrumbs;
20921
20922 {
20923 let editor_settings = EditorSettings::get_global(cx);
20924 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20925 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20926 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20927 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20928 }
20929
20930 if old_cursor_shape != self.cursor_shape {
20931 cx.emit(EditorEvent::CursorShapeChanged);
20932 }
20933
20934 if old_show_breadcrumbs != self.show_breadcrumbs {
20935 cx.emit(EditorEvent::BreadcrumbsChanged);
20936 }
20937
20938 let project_settings = ProjectSettings::get_global(cx);
20939 self.serialize_dirty_buffers =
20940 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20941
20942 if self.mode.is_full() {
20943 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20944 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20945 if self.show_inline_diagnostics != show_inline_diagnostics {
20946 self.show_inline_diagnostics = show_inline_diagnostics;
20947 self.refresh_inline_diagnostics(false, window, cx);
20948 }
20949
20950 if self.git_blame_inline_enabled != inline_blame_enabled {
20951 self.toggle_git_blame_inline_internal(false, window, cx);
20952 }
20953
20954 let minimap_settings = EditorSettings::get_global(cx).minimap;
20955 if self.minimap_visibility != MinimapVisibility::Disabled {
20956 if self.minimap_visibility.settings_visibility()
20957 != minimap_settings.minimap_enabled()
20958 {
20959 self.set_minimap_visibility(
20960 MinimapVisibility::for_mode(self.mode(), cx),
20961 window,
20962 cx,
20963 );
20964 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20965 minimap_entity.update(cx, |minimap_editor, cx| {
20966 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20967 })
20968 }
20969 }
20970 }
20971
20972 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20973 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20974 }) {
20975 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20976 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20977 }
20978 self.refresh_colors(false, None, window, cx);
20979 }
20980
20981 cx.notify();
20982 }
20983
20984 pub fn set_searchable(&mut self, searchable: bool) {
20985 self.searchable = searchable;
20986 }
20987
20988 pub fn searchable(&self) -> bool {
20989 self.searchable
20990 }
20991
20992 fn open_proposed_changes_editor(
20993 &mut self,
20994 _: &OpenProposedChangesEditor,
20995 window: &mut Window,
20996 cx: &mut Context<Self>,
20997 ) {
20998 let Some(workspace) = self.workspace() else {
20999 cx.propagate();
21000 return;
21001 };
21002
21003 let selections = self.selections.all::<usize>(cx);
21004 let multi_buffer = self.buffer.read(cx);
21005 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21006 let mut new_selections_by_buffer = HashMap::default();
21007 for selection in selections {
21008 for (buffer, range, _) in
21009 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21010 {
21011 let mut range = range.to_point(buffer);
21012 range.start.column = 0;
21013 range.end.column = buffer.line_len(range.end.row);
21014 new_selections_by_buffer
21015 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21016 .or_insert(Vec::new())
21017 .push(range)
21018 }
21019 }
21020
21021 let proposed_changes_buffers = new_selections_by_buffer
21022 .into_iter()
21023 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21024 .collect::<Vec<_>>();
21025 let proposed_changes_editor = cx.new(|cx| {
21026 ProposedChangesEditor::new(
21027 "Proposed changes",
21028 proposed_changes_buffers,
21029 self.project.clone(),
21030 window,
21031 cx,
21032 )
21033 });
21034
21035 window.defer(cx, move |window, cx| {
21036 workspace.update(cx, |workspace, cx| {
21037 workspace.active_pane().update(cx, |pane, cx| {
21038 pane.add_item(
21039 Box::new(proposed_changes_editor),
21040 true,
21041 true,
21042 None,
21043 window,
21044 cx,
21045 );
21046 });
21047 });
21048 });
21049 }
21050
21051 pub fn open_excerpts_in_split(
21052 &mut self,
21053 _: &OpenExcerptsSplit,
21054 window: &mut Window,
21055 cx: &mut Context<Self>,
21056 ) {
21057 self.open_excerpts_common(None, true, window, cx)
21058 }
21059
21060 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21061 self.open_excerpts_common(None, false, window, cx)
21062 }
21063
21064 fn open_excerpts_common(
21065 &mut self,
21066 jump_data: Option<JumpData>,
21067 split: bool,
21068 window: &mut Window,
21069 cx: &mut Context<Self>,
21070 ) {
21071 let Some(workspace) = self.workspace() else {
21072 cx.propagate();
21073 return;
21074 };
21075
21076 if self.buffer.read(cx).is_singleton() {
21077 cx.propagate();
21078 return;
21079 }
21080
21081 let mut new_selections_by_buffer = HashMap::default();
21082 match &jump_data {
21083 Some(JumpData::MultiBufferPoint {
21084 excerpt_id,
21085 position,
21086 anchor,
21087 line_offset_from_top,
21088 }) => {
21089 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21090 if let Some(buffer) = multi_buffer_snapshot
21091 .buffer_id_for_excerpt(*excerpt_id)
21092 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21093 {
21094 let buffer_snapshot = buffer.read(cx).snapshot();
21095 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21096 language::ToPoint::to_point(anchor, &buffer_snapshot)
21097 } else {
21098 buffer_snapshot.clip_point(*position, Bias::Left)
21099 };
21100 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21101 new_selections_by_buffer.insert(
21102 buffer,
21103 (
21104 vec![jump_to_offset..jump_to_offset],
21105 Some(*line_offset_from_top),
21106 ),
21107 );
21108 }
21109 }
21110 Some(JumpData::MultiBufferRow {
21111 row,
21112 line_offset_from_top,
21113 }) => {
21114 let point = MultiBufferPoint::new(row.0, 0);
21115 if let Some((buffer, buffer_point, _)) =
21116 self.buffer.read(cx).point_to_buffer_point(point, cx)
21117 {
21118 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21119 new_selections_by_buffer
21120 .entry(buffer)
21121 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21122 .0
21123 .push(buffer_offset..buffer_offset)
21124 }
21125 }
21126 None => {
21127 let selections = self.selections.all::<usize>(cx);
21128 let multi_buffer = self.buffer.read(cx);
21129 for selection in selections {
21130 for (snapshot, range, _, anchor) in multi_buffer
21131 .snapshot(cx)
21132 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21133 {
21134 if let Some(anchor) = anchor {
21135 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21136 else {
21137 continue;
21138 };
21139 let offset = text::ToOffset::to_offset(
21140 &anchor.text_anchor,
21141 &buffer_handle.read(cx).snapshot(),
21142 );
21143 let range = offset..offset;
21144 new_selections_by_buffer
21145 .entry(buffer_handle)
21146 .or_insert((Vec::new(), None))
21147 .0
21148 .push(range)
21149 } else {
21150 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21151 else {
21152 continue;
21153 };
21154 new_selections_by_buffer
21155 .entry(buffer_handle)
21156 .or_insert((Vec::new(), None))
21157 .0
21158 .push(range)
21159 }
21160 }
21161 }
21162 }
21163 }
21164
21165 new_selections_by_buffer
21166 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21167
21168 if new_selections_by_buffer.is_empty() {
21169 return;
21170 }
21171
21172 // We defer the pane interaction because we ourselves are a workspace item
21173 // and activating a new item causes the pane to call a method on us reentrantly,
21174 // which panics if we're on the stack.
21175 window.defer(cx, move |window, cx| {
21176 workspace.update(cx, |workspace, cx| {
21177 let pane = if split {
21178 workspace.adjacent_pane(window, cx)
21179 } else {
21180 workspace.active_pane().clone()
21181 };
21182
21183 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21184 let editor = buffer
21185 .read(cx)
21186 .file()
21187 .is_none()
21188 .then(|| {
21189 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21190 // so `workspace.open_project_item` will never find them, always opening a new editor.
21191 // Instead, we try to activate the existing editor in the pane first.
21192 let (editor, pane_item_index) =
21193 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21194 let editor = item.downcast::<Editor>()?;
21195 let singleton_buffer =
21196 editor.read(cx).buffer().read(cx).as_singleton()?;
21197 if singleton_buffer == buffer {
21198 Some((editor, i))
21199 } else {
21200 None
21201 }
21202 })?;
21203 pane.update(cx, |pane, cx| {
21204 pane.activate_item(pane_item_index, true, true, window, cx)
21205 });
21206 Some(editor)
21207 })
21208 .flatten()
21209 .unwrap_or_else(|| {
21210 workspace.open_project_item::<Self>(
21211 pane.clone(),
21212 buffer,
21213 true,
21214 true,
21215 window,
21216 cx,
21217 )
21218 });
21219
21220 editor.update(cx, |editor, cx| {
21221 let autoscroll = match scroll_offset {
21222 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21223 None => Autoscroll::newest(),
21224 };
21225 let nav_history = editor.nav_history.take();
21226 editor.change_selections(
21227 SelectionEffects::scroll(autoscroll),
21228 window,
21229 cx,
21230 |s| {
21231 s.select_ranges(ranges);
21232 },
21233 );
21234 editor.nav_history = nav_history;
21235 });
21236 }
21237 })
21238 });
21239 }
21240
21241 // For now, don't allow opening excerpts in buffers that aren't backed by
21242 // regular project files.
21243 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21244 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21245 }
21246
21247 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21248 let snapshot = self.buffer.read(cx).read(cx);
21249 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21250 Some(
21251 ranges
21252 .iter()
21253 .map(move |range| {
21254 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21255 })
21256 .collect(),
21257 )
21258 }
21259
21260 fn selection_replacement_ranges(
21261 &self,
21262 range: Range<OffsetUtf16>,
21263 cx: &mut App,
21264 ) -> Vec<Range<OffsetUtf16>> {
21265 let selections = self.selections.all::<OffsetUtf16>(cx);
21266 let newest_selection = selections
21267 .iter()
21268 .max_by_key(|selection| selection.id)
21269 .unwrap();
21270 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21271 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21272 let snapshot = self.buffer.read(cx).read(cx);
21273 selections
21274 .into_iter()
21275 .map(|mut selection| {
21276 selection.start.0 =
21277 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21278 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21279 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21280 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21281 })
21282 .collect()
21283 }
21284
21285 fn report_editor_event(
21286 &self,
21287 reported_event: ReportEditorEvent,
21288 file_extension: Option<String>,
21289 cx: &App,
21290 ) {
21291 if cfg!(any(test, feature = "test-support")) {
21292 return;
21293 }
21294
21295 let Some(project) = &self.project else { return };
21296
21297 // If None, we are in a file without an extension
21298 let file = self
21299 .buffer
21300 .read(cx)
21301 .as_singleton()
21302 .and_then(|b| b.read(cx).file());
21303 let file_extension = file_extension.or(file
21304 .as_ref()
21305 .and_then(|file| Path::new(file.file_name(cx)).extension())
21306 .and_then(|e| e.to_str())
21307 .map(|a| a.to_string()));
21308
21309 let vim_mode = vim_enabled(cx);
21310
21311 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21312 let copilot_enabled = edit_predictions_provider
21313 == language::language_settings::EditPredictionProvider::Copilot;
21314 let copilot_enabled_for_language = self
21315 .buffer
21316 .read(cx)
21317 .language_settings(cx)
21318 .show_edit_predictions;
21319
21320 let project = project.read(cx);
21321 let event_type = reported_event.event_type();
21322
21323 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21324 telemetry::event!(
21325 event_type,
21326 type = if auto_saved {"autosave"} else {"manual"},
21327 file_extension,
21328 vim_mode,
21329 copilot_enabled,
21330 copilot_enabled_for_language,
21331 edit_predictions_provider,
21332 is_via_ssh = project.is_via_remote_server(),
21333 );
21334 } else {
21335 telemetry::event!(
21336 event_type,
21337 file_extension,
21338 vim_mode,
21339 copilot_enabled,
21340 copilot_enabled_for_language,
21341 edit_predictions_provider,
21342 is_via_ssh = project.is_via_remote_server(),
21343 );
21344 };
21345 }
21346
21347 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21348 /// with each line being an array of {text, highlight} objects.
21349 fn copy_highlight_json(
21350 &mut self,
21351 _: &CopyHighlightJson,
21352 window: &mut Window,
21353 cx: &mut Context<Self>,
21354 ) {
21355 #[derive(Serialize)]
21356 struct Chunk<'a> {
21357 text: String,
21358 highlight: Option<&'a str>,
21359 }
21360
21361 let snapshot = self.buffer.read(cx).snapshot(cx);
21362 let range = self
21363 .selected_text_range(false, window, cx)
21364 .and_then(|selection| {
21365 if selection.range.is_empty() {
21366 None
21367 } else {
21368 Some(selection.range)
21369 }
21370 })
21371 .unwrap_or_else(|| 0..snapshot.len());
21372
21373 let chunks = snapshot.chunks(range, true);
21374 let mut lines = Vec::new();
21375 let mut line: VecDeque<Chunk> = VecDeque::new();
21376
21377 let Some(style) = self.style.as_ref() else {
21378 return;
21379 };
21380
21381 for chunk in chunks {
21382 let highlight = chunk
21383 .syntax_highlight_id
21384 .and_then(|id| id.name(&style.syntax));
21385 let mut chunk_lines = chunk.text.split('\n').peekable();
21386 while let Some(text) = chunk_lines.next() {
21387 let mut merged_with_last_token = false;
21388 if let Some(last_token) = line.back_mut()
21389 && last_token.highlight == highlight
21390 {
21391 last_token.text.push_str(text);
21392 merged_with_last_token = true;
21393 }
21394
21395 if !merged_with_last_token {
21396 line.push_back(Chunk {
21397 text: text.into(),
21398 highlight,
21399 });
21400 }
21401
21402 if chunk_lines.peek().is_some() {
21403 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21404 line.pop_front();
21405 }
21406 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21407 line.pop_back();
21408 }
21409
21410 lines.push(mem::take(&mut line));
21411 }
21412 }
21413 }
21414
21415 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21416 return;
21417 };
21418 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21419 }
21420
21421 pub fn open_context_menu(
21422 &mut self,
21423 _: &OpenContextMenu,
21424 window: &mut Window,
21425 cx: &mut Context<Self>,
21426 ) {
21427 self.request_autoscroll(Autoscroll::newest(), cx);
21428 let position = self.selections.newest_display(cx).start;
21429 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21430 }
21431
21432 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21433 &self.inlay_hint_cache
21434 }
21435
21436 pub fn replay_insert_event(
21437 &mut self,
21438 text: &str,
21439 relative_utf16_range: Option<Range<isize>>,
21440 window: &mut Window,
21441 cx: &mut Context<Self>,
21442 ) {
21443 if !self.input_enabled {
21444 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21445 return;
21446 }
21447 if let Some(relative_utf16_range) = relative_utf16_range {
21448 let selections = self.selections.all::<OffsetUtf16>(cx);
21449 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21450 let new_ranges = selections.into_iter().map(|range| {
21451 let start = OffsetUtf16(
21452 range
21453 .head()
21454 .0
21455 .saturating_add_signed(relative_utf16_range.start),
21456 );
21457 let end = OffsetUtf16(
21458 range
21459 .head()
21460 .0
21461 .saturating_add_signed(relative_utf16_range.end),
21462 );
21463 start..end
21464 });
21465 s.select_ranges(new_ranges);
21466 });
21467 }
21468
21469 self.handle_input(text, window, cx);
21470 }
21471
21472 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21473 let Some(provider) = self.semantics_provider.as_ref() else {
21474 return false;
21475 };
21476
21477 let mut supports = false;
21478 self.buffer().update(cx, |this, cx| {
21479 this.for_each_buffer(|buffer| {
21480 supports |= provider.supports_inlay_hints(buffer, cx);
21481 });
21482 });
21483
21484 supports
21485 }
21486
21487 pub fn is_focused(&self, window: &Window) -> bool {
21488 self.focus_handle.is_focused(window)
21489 }
21490
21491 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21492 cx.emit(EditorEvent::Focused);
21493
21494 if let Some(descendant) = self
21495 .last_focused_descendant
21496 .take()
21497 .and_then(|descendant| descendant.upgrade())
21498 {
21499 window.focus(&descendant);
21500 } else {
21501 if let Some(blame) = self.blame.as_ref() {
21502 blame.update(cx, GitBlame::focus)
21503 }
21504
21505 self.blink_manager.update(cx, BlinkManager::enable);
21506 self.show_cursor_names(window, cx);
21507 self.buffer.update(cx, |buffer, cx| {
21508 buffer.finalize_last_transaction(cx);
21509 if self.leader_id.is_none() {
21510 buffer.set_active_selections(
21511 &self.selections.disjoint_anchors_arc(),
21512 self.selections.line_mode(),
21513 self.cursor_shape,
21514 cx,
21515 );
21516 }
21517 });
21518 }
21519 }
21520
21521 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21522 cx.emit(EditorEvent::FocusedIn)
21523 }
21524
21525 fn handle_focus_out(
21526 &mut self,
21527 event: FocusOutEvent,
21528 _window: &mut Window,
21529 cx: &mut Context<Self>,
21530 ) {
21531 if event.blurred != self.focus_handle {
21532 self.last_focused_descendant = Some(event.blurred);
21533 }
21534 self.selection_drag_state = SelectionDragState::None;
21535 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21536 }
21537
21538 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21539 self.blink_manager.update(cx, BlinkManager::disable);
21540 self.buffer
21541 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21542
21543 if let Some(blame) = self.blame.as_ref() {
21544 blame.update(cx, GitBlame::blur)
21545 }
21546 if !self.hover_state.focused(window, cx) {
21547 hide_hover(self, cx);
21548 }
21549 if !self
21550 .context_menu
21551 .borrow()
21552 .as_ref()
21553 .is_some_and(|context_menu| context_menu.focused(window, cx))
21554 {
21555 self.hide_context_menu(window, cx);
21556 }
21557 self.take_active_edit_prediction(cx);
21558 cx.emit(EditorEvent::Blurred);
21559 cx.notify();
21560 }
21561
21562 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21563 let mut pending: String = window
21564 .pending_input_keystrokes()
21565 .into_iter()
21566 .flatten()
21567 .filter_map(|keystroke| {
21568 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21569 keystroke.key_char.clone()
21570 } else {
21571 None
21572 }
21573 })
21574 .collect();
21575
21576 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21577 pending = "".to_string();
21578 }
21579
21580 let existing_pending = self
21581 .text_highlights::<PendingInput>(cx)
21582 .map(|(_, ranges)| ranges.to_vec());
21583 if existing_pending.is_none() && pending.is_empty() {
21584 return;
21585 }
21586 let transaction =
21587 self.transact(window, cx, |this, window, cx| {
21588 let selections = this.selections.all::<usize>(cx);
21589 let edits = selections
21590 .iter()
21591 .map(|selection| (selection.end..selection.end, pending.clone()));
21592 this.edit(edits, cx);
21593 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21594 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21595 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21596 }));
21597 });
21598 if let Some(existing_ranges) = existing_pending {
21599 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21600 this.edit(edits, cx);
21601 }
21602 });
21603
21604 let snapshot = self.snapshot(window, cx);
21605 let ranges = self
21606 .selections
21607 .all::<usize>(cx)
21608 .into_iter()
21609 .map(|selection| {
21610 snapshot.buffer_snapshot().anchor_after(selection.end)
21611 ..snapshot
21612 .buffer_snapshot()
21613 .anchor_before(selection.end + pending.len())
21614 })
21615 .collect();
21616
21617 if pending.is_empty() {
21618 self.clear_highlights::<PendingInput>(cx);
21619 } else {
21620 self.highlight_text::<PendingInput>(
21621 ranges,
21622 HighlightStyle {
21623 underline: Some(UnderlineStyle {
21624 thickness: px(1.),
21625 color: None,
21626 wavy: false,
21627 }),
21628 ..Default::default()
21629 },
21630 cx,
21631 );
21632 }
21633
21634 self.ime_transaction = self.ime_transaction.or(transaction);
21635 if let Some(transaction) = self.ime_transaction {
21636 self.buffer.update(cx, |buffer, cx| {
21637 buffer.group_until_transaction(transaction, cx);
21638 });
21639 }
21640
21641 if self.text_highlights::<PendingInput>(cx).is_none() {
21642 self.ime_transaction.take();
21643 }
21644 }
21645
21646 pub fn register_action_renderer(
21647 &mut self,
21648 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21649 ) -> Subscription {
21650 let id = self.next_editor_action_id.post_inc();
21651 self.editor_actions
21652 .borrow_mut()
21653 .insert(id, Box::new(listener));
21654
21655 let editor_actions = self.editor_actions.clone();
21656 Subscription::new(move || {
21657 editor_actions.borrow_mut().remove(&id);
21658 })
21659 }
21660
21661 pub fn register_action<A: Action>(
21662 &mut self,
21663 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21664 ) -> Subscription {
21665 let id = self.next_editor_action_id.post_inc();
21666 let listener = Arc::new(listener);
21667 self.editor_actions.borrow_mut().insert(
21668 id,
21669 Box::new(move |_, window, _| {
21670 let listener = listener.clone();
21671 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21672 let action = action.downcast_ref().unwrap();
21673 if phase == DispatchPhase::Bubble {
21674 listener(action, window, cx)
21675 }
21676 })
21677 }),
21678 );
21679
21680 let editor_actions = self.editor_actions.clone();
21681 Subscription::new(move || {
21682 editor_actions.borrow_mut().remove(&id);
21683 })
21684 }
21685
21686 pub fn file_header_size(&self) -> u32 {
21687 FILE_HEADER_HEIGHT
21688 }
21689
21690 pub fn restore(
21691 &mut self,
21692 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21693 window: &mut Window,
21694 cx: &mut Context<Self>,
21695 ) {
21696 let workspace = self.workspace();
21697 let project = self.project();
21698 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21699 let mut tasks = Vec::new();
21700 for (buffer_id, changes) in revert_changes {
21701 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21702 buffer.update(cx, |buffer, cx| {
21703 buffer.edit(
21704 changes
21705 .into_iter()
21706 .map(|(range, text)| (range, text.to_string())),
21707 None,
21708 cx,
21709 );
21710 });
21711
21712 if let Some(project) =
21713 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21714 {
21715 project.update(cx, |project, cx| {
21716 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21717 })
21718 }
21719 }
21720 }
21721 tasks
21722 });
21723 cx.spawn_in(window, async move |_, cx| {
21724 for (buffer, task) in save_tasks {
21725 let result = task.await;
21726 if result.is_err() {
21727 let Some(path) = buffer
21728 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21729 .ok()
21730 else {
21731 continue;
21732 };
21733 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21734 let Some(task) = cx
21735 .update_window_entity(workspace, |workspace, window, cx| {
21736 workspace
21737 .open_path_preview(path, None, false, false, false, window, cx)
21738 })
21739 .ok()
21740 else {
21741 continue;
21742 };
21743 task.await.log_err();
21744 }
21745 }
21746 }
21747 })
21748 .detach();
21749 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21750 selections.refresh()
21751 });
21752 }
21753
21754 pub fn to_pixel_point(
21755 &self,
21756 source: multi_buffer::Anchor,
21757 editor_snapshot: &EditorSnapshot,
21758 window: &mut Window,
21759 ) -> Option<gpui::Point<Pixels>> {
21760 let source_point = source.to_display_point(editor_snapshot);
21761 self.display_to_pixel_point(source_point, editor_snapshot, window)
21762 }
21763
21764 pub fn display_to_pixel_point(
21765 &self,
21766 source: DisplayPoint,
21767 editor_snapshot: &EditorSnapshot,
21768 window: &mut Window,
21769 ) -> Option<gpui::Point<Pixels>> {
21770 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21771 let text_layout_details = self.text_layout_details(window);
21772 let scroll_top = text_layout_details
21773 .scroll_anchor
21774 .scroll_position(editor_snapshot)
21775 .y;
21776
21777 if source.row().as_f64() < scroll_top.floor() {
21778 return None;
21779 }
21780 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21781 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21782 Some(gpui::Point::new(source_x, source_y))
21783 }
21784
21785 pub fn has_visible_completions_menu(&self) -> bool {
21786 !self.edit_prediction_preview_is_active()
21787 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21788 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21789 })
21790 }
21791
21792 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21793 if self.mode.is_minimap() {
21794 return;
21795 }
21796 self.addons
21797 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21798 }
21799
21800 pub fn unregister_addon<T: Addon>(&mut self) {
21801 self.addons.remove(&std::any::TypeId::of::<T>());
21802 }
21803
21804 pub fn addon<T: Addon>(&self) -> Option<&T> {
21805 let type_id = std::any::TypeId::of::<T>();
21806 self.addons
21807 .get(&type_id)
21808 .and_then(|item| item.to_any().downcast_ref::<T>())
21809 }
21810
21811 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21812 let type_id = std::any::TypeId::of::<T>();
21813 self.addons
21814 .get_mut(&type_id)
21815 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21816 }
21817
21818 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21819 let text_layout_details = self.text_layout_details(window);
21820 let style = &text_layout_details.editor_style;
21821 let font_id = window.text_system().resolve_font(&style.text.font());
21822 let font_size = style.text.font_size.to_pixels(window.rem_size());
21823 let line_height = style.text.line_height_in_pixels(window.rem_size());
21824 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21825 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21826
21827 CharacterDimensions {
21828 em_width,
21829 em_advance,
21830 line_height,
21831 }
21832 }
21833
21834 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21835 self.load_diff_task.clone()
21836 }
21837
21838 fn read_metadata_from_db(
21839 &mut self,
21840 item_id: u64,
21841 workspace_id: WorkspaceId,
21842 window: &mut Window,
21843 cx: &mut Context<Editor>,
21844 ) {
21845 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21846 && !self.mode.is_minimap()
21847 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21848 {
21849 let buffer_snapshot = OnceCell::new();
21850
21851 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21852 && !folds.is_empty()
21853 {
21854 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21855 self.fold_ranges(
21856 folds
21857 .into_iter()
21858 .map(|(start, end)| {
21859 snapshot.clip_offset(start, Bias::Left)
21860 ..snapshot.clip_offset(end, Bias::Right)
21861 })
21862 .collect(),
21863 false,
21864 window,
21865 cx,
21866 );
21867 }
21868
21869 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21870 && !selections.is_empty()
21871 {
21872 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21873 // skip adding the initial selection to selection history
21874 self.selection_history.mode = SelectionHistoryMode::Skipping;
21875 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21876 s.select_ranges(selections.into_iter().map(|(start, end)| {
21877 snapshot.clip_offset(start, Bias::Left)
21878 ..snapshot.clip_offset(end, Bias::Right)
21879 }));
21880 });
21881 self.selection_history.mode = SelectionHistoryMode::Normal;
21882 };
21883 }
21884
21885 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21886 }
21887
21888 fn update_lsp_data(
21889 &mut self,
21890 ignore_cache: bool,
21891 for_buffer: Option<BufferId>,
21892 window: &mut Window,
21893 cx: &mut Context<'_, Self>,
21894 ) {
21895 self.pull_diagnostics(for_buffer, window, cx);
21896 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21897 }
21898}
21899
21900fn edit_for_markdown_paste<'a>(
21901 buffer: &MultiBufferSnapshot,
21902 range: Range<usize>,
21903 to_insert: &'a str,
21904 url: Option<url::Url>,
21905) -> (Range<usize>, Cow<'a, str>) {
21906 if url.is_none() {
21907 return (range, Cow::Borrowed(to_insert));
21908 };
21909
21910 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21911
21912 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21913 Cow::Borrowed(to_insert)
21914 } else {
21915 Cow::Owned(format!("[{old_text}]({to_insert})"))
21916 };
21917 (range, new_text)
21918}
21919
21920fn vim_enabled(cx: &App) -> bool {
21921 vim_mode_setting::VimModeSetting::try_get(cx)
21922 .map(|vim_mode| vim_mode.0)
21923 .unwrap_or(false)
21924}
21925
21926fn process_completion_for_edit(
21927 completion: &Completion,
21928 intent: CompletionIntent,
21929 buffer: &Entity<Buffer>,
21930 cursor_position: &text::Anchor,
21931 cx: &mut Context<Editor>,
21932) -> CompletionEdit {
21933 let buffer = buffer.read(cx);
21934 let buffer_snapshot = buffer.snapshot();
21935 let (snippet, new_text) = if completion.is_snippet() {
21936 let mut snippet_source = completion.new_text.clone();
21937 // Workaround for typescript language server issues so that methods don't expand within
21938 // strings and functions with type expressions. The previous point is used because the query
21939 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21940 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
21941 let previous_point = if previous_point.column > 0 {
21942 cursor_position.to_previous_offset(&buffer_snapshot)
21943 } else {
21944 cursor_position.to_offset(&buffer_snapshot)
21945 };
21946 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21947 && scope.prefers_label_for_snippet_in_completion()
21948 && let Some(label) = completion.label()
21949 && matches!(
21950 completion.kind(),
21951 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21952 )
21953 {
21954 snippet_source = label;
21955 }
21956 match Snippet::parse(&snippet_source).log_err() {
21957 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21958 None => (None, completion.new_text.clone()),
21959 }
21960 } else {
21961 (None, completion.new_text.clone())
21962 };
21963
21964 let mut range_to_replace = {
21965 let replace_range = &completion.replace_range;
21966 if let CompletionSource::Lsp {
21967 insert_range: Some(insert_range),
21968 ..
21969 } = &completion.source
21970 {
21971 debug_assert_eq!(
21972 insert_range.start, replace_range.start,
21973 "insert_range and replace_range should start at the same position"
21974 );
21975 debug_assert!(
21976 insert_range
21977 .start
21978 .cmp(cursor_position, &buffer_snapshot)
21979 .is_le(),
21980 "insert_range should start before or at cursor position"
21981 );
21982 debug_assert!(
21983 replace_range
21984 .start
21985 .cmp(cursor_position, &buffer_snapshot)
21986 .is_le(),
21987 "replace_range should start before or at cursor position"
21988 );
21989
21990 let should_replace = match intent {
21991 CompletionIntent::CompleteWithInsert => false,
21992 CompletionIntent::CompleteWithReplace => true,
21993 CompletionIntent::Complete | CompletionIntent::Compose => {
21994 let insert_mode =
21995 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21996 .completions
21997 .lsp_insert_mode;
21998 match insert_mode {
21999 LspInsertMode::Insert => false,
22000 LspInsertMode::Replace => true,
22001 LspInsertMode::ReplaceSubsequence => {
22002 let mut text_to_replace = buffer.chars_for_range(
22003 buffer.anchor_before(replace_range.start)
22004 ..buffer.anchor_after(replace_range.end),
22005 );
22006 let mut current_needle = text_to_replace.next();
22007 for haystack_ch in completion.label.text.chars() {
22008 if let Some(needle_ch) = current_needle
22009 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22010 {
22011 current_needle = text_to_replace.next();
22012 }
22013 }
22014 current_needle.is_none()
22015 }
22016 LspInsertMode::ReplaceSuffix => {
22017 if replace_range
22018 .end
22019 .cmp(cursor_position, &buffer_snapshot)
22020 .is_gt()
22021 {
22022 let range_after_cursor = *cursor_position..replace_range.end;
22023 let text_after_cursor = buffer
22024 .text_for_range(
22025 buffer.anchor_before(range_after_cursor.start)
22026 ..buffer.anchor_after(range_after_cursor.end),
22027 )
22028 .collect::<String>()
22029 .to_ascii_lowercase();
22030 completion
22031 .label
22032 .text
22033 .to_ascii_lowercase()
22034 .ends_with(&text_after_cursor)
22035 } else {
22036 true
22037 }
22038 }
22039 }
22040 }
22041 };
22042
22043 if should_replace {
22044 replace_range.clone()
22045 } else {
22046 insert_range.clone()
22047 }
22048 } else {
22049 replace_range.clone()
22050 }
22051 };
22052
22053 if range_to_replace
22054 .end
22055 .cmp(cursor_position, &buffer_snapshot)
22056 .is_lt()
22057 {
22058 range_to_replace.end = *cursor_position;
22059 }
22060
22061 CompletionEdit {
22062 new_text,
22063 replace_range: range_to_replace.to_offset(buffer),
22064 snippet,
22065 }
22066}
22067
22068struct CompletionEdit {
22069 new_text: String,
22070 replace_range: Range<usize>,
22071 snippet: Option<Snippet>,
22072}
22073
22074fn insert_extra_newline_brackets(
22075 buffer: &MultiBufferSnapshot,
22076 range: Range<usize>,
22077 language: &language::LanguageScope,
22078) -> bool {
22079 let leading_whitespace_len = buffer
22080 .reversed_chars_at(range.start)
22081 .take_while(|c| c.is_whitespace() && *c != '\n')
22082 .map(|c| c.len_utf8())
22083 .sum::<usize>();
22084 let trailing_whitespace_len = buffer
22085 .chars_at(range.end)
22086 .take_while(|c| c.is_whitespace() && *c != '\n')
22087 .map(|c| c.len_utf8())
22088 .sum::<usize>();
22089 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22090
22091 language.brackets().any(|(pair, enabled)| {
22092 let pair_start = pair.start.trim_end();
22093 let pair_end = pair.end.trim_start();
22094
22095 enabled
22096 && pair.newline
22097 && buffer.contains_str_at(range.end, pair_end)
22098 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22099 })
22100}
22101
22102fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22103 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22104 [(buffer, range, _)] => (*buffer, range.clone()),
22105 _ => return false,
22106 };
22107 let pair = {
22108 let mut result: Option<BracketMatch> = None;
22109
22110 for pair in buffer
22111 .all_bracket_ranges(range.clone())
22112 .filter(move |pair| {
22113 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22114 })
22115 {
22116 let len = pair.close_range.end - pair.open_range.start;
22117
22118 if let Some(existing) = &result {
22119 let existing_len = existing.close_range.end - existing.open_range.start;
22120 if len > existing_len {
22121 continue;
22122 }
22123 }
22124
22125 result = Some(pair);
22126 }
22127
22128 result
22129 };
22130 let Some(pair) = pair else {
22131 return false;
22132 };
22133 pair.newline_only
22134 && buffer
22135 .chars_for_range(pair.open_range.end..range.start)
22136 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22137 .all(|c| c.is_whitespace() && c != '\n')
22138}
22139
22140fn update_uncommitted_diff_for_buffer(
22141 editor: Entity<Editor>,
22142 project: &Entity<Project>,
22143 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22144 buffer: Entity<MultiBuffer>,
22145 cx: &mut App,
22146) -> Task<()> {
22147 let mut tasks = Vec::new();
22148 project.update(cx, |project, cx| {
22149 for buffer in buffers {
22150 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22151 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22152 }
22153 }
22154 });
22155 cx.spawn(async move |cx| {
22156 let diffs = future::join_all(tasks).await;
22157 if editor
22158 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22159 .unwrap_or(false)
22160 {
22161 return;
22162 }
22163
22164 buffer
22165 .update(cx, |buffer, cx| {
22166 for diff in diffs.into_iter().flatten() {
22167 buffer.add_diff(diff, cx);
22168 }
22169 })
22170 .ok();
22171 })
22172}
22173
22174fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22175 let tab_size = tab_size.get() as usize;
22176 let mut width = offset;
22177
22178 for ch in text.chars() {
22179 width += if ch == '\t' {
22180 tab_size - (width % tab_size)
22181 } else {
22182 1
22183 };
22184 }
22185
22186 width - offset
22187}
22188
22189#[cfg(test)]
22190mod tests {
22191 use super::*;
22192
22193 #[test]
22194 fn test_string_size_with_expanded_tabs() {
22195 let nz = |val| NonZeroU32::new(val).unwrap();
22196 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22197 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22198 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22199 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22200 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22201 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22202 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22203 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22204 }
22205}
22206
22207/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22208struct WordBreakingTokenizer<'a> {
22209 input: &'a str,
22210}
22211
22212impl<'a> WordBreakingTokenizer<'a> {
22213 fn new(input: &'a str) -> Self {
22214 Self { input }
22215 }
22216}
22217
22218fn is_char_ideographic(ch: char) -> bool {
22219 use unicode_script::Script::*;
22220 use unicode_script::UnicodeScript;
22221 matches!(ch.script(), Han | Tangut | Yi)
22222}
22223
22224fn is_grapheme_ideographic(text: &str) -> bool {
22225 text.chars().any(is_char_ideographic)
22226}
22227
22228fn is_grapheme_whitespace(text: &str) -> bool {
22229 text.chars().any(|x| x.is_whitespace())
22230}
22231
22232fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22233 text.chars()
22234 .next()
22235 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22236}
22237
22238#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22239enum WordBreakToken<'a> {
22240 Word { token: &'a str, grapheme_len: usize },
22241 InlineWhitespace { token: &'a str, grapheme_len: usize },
22242 Newline,
22243}
22244
22245impl<'a> Iterator for WordBreakingTokenizer<'a> {
22246 /// Yields a span, the count of graphemes in the token, and whether it was
22247 /// whitespace. Note that it also breaks at word boundaries.
22248 type Item = WordBreakToken<'a>;
22249
22250 fn next(&mut self) -> Option<Self::Item> {
22251 use unicode_segmentation::UnicodeSegmentation;
22252 if self.input.is_empty() {
22253 return None;
22254 }
22255
22256 let mut iter = self.input.graphemes(true).peekable();
22257 let mut offset = 0;
22258 let mut grapheme_len = 0;
22259 if let Some(first_grapheme) = iter.next() {
22260 let is_newline = first_grapheme == "\n";
22261 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22262 offset += first_grapheme.len();
22263 grapheme_len += 1;
22264 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22265 if let Some(grapheme) = iter.peek().copied()
22266 && should_stay_with_preceding_ideograph(grapheme)
22267 {
22268 offset += grapheme.len();
22269 grapheme_len += 1;
22270 }
22271 } else {
22272 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22273 let mut next_word_bound = words.peek().copied();
22274 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22275 next_word_bound = words.next();
22276 }
22277 while let Some(grapheme) = iter.peek().copied() {
22278 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22279 break;
22280 };
22281 if is_grapheme_whitespace(grapheme) != is_whitespace
22282 || (grapheme == "\n") != is_newline
22283 {
22284 break;
22285 };
22286 offset += grapheme.len();
22287 grapheme_len += 1;
22288 iter.next();
22289 }
22290 }
22291 let token = &self.input[..offset];
22292 self.input = &self.input[offset..];
22293 if token == "\n" {
22294 Some(WordBreakToken::Newline)
22295 } else if is_whitespace {
22296 Some(WordBreakToken::InlineWhitespace {
22297 token,
22298 grapheme_len,
22299 })
22300 } else {
22301 Some(WordBreakToken::Word {
22302 token,
22303 grapheme_len,
22304 })
22305 }
22306 } else {
22307 None
22308 }
22309 }
22310}
22311
22312#[test]
22313fn test_word_breaking_tokenizer() {
22314 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22315 ("", &[]),
22316 (" ", &[whitespace(" ", 2)]),
22317 ("Ʒ", &[word("Ʒ", 1)]),
22318 ("Ǽ", &[word("Ǽ", 1)]),
22319 ("⋑", &[word("⋑", 1)]),
22320 ("⋑⋑", &[word("⋑⋑", 2)]),
22321 (
22322 "原理,进而",
22323 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22324 ),
22325 (
22326 "hello world",
22327 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22328 ),
22329 (
22330 "hello, world",
22331 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22332 ),
22333 (
22334 " hello world",
22335 &[
22336 whitespace(" ", 2),
22337 word("hello", 5),
22338 whitespace(" ", 1),
22339 word("world", 5),
22340 ],
22341 ),
22342 (
22343 "这是什么 \n 钢笔",
22344 &[
22345 word("这", 1),
22346 word("是", 1),
22347 word("什", 1),
22348 word("么", 1),
22349 whitespace(" ", 1),
22350 newline(),
22351 whitespace(" ", 1),
22352 word("钢", 1),
22353 word("笔", 1),
22354 ],
22355 ),
22356 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22357 ];
22358
22359 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22360 WordBreakToken::Word {
22361 token,
22362 grapheme_len,
22363 }
22364 }
22365
22366 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22367 WordBreakToken::InlineWhitespace {
22368 token,
22369 grapheme_len,
22370 }
22371 }
22372
22373 fn newline() -> WordBreakToken<'static> {
22374 WordBreakToken::Newline
22375 }
22376
22377 for (input, result) in tests {
22378 assert_eq!(
22379 WordBreakingTokenizer::new(input)
22380 .collect::<Vec<_>>()
22381 .as_slice(),
22382 *result,
22383 );
22384 }
22385}
22386
22387fn wrap_with_prefix(
22388 first_line_prefix: String,
22389 subsequent_lines_prefix: String,
22390 unwrapped_text: String,
22391 wrap_column: usize,
22392 tab_size: NonZeroU32,
22393 preserve_existing_whitespace: bool,
22394) -> String {
22395 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22396 let subsequent_lines_prefix_len =
22397 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22398 let mut wrapped_text = String::new();
22399 let mut current_line = first_line_prefix;
22400 let mut is_first_line = true;
22401
22402 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22403 let mut current_line_len = first_line_prefix_len;
22404 let mut in_whitespace = false;
22405 for token in tokenizer {
22406 let have_preceding_whitespace = in_whitespace;
22407 match token {
22408 WordBreakToken::Word {
22409 token,
22410 grapheme_len,
22411 } => {
22412 in_whitespace = false;
22413 let current_prefix_len = if is_first_line {
22414 first_line_prefix_len
22415 } else {
22416 subsequent_lines_prefix_len
22417 };
22418 if current_line_len + grapheme_len > wrap_column
22419 && current_line_len != current_prefix_len
22420 {
22421 wrapped_text.push_str(current_line.trim_end());
22422 wrapped_text.push('\n');
22423 is_first_line = false;
22424 current_line = subsequent_lines_prefix.clone();
22425 current_line_len = subsequent_lines_prefix_len;
22426 }
22427 current_line.push_str(token);
22428 current_line_len += grapheme_len;
22429 }
22430 WordBreakToken::InlineWhitespace {
22431 mut token,
22432 mut grapheme_len,
22433 } => {
22434 in_whitespace = true;
22435 if have_preceding_whitespace && !preserve_existing_whitespace {
22436 continue;
22437 }
22438 if !preserve_existing_whitespace {
22439 // Keep a single whitespace grapheme as-is
22440 if let Some(first) =
22441 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22442 {
22443 token = first;
22444 } else {
22445 token = " ";
22446 }
22447 grapheme_len = 1;
22448 }
22449 let current_prefix_len = if is_first_line {
22450 first_line_prefix_len
22451 } else {
22452 subsequent_lines_prefix_len
22453 };
22454 if current_line_len + grapheme_len > wrap_column {
22455 wrapped_text.push_str(current_line.trim_end());
22456 wrapped_text.push('\n');
22457 is_first_line = false;
22458 current_line = subsequent_lines_prefix.clone();
22459 current_line_len = subsequent_lines_prefix_len;
22460 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22461 current_line.push_str(token);
22462 current_line_len += grapheme_len;
22463 }
22464 }
22465 WordBreakToken::Newline => {
22466 in_whitespace = true;
22467 let current_prefix_len = if is_first_line {
22468 first_line_prefix_len
22469 } else {
22470 subsequent_lines_prefix_len
22471 };
22472 if preserve_existing_whitespace {
22473 wrapped_text.push_str(current_line.trim_end());
22474 wrapped_text.push('\n');
22475 is_first_line = false;
22476 current_line = subsequent_lines_prefix.clone();
22477 current_line_len = subsequent_lines_prefix_len;
22478 } else if have_preceding_whitespace {
22479 continue;
22480 } else if current_line_len + 1 > wrap_column
22481 && current_line_len != current_prefix_len
22482 {
22483 wrapped_text.push_str(current_line.trim_end());
22484 wrapped_text.push('\n');
22485 is_first_line = false;
22486 current_line = subsequent_lines_prefix.clone();
22487 current_line_len = subsequent_lines_prefix_len;
22488 } else if current_line_len != current_prefix_len {
22489 current_line.push(' ');
22490 current_line_len += 1;
22491 }
22492 }
22493 }
22494 }
22495
22496 if !current_line.is_empty() {
22497 wrapped_text.push_str(¤t_line);
22498 }
22499 wrapped_text
22500}
22501
22502#[test]
22503fn test_wrap_with_prefix() {
22504 assert_eq!(
22505 wrap_with_prefix(
22506 "# ".to_string(),
22507 "# ".to_string(),
22508 "abcdefg".to_string(),
22509 4,
22510 NonZeroU32::new(4).unwrap(),
22511 false,
22512 ),
22513 "# abcdefg"
22514 );
22515 assert_eq!(
22516 wrap_with_prefix(
22517 "".to_string(),
22518 "".to_string(),
22519 "\thello world".to_string(),
22520 8,
22521 NonZeroU32::new(4).unwrap(),
22522 false,
22523 ),
22524 "hello\nworld"
22525 );
22526 assert_eq!(
22527 wrap_with_prefix(
22528 "// ".to_string(),
22529 "// ".to_string(),
22530 "xx \nyy zz aa bb cc".to_string(),
22531 12,
22532 NonZeroU32::new(4).unwrap(),
22533 false,
22534 ),
22535 "// xx yy zz\n// aa bb cc"
22536 );
22537 assert_eq!(
22538 wrap_with_prefix(
22539 String::new(),
22540 String::new(),
22541 "这是什么 \n 钢笔".to_string(),
22542 3,
22543 NonZeroU32::new(4).unwrap(),
22544 false,
22545 ),
22546 "这是什\n么 钢\n笔"
22547 );
22548 assert_eq!(
22549 wrap_with_prefix(
22550 String::new(),
22551 String::new(),
22552 format!("foo{}bar", '\u{2009}'), // thin space
22553 80,
22554 NonZeroU32::new(4).unwrap(),
22555 false,
22556 ),
22557 format!("foo{}bar", '\u{2009}')
22558 );
22559}
22560
22561pub trait CollaborationHub {
22562 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22563 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22564 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22565}
22566
22567impl CollaborationHub for Entity<Project> {
22568 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22569 self.read(cx).collaborators()
22570 }
22571
22572 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22573 self.read(cx).user_store().read(cx).participant_indices()
22574 }
22575
22576 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22577 let this = self.read(cx);
22578 let user_ids = this.collaborators().values().map(|c| c.user_id);
22579 this.user_store().read(cx).participant_names(user_ids, cx)
22580 }
22581}
22582
22583pub trait SemanticsProvider {
22584 fn hover(
22585 &self,
22586 buffer: &Entity<Buffer>,
22587 position: text::Anchor,
22588 cx: &mut App,
22589 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22590
22591 fn inline_values(
22592 &self,
22593 buffer_handle: Entity<Buffer>,
22594 range: Range<text::Anchor>,
22595 cx: &mut App,
22596 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22597
22598 fn inlay_hints(
22599 &self,
22600 buffer_handle: Entity<Buffer>,
22601 range: Range<text::Anchor>,
22602 cx: &mut App,
22603 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22604
22605 fn resolve_inlay_hint(
22606 &self,
22607 hint: InlayHint,
22608 buffer_handle: Entity<Buffer>,
22609 server_id: LanguageServerId,
22610 cx: &mut App,
22611 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22612
22613 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22614
22615 fn document_highlights(
22616 &self,
22617 buffer: &Entity<Buffer>,
22618 position: text::Anchor,
22619 cx: &mut App,
22620 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22621
22622 fn definitions(
22623 &self,
22624 buffer: &Entity<Buffer>,
22625 position: text::Anchor,
22626 kind: GotoDefinitionKind,
22627 cx: &mut App,
22628 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22629
22630 fn range_for_rename(
22631 &self,
22632 buffer: &Entity<Buffer>,
22633 position: text::Anchor,
22634 cx: &mut App,
22635 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22636
22637 fn perform_rename(
22638 &self,
22639 buffer: &Entity<Buffer>,
22640 position: text::Anchor,
22641 new_name: String,
22642 cx: &mut App,
22643 ) -> Option<Task<Result<ProjectTransaction>>>;
22644}
22645
22646pub trait CompletionProvider {
22647 fn completions(
22648 &self,
22649 excerpt_id: ExcerptId,
22650 buffer: &Entity<Buffer>,
22651 buffer_position: text::Anchor,
22652 trigger: CompletionContext,
22653 window: &mut Window,
22654 cx: &mut Context<Editor>,
22655 ) -> Task<Result<Vec<CompletionResponse>>>;
22656
22657 fn resolve_completions(
22658 &self,
22659 _buffer: Entity<Buffer>,
22660 _completion_indices: Vec<usize>,
22661 _completions: Rc<RefCell<Box<[Completion]>>>,
22662 _cx: &mut Context<Editor>,
22663 ) -> Task<Result<bool>> {
22664 Task::ready(Ok(false))
22665 }
22666
22667 fn apply_additional_edits_for_completion(
22668 &self,
22669 _buffer: Entity<Buffer>,
22670 _completions: Rc<RefCell<Box<[Completion]>>>,
22671 _completion_index: usize,
22672 _push_to_history: bool,
22673 _cx: &mut Context<Editor>,
22674 ) -> Task<Result<Option<language::Transaction>>> {
22675 Task::ready(Ok(None))
22676 }
22677
22678 fn is_completion_trigger(
22679 &self,
22680 buffer: &Entity<Buffer>,
22681 position: language::Anchor,
22682 text: &str,
22683 trigger_in_words: bool,
22684 menu_is_open: bool,
22685 cx: &mut Context<Editor>,
22686 ) -> bool;
22687
22688 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22689
22690 fn sort_completions(&self) -> bool {
22691 true
22692 }
22693
22694 fn filter_completions(&self) -> bool {
22695 true
22696 }
22697}
22698
22699pub trait CodeActionProvider {
22700 fn id(&self) -> Arc<str>;
22701
22702 fn code_actions(
22703 &self,
22704 buffer: &Entity<Buffer>,
22705 range: Range<text::Anchor>,
22706 window: &mut Window,
22707 cx: &mut App,
22708 ) -> Task<Result<Vec<CodeAction>>>;
22709
22710 fn apply_code_action(
22711 &self,
22712 buffer_handle: Entity<Buffer>,
22713 action: CodeAction,
22714 excerpt_id: ExcerptId,
22715 push_to_history: bool,
22716 window: &mut Window,
22717 cx: &mut App,
22718 ) -> Task<Result<ProjectTransaction>>;
22719}
22720
22721impl CodeActionProvider for Entity<Project> {
22722 fn id(&self) -> Arc<str> {
22723 "project".into()
22724 }
22725
22726 fn code_actions(
22727 &self,
22728 buffer: &Entity<Buffer>,
22729 range: Range<text::Anchor>,
22730 _window: &mut Window,
22731 cx: &mut App,
22732 ) -> Task<Result<Vec<CodeAction>>> {
22733 self.update(cx, |project, cx| {
22734 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22735 let code_actions = project.code_actions(buffer, range, None, cx);
22736 cx.background_spawn(async move {
22737 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22738 Ok(code_lens_actions
22739 .context("code lens fetch")?
22740 .into_iter()
22741 .flatten()
22742 .chain(
22743 code_actions
22744 .context("code action fetch")?
22745 .into_iter()
22746 .flatten(),
22747 )
22748 .collect())
22749 })
22750 })
22751 }
22752
22753 fn apply_code_action(
22754 &self,
22755 buffer_handle: Entity<Buffer>,
22756 action: CodeAction,
22757 _excerpt_id: ExcerptId,
22758 push_to_history: bool,
22759 _window: &mut Window,
22760 cx: &mut App,
22761 ) -> Task<Result<ProjectTransaction>> {
22762 self.update(cx, |project, cx| {
22763 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22764 })
22765 }
22766}
22767
22768fn snippet_completions(
22769 project: &Project,
22770 buffer: &Entity<Buffer>,
22771 buffer_position: text::Anchor,
22772 cx: &mut App,
22773) -> Task<Result<CompletionResponse>> {
22774 let languages = buffer.read(cx).languages_at(buffer_position);
22775 let snippet_store = project.snippets().read(cx);
22776
22777 let scopes: Vec<_> = languages
22778 .iter()
22779 .filter_map(|language| {
22780 let language_name = language.lsp_id();
22781 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22782
22783 if snippets.is_empty() {
22784 None
22785 } else {
22786 Some((language.default_scope(), snippets))
22787 }
22788 })
22789 .collect();
22790
22791 if scopes.is_empty() {
22792 return Task::ready(Ok(CompletionResponse {
22793 completions: vec![],
22794 display_options: CompletionDisplayOptions::default(),
22795 is_incomplete: false,
22796 }));
22797 }
22798
22799 let snapshot = buffer.read(cx).text_snapshot();
22800 let executor = cx.background_executor().clone();
22801
22802 cx.background_spawn(async move {
22803 let mut is_incomplete = false;
22804 let mut completions: Vec<Completion> = Vec::new();
22805 for (scope, snippets) in scopes.into_iter() {
22806 let classifier =
22807 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22808
22809 const MAX_WORD_PREFIX_LEN: usize = 128;
22810 let last_word: String = snapshot
22811 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22812 .take(MAX_WORD_PREFIX_LEN)
22813 .take_while(|c| classifier.is_word(*c))
22814 .collect::<String>()
22815 .chars()
22816 .rev()
22817 .collect();
22818
22819 if last_word.is_empty() {
22820 return Ok(CompletionResponse {
22821 completions: vec![],
22822 display_options: CompletionDisplayOptions::default(),
22823 is_incomplete: true,
22824 });
22825 }
22826
22827 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22828 let to_lsp = |point: &text::Anchor| {
22829 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22830 point_to_lsp(end)
22831 };
22832 let lsp_end = to_lsp(&buffer_position);
22833
22834 let candidates = snippets
22835 .iter()
22836 .enumerate()
22837 .flat_map(|(ix, snippet)| {
22838 snippet
22839 .prefix
22840 .iter()
22841 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22842 })
22843 .collect::<Vec<StringMatchCandidate>>();
22844
22845 const MAX_RESULTS: usize = 100;
22846 let mut matches = fuzzy::match_strings(
22847 &candidates,
22848 &last_word,
22849 last_word.chars().any(|c| c.is_uppercase()),
22850 true,
22851 MAX_RESULTS,
22852 &Default::default(),
22853 executor.clone(),
22854 )
22855 .await;
22856
22857 if matches.len() >= MAX_RESULTS {
22858 is_incomplete = true;
22859 }
22860
22861 // Remove all candidates where the query's start does not match the start of any word in the candidate
22862 if let Some(query_start) = last_word.chars().next() {
22863 matches.retain(|string_match| {
22864 split_words(&string_match.string).any(|word| {
22865 // Check that the first codepoint of the word as lowercase matches the first
22866 // codepoint of the query as lowercase
22867 word.chars()
22868 .flat_map(|codepoint| codepoint.to_lowercase())
22869 .zip(query_start.to_lowercase())
22870 .all(|(word_cp, query_cp)| word_cp == query_cp)
22871 })
22872 });
22873 }
22874
22875 let matched_strings = matches
22876 .into_iter()
22877 .map(|m| m.string)
22878 .collect::<HashSet<_>>();
22879
22880 completions.extend(snippets.iter().filter_map(|snippet| {
22881 let matching_prefix = snippet
22882 .prefix
22883 .iter()
22884 .find(|prefix| matched_strings.contains(*prefix))?;
22885 let start = as_offset - last_word.len();
22886 let start = snapshot.anchor_before(start);
22887 let range = start..buffer_position;
22888 let lsp_start = to_lsp(&start);
22889 let lsp_range = lsp::Range {
22890 start: lsp_start,
22891 end: lsp_end,
22892 };
22893 Some(Completion {
22894 replace_range: range,
22895 new_text: snippet.body.clone(),
22896 source: CompletionSource::Lsp {
22897 insert_range: None,
22898 server_id: LanguageServerId(usize::MAX),
22899 resolved: true,
22900 lsp_completion: Box::new(lsp::CompletionItem {
22901 label: snippet.prefix.first().unwrap().clone(),
22902 kind: Some(CompletionItemKind::SNIPPET),
22903 label_details: snippet.description.as_ref().map(|description| {
22904 lsp::CompletionItemLabelDetails {
22905 detail: Some(description.clone()),
22906 description: None,
22907 }
22908 }),
22909 insert_text_format: Some(InsertTextFormat::SNIPPET),
22910 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22911 lsp::InsertReplaceEdit {
22912 new_text: snippet.body.clone(),
22913 insert: lsp_range,
22914 replace: lsp_range,
22915 },
22916 )),
22917 filter_text: Some(snippet.body.clone()),
22918 sort_text: Some(char::MAX.to_string()),
22919 ..lsp::CompletionItem::default()
22920 }),
22921 lsp_defaults: None,
22922 },
22923 label: CodeLabel {
22924 text: matching_prefix.clone(),
22925 runs: Vec::new(),
22926 filter_range: 0..matching_prefix.len(),
22927 },
22928 icon_path: None,
22929 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22930 single_line: snippet.name.clone().into(),
22931 plain_text: snippet
22932 .description
22933 .clone()
22934 .map(|description| description.into()),
22935 }),
22936 insert_text_mode: None,
22937 confirm: None,
22938 })
22939 }))
22940 }
22941
22942 Ok(CompletionResponse {
22943 completions,
22944 display_options: CompletionDisplayOptions::default(),
22945 is_incomplete,
22946 })
22947 })
22948}
22949
22950impl CompletionProvider for Entity<Project> {
22951 fn completions(
22952 &self,
22953 _excerpt_id: ExcerptId,
22954 buffer: &Entity<Buffer>,
22955 buffer_position: text::Anchor,
22956 options: CompletionContext,
22957 _window: &mut Window,
22958 cx: &mut Context<Editor>,
22959 ) -> Task<Result<Vec<CompletionResponse>>> {
22960 self.update(cx, |project, cx| {
22961 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22962 let project_completions = project.completions(buffer, buffer_position, options, cx);
22963 cx.background_spawn(async move {
22964 let mut responses = project_completions.await?;
22965 let snippets = snippets.await?;
22966 if !snippets.completions.is_empty() {
22967 responses.push(snippets);
22968 }
22969 Ok(responses)
22970 })
22971 })
22972 }
22973
22974 fn resolve_completions(
22975 &self,
22976 buffer: Entity<Buffer>,
22977 completion_indices: Vec<usize>,
22978 completions: Rc<RefCell<Box<[Completion]>>>,
22979 cx: &mut Context<Editor>,
22980 ) -> Task<Result<bool>> {
22981 self.update(cx, |project, cx| {
22982 project.lsp_store().update(cx, |lsp_store, cx| {
22983 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22984 })
22985 })
22986 }
22987
22988 fn apply_additional_edits_for_completion(
22989 &self,
22990 buffer: Entity<Buffer>,
22991 completions: Rc<RefCell<Box<[Completion]>>>,
22992 completion_index: usize,
22993 push_to_history: bool,
22994 cx: &mut Context<Editor>,
22995 ) -> Task<Result<Option<language::Transaction>>> {
22996 self.update(cx, |project, cx| {
22997 project.lsp_store().update(cx, |lsp_store, cx| {
22998 lsp_store.apply_additional_edits_for_completion(
22999 buffer,
23000 completions,
23001 completion_index,
23002 push_to_history,
23003 cx,
23004 )
23005 })
23006 })
23007 }
23008
23009 fn is_completion_trigger(
23010 &self,
23011 buffer: &Entity<Buffer>,
23012 position: language::Anchor,
23013 text: &str,
23014 trigger_in_words: bool,
23015 menu_is_open: bool,
23016 cx: &mut Context<Editor>,
23017 ) -> bool {
23018 let mut chars = text.chars();
23019 let char = if let Some(char) = chars.next() {
23020 char
23021 } else {
23022 return false;
23023 };
23024 if chars.next().is_some() {
23025 return false;
23026 }
23027
23028 let buffer = buffer.read(cx);
23029 let snapshot = buffer.snapshot();
23030 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23031 return false;
23032 }
23033 let classifier = snapshot
23034 .char_classifier_at(position)
23035 .scope_context(Some(CharScopeContext::Completion));
23036 if trigger_in_words && classifier.is_word(char) {
23037 return true;
23038 }
23039
23040 buffer.completion_triggers().contains(text)
23041 }
23042}
23043
23044impl SemanticsProvider for Entity<Project> {
23045 fn hover(
23046 &self,
23047 buffer: &Entity<Buffer>,
23048 position: text::Anchor,
23049 cx: &mut App,
23050 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23051 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23052 }
23053
23054 fn document_highlights(
23055 &self,
23056 buffer: &Entity<Buffer>,
23057 position: text::Anchor,
23058 cx: &mut App,
23059 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23060 Some(self.update(cx, |project, cx| {
23061 project.document_highlights(buffer, position, cx)
23062 }))
23063 }
23064
23065 fn definitions(
23066 &self,
23067 buffer: &Entity<Buffer>,
23068 position: text::Anchor,
23069 kind: GotoDefinitionKind,
23070 cx: &mut App,
23071 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23072 Some(self.update(cx, |project, cx| match kind {
23073 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23074 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23075 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23076 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23077 }))
23078 }
23079
23080 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23081 self.update(cx, |project, cx| {
23082 if project
23083 .active_debug_session(cx)
23084 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23085 {
23086 return true;
23087 }
23088
23089 buffer.update(cx, |buffer, cx| {
23090 project.any_language_server_supports_inlay_hints(buffer, cx)
23091 })
23092 })
23093 }
23094
23095 fn inline_values(
23096 &self,
23097 buffer_handle: Entity<Buffer>,
23098 range: Range<text::Anchor>,
23099 cx: &mut App,
23100 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23101 self.update(cx, |project, cx| {
23102 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23103
23104 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23105 })
23106 }
23107
23108 fn inlay_hints(
23109 &self,
23110 buffer_handle: Entity<Buffer>,
23111 range: Range<text::Anchor>,
23112 cx: &mut App,
23113 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23114 Some(self.update(cx, |project, cx| {
23115 project.inlay_hints(buffer_handle, range, cx)
23116 }))
23117 }
23118
23119 fn resolve_inlay_hint(
23120 &self,
23121 hint: InlayHint,
23122 buffer_handle: Entity<Buffer>,
23123 server_id: LanguageServerId,
23124 cx: &mut App,
23125 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23126 Some(self.update(cx, |project, cx| {
23127 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23128 }))
23129 }
23130
23131 fn range_for_rename(
23132 &self,
23133 buffer: &Entity<Buffer>,
23134 position: text::Anchor,
23135 cx: &mut App,
23136 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23137 Some(self.update(cx, |project, cx| {
23138 let buffer = buffer.clone();
23139 let task = project.prepare_rename(buffer.clone(), position, cx);
23140 cx.spawn(async move |_, cx| {
23141 Ok(match task.await? {
23142 PrepareRenameResponse::Success(range) => Some(range),
23143 PrepareRenameResponse::InvalidPosition => None,
23144 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23145 // Fallback on using TreeSitter info to determine identifier range
23146 buffer.read_with(cx, |buffer, _| {
23147 let snapshot = buffer.snapshot();
23148 let (range, kind) = snapshot.surrounding_word(position, None);
23149 if kind != Some(CharKind::Word) {
23150 return None;
23151 }
23152 Some(
23153 snapshot.anchor_before(range.start)
23154 ..snapshot.anchor_after(range.end),
23155 )
23156 })?
23157 }
23158 })
23159 })
23160 }))
23161 }
23162
23163 fn perform_rename(
23164 &self,
23165 buffer: &Entity<Buffer>,
23166 position: text::Anchor,
23167 new_name: String,
23168 cx: &mut App,
23169 ) -> Option<Task<Result<ProjectTransaction>>> {
23170 Some(self.update(cx, |project, cx| {
23171 project.perform_rename(buffer.clone(), position, new_name, cx)
23172 }))
23173 }
23174}
23175
23176fn inlay_hint_settings(
23177 location: Anchor,
23178 snapshot: &MultiBufferSnapshot,
23179 cx: &mut Context<Editor>,
23180) -> InlayHintSettings {
23181 let file = snapshot.file_at(location);
23182 let language = snapshot.language_at(location).map(|l| l.name());
23183 language_settings(language, file, cx).inlay_hints
23184}
23185
23186fn consume_contiguous_rows(
23187 contiguous_row_selections: &mut Vec<Selection<Point>>,
23188 selection: &Selection<Point>,
23189 display_map: &DisplaySnapshot,
23190 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23191) -> (MultiBufferRow, MultiBufferRow) {
23192 contiguous_row_selections.push(selection.clone());
23193 let start_row = starting_row(selection, display_map);
23194 let mut end_row = ending_row(selection, display_map);
23195
23196 while let Some(next_selection) = selections.peek() {
23197 if next_selection.start.row <= end_row.0 {
23198 end_row = ending_row(next_selection, display_map);
23199 contiguous_row_selections.push(selections.next().unwrap().clone());
23200 } else {
23201 break;
23202 }
23203 }
23204 (start_row, end_row)
23205}
23206
23207fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23208 if selection.start.column > 0 {
23209 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23210 } else {
23211 MultiBufferRow(selection.start.row)
23212 }
23213}
23214
23215fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23216 if next_selection.end.column > 0 || next_selection.is_empty() {
23217 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23218 } else {
23219 MultiBufferRow(next_selection.end.row)
23220 }
23221}
23222
23223impl EditorSnapshot {
23224 pub fn remote_selections_in_range<'a>(
23225 &'a self,
23226 range: &'a Range<Anchor>,
23227 collaboration_hub: &dyn CollaborationHub,
23228 cx: &'a App,
23229 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23230 let participant_names = collaboration_hub.user_names(cx);
23231 let participant_indices = collaboration_hub.user_participant_indices(cx);
23232 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23233 let collaborators_by_replica_id = collaborators_by_peer_id
23234 .values()
23235 .map(|collaborator| (collaborator.replica_id, collaborator))
23236 .collect::<HashMap<_, _>>();
23237 self.buffer_snapshot()
23238 .selections_in_range(range, false)
23239 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23240 if replica_id == AGENT_REPLICA_ID {
23241 Some(RemoteSelection {
23242 replica_id,
23243 selection,
23244 cursor_shape,
23245 line_mode,
23246 collaborator_id: CollaboratorId::Agent,
23247 user_name: Some("Agent".into()),
23248 color: cx.theme().players().agent(),
23249 })
23250 } else {
23251 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23252 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23253 let user_name = participant_names.get(&collaborator.user_id).cloned();
23254 Some(RemoteSelection {
23255 replica_id,
23256 selection,
23257 cursor_shape,
23258 line_mode,
23259 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23260 user_name,
23261 color: if let Some(index) = participant_index {
23262 cx.theme().players().color_for_participant(index.0)
23263 } else {
23264 cx.theme().players().absent()
23265 },
23266 })
23267 }
23268 })
23269 }
23270
23271 pub fn hunks_for_ranges(
23272 &self,
23273 ranges: impl IntoIterator<Item = Range<Point>>,
23274 ) -> Vec<MultiBufferDiffHunk> {
23275 let mut hunks = Vec::new();
23276 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23277 HashMap::default();
23278 for query_range in ranges {
23279 let query_rows =
23280 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23281 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23282 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23283 ) {
23284 // Include deleted hunks that are adjacent to the query range, because
23285 // otherwise they would be missed.
23286 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23287 if hunk.status().is_deleted() {
23288 intersects_range |= hunk.row_range.start == query_rows.end;
23289 intersects_range |= hunk.row_range.end == query_rows.start;
23290 }
23291 if intersects_range {
23292 if !processed_buffer_rows
23293 .entry(hunk.buffer_id)
23294 .or_default()
23295 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23296 {
23297 continue;
23298 }
23299 hunks.push(hunk);
23300 }
23301 }
23302 }
23303
23304 hunks
23305 }
23306
23307 fn display_diff_hunks_for_rows<'a>(
23308 &'a self,
23309 display_rows: Range<DisplayRow>,
23310 folded_buffers: &'a HashSet<BufferId>,
23311 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23312 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23313 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23314
23315 self.buffer_snapshot()
23316 .diff_hunks_in_range(buffer_start..buffer_end)
23317 .filter_map(|hunk| {
23318 if folded_buffers.contains(&hunk.buffer_id) {
23319 return None;
23320 }
23321
23322 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23323 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23324
23325 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23326 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23327
23328 let display_hunk = if hunk_display_start.column() != 0 {
23329 DisplayDiffHunk::Folded {
23330 display_row: hunk_display_start.row(),
23331 }
23332 } else {
23333 let mut end_row = hunk_display_end.row();
23334 if hunk_display_end.column() > 0 {
23335 end_row.0 += 1;
23336 }
23337 let is_created_file = hunk.is_created_file();
23338 DisplayDiffHunk::Unfolded {
23339 status: hunk.status(),
23340 diff_base_byte_range: hunk.diff_base_byte_range,
23341 display_row_range: hunk_display_start.row()..end_row,
23342 multi_buffer_range: Anchor::range_in_buffer(
23343 hunk.excerpt_id,
23344 hunk.buffer_id,
23345 hunk.buffer_range,
23346 ),
23347 is_created_file,
23348 }
23349 };
23350
23351 Some(display_hunk)
23352 })
23353 }
23354
23355 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23356 self.display_snapshot
23357 .buffer_snapshot()
23358 .language_at(position)
23359 }
23360
23361 pub fn is_focused(&self) -> bool {
23362 self.is_focused
23363 }
23364
23365 pub fn placeholder_text(&self) -> Option<String> {
23366 self.placeholder_display_snapshot
23367 .as_ref()
23368 .map(|display_map| display_map.text())
23369 }
23370
23371 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23372 self.scroll_anchor.scroll_position(&self.display_snapshot)
23373 }
23374
23375 fn gutter_dimensions(
23376 &self,
23377 font_id: FontId,
23378 font_size: Pixels,
23379 max_line_number_width: Pixels,
23380 cx: &App,
23381 ) -> Option<GutterDimensions> {
23382 if !self.show_gutter {
23383 return None;
23384 }
23385
23386 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23387 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23388
23389 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23390 matches!(
23391 ProjectSettings::get_global(cx).git.git_gutter,
23392 GitGutterSetting::TrackedFiles
23393 )
23394 });
23395 let gutter_settings = EditorSettings::get_global(cx).gutter;
23396 let show_line_numbers = self
23397 .show_line_numbers
23398 .unwrap_or(gutter_settings.line_numbers);
23399 let line_gutter_width = if show_line_numbers {
23400 // Avoid flicker-like gutter resizes when the line number gains another digit by
23401 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23402 let min_width_for_number_on_gutter =
23403 ch_advance * gutter_settings.min_line_number_digits as f32;
23404 max_line_number_width.max(min_width_for_number_on_gutter)
23405 } else {
23406 0.0.into()
23407 };
23408
23409 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23410 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23411
23412 let git_blame_entries_width =
23413 self.git_blame_gutter_max_author_length
23414 .map(|max_author_length| {
23415 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23416 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23417
23418 /// The number of characters to dedicate to gaps and margins.
23419 const SPACING_WIDTH: usize = 4;
23420
23421 let max_char_count = max_author_length.min(renderer.max_author_length())
23422 + ::git::SHORT_SHA_LENGTH
23423 + MAX_RELATIVE_TIMESTAMP.len()
23424 + SPACING_WIDTH;
23425
23426 ch_advance * max_char_count
23427 });
23428
23429 let is_singleton = self.buffer_snapshot().is_singleton();
23430
23431 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23432 left_padding += if !is_singleton {
23433 ch_width * 4.0
23434 } else if show_runnables || show_breakpoints {
23435 ch_width * 3.0
23436 } else if show_git_gutter && show_line_numbers {
23437 ch_width * 2.0
23438 } else if show_git_gutter || show_line_numbers {
23439 ch_width
23440 } else {
23441 px(0.)
23442 };
23443
23444 let shows_folds = is_singleton && gutter_settings.folds;
23445
23446 let right_padding = if shows_folds && show_line_numbers {
23447 ch_width * 4.0
23448 } else if shows_folds || (!is_singleton && show_line_numbers) {
23449 ch_width * 3.0
23450 } else if show_line_numbers {
23451 ch_width
23452 } else {
23453 px(0.)
23454 };
23455
23456 Some(GutterDimensions {
23457 left_padding,
23458 right_padding,
23459 width: line_gutter_width + left_padding + right_padding,
23460 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23461 git_blame_entries_width,
23462 })
23463 }
23464
23465 pub fn render_crease_toggle(
23466 &self,
23467 buffer_row: MultiBufferRow,
23468 row_contains_cursor: bool,
23469 editor: Entity<Editor>,
23470 window: &mut Window,
23471 cx: &mut App,
23472 ) -> Option<AnyElement> {
23473 let folded = self.is_line_folded(buffer_row);
23474 let mut is_foldable = false;
23475
23476 if let Some(crease) = self
23477 .crease_snapshot
23478 .query_row(buffer_row, self.buffer_snapshot())
23479 {
23480 is_foldable = true;
23481 match crease {
23482 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23483 if let Some(render_toggle) = render_toggle {
23484 let toggle_callback =
23485 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23486 if folded {
23487 editor.update(cx, |editor, cx| {
23488 editor.fold_at(buffer_row, window, cx)
23489 });
23490 } else {
23491 editor.update(cx, |editor, cx| {
23492 editor.unfold_at(buffer_row, window, cx)
23493 });
23494 }
23495 });
23496 return Some((render_toggle)(
23497 buffer_row,
23498 folded,
23499 toggle_callback,
23500 window,
23501 cx,
23502 ));
23503 }
23504 }
23505 }
23506 }
23507
23508 is_foldable |= self.starts_indent(buffer_row);
23509
23510 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23511 Some(
23512 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23513 .toggle_state(folded)
23514 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23515 if folded {
23516 this.unfold_at(buffer_row, window, cx);
23517 } else {
23518 this.fold_at(buffer_row, window, cx);
23519 }
23520 }))
23521 .into_any_element(),
23522 )
23523 } else {
23524 None
23525 }
23526 }
23527
23528 pub fn render_crease_trailer(
23529 &self,
23530 buffer_row: MultiBufferRow,
23531 window: &mut Window,
23532 cx: &mut App,
23533 ) -> Option<AnyElement> {
23534 let folded = self.is_line_folded(buffer_row);
23535 if let Crease::Inline { render_trailer, .. } = self
23536 .crease_snapshot
23537 .query_row(buffer_row, self.buffer_snapshot())?
23538 {
23539 let render_trailer = render_trailer.as_ref()?;
23540 Some(render_trailer(buffer_row, folded, window, cx))
23541 } else {
23542 None
23543 }
23544 }
23545}
23546
23547impl Deref for EditorSnapshot {
23548 type Target = DisplaySnapshot;
23549
23550 fn deref(&self) -> &Self::Target {
23551 &self.display_snapshot
23552 }
23553}
23554
23555#[derive(Clone, Debug, PartialEq, Eq)]
23556pub enum EditorEvent {
23557 InputIgnored {
23558 text: Arc<str>,
23559 },
23560 InputHandled {
23561 utf16_range_to_replace: Option<Range<isize>>,
23562 text: Arc<str>,
23563 },
23564 ExcerptsAdded {
23565 buffer: Entity<Buffer>,
23566 predecessor: ExcerptId,
23567 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23568 },
23569 ExcerptsRemoved {
23570 ids: Vec<ExcerptId>,
23571 removed_buffer_ids: Vec<BufferId>,
23572 },
23573 BufferFoldToggled {
23574 ids: Vec<ExcerptId>,
23575 folded: bool,
23576 },
23577 ExcerptsEdited {
23578 ids: Vec<ExcerptId>,
23579 },
23580 ExcerptsExpanded {
23581 ids: Vec<ExcerptId>,
23582 },
23583 BufferEdited,
23584 Edited {
23585 transaction_id: clock::Lamport,
23586 },
23587 Reparsed(BufferId),
23588 Focused,
23589 FocusedIn,
23590 Blurred,
23591 DirtyChanged,
23592 Saved,
23593 TitleChanged,
23594 SelectionsChanged {
23595 local: bool,
23596 },
23597 ScrollPositionChanged {
23598 local: bool,
23599 autoscroll: bool,
23600 },
23601 TransactionUndone {
23602 transaction_id: clock::Lamport,
23603 },
23604 TransactionBegun {
23605 transaction_id: clock::Lamport,
23606 },
23607 CursorShapeChanged,
23608 BreadcrumbsChanged,
23609 PushedToNavHistory {
23610 anchor: Anchor,
23611 is_deactivate: bool,
23612 },
23613}
23614
23615impl EventEmitter<EditorEvent> for Editor {}
23616
23617impl Focusable for Editor {
23618 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23619 self.focus_handle.clone()
23620 }
23621}
23622
23623impl Render for Editor {
23624 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23625 let settings = ThemeSettings::get_global(cx);
23626
23627 let mut text_style = match self.mode {
23628 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23629 color: cx.theme().colors().editor_foreground,
23630 font_family: settings.ui_font.family.clone(),
23631 font_features: settings.ui_font.features.clone(),
23632 font_fallbacks: settings.ui_font.fallbacks.clone(),
23633 font_size: rems(0.875).into(),
23634 font_weight: settings.ui_font.weight,
23635 line_height: relative(settings.buffer_line_height.value()),
23636 ..Default::default()
23637 },
23638 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23639 color: cx.theme().colors().editor_foreground,
23640 font_family: settings.buffer_font.family.clone(),
23641 font_features: settings.buffer_font.features.clone(),
23642 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23643 font_size: settings.buffer_font_size(cx).into(),
23644 font_weight: settings.buffer_font.weight,
23645 line_height: relative(settings.buffer_line_height.value()),
23646 ..Default::default()
23647 },
23648 };
23649 if let Some(text_style_refinement) = &self.text_style_refinement {
23650 text_style.refine(text_style_refinement)
23651 }
23652
23653 let background = match self.mode {
23654 EditorMode::SingleLine => cx.theme().system().transparent,
23655 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23656 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23657 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23658 };
23659
23660 EditorElement::new(
23661 &cx.entity(),
23662 EditorStyle {
23663 background,
23664 border: cx.theme().colors().border,
23665 local_player: cx.theme().players().local(),
23666 text: text_style,
23667 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23668 syntax: cx.theme().syntax().clone(),
23669 status: cx.theme().status().clone(),
23670 inlay_hints_style: make_inlay_hints_style(cx),
23671 edit_prediction_styles: make_suggestion_styles(cx),
23672 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23673 show_underlines: self.diagnostics_enabled(),
23674 },
23675 )
23676 }
23677}
23678
23679impl EntityInputHandler for Editor {
23680 fn text_for_range(
23681 &mut self,
23682 range_utf16: Range<usize>,
23683 adjusted_range: &mut Option<Range<usize>>,
23684 _: &mut Window,
23685 cx: &mut Context<Self>,
23686 ) -> Option<String> {
23687 let snapshot = self.buffer.read(cx).read(cx);
23688 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23689 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23690 if (start.0..end.0) != range_utf16 {
23691 adjusted_range.replace(start.0..end.0);
23692 }
23693 Some(snapshot.text_for_range(start..end).collect())
23694 }
23695
23696 fn selected_text_range(
23697 &mut self,
23698 ignore_disabled_input: bool,
23699 _: &mut Window,
23700 cx: &mut Context<Self>,
23701 ) -> Option<UTF16Selection> {
23702 // Prevent the IME menu from appearing when holding down an alphabetic key
23703 // while input is disabled.
23704 if !ignore_disabled_input && !self.input_enabled {
23705 return None;
23706 }
23707
23708 let selection = self.selections.newest::<OffsetUtf16>(cx);
23709 let range = selection.range();
23710
23711 Some(UTF16Selection {
23712 range: range.start.0..range.end.0,
23713 reversed: selection.reversed,
23714 })
23715 }
23716
23717 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23718 let snapshot = self.buffer.read(cx).read(cx);
23719 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23720 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23721 }
23722
23723 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23724 self.clear_highlights::<InputComposition>(cx);
23725 self.ime_transaction.take();
23726 }
23727
23728 fn replace_text_in_range(
23729 &mut self,
23730 range_utf16: Option<Range<usize>>,
23731 text: &str,
23732 window: &mut Window,
23733 cx: &mut Context<Self>,
23734 ) {
23735 if !self.input_enabled {
23736 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23737 return;
23738 }
23739
23740 self.transact(window, cx, |this, window, cx| {
23741 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23742 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23743 Some(this.selection_replacement_ranges(range_utf16, cx))
23744 } else {
23745 this.marked_text_ranges(cx)
23746 };
23747
23748 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23749 let newest_selection_id = this.selections.newest_anchor().id;
23750 this.selections
23751 .all::<OffsetUtf16>(cx)
23752 .iter()
23753 .zip(ranges_to_replace.iter())
23754 .find_map(|(selection, range)| {
23755 if selection.id == newest_selection_id {
23756 Some(
23757 (range.start.0 as isize - selection.head().0 as isize)
23758 ..(range.end.0 as isize - selection.head().0 as isize),
23759 )
23760 } else {
23761 None
23762 }
23763 })
23764 });
23765
23766 cx.emit(EditorEvent::InputHandled {
23767 utf16_range_to_replace: range_to_replace,
23768 text: text.into(),
23769 });
23770
23771 if let Some(new_selected_ranges) = new_selected_ranges {
23772 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23773 selections.select_ranges(new_selected_ranges)
23774 });
23775 this.backspace(&Default::default(), window, cx);
23776 }
23777
23778 this.handle_input(text, window, cx);
23779 });
23780
23781 if let Some(transaction) = self.ime_transaction {
23782 self.buffer.update(cx, |buffer, cx| {
23783 buffer.group_until_transaction(transaction, cx);
23784 });
23785 }
23786
23787 self.unmark_text(window, cx);
23788 }
23789
23790 fn replace_and_mark_text_in_range(
23791 &mut self,
23792 range_utf16: Option<Range<usize>>,
23793 text: &str,
23794 new_selected_range_utf16: Option<Range<usize>>,
23795 window: &mut Window,
23796 cx: &mut Context<Self>,
23797 ) {
23798 if !self.input_enabled {
23799 return;
23800 }
23801
23802 let transaction = self.transact(window, cx, |this, window, cx| {
23803 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23804 let snapshot = this.buffer.read(cx).read(cx);
23805 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23806 for marked_range in &mut marked_ranges {
23807 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23808 marked_range.start.0 += relative_range_utf16.start;
23809 marked_range.start =
23810 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23811 marked_range.end =
23812 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23813 }
23814 }
23815 Some(marked_ranges)
23816 } else if let Some(range_utf16) = range_utf16 {
23817 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23818 Some(this.selection_replacement_ranges(range_utf16, cx))
23819 } else {
23820 None
23821 };
23822
23823 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23824 let newest_selection_id = this.selections.newest_anchor().id;
23825 this.selections
23826 .all::<OffsetUtf16>(cx)
23827 .iter()
23828 .zip(ranges_to_replace.iter())
23829 .find_map(|(selection, range)| {
23830 if selection.id == newest_selection_id {
23831 Some(
23832 (range.start.0 as isize - selection.head().0 as isize)
23833 ..(range.end.0 as isize - selection.head().0 as isize),
23834 )
23835 } else {
23836 None
23837 }
23838 })
23839 });
23840
23841 cx.emit(EditorEvent::InputHandled {
23842 utf16_range_to_replace: range_to_replace,
23843 text: text.into(),
23844 });
23845
23846 if let Some(ranges) = ranges_to_replace {
23847 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23848 s.select_ranges(ranges)
23849 });
23850 }
23851
23852 let marked_ranges = {
23853 let snapshot = this.buffer.read(cx).read(cx);
23854 this.selections
23855 .disjoint_anchors_arc()
23856 .iter()
23857 .map(|selection| {
23858 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23859 })
23860 .collect::<Vec<_>>()
23861 };
23862
23863 if text.is_empty() {
23864 this.unmark_text(window, cx);
23865 } else {
23866 this.highlight_text::<InputComposition>(
23867 marked_ranges.clone(),
23868 HighlightStyle {
23869 underline: Some(UnderlineStyle {
23870 thickness: px(1.),
23871 color: None,
23872 wavy: false,
23873 }),
23874 ..Default::default()
23875 },
23876 cx,
23877 );
23878 }
23879
23880 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23881 let use_autoclose = this.use_autoclose;
23882 let use_auto_surround = this.use_auto_surround;
23883 this.set_use_autoclose(false);
23884 this.set_use_auto_surround(false);
23885 this.handle_input(text, window, cx);
23886 this.set_use_autoclose(use_autoclose);
23887 this.set_use_auto_surround(use_auto_surround);
23888
23889 if let Some(new_selected_range) = new_selected_range_utf16 {
23890 let snapshot = this.buffer.read(cx).read(cx);
23891 let new_selected_ranges = marked_ranges
23892 .into_iter()
23893 .map(|marked_range| {
23894 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23895 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23896 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23897 snapshot.clip_offset_utf16(new_start, Bias::Left)
23898 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23899 })
23900 .collect::<Vec<_>>();
23901
23902 drop(snapshot);
23903 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23904 selections.select_ranges(new_selected_ranges)
23905 });
23906 }
23907 });
23908
23909 self.ime_transaction = self.ime_transaction.or(transaction);
23910 if let Some(transaction) = self.ime_transaction {
23911 self.buffer.update(cx, |buffer, cx| {
23912 buffer.group_until_transaction(transaction, cx);
23913 });
23914 }
23915
23916 if self.text_highlights::<InputComposition>(cx).is_none() {
23917 self.ime_transaction.take();
23918 }
23919 }
23920
23921 fn bounds_for_range(
23922 &mut self,
23923 range_utf16: Range<usize>,
23924 element_bounds: gpui::Bounds<Pixels>,
23925 window: &mut Window,
23926 cx: &mut Context<Self>,
23927 ) -> Option<gpui::Bounds<Pixels>> {
23928 let text_layout_details = self.text_layout_details(window);
23929 let CharacterDimensions {
23930 em_width,
23931 em_advance,
23932 line_height,
23933 } = self.character_dimensions(window);
23934
23935 let snapshot = self.snapshot(window, cx);
23936 let scroll_position = snapshot.scroll_position();
23937 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23938
23939 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23940 let x = Pixels::from(
23941 ScrollOffset::from(
23942 snapshot.x_for_display_point(start, &text_layout_details)
23943 + self.gutter_dimensions.full_width(),
23944 ) - scroll_left,
23945 );
23946 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23947
23948 Some(Bounds {
23949 origin: element_bounds.origin + point(x, y),
23950 size: size(em_width, line_height),
23951 })
23952 }
23953
23954 fn character_index_for_point(
23955 &mut self,
23956 point: gpui::Point<Pixels>,
23957 _window: &mut Window,
23958 _cx: &mut Context<Self>,
23959 ) -> Option<usize> {
23960 let position_map = self.last_position_map.as_ref()?;
23961 if !position_map.text_hitbox.contains(&point) {
23962 return None;
23963 }
23964 let display_point = position_map.point_for_position(point).previous_valid;
23965 let anchor = position_map
23966 .snapshot
23967 .display_point_to_anchor(display_point, Bias::Left);
23968 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
23969 Some(utf16_offset.0)
23970 }
23971}
23972
23973trait SelectionExt {
23974 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23975 fn spanned_rows(
23976 &self,
23977 include_end_if_at_line_start: bool,
23978 map: &DisplaySnapshot,
23979 ) -> Range<MultiBufferRow>;
23980}
23981
23982impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23983 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23984 let start = self
23985 .start
23986 .to_point(map.buffer_snapshot())
23987 .to_display_point(map);
23988 let end = self
23989 .end
23990 .to_point(map.buffer_snapshot())
23991 .to_display_point(map);
23992 if self.reversed {
23993 end..start
23994 } else {
23995 start..end
23996 }
23997 }
23998
23999 fn spanned_rows(
24000 &self,
24001 include_end_if_at_line_start: bool,
24002 map: &DisplaySnapshot,
24003 ) -> Range<MultiBufferRow> {
24004 let start = self.start.to_point(map.buffer_snapshot());
24005 let mut end = self.end.to_point(map.buffer_snapshot());
24006 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24007 end.row -= 1;
24008 }
24009
24010 let buffer_start = map.prev_line_boundary(start).0;
24011 let buffer_end = map.next_line_boundary(end).0;
24012 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24013 }
24014}
24015
24016impl<T: InvalidationRegion> InvalidationStack<T> {
24017 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24018 where
24019 S: Clone + ToOffset,
24020 {
24021 while let Some(region) = self.last() {
24022 let all_selections_inside_invalidation_ranges =
24023 if selections.len() == region.ranges().len() {
24024 selections
24025 .iter()
24026 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24027 .all(|(selection, invalidation_range)| {
24028 let head = selection.head().to_offset(buffer);
24029 invalidation_range.start <= head && invalidation_range.end >= head
24030 })
24031 } else {
24032 false
24033 };
24034
24035 if all_selections_inside_invalidation_ranges {
24036 break;
24037 } else {
24038 self.pop();
24039 }
24040 }
24041 }
24042}
24043
24044impl<T> Default for InvalidationStack<T> {
24045 fn default() -> Self {
24046 Self(Default::default())
24047 }
24048}
24049
24050impl<T> Deref for InvalidationStack<T> {
24051 type Target = Vec<T>;
24052
24053 fn deref(&self) -> &Self::Target {
24054 &self.0
24055 }
24056}
24057
24058impl<T> DerefMut for InvalidationStack<T> {
24059 fn deref_mut(&mut self) -> &mut Self::Target {
24060 &mut self.0
24061 }
24062}
24063
24064impl InvalidationRegion for SnippetState {
24065 fn ranges(&self) -> &[Range<Anchor>] {
24066 &self.ranges[self.active_index]
24067 }
24068}
24069
24070fn edit_prediction_edit_text(
24071 current_snapshot: &BufferSnapshot,
24072 edits: &[(Range<Anchor>, String)],
24073 edit_preview: &EditPreview,
24074 include_deletions: bool,
24075 cx: &App,
24076) -> HighlightedText {
24077 let edits = edits
24078 .iter()
24079 .map(|(anchor, text)| {
24080 (
24081 anchor.start.text_anchor..anchor.end.text_anchor,
24082 text.clone(),
24083 )
24084 })
24085 .collect::<Vec<_>>();
24086
24087 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24088}
24089
24090fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24091 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24092 // Just show the raw edit text with basic styling
24093 let mut text = String::new();
24094 let mut highlights = Vec::new();
24095
24096 let insertion_highlight_style = HighlightStyle {
24097 color: Some(cx.theme().colors().text),
24098 ..Default::default()
24099 };
24100
24101 for (_, edit_text) in edits {
24102 let start_offset = text.len();
24103 text.push_str(edit_text);
24104 let end_offset = text.len();
24105
24106 if start_offset < end_offset {
24107 highlights.push((start_offset..end_offset, insertion_highlight_style));
24108 }
24109 }
24110
24111 HighlightedText {
24112 text: text.into(),
24113 highlights,
24114 }
24115}
24116
24117pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24118 match severity {
24119 lsp::DiagnosticSeverity::ERROR => colors.error,
24120 lsp::DiagnosticSeverity::WARNING => colors.warning,
24121 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24122 lsp::DiagnosticSeverity::HINT => colors.info,
24123 _ => colors.ignored,
24124 }
24125}
24126
24127pub fn styled_runs_for_code_label<'a>(
24128 label: &'a CodeLabel,
24129 syntax_theme: &'a theme::SyntaxTheme,
24130) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24131 let fade_out = HighlightStyle {
24132 fade_out: Some(0.35),
24133 ..Default::default()
24134 };
24135
24136 let mut prev_end = label.filter_range.end;
24137 label
24138 .runs
24139 .iter()
24140 .enumerate()
24141 .flat_map(move |(ix, (range, highlight_id))| {
24142 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24143 style
24144 } else {
24145 return Default::default();
24146 };
24147 let muted_style = style.highlight(fade_out);
24148
24149 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24150 if range.start >= label.filter_range.end {
24151 if range.start > prev_end {
24152 runs.push((prev_end..range.start, fade_out));
24153 }
24154 runs.push((range.clone(), muted_style));
24155 } else if range.end <= label.filter_range.end {
24156 runs.push((range.clone(), style));
24157 } else {
24158 runs.push((range.start..label.filter_range.end, style));
24159 runs.push((label.filter_range.end..range.end, muted_style));
24160 }
24161 prev_end = cmp::max(prev_end, range.end);
24162
24163 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24164 runs.push((prev_end..label.text.len(), fade_out));
24165 }
24166
24167 runs
24168 })
24169}
24170
24171pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24172 let mut prev_index = 0;
24173 let mut prev_codepoint: Option<char> = None;
24174 text.char_indices()
24175 .chain([(text.len(), '\0')])
24176 .filter_map(move |(index, codepoint)| {
24177 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24178 let is_boundary = index == text.len()
24179 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24180 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24181 if is_boundary {
24182 let chunk = &text[prev_index..index];
24183 prev_index = index;
24184 Some(chunk)
24185 } else {
24186 None
24187 }
24188 })
24189}
24190
24191pub trait RangeToAnchorExt: Sized {
24192 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24193
24194 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24195 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24196 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24197 }
24198}
24199
24200impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24201 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24202 let start_offset = self.start.to_offset(snapshot);
24203 let end_offset = self.end.to_offset(snapshot);
24204 if start_offset == end_offset {
24205 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24206 } else {
24207 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24208 }
24209 }
24210}
24211
24212pub trait RowExt {
24213 fn as_f64(&self) -> f64;
24214
24215 fn next_row(&self) -> Self;
24216
24217 fn previous_row(&self) -> Self;
24218
24219 fn minus(&self, other: Self) -> u32;
24220}
24221
24222impl RowExt for DisplayRow {
24223 fn as_f64(&self) -> f64 {
24224 self.0 as _
24225 }
24226
24227 fn next_row(&self) -> Self {
24228 Self(self.0 + 1)
24229 }
24230
24231 fn previous_row(&self) -> Self {
24232 Self(self.0.saturating_sub(1))
24233 }
24234
24235 fn minus(&self, other: Self) -> u32 {
24236 self.0 - other.0
24237 }
24238}
24239
24240impl RowExt for MultiBufferRow {
24241 fn as_f64(&self) -> f64 {
24242 self.0 as _
24243 }
24244
24245 fn next_row(&self) -> Self {
24246 Self(self.0 + 1)
24247 }
24248
24249 fn previous_row(&self) -> Self {
24250 Self(self.0.saturating_sub(1))
24251 }
24252
24253 fn minus(&self, other: Self) -> u32 {
24254 self.0 - other.0
24255 }
24256}
24257
24258trait RowRangeExt {
24259 type Row;
24260
24261 fn len(&self) -> usize;
24262
24263 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24264}
24265
24266impl RowRangeExt for Range<MultiBufferRow> {
24267 type Row = MultiBufferRow;
24268
24269 fn len(&self) -> usize {
24270 (self.end.0 - self.start.0) as usize
24271 }
24272
24273 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24274 (self.start.0..self.end.0).map(MultiBufferRow)
24275 }
24276}
24277
24278impl RowRangeExt for Range<DisplayRow> {
24279 type Row = DisplayRow;
24280
24281 fn len(&self) -> usize {
24282 (self.end.0 - self.start.0) as usize
24283 }
24284
24285 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24286 (self.start.0..self.end.0).map(DisplayRow)
24287 }
24288}
24289
24290/// If select range has more than one line, we
24291/// just point the cursor to range.start.
24292fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24293 if range.start.row == range.end.row {
24294 range
24295 } else {
24296 range.start..range.start
24297 }
24298}
24299pub struct KillRing(ClipboardItem);
24300impl Global for KillRing {}
24301
24302const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24303
24304enum BreakpointPromptEditAction {
24305 Log,
24306 Condition,
24307 HitCondition,
24308}
24309
24310struct BreakpointPromptEditor {
24311 pub(crate) prompt: Entity<Editor>,
24312 editor: WeakEntity<Editor>,
24313 breakpoint_anchor: Anchor,
24314 breakpoint: Breakpoint,
24315 edit_action: BreakpointPromptEditAction,
24316 block_ids: HashSet<CustomBlockId>,
24317 editor_margins: Arc<Mutex<EditorMargins>>,
24318 _subscriptions: Vec<Subscription>,
24319}
24320
24321impl BreakpointPromptEditor {
24322 const MAX_LINES: u8 = 4;
24323
24324 fn new(
24325 editor: WeakEntity<Editor>,
24326 breakpoint_anchor: Anchor,
24327 breakpoint: Breakpoint,
24328 edit_action: BreakpointPromptEditAction,
24329 window: &mut Window,
24330 cx: &mut Context<Self>,
24331 ) -> Self {
24332 let base_text = match edit_action {
24333 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24334 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24335 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24336 }
24337 .map(|msg| msg.to_string())
24338 .unwrap_or_default();
24339
24340 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24341 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24342
24343 let prompt = cx.new(|cx| {
24344 let mut prompt = Editor::new(
24345 EditorMode::AutoHeight {
24346 min_lines: 1,
24347 max_lines: Some(Self::MAX_LINES as usize),
24348 },
24349 buffer,
24350 None,
24351 window,
24352 cx,
24353 );
24354 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24355 prompt.set_show_cursor_when_unfocused(false, cx);
24356 prompt.set_placeholder_text(
24357 match edit_action {
24358 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24359 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24360 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24361 },
24362 window,
24363 cx,
24364 );
24365
24366 prompt
24367 });
24368
24369 Self {
24370 prompt,
24371 editor,
24372 breakpoint_anchor,
24373 breakpoint,
24374 edit_action,
24375 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24376 block_ids: Default::default(),
24377 _subscriptions: vec![],
24378 }
24379 }
24380
24381 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24382 self.block_ids.extend(block_ids)
24383 }
24384
24385 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24386 if let Some(editor) = self.editor.upgrade() {
24387 let message = self
24388 .prompt
24389 .read(cx)
24390 .buffer
24391 .read(cx)
24392 .as_singleton()
24393 .expect("A multi buffer in breakpoint prompt isn't possible")
24394 .read(cx)
24395 .as_rope()
24396 .to_string();
24397
24398 editor.update(cx, |editor, cx| {
24399 editor.edit_breakpoint_at_anchor(
24400 self.breakpoint_anchor,
24401 self.breakpoint.clone(),
24402 match self.edit_action {
24403 BreakpointPromptEditAction::Log => {
24404 BreakpointEditAction::EditLogMessage(message.into())
24405 }
24406 BreakpointPromptEditAction::Condition => {
24407 BreakpointEditAction::EditCondition(message.into())
24408 }
24409 BreakpointPromptEditAction::HitCondition => {
24410 BreakpointEditAction::EditHitCondition(message.into())
24411 }
24412 },
24413 cx,
24414 );
24415
24416 editor.remove_blocks(self.block_ids.clone(), None, cx);
24417 cx.focus_self(window);
24418 });
24419 }
24420 }
24421
24422 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24423 self.editor
24424 .update(cx, |editor, cx| {
24425 editor.remove_blocks(self.block_ids.clone(), None, cx);
24426 window.focus(&editor.focus_handle);
24427 })
24428 .log_err();
24429 }
24430
24431 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24432 let settings = ThemeSettings::get_global(cx);
24433 let text_style = TextStyle {
24434 color: if self.prompt.read(cx).read_only(cx) {
24435 cx.theme().colors().text_disabled
24436 } else {
24437 cx.theme().colors().text
24438 },
24439 font_family: settings.buffer_font.family.clone(),
24440 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24441 font_size: settings.buffer_font_size(cx).into(),
24442 font_weight: settings.buffer_font.weight,
24443 line_height: relative(settings.buffer_line_height.value()),
24444 ..Default::default()
24445 };
24446 EditorElement::new(
24447 &self.prompt,
24448 EditorStyle {
24449 background: cx.theme().colors().editor_background,
24450 local_player: cx.theme().players().local(),
24451 text: text_style,
24452 ..Default::default()
24453 },
24454 )
24455 }
24456}
24457
24458impl Render for BreakpointPromptEditor {
24459 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24460 let editor_margins = *self.editor_margins.lock();
24461 let gutter_dimensions = editor_margins.gutter;
24462 h_flex()
24463 .key_context("Editor")
24464 .bg(cx.theme().colors().editor_background)
24465 .border_y_1()
24466 .border_color(cx.theme().status().info_border)
24467 .size_full()
24468 .py(window.line_height() / 2.5)
24469 .on_action(cx.listener(Self::confirm))
24470 .on_action(cx.listener(Self::cancel))
24471 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24472 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24473 }
24474}
24475
24476impl Focusable for BreakpointPromptEditor {
24477 fn focus_handle(&self, cx: &App) -> FocusHandle {
24478 self.prompt.focus_handle(cx)
24479 }
24480}
24481
24482fn all_edits_insertions_or_deletions(
24483 edits: &Vec<(Range<Anchor>, String)>,
24484 snapshot: &MultiBufferSnapshot,
24485) -> bool {
24486 let mut all_insertions = true;
24487 let mut all_deletions = true;
24488
24489 for (range, new_text) in edits.iter() {
24490 let range_is_empty = range.to_offset(snapshot).is_empty();
24491 let text_is_empty = new_text.is_empty();
24492
24493 if range_is_empty != text_is_empty {
24494 if range_is_empty {
24495 all_deletions = false;
24496 } else {
24497 all_insertions = false;
24498 }
24499 } else {
24500 return false;
24501 }
24502
24503 if !all_insertions && !all_deletions {
24504 return false;
24505 }
24506 }
24507 all_insertions || all_deletions
24508}
24509
24510struct MissingEditPredictionKeybindingTooltip;
24511
24512impl Render for MissingEditPredictionKeybindingTooltip {
24513 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24514 ui::tooltip_container(cx, |container, cx| {
24515 container
24516 .flex_shrink_0()
24517 .max_w_80()
24518 .min_h(rems_from_px(124.))
24519 .justify_between()
24520 .child(
24521 v_flex()
24522 .flex_1()
24523 .text_ui_sm(cx)
24524 .child(Label::new("Conflict with Accept Keybinding"))
24525 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24526 )
24527 .child(
24528 h_flex()
24529 .pb_1()
24530 .gap_1()
24531 .items_end()
24532 .w_full()
24533 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24534 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24535 }))
24536 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24537 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24538 })),
24539 )
24540 })
24541 }
24542}
24543
24544#[derive(Debug, Clone, Copy, PartialEq)]
24545pub struct LineHighlight {
24546 pub background: Background,
24547 pub border: Option<gpui::Hsla>,
24548 pub include_gutter: bool,
24549 pub type_id: Option<TypeId>,
24550}
24551
24552struct LineManipulationResult {
24553 pub new_text: String,
24554 pub line_count_before: usize,
24555 pub line_count_after: usize,
24556}
24557
24558fn render_diff_hunk_controls(
24559 row: u32,
24560 status: &DiffHunkStatus,
24561 hunk_range: Range<Anchor>,
24562 is_created_file: bool,
24563 line_height: Pixels,
24564 editor: &Entity<Editor>,
24565 _window: &mut Window,
24566 cx: &mut App,
24567) -> AnyElement {
24568 h_flex()
24569 .h(line_height)
24570 .mr_1()
24571 .gap_1()
24572 .px_0p5()
24573 .pb_1()
24574 .border_x_1()
24575 .border_b_1()
24576 .border_color(cx.theme().colors().border_variant)
24577 .rounded_b_lg()
24578 .bg(cx.theme().colors().editor_background)
24579 .gap_1()
24580 .block_mouse_except_scroll()
24581 .shadow_md()
24582 .child(if status.has_secondary_hunk() {
24583 Button::new(("stage", row as u64), "Stage")
24584 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24585 .tooltip({
24586 let focus_handle = editor.focus_handle(cx);
24587 move |window, cx| {
24588 Tooltip::for_action_in(
24589 "Stage Hunk",
24590 &::git::ToggleStaged,
24591 &focus_handle,
24592 window,
24593 cx,
24594 )
24595 }
24596 })
24597 .on_click({
24598 let editor = editor.clone();
24599 move |_event, _window, cx| {
24600 editor.update(cx, |editor, cx| {
24601 editor.stage_or_unstage_diff_hunks(
24602 true,
24603 vec![hunk_range.start..hunk_range.start],
24604 cx,
24605 );
24606 });
24607 }
24608 })
24609 } else {
24610 Button::new(("unstage", row as u64), "Unstage")
24611 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24612 .tooltip({
24613 let focus_handle = editor.focus_handle(cx);
24614 move |window, cx| {
24615 Tooltip::for_action_in(
24616 "Unstage Hunk",
24617 &::git::ToggleStaged,
24618 &focus_handle,
24619 window,
24620 cx,
24621 )
24622 }
24623 })
24624 .on_click({
24625 let editor = editor.clone();
24626 move |_event, _window, cx| {
24627 editor.update(cx, |editor, cx| {
24628 editor.stage_or_unstage_diff_hunks(
24629 false,
24630 vec![hunk_range.start..hunk_range.start],
24631 cx,
24632 );
24633 });
24634 }
24635 })
24636 })
24637 .child(
24638 Button::new(("restore", row as u64), "Restore")
24639 .tooltip({
24640 let focus_handle = editor.focus_handle(cx);
24641 move |window, cx| {
24642 Tooltip::for_action_in(
24643 "Restore Hunk",
24644 &::git::Restore,
24645 &focus_handle,
24646 window,
24647 cx,
24648 )
24649 }
24650 })
24651 .on_click({
24652 let editor = editor.clone();
24653 move |_event, window, cx| {
24654 editor.update(cx, |editor, cx| {
24655 let snapshot = editor.snapshot(window, cx);
24656 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24657 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24658 });
24659 }
24660 })
24661 .disabled(is_created_file),
24662 )
24663 .when(
24664 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24665 |el| {
24666 el.child(
24667 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24668 .shape(IconButtonShape::Square)
24669 .icon_size(IconSize::Small)
24670 // .disabled(!has_multiple_hunks)
24671 .tooltip({
24672 let focus_handle = editor.focus_handle(cx);
24673 move |window, cx| {
24674 Tooltip::for_action_in(
24675 "Next Hunk",
24676 &GoToHunk,
24677 &focus_handle,
24678 window,
24679 cx,
24680 )
24681 }
24682 })
24683 .on_click({
24684 let editor = editor.clone();
24685 move |_event, window, cx| {
24686 editor.update(cx, |editor, cx| {
24687 let snapshot = editor.snapshot(window, cx);
24688 let position =
24689 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24690 editor.go_to_hunk_before_or_after_position(
24691 &snapshot,
24692 position,
24693 Direction::Next,
24694 window,
24695 cx,
24696 );
24697 editor.expand_selected_diff_hunks(cx);
24698 });
24699 }
24700 }),
24701 )
24702 .child(
24703 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24704 .shape(IconButtonShape::Square)
24705 .icon_size(IconSize::Small)
24706 // .disabled(!has_multiple_hunks)
24707 .tooltip({
24708 let focus_handle = editor.focus_handle(cx);
24709 move |window, cx| {
24710 Tooltip::for_action_in(
24711 "Previous Hunk",
24712 &GoToPreviousHunk,
24713 &focus_handle,
24714 window,
24715 cx,
24716 )
24717 }
24718 })
24719 .on_click({
24720 let editor = editor.clone();
24721 move |_event, window, cx| {
24722 editor.update(cx, |editor, cx| {
24723 let snapshot = editor.snapshot(window, cx);
24724 let point =
24725 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24726 editor.go_to_hunk_before_or_after_position(
24727 &snapshot,
24728 point,
24729 Direction::Prev,
24730 window,
24731 cx,
24732 );
24733 editor.expand_selected_diff_hunks(cx);
24734 });
24735 }
24736 }),
24737 )
24738 },
24739 )
24740 .into_any_element()
24741}
24742
24743pub fn multibuffer_context_lines(cx: &App) -> u32 {
24744 EditorSettings::try_get(cx)
24745 .map(|settings| settings.excerpt_context_lines)
24746 .unwrap_or(2)
24747 .min(32)
24748}