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.splice_inlays(
5221 &self
5222 .visible_inlay_hints(cx)
5223 .iter()
5224 .map(|inlay| inlay.id)
5225 .collect::<Vec<InlayId>>(),
5226 Vec::new(),
5227 cx,
5228 );
5229 return;
5230 }
5231 }
5232 None => return,
5233 }
5234 }
5235 InlayHintRefreshReason::Toggle(enabled) => {
5236 if self.inlay_hint_cache.toggle(enabled) {
5237 if enabled {
5238 (InvalidationStrategy::RefreshRequested, None)
5239 } else {
5240 self.splice_inlays(
5241 &self
5242 .visible_inlay_hints(cx)
5243 .iter()
5244 .map(|inlay| inlay.id)
5245 .collect::<Vec<InlayId>>(),
5246 Vec::new(),
5247 cx,
5248 );
5249 return;
5250 }
5251 } else {
5252 return;
5253 }
5254 }
5255 InlayHintRefreshReason::SettingsChange(new_settings) => {
5256 match self.inlay_hint_cache.update_settings(
5257 &self.buffer,
5258 new_settings,
5259 self.visible_inlay_hints(cx),
5260 cx,
5261 ) {
5262 ControlFlow::Break(Some(InlaySplice {
5263 to_remove,
5264 to_insert,
5265 })) => {
5266 self.splice_inlays(&to_remove, to_insert, cx);
5267 return;
5268 }
5269 ControlFlow::Break(None) => return,
5270 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5271 }
5272 }
5273 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5274 if let Some(InlaySplice {
5275 to_remove,
5276 to_insert,
5277 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5278 {
5279 self.splice_inlays(&to_remove, to_insert, cx);
5280 }
5281 self.display_map.update(cx, |display_map, _| {
5282 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5283 });
5284 return;
5285 }
5286 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5287 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5288 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5289 }
5290 InlayHintRefreshReason::RefreshRequested => {
5291 (InvalidationStrategy::RefreshRequested, None)
5292 }
5293 };
5294
5295 if let Some(InlaySplice {
5296 to_remove,
5297 to_insert,
5298 }) = self.inlay_hint_cache.spawn_hint_refresh(
5299 reason_description,
5300 self.visible_excerpts(required_languages.as_ref(), cx),
5301 invalidate_cache,
5302 ignore_debounce,
5303 cx,
5304 ) {
5305 self.splice_inlays(&to_remove, to_insert, cx);
5306 }
5307 }
5308
5309 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5310 self.display_map
5311 .read(cx)
5312 .current_inlays()
5313 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5314 .cloned()
5315 .collect()
5316 }
5317
5318 pub fn visible_excerpts(
5319 &self,
5320 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5321 cx: &mut Context<Editor>,
5322 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5323 let Some(project) = self.project() else {
5324 return HashMap::default();
5325 };
5326 let project = project.read(cx);
5327 let multi_buffer = self.buffer().read(cx);
5328 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5329 let multi_buffer_visible_start = self
5330 .scroll_manager
5331 .anchor()
5332 .anchor
5333 .to_point(&multi_buffer_snapshot);
5334 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5335 multi_buffer_visible_start
5336 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5337 Bias::Left,
5338 );
5339 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5340 multi_buffer_snapshot
5341 .range_to_buffer_ranges(multi_buffer_visible_range)
5342 .into_iter()
5343 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5344 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5345 let buffer_file = project::File::from_dyn(buffer.file())?;
5346 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5347 let worktree_entry = buffer_worktree
5348 .read(cx)
5349 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5350 if worktree_entry.is_ignored {
5351 return None;
5352 }
5353
5354 let language = buffer.language()?;
5355 if let Some(restrict_to_languages) = restrict_to_languages
5356 && !restrict_to_languages.contains(language)
5357 {
5358 return None;
5359 }
5360 Some((
5361 excerpt_id,
5362 (
5363 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5364 buffer.version().clone(),
5365 excerpt_visible_range,
5366 ),
5367 ))
5368 })
5369 .collect()
5370 }
5371
5372 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5373 TextLayoutDetails {
5374 text_system: window.text_system().clone(),
5375 editor_style: self.style.clone().unwrap(),
5376 rem_size: window.rem_size(),
5377 scroll_anchor: self.scroll_manager.anchor(),
5378 visible_rows: self.visible_line_count(),
5379 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5380 }
5381 }
5382
5383 pub fn splice_inlays(
5384 &self,
5385 to_remove: &[InlayId],
5386 to_insert: Vec<Inlay>,
5387 cx: &mut Context<Self>,
5388 ) {
5389 self.display_map.update(cx, |display_map, cx| {
5390 display_map.splice_inlays(to_remove, to_insert, cx)
5391 });
5392 cx.notify();
5393 }
5394
5395 fn trigger_on_type_formatting(
5396 &self,
5397 input: String,
5398 window: &mut Window,
5399 cx: &mut Context<Self>,
5400 ) -> Option<Task<Result<()>>> {
5401 if input.len() != 1 {
5402 return None;
5403 }
5404
5405 let project = self.project()?;
5406 let position = self.selections.newest_anchor().head();
5407 let (buffer, buffer_position) = self
5408 .buffer
5409 .read(cx)
5410 .text_anchor_for_position(position, cx)?;
5411
5412 let settings = language_settings::language_settings(
5413 buffer
5414 .read(cx)
5415 .language_at(buffer_position)
5416 .map(|l| l.name()),
5417 buffer.read(cx).file(),
5418 cx,
5419 );
5420 if !settings.use_on_type_format {
5421 return None;
5422 }
5423
5424 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5425 // hence we do LSP request & edit on host side only — add formats to host's history.
5426 let push_to_lsp_host_history = true;
5427 // If this is not the host, append its history with new edits.
5428 let push_to_client_history = project.read(cx).is_via_collab();
5429
5430 let on_type_formatting = project.update(cx, |project, cx| {
5431 project.on_type_format(
5432 buffer.clone(),
5433 buffer_position,
5434 input,
5435 push_to_lsp_host_history,
5436 cx,
5437 )
5438 });
5439 Some(cx.spawn_in(window, async move |editor, cx| {
5440 if let Some(transaction) = on_type_formatting.await? {
5441 if push_to_client_history {
5442 buffer
5443 .update(cx, |buffer, _| {
5444 buffer.push_transaction(transaction, Instant::now());
5445 buffer.finalize_last_transaction();
5446 })
5447 .ok();
5448 }
5449 editor.update(cx, |editor, cx| {
5450 editor.refresh_document_highlights(cx);
5451 })?;
5452 }
5453 Ok(())
5454 }))
5455 }
5456
5457 pub fn show_word_completions(
5458 &mut self,
5459 _: &ShowWordCompletions,
5460 window: &mut Window,
5461 cx: &mut Context<Self>,
5462 ) {
5463 self.open_or_update_completions_menu(
5464 Some(CompletionsMenuSource::Words {
5465 ignore_threshold: true,
5466 }),
5467 None,
5468 window,
5469 cx,
5470 );
5471 }
5472
5473 pub fn show_completions(
5474 &mut self,
5475 options: &ShowCompletions,
5476 window: &mut Window,
5477 cx: &mut Context<Self>,
5478 ) {
5479 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5480 }
5481
5482 fn open_or_update_completions_menu(
5483 &mut self,
5484 requested_source: Option<CompletionsMenuSource>,
5485 trigger: Option<&str>,
5486 window: &mut Window,
5487 cx: &mut Context<Self>,
5488 ) {
5489 if self.pending_rename.is_some() {
5490 return;
5491 }
5492
5493 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5494
5495 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5496 // inserted and selected. To handle that case, the start of the selection is used so that
5497 // the menu starts with all choices.
5498 let position = self
5499 .selections
5500 .newest_anchor()
5501 .start
5502 .bias_right(&multibuffer_snapshot);
5503 if position.diff_base_anchor.is_some() {
5504 return;
5505 }
5506 let buffer_position = multibuffer_snapshot.anchor_before(position);
5507 let Some(buffer) = buffer_position
5508 .buffer_id
5509 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5510 else {
5511 return;
5512 };
5513 let buffer_snapshot = buffer.read(cx).snapshot();
5514
5515 let query: Option<Arc<String>> =
5516 Self::completion_query(&multibuffer_snapshot, buffer_position)
5517 .map(|query| query.into());
5518
5519 drop(multibuffer_snapshot);
5520
5521 // Hide the current completions menu when query is empty. Without this, cached
5522 // completions from before the trigger char may be reused (#32774).
5523 if query.is_none() {
5524 let menu_is_open = matches!(
5525 self.context_menu.borrow().as_ref(),
5526 Some(CodeContextMenu::Completions(_))
5527 );
5528 if menu_is_open {
5529 self.hide_context_menu(window, cx);
5530 }
5531 }
5532
5533 let mut ignore_word_threshold = false;
5534 let provider = match requested_source {
5535 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5536 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5537 ignore_word_threshold = ignore_threshold;
5538 None
5539 }
5540 Some(CompletionsMenuSource::SnippetChoices) => {
5541 log::error!("bug: SnippetChoices requested_source is not handled");
5542 None
5543 }
5544 };
5545
5546 let sort_completions = provider
5547 .as_ref()
5548 .is_some_and(|provider| provider.sort_completions());
5549
5550 let filter_completions = provider
5551 .as_ref()
5552 .is_none_or(|provider| provider.filter_completions());
5553
5554 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5555 if filter_completions {
5556 menu.filter(query.clone(), provider.clone(), window, cx);
5557 }
5558 // When `is_incomplete` is false, no need to re-query completions when the current query
5559 // is a suffix of the initial query.
5560 if !menu.is_incomplete {
5561 // If the new query is a suffix of the old query (typing more characters) and
5562 // the previous result was complete, the existing completions can be filtered.
5563 //
5564 // Note that this is always true for snippet completions.
5565 let query_matches = match (&menu.initial_query, &query) {
5566 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5567 (None, _) => true,
5568 _ => false,
5569 };
5570 if query_matches {
5571 let position_matches = if menu.initial_position == position {
5572 true
5573 } else {
5574 let snapshot = self.buffer.read(cx).read(cx);
5575 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5576 };
5577 if position_matches {
5578 return;
5579 }
5580 }
5581 }
5582 };
5583
5584 let trigger_kind = match trigger {
5585 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5586 CompletionTriggerKind::TRIGGER_CHARACTER
5587 }
5588 _ => CompletionTriggerKind::INVOKED,
5589 };
5590 let completion_context = CompletionContext {
5591 trigger_character: trigger.and_then(|trigger| {
5592 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5593 Some(String::from(trigger))
5594 } else {
5595 None
5596 }
5597 }),
5598 trigger_kind,
5599 };
5600
5601 let Anchor {
5602 excerpt_id: buffer_excerpt_id,
5603 text_anchor: buffer_position,
5604 ..
5605 } = buffer_position;
5606
5607 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5608 buffer_snapshot.surrounding_word(buffer_position, None)
5609 {
5610 let word_to_exclude = buffer_snapshot
5611 .text_for_range(word_range.clone())
5612 .collect::<String>();
5613 (
5614 buffer_snapshot.anchor_before(word_range.start)
5615 ..buffer_snapshot.anchor_after(buffer_position),
5616 Some(word_to_exclude),
5617 )
5618 } else {
5619 (buffer_position..buffer_position, None)
5620 };
5621
5622 let language = buffer_snapshot
5623 .language_at(buffer_position)
5624 .map(|language| language.name());
5625
5626 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5627 .completions
5628 .clone();
5629
5630 let show_completion_documentation = buffer_snapshot
5631 .settings_at(buffer_position, cx)
5632 .show_completion_documentation;
5633
5634 // The document can be large, so stay in reasonable bounds when searching for words,
5635 // otherwise completion pop-up might be slow to appear.
5636 const WORD_LOOKUP_ROWS: u32 = 5_000;
5637 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5638 let min_word_search = buffer_snapshot.clip_point(
5639 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5640 Bias::Left,
5641 );
5642 let max_word_search = buffer_snapshot.clip_point(
5643 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5644 Bias::Right,
5645 );
5646 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5647 ..buffer_snapshot.point_to_offset(max_word_search);
5648
5649 let skip_digits = query
5650 .as_ref()
5651 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5652
5653 let omit_word_completions = !self.word_completions_enabled
5654 || (!ignore_word_threshold
5655 && match &query {
5656 Some(query) => query.chars().count() < completion_settings.words_min_length,
5657 None => completion_settings.words_min_length != 0,
5658 });
5659
5660 let (mut words, provider_responses) = match &provider {
5661 Some(provider) => {
5662 let provider_responses = provider.completions(
5663 buffer_excerpt_id,
5664 &buffer,
5665 buffer_position,
5666 completion_context,
5667 window,
5668 cx,
5669 );
5670
5671 let words = match (omit_word_completions, completion_settings.words) {
5672 (true, _) | (_, WordsCompletionMode::Disabled) => {
5673 Task::ready(BTreeMap::default())
5674 }
5675 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5676 .background_spawn(async move {
5677 buffer_snapshot.words_in_range(WordsQuery {
5678 fuzzy_contents: None,
5679 range: word_search_range,
5680 skip_digits,
5681 })
5682 }),
5683 };
5684
5685 (words, provider_responses)
5686 }
5687 None => {
5688 let words = if omit_word_completions {
5689 Task::ready(BTreeMap::default())
5690 } else {
5691 cx.background_spawn(async move {
5692 buffer_snapshot.words_in_range(WordsQuery {
5693 fuzzy_contents: None,
5694 range: word_search_range,
5695 skip_digits,
5696 })
5697 })
5698 };
5699 (words, Task::ready(Ok(Vec::new())))
5700 }
5701 };
5702
5703 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5704
5705 let id = post_inc(&mut self.next_completion_id);
5706 let task = cx.spawn_in(window, async move |editor, cx| {
5707 let Ok(()) = editor.update(cx, |this, _| {
5708 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5709 }) else {
5710 return;
5711 };
5712
5713 // TODO: Ideally completions from different sources would be selectively re-queried, so
5714 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5715 let mut completions = Vec::new();
5716 let mut is_incomplete = false;
5717 let mut display_options: Option<CompletionDisplayOptions> = None;
5718 if let Some(provider_responses) = provider_responses.await.log_err()
5719 && !provider_responses.is_empty()
5720 {
5721 for response in provider_responses {
5722 completions.extend(response.completions);
5723 is_incomplete = is_incomplete || response.is_incomplete;
5724 match display_options.as_mut() {
5725 None => {
5726 display_options = Some(response.display_options);
5727 }
5728 Some(options) => options.merge(&response.display_options),
5729 }
5730 }
5731 if completion_settings.words == WordsCompletionMode::Fallback {
5732 words = Task::ready(BTreeMap::default());
5733 }
5734 }
5735 let display_options = display_options.unwrap_or_default();
5736
5737 let mut words = words.await;
5738 if let Some(word_to_exclude) = &word_to_exclude {
5739 words.remove(word_to_exclude);
5740 }
5741 for lsp_completion in &completions {
5742 words.remove(&lsp_completion.new_text);
5743 }
5744 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5745 replace_range: word_replace_range.clone(),
5746 new_text: word.clone(),
5747 label: CodeLabel::plain(word, None),
5748 icon_path: None,
5749 documentation: None,
5750 source: CompletionSource::BufferWord {
5751 word_range,
5752 resolved: false,
5753 },
5754 insert_text_mode: Some(InsertTextMode::AS_IS),
5755 confirm: None,
5756 }));
5757
5758 let menu = if completions.is_empty() {
5759 None
5760 } else {
5761 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5762 let languages = editor
5763 .workspace
5764 .as_ref()
5765 .and_then(|(workspace, _)| workspace.upgrade())
5766 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5767 let menu = CompletionsMenu::new(
5768 id,
5769 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5770 sort_completions,
5771 show_completion_documentation,
5772 position,
5773 query.clone(),
5774 is_incomplete,
5775 buffer.clone(),
5776 completions.into(),
5777 display_options,
5778 snippet_sort_order,
5779 languages,
5780 language,
5781 cx,
5782 );
5783
5784 let query = if filter_completions { query } else { None };
5785 let matches_task = if let Some(query) = query {
5786 menu.do_async_filtering(query, cx)
5787 } else {
5788 Task::ready(menu.unfiltered_matches())
5789 };
5790 (menu, matches_task)
5791 }) else {
5792 return;
5793 };
5794
5795 let matches = matches_task.await;
5796
5797 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5798 // Newer menu already set, so exit.
5799 if let Some(CodeContextMenu::Completions(prev_menu)) =
5800 editor.context_menu.borrow().as_ref()
5801 && prev_menu.id > id
5802 {
5803 return;
5804 };
5805
5806 // Only valid to take prev_menu because it the new menu is immediately set
5807 // below, or the menu is hidden.
5808 if let Some(CodeContextMenu::Completions(prev_menu)) =
5809 editor.context_menu.borrow_mut().take()
5810 {
5811 let position_matches =
5812 if prev_menu.initial_position == menu.initial_position {
5813 true
5814 } else {
5815 let snapshot = editor.buffer.read(cx).read(cx);
5816 prev_menu.initial_position.to_offset(&snapshot)
5817 == menu.initial_position.to_offset(&snapshot)
5818 };
5819 if position_matches {
5820 // Preserve markdown cache before `set_filter_results` because it will
5821 // try to populate the documentation cache.
5822 menu.preserve_markdown_cache(prev_menu);
5823 }
5824 };
5825
5826 menu.set_filter_results(matches, provider, window, cx);
5827 }) else {
5828 return;
5829 };
5830
5831 menu.visible().then_some(menu)
5832 };
5833
5834 editor
5835 .update_in(cx, |editor, window, cx| {
5836 if editor.focus_handle.is_focused(window)
5837 && let Some(menu) = menu
5838 {
5839 *editor.context_menu.borrow_mut() =
5840 Some(CodeContextMenu::Completions(menu));
5841
5842 crate::hover_popover::hide_hover(editor, cx);
5843 if editor.show_edit_predictions_in_menu() {
5844 editor.update_visible_edit_prediction(window, cx);
5845 } else {
5846 editor.discard_edit_prediction(false, cx);
5847 }
5848
5849 cx.notify();
5850 return;
5851 }
5852
5853 if editor.completion_tasks.len() <= 1 {
5854 // If there are no more completion tasks and the last menu was empty, we should hide it.
5855 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5856 // If it was already hidden and we don't show edit predictions in the menu,
5857 // we should also show the edit prediction when available.
5858 if was_hidden && editor.show_edit_predictions_in_menu() {
5859 editor.update_visible_edit_prediction(window, cx);
5860 }
5861 }
5862 })
5863 .ok();
5864 });
5865
5866 self.completion_tasks.push((id, task));
5867 }
5868
5869 #[cfg(feature = "test-support")]
5870 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5871 let menu = self.context_menu.borrow();
5872 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5873 let completions = menu.completions.borrow();
5874 Some(completions.to_vec())
5875 } else {
5876 None
5877 }
5878 }
5879
5880 pub fn with_completions_menu_matching_id<R>(
5881 &self,
5882 id: CompletionId,
5883 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5884 ) -> R {
5885 let mut context_menu = self.context_menu.borrow_mut();
5886 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5887 return f(None);
5888 };
5889 if completions_menu.id != id {
5890 return f(None);
5891 }
5892 f(Some(completions_menu))
5893 }
5894
5895 pub fn confirm_completion(
5896 &mut self,
5897 action: &ConfirmCompletion,
5898 window: &mut Window,
5899 cx: &mut Context<Self>,
5900 ) -> Option<Task<Result<()>>> {
5901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5902 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5903 }
5904
5905 pub fn confirm_completion_insert(
5906 &mut self,
5907 _: &ConfirmCompletionInsert,
5908 window: &mut Window,
5909 cx: &mut Context<Self>,
5910 ) -> Option<Task<Result<()>>> {
5911 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5912 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5913 }
5914
5915 pub fn confirm_completion_replace(
5916 &mut self,
5917 _: &ConfirmCompletionReplace,
5918 window: &mut Window,
5919 cx: &mut Context<Self>,
5920 ) -> Option<Task<Result<()>>> {
5921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5922 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5923 }
5924
5925 pub fn compose_completion(
5926 &mut self,
5927 action: &ComposeCompletion,
5928 window: &mut Window,
5929 cx: &mut Context<Self>,
5930 ) -> Option<Task<Result<()>>> {
5931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5932 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5933 }
5934
5935 fn do_completion(
5936 &mut self,
5937 item_ix: Option<usize>,
5938 intent: CompletionIntent,
5939 window: &mut Window,
5940 cx: &mut Context<Editor>,
5941 ) -> Option<Task<Result<()>>> {
5942 use language::ToOffset as _;
5943
5944 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5945 else {
5946 return None;
5947 };
5948
5949 let candidate_id = {
5950 let entries = completions_menu.entries.borrow();
5951 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5952 if self.show_edit_predictions_in_menu() {
5953 self.discard_edit_prediction(true, cx);
5954 }
5955 mat.candidate_id
5956 };
5957
5958 let completion = completions_menu
5959 .completions
5960 .borrow()
5961 .get(candidate_id)?
5962 .clone();
5963 cx.stop_propagation();
5964
5965 let buffer_handle = completions_menu.buffer.clone();
5966
5967 let CompletionEdit {
5968 new_text,
5969 snippet,
5970 replace_range,
5971 } = process_completion_for_edit(
5972 &completion,
5973 intent,
5974 &buffer_handle,
5975 &completions_menu.initial_position.text_anchor,
5976 cx,
5977 );
5978
5979 let buffer = buffer_handle.read(cx);
5980 let snapshot = self.buffer.read(cx).snapshot(cx);
5981 let newest_anchor = self.selections.newest_anchor();
5982 let replace_range_multibuffer = {
5983 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5984 let multibuffer_anchor = snapshot
5985 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5986 .unwrap()
5987 ..snapshot
5988 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5989 .unwrap();
5990 multibuffer_anchor.start.to_offset(&snapshot)
5991 ..multibuffer_anchor.end.to_offset(&snapshot)
5992 };
5993 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5994 return None;
5995 }
5996
5997 let old_text = buffer
5998 .text_for_range(replace_range.clone())
5999 .collect::<String>();
6000 let lookbehind = newest_anchor
6001 .start
6002 .text_anchor
6003 .to_offset(buffer)
6004 .saturating_sub(replace_range.start);
6005 let lookahead = replace_range
6006 .end
6007 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6008 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6009 let suffix = &old_text[lookbehind.min(old_text.len())..];
6010
6011 let selections = self.selections.all::<usize>(cx);
6012 let mut ranges = Vec::new();
6013 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6014
6015 for selection in &selections {
6016 let range = if selection.id == newest_anchor.id {
6017 replace_range_multibuffer.clone()
6018 } else {
6019 let mut range = selection.range();
6020
6021 // if prefix is present, don't duplicate it
6022 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6023 range.start = range.start.saturating_sub(lookbehind);
6024
6025 // if suffix is also present, mimic the newest cursor and replace it
6026 if selection.id != newest_anchor.id
6027 && snapshot.contains_str_at(range.end, suffix)
6028 {
6029 range.end += lookahead;
6030 }
6031 }
6032 range
6033 };
6034
6035 ranges.push(range.clone());
6036
6037 if !self.linked_edit_ranges.is_empty() {
6038 let start_anchor = snapshot.anchor_before(range.start);
6039 let end_anchor = snapshot.anchor_after(range.end);
6040 if let Some(ranges) = self
6041 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6042 {
6043 for (buffer, edits) in ranges {
6044 linked_edits
6045 .entry(buffer.clone())
6046 .or_default()
6047 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6048 }
6049 }
6050 }
6051 }
6052
6053 let common_prefix_len = old_text
6054 .chars()
6055 .zip(new_text.chars())
6056 .take_while(|(a, b)| a == b)
6057 .map(|(a, _)| a.len_utf8())
6058 .sum::<usize>();
6059
6060 cx.emit(EditorEvent::InputHandled {
6061 utf16_range_to_replace: None,
6062 text: new_text[common_prefix_len..].into(),
6063 });
6064
6065 self.transact(window, cx, |editor, window, cx| {
6066 if let Some(mut snippet) = snippet {
6067 snippet.text = new_text.to_string();
6068 editor
6069 .insert_snippet(&ranges, snippet, window, cx)
6070 .log_err();
6071 } else {
6072 editor.buffer.update(cx, |multi_buffer, cx| {
6073 let auto_indent = match completion.insert_text_mode {
6074 Some(InsertTextMode::AS_IS) => None,
6075 _ => editor.autoindent_mode.clone(),
6076 };
6077 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6078 multi_buffer.edit(edits, auto_indent, cx);
6079 });
6080 }
6081 for (buffer, edits) in linked_edits {
6082 buffer.update(cx, |buffer, cx| {
6083 let snapshot = buffer.snapshot();
6084 let edits = edits
6085 .into_iter()
6086 .map(|(range, text)| {
6087 use text::ToPoint as TP;
6088 let end_point = TP::to_point(&range.end, &snapshot);
6089 let start_point = TP::to_point(&range.start, &snapshot);
6090 (start_point..end_point, text)
6091 })
6092 .sorted_by_key(|(range, _)| range.start);
6093 buffer.edit(edits, None, cx);
6094 })
6095 }
6096
6097 editor.refresh_edit_prediction(true, false, window, cx);
6098 });
6099 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6100
6101 let show_new_completions_on_confirm = completion
6102 .confirm
6103 .as_ref()
6104 .is_some_and(|confirm| confirm(intent, window, cx));
6105 if show_new_completions_on_confirm {
6106 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6107 }
6108
6109 let provider = self.completion_provider.as_ref()?;
6110 drop(completion);
6111 let apply_edits = provider.apply_additional_edits_for_completion(
6112 buffer_handle,
6113 completions_menu.completions.clone(),
6114 candidate_id,
6115 true,
6116 cx,
6117 );
6118
6119 let editor_settings = EditorSettings::get_global(cx);
6120 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6121 // After the code completion is finished, users often want to know what signatures are needed.
6122 // so we should automatically call signature_help
6123 self.show_signature_help(&ShowSignatureHelp, window, cx);
6124 }
6125
6126 Some(cx.foreground_executor().spawn(async move {
6127 apply_edits.await?;
6128 Ok(())
6129 }))
6130 }
6131
6132 pub fn toggle_code_actions(
6133 &mut self,
6134 action: &ToggleCodeActions,
6135 window: &mut Window,
6136 cx: &mut Context<Self>,
6137 ) {
6138 let quick_launch = action.quick_launch;
6139 let mut context_menu = self.context_menu.borrow_mut();
6140 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6141 if code_actions.deployed_from == action.deployed_from {
6142 // Toggle if we're selecting the same one
6143 *context_menu = None;
6144 cx.notify();
6145 return;
6146 } else {
6147 // Otherwise, clear it and start a new one
6148 *context_menu = None;
6149 cx.notify();
6150 }
6151 }
6152 drop(context_menu);
6153 let snapshot = self.snapshot(window, cx);
6154 let deployed_from = action.deployed_from.clone();
6155 let action = action.clone();
6156 self.completion_tasks.clear();
6157 self.discard_edit_prediction(false, cx);
6158
6159 let multibuffer_point = match &action.deployed_from {
6160 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6161 DisplayPoint::new(*row, 0).to_point(&snapshot)
6162 }
6163 _ => self.selections.newest::<Point>(cx).head(),
6164 };
6165 let Some((buffer, buffer_row)) = snapshot
6166 .buffer_snapshot()
6167 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6168 .and_then(|(buffer_snapshot, range)| {
6169 self.buffer()
6170 .read(cx)
6171 .buffer(buffer_snapshot.remote_id())
6172 .map(|buffer| (buffer, range.start.row))
6173 })
6174 else {
6175 return;
6176 };
6177 let buffer_id = buffer.read(cx).remote_id();
6178 let tasks = self
6179 .tasks
6180 .get(&(buffer_id, buffer_row))
6181 .map(|t| Arc::new(t.to_owned()));
6182
6183 if !self.focus_handle.is_focused(window) {
6184 return;
6185 }
6186 let project = self.project.clone();
6187
6188 let code_actions_task = match deployed_from {
6189 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6190 _ => self.code_actions(buffer_row, window, cx),
6191 };
6192
6193 let runnable_task = match deployed_from {
6194 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6195 _ => {
6196 let mut task_context_task = Task::ready(None);
6197 if let Some(tasks) = &tasks
6198 && let Some(project) = project
6199 {
6200 task_context_task =
6201 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6202 }
6203
6204 cx.spawn_in(window, {
6205 let buffer = buffer.clone();
6206 async move |editor, cx| {
6207 let task_context = task_context_task.await;
6208
6209 let resolved_tasks =
6210 tasks
6211 .zip(task_context.clone())
6212 .map(|(tasks, task_context)| ResolvedTasks {
6213 templates: tasks.resolve(&task_context).collect(),
6214 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6215 multibuffer_point.row,
6216 tasks.column,
6217 )),
6218 });
6219 let debug_scenarios = editor
6220 .update(cx, |editor, cx| {
6221 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6222 })?
6223 .await;
6224 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6225 }
6226 })
6227 }
6228 };
6229
6230 cx.spawn_in(window, async move |editor, cx| {
6231 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6232 let code_actions = code_actions_task.await;
6233 let spawn_straight_away = quick_launch
6234 && resolved_tasks
6235 .as_ref()
6236 .is_some_and(|tasks| tasks.templates.len() == 1)
6237 && code_actions
6238 .as_ref()
6239 .is_none_or(|actions| actions.is_empty())
6240 && debug_scenarios.is_empty();
6241
6242 editor.update_in(cx, |editor, window, cx| {
6243 crate::hover_popover::hide_hover(editor, cx);
6244 let actions = CodeActionContents::new(
6245 resolved_tasks,
6246 code_actions,
6247 debug_scenarios,
6248 task_context.unwrap_or_default(),
6249 );
6250
6251 // Don't show the menu if there are no actions available
6252 if actions.is_empty() {
6253 cx.notify();
6254 return Task::ready(Ok(()));
6255 }
6256
6257 *editor.context_menu.borrow_mut() =
6258 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6259 buffer,
6260 actions,
6261 selected_item: Default::default(),
6262 scroll_handle: UniformListScrollHandle::default(),
6263 deployed_from,
6264 }));
6265 cx.notify();
6266 if spawn_straight_away
6267 && let Some(task) = editor.confirm_code_action(
6268 &ConfirmCodeAction { item_ix: Some(0) },
6269 window,
6270 cx,
6271 )
6272 {
6273 return task;
6274 }
6275
6276 Task::ready(Ok(()))
6277 })
6278 })
6279 .detach_and_log_err(cx);
6280 }
6281
6282 fn debug_scenarios(
6283 &mut self,
6284 resolved_tasks: &Option<ResolvedTasks>,
6285 buffer: &Entity<Buffer>,
6286 cx: &mut App,
6287 ) -> Task<Vec<task::DebugScenario>> {
6288 maybe!({
6289 let project = self.project()?;
6290 let dap_store = project.read(cx).dap_store();
6291 let mut scenarios = vec![];
6292 let resolved_tasks = resolved_tasks.as_ref()?;
6293 let buffer = buffer.read(cx);
6294 let language = buffer.language()?;
6295 let file = buffer.file();
6296 let debug_adapter = language_settings(language.name().into(), file, cx)
6297 .debuggers
6298 .first()
6299 .map(SharedString::from)
6300 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6301
6302 dap_store.update(cx, |dap_store, cx| {
6303 for (_, task) in &resolved_tasks.templates {
6304 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6305 task.original_task().clone(),
6306 debug_adapter.clone().into(),
6307 task.display_label().to_owned().into(),
6308 cx,
6309 );
6310 scenarios.push(maybe_scenario);
6311 }
6312 });
6313 Some(cx.background_spawn(async move {
6314 futures::future::join_all(scenarios)
6315 .await
6316 .into_iter()
6317 .flatten()
6318 .collect::<Vec<_>>()
6319 }))
6320 })
6321 .unwrap_or_else(|| Task::ready(vec![]))
6322 }
6323
6324 fn code_actions(
6325 &mut self,
6326 buffer_row: u32,
6327 window: &mut Window,
6328 cx: &mut Context<Self>,
6329 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6330 let mut task = self.code_actions_task.take();
6331 cx.spawn_in(window, async move |editor, cx| {
6332 while let Some(prev_task) = task {
6333 prev_task.await.log_err();
6334 task = editor
6335 .update(cx, |this, _| this.code_actions_task.take())
6336 .ok()?;
6337 }
6338
6339 editor
6340 .update(cx, |editor, cx| {
6341 editor
6342 .available_code_actions
6343 .clone()
6344 .and_then(|(location, code_actions)| {
6345 let snapshot = location.buffer.read(cx).snapshot();
6346 let point_range = location.range.to_point(&snapshot);
6347 let point_range = point_range.start.row..=point_range.end.row;
6348 if point_range.contains(&buffer_row) {
6349 Some(code_actions)
6350 } else {
6351 None
6352 }
6353 })
6354 })
6355 .ok()
6356 .flatten()
6357 })
6358 }
6359
6360 pub fn confirm_code_action(
6361 &mut self,
6362 action: &ConfirmCodeAction,
6363 window: &mut Window,
6364 cx: &mut Context<Self>,
6365 ) -> Option<Task<Result<()>>> {
6366 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6367
6368 let actions_menu =
6369 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6370 menu
6371 } else {
6372 return None;
6373 };
6374
6375 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6376 let action = actions_menu.actions.get(action_ix)?;
6377 let title = action.label();
6378 let buffer = actions_menu.buffer;
6379 let workspace = self.workspace()?;
6380
6381 match action {
6382 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6383 workspace.update(cx, |workspace, cx| {
6384 workspace.schedule_resolved_task(
6385 task_source_kind,
6386 resolved_task,
6387 false,
6388 window,
6389 cx,
6390 );
6391
6392 Some(Task::ready(Ok(())))
6393 })
6394 }
6395 CodeActionsItem::CodeAction {
6396 excerpt_id,
6397 action,
6398 provider,
6399 } => {
6400 let apply_code_action =
6401 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6402 let workspace = workspace.downgrade();
6403 Some(cx.spawn_in(window, async move |editor, cx| {
6404 let project_transaction = apply_code_action.await?;
6405 Self::open_project_transaction(
6406 &editor,
6407 workspace,
6408 project_transaction,
6409 title,
6410 cx,
6411 )
6412 .await
6413 }))
6414 }
6415 CodeActionsItem::DebugScenario(scenario) => {
6416 let context = actions_menu.actions.context;
6417
6418 workspace.update(cx, |workspace, cx| {
6419 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6420 workspace.start_debug_session(
6421 scenario,
6422 context,
6423 Some(buffer),
6424 None,
6425 window,
6426 cx,
6427 );
6428 });
6429 Some(Task::ready(Ok(())))
6430 }
6431 }
6432 }
6433
6434 pub async fn open_project_transaction(
6435 editor: &WeakEntity<Editor>,
6436 workspace: WeakEntity<Workspace>,
6437 transaction: ProjectTransaction,
6438 title: String,
6439 cx: &mut AsyncWindowContext,
6440 ) -> Result<()> {
6441 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6442 cx.update(|_, cx| {
6443 entries.sort_unstable_by_key(|(buffer, _)| {
6444 buffer.read(cx).file().map(|f| f.path().clone())
6445 });
6446 })?;
6447 if entries.is_empty() {
6448 return Ok(());
6449 }
6450
6451 // If the project transaction's edits are all contained within this editor, then
6452 // avoid opening a new editor to display them.
6453
6454 if let [(buffer, transaction)] = &*entries {
6455 let excerpt = editor.update(cx, |editor, cx| {
6456 editor
6457 .buffer()
6458 .read(cx)
6459 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6460 })?;
6461 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6462 && excerpted_buffer == *buffer
6463 {
6464 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6465 let excerpt_range = excerpt_range.to_offset(buffer);
6466 buffer
6467 .edited_ranges_for_transaction::<usize>(transaction)
6468 .all(|range| {
6469 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6470 })
6471 })?;
6472
6473 if all_edits_within_excerpt {
6474 return Ok(());
6475 }
6476 }
6477 }
6478
6479 let mut ranges_to_highlight = Vec::new();
6480 let excerpt_buffer = cx.new(|cx| {
6481 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6482 for (buffer_handle, transaction) in &entries {
6483 let edited_ranges = buffer_handle
6484 .read(cx)
6485 .edited_ranges_for_transaction::<Point>(transaction)
6486 .collect::<Vec<_>>();
6487 let (ranges, _) = multibuffer.set_excerpts_for_path(
6488 PathKey::for_buffer(buffer_handle, cx),
6489 buffer_handle.clone(),
6490 edited_ranges,
6491 multibuffer_context_lines(cx),
6492 cx,
6493 );
6494
6495 ranges_to_highlight.extend(ranges);
6496 }
6497 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6498 multibuffer
6499 })?;
6500
6501 workspace.update_in(cx, |workspace, window, cx| {
6502 let project = workspace.project().clone();
6503 let editor =
6504 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6505 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6506 editor.update(cx, |editor, cx| {
6507 editor.highlight_background::<Self>(
6508 &ranges_to_highlight,
6509 |theme| theme.colors().editor_highlighted_line_background,
6510 cx,
6511 );
6512 });
6513 })?;
6514
6515 Ok(())
6516 }
6517
6518 pub fn clear_code_action_providers(&mut self) {
6519 self.code_action_providers.clear();
6520 self.available_code_actions.take();
6521 }
6522
6523 pub fn add_code_action_provider(
6524 &mut self,
6525 provider: Rc<dyn CodeActionProvider>,
6526 window: &mut Window,
6527 cx: &mut Context<Self>,
6528 ) {
6529 if self
6530 .code_action_providers
6531 .iter()
6532 .any(|existing_provider| existing_provider.id() == provider.id())
6533 {
6534 return;
6535 }
6536
6537 self.code_action_providers.push(provider);
6538 self.refresh_code_actions(window, cx);
6539 }
6540
6541 pub fn remove_code_action_provider(
6542 &mut self,
6543 id: Arc<str>,
6544 window: &mut Window,
6545 cx: &mut Context<Self>,
6546 ) {
6547 self.code_action_providers
6548 .retain(|provider| provider.id() != id);
6549 self.refresh_code_actions(window, cx);
6550 }
6551
6552 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6553 !self.code_action_providers.is_empty()
6554 && EditorSettings::get_global(cx).toolbar.code_actions
6555 }
6556
6557 pub fn has_available_code_actions(&self) -> bool {
6558 self.available_code_actions
6559 .as_ref()
6560 .is_some_and(|(_, actions)| !actions.is_empty())
6561 }
6562
6563 fn render_inline_code_actions(
6564 &self,
6565 icon_size: ui::IconSize,
6566 display_row: DisplayRow,
6567 is_active: bool,
6568 cx: &mut Context<Self>,
6569 ) -> AnyElement {
6570 let show_tooltip = !self.context_menu_visible();
6571 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6572 .icon_size(icon_size)
6573 .shape(ui::IconButtonShape::Square)
6574 .icon_color(ui::Color::Hidden)
6575 .toggle_state(is_active)
6576 .when(show_tooltip, |this| {
6577 this.tooltip({
6578 let focus_handle = self.focus_handle.clone();
6579 move |window, cx| {
6580 Tooltip::for_action_in(
6581 "Toggle Code Actions",
6582 &ToggleCodeActions {
6583 deployed_from: None,
6584 quick_launch: false,
6585 },
6586 &focus_handle,
6587 window,
6588 cx,
6589 )
6590 }
6591 })
6592 })
6593 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6594 window.focus(&editor.focus_handle(cx));
6595 editor.toggle_code_actions(
6596 &crate::actions::ToggleCodeActions {
6597 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6598 display_row,
6599 )),
6600 quick_launch: false,
6601 },
6602 window,
6603 cx,
6604 );
6605 }))
6606 .into_any_element()
6607 }
6608
6609 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6610 &self.context_menu
6611 }
6612
6613 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6614 let newest_selection = self.selections.newest_anchor().clone();
6615 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6616 let buffer = self.buffer.read(cx);
6617 if newest_selection.head().diff_base_anchor.is_some() {
6618 return None;
6619 }
6620 let (start_buffer, start) =
6621 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6622 let (end_buffer, end) =
6623 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6624 if start_buffer != end_buffer {
6625 return None;
6626 }
6627
6628 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6629 cx.background_executor()
6630 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6631 .await;
6632
6633 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6634 let providers = this.code_action_providers.clone();
6635 let tasks = this
6636 .code_action_providers
6637 .iter()
6638 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6639 .collect::<Vec<_>>();
6640 (providers, tasks)
6641 })?;
6642
6643 let mut actions = Vec::new();
6644 for (provider, provider_actions) in
6645 providers.into_iter().zip(future::join_all(tasks).await)
6646 {
6647 if let Some(provider_actions) = provider_actions.log_err() {
6648 actions.extend(provider_actions.into_iter().map(|action| {
6649 AvailableCodeAction {
6650 excerpt_id: newest_selection.start.excerpt_id,
6651 action,
6652 provider: provider.clone(),
6653 }
6654 }));
6655 }
6656 }
6657
6658 this.update(cx, |this, cx| {
6659 this.available_code_actions = if actions.is_empty() {
6660 None
6661 } else {
6662 Some((
6663 Location {
6664 buffer: start_buffer,
6665 range: start..end,
6666 },
6667 actions.into(),
6668 ))
6669 };
6670 cx.notify();
6671 })
6672 }));
6673 None
6674 }
6675
6676 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6677 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6678 self.show_git_blame_inline = false;
6679
6680 self.show_git_blame_inline_delay_task =
6681 Some(cx.spawn_in(window, async move |this, cx| {
6682 cx.background_executor().timer(delay).await;
6683
6684 this.update(cx, |this, cx| {
6685 this.show_git_blame_inline = true;
6686 cx.notify();
6687 })
6688 .log_err();
6689 }));
6690 }
6691 }
6692
6693 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6694 let snapshot = self.snapshot(window, cx);
6695 let cursor = self.selections.newest::<Point>(cx).head();
6696 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6697 else {
6698 return;
6699 };
6700
6701 let Some(blame) = self.blame.as_ref() else {
6702 return;
6703 };
6704
6705 let row_info = RowInfo {
6706 buffer_id: Some(buffer.remote_id()),
6707 buffer_row: Some(point.row),
6708 ..Default::default()
6709 };
6710 let Some((buffer, blame_entry)) = blame
6711 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6712 .flatten()
6713 else {
6714 return;
6715 };
6716
6717 let anchor = self.selections.newest_anchor().head();
6718 let position = self.to_pixel_point(anchor, &snapshot, window);
6719 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6720 self.show_blame_popover(
6721 buffer,
6722 &blame_entry,
6723 position + last_bounds.origin,
6724 true,
6725 cx,
6726 );
6727 };
6728 }
6729
6730 fn show_blame_popover(
6731 &mut self,
6732 buffer: BufferId,
6733 blame_entry: &BlameEntry,
6734 position: gpui::Point<Pixels>,
6735 ignore_timeout: bool,
6736 cx: &mut Context<Self>,
6737 ) {
6738 if let Some(state) = &mut self.inline_blame_popover {
6739 state.hide_task.take();
6740 } else {
6741 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6742 let blame_entry = blame_entry.clone();
6743 let show_task = cx.spawn(async move |editor, cx| {
6744 if !ignore_timeout {
6745 cx.background_executor()
6746 .timer(std::time::Duration::from_millis(blame_popover_delay))
6747 .await;
6748 }
6749 editor
6750 .update(cx, |editor, cx| {
6751 editor.inline_blame_popover_show_task.take();
6752 let Some(blame) = editor.blame.as_ref() else {
6753 return;
6754 };
6755 let blame = blame.read(cx);
6756 let details = blame.details_for_entry(buffer, &blame_entry);
6757 let markdown = cx.new(|cx| {
6758 Markdown::new(
6759 details
6760 .as_ref()
6761 .map(|message| message.message.clone())
6762 .unwrap_or_default(),
6763 None,
6764 None,
6765 cx,
6766 )
6767 });
6768 editor.inline_blame_popover = Some(InlineBlamePopover {
6769 position,
6770 hide_task: None,
6771 popover_bounds: None,
6772 popover_state: InlineBlamePopoverState {
6773 scroll_handle: ScrollHandle::new(),
6774 commit_message: details,
6775 markdown,
6776 },
6777 keyboard_grace: ignore_timeout,
6778 });
6779 cx.notify();
6780 })
6781 .ok();
6782 });
6783 self.inline_blame_popover_show_task = Some(show_task);
6784 }
6785 }
6786
6787 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6788 self.inline_blame_popover_show_task.take();
6789 if let Some(state) = &mut self.inline_blame_popover {
6790 let hide_task = cx.spawn(async move |editor, cx| {
6791 cx.background_executor()
6792 .timer(std::time::Duration::from_millis(100))
6793 .await;
6794 editor
6795 .update(cx, |editor, cx| {
6796 editor.inline_blame_popover.take();
6797 cx.notify();
6798 })
6799 .ok();
6800 });
6801 state.hide_task = Some(hide_task);
6802 }
6803 }
6804
6805 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6806 if self.pending_rename.is_some() {
6807 return None;
6808 }
6809
6810 let provider = self.semantics_provider.clone()?;
6811 let buffer = self.buffer.read(cx);
6812 let newest_selection = self.selections.newest_anchor().clone();
6813 let cursor_position = newest_selection.head();
6814 let (cursor_buffer, cursor_buffer_position) =
6815 buffer.text_anchor_for_position(cursor_position, cx)?;
6816 let (tail_buffer, tail_buffer_position) =
6817 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6818 if cursor_buffer != tail_buffer {
6819 return None;
6820 }
6821
6822 let snapshot = cursor_buffer.read(cx).snapshot();
6823 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6824 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6825 if start_word_range != end_word_range {
6826 self.document_highlights_task.take();
6827 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6828 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6829 return None;
6830 }
6831
6832 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6833 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6834 cx.background_executor()
6835 .timer(Duration::from_millis(debounce))
6836 .await;
6837
6838 let highlights = if let Some(highlights) = cx
6839 .update(|cx| {
6840 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6841 })
6842 .ok()
6843 .flatten()
6844 {
6845 highlights.await.log_err()
6846 } else {
6847 None
6848 };
6849
6850 if let Some(highlights) = highlights {
6851 this.update(cx, |this, cx| {
6852 if this.pending_rename.is_some() {
6853 return;
6854 }
6855
6856 let buffer = this.buffer.read(cx);
6857 if buffer
6858 .text_anchor_for_position(cursor_position, cx)
6859 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6860 {
6861 return;
6862 }
6863
6864 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6865 let mut write_ranges = Vec::new();
6866 let mut read_ranges = Vec::new();
6867 for highlight in highlights {
6868 let buffer_id = cursor_buffer.read(cx).remote_id();
6869 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6870 {
6871 let start = highlight
6872 .range
6873 .start
6874 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6875 let end = highlight
6876 .range
6877 .end
6878 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6879 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6880 continue;
6881 }
6882
6883 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6884 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6885 write_ranges.push(range);
6886 } else {
6887 read_ranges.push(range);
6888 }
6889 }
6890 }
6891
6892 this.highlight_background::<DocumentHighlightRead>(
6893 &read_ranges,
6894 |theme| theme.colors().editor_document_highlight_read_background,
6895 cx,
6896 );
6897 this.highlight_background::<DocumentHighlightWrite>(
6898 &write_ranges,
6899 |theme| theme.colors().editor_document_highlight_write_background,
6900 cx,
6901 );
6902 cx.notify();
6903 })
6904 .log_err();
6905 }
6906 }));
6907 None
6908 }
6909
6910 fn prepare_highlight_query_from_selection(
6911 &mut self,
6912 cx: &mut Context<Editor>,
6913 ) -> Option<(String, Range<Anchor>)> {
6914 if matches!(self.mode, EditorMode::SingleLine) {
6915 return None;
6916 }
6917 if !EditorSettings::get_global(cx).selection_highlight {
6918 return None;
6919 }
6920 if self.selections.count() != 1 || self.selections.line_mode() {
6921 return None;
6922 }
6923 let selection = self.selections.newest::<Point>(cx);
6924 if selection.is_empty() || selection.start.row != selection.end.row {
6925 return None;
6926 }
6927 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6928 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6929 let query = multi_buffer_snapshot
6930 .text_for_range(selection_anchor_range.clone())
6931 .collect::<String>();
6932 if query.trim().is_empty() {
6933 return None;
6934 }
6935 Some((query, selection_anchor_range))
6936 }
6937
6938 fn update_selection_occurrence_highlights(
6939 &mut self,
6940 query_text: String,
6941 query_range: Range<Anchor>,
6942 multi_buffer_range_to_query: Range<Point>,
6943 use_debounce: bool,
6944 window: &mut Window,
6945 cx: &mut Context<Editor>,
6946 ) -> Task<()> {
6947 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6948 cx.spawn_in(window, async move |editor, cx| {
6949 if use_debounce {
6950 cx.background_executor()
6951 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6952 .await;
6953 }
6954 let match_task = cx.background_spawn(async move {
6955 let buffer_ranges = multi_buffer_snapshot
6956 .range_to_buffer_ranges(multi_buffer_range_to_query)
6957 .into_iter()
6958 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6959 let mut match_ranges = Vec::new();
6960 let Ok(regex) = project::search::SearchQuery::text(
6961 query_text.clone(),
6962 false,
6963 false,
6964 false,
6965 Default::default(),
6966 Default::default(),
6967 false,
6968 None,
6969 ) else {
6970 return Vec::default();
6971 };
6972 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6973 match_ranges.extend(
6974 regex
6975 .search(buffer_snapshot, Some(search_range.clone()))
6976 .await
6977 .into_iter()
6978 .filter_map(|match_range| {
6979 let match_start = buffer_snapshot
6980 .anchor_after(search_range.start + match_range.start);
6981 let match_end = buffer_snapshot
6982 .anchor_before(search_range.start + match_range.end);
6983 let match_anchor_range = Anchor::range_in_buffer(
6984 excerpt_id,
6985 buffer_snapshot.remote_id(),
6986 match_start..match_end,
6987 );
6988 (match_anchor_range != query_range).then_some(match_anchor_range)
6989 }),
6990 );
6991 }
6992 match_ranges
6993 });
6994 let match_ranges = match_task.await;
6995 editor
6996 .update_in(cx, |editor, _, cx| {
6997 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6998 if !match_ranges.is_empty() {
6999 editor.highlight_background::<SelectedTextHighlight>(
7000 &match_ranges,
7001 |theme| theme.colors().editor_document_highlight_bracket_background,
7002 cx,
7003 )
7004 }
7005 })
7006 .log_err();
7007 })
7008 }
7009
7010 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7011 struct NewlineFold;
7012 let type_id = std::any::TypeId::of::<NewlineFold>();
7013 if !self.mode.is_single_line() {
7014 return;
7015 }
7016 let snapshot = self.snapshot(window, cx);
7017 if snapshot.buffer_snapshot().max_point().row == 0 {
7018 return;
7019 }
7020 let task = cx.background_spawn(async move {
7021 let new_newlines = snapshot
7022 .buffer_chars_at(0)
7023 .filter_map(|(c, i)| {
7024 if c == '\n' {
7025 Some(
7026 snapshot.buffer_snapshot().anchor_after(i)
7027 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7028 )
7029 } else {
7030 None
7031 }
7032 })
7033 .collect::<Vec<_>>();
7034 let existing_newlines = snapshot
7035 .folds_in_range(0..snapshot.buffer_snapshot().len())
7036 .filter_map(|fold| {
7037 if fold.placeholder.type_tag == Some(type_id) {
7038 Some(fold.range.start..fold.range.end)
7039 } else {
7040 None
7041 }
7042 })
7043 .collect::<Vec<_>>();
7044
7045 (new_newlines, existing_newlines)
7046 });
7047 self.folding_newlines = cx.spawn(async move |this, cx| {
7048 let (new_newlines, existing_newlines) = task.await;
7049 if new_newlines == existing_newlines {
7050 return;
7051 }
7052 let placeholder = FoldPlaceholder {
7053 render: Arc::new(move |_, _, cx| {
7054 div()
7055 .bg(cx.theme().status().hint_background)
7056 .border_b_1()
7057 .size_full()
7058 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7059 .border_color(cx.theme().status().hint)
7060 .child("\\n")
7061 .into_any()
7062 }),
7063 constrain_width: false,
7064 merge_adjacent: false,
7065 type_tag: Some(type_id),
7066 };
7067 let creases = new_newlines
7068 .into_iter()
7069 .map(|range| Crease::simple(range, placeholder.clone()))
7070 .collect();
7071 this.update(cx, |this, cx| {
7072 this.display_map.update(cx, |display_map, cx| {
7073 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7074 display_map.fold(creases, cx);
7075 });
7076 })
7077 .ok();
7078 });
7079 }
7080
7081 fn refresh_selected_text_highlights(
7082 &mut self,
7083 on_buffer_edit: bool,
7084 window: &mut Window,
7085 cx: &mut Context<Editor>,
7086 ) {
7087 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7088 else {
7089 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7090 self.quick_selection_highlight_task.take();
7091 self.debounced_selection_highlight_task.take();
7092 return;
7093 };
7094 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7095 if on_buffer_edit
7096 || self
7097 .quick_selection_highlight_task
7098 .as_ref()
7099 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7100 {
7101 let multi_buffer_visible_start = self
7102 .scroll_manager
7103 .anchor()
7104 .anchor
7105 .to_point(&multi_buffer_snapshot);
7106 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7107 multi_buffer_visible_start
7108 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7109 Bias::Left,
7110 );
7111 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7112 self.quick_selection_highlight_task = Some((
7113 query_range.clone(),
7114 self.update_selection_occurrence_highlights(
7115 query_text.clone(),
7116 query_range.clone(),
7117 multi_buffer_visible_range,
7118 false,
7119 window,
7120 cx,
7121 ),
7122 ));
7123 }
7124 if on_buffer_edit
7125 || self
7126 .debounced_selection_highlight_task
7127 .as_ref()
7128 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7129 {
7130 let multi_buffer_start = multi_buffer_snapshot
7131 .anchor_before(0)
7132 .to_point(&multi_buffer_snapshot);
7133 let multi_buffer_end = multi_buffer_snapshot
7134 .anchor_after(multi_buffer_snapshot.len())
7135 .to_point(&multi_buffer_snapshot);
7136 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7137 self.debounced_selection_highlight_task = Some((
7138 query_range.clone(),
7139 self.update_selection_occurrence_highlights(
7140 query_text,
7141 query_range,
7142 multi_buffer_full_range,
7143 true,
7144 window,
7145 cx,
7146 ),
7147 ));
7148 }
7149 }
7150
7151 pub fn refresh_edit_prediction(
7152 &mut self,
7153 debounce: bool,
7154 user_requested: bool,
7155 window: &mut Window,
7156 cx: &mut Context<Self>,
7157 ) -> Option<()> {
7158 if DisableAiSettings::get_global(cx).disable_ai {
7159 return None;
7160 }
7161
7162 let provider = self.edit_prediction_provider()?;
7163 let cursor = self.selections.newest_anchor().head();
7164 let (buffer, cursor_buffer_position) =
7165 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7166
7167 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7168 self.discard_edit_prediction(false, cx);
7169 return None;
7170 }
7171
7172 self.update_visible_edit_prediction(window, cx);
7173
7174 if !user_requested
7175 && (!self.should_show_edit_predictions()
7176 || !self.is_focused(window)
7177 || buffer.read(cx).is_empty())
7178 {
7179 self.discard_edit_prediction(false, cx);
7180 return None;
7181 }
7182
7183 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7184 Some(())
7185 }
7186
7187 fn show_edit_predictions_in_menu(&self) -> bool {
7188 match self.edit_prediction_settings {
7189 EditPredictionSettings::Disabled => false,
7190 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7191 }
7192 }
7193
7194 pub fn edit_predictions_enabled(&self) -> bool {
7195 match self.edit_prediction_settings {
7196 EditPredictionSettings::Disabled => false,
7197 EditPredictionSettings::Enabled { .. } => true,
7198 }
7199 }
7200
7201 fn edit_prediction_requires_modifier(&self) -> bool {
7202 match self.edit_prediction_settings {
7203 EditPredictionSettings::Disabled => false,
7204 EditPredictionSettings::Enabled {
7205 preview_requires_modifier,
7206 ..
7207 } => preview_requires_modifier,
7208 }
7209 }
7210
7211 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7212 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7213 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7214 self.discard_edit_prediction(false, cx);
7215 } else {
7216 let selection = self.selections.newest_anchor();
7217 let cursor = selection.head();
7218
7219 if let Some((buffer, cursor_buffer_position)) =
7220 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7221 {
7222 self.edit_prediction_settings =
7223 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7224 }
7225 }
7226 }
7227
7228 fn edit_prediction_settings_at_position(
7229 &self,
7230 buffer: &Entity<Buffer>,
7231 buffer_position: language::Anchor,
7232 cx: &App,
7233 ) -> EditPredictionSettings {
7234 if !self.mode.is_full()
7235 || !self.show_edit_predictions_override.unwrap_or(true)
7236 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7237 {
7238 return EditPredictionSettings::Disabled;
7239 }
7240
7241 let buffer = buffer.read(cx);
7242
7243 let file = buffer.file();
7244
7245 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7246 return EditPredictionSettings::Disabled;
7247 };
7248
7249 let by_provider = matches!(
7250 self.menu_edit_predictions_policy,
7251 MenuEditPredictionsPolicy::ByProvider
7252 );
7253
7254 let show_in_menu = by_provider
7255 && self
7256 .edit_prediction_provider
7257 .as_ref()
7258 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7259
7260 let preview_requires_modifier =
7261 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7262
7263 EditPredictionSettings::Enabled {
7264 show_in_menu,
7265 preview_requires_modifier,
7266 }
7267 }
7268
7269 fn should_show_edit_predictions(&self) -> bool {
7270 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7271 }
7272
7273 pub fn edit_prediction_preview_is_active(&self) -> bool {
7274 matches!(
7275 self.edit_prediction_preview,
7276 EditPredictionPreview::Active { .. }
7277 )
7278 }
7279
7280 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7281 let cursor = self.selections.newest_anchor().head();
7282 if let Some((buffer, cursor_position)) =
7283 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7284 {
7285 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7286 } else {
7287 false
7288 }
7289 }
7290
7291 pub fn supports_minimap(&self, cx: &App) -> bool {
7292 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7293 }
7294
7295 fn edit_predictions_enabled_in_buffer(
7296 &self,
7297 buffer: &Entity<Buffer>,
7298 buffer_position: language::Anchor,
7299 cx: &App,
7300 ) -> bool {
7301 maybe!({
7302 if self.read_only(cx) {
7303 return Some(false);
7304 }
7305 let provider = self.edit_prediction_provider()?;
7306 if !provider.is_enabled(buffer, buffer_position, cx) {
7307 return Some(false);
7308 }
7309 let buffer = buffer.read(cx);
7310 let Some(file) = buffer.file() else {
7311 return Some(true);
7312 };
7313 let settings = all_language_settings(Some(file), cx);
7314 Some(settings.edit_predictions_enabled_for_file(file, cx))
7315 })
7316 .unwrap_or(false)
7317 }
7318
7319 fn cycle_edit_prediction(
7320 &mut self,
7321 direction: Direction,
7322 window: &mut Window,
7323 cx: &mut Context<Self>,
7324 ) -> Option<()> {
7325 let provider = self.edit_prediction_provider()?;
7326 let cursor = self.selections.newest_anchor().head();
7327 let (buffer, cursor_buffer_position) =
7328 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7329 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7330 return None;
7331 }
7332
7333 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7334 self.update_visible_edit_prediction(window, cx);
7335
7336 Some(())
7337 }
7338
7339 pub fn show_edit_prediction(
7340 &mut self,
7341 _: &ShowEditPrediction,
7342 window: &mut Window,
7343 cx: &mut Context<Self>,
7344 ) {
7345 if !self.has_active_edit_prediction() {
7346 self.refresh_edit_prediction(false, true, window, cx);
7347 return;
7348 }
7349
7350 self.update_visible_edit_prediction(window, cx);
7351 }
7352
7353 pub fn display_cursor_names(
7354 &mut self,
7355 _: &DisplayCursorNames,
7356 window: &mut Window,
7357 cx: &mut Context<Self>,
7358 ) {
7359 self.show_cursor_names(window, cx);
7360 }
7361
7362 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7363 self.show_cursor_names = true;
7364 cx.notify();
7365 cx.spawn_in(window, async move |this, cx| {
7366 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7367 this.update(cx, |this, cx| {
7368 this.show_cursor_names = false;
7369 cx.notify()
7370 })
7371 .ok()
7372 })
7373 .detach();
7374 }
7375
7376 pub fn next_edit_prediction(
7377 &mut self,
7378 _: &NextEditPrediction,
7379 window: &mut Window,
7380 cx: &mut Context<Self>,
7381 ) {
7382 if self.has_active_edit_prediction() {
7383 self.cycle_edit_prediction(Direction::Next, window, cx);
7384 } else {
7385 let is_copilot_disabled = self
7386 .refresh_edit_prediction(false, true, window, cx)
7387 .is_none();
7388 if is_copilot_disabled {
7389 cx.propagate();
7390 }
7391 }
7392 }
7393
7394 pub fn previous_edit_prediction(
7395 &mut self,
7396 _: &PreviousEditPrediction,
7397 window: &mut Window,
7398 cx: &mut Context<Self>,
7399 ) {
7400 if self.has_active_edit_prediction() {
7401 self.cycle_edit_prediction(Direction::Prev, window, cx);
7402 } else {
7403 let is_copilot_disabled = self
7404 .refresh_edit_prediction(false, true, window, cx)
7405 .is_none();
7406 if is_copilot_disabled {
7407 cx.propagate();
7408 }
7409 }
7410 }
7411
7412 pub fn accept_edit_prediction(
7413 &mut self,
7414 _: &AcceptEditPrediction,
7415 window: &mut Window,
7416 cx: &mut Context<Self>,
7417 ) {
7418 if self.show_edit_predictions_in_menu() {
7419 self.hide_context_menu(window, cx);
7420 }
7421
7422 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7423 return;
7424 };
7425
7426 match &active_edit_prediction.completion {
7427 EditPrediction::MoveWithin { target, .. } => {
7428 let target = *target;
7429
7430 if let Some(position_map) = &self.last_position_map {
7431 if position_map
7432 .visible_row_range
7433 .contains(&target.to_display_point(&position_map.snapshot).row())
7434 || !self.edit_prediction_requires_modifier()
7435 {
7436 self.unfold_ranges(&[target..target], true, false, cx);
7437 // Note that this is also done in vim's handler of the Tab action.
7438 self.change_selections(
7439 SelectionEffects::scroll(Autoscroll::newest()),
7440 window,
7441 cx,
7442 |selections| {
7443 selections.select_anchor_ranges([target..target]);
7444 },
7445 );
7446 self.clear_row_highlights::<EditPredictionPreview>();
7447
7448 self.edit_prediction_preview
7449 .set_previous_scroll_position(None);
7450 } else {
7451 self.edit_prediction_preview
7452 .set_previous_scroll_position(Some(
7453 position_map.snapshot.scroll_anchor,
7454 ));
7455
7456 self.highlight_rows::<EditPredictionPreview>(
7457 target..target,
7458 cx.theme().colors().editor_highlighted_line_background,
7459 RowHighlightOptions {
7460 autoscroll: true,
7461 ..Default::default()
7462 },
7463 cx,
7464 );
7465 self.request_autoscroll(Autoscroll::fit(), cx);
7466 }
7467 }
7468 }
7469 EditPrediction::MoveOutside { snapshot, target } => {
7470 if let Some(workspace) = self.workspace() {
7471 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7472 .detach_and_log_err(cx);
7473 }
7474 }
7475 EditPrediction::Edit { edits, .. } => {
7476 self.report_edit_prediction_event(
7477 active_edit_prediction.completion_id.clone(),
7478 true,
7479 cx,
7480 );
7481
7482 if let Some(provider) = self.edit_prediction_provider() {
7483 provider.accept(cx);
7484 }
7485
7486 // Store the transaction ID and selections before applying the edit
7487 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7488
7489 let snapshot = self.buffer.read(cx).snapshot(cx);
7490 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7491
7492 self.buffer.update(cx, |buffer, cx| {
7493 buffer.edit(edits.iter().cloned(), None, cx)
7494 });
7495
7496 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7497 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7498 });
7499
7500 let selections = self.selections.disjoint_anchors_arc();
7501 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7502 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7503 if has_new_transaction {
7504 self.selection_history
7505 .insert_transaction(transaction_id_now, selections);
7506 }
7507 }
7508
7509 self.update_visible_edit_prediction(window, cx);
7510 if self.active_edit_prediction.is_none() {
7511 self.refresh_edit_prediction(true, true, window, cx);
7512 }
7513
7514 cx.notify();
7515 }
7516 }
7517
7518 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7519 }
7520
7521 pub fn accept_partial_edit_prediction(
7522 &mut self,
7523 _: &AcceptPartialEditPrediction,
7524 window: &mut Window,
7525 cx: &mut Context<Self>,
7526 ) {
7527 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7528 return;
7529 };
7530 if self.selections.count() != 1 {
7531 return;
7532 }
7533
7534 match &active_edit_prediction.completion {
7535 EditPrediction::MoveWithin { target, .. } => {
7536 let target = *target;
7537 self.change_selections(
7538 SelectionEffects::scroll(Autoscroll::newest()),
7539 window,
7540 cx,
7541 |selections| {
7542 selections.select_anchor_ranges([target..target]);
7543 },
7544 );
7545 }
7546 EditPrediction::MoveOutside { snapshot, target } => {
7547 if let Some(workspace) = self.workspace() {
7548 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7549 .detach_and_log_err(cx);
7550 }
7551 }
7552 EditPrediction::Edit { edits, .. } => {
7553 self.report_edit_prediction_event(
7554 active_edit_prediction.completion_id.clone(),
7555 true,
7556 cx,
7557 );
7558
7559 // Find an insertion that starts at the cursor position.
7560 let snapshot = self.buffer.read(cx).snapshot(cx);
7561 let cursor_offset = self.selections.newest::<usize>(cx).head();
7562 let insertion = edits.iter().find_map(|(range, text)| {
7563 let range = range.to_offset(&snapshot);
7564 if range.is_empty() && range.start == cursor_offset {
7565 Some(text)
7566 } else {
7567 None
7568 }
7569 });
7570
7571 if let Some(text) = insertion {
7572 let mut partial_completion = text
7573 .chars()
7574 .by_ref()
7575 .take_while(|c| c.is_alphabetic())
7576 .collect::<String>();
7577 if partial_completion.is_empty() {
7578 partial_completion = text
7579 .chars()
7580 .by_ref()
7581 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7582 .collect::<String>();
7583 }
7584
7585 cx.emit(EditorEvent::InputHandled {
7586 utf16_range_to_replace: None,
7587 text: partial_completion.clone().into(),
7588 });
7589
7590 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7591
7592 self.refresh_edit_prediction(true, true, window, cx);
7593 cx.notify();
7594 } else {
7595 self.accept_edit_prediction(&Default::default(), window, cx);
7596 }
7597 }
7598 }
7599 }
7600
7601 fn discard_edit_prediction(
7602 &mut self,
7603 should_report_edit_prediction_event: bool,
7604 cx: &mut Context<Self>,
7605 ) -> bool {
7606 if should_report_edit_prediction_event {
7607 let completion_id = self
7608 .active_edit_prediction
7609 .as_ref()
7610 .and_then(|active_completion| active_completion.completion_id.clone());
7611
7612 self.report_edit_prediction_event(completion_id, false, cx);
7613 }
7614
7615 if let Some(provider) = self.edit_prediction_provider() {
7616 provider.discard(cx);
7617 }
7618
7619 self.take_active_edit_prediction(cx)
7620 }
7621
7622 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7623 let Some(provider) = self.edit_prediction_provider() else {
7624 return;
7625 };
7626
7627 let Some((_, buffer, _)) = self
7628 .buffer
7629 .read(cx)
7630 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7631 else {
7632 return;
7633 };
7634
7635 let extension = buffer
7636 .read(cx)
7637 .file()
7638 .and_then(|file| Some(file.path().extension()?.to_string()));
7639
7640 let event_type = match accepted {
7641 true => "Edit Prediction Accepted",
7642 false => "Edit Prediction Discarded",
7643 };
7644 telemetry::event!(
7645 event_type,
7646 provider = provider.name(),
7647 prediction_id = id,
7648 suggestion_accepted = accepted,
7649 file_extension = extension,
7650 );
7651 }
7652
7653 fn open_editor_at_anchor(
7654 snapshot: &language::BufferSnapshot,
7655 target: language::Anchor,
7656 workspace: &Entity<Workspace>,
7657 window: &mut Window,
7658 cx: &mut App,
7659 ) -> Task<Result<()>> {
7660 workspace.update(cx, |workspace, cx| {
7661 let path = snapshot.file().map(|file| file.full_path(cx));
7662 let Some(path) =
7663 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7664 else {
7665 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7666 };
7667 let target = text::ToPoint::to_point(&target, snapshot);
7668 let item = workspace.open_path(path, None, true, window, cx);
7669 window.spawn(cx, async move |cx| {
7670 let Some(editor) = item.await?.downcast::<Editor>() else {
7671 return Ok(());
7672 };
7673 editor
7674 .update_in(cx, |editor, window, cx| {
7675 editor.go_to_singleton_buffer_point(target, window, cx);
7676 })
7677 .ok();
7678 anyhow::Ok(())
7679 })
7680 })
7681 }
7682
7683 pub fn has_active_edit_prediction(&self) -> bool {
7684 self.active_edit_prediction.is_some()
7685 }
7686
7687 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7688 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7689 return false;
7690 };
7691
7692 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7693 self.clear_highlights::<EditPredictionHighlight>(cx);
7694 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7695 true
7696 }
7697
7698 /// Returns true when we're displaying the edit prediction popover below the cursor
7699 /// like we are not previewing and the LSP autocomplete menu is visible
7700 /// or we are in `when_holding_modifier` mode.
7701 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7702 if self.edit_prediction_preview_is_active()
7703 || !self.show_edit_predictions_in_menu()
7704 || !self.edit_predictions_enabled()
7705 {
7706 return false;
7707 }
7708
7709 if self.has_visible_completions_menu() {
7710 return true;
7711 }
7712
7713 has_completion && self.edit_prediction_requires_modifier()
7714 }
7715
7716 fn handle_modifiers_changed(
7717 &mut self,
7718 modifiers: Modifiers,
7719 position_map: &PositionMap,
7720 window: &mut Window,
7721 cx: &mut Context<Self>,
7722 ) {
7723 if self.show_edit_predictions_in_menu() {
7724 self.update_edit_prediction_preview(&modifiers, window, cx);
7725 }
7726
7727 self.update_selection_mode(&modifiers, position_map, window, cx);
7728
7729 let mouse_position = window.mouse_position();
7730 if !position_map.text_hitbox.is_hovered(window) {
7731 return;
7732 }
7733
7734 self.update_hovered_link(
7735 position_map.point_for_position(mouse_position),
7736 &position_map.snapshot,
7737 modifiers,
7738 window,
7739 cx,
7740 )
7741 }
7742
7743 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7744 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7745 if invert {
7746 match multi_cursor_setting {
7747 MultiCursorModifier::Alt => modifiers.alt,
7748 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7749 }
7750 } else {
7751 match multi_cursor_setting {
7752 MultiCursorModifier::Alt => modifiers.secondary(),
7753 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7754 }
7755 }
7756 }
7757
7758 fn columnar_selection_mode(
7759 modifiers: &Modifiers,
7760 cx: &mut Context<Self>,
7761 ) -> Option<ColumnarMode> {
7762 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7763 if Self::multi_cursor_modifier(false, modifiers, cx) {
7764 Some(ColumnarMode::FromMouse)
7765 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7766 Some(ColumnarMode::FromSelection)
7767 } else {
7768 None
7769 }
7770 } else {
7771 None
7772 }
7773 }
7774
7775 fn update_selection_mode(
7776 &mut self,
7777 modifiers: &Modifiers,
7778 position_map: &PositionMap,
7779 window: &mut Window,
7780 cx: &mut Context<Self>,
7781 ) {
7782 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7783 return;
7784 };
7785 if self.selections.pending_anchor().is_none() {
7786 return;
7787 }
7788
7789 let mouse_position = window.mouse_position();
7790 let point_for_position = position_map.point_for_position(mouse_position);
7791 let position = point_for_position.previous_valid;
7792
7793 self.select(
7794 SelectPhase::BeginColumnar {
7795 position,
7796 reset: false,
7797 mode,
7798 goal_column: point_for_position.exact_unclipped.column(),
7799 },
7800 window,
7801 cx,
7802 );
7803 }
7804
7805 fn update_edit_prediction_preview(
7806 &mut self,
7807 modifiers: &Modifiers,
7808 window: &mut Window,
7809 cx: &mut Context<Self>,
7810 ) {
7811 let mut modifiers_held = false;
7812 if let Some(accept_keystroke) = self
7813 .accept_edit_prediction_keybind(false, window, cx)
7814 .keystroke()
7815 {
7816 modifiers_held = modifiers_held
7817 || (accept_keystroke.modifiers() == modifiers
7818 && accept_keystroke.modifiers().modified());
7819 };
7820 if let Some(accept_partial_keystroke) = self
7821 .accept_edit_prediction_keybind(true, window, cx)
7822 .keystroke()
7823 {
7824 modifiers_held = modifiers_held
7825 || (accept_partial_keystroke.modifiers() == modifiers
7826 && accept_partial_keystroke.modifiers().modified());
7827 }
7828
7829 if modifiers_held {
7830 if matches!(
7831 self.edit_prediction_preview,
7832 EditPredictionPreview::Inactive { .. }
7833 ) {
7834 self.edit_prediction_preview = EditPredictionPreview::Active {
7835 previous_scroll_position: None,
7836 since: Instant::now(),
7837 };
7838
7839 self.update_visible_edit_prediction(window, cx);
7840 cx.notify();
7841 }
7842 } else if let EditPredictionPreview::Active {
7843 previous_scroll_position,
7844 since,
7845 } = self.edit_prediction_preview
7846 {
7847 if let (Some(previous_scroll_position), Some(position_map)) =
7848 (previous_scroll_position, self.last_position_map.as_ref())
7849 {
7850 self.set_scroll_position(
7851 previous_scroll_position
7852 .scroll_position(&position_map.snapshot.display_snapshot),
7853 window,
7854 cx,
7855 );
7856 }
7857
7858 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7859 released_too_fast: since.elapsed() < Duration::from_millis(200),
7860 };
7861 self.clear_row_highlights::<EditPredictionPreview>();
7862 self.update_visible_edit_prediction(window, cx);
7863 cx.notify();
7864 }
7865 }
7866
7867 fn update_visible_edit_prediction(
7868 &mut self,
7869 _window: &mut Window,
7870 cx: &mut Context<Self>,
7871 ) -> Option<()> {
7872 if DisableAiSettings::get_global(cx).disable_ai {
7873 return None;
7874 }
7875
7876 if self.ime_transaction.is_some() {
7877 self.discard_edit_prediction(false, cx);
7878 return None;
7879 }
7880
7881 let selection = self.selections.newest_anchor();
7882 let cursor = selection.head();
7883 let multibuffer = self.buffer.read(cx).snapshot(cx);
7884 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7885 let excerpt_id = cursor.excerpt_id;
7886
7887 let show_in_menu = self.show_edit_predictions_in_menu();
7888 let completions_menu_has_precedence = !show_in_menu
7889 && (self.context_menu.borrow().is_some()
7890 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7891
7892 if completions_menu_has_precedence
7893 || !offset_selection.is_empty()
7894 || self
7895 .active_edit_prediction
7896 .as_ref()
7897 .is_some_and(|completion| {
7898 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7899 return false;
7900 };
7901 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7902 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7903 !invalidation_range.contains(&offset_selection.head())
7904 })
7905 {
7906 self.discard_edit_prediction(false, cx);
7907 return None;
7908 }
7909
7910 self.take_active_edit_prediction(cx);
7911 let Some(provider) = self.edit_prediction_provider() else {
7912 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7913 return None;
7914 };
7915
7916 let (buffer, cursor_buffer_position) =
7917 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7918
7919 self.edit_prediction_settings =
7920 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7921
7922 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7923
7924 if self.edit_prediction_indent_conflict {
7925 let cursor_point = cursor.to_point(&multibuffer);
7926
7927 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7928
7929 if let Some((_, indent)) = indents.iter().next()
7930 && indent.len == cursor_point.column
7931 {
7932 self.edit_prediction_indent_conflict = false;
7933 }
7934 }
7935
7936 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7937
7938 let (completion_id, edits, edit_preview) = match edit_prediction {
7939 edit_prediction::EditPrediction::Local {
7940 id,
7941 edits,
7942 edit_preview,
7943 } => (id, edits, edit_preview),
7944 edit_prediction::EditPrediction::Jump {
7945 id,
7946 snapshot,
7947 target,
7948 } => {
7949 self.stale_edit_prediction_in_menu = None;
7950 self.active_edit_prediction = Some(EditPredictionState {
7951 inlay_ids: vec![],
7952 completion: EditPrediction::MoveOutside { snapshot, target },
7953 completion_id: id,
7954 invalidation_range: None,
7955 });
7956 cx.notify();
7957 return Some(());
7958 }
7959 };
7960
7961 let edits = edits
7962 .into_iter()
7963 .flat_map(|(range, new_text)| {
7964 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7965 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7966 Some((start..end, new_text))
7967 })
7968 .collect::<Vec<_>>();
7969 if edits.is_empty() {
7970 return None;
7971 }
7972
7973 let first_edit_start = edits.first().unwrap().0.start;
7974 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7975 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7976
7977 let last_edit_end = edits.last().unwrap().0.end;
7978 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7979 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7980
7981 let cursor_row = cursor.to_point(&multibuffer).row;
7982
7983 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7984
7985 let mut inlay_ids = Vec::new();
7986 let invalidation_row_range;
7987 let move_invalidation_row_range = if cursor_row < edit_start_row {
7988 Some(cursor_row..edit_end_row)
7989 } else if cursor_row > edit_end_row {
7990 Some(edit_start_row..cursor_row)
7991 } else {
7992 None
7993 };
7994 let supports_jump = self
7995 .edit_prediction_provider
7996 .as_ref()
7997 .map(|provider| provider.provider.supports_jump_to_edit())
7998 .unwrap_or(true);
7999
8000 let is_move = supports_jump
8001 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8002 let completion = if is_move {
8003 invalidation_row_range =
8004 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8005 let target = first_edit_start;
8006 EditPrediction::MoveWithin { target, snapshot }
8007 } else {
8008 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8009 && !self.edit_predictions_hidden_for_vim_mode;
8010
8011 if show_completions_in_buffer {
8012 if edits
8013 .iter()
8014 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8015 {
8016 let mut inlays = Vec::new();
8017 for (range, new_text) in &edits {
8018 let inlay = Inlay::edit_prediction(
8019 post_inc(&mut self.next_inlay_id),
8020 range.start,
8021 new_text.as_str(),
8022 );
8023 inlay_ids.push(inlay.id);
8024 inlays.push(inlay);
8025 }
8026
8027 self.splice_inlays(&[], inlays, cx);
8028 } else {
8029 let background_color = cx.theme().status().deleted_background;
8030 self.highlight_text::<EditPredictionHighlight>(
8031 edits.iter().map(|(range, _)| range.clone()).collect(),
8032 HighlightStyle {
8033 background_color: Some(background_color),
8034 ..Default::default()
8035 },
8036 cx,
8037 );
8038 }
8039 }
8040
8041 invalidation_row_range = edit_start_row..edit_end_row;
8042
8043 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8044 if provider.show_tab_accept_marker() {
8045 EditDisplayMode::TabAccept
8046 } else {
8047 EditDisplayMode::Inline
8048 }
8049 } else {
8050 EditDisplayMode::DiffPopover
8051 };
8052
8053 EditPrediction::Edit {
8054 edits,
8055 edit_preview,
8056 display_mode,
8057 snapshot,
8058 }
8059 };
8060
8061 let invalidation_range = multibuffer
8062 .anchor_before(Point::new(invalidation_row_range.start, 0))
8063 ..multibuffer.anchor_after(Point::new(
8064 invalidation_row_range.end,
8065 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8066 ));
8067
8068 self.stale_edit_prediction_in_menu = None;
8069 self.active_edit_prediction = Some(EditPredictionState {
8070 inlay_ids,
8071 completion,
8072 completion_id,
8073 invalidation_range: Some(invalidation_range),
8074 });
8075
8076 cx.notify();
8077
8078 Some(())
8079 }
8080
8081 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8082 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8083 }
8084
8085 fn clear_tasks(&mut self) {
8086 self.tasks.clear()
8087 }
8088
8089 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8090 if self.tasks.insert(key, value).is_some() {
8091 // This case should hopefully be rare, but just in case...
8092 log::error!(
8093 "multiple different run targets found on a single line, only the last target will be rendered"
8094 )
8095 }
8096 }
8097
8098 /// Get all display points of breakpoints that will be rendered within editor
8099 ///
8100 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8101 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8102 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8103 fn active_breakpoints(
8104 &self,
8105 range: Range<DisplayRow>,
8106 window: &mut Window,
8107 cx: &mut Context<Self>,
8108 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8109 let mut breakpoint_display_points = HashMap::default();
8110
8111 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8112 return breakpoint_display_points;
8113 };
8114
8115 let snapshot = self.snapshot(window, cx);
8116
8117 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8118 let Some(project) = self.project() else {
8119 return breakpoint_display_points;
8120 };
8121
8122 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8123 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8124
8125 for (buffer_snapshot, range, excerpt_id) in
8126 multi_buffer_snapshot.range_to_buffer_ranges(range)
8127 {
8128 let Some(buffer) = project
8129 .read(cx)
8130 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8131 else {
8132 continue;
8133 };
8134 let breakpoints = breakpoint_store.read(cx).breakpoints(
8135 &buffer,
8136 Some(
8137 buffer_snapshot.anchor_before(range.start)
8138 ..buffer_snapshot.anchor_after(range.end),
8139 ),
8140 buffer_snapshot,
8141 cx,
8142 );
8143 for (breakpoint, state) in breakpoints {
8144 let multi_buffer_anchor =
8145 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8146 let position = multi_buffer_anchor
8147 .to_point(multi_buffer_snapshot)
8148 .to_display_point(&snapshot);
8149
8150 breakpoint_display_points.insert(
8151 position.row(),
8152 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8153 );
8154 }
8155 }
8156
8157 breakpoint_display_points
8158 }
8159
8160 fn breakpoint_context_menu(
8161 &self,
8162 anchor: Anchor,
8163 window: &mut Window,
8164 cx: &mut Context<Self>,
8165 ) -> Entity<ui::ContextMenu> {
8166 let weak_editor = cx.weak_entity();
8167 let focus_handle = self.focus_handle(cx);
8168
8169 let row = self
8170 .buffer
8171 .read(cx)
8172 .snapshot(cx)
8173 .summary_for_anchor::<Point>(&anchor)
8174 .row;
8175
8176 let breakpoint = self
8177 .breakpoint_at_row(row, window, cx)
8178 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8179
8180 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8181 "Edit Log Breakpoint"
8182 } else {
8183 "Set Log Breakpoint"
8184 };
8185
8186 let condition_breakpoint_msg = if breakpoint
8187 .as_ref()
8188 .is_some_and(|bp| bp.1.condition.is_some())
8189 {
8190 "Edit Condition Breakpoint"
8191 } else {
8192 "Set Condition Breakpoint"
8193 };
8194
8195 let hit_condition_breakpoint_msg = if breakpoint
8196 .as_ref()
8197 .is_some_and(|bp| bp.1.hit_condition.is_some())
8198 {
8199 "Edit Hit Condition Breakpoint"
8200 } else {
8201 "Set Hit Condition Breakpoint"
8202 };
8203
8204 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8205 "Unset Breakpoint"
8206 } else {
8207 "Set Breakpoint"
8208 };
8209
8210 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8211
8212 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8213 BreakpointState::Enabled => Some("Disable"),
8214 BreakpointState::Disabled => Some("Enable"),
8215 });
8216
8217 let (anchor, breakpoint) =
8218 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8219
8220 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8221 menu.on_blur_subscription(Subscription::new(|| {}))
8222 .context(focus_handle)
8223 .when(run_to_cursor, |this| {
8224 let weak_editor = weak_editor.clone();
8225 this.entry("Run to cursor", None, move |window, cx| {
8226 weak_editor
8227 .update(cx, |editor, cx| {
8228 editor.change_selections(
8229 SelectionEffects::no_scroll(),
8230 window,
8231 cx,
8232 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8233 );
8234 })
8235 .ok();
8236
8237 window.dispatch_action(Box::new(RunToCursor), cx);
8238 })
8239 .separator()
8240 })
8241 .when_some(toggle_state_msg, |this, msg| {
8242 this.entry(msg, None, {
8243 let weak_editor = weak_editor.clone();
8244 let breakpoint = breakpoint.clone();
8245 move |_window, cx| {
8246 weak_editor
8247 .update(cx, |this, cx| {
8248 this.edit_breakpoint_at_anchor(
8249 anchor,
8250 breakpoint.as_ref().clone(),
8251 BreakpointEditAction::InvertState,
8252 cx,
8253 );
8254 })
8255 .log_err();
8256 }
8257 })
8258 })
8259 .entry(set_breakpoint_msg, None, {
8260 let weak_editor = weak_editor.clone();
8261 let breakpoint = breakpoint.clone();
8262 move |_window, cx| {
8263 weak_editor
8264 .update(cx, |this, cx| {
8265 this.edit_breakpoint_at_anchor(
8266 anchor,
8267 breakpoint.as_ref().clone(),
8268 BreakpointEditAction::Toggle,
8269 cx,
8270 );
8271 })
8272 .log_err();
8273 }
8274 })
8275 .entry(log_breakpoint_msg, None, {
8276 let breakpoint = breakpoint.clone();
8277 let weak_editor = weak_editor.clone();
8278 move |window, cx| {
8279 weak_editor
8280 .update(cx, |this, cx| {
8281 this.add_edit_breakpoint_block(
8282 anchor,
8283 breakpoint.as_ref(),
8284 BreakpointPromptEditAction::Log,
8285 window,
8286 cx,
8287 );
8288 })
8289 .log_err();
8290 }
8291 })
8292 .entry(condition_breakpoint_msg, None, {
8293 let breakpoint = breakpoint.clone();
8294 let weak_editor = weak_editor.clone();
8295 move |window, cx| {
8296 weak_editor
8297 .update(cx, |this, cx| {
8298 this.add_edit_breakpoint_block(
8299 anchor,
8300 breakpoint.as_ref(),
8301 BreakpointPromptEditAction::Condition,
8302 window,
8303 cx,
8304 );
8305 })
8306 .log_err();
8307 }
8308 })
8309 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8310 weak_editor
8311 .update(cx, |this, cx| {
8312 this.add_edit_breakpoint_block(
8313 anchor,
8314 breakpoint.as_ref(),
8315 BreakpointPromptEditAction::HitCondition,
8316 window,
8317 cx,
8318 );
8319 })
8320 .log_err();
8321 })
8322 })
8323 }
8324
8325 fn render_breakpoint(
8326 &self,
8327 position: Anchor,
8328 row: DisplayRow,
8329 breakpoint: &Breakpoint,
8330 state: Option<BreakpointSessionState>,
8331 cx: &mut Context<Self>,
8332 ) -> IconButton {
8333 let is_rejected = state.is_some_and(|s| !s.verified);
8334 // Is it a breakpoint that shows up when hovering over gutter?
8335 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8336 (false, false),
8337 |PhantomBreakpointIndicator {
8338 is_active,
8339 display_row,
8340 collides_with_existing_breakpoint,
8341 }| {
8342 (
8343 is_active && display_row == row,
8344 collides_with_existing_breakpoint,
8345 )
8346 },
8347 );
8348
8349 let (color, icon) = {
8350 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8351 (false, false) => ui::IconName::DebugBreakpoint,
8352 (true, false) => ui::IconName::DebugLogBreakpoint,
8353 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8354 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8355 };
8356
8357 let color = if is_phantom {
8358 Color::Hint
8359 } else if is_rejected {
8360 Color::Disabled
8361 } else {
8362 Color::Debugger
8363 };
8364
8365 (color, icon)
8366 };
8367
8368 let breakpoint = Arc::from(breakpoint.clone());
8369
8370 let alt_as_text = gpui::Keystroke {
8371 modifiers: Modifiers::secondary_key(),
8372 ..Default::default()
8373 };
8374 let primary_action_text = if breakpoint.is_disabled() {
8375 "Enable breakpoint"
8376 } else if is_phantom && !collides_with_existing {
8377 "Set breakpoint"
8378 } else {
8379 "Unset breakpoint"
8380 };
8381 let focus_handle = self.focus_handle.clone();
8382
8383 let meta = if is_rejected {
8384 SharedString::from("No executable code is associated with this line.")
8385 } else if collides_with_existing && !breakpoint.is_disabled() {
8386 SharedString::from(format!(
8387 "{alt_as_text}-click to disable,\nright-click for more options."
8388 ))
8389 } else {
8390 SharedString::from("Right-click for more options.")
8391 };
8392 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8393 .icon_size(IconSize::XSmall)
8394 .size(ui::ButtonSize::None)
8395 .when(is_rejected, |this| {
8396 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8397 })
8398 .icon_color(color)
8399 .style(ButtonStyle::Transparent)
8400 .on_click(cx.listener({
8401 move |editor, event: &ClickEvent, window, cx| {
8402 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8403 BreakpointEditAction::InvertState
8404 } else {
8405 BreakpointEditAction::Toggle
8406 };
8407
8408 window.focus(&editor.focus_handle(cx));
8409 editor.edit_breakpoint_at_anchor(
8410 position,
8411 breakpoint.as_ref().clone(),
8412 edit_action,
8413 cx,
8414 );
8415 }
8416 }))
8417 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8418 editor.set_breakpoint_context_menu(
8419 row,
8420 Some(position),
8421 event.position(),
8422 window,
8423 cx,
8424 );
8425 }))
8426 .tooltip(move |window, cx| {
8427 Tooltip::with_meta_in(
8428 primary_action_text,
8429 Some(&ToggleBreakpoint),
8430 meta.clone(),
8431 &focus_handle,
8432 window,
8433 cx,
8434 )
8435 })
8436 }
8437
8438 fn build_tasks_context(
8439 project: &Entity<Project>,
8440 buffer: &Entity<Buffer>,
8441 buffer_row: u32,
8442 tasks: &Arc<RunnableTasks>,
8443 cx: &mut Context<Self>,
8444 ) -> Task<Option<task::TaskContext>> {
8445 let position = Point::new(buffer_row, tasks.column);
8446 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8447 let location = Location {
8448 buffer: buffer.clone(),
8449 range: range_start..range_start,
8450 };
8451 // Fill in the environmental variables from the tree-sitter captures
8452 let mut captured_task_variables = TaskVariables::default();
8453 for (capture_name, value) in tasks.extra_variables.clone() {
8454 captured_task_variables.insert(
8455 task::VariableName::Custom(capture_name.into()),
8456 value.clone(),
8457 );
8458 }
8459 project.update(cx, |project, cx| {
8460 project.task_store().update(cx, |task_store, cx| {
8461 task_store.task_context_for_location(captured_task_variables, location, cx)
8462 })
8463 })
8464 }
8465
8466 pub fn spawn_nearest_task(
8467 &mut self,
8468 action: &SpawnNearestTask,
8469 window: &mut Window,
8470 cx: &mut Context<Self>,
8471 ) {
8472 let Some((workspace, _)) = self.workspace.clone() else {
8473 return;
8474 };
8475 let Some(project) = self.project.clone() else {
8476 return;
8477 };
8478
8479 // Try to find a closest, enclosing node using tree-sitter that has a task
8480 let Some((buffer, buffer_row, tasks)) = self
8481 .find_enclosing_node_task(cx)
8482 // Or find the task that's closest in row-distance.
8483 .or_else(|| self.find_closest_task(cx))
8484 else {
8485 return;
8486 };
8487
8488 let reveal_strategy = action.reveal;
8489 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8490 cx.spawn_in(window, async move |_, cx| {
8491 let context = task_context.await?;
8492 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8493
8494 let resolved = &mut resolved_task.resolved;
8495 resolved.reveal = reveal_strategy;
8496
8497 workspace
8498 .update_in(cx, |workspace, window, cx| {
8499 workspace.schedule_resolved_task(
8500 task_source_kind,
8501 resolved_task,
8502 false,
8503 window,
8504 cx,
8505 );
8506 })
8507 .ok()
8508 })
8509 .detach();
8510 }
8511
8512 fn find_closest_task(
8513 &mut self,
8514 cx: &mut Context<Self>,
8515 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8516 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8517
8518 let ((buffer_id, row), tasks) = self
8519 .tasks
8520 .iter()
8521 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8522
8523 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8524 let tasks = Arc::new(tasks.to_owned());
8525 Some((buffer, *row, tasks))
8526 }
8527
8528 fn find_enclosing_node_task(
8529 &mut self,
8530 cx: &mut Context<Self>,
8531 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8532 let snapshot = self.buffer.read(cx).snapshot(cx);
8533 let offset = self.selections.newest::<usize>(cx).head();
8534 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8535 let buffer_id = excerpt.buffer().remote_id();
8536
8537 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8538 let mut cursor = layer.node().walk();
8539
8540 while cursor.goto_first_child_for_byte(offset).is_some() {
8541 if cursor.node().end_byte() == offset {
8542 cursor.goto_next_sibling();
8543 }
8544 }
8545
8546 // Ascend to the smallest ancestor that contains the range and has a task.
8547 loop {
8548 let node = cursor.node();
8549 let node_range = node.byte_range();
8550 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8551
8552 // Check if this node contains our offset
8553 if node_range.start <= offset && node_range.end >= offset {
8554 // If it contains offset, check for task
8555 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8556 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8557 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8558 }
8559 }
8560
8561 if !cursor.goto_parent() {
8562 break;
8563 }
8564 }
8565 None
8566 }
8567
8568 fn render_run_indicator(
8569 &self,
8570 _style: &EditorStyle,
8571 is_active: bool,
8572 row: DisplayRow,
8573 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8574 cx: &mut Context<Self>,
8575 ) -> IconButton {
8576 let color = Color::Muted;
8577 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8578
8579 IconButton::new(
8580 ("run_indicator", row.0 as usize),
8581 ui::IconName::PlayOutlined,
8582 )
8583 .shape(ui::IconButtonShape::Square)
8584 .icon_size(IconSize::XSmall)
8585 .icon_color(color)
8586 .toggle_state(is_active)
8587 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8588 let quick_launch = match e {
8589 ClickEvent::Keyboard(_) => true,
8590 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8591 };
8592
8593 window.focus(&editor.focus_handle(cx));
8594 editor.toggle_code_actions(
8595 &ToggleCodeActions {
8596 deployed_from: Some(CodeActionSource::RunMenu(row)),
8597 quick_launch,
8598 },
8599 window,
8600 cx,
8601 );
8602 }))
8603 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8604 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8605 }))
8606 }
8607
8608 pub fn context_menu_visible(&self) -> bool {
8609 !self.edit_prediction_preview_is_active()
8610 && self
8611 .context_menu
8612 .borrow()
8613 .as_ref()
8614 .is_some_and(|menu| menu.visible())
8615 }
8616
8617 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8618 self.context_menu
8619 .borrow()
8620 .as_ref()
8621 .map(|menu| menu.origin())
8622 }
8623
8624 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8625 self.context_menu_options = Some(options);
8626 }
8627
8628 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8629 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8630
8631 fn render_edit_prediction_popover(
8632 &mut self,
8633 text_bounds: &Bounds<Pixels>,
8634 content_origin: gpui::Point<Pixels>,
8635 right_margin: Pixels,
8636 editor_snapshot: &EditorSnapshot,
8637 visible_row_range: Range<DisplayRow>,
8638 scroll_top: ScrollOffset,
8639 scroll_bottom: ScrollOffset,
8640 line_layouts: &[LineWithInvisibles],
8641 line_height: Pixels,
8642 scroll_position: gpui::Point<ScrollOffset>,
8643 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8644 newest_selection_head: Option<DisplayPoint>,
8645 editor_width: Pixels,
8646 style: &EditorStyle,
8647 window: &mut Window,
8648 cx: &mut App,
8649 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8650 if self.mode().is_minimap() {
8651 return None;
8652 }
8653 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8654
8655 if self.edit_prediction_visible_in_cursor_popover(true) {
8656 return None;
8657 }
8658
8659 match &active_edit_prediction.completion {
8660 EditPrediction::MoveWithin { target, .. } => {
8661 let target_display_point = target.to_display_point(editor_snapshot);
8662
8663 if self.edit_prediction_requires_modifier() {
8664 if !self.edit_prediction_preview_is_active() {
8665 return None;
8666 }
8667
8668 self.render_edit_prediction_modifier_jump_popover(
8669 text_bounds,
8670 content_origin,
8671 visible_row_range,
8672 line_layouts,
8673 line_height,
8674 scroll_pixel_position,
8675 newest_selection_head,
8676 target_display_point,
8677 window,
8678 cx,
8679 )
8680 } else {
8681 self.render_edit_prediction_eager_jump_popover(
8682 text_bounds,
8683 content_origin,
8684 editor_snapshot,
8685 visible_row_range,
8686 scroll_top,
8687 scroll_bottom,
8688 line_height,
8689 scroll_pixel_position,
8690 target_display_point,
8691 editor_width,
8692 window,
8693 cx,
8694 )
8695 }
8696 }
8697 EditPrediction::Edit {
8698 display_mode: EditDisplayMode::Inline,
8699 ..
8700 } => None,
8701 EditPrediction::Edit {
8702 display_mode: EditDisplayMode::TabAccept,
8703 edits,
8704 ..
8705 } => {
8706 let range = &edits.first()?.0;
8707 let target_display_point = range.end.to_display_point(editor_snapshot);
8708
8709 self.render_edit_prediction_end_of_line_popover(
8710 "Accept",
8711 editor_snapshot,
8712 visible_row_range,
8713 target_display_point,
8714 line_height,
8715 scroll_pixel_position,
8716 content_origin,
8717 editor_width,
8718 window,
8719 cx,
8720 )
8721 }
8722 EditPrediction::Edit {
8723 edits,
8724 edit_preview,
8725 display_mode: EditDisplayMode::DiffPopover,
8726 snapshot,
8727 } => self.render_edit_prediction_diff_popover(
8728 text_bounds,
8729 content_origin,
8730 right_margin,
8731 editor_snapshot,
8732 visible_row_range,
8733 line_layouts,
8734 line_height,
8735 scroll_position,
8736 scroll_pixel_position,
8737 newest_selection_head,
8738 editor_width,
8739 style,
8740 edits,
8741 edit_preview,
8742 snapshot,
8743 window,
8744 cx,
8745 ),
8746 EditPrediction::MoveOutside { snapshot, .. } => {
8747 let file_name = snapshot
8748 .file()
8749 .map(|file| file.file_name(cx))
8750 .unwrap_or("untitled");
8751 let mut element = self
8752 .render_edit_prediction_line_popover(
8753 format!("Jump to {file_name}"),
8754 Some(IconName::ZedPredict),
8755 window,
8756 cx,
8757 )
8758 .into_any();
8759
8760 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8761 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8762 let origin_y = text_bounds.size.height - size.height - px(30.);
8763 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8764 element.prepaint_at(origin, window, cx);
8765
8766 Some((element, origin))
8767 }
8768 }
8769 }
8770
8771 fn render_edit_prediction_modifier_jump_popover(
8772 &mut self,
8773 text_bounds: &Bounds<Pixels>,
8774 content_origin: gpui::Point<Pixels>,
8775 visible_row_range: Range<DisplayRow>,
8776 line_layouts: &[LineWithInvisibles],
8777 line_height: Pixels,
8778 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8779 newest_selection_head: Option<DisplayPoint>,
8780 target_display_point: DisplayPoint,
8781 window: &mut Window,
8782 cx: &mut App,
8783 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8784 let scrolled_content_origin =
8785 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8786
8787 const SCROLL_PADDING_Y: Pixels = px(12.);
8788
8789 if target_display_point.row() < visible_row_range.start {
8790 return self.render_edit_prediction_scroll_popover(
8791 |_| SCROLL_PADDING_Y,
8792 IconName::ArrowUp,
8793 visible_row_range,
8794 line_layouts,
8795 newest_selection_head,
8796 scrolled_content_origin,
8797 window,
8798 cx,
8799 );
8800 } else if target_display_point.row() >= visible_row_range.end {
8801 return self.render_edit_prediction_scroll_popover(
8802 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8803 IconName::ArrowDown,
8804 visible_row_range,
8805 line_layouts,
8806 newest_selection_head,
8807 scrolled_content_origin,
8808 window,
8809 cx,
8810 );
8811 }
8812
8813 const POLE_WIDTH: Pixels = px(2.);
8814
8815 let line_layout =
8816 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8817 let target_column = target_display_point.column() as usize;
8818
8819 let target_x = line_layout.x_for_index(target_column);
8820 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8821 - scroll_pixel_position.y;
8822
8823 let flag_on_right = target_x < text_bounds.size.width / 2.;
8824
8825 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8826 border_color.l += 0.001;
8827
8828 let mut element = v_flex()
8829 .items_end()
8830 .when(flag_on_right, |el| el.items_start())
8831 .child(if flag_on_right {
8832 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8833 .rounded_bl(px(0.))
8834 .rounded_tl(px(0.))
8835 .border_l_2()
8836 .border_color(border_color)
8837 } else {
8838 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8839 .rounded_br(px(0.))
8840 .rounded_tr(px(0.))
8841 .border_r_2()
8842 .border_color(border_color)
8843 })
8844 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8845 .into_any();
8846
8847 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8848
8849 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8850 - point(
8851 if flag_on_right {
8852 POLE_WIDTH
8853 } else {
8854 size.width - POLE_WIDTH
8855 },
8856 size.height - line_height,
8857 );
8858
8859 origin.x = origin.x.max(content_origin.x);
8860
8861 element.prepaint_at(origin, window, cx);
8862
8863 Some((element, origin))
8864 }
8865
8866 fn render_edit_prediction_scroll_popover(
8867 &mut self,
8868 to_y: impl Fn(Size<Pixels>) -> Pixels,
8869 scroll_icon: IconName,
8870 visible_row_range: Range<DisplayRow>,
8871 line_layouts: &[LineWithInvisibles],
8872 newest_selection_head: Option<DisplayPoint>,
8873 scrolled_content_origin: gpui::Point<Pixels>,
8874 window: &mut Window,
8875 cx: &mut App,
8876 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8877 let mut element = self
8878 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8879 .into_any();
8880
8881 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8882
8883 let cursor = newest_selection_head?;
8884 let cursor_row_layout =
8885 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8886 let cursor_column = cursor.column() as usize;
8887
8888 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8889
8890 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8891
8892 element.prepaint_at(origin, window, cx);
8893 Some((element, origin))
8894 }
8895
8896 fn render_edit_prediction_eager_jump_popover(
8897 &mut self,
8898 text_bounds: &Bounds<Pixels>,
8899 content_origin: gpui::Point<Pixels>,
8900 editor_snapshot: &EditorSnapshot,
8901 visible_row_range: Range<DisplayRow>,
8902 scroll_top: ScrollOffset,
8903 scroll_bottom: ScrollOffset,
8904 line_height: Pixels,
8905 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8906 target_display_point: DisplayPoint,
8907 editor_width: Pixels,
8908 window: &mut Window,
8909 cx: &mut App,
8910 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8911 if target_display_point.row().as_f64() < scroll_top {
8912 let mut element = self
8913 .render_edit_prediction_line_popover(
8914 "Jump to Edit",
8915 Some(IconName::ArrowUp),
8916 window,
8917 cx,
8918 )
8919 .into_any();
8920
8921 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8922 let offset = point(
8923 (text_bounds.size.width - size.width) / 2.,
8924 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8925 );
8926
8927 let origin = text_bounds.origin + offset;
8928 element.prepaint_at(origin, window, cx);
8929 Some((element, origin))
8930 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8931 let mut element = self
8932 .render_edit_prediction_line_popover(
8933 "Jump to Edit",
8934 Some(IconName::ArrowDown),
8935 window,
8936 cx,
8937 )
8938 .into_any();
8939
8940 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8941 let offset = point(
8942 (text_bounds.size.width - size.width) / 2.,
8943 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8944 );
8945
8946 let origin = text_bounds.origin + offset;
8947 element.prepaint_at(origin, window, cx);
8948 Some((element, origin))
8949 } else {
8950 self.render_edit_prediction_end_of_line_popover(
8951 "Jump to Edit",
8952 editor_snapshot,
8953 visible_row_range,
8954 target_display_point,
8955 line_height,
8956 scroll_pixel_position,
8957 content_origin,
8958 editor_width,
8959 window,
8960 cx,
8961 )
8962 }
8963 }
8964
8965 fn render_edit_prediction_end_of_line_popover(
8966 self: &mut Editor,
8967 label: &'static str,
8968 editor_snapshot: &EditorSnapshot,
8969 visible_row_range: Range<DisplayRow>,
8970 target_display_point: DisplayPoint,
8971 line_height: Pixels,
8972 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8973 content_origin: gpui::Point<Pixels>,
8974 editor_width: Pixels,
8975 window: &mut Window,
8976 cx: &mut App,
8977 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8978 let target_line_end = DisplayPoint::new(
8979 target_display_point.row(),
8980 editor_snapshot.line_len(target_display_point.row()),
8981 );
8982
8983 let mut element = self
8984 .render_edit_prediction_line_popover(label, None, window, cx)
8985 .into_any();
8986
8987 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8988
8989 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8990
8991 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8992 let mut origin = start_point
8993 + line_origin
8994 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8995 origin.x = origin.x.max(content_origin.x);
8996
8997 let max_x = content_origin.x + editor_width - size.width;
8998
8999 if origin.x > max_x {
9000 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9001
9002 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9003 origin.y += offset;
9004 IconName::ArrowUp
9005 } else {
9006 origin.y -= offset;
9007 IconName::ArrowDown
9008 };
9009
9010 element = self
9011 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9012 .into_any();
9013
9014 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9015
9016 origin.x = content_origin.x + editor_width - size.width - px(2.);
9017 }
9018
9019 element.prepaint_at(origin, window, cx);
9020 Some((element, origin))
9021 }
9022
9023 fn render_edit_prediction_diff_popover(
9024 self: &Editor,
9025 text_bounds: &Bounds<Pixels>,
9026 content_origin: gpui::Point<Pixels>,
9027 right_margin: Pixels,
9028 editor_snapshot: &EditorSnapshot,
9029 visible_row_range: Range<DisplayRow>,
9030 line_layouts: &[LineWithInvisibles],
9031 line_height: Pixels,
9032 scroll_position: gpui::Point<ScrollOffset>,
9033 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9034 newest_selection_head: Option<DisplayPoint>,
9035 editor_width: Pixels,
9036 style: &EditorStyle,
9037 edits: &Vec<(Range<Anchor>, String)>,
9038 edit_preview: &Option<language::EditPreview>,
9039 snapshot: &language::BufferSnapshot,
9040 window: &mut Window,
9041 cx: &mut App,
9042 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9043 let edit_start = edits
9044 .first()
9045 .unwrap()
9046 .0
9047 .start
9048 .to_display_point(editor_snapshot);
9049 let edit_end = edits
9050 .last()
9051 .unwrap()
9052 .0
9053 .end
9054 .to_display_point(editor_snapshot);
9055
9056 let is_visible = visible_row_range.contains(&edit_start.row())
9057 || visible_row_range.contains(&edit_end.row());
9058 if !is_visible {
9059 return None;
9060 }
9061
9062 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9063 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9064 } else {
9065 // Fallback for providers without edit_preview
9066 crate::edit_prediction_fallback_text(edits, cx)
9067 };
9068
9069 let styled_text = highlighted_edits.to_styled_text(&style.text);
9070 let line_count = highlighted_edits.text.lines().count();
9071
9072 const BORDER_WIDTH: Pixels = px(1.);
9073
9074 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9075 let has_keybind = keybind.is_some();
9076
9077 let mut element = h_flex()
9078 .items_start()
9079 .child(
9080 h_flex()
9081 .bg(cx.theme().colors().editor_background)
9082 .border(BORDER_WIDTH)
9083 .shadow_xs()
9084 .border_color(cx.theme().colors().border)
9085 .rounded_l_lg()
9086 .when(line_count > 1, |el| el.rounded_br_lg())
9087 .pr_1()
9088 .child(styled_text),
9089 )
9090 .child(
9091 h_flex()
9092 .h(line_height + BORDER_WIDTH * 2.)
9093 .px_1p5()
9094 .gap_1()
9095 // Workaround: For some reason, there's a gap if we don't do this
9096 .ml(-BORDER_WIDTH)
9097 .shadow(vec![gpui::BoxShadow {
9098 color: gpui::black().opacity(0.05),
9099 offset: point(px(1.), px(1.)),
9100 blur_radius: px(2.),
9101 spread_radius: px(0.),
9102 }])
9103 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9104 .border(BORDER_WIDTH)
9105 .border_color(cx.theme().colors().border)
9106 .rounded_r_lg()
9107 .id("edit_prediction_diff_popover_keybind")
9108 .when(!has_keybind, |el| {
9109 let status_colors = cx.theme().status();
9110
9111 el.bg(status_colors.error_background)
9112 .border_color(status_colors.error.opacity(0.6))
9113 .child(Icon::new(IconName::Info).color(Color::Error))
9114 .cursor_default()
9115 .hoverable_tooltip(move |_window, cx| {
9116 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9117 })
9118 })
9119 .children(keybind),
9120 )
9121 .into_any();
9122
9123 let longest_row =
9124 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9125 let longest_line_width = if visible_row_range.contains(&longest_row) {
9126 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9127 } else {
9128 layout_line(
9129 longest_row,
9130 editor_snapshot,
9131 style,
9132 editor_width,
9133 |_| false,
9134 window,
9135 cx,
9136 )
9137 .width
9138 };
9139
9140 let viewport_bounds =
9141 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9142 right: -right_margin,
9143 ..Default::default()
9144 });
9145
9146 let x_after_longest = Pixels::from(
9147 ScrollPixelOffset::from(
9148 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9149 ) - scroll_pixel_position.x,
9150 );
9151
9152 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9153
9154 // Fully visible if it can be displayed within the window (allow overlapping other
9155 // panes). However, this is only allowed if the popover starts within text_bounds.
9156 let can_position_to_the_right = x_after_longest < text_bounds.right()
9157 && x_after_longest + element_bounds.width < viewport_bounds.right();
9158
9159 let mut origin = if can_position_to_the_right {
9160 point(
9161 x_after_longest,
9162 text_bounds.origin.y
9163 + Pixels::from(
9164 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9165 - scroll_pixel_position.y,
9166 ),
9167 )
9168 } else {
9169 let cursor_row = newest_selection_head.map(|head| head.row());
9170 let above_edit = edit_start
9171 .row()
9172 .0
9173 .checked_sub(line_count as u32)
9174 .map(DisplayRow);
9175 let below_edit = Some(edit_end.row() + 1);
9176 let above_cursor =
9177 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9178 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9179
9180 // Place the edit popover adjacent to the edit if there is a location
9181 // available that is onscreen and does not obscure the cursor. Otherwise,
9182 // place it adjacent to the cursor.
9183 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9184 .into_iter()
9185 .flatten()
9186 .find(|&start_row| {
9187 let end_row = start_row + line_count as u32;
9188 visible_row_range.contains(&start_row)
9189 && visible_row_range.contains(&end_row)
9190 && cursor_row
9191 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9192 })?;
9193
9194 content_origin
9195 + point(
9196 Pixels::from(-scroll_pixel_position.x),
9197 Pixels::from(
9198 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9199 ),
9200 )
9201 };
9202
9203 origin.x -= BORDER_WIDTH;
9204
9205 window.defer_draw(element, origin, 1);
9206
9207 // Do not return an element, since it will already be drawn due to defer_draw.
9208 None
9209 }
9210
9211 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9212 px(30.)
9213 }
9214
9215 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9216 if self.read_only(cx) {
9217 cx.theme().players().read_only()
9218 } else {
9219 self.style.as_ref().unwrap().local_player
9220 }
9221 }
9222
9223 fn render_edit_prediction_accept_keybind(
9224 &self,
9225 window: &mut Window,
9226 cx: &App,
9227 ) -> Option<AnyElement> {
9228 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9229 let accept_keystroke = accept_binding.keystroke()?;
9230
9231 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9232
9233 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9234 Color::Accent
9235 } else {
9236 Color::Muted
9237 };
9238
9239 h_flex()
9240 .px_0p5()
9241 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9242 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9243 .text_size(TextSize::XSmall.rems(cx))
9244 .child(h_flex().children(ui::render_modifiers(
9245 accept_keystroke.modifiers(),
9246 PlatformStyle::platform(),
9247 Some(modifiers_color),
9248 Some(IconSize::XSmall.rems().into()),
9249 true,
9250 )))
9251 .when(is_platform_style_mac, |parent| {
9252 parent.child(accept_keystroke.key().to_string())
9253 })
9254 .when(!is_platform_style_mac, |parent| {
9255 parent.child(
9256 Key::new(
9257 util::capitalize(accept_keystroke.key()),
9258 Some(Color::Default),
9259 )
9260 .size(Some(IconSize::XSmall.rems().into())),
9261 )
9262 })
9263 .into_any()
9264 .into()
9265 }
9266
9267 fn render_edit_prediction_line_popover(
9268 &self,
9269 label: impl Into<SharedString>,
9270 icon: Option<IconName>,
9271 window: &mut Window,
9272 cx: &App,
9273 ) -> Stateful<Div> {
9274 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9275
9276 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9277 let has_keybind = keybind.is_some();
9278
9279 h_flex()
9280 .id("ep-line-popover")
9281 .py_0p5()
9282 .pl_1()
9283 .pr(padding_right)
9284 .gap_1()
9285 .rounded_md()
9286 .border_1()
9287 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9288 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9289 .shadow_xs()
9290 .when(!has_keybind, |el| {
9291 let status_colors = cx.theme().status();
9292
9293 el.bg(status_colors.error_background)
9294 .border_color(status_colors.error.opacity(0.6))
9295 .pl_2()
9296 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9297 .cursor_default()
9298 .hoverable_tooltip(move |_window, cx| {
9299 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9300 })
9301 })
9302 .children(keybind)
9303 .child(
9304 Label::new(label)
9305 .size(LabelSize::Small)
9306 .when(!has_keybind, |el| {
9307 el.color(cx.theme().status().error.into()).strikethrough()
9308 }),
9309 )
9310 .when(!has_keybind, |el| {
9311 el.child(
9312 h_flex().ml_1().child(
9313 Icon::new(IconName::Info)
9314 .size(IconSize::Small)
9315 .color(cx.theme().status().error.into()),
9316 ),
9317 )
9318 })
9319 .when_some(icon, |element, icon| {
9320 element.child(
9321 div()
9322 .mt(px(1.5))
9323 .child(Icon::new(icon).size(IconSize::Small)),
9324 )
9325 })
9326 }
9327
9328 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9329 let accent_color = cx.theme().colors().text_accent;
9330 let editor_bg_color = cx.theme().colors().editor_background;
9331 editor_bg_color.blend(accent_color.opacity(0.1))
9332 }
9333
9334 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9335 let accent_color = cx.theme().colors().text_accent;
9336 let editor_bg_color = cx.theme().colors().editor_background;
9337 editor_bg_color.blend(accent_color.opacity(0.6))
9338 }
9339 fn get_prediction_provider_icon_name(
9340 provider: &Option<RegisteredEditPredictionProvider>,
9341 ) -> IconName {
9342 match provider {
9343 Some(provider) => match provider.provider.name() {
9344 "copilot" => IconName::Copilot,
9345 "supermaven" => IconName::Supermaven,
9346 _ => IconName::ZedPredict,
9347 },
9348 None => IconName::ZedPredict,
9349 }
9350 }
9351
9352 fn render_edit_prediction_cursor_popover(
9353 &self,
9354 min_width: Pixels,
9355 max_width: Pixels,
9356 cursor_point: Point,
9357 style: &EditorStyle,
9358 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9359 _window: &Window,
9360 cx: &mut Context<Editor>,
9361 ) -> Option<AnyElement> {
9362 let provider = self.edit_prediction_provider.as_ref()?;
9363 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9364
9365 let is_refreshing = provider.provider.is_refreshing(cx);
9366
9367 fn pending_completion_container(icon: IconName) -> Div {
9368 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9369 }
9370
9371 let completion = match &self.active_edit_prediction {
9372 Some(prediction) => {
9373 if !self.has_visible_completions_menu() {
9374 const RADIUS: Pixels = px(6.);
9375 const BORDER_WIDTH: Pixels = px(1.);
9376
9377 return Some(
9378 h_flex()
9379 .elevation_2(cx)
9380 .border(BORDER_WIDTH)
9381 .border_color(cx.theme().colors().border)
9382 .when(accept_keystroke.is_none(), |el| {
9383 el.border_color(cx.theme().status().error)
9384 })
9385 .rounded(RADIUS)
9386 .rounded_tl(px(0.))
9387 .overflow_hidden()
9388 .child(div().px_1p5().child(match &prediction.completion {
9389 EditPrediction::MoveWithin { target, snapshot } => {
9390 use text::ToPoint as _;
9391 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9392 {
9393 Icon::new(IconName::ZedPredictDown)
9394 } else {
9395 Icon::new(IconName::ZedPredictUp)
9396 }
9397 }
9398 EditPrediction::MoveOutside { .. } => {
9399 // TODO [zeta2] custom icon for external jump?
9400 Icon::new(provider_icon)
9401 }
9402 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9403 }))
9404 .child(
9405 h_flex()
9406 .gap_1()
9407 .py_1()
9408 .px_2()
9409 .rounded_r(RADIUS - BORDER_WIDTH)
9410 .border_l_1()
9411 .border_color(cx.theme().colors().border)
9412 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9413 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9414 el.child(
9415 Label::new("Hold")
9416 .size(LabelSize::Small)
9417 .when(accept_keystroke.is_none(), |el| {
9418 el.strikethrough()
9419 })
9420 .line_height_style(LineHeightStyle::UiLabel),
9421 )
9422 })
9423 .id("edit_prediction_cursor_popover_keybind")
9424 .when(accept_keystroke.is_none(), |el| {
9425 let status_colors = cx.theme().status();
9426
9427 el.bg(status_colors.error_background)
9428 .border_color(status_colors.error.opacity(0.6))
9429 .child(Icon::new(IconName::Info).color(Color::Error))
9430 .cursor_default()
9431 .hoverable_tooltip(move |_window, cx| {
9432 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9433 .into()
9434 })
9435 })
9436 .when_some(
9437 accept_keystroke.as_ref(),
9438 |el, accept_keystroke| {
9439 el.child(h_flex().children(ui::render_modifiers(
9440 accept_keystroke.modifiers(),
9441 PlatformStyle::platform(),
9442 Some(Color::Default),
9443 Some(IconSize::XSmall.rems().into()),
9444 false,
9445 )))
9446 },
9447 ),
9448 )
9449 .into_any(),
9450 );
9451 }
9452
9453 self.render_edit_prediction_cursor_popover_preview(
9454 prediction,
9455 cursor_point,
9456 style,
9457 cx,
9458 )?
9459 }
9460
9461 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9462 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9463 stale_completion,
9464 cursor_point,
9465 style,
9466 cx,
9467 )?,
9468
9469 None => pending_completion_container(provider_icon)
9470 .child(Label::new("...").size(LabelSize::Small)),
9471 },
9472
9473 None => pending_completion_container(provider_icon)
9474 .child(Label::new("...").size(LabelSize::Small)),
9475 };
9476
9477 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9478 completion
9479 .with_animation(
9480 "loading-completion",
9481 Animation::new(Duration::from_secs(2))
9482 .repeat()
9483 .with_easing(pulsating_between(0.4, 0.8)),
9484 |label, delta| label.opacity(delta),
9485 )
9486 .into_any_element()
9487 } else {
9488 completion.into_any_element()
9489 };
9490
9491 let has_completion = self.active_edit_prediction.is_some();
9492
9493 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9494 Some(
9495 h_flex()
9496 .min_w(min_width)
9497 .max_w(max_width)
9498 .flex_1()
9499 .elevation_2(cx)
9500 .border_color(cx.theme().colors().border)
9501 .child(
9502 div()
9503 .flex_1()
9504 .py_1()
9505 .px_2()
9506 .overflow_hidden()
9507 .child(completion),
9508 )
9509 .when_some(accept_keystroke, |el, accept_keystroke| {
9510 if !accept_keystroke.modifiers().modified() {
9511 return el;
9512 }
9513
9514 el.child(
9515 h_flex()
9516 .h_full()
9517 .border_l_1()
9518 .rounded_r_lg()
9519 .border_color(cx.theme().colors().border)
9520 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9521 .gap_1()
9522 .py_1()
9523 .px_2()
9524 .child(
9525 h_flex()
9526 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9527 .when(is_platform_style_mac, |parent| parent.gap_1())
9528 .child(h_flex().children(ui::render_modifiers(
9529 accept_keystroke.modifiers(),
9530 PlatformStyle::platform(),
9531 Some(if !has_completion {
9532 Color::Muted
9533 } else {
9534 Color::Default
9535 }),
9536 None,
9537 false,
9538 ))),
9539 )
9540 .child(Label::new("Preview").into_any_element())
9541 .opacity(if has_completion { 1.0 } else { 0.4 }),
9542 )
9543 })
9544 .into_any(),
9545 )
9546 }
9547
9548 fn render_edit_prediction_cursor_popover_preview(
9549 &self,
9550 completion: &EditPredictionState,
9551 cursor_point: Point,
9552 style: &EditorStyle,
9553 cx: &mut Context<Editor>,
9554 ) -> Option<Div> {
9555 use text::ToPoint as _;
9556
9557 fn render_relative_row_jump(
9558 prefix: impl Into<String>,
9559 current_row: u32,
9560 target_row: u32,
9561 ) -> Div {
9562 let (row_diff, arrow) = if target_row < current_row {
9563 (current_row - target_row, IconName::ArrowUp)
9564 } else {
9565 (target_row - current_row, IconName::ArrowDown)
9566 };
9567
9568 h_flex()
9569 .child(
9570 Label::new(format!("{}{}", prefix.into(), row_diff))
9571 .color(Color::Muted)
9572 .size(LabelSize::Small),
9573 )
9574 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9575 }
9576
9577 let supports_jump = self
9578 .edit_prediction_provider
9579 .as_ref()
9580 .map(|provider| provider.provider.supports_jump_to_edit())
9581 .unwrap_or(true);
9582
9583 match &completion.completion {
9584 EditPrediction::MoveWithin {
9585 target, snapshot, ..
9586 } => {
9587 if !supports_jump {
9588 return None;
9589 }
9590
9591 Some(
9592 h_flex()
9593 .px_2()
9594 .gap_2()
9595 .flex_1()
9596 .child(
9597 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9598 Icon::new(IconName::ZedPredictDown)
9599 } else {
9600 Icon::new(IconName::ZedPredictUp)
9601 },
9602 )
9603 .child(Label::new("Jump to Edit")),
9604 )
9605 }
9606 EditPrediction::MoveOutside { snapshot, .. } => {
9607 let file_name = snapshot
9608 .file()
9609 .map(|file| file.file_name(cx))
9610 .unwrap_or("untitled");
9611 Some(
9612 h_flex()
9613 .px_2()
9614 .gap_2()
9615 .flex_1()
9616 .child(Icon::new(IconName::ZedPredict))
9617 .child(Label::new(format!("Jump to {file_name}"))),
9618 )
9619 }
9620 EditPrediction::Edit {
9621 edits,
9622 edit_preview,
9623 snapshot,
9624 display_mode: _,
9625 } => {
9626 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9627
9628 let (highlighted_edits, has_more_lines) =
9629 if let Some(edit_preview) = edit_preview.as_ref() {
9630 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9631 .first_line_preview()
9632 } else {
9633 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9634 };
9635
9636 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9637 .with_default_highlights(&style.text, highlighted_edits.highlights);
9638
9639 let preview = h_flex()
9640 .gap_1()
9641 .min_w_16()
9642 .child(styled_text)
9643 .when(has_more_lines, |parent| parent.child("…"));
9644
9645 let left = if supports_jump && first_edit_row != cursor_point.row {
9646 render_relative_row_jump("", cursor_point.row, first_edit_row)
9647 .into_any_element()
9648 } else {
9649 let icon_name =
9650 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9651 Icon::new(icon_name).into_any_element()
9652 };
9653
9654 Some(
9655 h_flex()
9656 .h_full()
9657 .flex_1()
9658 .gap_2()
9659 .pr_1()
9660 .overflow_x_hidden()
9661 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9662 .child(left)
9663 .child(preview),
9664 )
9665 }
9666 }
9667 }
9668
9669 pub fn render_context_menu(
9670 &self,
9671 style: &EditorStyle,
9672 max_height_in_lines: u32,
9673 window: &mut Window,
9674 cx: &mut Context<Editor>,
9675 ) -> Option<AnyElement> {
9676 let menu = self.context_menu.borrow();
9677 let menu = menu.as_ref()?;
9678 if !menu.visible() {
9679 return None;
9680 };
9681 Some(menu.render(style, max_height_in_lines, window, cx))
9682 }
9683
9684 fn render_context_menu_aside(
9685 &mut self,
9686 max_size: Size<Pixels>,
9687 window: &mut Window,
9688 cx: &mut Context<Editor>,
9689 ) -> Option<AnyElement> {
9690 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9691 if menu.visible() {
9692 menu.render_aside(max_size, window, cx)
9693 } else {
9694 None
9695 }
9696 })
9697 }
9698
9699 fn hide_context_menu(
9700 &mut self,
9701 window: &mut Window,
9702 cx: &mut Context<Self>,
9703 ) -> Option<CodeContextMenu> {
9704 cx.notify();
9705 self.completion_tasks.clear();
9706 let context_menu = self.context_menu.borrow_mut().take();
9707 self.stale_edit_prediction_in_menu.take();
9708 self.update_visible_edit_prediction(window, cx);
9709 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9710 && let Some(completion_provider) = &self.completion_provider
9711 {
9712 completion_provider.selection_changed(None, window, cx);
9713 }
9714 context_menu
9715 }
9716
9717 fn show_snippet_choices(
9718 &mut self,
9719 choices: &Vec<String>,
9720 selection: Range<Anchor>,
9721 cx: &mut Context<Self>,
9722 ) {
9723 let Some((_, buffer, _)) = self
9724 .buffer()
9725 .read(cx)
9726 .excerpt_containing(selection.start, cx)
9727 else {
9728 return;
9729 };
9730 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9731 else {
9732 return;
9733 };
9734 if buffer != end_buffer {
9735 log::error!("expected anchor range to have matching buffer IDs");
9736 return;
9737 }
9738
9739 let id = post_inc(&mut self.next_completion_id);
9740 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9741 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9742 CompletionsMenu::new_snippet_choices(
9743 id,
9744 true,
9745 choices,
9746 selection,
9747 buffer,
9748 snippet_sort_order,
9749 ),
9750 ));
9751 }
9752
9753 pub fn insert_snippet(
9754 &mut self,
9755 insertion_ranges: &[Range<usize>],
9756 snippet: Snippet,
9757 window: &mut Window,
9758 cx: &mut Context<Self>,
9759 ) -> Result<()> {
9760 struct Tabstop<T> {
9761 is_end_tabstop: bool,
9762 ranges: Vec<Range<T>>,
9763 choices: Option<Vec<String>>,
9764 }
9765
9766 let tabstops = self.buffer.update(cx, |buffer, cx| {
9767 let snippet_text: Arc<str> = snippet.text.clone().into();
9768 let edits = insertion_ranges
9769 .iter()
9770 .cloned()
9771 .map(|range| (range, snippet_text.clone()));
9772 let autoindent_mode = AutoindentMode::Block {
9773 original_indent_columns: Vec::new(),
9774 };
9775 buffer.edit(edits, Some(autoindent_mode), cx);
9776
9777 let snapshot = &*buffer.read(cx);
9778 let snippet = &snippet;
9779 snippet
9780 .tabstops
9781 .iter()
9782 .map(|tabstop| {
9783 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9784 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9785 });
9786 let mut tabstop_ranges = tabstop
9787 .ranges
9788 .iter()
9789 .flat_map(|tabstop_range| {
9790 let mut delta = 0_isize;
9791 insertion_ranges.iter().map(move |insertion_range| {
9792 let insertion_start = insertion_range.start as isize + delta;
9793 delta +=
9794 snippet.text.len() as isize - insertion_range.len() as isize;
9795
9796 let start = ((insertion_start + tabstop_range.start) as usize)
9797 .min(snapshot.len());
9798 let end = ((insertion_start + tabstop_range.end) as usize)
9799 .min(snapshot.len());
9800 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9801 })
9802 })
9803 .collect::<Vec<_>>();
9804 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9805
9806 Tabstop {
9807 is_end_tabstop,
9808 ranges: tabstop_ranges,
9809 choices: tabstop.choices.clone(),
9810 }
9811 })
9812 .collect::<Vec<_>>()
9813 });
9814 if let Some(tabstop) = tabstops.first() {
9815 self.change_selections(Default::default(), window, cx, |s| {
9816 // Reverse order so that the first range is the newest created selection.
9817 // Completions will use it and autoscroll will prioritize it.
9818 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9819 });
9820
9821 if let Some(choices) = &tabstop.choices
9822 && let Some(selection) = tabstop.ranges.first()
9823 {
9824 self.show_snippet_choices(choices, selection.clone(), cx)
9825 }
9826
9827 // If we're already at the last tabstop and it's at the end of the snippet,
9828 // we're done, we don't need to keep the state around.
9829 if !tabstop.is_end_tabstop {
9830 let choices = tabstops
9831 .iter()
9832 .map(|tabstop| tabstop.choices.clone())
9833 .collect();
9834
9835 let ranges = tabstops
9836 .into_iter()
9837 .map(|tabstop| tabstop.ranges)
9838 .collect::<Vec<_>>();
9839
9840 self.snippet_stack.push(SnippetState {
9841 active_index: 0,
9842 ranges,
9843 choices,
9844 });
9845 }
9846
9847 // Check whether the just-entered snippet ends with an auto-closable bracket.
9848 if self.autoclose_regions.is_empty() {
9849 let snapshot = self.buffer.read(cx).snapshot(cx);
9850 let mut all_selections = self.selections.all::<Point>(cx);
9851 for selection in &mut all_selections {
9852 let selection_head = selection.head();
9853 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9854 continue;
9855 };
9856
9857 let mut bracket_pair = None;
9858 let max_lookup_length = scope
9859 .brackets()
9860 .map(|(pair, _)| {
9861 pair.start
9862 .as_str()
9863 .chars()
9864 .count()
9865 .max(pair.end.as_str().chars().count())
9866 })
9867 .max();
9868 if let Some(max_lookup_length) = max_lookup_length {
9869 let next_text = snapshot
9870 .chars_at(selection_head)
9871 .take(max_lookup_length)
9872 .collect::<String>();
9873 let prev_text = snapshot
9874 .reversed_chars_at(selection_head)
9875 .take(max_lookup_length)
9876 .collect::<String>();
9877
9878 for (pair, enabled) in scope.brackets() {
9879 if enabled
9880 && pair.close
9881 && prev_text.starts_with(pair.start.as_str())
9882 && next_text.starts_with(pair.end.as_str())
9883 {
9884 bracket_pair = Some(pair.clone());
9885 break;
9886 }
9887 }
9888 }
9889
9890 if let Some(pair) = bracket_pair {
9891 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9892 let autoclose_enabled =
9893 self.use_autoclose && snapshot_settings.use_autoclose;
9894 if autoclose_enabled {
9895 let start = snapshot.anchor_after(selection_head);
9896 let end = snapshot.anchor_after(selection_head);
9897 self.autoclose_regions.push(AutocloseRegion {
9898 selection_id: selection.id,
9899 range: start..end,
9900 pair,
9901 });
9902 }
9903 }
9904 }
9905 }
9906 }
9907 Ok(())
9908 }
9909
9910 pub fn move_to_next_snippet_tabstop(
9911 &mut self,
9912 window: &mut Window,
9913 cx: &mut Context<Self>,
9914 ) -> bool {
9915 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9916 }
9917
9918 pub fn move_to_prev_snippet_tabstop(
9919 &mut self,
9920 window: &mut Window,
9921 cx: &mut Context<Self>,
9922 ) -> bool {
9923 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9924 }
9925
9926 pub fn move_to_snippet_tabstop(
9927 &mut self,
9928 bias: Bias,
9929 window: &mut Window,
9930 cx: &mut Context<Self>,
9931 ) -> bool {
9932 if let Some(mut snippet) = self.snippet_stack.pop() {
9933 match bias {
9934 Bias::Left => {
9935 if snippet.active_index > 0 {
9936 snippet.active_index -= 1;
9937 } else {
9938 self.snippet_stack.push(snippet);
9939 return false;
9940 }
9941 }
9942 Bias::Right => {
9943 if snippet.active_index + 1 < snippet.ranges.len() {
9944 snippet.active_index += 1;
9945 } else {
9946 self.snippet_stack.push(snippet);
9947 return false;
9948 }
9949 }
9950 }
9951 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9952 self.change_selections(Default::default(), window, cx, |s| {
9953 // Reverse order so that the first range is the newest created selection.
9954 // Completions will use it and autoscroll will prioritize it.
9955 s.select_ranges(current_ranges.iter().rev().cloned())
9956 });
9957
9958 if let Some(choices) = &snippet.choices[snippet.active_index]
9959 && let Some(selection) = current_ranges.first()
9960 {
9961 self.show_snippet_choices(choices, selection.clone(), cx);
9962 }
9963
9964 // If snippet state is not at the last tabstop, push it back on the stack
9965 if snippet.active_index + 1 < snippet.ranges.len() {
9966 self.snippet_stack.push(snippet);
9967 }
9968 return true;
9969 }
9970 }
9971
9972 false
9973 }
9974
9975 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9976 self.transact(window, cx, |this, window, cx| {
9977 this.select_all(&SelectAll, window, cx);
9978 this.insert("", window, cx);
9979 });
9980 }
9981
9982 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9983 if self.read_only(cx) {
9984 return;
9985 }
9986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9987 self.transact(window, cx, |this, window, cx| {
9988 this.select_autoclose_pair(window, cx);
9989 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9990 if !this.linked_edit_ranges.is_empty() {
9991 let selections = this.selections.all::<MultiBufferPoint>(cx);
9992 let snapshot = this.buffer.read(cx).snapshot(cx);
9993
9994 for selection in selections.iter() {
9995 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9996 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9997 if selection_start.buffer_id != selection_end.buffer_id {
9998 continue;
9999 }
10000 if let Some(ranges) =
10001 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10002 {
10003 for (buffer, entries) in ranges {
10004 linked_ranges.entry(buffer).or_default().extend(entries);
10005 }
10006 }
10007 }
10008 }
10009
10010 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10011 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10012 for selection in &mut selections {
10013 if selection.is_empty() {
10014 let old_head = selection.head();
10015 let mut new_head =
10016 movement::left(&display_map, old_head.to_display_point(&display_map))
10017 .to_point(&display_map);
10018 if let Some((buffer, line_buffer_range)) = display_map
10019 .buffer_snapshot()
10020 .buffer_line_for_row(MultiBufferRow(old_head.row))
10021 {
10022 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10023 let indent_len = match indent_size.kind {
10024 IndentKind::Space => {
10025 buffer.settings_at(line_buffer_range.start, cx).tab_size
10026 }
10027 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10028 };
10029 if old_head.column <= indent_size.len && old_head.column > 0 {
10030 let indent_len = indent_len.get();
10031 new_head = cmp::min(
10032 new_head,
10033 MultiBufferPoint::new(
10034 old_head.row,
10035 ((old_head.column - 1) / indent_len) * indent_len,
10036 ),
10037 );
10038 }
10039 }
10040
10041 selection.set_head(new_head, SelectionGoal::None);
10042 }
10043 }
10044
10045 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10046 this.insert("", window, cx);
10047 let empty_str: Arc<str> = Arc::from("");
10048 for (buffer, edits) in linked_ranges {
10049 let snapshot = buffer.read(cx).snapshot();
10050 use text::ToPoint as TP;
10051
10052 let edits = edits
10053 .into_iter()
10054 .map(|range| {
10055 let end_point = TP::to_point(&range.end, &snapshot);
10056 let mut start_point = TP::to_point(&range.start, &snapshot);
10057
10058 if end_point == start_point {
10059 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10060 .saturating_sub(1);
10061 start_point =
10062 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10063 };
10064
10065 (start_point..end_point, empty_str.clone())
10066 })
10067 .sorted_by_key(|(range, _)| range.start)
10068 .collect::<Vec<_>>();
10069 buffer.update(cx, |this, cx| {
10070 this.edit(edits, None, cx);
10071 })
10072 }
10073 this.refresh_edit_prediction(true, false, window, cx);
10074 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10075 });
10076 }
10077
10078 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10079 if self.read_only(cx) {
10080 return;
10081 }
10082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10083 self.transact(window, cx, |this, window, cx| {
10084 this.change_selections(Default::default(), window, cx, |s| {
10085 s.move_with(|map, selection| {
10086 if selection.is_empty() {
10087 let cursor = movement::right(map, selection.head());
10088 selection.end = cursor;
10089 selection.reversed = true;
10090 selection.goal = SelectionGoal::None;
10091 }
10092 })
10093 });
10094 this.insert("", window, cx);
10095 this.refresh_edit_prediction(true, false, window, cx);
10096 });
10097 }
10098
10099 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10100 if self.mode.is_single_line() {
10101 cx.propagate();
10102 return;
10103 }
10104
10105 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10106 if self.move_to_prev_snippet_tabstop(window, cx) {
10107 return;
10108 }
10109 self.outdent(&Outdent, window, cx);
10110 }
10111
10112 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10113 if self.mode.is_single_line() {
10114 cx.propagate();
10115 return;
10116 }
10117
10118 if self.move_to_next_snippet_tabstop(window, cx) {
10119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10120 return;
10121 }
10122 if self.read_only(cx) {
10123 return;
10124 }
10125 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10126 let mut selections = self.selections.all_adjusted(cx);
10127 let buffer = self.buffer.read(cx);
10128 let snapshot = buffer.snapshot(cx);
10129 let rows_iter = selections.iter().map(|s| s.head().row);
10130 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10131
10132 let has_some_cursor_in_whitespace = selections
10133 .iter()
10134 .filter(|selection| selection.is_empty())
10135 .any(|selection| {
10136 let cursor = selection.head();
10137 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10138 cursor.column < current_indent.len
10139 });
10140
10141 let mut edits = Vec::new();
10142 let mut prev_edited_row = 0;
10143 let mut row_delta = 0;
10144 for selection in &mut selections {
10145 if selection.start.row != prev_edited_row {
10146 row_delta = 0;
10147 }
10148 prev_edited_row = selection.end.row;
10149
10150 // If the selection is non-empty, then increase the indentation of the selected lines.
10151 if !selection.is_empty() {
10152 row_delta =
10153 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10154 continue;
10155 }
10156
10157 let cursor = selection.head();
10158 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10159 if let Some(suggested_indent) =
10160 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10161 {
10162 // Don't do anything if already at suggested indent
10163 // and there is any other cursor which is not
10164 if has_some_cursor_in_whitespace
10165 && cursor.column == current_indent.len
10166 && current_indent.len == suggested_indent.len
10167 {
10168 continue;
10169 }
10170
10171 // Adjust line and move cursor to suggested indent
10172 // if cursor is not at suggested indent
10173 if cursor.column < suggested_indent.len
10174 && cursor.column <= current_indent.len
10175 && current_indent.len <= suggested_indent.len
10176 {
10177 selection.start = Point::new(cursor.row, suggested_indent.len);
10178 selection.end = selection.start;
10179 if row_delta == 0 {
10180 edits.extend(Buffer::edit_for_indent_size_adjustment(
10181 cursor.row,
10182 current_indent,
10183 suggested_indent,
10184 ));
10185 row_delta = suggested_indent.len - current_indent.len;
10186 }
10187 continue;
10188 }
10189
10190 // If current indent is more than suggested indent
10191 // only move cursor to current indent and skip indent
10192 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10193 selection.start = Point::new(cursor.row, current_indent.len);
10194 selection.end = selection.start;
10195 continue;
10196 }
10197 }
10198
10199 // Otherwise, insert a hard or soft tab.
10200 let settings = buffer.language_settings_at(cursor, cx);
10201 let tab_size = if settings.hard_tabs {
10202 IndentSize::tab()
10203 } else {
10204 let tab_size = settings.tab_size.get();
10205 let indent_remainder = snapshot
10206 .text_for_range(Point::new(cursor.row, 0)..cursor)
10207 .flat_map(str::chars)
10208 .fold(row_delta % tab_size, |counter: u32, c| {
10209 if c == '\t' {
10210 0
10211 } else {
10212 (counter + 1) % tab_size
10213 }
10214 });
10215
10216 let chars_to_next_tab_stop = tab_size - indent_remainder;
10217 IndentSize::spaces(chars_to_next_tab_stop)
10218 };
10219 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10220 selection.end = selection.start;
10221 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10222 row_delta += tab_size.len;
10223 }
10224
10225 self.transact(window, cx, |this, window, cx| {
10226 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10227 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10228 this.refresh_edit_prediction(true, false, window, cx);
10229 });
10230 }
10231
10232 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10233 if self.read_only(cx) {
10234 return;
10235 }
10236 if self.mode.is_single_line() {
10237 cx.propagate();
10238 return;
10239 }
10240
10241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10242 let mut selections = self.selections.all::<Point>(cx);
10243 let mut prev_edited_row = 0;
10244 let mut row_delta = 0;
10245 let mut edits = Vec::new();
10246 let buffer = self.buffer.read(cx);
10247 let snapshot = buffer.snapshot(cx);
10248 for selection in &mut selections {
10249 if selection.start.row != prev_edited_row {
10250 row_delta = 0;
10251 }
10252 prev_edited_row = selection.end.row;
10253
10254 row_delta =
10255 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10256 }
10257
10258 self.transact(window, cx, |this, window, cx| {
10259 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10260 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10261 });
10262 }
10263
10264 fn indent_selection(
10265 buffer: &MultiBuffer,
10266 snapshot: &MultiBufferSnapshot,
10267 selection: &mut Selection<Point>,
10268 edits: &mut Vec<(Range<Point>, String)>,
10269 delta_for_start_row: u32,
10270 cx: &App,
10271 ) -> u32 {
10272 let settings = buffer.language_settings_at(selection.start, cx);
10273 let tab_size = settings.tab_size.get();
10274 let indent_kind = if settings.hard_tabs {
10275 IndentKind::Tab
10276 } else {
10277 IndentKind::Space
10278 };
10279 let mut start_row = selection.start.row;
10280 let mut end_row = selection.end.row + 1;
10281
10282 // If a selection ends at the beginning of a line, don't indent
10283 // that last line.
10284 if selection.end.column == 0 && selection.end.row > selection.start.row {
10285 end_row -= 1;
10286 }
10287
10288 // Avoid re-indenting a row that has already been indented by a
10289 // previous selection, but still update this selection's column
10290 // to reflect that indentation.
10291 if delta_for_start_row > 0 {
10292 start_row += 1;
10293 selection.start.column += delta_for_start_row;
10294 if selection.end.row == selection.start.row {
10295 selection.end.column += delta_for_start_row;
10296 }
10297 }
10298
10299 let mut delta_for_end_row = 0;
10300 let has_multiple_rows = start_row + 1 != end_row;
10301 for row in start_row..end_row {
10302 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10303 let indent_delta = match (current_indent.kind, indent_kind) {
10304 (IndentKind::Space, IndentKind::Space) => {
10305 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10306 IndentSize::spaces(columns_to_next_tab_stop)
10307 }
10308 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10309 (_, IndentKind::Tab) => IndentSize::tab(),
10310 };
10311
10312 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10313 0
10314 } else {
10315 selection.start.column
10316 };
10317 let row_start = Point::new(row, start);
10318 edits.push((
10319 row_start..row_start,
10320 indent_delta.chars().collect::<String>(),
10321 ));
10322
10323 // Update this selection's endpoints to reflect the indentation.
10324 if row == selection.start.row {
10325 selection.start.column += indent_delta.len;
10326 }
10327 if row == selection.end.row {
10328 selection.end.column += indent_delta.len;
10329 delta_for_end_row = indent_delta.len;
10330 }
10331 }
10332
10333 if selection.start.row == selection.end.row {
10334 delta_for_start_row + delta_for_end_row
10335 } else {
10336 delta_for_end_row
10337 }
10338 }
10339
10340 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10341 if self.read_only(cx) {
10342 return;
10343 }
10344 if self.mode.is_single_line() {
10345 cx.propagate();
10346 return;
10347 }
10348
10349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10351 let selections = self.selections.all::<Point>(cx);
10352 let mut deletion_ranges = Vec::new();
10353 let mut last_outdent = None;
10354 {
10355 let buffer = self.buffer.read(cx);
10356 let snapshot = buffer.snapshot(cx);
10357 for selection in &selections {
10358 let settings = buffer.language_settings_at(selection.start, cx);
10359 let tab_size = settings.tab_size.get();
10360 let mut rows = selection.spanned_rows(false, &display_map);
10361
10362 // Avoid re-outdenting a row that has already been outdented by a
10363 // previous selection.
10364 if let Some(last_row) = last_outdent
10365 && last_row == rows.start
10366 {
10367 rows.start = rows.start.next_row();
10368 }
10369 let has_multiple_rows = rows.len() > 1;
10370 for row in rows.iter_rows() {
10371 let indent_size = snapshot.indent_size_for_line(row);
10372 if indent_size.len > 0 {
10373 let deletion_len = match indent_size.kind {
10374 IndentKind::Space => {
10375 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10376 if columns_to_prev_tab_stop == 0 {
10377 tab_size
10378 } else {
10379 columns_to_prev_tab_stop
10380 }
10381 }
10382 IndentKind::Tab => 1,
10383 };
10384 let start = if has_multiple_rows
10385 || deletion_len > selection.start.column
10386 || indent_size.len < selection.start.column
10387 {
10388 0
10389 } else {
10390 selection.start.column - deletion_len
10391 };
10392 deletion_ranges.push(
10393 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10394 );
10395 last_outdent = Some(row);
10396 }
10397 }
10398 }
10399 }
10400
10401 self.transact(window, cx, |this, window, cx| {
10402 this.buffer.update(cx, |buffer, cx| {
10403 let empty_str: Arc<str> = Arc::default();
10404 buffer.edit(
10405 deletion_ranges
10406 .into_iter()
10407 .map(|range| (range, empty_str.clone())),
10408 None,
10409 cx,
10410 );
10411 });
10412 let selections = this.selections.all::<usize>(cx);
10413 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10414 });
10415 }
10416
10417 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10418 if self.read_only(cx) {
10419 return;
10420 }
10421 if self.mode.is_single_line() {
10422 cx.propagate();
10423 return;
10424 }
10425
10426 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10427 let selections = self
10428 .selections
10429 .all::<usize>(cx)
10430 .into_iter()
10431 .map(|s| s.range());
10432
10433 self.transact(window, cx, |this, window, cx| {
10434 this.buffer.update(cx, |buffer, cx| {
10435 buffer.autoindent_ranges(selections, cx);
10436 });
10437 let selections = this.selections.all::<usize>(cx);
10438 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10439 });
10440 }
10441
10442 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10443 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10444 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10445 let selections = self.selections.all::<Point>(cx);
10446
10447 let mut new_cursors = Vec::new();
10448 let mut edit_ranges = Vec::new();
10449 let mut selections = selections.iter().peekable();
10450 while let Some(selection) = selections.next() {
10451 let mut rows = selection.spanned_rows(false, &display_map);
10452
10453 // Accumulate contiguous regions of rows that we want to delete.
10454 while let Some(next_selection) = selections.peek() {
10455 let next_rows = next_selection.spanned_rows(false, &display_map);
10456 if next_rows.start <= rows.end {
10457 rows.end = next_rows.end;
10458 selections.next().unwrap();
10459 } else {
10460 break;
10461 }
10462 }
10463
10464 let buffer = display_map.buffer_snapshot();
10465 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10466 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10467 // If there's a line after the range, delete the \n from the end of the row range
10468 (
10469 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10470 rows.end,
10471 )
10472 } else {
10473 // If there isn't a line after the range, delete the \n from the line before the
10474 // start of the row range
10475 edit_start = edit_start.saturating_sub(1);
10476 (buffer.len(), rows.start.previous_row())
10477 };
10478
10479 let text_layout_details = self.text_layout_details(window);
10480 let x = display_map.x_for_display_point(
10481 selection.head().to_display_point(&display_map),
10482 &text_layout_details,
10483 );
10484 let row = Point::new(target_row.0, 0)
10485 .to_display_point(&display_map)
10486 .row();
10487 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10488
10489 new_cursors.push((
10490 selection.id,
10491 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10492 SelectionGoal::None,
10493 ));
10494 edit_ranges.push(edit_start..edit_end);
10495 }
10496
10497 self.transact(window, cx, |this, window, cx| {
10498 let buffer = this.buffer.update(cx, |buffer, cx| {
10499 let empty_str: Arc<str> = Arc::default();
10500 buffer.edit(
10501 edit_ranges
10502 .into_iter()
10503 .map(|range| (range, empty_str.clone())),
10504 None,
10505 cx,
10506 );
10507 buffer.snapshot(cx)
10508 });
10509 let new_selections = new_cursors
10510 .into_iter()
10511 .map(|(id, cursor, goal)| {
10512 let cursor = cursor.to_point(&buffer);
10513 Selection {
10514 id,
10515 start: cursor,
10516 end: cursor,
10517 reversed: false,
10518 goal,
10519 }
10520 })
10521 .collect();
10522
10523 this.change_selections(Default::default(), window, cx, |s| {
10524 s.select(new_selections);
10525 });
10526 });
10527 }
10528
10529 pub fn join_lines_impl(
10530 &mut self,
10531 insert_whitespace: bool,
10532 window: &mut Window,
10533 cx: &mut Context<Self>,
10534 ) {
10535 if self.read_only(cx) {
10536 return;
10537 }
10538 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10539 for selection in self.selections.all::<Point>(cx) {
10540 let start = MultiBufferRow(selection.start.row);
10541 // Treat single line selections as if they include the next line. Otherwise this action
10542 // would do nothing for single line selections individual cursors.
10543 let end = if selection.start.row == selection.end.row {
10544 MultiBufferRow(selection.start.row + 1)
10545 } else {
10546 MultiBufferRow(selection.end.row)
10547 };
10548
10549 if let Some(last_row_range) = row_ranges.last_mut()
10550 && start <= last_row_range.end
10551 {
10552 last_row_range.end = end;
10553 continue;
10554 }
10555 row_ranges.push(start..end);
10556 }
10557
10558 let snapshot = self.buffer.read(cx).snapshot(cx);
10559 let mut cursor_positions = Vec::new();
10560 for row_range in &row_ranges {
10561 let anchor = snapshot.anchor_before(Point::new(
10562 row_range.end.previous_row().0,
10563 snapshot.line_len(row_range.end.previous_row()),
10564 ));
10565 cursor_positions.push(anchor..anchor);
10566 }
10567
10568 self.transact(window, cx, |this, window, cx| {
10569 for row_range in row_ranges.into_iter().rev() {
10570 for row in row_range.iter_rows().rev() {
10571 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10572 let next_line_row = row.next_row();
10573 let indent = snapshot.indent_size_for_line(next_line_row);
10574 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10575
10576 let replace =
10577 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10578 " "
10579 } else {
10580 ""
10581 };
10582
10583 this.buffer.update(cx, |buffer, cx| {
10584 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10585 });
10586 }
10587 }
10588
10589 this.change_selections(Default::default(), window, cx, |s| {
10590 s.select_anchor_ranges(cursor_positions)
10591 });
10592 });
10593 }
10594
10595 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10597 self.join_lines_impl(true, window, cx);
10598 }
10599
10600 pub fn sort_lines_case_sensitive(
10601 &mut self,
10602 _: &SortLinesCaseSensitive,
10603 window: &mut Window,
10604 cx: &mut Context<Self>,
10605 ) {
10606 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10607 }
10608
10609 pub fn sort_lines_by_length(
10610 &mut self,
10611 _: &SortLinesByLength,
10612 window: &mut Window,
10613 cx: &mut Context<Self>,
10614 ) {
10615 self.manipulate_immutable_lines(window, cx, |lines| {
10616 lines.sort_by_key(|&line| line.chars().count())
10617 })
10618 }
10619
10620 pub fn sort_lines_case_insensitive(
10621 &mut self,
10622 _: &SortLinesCaseInsensitive,
10623 window: &mut Window,
10624 cx: &mut Context<Self>,
10625 ) {
10626 self.manipulate_immutable_lines(window, cx, |lines| {
10627 lines.sort_by_key(|line| line.to_lowercase())
10628 })
10629 }
10630
10631 pub fn unique_lines_case_insensitive(
10632 &mut self,
10633 _: &UniqueLinesCaseInsensitive,
10634 window: &mut Window,
10635 cx: &mut Context<Self>,
10636 ) {
10637 self.manipulate_immutable_lines(window, cx, |lines| {
10638 let mut seen = HashSet::default();
10639 lines.retain(|line| seen.insert(line.to_lowercase()));
10640 })
10641 }
10642
10643 pub fn unique_lines_case_sensitive(
10644 &mut self,
10645 _: &UniqueLinesCaseSensitive,
10646 window: &mut Window,
10647 cx: &mut Context<Self>,
10648 ) {
10649 self.manipulate_immutable_lines(window, cx, |lines| {
10650 let mut seen = HashSet::default();
10651 lines.retain(|line| seen.insert(*line));
10652 })
10653 }
10654
10655 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10656 let snapshot = self.buffer.read(cx).snapshot(cx);
10657 for selection in self.selections.disjoint_anchors_arc().iter() {
10658 if snapshot
10659 .language_at(selection.start)
10660 .and_then(|lang| lang.config().wrap_characters.as_ref())
10661 .is_some()
10662 {
10663 return true;
10664 }
10665 }
10666 false
10667 }
10668
10669 fn wrap_selections_in_tag(
10670 &mut self,
10671 _: &WrapSelectionsInTag,
10672 window: &mut Window,
10673 cx: &mut Context<Self>,
10674 ) {
10675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10676
10677 let snapshot = self.buffer.read(cx).snapshot(cx);
10678
10679 let mut edits = Vec::new();
10680 let mut boundaries = Vec::new();
10681
10682 for selection in self.selections.all::<Point>(cx).iter() {
10683 let Some(wrap_config) = snapshot
10684 .language_at(selection.start)
10685 .and_then(|lang| lang.config().wrap_characters.clone())
10686 else {
10687 continue;
10688 };
10689
10690 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10691 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10692
10693 let start_before = snapshot.anchor_before(selection.start);
10694 let end_after = snapshot.anchor_after(selection.end);
10695
10696 edits.push((start_before..start_before, open_tag));
10697 edits.push((end_after..end_after, close_tag));
10698
10699 boundaries.push((
10700 start_before,
10701 end_after,
10702 wrap_config.start_prefix.len(),
10703 wrap_config.end_suffix.len(),
10704 ));
10705 }
10706
10707 if edits.is_empty() {
10708 return;
10709 }
10710
10711 self.transact(window, cx, |this, window, cx| {
10712 let buffer = this.buffer.update(cx, |buffer, cx| {
10713 buffer.edit(edits, None, cx);
10714 buffer.snapshot(cx)
10715 });
10716
10717 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10718 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10719 boundaries.into_iter()
10720 {
10721 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10722 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10723 new_selections.push(open_offset..open_offset);
10724 new_selections.push(close_offset..close_offset);
10725 }
10726
10727 this.change_selections(Default::default(), window, cx, |s| {
10728 s.select_ranges(new_selections);
10729 });
10730
10731 this.request_autoscroll(Autoscroll::fit(), cx);
10732 });
10733 }
10734
10735 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10736 let Some(project) = self.project.clone() else {
10737 return;
10738 };
10739 self.reload(project, window, cx)
10740 .detach_and_notify_err(window, cx);
10741 }
10742
10743 pub fn restore_file(
10744 &mut self,
10745 _: &::git::RestoreFile,
10746 window: &mut Window,
10747 cx: &mut Context<Self>,
10748 ) {
10749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10750 let mut buffer_ids = HashSet::default();
10751 let snapshot = self.buffer().read(cx).snapshot(cx);
10752 for selection in self.selections.all::<usize>(cx) {
10753 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10754 }
10755
10756 let buffer = self.buffer().read(cx);
10757 let ranges = buffer_ids
10758 .into_iter()
10759 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10760 .collect::<Vec<_>>();
10761
10762 self.restore_hunks_in_ranges(ranges, window, cx);
10763 }
10764
10765 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10767 let selections = self
10768 .selections
10769 .all(cx)
10770 .into_iter()
10771 .map(|s| s.range())
10772 .collect();
10773 self.restore_hunks_in_ranges(selections, window, cx);
10774 }
10775
10776 pub fn restore_hunks_in_ranges(
10777 &mut self,
10778 ranges: Vec<Range<Point>>,
10779 window: &mut Window,
10780 cx: &mut Context<Editor>,
10781 ) {
10782 let mut revert_changes = HashMap::default();
10783 let chunk_by = self
10784 .snapshot(window, cx)
10785 .hunks_for_ranges(ranges)
10786 .into_iter()
10787 .chunk_by(|hunk| hunk.buffer_id);
10788 for (buffer_id, hunks) in &chunk_by {
10789 let hunks = hunks.collect::<Vec<_>>();
10790 for hunk in &hunks {
10791 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10792 }
10793 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10794 }
10795 drop(chunk_by);
10796 if !revert_changes.is_empty() {
10797 self.transact(window, cx, |editor, window, cx| {
10798 editor.restore(revert_changes, window, cx);
10799 });
10800 }
10801 }
10802
10803 pub fn open_active_item_in_terminal(
10804 &mut self,
10805 _: &OpenInTerminal,
10806 window: &mut Window,
10807 cx: &mut Context<Self>,
10808 ) {
10809 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10810 let project_path = buffer.read(cx).project_path(cx)?;
10811 let project = self.project()?.read(cx);
10812 let entry = project.entry_for_path(&project_path, cx)?;
10813 let parent = match &entry.canonical_path {
10814 Some(canonical_path) => canonical_path.to_path_buf(),
10815 None => project.absolute_path(&project_path, cx)?,
10816 }
10817 .parent()?
10818 .to_path_buf();
10819 Some(parent)
10820 }) {
10821 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10822 }
10823 }
10824
10825 fn set_breakpoint_context_menu(
10826 &mut self,
10827 display_row: DisplayRow,
10828 position: Option<Anchor>,
10829 clicked_point: gpui::Point<Pixels>,
10830 window: &mut Window,
10831 cx: &mut Context<Self>,
10832 ) {
10833 let source = self
10834 .buffer
10835 .read(cx)
10836 .snapshot(cx)
10837 .anchor_before(Point::new(display_row.0, 0u32));
10838
10839 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10840
10841 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10842 self,
10843 source,
10844 clicked_point,
10845 context_menu,
10846 window,
10847 cx,
10848 );
10849 }
10850
10851 fn add_edit_breakpoint_block(
10852 &mut self,
10853 anchor: Anchor,
10854 breakpoint: &Breakpoint,
10855 edit_action: BreakpointPromptEditAction,
10856 window: &mut Window,
10857 cx: &mut Context<Self>,
10858 ) {
10859 let weak_editor = cx.weak_entity();
10860 let bp_prompt = cx.new(|cx| {
10861 BreakpointPromptEditor::new(
10862 weak_editor,
10863 anchor,
10864 breakpoint.clone(),
10865 edit_action,
10866 window,
10867 cx,
10868 )
10869 });
10870
10871 let height = bp_prompt.update(cx, |this, cx| {
10872 this.prompt
10873 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10874 });
10875 let cloned_prompt = bp_prompt.clone();
10876 let blocks = vec![BlockProperties {
10877 style: BlockStyle::Sticky,
10878 placement: BlockPlacement::Above(anchor),
10879 height: Some(height),
10880 render: Arc::new(move |cx| {
10881 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10882 cloned_prompt.clone().into_any_element()
10883 }),
10884 priority: 0,
10885 }];
10886
10887 let focus_handle = bp_prompt.focus_handle(cx);
10888 window.focus(&focus_handle);
10889
10890 let block_ids = self.insert_blocks(blocks, None, cx);
10891 bp_prompt.update(cx, |prompt, _| {
10892 prompt.add_block_ids(block_ids);
10893 });
10894 }
10895
10896 pub(crate) fn breakpoint_at_row(
10897 &self,
10898 row: u32,
10899 window: &mut Window,
10900 cx: &mut Context<Self>,
10901 ) -> Option<(Anchor, Breakpoint)> {
10902 let snapshot = self.snapshot(window, cx);
10903 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10904
10905 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10906 }
10907
10908 pub(crate) fn breakpoint_at_anchor(
10909 &self,
10910 breakpoint_position: Anchor,
10911 snapshot: &EditorSnapshot,
10912 cx: &mut Context<Self>,
10913 ) -> Option<(Anchor, Breakpoint)> {
10914 let buffer = self
10915 .buffer
10916 .read(cx)
10917 .buffer_for_anchor(breakpoint_position, cx)?;
10918
10919 let enclosing_excerpt = breakpoint_position.excerpt_id;
10920 let buffer_snapshot = buffer.read(cx).snapshot();
10921
10922 let row = buffer_snapshot
10923 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10924 .row;
10925
10926 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10927 let anchor_end = snapshot
10928 .buffer_snapshot()
10929 .anchor_after(Point::new(row, line_len));
10930
10931 self.breakpoint_store
10932 .as_ref()?
10933 .read_with(cx, |breakpoint_store, cx| {
10934 breakpoint_store
10935 .breakpoints(
10936 &buffer,
10937 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10938 &buffer_snapshot,
10939 cx,
10940 )
10941 .next()
10942 .and_then(|(bp, _)| {
10943 let breakpoint_row = buffer_snapshot
10944 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10945 .row;
10946
10947 if breakpoint_row == row {
10948 snapshot
10949 .buffer_snapshot()
10950 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10951 .map(|position| (position, bp.bp.clone()))
10952 } else {
10953 None
10954 }
10955 })
10956 })
10957 }
10958
10959 pub fn edit_log_breakpoint(
10960 &mut self,
10961 _: &EditLogBreakpoint,
10962 window: &mut Window,
10963 cx: &mut Context<Self>,
10964 ) {
10965 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10966 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10967 message: None,
10968 state: BreakpointState::Enabled,
10969 condition: None,
10970 hit_condition: None,
10971 });
10972
10973 self.add_edit_breakpoint_block(
10974 anchor,
10975 &breakpoint,
10976 BreakpointPromptEditAction::Log,
10977 window,
10978 cx,
10979 );
10980 }
10981 }
10982
10983 fn breakpoints_at_cursors(
10984 &self,
10985 window: &mut Window,
10986 cx: &mut Context<Self>,
10987 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10988 let snapshot = self.snapshot(window, cx);
10989 let cursors = self
10990 .selections
10991 .disjoint_anchors_arc()
10992 .iter()
10993 .map(|selection| {
10994 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10995
10996 let breakpoint_position = self
10997 .breakpoint_at_row(cursor_position.row, window, cx)
10998 .map(|bp| bp.0)
10999 .unwrap_or_else(|| {
11000 snapshot
11001 .display_snapshot
11002 .buffer_snapshot()
11003 .anchor_after(Point::new(cursor_position.row, 0))
11004 });
11005
11006 let breakpoint = self
11007 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11008 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11009
11010 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11011 })
11012 // 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.
11013 .collect::<HashMap<Anchor, _>>();
11014
11015 cursors.into_iter().collect()
11016 }
11017
11018 pub fn enable_breakpoint(
11019 &mut self,
11020 _: &crate::actions::EnableBreakpoint,
11021 window: &mut Window,
11022 cx: &mut Context<Self>,
11023 ) {
11024 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11025 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11026 continue;
11027 };
11028 self.edit_breakpoint_at_anchor(
11029 anchor,
11030 breakpoint,
11031 BreakpointEditAction::InvertState,
11032 cx,
11033 );
11034 }
11035 }
11036
11037 pub fn disable_breakpoint(
11038 &mut self,
11039 _: &crate::actions::DisableBreakpoint,
11040 window: &mut Window,
11041 cx: &mut Context<Self>,
11042 ) {
11043 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11044 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11045 continue;
11046 };
11047 self.edit_breakpoint_at_anchor(
11048 anchor,
11049 breakpoint,
11050 BreakpointEditAction::InvertState,
11051 cx,
11052 );
11053 }
11054 }
11055
11056 pub fn toggle_breakpoint(
11057 &mut self,
11058 _: &crate::actions::ToggleBreakpoint,
11059 window: &mut Window,
11060 cx: &mut Context<Self>,
11061 ) {
11062 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11063 if let Some(breakpoint) = breakpoint {
11064 self.edit_breakpoint_at_anchor(
11065 anchor,
11066 breakpoint,
11067 BreakpointEditAction::Toggle,
11068 cx,
11069 );
11070 } else {
11071 self.edit_breakpoint_at_anchor(
11072 anchor,
11073 Breakpoint::new_standard(),
11074 BreakpointEditAction::Toggle,
11075 cx,
11076 );
11077 }
11078 }
11079 }
11080
11081 pub fn edit_breakpoint_at_anchor(
11082 &mut self,
11083 breakpoint_position: Anchor,
11084 breakpoint: Breakpoint,
11085 edit_action: BreakpointEditAction,
11086 cx: &mut Context<Self>,
11087 ) {
11088 let Some(breakpoint_store) = &self.breakpoint_store else {
11089 return;
11090 };
11091
11092 let Some(buffer) = self
11093 .buffer
11094 .read(cx)
11095 .buffer_for_anchor(breakpoint_position, cx)
11096 else {
11097 return;
11098 };
11099
11100 breakpoint_store.update(cx, |breakpoint_store, cx| {
11101 breakpoint_store.toggle_breakpoint(
11102 buffer,
11103 BreakpointWithPosition {
11104 position: breakpoint_position.text_anchor,
11105 bp: breakpoint,
11106 },
11107 edit_action,
11108 cx,
11109 );
11110 });
11111
11112 cx.notify();
11113 }
11114
11115 #[cfg(any(test, feature = "test-support"))]
11116 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11117 self.breakpoint_store.clone()
11118 }
11119
11120 pub fn prepare_restore_change(
11121 &self,
11122 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11123 hunk: &MultiBufferDiffHunk,
11124 cx: &mut App,
11125 ) -> Option<()> {
11126 if hunk.is_created_file() {
11127 return None;
11128 }
11129 let buffer = self.buffer.read(cx);
11130 let diff = buffer.diff_for(hunk.buffer_id)?;
11131 let buffer = buffer.buffer(hunk.buffer_id)?;
11132 let buffer = buffer.read(cx);
11133 let original_text = diff
11134 .read(cx)
11135 .base_text()
11136 .as_rope()
11137 .slice(hunk.diff_base_byte_range.clone());
11138 let buffer_snapshot = buffer.snapshot();
11139 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11140 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11141 probe
11142 .0
11143 .start
11144 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11145 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11146 }) {
11147 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11148 Some(())
11149 } else {
11150 None
11151 }
11152 }
11153
11154 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11155 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11156 }
11157
11158 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11159 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11160 }
11161
11162 fn manipulate_lines<M>(
11163 &mut self,
11164 window: &mut Window,
11165 cx: &mut Context<Self>,
11166 mut manipulate: M,
11167 ) where
11168 M: FnMut(&str) -> LineManipulationResult,
11169 {
11170 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11171
11172 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11173 let buffer = self.buffer.read(cx).snapshot(cx);
11174
11175 let mut edits = Vec::new();
11176
11177 let selections = self.selections.all::<Point>(cx);
11178 let mut selections = selections.iter().peekable();
11179 let mut contiguous_row_selections = Vec::new();
11180 let mut new_selections = Vec::new();
11181 let mut added_lines = 0;
11182 let mut removed_lines = 0;
11183
11184 while let Some(selection) = selections.next() {
11185 let (start_row, end_row) = consume_contiguous_rows(
11186 &mut contiguous_row_selections,
11187 selection,
11188 &display_map,
11189 &mut selections,
11190 );
11191
11192 let start_point = Point::new(start_row.0, 0);
11193 let end_point = Point::new(
11194 end_row.previous_row().0,
11195 buffer.line_len(end_row.previous_row()),
11196 );
11197 let text = buffer
11198 .text_for_range(start_point..end_point)
11199 .collect::<String>();
11200
11201 let LineManipulationResult {
11202 new_text,
11203 line_count_before,
11204 line_count_after,
11205 } = manipulate(&text);
11206
11207 edits.push((start_point..end_point, new_text));
11208
11209 // Selections must change based on added and removed line count
11210 let start_row =
11211 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11212 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11213 new_selections.push(Selection {
11214 id: selection.id,
11215 start: start_row,
11216 end: end_row,
11217 goal: SelectionGoal::None,
11218 reversed: selection.reversed,
11219 });
11220
11221 if line_count_after > line_count_before {
11222 added_lines += line_count_after - line_count_before;
11223 } else if line_count_before > line_count_after {
11224 removed_lines += line_count_before - line_count_after;
11225 }
11226 }
11227
11228 self.transact(window, cx, |this, window, cx| {
11229 let buffer = this.buffer.update(cx, |buffer, cx| {
11230 buffer.edit(edits, None, cx);
11231 buffer.snapshot(cx)
11232 });
11233
11234 // Recalculate offsets on newly edited buffer
11235 let new_selections = new_selections
11236 .iter()
11237 .map(|s| {
11238 let start_point = Point::new(s.start.0, 0);
11239 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11240 Selection {
11241 id: s.id,
11242 start: buffer.point_to_offset(start_point),
11243 end: buffer.point_to_offset(end_point),
11244 goal: s.goal,
11245 reversed: s.reversed,
11246 }
11247 })
11248 .collect();
11249
11250 this.change_selections(Default::default(), window, cx, |s| {
11251 s.select(new_selections);
11252 });
11253
11254 this.request_autoscroll(Autoscroll::fit(), cx);
11255 });
11256 }
11257
11258 fn manipulate_immutable_lines<Fn>(
11259 &mut self,
11260 window: &mut Window,
11261 cx: &mut Context<Self>,
11262 mut callback: Fn,
11263 ) where
11264 Fn: FnMut(&mut Vec<&str>),
11265 {
11266 self.manipulate_lines(window, cx, |text| {
11267 let mut lines: Vec<&str> = text.split('\n').collect();
11268 let line_count_before = lines.len();
11269
11270 callback(&mut lines);
11271
11272 LineManipulationResult {
11273 new_text: lines.join("\n"),
11274 line_count_before,
11275 line_count_after: lines.len(),
11276 }
11277 });
11278 }
11279
11280 fn manipulate_mutable_lines<Fn>(
11281 &mut self,
11282 window: &mut Window,
11283 cx: &mut Context<Self>,
11284 mut callback: Fn,
11285 ) where
11286 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11287 {
11288 self.manipulate_lines(window, cx, |text| {
11289 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11290 let line_count_before = lines.len();
11291
11292 callback(&mut lines);
11293
11294 LineManipulationResult {
11295 new_text: lines.join("\n"),
11296 line_count_before,
11297 line_count_after: lines.len(),
11298 }
11299 });
11300 }
11301
11302 pub fn convert_indentation_to_spaces(
11303 &mut self,
11304 _: &ConvertIndentationToSpaces,
11305 window: &mut Window,
11306 cx: &mut Context<Self>,
11307 ) {
11308 let settings = self.buffer.read(cx).language_settings(cx);
11309 let tab_size = settings.tab_size.get() as usize;
11310
11311 self.manipulate_mutable_lines(window, cx, |lines| {
11312 // Allocates a reasonably sized scratch buffer once for the whole loop
11313 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11314 // Avoids recomputing spaces that could be inserted many times
11315 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11316 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11317 .collect();
11318
11319 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11320 let mut chars = line.as_ref().chars();
11321 let mut col = 0;
11322 let mut changed = false;
11323
11324 for ch in chars.by_ref() {
11325 match ch {
11326 ' ' => {
11327 reindented_line.push(' ');
11328 col += 1;
11329 }
11330 '\t' => {
11331 // \t are converted to spaces depending on the current column
11332 let spaces_len = tab_size - (col % tab_size);
11333 reindented_line.extend(&space_cache[spaces_len - 1]);
11334 col += spaces_len;
11335 changed = true;
11336 }
11337 _ => {
11338 // If we dont append before break, the character is consumed
11339 reindented_line.push(ch);
11340 break;
11341 }
11342 }
11343 }
11344
11345 if !changed {
11346 reindented_line.clear();
11347 continue;
11348 }
11349 // Append the rest of the line and replace old reference with new one
11350 reindented_line.extend(chars);
11351 *line = Cow::Owned(reindented_line.clone());
11352 reindented_line.clear();
11353 }
11354 });
11355 }
11356
11357 pub fn convert_indentation_to_tabs(
11358 &mut self,
11359 _: &ConvertIndentationToTabs,
11360 window: &mut Window,
11361 cx: &mut Context<Self>,
11362 ) {
11363 let settings = self.buffer.read(cx).language_settings(cx);
11364 let tab_size = settings.tab_size.get() as usize;
11365
11366 self.manipulate_mutable_lines(window, cx, |lines| {
11367 // Allocates a reasonably sized buffer once for the whole loop
11368 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11369 // Avoids recomputing spaces that could be inserted many times
11370 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11371 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11372 .collect();
11373
11374 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11375 let mut chars = line.chars();
11376 let mut spaces_count = 0;
11377 let mut first_non_indent_char = None;
11378 let mut changed = false;
11379
11380 for ch in chars.by_ref() {
11381 match ch {
11382 ' ' => {
11383 // Keep track of spaces. Append \t when we reach tab_size
11384 spaces_count += 1;
11385 changed = true;
11386 if spaces_count == tab_size {
11387 reindented_line.push('\t');
11388 spaces_count = 0;
11389 }
11390 }
11391 '\t' => {
11392 reindented_line.push('\t');
11393 spaces_count = 0;
11394 }
11395 _ => {
11396 // Dont append it yet, we might have remaining spaces
11397 first_non_indent_char = Some(ch);
11398 break;
11399 }
11400 }
11401 }
11402
11403 if !changed {
11404 reindented_line.clear();
11405 continue;
11406 }
11407 // Remaining spaces that didn't make a full tab stop
11408 if spaces_count > 0 {
11409 reindented_line.extend(&space_cache[spaces_count - 1]);
11410 }
11411 // If we consume an extra character that was not indentation, add it back
11412 if let Some(extra_char) = first_non_indent_char {
11413 reindented_line.push(extra_char);
11414 }
11415 // Append the rest of the line and replace old reference with new one
11416 reindented_line.extend(chars);
11417 *line = Cow::Owned(reindented_line.clone());
11418 reindented_line.clear();
11419 }
11420 });
11421 }
11422
11423 pub fn convert_to_upper_case(
11424 &mut self,
11425 _: &ConvertToUpperCase,
11426 window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 self.manipulate_text(window, cx, |text| text.to_uppercase())
11430 }
11431
11432 pub fn convert_to_lower_case(
11433 &mut self,
11434 _: &ConvertToLowerCase,
11435 window: &mut Window,
11436 cx: &mut Context<Self>,
11437 ) {
11438 self.manipulate_text(window, cx, |text| text.to_lowercase())
11439 }
11440
11441 pub fn convert_to_title_case(
11442 &mut self,
11443 _: &ConvertToTitleCase,
11444 window: &mut Window,
11445 cx: &mut Context<Self>,
11446 ) {
11447 self.manipulate_text(window, cx, |text| {
11448 text.split('\n')
11449 .map(|line| line.to_case(Case::Title))
11450 .join("\n")
11451 })
11452 }
11453
11454 pub fn convert_to_snake_case(
11455 &mut self,
11456 _: &ConvertToSnakeCase,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11461 }
11462
11463 pub fn convert_to_kebab_case(
11464 &mut self,
11465 _: &ConvertToKebabCase,
11466 window: &mut Window,
11467 cx: &mut Context<Self>,
11468 ) {
11469 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11470 }
11471
11472 pub fn convert_to_upper_camel_case(
11473 &mut self,
11474 _: &ConvertToUpperCamelCase,
11475 window: &mut Window,
11476 cx: &mut Context<Self>,
11477 ) {
11478 self.manipulate_text(window, cx, |text| {
11479 text.split('\n')
11480 .map(|line| line.to_case(Case::UpperCamel))
11481 .join("\n")
11482 })
11483 }
11484
11485 pub fn convert_to_lower_camel_case(
11486 &mut self,
11487 _: &ConvertToLowerCamelCase,
11488 window: &mut Window,
11489 cx: &mut Context<Self>,
11490 ) {
11491 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11492 }
11493
11494 pub fn convert_to_opposite_case(
11495 &mut self,
11496 _: &ConvertToOppositeCase,
11497 window: &mut Window,
11498 cx: &mut Context<Self>,
11499 ) {
11500 self.manipulate_text(window, cx, |text| {
11501 text.chars()
11502 .fold(String::with_capacity(text.len()), |mut t, c| {
11503 if c.is_uppercase() {
11504 t.extend(c.to_lowercase());
11505 } else {
11506 t.extend(c.to_uppercase());
11507 }
11508 t
11509 })
11510 })
11511 }
11512
11513 pub fn convert_to_sentence_case(
11514 &mut self,
11515 _: &ConvertToSentenceCase,
11516 window: &mut Window,
11517 cx: &mut Context<Self>,
11518 ) {
11519 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11520 }
11521
11522 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11523 self.manipulate_text(window, cx, |text| {
11524 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11525 if has_upper_case_characters {
11526 text.to_lowercase()
11527 } else {
11528 text.to_uppercase()
11529 }
11530 })
11531 }
11532
11533 pub fn convert_to_rot13(
11534 &mut self,
11535 _: &ConvertToRot13,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.manipulate_text(window, cx, |text| {
11540 text.chars()
11541 .map(|c| match c {
11542 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11543 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11544 _ => c,
11545 })
11546 .collect()
11547 })
11548 }
11549
11550 pub fn convert_to_rot47(
11551 &mut self,
11552 _: &ConvertToRot47,
11553 window: &mut Window,
11554 cx: &mut Context<Self>,
11555 ) {
11556 self.manipulate_text(window, cx, |text| {
11557 text.chars()
11558 .map(|c| {
11559 let code_point = c as u32;
11560 if code_point >= 33 && code_point <= 126 {
11561 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11562 }
11563 c
11564 })
11565 .collect()
11566 })
11567 }
11568
11569 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11570 where
11571 Fn: FnMut(&str) -> String,
11572 {
11573 let buffer = self.buffer.read(cx).snapshot(cx);
11574
11575 let mut new_selections = Vec::new();
11576 let mut edits = Vec::new();
11577 let mut selection_adjustment = 0i32;
11578
11579 for selection in self.selections.all_adjusted(cx) {
11580 let selection_is_empty = selection.is_empty();
11581
11582 let (start, end) = if selection_is_empty {
11583 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11584 (word_range.start, word_range.end)
11585 } else {
11586 (
11587 buffer.point_to_offset(selection.start),
11588 buffer.point_to_offset(selection.end),
11589 )
11590 };
11591
11592 let text = buffer.text_for_range(start..end).collect::<String>();
11593 let old_length = text.len() as i32;
11594 let text = callback(&text);
11595
11596 new_selections.push(Selection {
11597 start: (start as i32 - selection_adjustment) as usize,
11598 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11599 goal: SelectionGoal::None,
11600 id: selection.id,
11601 reversed: selection.reversed,
11602 });
11603
11604 selection_adjustment += old_length - text.len() as i32;
11605
11606 edits.push((start..end, text));
11607 }
11608
11609 self.transact(window, cx, |this, window, cx| {
11610 this.buffer.update(cx, |buffer, cx| {
11611 buffer.edit(edits, None, cx);
11612 });
11613
11614 this.change_selections(Default::default(), window, cx, |s| {
11615 s.select(new_selections);
11616 });
11617
11618 this.request_autoscroll(Autoscroll::fit(), cx);
11619 });
11620 }
11621
11622 pub fn move_selection_on_drop(
11623 &mut self,
11624 selection: &Selection<Anchor>,
11625 target: DisplayPoint,
11626 is_cut: bool,
11627 window: &mut Window,
11628 cx: &mut Context<Self>,
11629 ) {
11630 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11631 let buffer = display_map.buffer_snapshot();
11632 let mut edits = Vec::new();
11633 let insert_point = display_map
11634 .clip_point(target, Bias::Left)
11635 .to_point(&display_map);
11636 let text = buffer
11637 .text_for_range(selection.start..selection.end)
11638 .collect::<String>();
11639 if is_cut {
11640 edits.push(((selection.start..selection.end), String::new()));
11641 }
11642 let insert_anchor = buffer.anchor_before(insert_point);
11643 edits.push(((insert_anchor..insert_anchor), text));
11644 let last_edit_start = insert_anchor.bias_left(buffer);
11645 let last_edit_end = insert_anchor.bias_right(buffer);
11646 self.transact(window, cx, |this, window, cx| {
11647 this.buffer.update(cx, |buffer, cx| {
11648 buffer.edit(edits, None, cx);
11649 });
11650 this.change_selections(Default::default(), window, cx, |s| {
11651 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11652 });
11653 });
11654 }
11655
11656 pub fn clear_selection_drag_state(&mut self) {
11657 self.selection_drag_state = SelectionDragState::None;
11658 }
11659
11660 pub fn duplicate(
11661 &mut self,
11662 upwards: bool,
11663 whole_lines: bool,
11664 window: &mut Window,
11665 cx: &mut Context<Self>,
11666 ) {
11667 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11668
11669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11670 let buffer = display_map.buffer_snapshot();
11671 let selections = self.selections.all::<Point>(cx);
11672
11673 let mut edits = Vec::new();
11674 let mut selections_iter = selections.iter().peekable();
11675 while let Some(selection) = selections_iter.next() {
11676 let mut rows = selection.spanned_rows(false, &display_map);
11677 // duplicate line-wise
11678 if whole_lines || selection.start == selection.end {
11679 // Avoid duplicating the same lines twice.
11680 while let Some(next_selection) = selections_iter.peek() {
11681 let next_rows = next_selection.spanned_rows(false, &display_map);
11682 if next_rows.start < rows.end {
11683 rows.end = next_rows.end;
11684 selections_iter.next().unwrap();
11685 } else {
11686 break;
11687 }
11688 }
11689
11690 // Copy the text from the selected row region and splice it either at the start
11691 // or end of the region.
11692 let start = Point::new(rows.start.0, 0);
11693 let end = Point::new(
11694 rows.end.previous_row().0,
11695 buffer.line_len(rows.end.previous_row()),
11696 );
11697 let text = buffer
11698 .text_for_range(start..end)
11699 .chain(Some("\n"))
11700 .collect::<String>();
11701 let insert_location = if upwards {
11702 Point::new(rows.end.0, 0)
11703 } else {
11704 start
11705 };
11706 edits.push((insert_location..insert_location, text));
11707 } else {
11708 // duplicate character-wise
11709 let start = selection.start;
11710 let end = selection.end;
11711 let text = buffer.text_for_range(start..end).collect::<String>();
11712 edits.push((selection.end..selection.end, text));
11713 }
11714 }
11715
11716 self.transact(window, cx, |this, _, cx| {
11717 this.buffer.update(cx, |buffer, cx| {
11718 buffer.edit(edits, None, cx);
11719 });
11720
11721 this.request_autoscroll(Autoscroll::fit(), cx);
11722 });
11723 }
11724
11725 pub fn duplicate_line_up(
11726 &mut self,
11727 _: &DuplicateLineUp,
11728 window: &mut Window,
11729 cx: &mut Context<Self>,
11730 ) {
11731 self.duplicate(true, true, window, cx);
11732 }
11733
11734 pub fn duplicate_line_down(
11735 &mut self,
11736 _: &DuplicateLineDown,
11737 window: &mut Window,
11738 cx: &mut Context<Self>,
11739 ) {
11740 self.duplicate(false, true, window, cx);
11741 }
11742
11743 pub fn duplicate_selection(
11744 &mut self,
11745 _: &DuplicateSelection,
11746 window: &mut Window,
11747 cx: &mut Context<Self>,
11748 ) {
11749 self.duplicate(false, false, window, cx);
11750 }
11751
11752 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11754 if self.mode.is_single_line() {
11755 cx.propagate();
11756 return;
11757 }
11758
11759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11760 let buffer = self.buffer.read(cx).snapshot(cx);
11761
11762 let mut edits = Vec::new();
11763 let mut unfold_ranges = Vec::new();
11764 let mut refold_creases = Vec::new();
11765
11766 let selections = self.selections.all::<Point>(cx);
11767 let mut selections = selections.iter().peekable();
11768 let mut contiguous_row_selections = Vec::new();
11769 let mut new_selections = Vec::new();
11770
11771 while let Some(selection) = selections.next() {
11772 // Find all the selections that span a contiguous row range
11773 let (start_row, end_row) = consume_contiguous_rows(
11774 &mut contiguous_row_selections,
11775 selection,
11776 &display_map,
11777 &mut selections,
11778 );
11779
11780 // Move the text spanned by the row range to be before the line preceding the row range
11781 if start_row.0 > 0 {
11782 let range_to_move = Point::new(
11783 start_row.previous_row().0,
11784 buffer.line_len(start_row.previous_row()),
11785 )
11786 ..Point::new(
11787 end_row.previous_row().0,
11788 buffer.line_len(end_row.previous_row()),
11789 );
11790 let insertion_point = display_map
11791 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11792 .0;
11793
11794 // Don't move lines across excerpts
11795 if buffer
11796 .excerpt_containing(insertion_point..range_to_move.end)
11797 .is_some()
11798 {
11799 let text = buffer
11800 .text_for_range(range_to_move.clone())
11801 .flat_map(|s| s.chars())
11802 .skip(1)
11803 .chain(['\n'])
11804 .collect::<String>();
11805
11806 edits.push((
11807 buffer.anchor_after(range_to_move.start)
11808 ..buffer.anchor_before(range_to_move.end),
11809 String::new(),
11810 ));
11811 let insertion_anchor = buffer.anchor_after(insertion_point);
11812 edits.push((insertion_anchor..insertion_anchor, text));
11813
11814 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11815
11816 // Move selections up
11817 new_selections.extend(contiguous_row_selections.drain(..).map(
11818 |mut selection| {
11819 selection.start.row -= row_delta;
11820 selection.end.row -= row_delta;
11821 selection
11822 },
11823 ));
11824
11825 // Move folds up
11826 unfold_ranges.push(range_to_move.clone());
11827 for fold in display_map.folds_in_range(
11828 buffer.anchor_before(range_to_move.start)
11829 ..buffer.anchor_after(range_to_move.end),
11830 ) {
11831 let mut start = fold.range.start.to_point(&buffer);
11832 let mut end = fold.range.end.to_point(&buffer);
11833 start.row -= row_delta;
11834 end.row -= row_delta;
11835 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11836 }
11837 }
11838 }
11839
11840 // If we didn't move line(s), preserve the existing selections
11841 new_selections.append(&mut contiguous_row_selections);
11842 }
11843
11844 self.transact(window, cx, |this, window, cx| {
11845 this.unfold_ranges(&unfold_ranges, true, true, cx);
11846 this.buffer.update(cx, |buffer, cx| {
11847 for (range, text) in edits {
11848 buffer.edit([(range, text)], None, cx);
11849 }
11850 });
11851 this.fold_creases(refold_creases, true, window, cx);
11852 this.change_selections(Default::default(), window, cx, |s| {
11853 s.select(new_selections);
11854 })
11855 });
11856 }
11857
11858 pub fn move_line_down(
11859 &mut self,
11860 _: &MoveLineDown,
11861 window: &mut Window,
11862 cx: &mut Context<Self>,
11863 ) {
11864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11865 if self.mode.is_single_line() {
11866 cx.propagate();
11867 return;
11868 }
11869
11870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11871 let buffer = self.buffer.read(cx).snapshot(cx);
11872
11873 let mut edits = Vec::new();
11874 let mut unfold_ranges = Vec::new();
11875 let mut refold_creases = Vec::new();
11876
11877 let selections = self.selections.all::<Point>(cx);
11878 let mut selections = selections.iter().peekable();
11879 let mut contiguous_row_selections = Vec::new();
11880 let mut new_selections = Vec::new();
11881
11882 while let Some(selection) = selections.next() {
11883 // Find all the selections that span a contiguous row range
11884 let (start_row, end_row) = consume_contiguous_rows(
11885 &mut contiguous_row_selections,
11886 selection,
11887 &display_map,
11888 &mut selections,
11889 );
11890
11891 // Move the text spanned by the row range to be after the last line of the row range
11892 if end_row.0 <= buffer.max_point().row {
11893 let range_to_move =
11894 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11895 let insertion_point = display_map
11896 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11897 .0;
11898
11899 // Don't move lines across excerpt boundaries
11900 if buffer
11901 .excerpt_containing(range_to_move.start..insertion_point)
11902 .is_some()
11903 {
11904 let mut text = String::from("\n");
11905 text.extend(buffer.text_for_range(range_to_move.clone()));
11906 text.pop(); // Drop trailing newline
11907 edits.push((
11908 buffer.anchor_after(range_to_move.start)
11909 ..buffer.anchor_before(range_to_move.end),
11910 String::new(),
11911 ));
11912 let insertion_anchor = buffer.anchor_after(insertion_point);
11913 edits.push((insertion_anchor..insertion_anchor, text));
11914
11915 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11916
11917 // Move selections down
11918 new_selections.extend(contiguous_row_selections.drain(..).map(
11919 |mut selection| {
11920 selection.start.row += row_delta;
11921 selection.end.row += row_delta;
11922 selection
11923 },
11924 ));
11925
11926 // Move folds down
11927 unfold_ranges.push(range_to_move.clone());
11928 for fold in display_map.folds_in_range(
11929 buffer.anchor_before(range_to_move.start)
11930 ..buffer.anchor_after(range_to_move.end),
11931 ) {
11932 let mut start = fold.range.start.to_point(&buffer);
11933 let mut end = fold.range.end.to_point(&buffer);
11934 start.row += row_delta;
11935 end.row += row_delta;
11936 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11937 }
11938 }
11939 }
11940
11941 // If we didn't move line(s), preserve the existing selections
11942 new_selections.append(&mut contiguous_row_selections);
11943 }
11944
11945 self.transact(window, cx, |this, window, cx| {
11946 this.unfold_ranges(&unfold_ranges, true, true, cx);
11947 this.buffer.update(cx, |buffer, cx| {
11948 for (range, text) in edits {
11949 buffer.edit([(range, text)], None, cx);
11950 }
11951 });
11952 this.fold_creases(refold_creases, true, window, cx);
11953 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11954 });
11955 }
11956
11957 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11958 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11959 let text_layout_details = &self.text_layout_details(window);
11960 self.transact(window, cx, |this, window, cx| {
11961 let edits = this.change_selections(Default::default(), window, cx, |s| {
11962 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11963 s.move_with(|display_map, selection| {
11964 if !selection.is_empty() {
11965 return;
11966 }
11967
11968 let mut head = selection.head();
11969 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11970 if head.column() == display_map.line_len(head.row()) {
11971 transpose_offset = display_map
11972 .buffer_snapshot()
11973 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11974 }
11975
11976 if transpose_offset == 0 {
11977 return;
11978 }
11979
11980 *head.column_mut() += 1;
11981 head = display_map.clip_point(head, Bias::Right);
11982 let goal = SelectionGoal::HorizontalPosition(
11983 display_map
11984 .x_for_display_point(head, text_layout_details)
11985 .into(),
11986 );
11987 selection.collapse_to(head, goal);
11988
11989 let transpose_start = display_map
11990 .buffer_snapshot()
11991 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11992 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11993 let transpose_end = display_map
11994 .buffer_snapshot()
11995 .clip_offset(transpose_offset + 1, Bias::Right);
11996 if let Some(ch) = display_map
11997 .buffer_snapshot()
11998 .chars_at(transpose_start)
11999 .next()
12000 {
12001 edits.push((transpose_start..transpose_offset, String::new()));
12002 edits.push((transpose_end..transpose_end, ch.to_string()));
12003 }
12004 }
12005 });
12006 edits
12007 });
12008 this.buffer
12009 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12010 let selections = this.selections.all::<usize>(cx);
12011 this.change_selections(Default::default(), window, cx, |s| {
12012 s.select(selections);
12013 });
12014 });
12015 }
12016
12017 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12019 if self.mode.is_single_line() {
12020 cx.propagate();
12021 return;
12022 }
12023
12024 self.rewrap_impl(RewrapOptions::default(), cx)
12025 }
12026
12027 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12028 let buffer = self.buffer.read(cx).snapshot(cx);
12029 let selections = self.selections.all::<Point>(cx);
12030
12031 #[derive(Clone, Debug, PartialEq)]
12032 enum CommentFormat {
12033 /// single line comment, with prefix for line
12034 Line(String),
12035 /// single line within a block comment, with prefix for line
12036 BlockLine(String),
12037 /// a single line of a block comment that includes the initial delimiter
12038 BlockCommentWithStart(BlockCommentConfig),
12039 /// a single line of a block comment that includes the ending delimiter
12040 BlockCommentWithEnd(BlockCommentConfig),
12041 }
12042
12043 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12044 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12045 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12046 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12047 .peekable();
12048
12049 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12050 row
12051 } else {
12052 return Vec::new();
12053 };
12054
12055 let language_settings = buffer.language_settings_at(selection.head(), cx);
12056 let language_scope = buffer.language_scope_at(selection.head());
12057
12058 let indent_and_prefix_for_row =
12059 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12060 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12061 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12062 &language_scope
12063 {
12064 let indent_end = Point::new(row, indent.len);
12065 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12066 let line_text_after_indent = buffer
12067 .text_for_range(indent_end..line_end)
12068 .collect::<String>();
12069
12070 let is_within_comment_override = buffer
12071 .language_scope_at(indent_end)
12072 .is_some_and(|scope| scope.override_name() == Some("comment"));
12073 let comment_delimiters = if is_within_comment_override {
12074 // we are within a comment syntax node, but we don't
12075 // yet know what kind of comment: block, doc or line
12076 match (
12077 language_scope.documentation_comment(),
12078 language_scope.block_comment(),
12079 ) {
12080 (Some(config), _) | (_, Some(config))
12081 if buffer.contains_str_at(indent_end, &config.start) =>
12082 {
12083 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12084 }
12085 (Some(config), _) | (_, Some(config))
12086 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12087 {
12088 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12089 }
12090 (Some(config), _) | (_, Some(config))
12091 if buffer.contains_str_at(indent_end, &config.prefix) =>
12092 {
12093 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12094 }
12095 (_, _) => language_scope
12096 .line_comment_prefixes()
12097 .iter()
12098 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12099 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12100 }
12101 } else {
12102 // we not in an overridden comment node, but we may
12103 // be within a non-overridden line comment node
12104 language_scope
12105 .line_comment_prefixes()
12106 .iter()
12107 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12108 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12109 };
12110
12111 let rewrap_prefix = language_scope
12112 .rewrap_prefixes()
12113 .iter()
12114 .find_map(|prefix_regex| {
12115 prefix_regex.find(&line_text_after_indent).map(|mat| {
12116 if mat.start() == 0 {
12117 Some(mat.as_str().to_string())
12118 } else {
12119 None
12120 }
12121 })
12122 })
12123 .flatten();
12124 (comment_delimiters, rewrap_prefix)
12125 } else {
12126 (None, None)
12127 };
12128 (indent, comment_prefix, rewrap_prefix)
12129 };
12130
12131 let mut ranges = Vec::new();
12132 let from_empty_selection = selection.is_empty();
12133
12134 let mut current_range_start = first_row;
12135 let mut prev_row = first_row;
12136 let (
12137 mut current_range_indent,
12138 mut current_range_comment_delimiters,
12139 mut current_range_rewrap_prefix,
12140 ) = indent_and_prefix_for_row(first_row);
12141
12142 for row in non_blank_rows_iter.skip(1) {
12143 let has_paragraph_break = row > prev_row + 1;
12144
12145 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12146 indent_and_prefix_for_row(row);
12147
12148 let has_indent_change = row_indent != current_range_indent;
12149 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12150
12151 let has_boundary_change = has_comment_change
12152 || row_rewrap_prefix.is_some()
12153 || (has_indent_change && current_range_comment_delimiters.is_some());
12154
12155 if has_paragraph_break || has_boundary_change {
12156 ranges.push((
12157 language_settings.clone(),
12158 Point::new(current_range_start, 0)
12159 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12160 current_range_indent,
12161 current_range_comment_delimiters.clone(),
12162 current_range_rewrap_prefix.clone(),
12163 from_empty_selection,
12164 ));
12165 current_range_start = row;
12166 current_range_indent = row_indent;
12167 current_range_comment_delimiters = row_comment_delimiters;
12168 current_range_rewrap_prefix = row_rewrap_prefix;
12169 }
12170 prev_row = row;
12171 }
12172
12173 ranges.push((
12174 language_settings.clone(),
12175 Point::new(current_range_start, 0)
12176 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12177 current_range_indent,
12178 current_range_comment_delimiters,
12179 current_range_rewrap_prefix,
12180 from_empty_selection,
12181 ));
12182
12183 ranges
12184 });
12185
12186 let mut edits = Vec::new();
12187 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12188
12189 for (
12190 language_settings,
12191 wrap_range,
12192 mut indent_size,
12193 comment_prefix,
12194 rewrap_prefix,
12195 from_empty_selection,
12196 ) in wrap_ranges
12197 {
12198 let mut start_row = wrap_range.start.row;
12199 let mut end_row = wrap_range.end.row;
12200
12201 // Skip selections that overlap with a range that has already been rewrapped.
12202 let selection_range = start_row..end_row;
12203 if rewrapped_row_ranges
12204 .iter()
12205 .any(|range| range.overlaps(&selection_range))
12206 {
12207 continue;
12208 }
12209
12210 let tab_size = language_settings.tab_size;
12211
12212 let (line_prefix, inside_comment) = match &comment_prefix {
12213 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12214 (Some(prefix.as_str()), true)
12215 }
12216 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12217 (Some(prefix.as_ref()), true)
12218 }
12219 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12220 start: _,
12221 end: _,
12222 prefix,
12223 tab_size,
12224 })) => {
12225 indent_size.len += tab_size;
12226 (Some(prefix.as_ref()), true)
12227 }
12228 None => (None, false),
12229 };
12230 let indent_prefix = indent_size.chars().collect::<String>();
12231 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12232
12233 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12234 RewrapBehavior::InComments => inside_comment,
12235 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12236 RewrapBehavior::Anywhere => true,
12237 };
12238
12239 let should_rewrap = options.override_language_settings
12240 || allow_rewrap_based_on_language
12241 || self.hard_wrap.is_some();
12242 if !should_rewrap {
12243 continue;
12244 }
12245
12246 if from_empty_selection {
12247 'expand_upwards: while start_row > 0 {
12248 let prev_row = start_row - 1;
12249 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12250 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12251 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12252 {
12253 start_row = prev_row;
12254 } else {
12255 break 'expand_upwards;
12256 }
12257 }
12258
12259 'expand_downwards: while end_row < buffer.max_point().row {
12260 let next_row = end_row + 1;
12261 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12262 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12263 && !buffer.is_line_blank(MultiBufferRow(next_row))
12264 {
12265 end_row = next_row;
12266 } else {
12267 break 'expand_downwards;
12268 }
12269 }
12270 }
12271
12272 let start = Point::new(start_row, 0);
12273 let start_offset = ToOffset::to_offset(&start, &buffer);
12274 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12275 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12276 let mut first_line_delimiter = None;
12277 let mut last_line_delimiter = None;
12278 let Some(lines_without_prefixes) = selection_text
12279 .lines()
12280 .enumerate()
12281 .map(|(ix, line)| {
12282 let line_trimmed = line.trim_start();
12283 if rewrap_prefix.is_some() && ix > 0 {
12284 Ok(line_trimmed)
12285 } else if let Some(
12286 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12287 start,
12288 prefix,
12289 end,
12290 tab_size,
12291 })
12292 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12293 start,
12294 prefix,
12295 end,
12296 tab_size,
12297 }),
12298 ) = &comment_prefix
12299 {
12300 let line_trimmed = line_trimmed
12301 .strip_prefix(start.as_ref())
12302 .map(|s| {
12303 let mut indent_size = indent_size;
12304 indent_size.len -= tab_size;
12305 let indent_prefix: String = indent_size.chars().collect();
12306 first_line_delimiter = Some((indent_prefix, start));
12307 s.trim_start()
12308 })
12309 .unwrap_or(line_trimmed);
12310 let line_trimmed = line_trimmed
12311 .strip_suffix(end.as_ref())
12312 .map(|s| {
12313 last_line_delimiter = Some(end);
12314 s.trim_end()
12315 })
12316 .unwrap_or(line_trimmed);
12317 let line_trimmed = line_trimmed
12318 .strip_prefix(prefix.as_ref())
12319 .unwrap_or(line_trimmed);
12320 Ok(line_trimmed)
12321 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12322 line_trimmed.strip_prefix(prefix).with_context(|| {
12323 format!("line did not start with prefix {prefix:?}: {line:?}")
12324 })
12325 } else {
12326 line_trimmed
12327 .strip_prefix(&line_prefix.trim_start())
12328 .with_context(|| {
12329 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12330 })
12331 }
12332 })
12333 .collect::<Result<Vec<_>, _>>()
12334 .log_err()
12335 else {
12336 continue;
12337 };
12338
12339 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12340 buffer
12341 .language_settings_at(Point::new(start_row, 0), cx)
12342 .preferred_line_length as usize
12343 });
12344
12345 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12346 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12347 } else {
12348 line_prefix.clone()
12349 };
12350
12351 let wrapped_text = {
12352 let mut wrapped_text = wrap_with_prefix(
12353 line_prefix,
12354 subsequent_lines_prefix,
12355 lines_without_prefixes.join("\n"),
12356 wrap_column,
12357 tab_size,
12358 options.preserve_existing_whitespace,
12359 );
12360
12361 if let Some((indent, delimiter)) = first_line_delimiter {
12362 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12363 }
12364 if let Some(last_line) = last_line_delimiter {
12365 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12366 }
12367
12368 wrapped_text
12369 };
12370
12371 // TODO: should always use char-based diff while still supporting cursor behavior that
12372 // matches vim.
12373 let mut diff_options = DiffOptions::default();
12374 if options.override_language_settings {
12375 diff_options.max_word_diff_len = 0;
12376 diff_options.max_word_diff_line_count = 0;
12377 } else {
12378 diff_options.max_word_diff_len = usize::MAX;
12379 diff_options.max_word_diff_line_count = usize::MAX;
12380 }
12381
12382 for (old_range, new_text) in
12383 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12384 {
12385 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12386 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12387 edits.push((edit_start..edit_end, new_text));
12388 }
12389
12390 rewrapped_row_ranges.push(start_row..=end_row);
12391 }
12392
12393 self.buffer
12394 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12395 }
12396
12397 pub fn cut_common(
12398 &mut self,
12399 cut_no_selection_line: bool,
12400 window: &mut Window,
12401 cx: &mut Context<Self>,
12402 ) -> ClipboardItem {
12403 let mut text = String::new();
12404 let buffer = self.buffer.read(cx).snapshot(cx);
12405 let mut selections = self.selections.all::<Point>(cx);
12406 let mut clipboard_selections = Vec::with_capacity(selections.len());
12407 {
12408 let max_point = buffer.max_point();
12409 let mut is_first = true;
12410 for selection in &mut selections {
12411 let is_entire_line =
12412 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12413 if is_entire_line {
12414 selection.start = Point::new(selection.start.row, 0);
12415 if !selection.is_empty() && selection.end.column == 0 {
12416 selection.end = cmp::min(max_point, selection.end);
12417 } else {
12418 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12419 }
12420 selection.goal = SelectionGoal::None;
12421 }
12422 if is_first {
12423 is_first = false;
12424 } else {
12425 text += "\n";
12426 }
12427 let mut len = 0;
12428 for chunk in buffer.text_for_range(selection.start..selection.end) {
12429 text.push_str(chunk);
12430 len += chunk.len();
12431 }
12432 clipboard_selections.push(ClipboardSelection {
12433 len,
12434 is_entire_line,
12435 first_line_indent: buffer
12436 .indent_size_for_line(MultiBufferRow(selection.start.row))
12437 .len,
12438 });
12439 }
12440 }
12441
12442 self.transact(window, cx, |this, window, cx| {
12443 this.change_selections(Default::default(), window, cx, |s| {
12444 s.select(selections);
12445 });
12446 this.insert("", window, cx);
12447 });
12448 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12449 }
12450
12451 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12452 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12453 let item = self.cut_common(true, window, cx);
12454 cx.write_to_clipboard(item);
12455 }
12456
12457 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12459 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12460 s.move_with(|snapshot, sel| {
12461 if sel.is_empty() {
12462 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12463 }
12464 if sel.is_empty() {
12465 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12466 }
12467 });
12468 });
12469 let item = self.cut_common(false, window, cx);
12470 cx.set_global(KillRing(item))
12471 }
12472
12473 pub fn kill_ring_yank(
12474 &mut self,
12475 _: &KillRingYank,
12476 window: &mut Window,
12477 cx: &mut Context<Self>,
12478 ) {
12479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12480 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12481 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12482 (kill_ring.text().to_string(), kill_ring.metadata_json())
12483 } else {
12484 return;
12485 }
12486 } else {
12487 return;
12488 };
12489 self.do_paste(&text, metadata, false, window, cx);
12490 }
12491
12492 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12493 self.do_copy(true, cx);
12494 }
12495
12496 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12497 self.do_copy(false, cx);
12498 }
12499
12500 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12501 let selections = self.selections.all::<Point>(cx);
12502 let buffer = self.buffer.read(cx).read(cx);
12503 let mut text = String::new();
12504
12505 let mut clipboard_selections = Vec::with_capacity(selections.len());
12506 {
12507 let max_point = buffer.max_point();
12508 let mut is_first = true;
12509 for selection in &selections {
12510 let mut start = selection.start;
12511 let mut end = selection.end;
12512 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12513 if is_entire_line {
12514 start = Point::new(start.row, 0);
12515 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12516 }
12517
12518 let mut trimmed_selections = Vec::new();
12519 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12520 let row = MultiBufferRow(start.row);
12521 let first_indent = buffer.indent_size_for_line(row);
12522 if first_indent.len == 0 || start.column > first_indent.len {
12523 trimmed_selections.push(start..end);
12524 } else {
12525 trimmed_selections.push(
12526 Point::new(row.0, first_indent.len)
12527 ..Point::new(row.0, buffer.line_len(row)),
12528 );
12529 for row in start.row + 1..=end.row {
12530 let mut line_len = buffer.line_len(MultiBufferRow(row));
12531 if row == end.row {
12532 line_len = end.column;
12533 }
12534 if line_len == 0 {
12535 trimmed_selections
12536 .push(Point::new(row, 0)..Point::new(row, line_len));
12537 continue;
12538 }
12539 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12540 if row_indent_size.len >= first_indent.len {
12541 trimmed_selections.push(
12542 Point::new(row, first_indent.len)..Point::new(row, line_len),
12543 );
12544 } else {
12545 trimmed_selections.clear();
12546 trimmed_selections.push(start..end);
12547 break;
12548 }
12549 }
12550 }
12551 } else {
12552 trimmed_selections.push(start..end);
12553 }
12554
12555 for trimmed_range in trimmed_selections {
12556 if is_first {
12557 is_first = false;
12558 } else {
12559 text += "\n";
12560 }
12561 let mut len = 0;
12562 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12563 text.push_str(chunk);
12564 len += chunk.len();
12565 }
12566 clipboard_selections.push(ClipboardSelection {
12567 len,
12568 is_entire_line,
12569 first_line_indent: buffer
12570 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12571 .len,
12572 });
12573 }
12574 }
12575 }
12576
12577 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12578 text,
12579 clipboard_selections,
12580 ));
12581 }
12582
12583 pub fn do_paste(
12584 &mut self,
12585 text: &String,
12586 clipboard_selections: Option<Vec<ClipboardSelection>>,
12587 handle_entire_lines: bool,
12588 window: &mut Window,
12589 cx: &mut Context<Self>,
12590 ) {
12591 if self.read_only(cx) {
12592 return;
12593 }
12594
12595 let clipboard_text = Cow::Borrowed(text.as_str());
12596
12597 self.transact(window, cx, |this, window, cx| {
12598 let had_active_edit_prediction = this.has_active_edit_prediction();
12599 let old_selections = this.selections.all::<usize>(cx);
12600 let cursor_offset = this.selections.last::<usize>(cx).head();
12601
12602 if let Some(mut clipboard_selections) = clipboard_selections {
12603 let all_selections_were_entire_line =
12604 clipboard_selections.iter().all(|s| s.is_entire_line);
12605 let first_selection_indent_column =
12606 clipboard_selections.first().map(|s| s.first_line_indent);
12607 if clipboard_selections.len() != old_selections.len() {
12608 clipboard_selections.drain(..);
12609 }
12610 let mut auto_indent_on_paste = true;
12611
12612 this.buffer.update(cx, |buffer, cx| {
12613 let snapshot = buffer.read(cx);
12614 auto_indent_on_paste = snapshot
12615 .language_settings_at(cursor_offset, cx)
12616 .auto_indent_on_paste;
12617
12618 let mut start_offset = 0;
12619 let mut edits = Vec::new();
12620 let mut original_indent_columns = Vec::new();
12621 for (ix, selection) in old_selections.iter().enumerate() {
12622 let to_insert;
12623 let entire_line;
12624 let original_indent_column;
12625 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12626 let end_offset = start_offset + clipboard_selection.len;
12627 to_insert = &clipboard_text[start_offset..end_offset];
12628 entire_line = clipboard_selection.is_entire_line;
12629 start_offset = end_offset + 1;
12630 original_indent_column = Some(clipboard_selection.first_line_indent);
12631 } else {
12632 to_insert = &*clipboard_text;
12633 entire_line = all_selections_were_entire_line;
12634 original_indent_column = first_selection_indent_column
12635 }
12636
12637 let (range, to_insert) =
12638 if selection.is_empty() && handle_entire_lines && entire_line {
12639 // If the corresponding selection was empty when this slice of the
12640 // clipboard text was written, then the entire line containing the
12641 // selection was copied. If this selection is also currently empty,
12642 // then paste the line before the current line of the buffer.
12643 let column = selection.start.to_point(&snapshot).column as usize;
12644 let line_start = selection.start - column;
12645 (line_start..line_start, Cow::Borrowed(to_insert))
12646 } else {
12647 let language = snapshot.language_at(selection.head());
12648 let range = selection.range();
12649 if let Some(language) = language
12650 && language.name() == "Markdown".into()
12651 {
12652 edit_for_markdown_paste(
12653 &snapshot,
12654 range,
12655 to_insert,
12656 url::Url::parse(to_insert).ok(),
12657 )
12658 } else {
12659 (range, Cow::Borrowed(to_insert))
12660 }
12661 };
12662
12663 edits.push((range, to_insert));
12664 original_indent_columns.push(original_indent_column);
12665 }
12666 drop(snapshot);
12667
12668 buffer.edit(
12669 edits,
12670 if auto_indent_on_paste {
12671 Some(AutoindentMode::Block {
12672 original_indent_columns,
12673 })
12674 } else {
12675 None
12676 },
12677 cx,
12678 );
12679 });
12680
12681 let selections = this.selections.all::<usize>(cx);
12682 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12683 } else {
12684 let url = url::Url::parse(&clipboard_text).ok();
12685
12686 let auto_indent_mode = if !clipboard_text.is_empty() {
12687 Some(AutoindentMode::Block {
12688 original_indent_columns: Vec::new(),
12689 })
12690 } else {
12691 None
12692 };
12693
12694 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12695 let snapshot = buffer.snapshot(cx);
12696
12697 let anchors = old_selections
12698 .iter()
12699 .map(|s| {
12700 let anchor = snapshot.anchor_after(s.head());
12701 s.map(|_| anchor)
12702 })
12703 .collect::<Vec<_>>();
12704
12705 let mut edits = Vec::new();
12706
12707 for selection in old_selections.iter() {
12708 let language = snapshot.language_at(selection.head());
12709 let range = selection.range();
12710
12711 let (edit_range, edit_text) = if let Some(language) = language
12712 && language.name() == "Markdown".into()
12713 {
12714 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12715 } else {
12716 (range, clipboard_text.clone())
12717 };
12718
12719 edits.push((edit_range, edit_text));
12720 }
12721
12722 drop(snapshot);
12723 buffer.edit(edits, auto_indent_mode, cx);
12724
12725 anchors
12726 });
12727
12728 this.change_selections(Default::default(), window, cx, |s| {
12729 s.select_anchors(selection_anchors);
12730 });
12731 }
12732
12733 let trigger_in_words =
12734 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12735
12736 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12737 });
12738 }
12739
12740 pub fn diff_clipboard_with_selection(
12741 &mut self,
12742 _: &DiffClipboardWithSelection,
12743 window: &mut Window,
12744 cx: &mut Context<Self>,
12745 ) {
12746 let selections = self.selections.all::<usize>(cx);
12747
12748 if selections.is_empty() {
12749 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12750 return;
12751 };
12752
12753 let clipboard_text = match cx.read_from_clipboard() {
12754 Some(item) => match item.entries().first() {
12755 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12756 _ => None,
12757 },
12758 None => None,
12759 };
12760
12761 let Some(clipboard_text) = clipboard_text else {
12762 log::warn!("Clipboard doesn't contain text.");
12763 return;
12764 };
12765
12766 window.dispatch_action(
12767 Box::new(DiffClipboardWithSelectionData {
12768 clipboard_text,
12769 editor: cx.entity(),
12770 }),
12771 cx,
12772 );
12773 }
12774
12775 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12776 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12777 if let Some(item) = cx.read_from_clipboard() {
12778 let entries = item.entries();
12779
12780 match entries.first() {
12781 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12782 // of all the pasted entries.
12783 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12784 .do_paste(
12785 clipboard_string.text(),
12786 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12787 true,
12788 window,
12789 cx,
12790 ),
12791 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12792 }
12793 }
12794 }
12795
12796 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12797 if self.read_only(cx) {
12798 return;
12799 }
12800
12801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12802
12803 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12804 if let Some((selections, _)) =
12805 self.selection_history.transaction(transaction_id).cloned()
12806 {
12807 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12808 s.select_anchors(selections.to_vec());
12809 });
12810 } else {
12811 log::error!(
12812 "No entry in selection_history found for undo. \
12813 This may correspond to a bug where undo does not update the selection. \
12814 If this is occurring, please add details to \
12815 https://github.com/zed-industries/zed/issues/22692"
12816 );
12817 }
12818 self.request_autoscroll(Autoscroll::fit(), cx);
12819 self.unmark_text(window, cx);
12820 self.refresh_edit_prediction(true, false, window, cx);
12821 cx.emit(EditorEvent::Edited { transaction_id });
12822 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12823 }
12824 }
12825
12826 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12827 if self.read_only(cx) {
12828 return;
12829 }
12830
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12832
12833 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12834 if let Some((_, Some(selections))) =
12835 self.selection_history.transaction(transaction_id).cloned()
12836 {
12837 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12838 s.select_anchors(selections.to_vec());
12839 });
12840 } else {
12841 log::error!(
12842 "No entry in selection_history found for redo. \
12843 This may correspond to a bug where undo does not update the selection. \
12844 If this is occurring, please add details to \
12845 https://github.com/zed-industries/zed/issues/22692"
12846 );
12847 }
12848 self.request_autoscroll(Autoscroll::fit(), cx);
12849 self.unmark_text(window, cx);
12850 self.refresh_edit_prediction(true, false, window, cx);
12851 cx.emit(EditorEvent::Edited { transaction_id });
12852 }
12853 }
12854
12855 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12856 self.buffer
12857 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12858 }
12859
12860 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12861 self.buffer
12862 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12863 }
12864
12865 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12867 self.change_selections(Default::default(), window, cx, |s| {
12868 s.move_with(|map, selection| {
12869 let cursor = if selection.is_empty() {
12870 movement::left(map, selection.start)
12871 } else {
12872 selection.start
12873 };
12874 selection.collapse_to(cursor, SelectionGoal::None);
12875 });
12876 })
12877 }
12878
12879 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12881 self.change_selections(Default::default(), window, cx, |s| {
12882 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12883 })
12884 }
12885
12886 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12888 self.change_selections(Default::default(), window, cx, |s| {
12889 s.move_with(|map, selection| {
12890 let cursor = if selection.is_empty() {
12891 movement::right(map, selection.end)
12892 } else {
12893 selection.end
12894 };
12895 selection.collapse_to(cursor, SelectionGoal::None)
12896 });
12897 })
12898 }
12899
12900 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12902 self.change_selections(Default::default(), window, cx, |s| {
12903 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12904 });
12905 }
12906
12907 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12908 if self.take_rename(true, window, cx).is_some() {
12909 return;
12910 }
12911
12912 if self.mode.is_single_line() {
12913 cx.propagate();
12914 return;
12915 }
12916
12917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12918
12919 let text_layout_details = &self.text_layout_details(window);
12920 let selection_count = self.selections.count();
12921 let first_selection = self.selections.first_anchor();
12922
12923 self.change_selections(Default::default(), window, cx, |s| {
12924 s.move_with(|map, selection| {
12925 if !selection.is_empty() {
12926 selection.goal = SelectionGoal::None;
12927 }
12928 let (cursor, goal) = movement::up(
12929 map,
12930 selection.start,
12931 selection.goal,
12932 false,
12933 text_layout_details,
12934 );
12935 selection.collapse_to(cursor, goal);
12936 });
12937 });
12938
12939 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12940 {
12941 cx.propagate();
12942 }
12943 }
12944
12945 pub fn move_up_by_lines(
12946 &mut self,
12947 action: &MoveUpByLines,
12948 window: &mut Window,
12949 cx: &mut Context<Self>,
12950 ) {
12951 if self.take_rename(true, window, cx).is_some() {
12952 return;
12953 }
12954
12955 if self.mode.is_single_line() {
12956 cx.propagate();
12957 return;
12958 }
12959
12960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12961
12962 let text_layout_details = &self.text_layout_details(window);
12963
12964 self.change_selections(Default::default(), window, cx, |s| {
12965 s.move_with(|map, selection| {
12966 if !selection.is_empty() {
12967 selection.goal = SelectionGoal::None;
12968 }
12969 let (cursor, goal) = movement::up_by_rows(
12970 map,
12971 selection.start,
12972 action.lines,
12973 selection.goal,
12974 false,
12975 text_layout_details,
12976 );
12977 selection.collapse_to(cursor, goal);
12978 });
12979 })
12980 }
12981
12982 pub fn move_down_by_lines(
12983 &mut self,
12984 action: &MoveDownByLines,
12985 window: &mut Window,
12986 cx: &mut Context<Self>,
12987 ) {
12988 if self.take_rename(true, window, cx).is_some() {
12989 return;
12990 }
12991
12992 if self.mode.is_single_line() {
12993 cx.propagate();
12994 return;
12995 }
12996
12997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12998
12999 let text_layout_details = &self.text_layout_details(window);
13000
13001 self.change_selections(Default::default(), window, cx, |s| {
13002 s.move_with(|map, selection| {
13003 if !selection.is_empty() {
13004 selection.goal = SelectionGoal::None;
13005 }
13006 let (cursor, goal) = movement::down_by_rows(
13007 map,
13008 selection.start,
13009 action.lines,
13010 selection.goal,
13011 false,
13012 text_layout_details,
13013 );
13014 selection.collapse_to(cursor, goal);
13015 });
13016 })
13017 }
13018
13019 pub fn select_down_by_lines(
13020 &mut self,
13021 action: &SelectDownByLines,
13022 window: &mut Window,
13023 cx: &mut Context<Self>,
13024 ) {
13025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13026 let text_layout_details = &self.text_layout_details(window);
13027 self.change_selections(Default::default(), window, cx, |s| {
13028 s.move_heads_with(|map, head, goal| {
13029 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13030 })
13031 })
13032 }
13033
13034 pub fn select_up_by_lines(
13035 &mut self,
13036 action: &SelectUpByLines,
13037 window: &mut Window,
13038 cx: &mut Context<Self>,
13039 ) {
13040 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13041 let text_layout_details = &self.text_layout_details(window);
13042 self.change_selections(Default::default(), window, cx, |s| {
13043 s.move_heads_with(|map, head, goal| {
13044 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13045 })
13046 })
13047 }
13048
13049 pub fn select_page_up(
13050 &mut self,
13051 _: &SelectPageUp,
13052 window: &mut Window,
13053 cx: &mut Context<Self>,
13054 ) {
13055 let Some(row_count) = self.visible_row_count() else {
13056 return;
13057 };
13058
13059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13060
13061 let text_layout_details = &self.text_layout_details(window);
13062
13063 self.change_selections(Default::default(), window, cx, |s| {
13064 s.move_heads_with(|map, head, goal| {
13065 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13066 })
13067 })
13068 }
13069
13070 pub fn move_page_up(
13071 &mut self,
13072 action: &MovePageUp,
13073 window: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 if self.take_rename(true, window, cx).is_some() {
13077 return;
13078 }
13079
13080 if self
13081 .context_menu
13082 .borrow_mut()
13083 .as_mut()
13084 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13085 .unwrap_or(false)
13086 {
13087 return;
13088 }
13089
13090 if matches!(self.mode, EditorMode::SingleLine) {
13091 cx.propagate();
13092 return;
13093 }
13094
13095 let Some(row_count) = self.visible_row_count() else {
13096 return;
13097 };
13098
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100
13101 let effects = if action.center_cursor {
13102 SelectionEffects::scroll(Autoscroll::center())
13103 } else {
13104 SelectionEffects::default()
13105 };
13106
13107 let text_layout_details = &self.text_layout_details(window);
13108
13109 self.change_selections(effects, window, cx, |s| {
13110 s.move_with(|map, selection| {
13111 if !selection.is_empty() {
13112 selection.goal = SelectionGoal::None;
13113 }
13114 let (cursor, goal) = movement::up_by_rows(
13115 map,
13116 selection.end,
13117 row_count,
13118 selection.goal,
13119 false,
13120 text_layout_details,
13121 );
13122 selection.collapse_to(cursor, goal);
13123 });
13124 });
13125 }
13126
13127 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13129 let text_layout_details = &self.text_layout_details(window);
13130 self.change_selections(Default::default(), window, cx, |s| {
13131 s.move_heads_with(|map, head, goal| {
13132 movement::up(map, head, goal, false, text_layout_details)
13133 })
13134 })
13135 }
13136
13137 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13138 self.take_rename(true, window, cx);
13139
13140 if self.mode.is_single_line() {
13141 cx.propagate();
13142 return;
13143 }
13144
13145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13146
13147 let text_layout_details = &self.text_layout_details(window);
13148 let selection_count = self.selections.count();
13149 let first_selection = self.selections.first_anchor();
13150
13151 self.change_selections(Default::default(), window, cx, |s| {
13152 s.move_with(|map, selection| {
13153 if !selection.is_empty() {
13154 selection.goal = SelectionGoal::None;
13155 }
13156 let (cursor, goal) = movement::down(
13157 map,
13158 selection.end,
13159 selection.goal,
13160 false,
13161 text_layout_details,
13162 );
13163 selection.collapse_to(cursor, goal);
13164 });
13165 });
13166
13167 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13168 {
13169 cx.propagate();
13170 }
13171 }
13172
13173 pub fn select_page_down(
13174 &mut self,
13175 _: &SelectPageDown,
13176 window: &mut Window,
13177 cx: &mut Context<Self>,
13178 ) {
13179 let Some(row_count) = self.visible_row_count() else {
13180 return;
13181 };
13182
13183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13184
13185 let text_layout_details = &self.text_layout_details(window);
13186
13187 self.change_selections(Default::default(), window, cx, |s| {
13188 s.move_heads_with(|map, head, goal| {
13189 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13190 })
13191 })
13192 }
13193
13194 pub fn move_page_down(
13195 &mut self,
13196 action: &MovePageDown,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) {
13200 if self.take_rename(true, window, cx).is_some() {
13201 return;
13202 }
13203
13204 if self
13205 .context_menu
13206 .borrow_mut()
13207 .as_mut()
13208 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13209 .unwrap_or(false)
13210 {
13211 return;
13212 }
13213
13214 if matches!(self.mode, EditorMode::SingleLine) {
13215 cx.propagate();
13216 return;
13217 }
13218
13219 let Some(row_count) = self.visible_row_count() else {
13220 return;
13221 };
13222
13223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13224
13225 let effects = if action.center_cursor {
13226 SelectionEffects::scroll(Autoscroll::center())
13227 } else {
13228 SelectionEffects::default()
13229 };
13230
13231 let text_layout_details = &self.text_layout_details(window);
13232 self.change_selections(effects, window, cx, |s| {
13233 s.move_with(|map, selection| {
13234 if !selection.is_empty() {
13235 selection.goal = SelectionGoal::None;
13236 }
13237 let (cursor, goal) = movement::down_by_rows(
13238 map,
13239 selection.end,
13240 row_count,
13241 selection.goal,
13242 false,
13243 text_layout_details,
13244 );
13245 selection.collapse_to(cursor, goal);
13246 });
13247 });
13248 }
13249
13250 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13252 let text_layout_details = &self.text_layout_details(window);
13253 self.change_selections(Default::default(), window, cx, |s| {
13254 s.move_heads_with(|map, head, goal| {
13255 movement::down(map, head, goal, false, text_layout_details)
13256 })
13257 });
13258 }
13259
13260 pub fn context_menu_first(
13261 &mut self,
13262 _: &ContextMenuFirst,
13263 window: &mut Window,
13264 cx: &mut Context<Self>,
13265 ) {
13266 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13267 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13268 }
13269 }
13270
13271 pub fn context_menu_prev(
13272 &mut self,
13273 _: &ContextMenuPrevious,
13274 window: &mut Window,
13275 cx: &mut Context<Self>,
13276 ) {
13277 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13278 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13279 }
13280 }
13281
13282 pub fn context_menu_next(
13283 &mut self,
13284 _: &ContextMenuNext,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) {
13288 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13289 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13290 }
13291 }
13292
13293 pub fn context_menu_last(
13294 &mut self,
13295 _: &ContextMenuLast,
13296 window: &mut Window,
13297 cx: &mut Context<Self>,
13298 ) {
13299 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13300 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13301 }
13302 }
13303
13304 pub fn signature_help_prev(
13305 &mut self,
13306 _: &SignatureHelpPrevious,
13307 _: &mut Window,
13308 cx: &mut Context<Self>,
13309 ) {
13310 if let Some(popover) = self.signature_help_state.popover_mut() {
13311 if popover.current_signature == 0 {
13312 popover.current_signature = popover.signatures.len() - 1;
13313 } else {
13314 popover.current_signature -= 1;
13315 }
13316 cx.notify();
13317 }
13318 }
13319
13320 pub fn signature_help_next(
13321 &mut self,
13322 _: &SignatureHelpNext,
13323 _: &mut Window,
13324 cx: &mut Context<Self>,
13325 ) {
13326 if let Some(popover) = self.signature_help_state.popover_mut() {
13327 if popover.current_signature + 1 == popover.signatures.len() {
13328 popover.current_signature = 0;
13329 } else {
13330 popover.current_signature += 1;
13331 }
13332 cx.notify();
13333 }
13334 }
13335
13336 pub fn move_to_previous_word_start(
13337 &mut self,
13338 _: &MoveToPreviousWordStart,
13339 window: &mut Window,
13340 cx: &mut Context<Self>,
13341 ) {
13342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13343 self.change_selections(Default::default(), window, cx, |s| {
13344 s.move_cursors_with(|map, head, _| {
13345 (
13346 movement::previous_word_start(map, head),
13347 SelectionGoal::None,
13348 )
13349 });
13350 })
13351 }
13352
13353 pub fn move_to_previous_subword_start(
13354 &mut self,
13355 _: &MoveToPreviousSubwordStart,
13356 window: &mut Window,
13357 cx: &mut Context<Self>,
13358 ) {
13359 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13360 self.change_selections(Default::default(), window, cx, |s| {
13361 s.move_cursors_with(|map, head, _| {
13362 (
13363 movement::previous_subword_start(map, head),
13364 SelectionGoal::None,
13365 )
13366 });
13367 })
13368 }
13369
13370 pub fn select_to_previous_word_start(
13371 &mut self,
13372 _: &SelectToPreviousWordStart,
13373 window: &mut Window,
13374 cx: &mut Context<Self>,
13375 ) {
13376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13377 self.change_selections(Default::default(), window, cx, |s| {
13378 s.move_heads_with(|map, head, _| {
13379 (
13380 movement::previous_word_start(map, head),
13381 SelectionGoal::None,
13382 )
13383 });
13384 })
13385 }
13386
13387 pub fn select_to_previous_subword_start(
13388 &mut self,
13389 _: &SelectToPreviousSubwordStart,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13394 self.change_selections(Default::default(), window, cx, |s| {
13395 s.move_heads_with(|map, head, _| {
13396 (
13397 movement::previous_subword_start(map, head),
13398 SelectionGoal::None,
13399 )
13400 });
13401 })
13402 }
13403
13404 pub fn delete_to_previous_word_start(
13405 &mut self,
13406 action: &DeleteToPreviousWordStart,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13411 self.transact(window, cx, |this, window, cx| {
13412 this.select_autoclose_pair(window, cx);
13413 this.change_selections(Default::default(), window, cx, |s| {
13414 s.move_with(|map, selection| {
13415 if selection.is_empty() {
13416 let mut cursor = if action.ignore_newlines {
13417 movement::previous_word_start(map, selection.head())
13418 } else {
13419 movement::previous_word_start_or_newline(map, selection.head())
13420 };
13421 cursor = movement::adjust_greedy_deletion(
13422 map,
13423 selection.head(),
13424 cursor,
13425 action.ignore_brackets,
13426 );
13427 selection.set_head(cursor, SelectionGoal::None);
13428 }
13429 });
13430 });
13431 this.insert("", window, cx);
13432 });
13433 }
13434
13435 pub fn delete_to_previous_subword_start(
13436 &mut self,
13437 _: &DeleteToPreviousSubwordStart,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13442 self.transact(window, cx, |this, window, cx| {
13443 this.select_autoclose_pair(window, cx);
13444 this.change_selections(Default::default(), window, cx, |s| {
13445 s.move_with(|map, selection| {
13446 if selection.is_empty() {
13447 let mut cursor = movement::previous_subword_start(map, selection.head());
13448 cursor =
13449 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13450 selection.set_head(cursor, SelectionGoal::None);
13451 }
13452 });
13453 });
13454 this.insert("", window, cx);
13455 });
13456 }
13457
13458 pub fn move_to_next_word_end(
13459 &mut self,
13460 _: &MoveToNextWordEnd,
13461 window: &mut Window,
13462 cx: &mut Context<Self>,
13463 ) {
13464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13465 self.change_selections(Default::default(), window, cx, |s| {
13466 s.move_cursors_with(|map, head, _| {
13467 (movement::next_word_end(map, head), SelectionGoal::None)
13468 });
13469 })
13470 }
13471
13472 pub fn move_to_next_subword_end(
13473 &mut self,
13474 _: &MoveToNextSubwordEnd,
13475 window: &mut Window,
13476 cx: &mut Context<Self>,
13477 ) {
13478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13479 self.change_selections(Default::default(), window, cx, |s| {
13480 s.move_cursors_with(|map, head, _| {
13481 (movement::next_subword_end(map, head), SelectionGoal::None)
13482 });
13483 })
13484 }
13485
13486 pub fn select_to_next_word_end(
13487 &mut self,
13488 _: &SelectToNextWordEnd,
13489 window: &mut Window,
13490 cx: &mut Context<Self>,
13491 ) {
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13493 self.change_selections(Default::default(), window, cx, |s| {
13494 s.move_heads_with(|map, head, _| {
13495 (movement::next_word_end(map, head), SelectionGoal::None)
13496 });
13497 })
13498 }
13499
13500 pub fn select_to_next_subword_end(
13501 &mut self,
13502 _: &SelectToNextSubwordEnd,
13503 window: &mut Window,
13504 cx: &mut Context<Self>,
13505 ) {
13506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13507 self.change_selections(Default::default(), window, cx, |s| {
13508 s.move_heads_with(|map, head, _| {
13509 (movement::next_subword_end(map, head), SelectionGoal::None)
13510 });
13511 })
13512 }
13513
13514 pub fn delete_to_next_word_end(
13515 &mut self,
13516 action: &DeleteToNextWordEnd,
13517 window: &mut Window,
13518 cx: &mut Context<Self>,
13519 ) {
13520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13521 self.transact(window, cx, |this, window, cx| {
13522 this.change_selections(Default::default(), window, cx, |s| {
13523 s.move_with(|map, selection| {
13524 if selection.is_empty() {
13525 let mut cursor = if action.ignore_newlines {
13526 movement::next_word_end(map, selection.head())
13527 } else {
13528 movement::next_word_end_or_newline(map, selection.head())
13529 };
13530 cursor = movement::adjust_greedy_deletion(
13531 map,
13532 selection.head(),
13533 cursor,
13534 action.ignore_brackets,
13535 );
13536 selection.set_head(cursor, SelectionGoal::None);
13537 }
13538 });
13539 });
13540 this.insert("", window, cx);
13541 });
13542 }
13543
13544 pub fn delete_to_next_subword_end(
13545 &mut self,
13546 _: &DeleteToNextSubwordEnd,
13547 window: &mut Window,
13548 cx: &mut Context<Self>,
13549 ) {
13550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13551 self.transact(window, cx, |this, window, cx| {
13552 this.change_selections(Default::default(), window, cx, |s| {
13553 s.move_with(|map, selection| {
13554 if selection.is_empty() {
13555 let mut cursor = movement::next_subword_end(map, selection.head());
13556 cursor =
13557 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13558 selection.set_head(cursor, SelectionGoal::None);
13559 }
13560 });
13561 });
13562 this.insert("", window, cx);
13563 });
13564 }
13565
13566 pub fn move_to_beginning_of_line(
13567 &mut self,
13568 action: &MoveToBeginningOfLine,
13569 window: &mut Window,
13570 cx: &mut Context<Self>,
13571 ) {
13572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13573 self.change_selections(Default::default(), window, cx, |s| {
13574 s.move_cursors_with(|map, head, _| {
13575 (
13576 movement::indented_line_beginning(
13577 map,
13578 head,
13579 action.stop_at_soft_wraps,
13580 action.stop_at_indent,
13581 ),
13582 SelectionGoal::None,
13583 )
13584 });
13585 })
13586 }
13587
13588 pub fn select_to_beginning_of_line(
13589 &mut self,
13590 action: &SelectToBeginningOfLine,
13591 window: &mut Window,
13592 cx: &mut Context<Self>,
13593 ) {
13594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13595 self.change_selections(Default::default(), window, cx, |s| {
13596 s.move_heads_with(|map, head, _| {
13597 (
13598 movement::indented_line_beginning(
13599 map,
13600 head,
13601 action.stop_at_soft_wraps,
13602 action.stop_at_indent,
13603 ),
13604 SelectionGoal::None,
13605 )
13606 });
13607 });
13608 }
13609
13610 pub fn delete_to_beginning_of_line(
13611 &mut self,
13612 action: &DeleteToBeginningOfLine,
13613 window: &mut Window,
13614 cx: &mut Context<Self>,
13615 ) {
13616 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13617 self.transact(window, cx, |this, window, cx| {
13618 this.change_selections(Default::default(), window, cx, |s| {
13619 s.move_with(|_, selection| {
13620 selection.reversed = true;
13621 });
13622 });
13623
13624 this.select_to_beginning_of_line(
13625 &SelectToBeginningOfLine {
13626 stop_at_soft_wraps: false,
13627 stop_at_indent: action.stop_at_indent,
13628 },
13629 window,
13630 cx,
13631 );
13632 this.backspace(&Backspace, window, cx);
13633 });
13634 }
13635
13636 pub fn move_to_end_of_line(
13637 &mut self,
13638 action: &MoveToEndOfLine,
13639 window: &mut Window,
13640 cx: &mut Context<Self>,
13641 ) {
13642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13643 self.change_selections(Default::default(), window, cx, |s| {
13644 s.move_cursors_with(|map, head, _| {
13645 (
13646 movement::line_end(map, head, action.stop_at_soft_wraps),
13647 SelectionGoal::None,
13648 )
13649 });
13650 })
13651 }
13652
13653 pub fn select_to_end_of_line(
13654 &mut self,
13655 action: &SelectToEndOfLine,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13660 self.change_selections(Default::default(), window, cx, |s| {
13661 s.move_heads_with(|map, head, _| {
13662 (
13663 movement::line_end(map, head, action.stop_at_soft_wraps),
13664 SelectionGoal::None,
13665 )
13666 });
13667 })
13668 }
13669
13670 pub fn delete_to_end_of_line(
13671 &mut self,
13672 _: &DeleteToEndOfLine,
13673 window: &mut Window,
13674 cx: &mut Context<Self>,
13675 ) {
13676 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13677 self.transact(window, cx, |this, window, cx| {
13678 this.select_to_end_of_line(
13679 &SelectToEndOfLine {
13680 stop_at_soft_wraps: false,
13681 },
13682 window,
13683 cx,
13684 );
13685 this.delete(&Delete, window, cx);
13686 });
13687 }
13688
13689 pub fn cut_to_end_of_line(
13690 &mut self,
13691 action: &CutToEndOfLine,
13692 window: &mut Window,
13693 cx: &mut Context<Self>,
13694 ) {
13695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13696 self.transact(window, cx, |this, window, cx| {
13697 this.select_to_end_of_line(
13698 &SelectToEndOfLine {
13699 stop_at_soft_wraps: false,
13700 },
13701 window,
13702 cx,
13703 );
13704 if !action.stop_at_newlines {
13705 this.change_selections(Default::default(), window, cx, |s| {
13706 s.move_with(|_, sel| {
13707 if sel.is_empty() {
13708 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13709 }
13710 });
13711 });
13712 }
13713 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13714 let item = this.cut_common(false, window, cx);
13715 cx.write_to_clipboard(item);
13716 });
13717 }
13718
13719 pub fn move_to_start_of_paragraph(
13720 &mut self,
13721 _: &MoveToStartOfParagraph,
13722 window: &mut Window,
13723 cx: &mut Context<Self>,
13724 ) {
13725 if matches!(self.mode, EditorMode::SingleLine) {
13726 cx.propagate();
13727 return;
13728 }
13729 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13730 self.change_selections(Default::default(), window, cx, |s| {
13731 s.move_with(|map, selection| {
13732 selection.collapse_to(
13733 movement::start_of_paragraph(map, selection.head(), 1),
13734 SelectionGoal::None,
13735 )
13736 });
13737 })
13738 }
13739
13740 pub fn move_to_end_of_paragraph(
13741 &mut self,
13742 _: &MoveToEndOfParagraph,
13743 window: &mut Window,
13744 cx: &mut Context<Self>,
13745 ) {
13746 if matches!(self.mode, EditorMode::SingleLine) {
13747 cx.propagate();
13748 return;
13749 }
13750 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13751 self.change_selections(Default::default(), window, cx, |s| {
13752 s.move_with(|map, selection| {
13753 selection.collapse_to(
13754 movement::end_of_paragraph(map, selection.head(), 1),
13755 SelectionGoal::None,
13756 )
13757 });
13758 })
13759 }
13760
13761 pub fn select_to_start_of_paragraph(
13762 &mut self,
13763 _: &SelectToStartOfParagraph,
13764 window: &mut Window,
13765 cx: &mut Context<Self>,
13766 ) {
13767 if matches!(self.mode, EditorMode::SingleLine) {
13768 cx.propagate();
13769 return;
13770 }
13771 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13772 self.change_selections(Default::default(), window, cx, |s| {
13773 s.move_heads_with(|map, head, _| {
13774 (
13775 movement::start_of_paragraph(map, head, 1),
13776 SelectionGoal::None,
13777 )
13778 });
13779 })
13780 }
13781
13782 pub fn select_to_end_of_paragraph(
13783 &mut self,
13784 _: &SelectToEndOfParagraph,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 ) {
13788 if matches!(self.mode, EditorMode::SingleLine) {
13789 cx.propagate();
13790 return;
13791 }
13792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13793 self.change_selections(Default::default(), window, cx, |s| {
13794 s.move_heads_with(|map, head, _| {
13795 (
13796 movement::end_of_paragraph(map, head, 1),
13797 SelectionGoal::None,
13798 )
13799 });
13800 })
13801 }
13802
13803 pub fn move_to_start_of_excerpt(
13804 &mut self,
13805 _: &MoveToStartOfExcerpt,
13806 window: &mut Window,
13807 cx: &mut Context<Self>,
13808 ) {
13809 if matches!(self.mode, EditorMode::SingleLine) {
13810 cx.propagate();
13811 return;
13812 }
13813 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13814 self.change_selections(Default::default(), window, cx, |s| {
13815 s.move_with(|map, selection| {
13816 selection.collapse_to(
13817 movement::start_of_excerpt(
13818 map,
13819 selection.head(),
13820 workspace::searchable::Direction::Prev,
13821 ),
13822 SelectionGoal::None,
13823 )
13824 });
13825 })
13826 }
13827
13828 pub fn move_to_start_of_next_excerpt(
13829 &mut self,
13830 _: &MoveToStartOfNextExcerpt,
13831 window: &mut Window,
13832 cx: &mut Context<Self>,
13833 ) {
13834 if matches!(self.mode, EditorMode::SingleLine) {
13835 cx.propagate();
13836 return;
13837 }
13838
13839 self.change_selections(Default::default(), window, cx, |s| {
13840 s.move_with(|map, selection| {
13841 selection.collapse_to(
13842 movement::start_of_excerpt(
13843 map,
13844 selection.head(),
13845 workspace::searchable::Direction::Next,
13846 ),
13847 SelectionGoal::None,
13848 )
13849 });
13850 })
13851 }
13852
13853 pub fn move_to_end_of_excerpt(
13854 &mut self,
13855 _: &MoveToEndOfExcerpt,
13856 window: &mut Window,
13857 cx: &mut Context<Self>,
13858 ) {
13859 if matches!(self.mode, EditorMode::SingleLine) {
13860 cx.propagate();
13861 return;
13862 }
13863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13864 self.change_selections(Default::default(), window, cx, |s| {
13865 s.move_with(|map, selection| {
13866 selection.collapse_to(
13867 movement::end_of_excerpt(
13868 map,
13869 selection.head(),
13870 workspace::searchable::Direction::Next,
13871 ),
13872 SelectionGoal::None,
13873 )
13874 });
13875 })
13876 }
13877
13878 pub fn move_to_end_of_previous_excerpt(
13879 &mut self,
13880 _: &MoveToEndOfPreviousExcerpt,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) {
13884 if matches!(self.mode, EditorMode::SingleLine) {
13885 cx.propagate();
13886 return;
13887 }
13888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13889 self.change_selections(Default::default(), window, cx, |s| {
13890 s.move_with(|map, selection| {
13891 selection.collapse_to(
13892 movement::end_of_excerpt(
13893 map,
13894 selection.head(),
13895 workspace::searchable::Direction::Prev,
13896 ),
13897 SelectionGoal::None,
13898 )
13899 });
13900 })
13901 }
13902
13903 pub fn select_to_start_of_excerpt(
13904 &mut self,
13905 _: &SelectToStartOfExcerpt,
13906 window: &mut Window,
13907 cx: &mut Context<Self>,
13908 ) {
13909 if matches!(self.mode, EditorMode::SingleLine) {
13910 cx.propagate();
13911 return;
13912 }
13913 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13914 self.change_selections(Default::default(), window, cx, |s| {
13915 s.move_heads_with(|map, head, _| {
13916 (
13917 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13918 SelectionGoal::None,
13919 )
13920 });
13921 })
13922 }
13923
13924 pub fn select_to_start_of_next_excerpt(
13925 &mut self,
13926 _: &SelectToStartOfNextExcerpt,
13927 window: &mut Window,
13928 cx: &mut Context<Self>,
13929 ) {
13930 if matches!(self.mode, EditorMode::SingleLine) {
13931 cx.propagate();
13932 return;
13933 }
13934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13935 self.change_selections(Default::default(), window, cx, |s| {
13936 s.move_heads_with(|map, head, _| {
13937 (
13938 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13939 SelectionGoal::None,
13940 )
13941 });
13942 })
13943 }
13944
13945 pub fn select_to_end_of_excerpt(
13946 &mut self,
13947 _: &SelectToEndOfExcerpt,
13948 window: &mut Window,
13949 cx: &mut Context<Self>,
13950 ) {
13951 if matches!(self.mode, EditorMode::SingleLine) {
13952 cx.propagate();
13953 return;
13954 }
13955 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13956 self.change_selections(Default::default(), window, cx, |s| {
13957 s.move_heads_with(|map, head, _| {
13958 (
13959 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13960 SelectionGoal::None,
13961 )
13962 });
13963 })
13964 }
13965
13966 pub fn select_to_end_of_previous_excerpt(
13967 &mut self,
13968 _: &SelectToEndOfPreviousExcerpt,
13969 window: &mut Window,
13970 cx: &mut Context<Self>,
13971 ) {
13972 if matches!(self.mode, EditorMode::SingleLine) {
13973 cx.propagate();
13974 return;
13975 }
13976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13977 self.change_selections(Default::default(), window, cx, |s| {
13978 s.move_heads_with(|map, head, _| {
13979 (
13980 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13981 SelectionGoal::None,
13982 )
13983 });
13984 })
13985 }
13986
13987 pub fn move_to_beginning(
13988 &mut self,
13989 _: &MoveToBeginning,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 if matches!(self.mode, EditorMode::SingleLine) {
13994 cx.propagate();
13995 return;
13996 }
13997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13998 self.change_selections(Default::default(), window, cx, |s| {
13999 s.select_ranges(vec![0..0]);
14000 });
14001 }
14002
14003 pub fn select_to_beginning(
14004 &mut self,
14005 _: &SelectToBeginning,
14006 window: &mut Window,
14007 cx: &mut Context<Self>,
14008 ) {
14009 let mut selection = self.selections.last::<Point>(cx);
14010 selection.set_head(Point::zero(), SelectionGoal::None);
14011 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14012 self.change_selections(Default::default(), window, cx, |s| {
14013 s.select(vec![selection]);
14014 });
14015 }
14016
14017 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14018 if matches!(self.mode, EditorMode::SingleLine) {
14019 cx.propagate();
14020 return;
14021 }
14022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14023 let cursor = self.buffer.read(cx).read(cx).len();
14024 self.change_selections(Default::default(), window, cx, |s| {
14025 s.select_ranges(vec![cursor..cursor])
14026 });
14027 }
14028
14029 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14030 self.nav_history = nav_history;
14031 }
14032
14033 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14034 self.nav_history.as_ref()
14035 }
14036
14037 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14038 self.push_to_nav_history(
14039 self.selections.newest_anchor().head(),
14040 None,
14041 false,
14042 true,
14043 cx,
14044 );
14045 }
14046
14047 fn push_to_nav_history(
14048 &mut self,
14049 cursor_anchor: Anchor,
14050 new_position: Option<Point>,
14051 is_deactivate: bool,
14052 always: bool,
14053 cx: &mut Context<Self>,
14054 ) {
14055 if let Some(nav_history) = self.nav_history.as_mut() {
14056 let buffer = self.buffer.read(cx).read(cx);
14057 let cursor_position = cursor_anchor.to_point(&buffer);
14058 let scroll_state = self.scroll_manager.anchor();
14059 let scroll_top_row = scroll_state.top_row(&buffer);
14060 drop(buffer);
14061
14062 if let Some(new_position) = new_position {
14063 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14064 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14065 return;
14066 }
14067 }
14068
14069 nav_history.push(
14070 Some(NavigationData {
14071 cursor_anchor,
14072 cursor_position,
14073 scroll_anchor: scroll_state,
14074 scroll_top_row,
14075 }),
14076 cx,
14077 );
14078 cx.emit(EditorEvent::PushedToNavHistory {
14079 anchor: cursor_anchor,
14080 is_deactivate,
14081 })
14082 }
14083 }
14084
14085 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14087 let buffer = self.buffer.read(cx).snapshot(cx);
14088 let mut selection = self.selections.first::<usize>(cx);
14089 selection.set_head(buffer.len(), SelectionGoal::None);
14090 self.change_selections(Default::default(), window, cx, |s| {
14091 s.select(vec![selection]);
14092 });
14093 }
14094
14095 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14097 let end = self.buffer.read(cx).read(cx).len();
14098 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14099 s.select_ranges(vec![0..end]);
14100 });
14101 }
14102
14103 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14105 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14106 let mut selections = self.selections.all::<Point>(cx);
14107 let max_point = display_map.buffer_snapshot().max_point();
14108 for selection in &mut selections {
14109 let rows = selection.spanned_rows(true, &display_map);
14110 selection.start = Point::new(rows.start.0, 0);
14111 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14112 selection.reversed = false;
14113 }
14114 self.change_selections(Default::default(), window, cx, |s| {
14115 s.select(selections);
14116 });
14117 }
14118
14119 pub fn split_selection_into_lines(
14120 &mut self,
14121 action: &SplitSelectionIntoLines,
14122 window: &mut Window,
14123 cx: &mut Context<Self>,
14124 ) {
14125 let selections = self
14126 .selections
14127 .all::<Point>(cx)
14128 .into_iter()
14129 .map(|selection| selection.start..selection.end)
14130 .collect::<Vec<_>>();
14131 self.unfold_ranges(&selections, true, true, cx);
14132
14133 let mut new_selection_ranges = Vec::new();
14134 {
14135 let buffer = self.buffer.read(cx).read(cx);
14136 for selection in selections {
14137 for row in selection.start.row..selection.end.row {
14138 let line_start = Point::new(row, 0);
14139 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14140
14141 if action.keep_selections {
14142 // Keep the selection range for each line
14143 let selection_start = if row == selection.start.row {
14144 selection.start
14145 } else {
14146 line_start
14147 };
14148 new_selection_ranges.push(selection_start..line_end);
14149 } else {
14150 // Collapse to cursor at end of line
14151 new_selection_ranges.push(line_end..line_end);
14152 }
14153 }
14154
14155 let is_multiline_selection = selection.start.row != selection.end.row;
14156 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14157 // so this action feels more ergonomic when paired with other selection operations
14158 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14159 if !should_skip_last {
14160 if action.keep_selections {
14161 if is_multiline_selection {
14162 let line_start = Point::new(selection.end.row, 0);
14163 new_selection_ranges.push(line_start..selection.end);
14164 } else {
14165 new_selection_ranges.push(selection.start..selection.end);
14166 }
14167 } else {
14168 new_selection_ranges.push(selection.end..selection.end);
14169 }
14170 }
14171 }
14172 }
14173 self.change_selections(Default::default(), window, cx, |s| {
14174 s.select_ranges(new_selection_ranges);
14175 });
14176 }
14177
14178 pub fn add_selection_above(
14179 &mut self,
14180 _: &AddSelectionAbove,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 self.add_selection(true, window, cx);
14185 }
14186
14187 pub fn add_selection_below(
14188 &mut self,
14189 _: &AddSelectionBelow,
14190 window: &mut Window,
14191 cx: &mut Context<Self>,
14192 ) {
14193 self.add_selection(false, window, cx);
14194 }
14195
14196 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14198
14199 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14200 let all_selections = self.selections.all::<Point>(cx);
14201 let text_layout_details = self.text_layout_details(window);
14202
14203 let (mut columnar_selections, new_selections_to_columnarize) = {
14204 if let Some(state) = self.add_selections_state.as_ref() {
14205 let columnar_selection_ids: HashSet<_> = state
14206 .groups
14207 .iter()
14208 .flat_map(|group| group.stack.iter())
14209 .copied()
14210 .collect();
14211
14212 all_selections
14213 .into_iter()
14214 .partition(|s| columnar_selection_ids.contains(&s.id))
14215 } else {
14216 (Vec::new(), all_selections)
14217 }
14218 };
14219
14220 let mut state = self
14221 .add_selections_state
14222 .take()
14223 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14224
14225 for selection in new_selections_to_columnarize {
14226 let range = selection.display_range(&display_map).sorted();
14227 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14228 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14229 let positions = start_x.min(end_x)..start_x.max(end_x);
14230 let mut stack = Vec::new();
14231 for row in range.start.row().0..=range.end.row().0 {
14232 if let Some(selection) = self.selections.build_columnar_selection(
14233 &display_map,
14234 DisplayRow(row),
14235 &positions,
14236 selection.reversed,
14237 &text_layout_details,
14238 ) {
14239 stack.push(selection.id);
14240 columnar_selections.push(selection);
14241 }
14242 }
14243 if !stack.is_empty() {
14244 if above {
14245 stack.reverse();
14246 }
14247 state.groups.push(AddSelectionsGroup { above, stack });
14248 }
14249 }
14250
14251 let mut final_selections = Vec::new();
14252 let end_row = if above {
14253 DisplayRow(0)
14254 } else {
14255 display_map.max_point().row()
14256 };
14257
14258 let mut last_added_item_per_group = HashMap::default();
14259 for group in state.groups.iter_mut() {
14260 if let Some(last_id) = group.stack.last() {
14261 last_added_item_per_group.insert(*last_id, group);
14262 }
14263 }
14264
14265 for selection in columnar_selections {
14266 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14267 if above == group.above {
14268 let range = selection.display_range(&display_map).sorted();
14269 debug_assert_eq!(range.start.row(), range.end.row());
14270 let mut row = range.start.row();
14271 let positions =
14272 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14273 Pixels::from(start)..Pixels::from(end)
14274 } else {
14275 let start_x =
14276 display_map.x_for_display_point(range.start, &text_layout_details);
14277 let end_x =
14278 display_map.x_for_display_point(range.end, &text_layout_details);
14279 start_x.min(end_x)..start_x.max(end_x)
14280 };
14281
14282 let mut maybe_new_selection = None;
14283 while row != end_row {
14284 if above {
14285 row.0 -= 1;
14286 } else {
14287 row.0 += 1;
14288 }
14289 if let Some(new_selection) = self.selections.build_columnar_selection(
14290 &display_map,
14291 row,
14292 &positions,
14293 selection.reversed,
14294 &text_layout_details,
14295 ) {
14296 maybe_new_selection = Some(new_selection);
14297 break;
14298 }
14299 }
14300
14301 if let Some(new_selection) = maybe_new_selection {
14302 group.stack.push(new_selection.id);
14303 if above {
14304 final_selections.push(new_selection);
14305 final_selections.push(selection);
14306 } else {
14307 final_selections.push(selection);
14308 final_selections.push(new_selection);
14309 }
14310 } else {
14311 final_selections.push(selection);
14312 }
14313 } else {
14314 group.stack.pop();
14315 }
14316 } else {
14317 final_selections.push(selection);
14318 }
14319 }
14320
14321 self.change_selections(Default::default(), window, cx, |s| {
14322 s.select(final_selections);
14323 });
14324
14325 let final_selection_ids: HashSet<_> = self
14326 .selections
14327 .all::<Point>(cx)
14328 .iter()
14329 .map(|s| s.id)
14330 .collect();
14331 state.groups.retain_mut(|group| {
14332 // selections might get merged above so we remove invalid items from stacks
14333 group.stack.retain(|id| final_selection_ids.contains(id));
14334
14335 // single selection in stack can be treated as initial state
14336 group.stack.len() > 1
14337 });
14338
14339 if !state.groups.is_empty() {
14340 self.add_selections_state = Some(state);
14341 }
14342 }
14343
14344 fn select_match_ranges(
14345 &mut self,
14346 range: Range<usize>,
14347 reversed: bool,
14348 replace_newest: bool,
14349 auto_scroll: Option<Autoscroll>,
14350 window: &mut Window,
14351 cx: &mut Context<Editor>,
14352 ) {
14353 self.unfold_ranges(
14354 std::slice::from_ref(&range),
14355 false,
14356 auto_scroll.is_some(),
14357 cx,
14358 );
14359 let effects = if let Some(scroll) = auto_scroll {
14360 SelectionEffects::scroll(scroll)
14361 } else {
14362 SelectionEffects::no_scroll()
14363 };
14364 self.change_selections(effects, window, cx, |s| {
14365 if replace_newest {
14366 s.delete(s.newest_anchor().id);
14367 }
14368 if reversed {
14369 s.insert_range(range.end..range.start);
14370 } else {
14371 s.insert_range(range);
14372 }
14373 });
14374 }
14375
14376 pub fn select_next_match_internal(
14377 &mut self,
14378 display_map: &DisplaySnapshot,
14379 replace_newest: bool,
14380 autoscroll: Option<Autoscroll>,
14381 window: &mut Window,
14382 cx: &mut Context<Self>,
14383 ) -> Result<()> {
14384 let buffer = display_map.buffer_snapshot();
14385 let mut selections = self.selections.all::<usize>(cx);
14386 if let Some(mut select_next_state) = self.select_next_state.take() {
14387 let query = &select_next_state.query;
14388 if !select_next_state.done {
14389 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14390 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14391 let mut next_selected_range = None;
14392
14393 let bytes_after_last_selection =
14394 buffer.bytes_in_range(last_selection.end..buffer.len());
14395 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14396 let query_matches = query
14397 .stream_find_iter(bytes_after_last_selection)
14398 .map(|result| (last_selection.end, result))
14399 .chain(
14400 query
14401 .stream_find_iter(bytes_before_first_selection)
14402 .map(|result| (0, result)),
14403 );
14404
14405 for (start_offset, query_match) in query_matches {
14406 let query_match = query_match.unwrap(); // can only fail due to I/O
14407 let offset_range =
14408 start_offset + query_match.start()..start_offset + query_match.end();
14409
14410 if !select_next_state.wordwise
14411 || (!buffer.is_inside_word(offset_range.start, None)
14412 && !buffer.is_inside_word(offset_range.end, None))
14413 {
14414 // TODO: This is n^2, because we might check all the selections
14415 if !selections
14416 .iter()
14417 .any(|selection| selection.range().overlaps(&offset_range))
14418 {
14419 next_selected_range = Some(offset_range);
14420 break;
14421 }
14422 }
14423 }
14424
14425 if let Some(next_selected_range) = next_selected_range {
14426 self.select_match_ranges(
14427 next_selected_range,
14428 last_selection.reversed,
14429 replace_newest,
14430 autoscroll,
14431 window,
14432 cx,
14433 );
14434 } else {
14435 select_next_state.done = true;
14436 }
14437 }
14438
14439 self.select_next_state = Some(select_next_state);
14440 } else {
14441 let mut only_carets = true;
14442 let mut same_text_selected = true;
14443 let mut selected_text = None;
14444
14445 let mut selections_iter = selections.iter().peekable();
14446 while let Some(selection) = selections_iter.next() {
14447 if selection.start != selection.end {
14448 only_carets = false;
14449 }
14450
14451 if same_text_selected {
14452 if selected_text.is_none() {
14453 selected_text =
14454 Some(buffer.text_for_range(selection.range()).collect::<String>());
14455 }
14456
14457 if let Some(next_selection) = selections_iter.peek() {
14458 if next_selection.range().len() == selection.range().len() {
14459 let next_selected_text = buffer
14460 .text_for_range(next_selection.range())
14461 .collect::<String>();
14462 if Some(next_selected_text) != selected_text {
14463 same_text_selected = false;
14464 selected_text = None;
14465 }
14466 } else {
14467 same_text_selected = false;
14468 selected_text = None;
14469 }
14470 }
14471 }
14472 }
14473
14474 if only_carets {
14475 for selection in &mut selections {
14476 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14477 selection.start = word_range.start;
14478 selection.end = word_range.end;
14479 selection.goal = SelectionGoal::None;
14480 selection.reversed = false;
14481 self.select_match_ranges(
14482 selection.start..selection.end,
14483 selection.reversed,
14484 replace_newest,
14485 autoscroll,
14486 window,
14487 cx,
14488 );
14489 }
14490
14491 if selections.len() == 1 {
14492 let selection = selections
14493 .last()
14494 .expect("ensured that there's only one selection");
14495 let query = buffer
14496 .text_for_range(selection.start..selection.end)
14497 .collect::<String>();
14498 let is_empty = query.is_empty();
14499 let select_state = SelectNextState {
14500 query: AhoCorasick::new(&[query])?,
14501 wordwise: true,
14502 done: is_empty,
14503 };
14504 self.select_next_state = Some(select_state);
14505 } else {
14506 self.select_next_state = None;
14507 }
14508 } else if let Some(selected_text) = selected_text {
14509 self.select_next_state = Some(SelectNextState {
14510 query: AhoCorasick::new(&[selected_text])?,
14511 wordwise: false,
14512 done: false,
14513 });
14514 self.select_next_match_internal(
14515 display_map,
14516 replace_newest,
14517 autoscroll,
14518 window,
14519 cx,
14520 )?;
14521 }
14522 }
14523 Ok(())
14524 }
14525
14526 pub fn select_all_matches(
14527 &mut self,
14528 _action: &SelectAllMatches,
14529 window: &mut Window,
14530 cx: &mut Context<Self>,
14531 ) -> Result<()> {
14532 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14533
14534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14535
14536 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14537 let Some(select_next_state) = self.select_next_state.as_mut() else {
14538 return Ok(());
14539 };
14540 if select_next_state.done {
14541 return Ok(());
14542 }
14543
14544 let mut new_selections = Vec::new();
14545
14546 let reversed = self.selections.oldest::<usize>(cx).reversed;
14547 let buffer = display_map.buffer_snapshot();
14548 let query_matches = select_next_state
14549 .query
14550 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14551
14552 for query_match in query_matches.into_iter() {
14553 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14554 let offset_range = if reversed {
14555 query_match.end()..query_match.start()
14556 } else {
14557 query_match.start()..query_match.end()
14558 };
14559
14560 if !select_next_state.wordwise
14561 || (!buffer.is_inside_word(offset_range.start, None)
14562 && !buffer.is_inside_word(offset_range.end, None))
14563 {
14564 new_selections.push(offset_range.start..offset_range.end);
14565 }
14566 }
14567
14568 select_next_state.done = true;
14569
14570 if new_selections.is_empty() {
14571 log::error!("bug: new_selections is empty in select_all_matches");
14572 return Ok(());
14573 }
14574
14575 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14576 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14577 selections.select_ranges(new_selections)
14578 });
14579
14580 Ok(())
14581 }
14582
14583 pub fn select_next(
14584 &mut self,
14585 action: &SelectNext,
14586 window: &mut Window,
14587 cx: &mut Context<Self>,
14588 ) -> Result<()> {
14589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14591 self.select_next_match_internal(
14592 &display_map,
14593 action.replace_newest,
14594 Some(Autoscroll::newest()),
14595 window,
14596 cx,
14597 )?;
14598 Ok(())
14599 }
14600
14601 pub fn select_previous(
14602 &mut self,
14603 action: &SelectPrevious,
14604 window: &mut Window,
14605 cx: &mut Context<Self>,
14606 ) -> Result<()> {
14607 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14608 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14609 let buffer = display_map.buffer_snapshot();
14610 let mut selections = self.selections.all::<usize>(cx);
14611 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14612 let query = &select_prev_state.query;
14613 if !select_prev_state.done {
14614 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14615 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14616 let mut next_selected_range = None;
14617 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14618 let bytes_before_last_selection =
14619 buffer.reversed_bytes_in_range(0..last_selection.start);
14620 let bytes_after_first_selection =
14621 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14622 let query_matches = query
14623 .stream_find_iter(bytes_before_last_selection)
14624 .map(|result| (last_selection.start, result))
14625 .chain(
14626 query
14627 .stream_find_iter(bytes_after_first_selection)
14628 .map(|result| (buffer.len(), result)),
14629 );
14630 for (end_offset, query_match) in query_matches {
14631 let query_match = query_match.unwrap(); // can only fail due to I/O
14632 let offset_range =
14633 end_offset - query_match.end()..end_offset - query_match.start();
14634
14635 if !select_prev_state.wordwise
14636 || (!buffer.is_inside_word(offset_range.start, None)
14637 && !buffer.is_inside_word(offset_range.end, None))
14638 {
14639 next_selected_range = Some(offset_range);
14640 break;
14641 }
14642 }
14643
14644 if let Some(next_selected_range) = next_selected_range {
14645 self.select_match_ranges(
14646 next_selected_range,
14647 last_selection.reversed,
14648 action.replace_newest,
14649 Some(Autoscroll::newest()),
14650 window,
14651 cx,
14652 );
14653 } else {
14654 select_prev_state.done = true;
14655 }
14656 }
14657
14658 self.select_prev_state = Some(select_prev_state);
14659 } else {
14660 let mut only_carets = true;
14661 let mut same_text_selected = true;
14662 let mut selected_text = None;
14663
14664 let mut selections_iter = selections.iter().peekable();
14665 while let Some(selection) = selections_iter.next() {
14666 if selection.start != selection.end {
14667 only_carets = false;
14668 }
14669
14670 if same_text_selected {
14671 if selected_text.is_none() {
14672 selected_text =
14673 Some(buffer.text_for_range(selection.range()).collect::<String>());
14674 }
14675
14676 if let Some(next_selection) = selections_iter.peek() {
14677 if next_selection.range().len() == selection.range().len() {
14678 let next_selected_text = buffer
14679 .text_for_range(next_selection.range())
14680 .collect::<String>();
14681 if Some(next_selected_text) != selected_text {
14682 same_text_selected = false;
14683 selected_text = None;
14684 }
14685 } else {
14686 same_text_selected = false;
14687 selected_text = None;
14688 }
14689 }
14690 }
14691 }
14692
14693 if only_carets {
14694 for selection in &mut selections {
14695 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14696 selection.start = word_range.start;
14697 selection.end = word_range.end;
14698 selection.goal = SelectionGoal::None;
14699 selection.reversed = false;
14700 self.select_match_ranges(
14701 selection.start..selection.end,
14702 selection.reversed,
14703 action.replace_newest,
14704 Some(Autoscroll::newest()),
14705 window,
14706 cx,
14707 );
14708 }
14709 if selections.len() == 1 {
14710 let selection = selections
14711 .last()
14712 .expect("ensured that there's only one selection");
14713 let query = buffer
14714 .text_for_range(selection.start..selection.end)
14715 .collect::<String>();
14716 let is_empty = query.is_empty();
14717 let select_state = SelectNextState {
14718 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14719 wordwise: true,
14720 done: is_empty,
14721 };
14722 self.select_prev_state = Some(select_state);
14723 } else {
14724 self.select_prev_state = None;
14725 }
14726 } else if let Some(selected_text) = selected_text {
14727 self.select_prev_state = Some(SelectNextState {
14728 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14729 wordwise: false,
14730 done: false,
14731 });
14732 self.select_previous(action, window, cx)?;
14733 }
14734 }
14735 Ok(())
14736 }
14737
14738 pub fn find_next_match(
14739 &mut self,
14740 _: &FindNextMatch,
14741 window: &mut Window,
14742 cx: &mut Context<Self>,
14743 ) -> Result<()> {
14744 let selections = self.selections.disjoint_anchors_arc();
14745 match selections.first() {
14746 Some(first) if selections.len() >= 2 => {
14747 self.change_selections(Default::default(), window, cx, |s| {
14748 s.select_ranges([first.range()]);
14749 });
14750 }
14751 _ => self.select_next(
14752 &SelectNext {
14753 replace_newest: true,
14754 },
14755 window,
14756 cx,
14757 )?,
14758 }
14759 Ok(())
14760 }
14761
14762 pub fn find_previous_match(
14763 &mut self,
14764 _: &FindPreviousMatch,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) -> Result<()> {
14768 let selections = self.selections.disjoint_anchors_arc();
14769 match selections.last() {
14770 Some(last) if selections.len() >= 2 => {
14771 self.change_selections(Default::default(), window, cx, |s| {
14772 s.select_ranges([last.range()]);
14773 });
14774 }
14775 _ => self.select_previous(
14776 &SelectPrevious {
14777 replace_newest: true,
14778 },
14779 window,
14780 cx,
14781 )?,
14782 }
14783 Ok(())
14784 }
14785
14786 pub fn toggle_comments(
14787 &mut self,
14788 action: &ToggleComments,
14789 window: &mut Window,
14790 cx: &mut Context<Self>,
14791 ) {
14792 if self.read_only(cx) {
14793 return;
14794 }
14795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14796 let text_layout_details = &self.text_layout_details(window);
14797 self.transact(window, cx, |this, window, cx| {
14798 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14799 let mut edits = Vec::new();
14800 let mut selection_edit_ranges = Vec::new();
14801 let mut last_toggled_row = None;
14802 let snapshot = this.buffer.read(cx).read(cx);
14803 let empty_str: Arc<str> = Arc::default();
14804 let mut suffixes_inserted = Vec::new();
14805 let ignore_indent = action.ignore_indent;
14806
14807 fn comment_prefix_range(
14808 snapshot: &MultiBufferSnapshot,
14809 row: MultiBufferRow,
14810 comment_prefix: &str,
14811 comment_prefix_whitespace: &str,
14812 ignore_indent: bool,
14813 ) -> Range<Point> {
14814 let indent_size = if ignore_indent {
14815 0
14816 } else {
14817 snapshot.indent_size_for_line(row).len
14818 };
14819
14820 let start = Point::new(row.0, indent_size);
14821
14822 let mut line_bytes = snapshot
14823 .bytes_in_range(start..snapshot.max_point())
14824 .flatten()
14825 .copied();
14826
14827 // If this line currently begins with the line comment prefix, then record
14828 // the range containing the prefix.
14829 if line_bytes
14830 .by_ref()
14831 .take(comment_prefix.len())
14832 .eq(comment_prefix.bytes())
14833 {
14834 // Include any whitespace that matches the comment prefix.
14835 let matching_whitespace_len = line_bytes
14836 .zip(comment_prefix_whitespace.bytes())
14837 .take_while(|(a, b)| a == b)
14838 .count() as u32;
14839 let end = Point::new(
14840 start.row,
14841 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14842 );
14843 start..end
14844 } else {
14845 start..start
14846 }
14847 }
14848
14849 fn comment_suffix_range(
14850 snapshot: &MultiBufferSnapshot,
14851 row: MultiBufferRow,
14852 comment_suffix: &str,
14853 comment_suffix_has_leading_space: bool,
14854 ) -> Range<Point> {
14855 let end = Point::new(row.0, snapshot.line_len(row));
14856 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14857
14858 let mut line_end_bytes = snapshot
14859 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14860 .flatten()
14861 .copied();
14862
14863 let leading_space_len = if suffix_start_column > 0
14864 && line_end_bytes.next() == Some(b' ')
14865 && comment_suffix_has_leading_space
14866 {
14867 1
14868 } else {
14869 0
14870 };
14871
14872 // If this line currently begins with the line comment prefix, then record
14873 // the range containing the prefix.
14874 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14875 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14876 start..end
14877 } else {
14878 end..end
14879 }
14880 }
14881
14882 // TODO: Handle selections that cross excerpts
14883 for selection in &mut selections {
14884 let start_column = snapshot
14885 .indent_size_for_line(MultiBufferRow(selection.start.row))
14886 .len;
14887 let language = if let Some(language) =
14888 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14889 {
14890 language
14891 } else {
14892 continue;
14893 };
14894
14895 selection_edit_ranges.clear();
14896
14897 // If multiple selections contain a given row, avoid processing that
14898 // row more than once.
14899 let mut start_row = MultiBufferRow(selection.start.row);
14900 if last_toggled_row == Some(start_row) {
14901 start_row = start_row.next_row();
14902 }
14903 let end_row =
14904 if selection.end.row > selection.start.row && selection.end.column == 0 {
14905 MultiBufferRow(selection.end.row - 1)
14906 } else {
14907 MultiBufferRow(selection.end.row)
14908 };
14909 last_toggled_row = Some(end_row);
14910
14911 if start_row > end_row {
14912 continue;
14913 }
14914
14915 // If the language has line comments, toggle those.
14916 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14917
14918 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14919 if ignore_indent {
14920 full_comment_prefixes = full_comment_prefixes
14921 .into_iter()
14922 .map(|s| Arc::from(s.trim_end()))
14923 .collect();
14924 }
14925
14926 if !full_comment_prefixes.is_empty() {
14927 let first_prefix = full_comment_prefixes
14928 .first()
14929 .expect("prefixes is non-empty");
14930 let prefix_trimmed_lengths = full_comment_prefixes
14931 .iter()
14932 .map(|p| p.trim_end_matches(' ').len())
14933 .collect::<SmallVec<[usize; 4]>>();
14934
14935 let mut all_selection_lines_are_comments = true;
14936
14937 for row in start_row.0..=end_row.0 {
14938 let row = MultiBufferRow(row);
14939 if start_row < end_row && snapshot.is_line_blank(row) {
14940 continue;
14941 }
14942
14943 let prefix_range = full_comment_prefixes
14944 .iter()
14945 .zip(prefix_trimmed_lengths.iter().copied())
14946 .map(|(prefix, trimmed_prefix_len)| {
14947 comment_prefix_range(
14948 snapshot.deref(),
14949 row,
14950 &prefix[..trimmed_prefix_len],
14951 &prefix[trimmed_prefix_len..],
14952 ignore_indent,
14953 )
14954 })
14955 .max_by_key(|range| range.end.column - range.start.column)
14956 .expect("prefixes is non-empty");
14957
14958 if prefix_range.is_empty() {
14959 all_selection_lines_are_comments = false;
14960 }
14961
14962 selection_edit_ranges.push(prefix_range);
14963 }
14964
14965 if all_selection_lines_are_comments {
14966 edits.extend(
14967 selection_edit_ranges
14968 .iter()
14969 .cloned()
14970 .map(|range| (range, empty_str.clone())),
14971 );
14972 } else {
14973 let min_column = selection_edit_ranges
14974 .iter()
14975 .map(|range| range.start.column)
14976 .min()
14977 .unwrap_or(0);
14978 edits.extend(selection_edit_ranges.iter().map(|range| {
14979 let position = Point::new(range.start.row, min_column);
14980 (position..position, first_prefix.clone())
14981 }));
14982 }
14983 } else if let Some(BlockCommentConfig {
14984 start: full_comment_prefix,
14985 end: comment_suffix,
14986 ..
14987 }) = language.block_comment()
14988 {
14989 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14990 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14991 let prefix_range = comment_prefix_range(
14992 snapshot.deref(),
14993 start_row,
14994 comment_prefix,
14995 comment_prefix_whitespace,
14996 ignore_indent,
14997 );
14998 let suffix_range = comment_suffix_range(
14999 snapshot.deref(),
15000 end_row,
15001 comment_suffix.trim_start_matches(' '),
15002 comment_suffix.starts_with(' '),
15003 );
15004
15005 if prefix_range.is_empty() || suffix_range.is_empty() {
15006 edits.push((
15007 prefix_range.start..prefix_range.start,
15008 full_comment_prefix.clone(),
15009 ));
15010 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15011 suffixes_inserted.push((end_row, comment_suffix.len()));
15012 } else {
15013 edits.push((prefix_range, empty_str.clone()));
15014 edits.push((suffix_range, empty_str.clone()));
15015 }
15016 } else {
15017 continue;
15018 }
15019 }
15020
15021 drop(snapshot);
15022 this.buffer.update(cx, |buffer, cx| {
15023 buffer.edit(edits, None, cx);
15024 });
15025
15026 // Adjust selections so that they end before any comment suffixes that
15027 // were inserted.
15028 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15029 let mut selections = this.selections.all::<Point>(cx);
15030 let snapshot = this.buffer.read(cx).read(cx);
15031 for selection in &mut selections {
15032 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15033 match row.cmp(&MultiBufferRow(selection.end.row)) {
15034 Ordering::Less => {
15035 suffixes_inserted.next();
15036 continue;
15037 }
15038 Ordering::Greater => break,
15039 Ordering::Equal => {
15040 if selection.end.column == snapshot.line_len(row) {
15041 if selection.is_empty() {
15042 selection.start.column -= suffix_len as u32;
15043 }
15044 selection.end.column -= suffix_len as u32;
15045 }
15046 break;
15047 }
15048 }
15049 }
15050 }
15051
15052 drop(snapshot);
15053 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15054
15055 let selections = this.selections.all::<Point>(cx);
15056 let selections_on_single_row = selections.windows(2).all(|selections| {
15057 selections[0].start.row == selections[1].start.row
15058 && selections[0].end.row == selections[1].end.row
15059 && selections[0].start.row == selections[0].end.row
15060 });
15061 let selections_selecting = selections
15062 .iter()
15063 .any(|selection| selection.start != selection.end);
15064 let advance_downwards = action.advance_downwards
15065 && selections_on_single_row
15066 && !selections_selecting
15067 && !matches!(this.mode, EditorMode::SingleLine);
15068
15069 if advance_downwards {
15070 let snapshot = this.buffer.read(cx).snapshot(cx);
15071
15072 this.change_selections(Default::default(), window, cx, |s| {
15073 s.move_cursors_with(|display_snapshot, display_point, _| {
15074 let mut point = display_point.to_point(display_snapshot);
15075 point.row += 1;
15076 point = snapshot.clip_point(point, Bias::Left);
15077 let display_point = point.to_display_point(display_snapshot);
15078 let goal = SelectionGoal::HorizontalPosition(
15079 display_snapshot
15080 .x_for_display_point(display_point, text_layout_details)
15081 .into(),
15082 );
15083 (display_point, goal)
15084 })
15085 });
15086 }
15087 });
15088 }
15089
15090 pub fn select_enclosing_symbol(
15091 &mut self,
15092 _: &SelectEnclosingSymbol,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15097
15098 let buffer = self.buffer.read(cx).snapshot(cx);
15099 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15100
15101 fn update_selection(
15102 selection: &Selection<usize>,
15103 buffer_snap: &MultiBufferSnapshot,
15104 ) -> Option<Selection<usize>> {
15105 let cursor = selection.head();
15106 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15107 for symbol in symbols.iter().rev() {
15108 let start = symbol.range.start.to_offset(buffer_snap);
15109 let end = symbol.range.end.to_offset(buffer_snap);
15110 let new_range = start..end;
15111 if start < selection.start || end > selection.end {
15112 return Some(Selection {
15113 id: selection.id,
15114 start: new_range.start,
15115 end: new_range.end,
15116 goal: SelectionGoal::None,
15117 reversed: selection.reversed,
15118 });
15119 }
15120 }
15121 None
15122 }
15123
15124 let mut selected_larger_symbol = false;
15125 let new_selections = old_selections
15126 .iter()
15127 .map(|selection| match update_selection(selection, &buffer) {
15128 Some(new_selection) => {
15129 if new_selection.range() != selection.range() {
15130 selected_larger_symbol = true;
15131 }
15132 new_selection
15133 }
15134 None => selection.clone(),
15135 })
15136 .collect::<Vec<_>>();
15137
15138 if selected_larger_symbol {
15139 self.change_selections(Default::default(), window, cx, |s| {
15140 s.select(new_selections);
15141 });
15142 }
15143 }
15144
15145 pub fn select_larger_syntax_node(
15146 &mut self,
15147 _: &SelectLargerSyntaxNode,
15148 window: &mut Window,
15149 cx: &mut Context<Self>,
15150 ) {
15151 let Some(visible_row_count) = self.visible_row_count() else {
15152 return;
15153 };
15154 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15155 if old_selections.is_empty() {
15156 return;
15157 }
15158
15159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15160
15161 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15162 let buffer = self.buffer.read(cx).snapshot(cx);
15163
15164 let mut selected_larger_node = false;
15165 let mut new_selections = old_selections
15166 .iter()
15167 .map(|selection| {
15168 let old_range = selection.start..selection.end;
15169
15170 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15171 // manually select word at selection
15172 if ["string_content", "inline"].contains(&node.kind()) {
15173 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15174 // ignore if word is already selected
15175 if !word_range.is_empty() && old_range != word_range {
15176 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15177 // only select word if start and end point belongs to same word
15178 if word_range == last_word_range {
15179 selected_larger_node = true;
15180 return Selection {
15181 id: selection.id,
15182 start: word_range.start,
15183 end: word_range.end,
15184 goal: SelectionGoal::None,
15185 reversed: selection.reversed,
15186 };
15187 }
15188 }
15189 }
15190 }
15191
15192 let mut new_range = old_range.clone();
15193 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15194 new_range = range;
15195 if !node.is_named() {
15196 continue;
15197 }
15198 if !display_map.intersects_fold(new_range.start)
15199 && !display_map.intersects_fold(new_range.end)
15200 {
15201 break;
15202 }
15203 }
15204
15205 selected_larger_node |= new_range != old_range;
15206 Selection {
15207 id: selection.id,
15208 start: new_range.start,
15209 end: new_range.end,
15210 goal: SelectionGoal::None,
15211 reversed: selection.reversed,
15212 }
15213 })
15214 .collect::<Vec<_>>();
15215
15216 if !selected_larger_node {
15217 return; // don't put this call in the history
15218 }
15219
15220 // scroll based on transformation done to the last selection created by the user
15221 let (last_old, last_new) = old_selections
15222 .last()
15223 .zip(new_selections.last().cloned())
15224 .expect("old_selections isn't empty");
15225
15226 // revert selection
15227 let is_selection_reversed = {
15228 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15229 new_selections.last_mut().expect("checked above").reversed =
15230 should_newest_selection_be_reversed;
15231 should_newest_selection_be_reversed
15232 };
15233
15234 if selected_larger_node {
15235 self.select_syntax_node_history.disable_clearing = true;
15236 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15237 s.select(new_selections.clone());
15238 });
15239 self.select_syntax_node_history.disable_clearing = false;
15240 }
15241
15242 let start_row = last_new.start.to_display_point(&display_map).row().0;
15243 let end_row = last_new.end.to_display_point(&display_map).row().0;
15244 let selection_height = end_row - start_row + 1;
15245 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15246
15247 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15248 let scroll_behavior = if fits_on_the_screen {
15249 self.request_autoscroll(Autoscroll::fit(), cx);
15250 SelectSyntaxNodeScrollBehavior::FitSelection
15251 } else if is_selection_reversed {
15252 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15253 SelectSyntaxNodeScrollBehavior::CursorTop
15254 } else {
15255 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15256 SelectSyntaxNodeScrollBehavior::CursorBottom
15257 };
15258
15259 self.select_syntax_node_history.push((
15260 old_selections,
15261 scroll_behavior,
15262 is_selection_reversed,
15263 ));
15264 }
15265
15266 pub fn select_smaller_syntax_node(
15267 &mut self,
15268 _: &SelectSmallerSyntaxNode,
15269 window: &mut Window,
15270 cx: &mut Context<Self>,
15271 ) {
15272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15273
15274 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15275 self.select_syntax_node_history.pop()
15276 {
15277 if let Some(selection) = selections.last_mut() {
15278 selection.reversed = is_selection_reversed;
15279 }
15280
15281 self.select_syntax_node_history.disable_clearing = true;
15282 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15283 s.select(selections.to_vec());
15284 });
15285 self.select_syntax_node_history.disable_clearing = false;
15286
15287 match scroll_behavior {
15288 SelectSyntaxNodeScrollBehavior::CursorTop => {
15289 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15290 }
15291 SelectSyntaxNodeScrollBehavior::FitSelection => {
15292 self.request_autoscroll(Autoscroll::fit(), cx);
15293 }
15294 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15295 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15296 }
15297 }
15298 }
15299 }
15300
15301 pub fn unwrap_syntax_node(
15302 &mut self,
15303 _: &UnwrapSyntaxNode,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15308
15309 let buffer = self.buffer.read(cx).snapshot(cx);
15310 let selections = self
15311 .selections
15312 .all::<usize>(cx)
15313 .into_iter()
15314 // subtracting the offset requires sorting
15315 .sorted_by_key(|i| i.start);
15316
15317 let full_edits = selections
15318 .into_iter()
15319 .filter_map(|selection| {
15320 let child = if selection.is_empty()
15321 && let Some((_, ancestor_range)) =
15322 buffer.syntax_ancestor(selection.start..selection.end)
15323 {
15324 ancestor_range
15325 } else {
15326 selection.range()
15327 };
15328
15329 let mut parent = child.clone();
15330 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15331 parent = ancestor_range;
15332 if parent.start < child.start || parent.end > child.end {
15333 break;
15334 }
15335 }
15336
15337 if parent == child {
15338 return None;
15339 }
15340 let text = buffer.text_for_range(child).collect::<String>();
15341 Some((selection.id, parent, text))
15342 })
15343 .collect::<Vec<_>>();
15344 if full_edits.is_empty() {
15345 return;
15346 }
15347
15348 self.transact(window, cx, |this, window, cx| {
15349 this.buffer.update(cx, |buffer, cx| {
15350 buffer.edit(
15351 full_edits
15352 .iter()
15353 .map(|(_, p, t)| (p.clone(), t.clone()))
15354 .collect::<Vec<_>>(),
15355 None,
15356 cx,
15357 );
15358 });
15359 this.change_selections(Default::default(), window, cx, |s| {
15360 let mut offset = 0;
15361 let mut selections = vec![];
15362 for (id, parent, text) in full_edits {
15363 let start = parent.start - offset;
15364 offset += parent.len() - text.len();
15365 selections.push(Selection {
15366 id,
15367 start,
15368 end: start + text.len(),
15369 reversed: false,
15370 goal: Default::default(),
15371 });
15372 }
15373 s.select(selections);
15374 });
15375 });
15376 }
15377
15378 pub fn select_next_syntax_node(
15379 &mut self,
15380 _: &SelectNextSyntaxNode,
15381 window: &mut Window,
15382 cx: &mut Context<Self>,
15383 ) {
15384 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15385 if old_selections.is_empty() {
15386 return;
15387 }
15388
15389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15390
15391 let buffer = self.buffer.read(cx).snapshot(cx);
15392 let mut selected_sibling = false;
15393
15394 let new_selections = old_selections
15395 .iter()
15396 .map(|selection| {
15397 let old_range = selection.start..selection.end;
15398
15399 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15400 let new_range = node.byte_range();
15401 selected_sibling = true;
15402 Selection {
15403 id: selection.id,
15404 start: new_range.start,
15405 end: new_range.end,
15406 goal: SelectionGoal::None,
15407 reversed: selection.reversed,
15408 }
15409 } else {
15410 selection.clone()
15411 }
15412 })
15413 .collect::<Vec<_>>();
15414
15415 if selected_sibling {
15416 self.change_selections(
15417 SelectionEffects::scroll(Autoscroll::fit()),
15418 window,
15419 cx,
15420 |s| {
15421 s.select(new_selections);
15422 },
15423 );
15424 }
15425 }
15426
15427 pub fn select_prev_syntax_node(
15428 &mut self,
15429 _: &SelectPreviousSyntaxNode,
15430 window: &mut Window,
15431 cx: &mut Context<Self>,
15432 ) {
15433 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15434 if old_selections.is_empty() {
15435 return;
15436 }
15437
15438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15439
15440 let buffer = self.buffer.read(cx).snapshot(cx);
15441 let mut selected_sibling = false;
15442
15443 let new_selections = old_selections
15444 .iter()
15445 .map(|selection| {
15446 let old_range = selection.start..selection.end;
15447
15448 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15449 let new_range = node.byte_range();
15450 selected_sibling = true;
15451 Selection {
15452 id: selection.id,
15453 start: new_range.start,
15454 end: new_range.end,
15455 goal: SelectionGoal::None,
15456 reversed: selection.reversed,
15457 }
15458 } else {
15459 selection.clone()
15460 }
15461 })
15462 .collect::<Vec<_>>();
15463
15464 if selected_sibling {
15465 self.change_selections(
15466 SelectionEffects::scroll(Autoscroll::fit()),
15467 window,
15468 cx,
15469 |s| {
15470 s.select(new_selections);
15471 },
15472 );
15473 }
15474 }
15475
15476 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15477 if !EditorSettings::get_global(cx).gutter.runnables {
15478 self.clear_tasks();
15479 return Task::ready(());
15480 }
15481 let project = self.project().map(Entity::downgrade);
15482 let task_sources = self.lsp_task_sources(cx);
15483 let multi_buffer = self.buffer.downgrade();
15484 cx.spawn_in(window, async move |editor, cx| {
15485 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15486 let Some(project) = project.and_then(|p| p.upgrade()) else {
15487 return;
15488 };
15489 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15490 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15491 }) else {
15492 return;
15493 };
15494
15495 let hide_runnables = project
15496 .update(cx, |project, _| project.is_via_collab())
15497 .unwrap_or(true);
15498 if hide_runnables {
15499 return;
15500 }
15501 let new_rows =
15502 cx.background_spawn({
15503 let snapshot = display_snapshot.clone();
15504 async move {
15505 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15506 }
15507 })
15508 .await;
15509 let Ok(lsp_tasks) =
15510 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15511 else {
15512 return;
15513 };
15514 let lsp_tasks = lsp_tasks.await;
15515
15516 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15517 lsp_tasks
15518 .into_iter()
15519 .flat_map(|(kind, tasks)| {
15520 tasks.into_iter().filter_map(move |(location, task)| {
15521 Some((kind.clone(), location?, task))
15522 })
15523 })
15524 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15525 let buffer = location.target.buffer;
15526 let buffer_snapshot = buffer.read(cx).snapshot();
15527 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15528 |(excerpt_id, snapshot, _)| {
15529 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15530 display_snapshot
15531 .buffer_snapshot()
15532 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15533 } else {
15534 None
15535 }
15536 },
15537 );
15538 if let Some(offset) = offset {
15539 let task_buffer_range =
15540 location.target.range.to_point(&buffer_snapshot);
15541 let context_buffer_range =
15542 task_buffer_range.to_offset(&buffer_snapshot);
15543 let context_range = BufferOffset(context_buffer_range.start)
15544 ..BufferOffset(context_buffer_range.end);
15545
15546 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15547 .or_insert_with(|| RunnableTasks {
15548 templates: Vec::new(),
15549 offset,
15550 column: task_buffer_range.start.column,
15551 extra_variables: HashMap::default(),
15552 context_range,
15553 })
15554 .templates
15555 .push((kind, task.original_task().clone()));
15556 }
15557
15558 acc
15559 })
15560 }) else {
15561 return;
15562 };
15563
15564 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15565 buffer.language_settings(cx).tasks.prefer_lsp
15566 }) else {
15567 return;
15568 };
15569
15570 let rows = Self::runnable_rows(
15571 project,
15572 display_snapshot,
15573 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15574 new_rows,
15575 cx.clone(),
15576 )
15577 .await;
15578 editor
15579 .update(cx, |editor, _| {
15580 editor.clear_tasks();
15581 for (key, mut value) in rows {
15582 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15583 value.templates.extend(lsp_tasks.templates);
15584 }
15585
15586 editor.insert_tasks(key, value);
15587 }
15588 for (key, value) in lsp_tasks_by_rows {
15589 editor.insert_tasks(key, value);
15590 }
15591 })
15592 .ok();
15593 })
15594 }
15595 fn fetch_runnable_ranges(
15596 snapshot: &DisplaySnapshot,
15597 range: Range<Anchor>,
15598 ) -> Vec<language::RunnableRange> {
15599 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15600 }
15601
15602 fn runnable_rows(
15603 project: Entity<Project>,
15604 snapshot: DisplaySnapshot,
15605 prefer_lsp: bool,
15606 runnable_ranges: Vec<RunnableRange>,
15607 cx: AsyncWindowContext,
15608 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15609 cx.spawn(async move |cx| {
15610 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15611 for mut runnable in runnable_ranges {
15612 let Some(tasks) = cx
15613 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15614 .ok()
15615 else {
15616 continue;
15617 };
15618 let mut tasks = tasks.await;
15619
15620 if prefer_lsp {
15621 tasks.retain(|(task_kind, _)| {
15622 !matches!(task_kind, TaskSourceKind::Language { .. })
15623 });
15624 }
15625 if tasks.is_empty() {
15626 continue;
15627 }
15628
15629 let point = runnable
15630 .run_range
15631 .start
15632 .to_point(&snapshot.buffer_snapshot());
15633 let Some(row) = snapshot
15634 .buffer_snapshot()
15635 .buffer_line_for_row(MultiBufferRow(point.row))
15636 .map(|(_, range)| range.start.row)
15637 else {
15638 continue;
15639 };
15640
15641 let context_range =
15642 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15643 runnable_rows.push((
15644 (runnable.buffer_id, row),
15645 RunnableTasks {
15646 templates: tasks,
15647 offset: snapshot
15648 .buffer_snapshot()
15649 .anchor_before(runnable.run_range.start),
15650 context_range,
15651 column: point.column,
15652 extra_variables: runnable.extra_captures,
15653 },
15654 ));
15655 }
15656 runnable_rows
15657 })
15658 }
15659
15660 fn templates_with_tags(
15661 project: &Entity<Project>,
15662 runnable: &mut Runnable,
15663 cx: &mut App,
15664 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15665 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15666 let (worktree_id, file) = project
15667 .buffer_for_id(runnable.buffer, cx)
15668 .and_then(|buffer| buffer.read(cx).file())
15669 .map(|file| (file.worktree_id(cx), file.clone()))
15670 .unzip();
15671
15672 (
15673 project.task_store().read(cx).task_inventory().cloned(),
15674 worktree_id,
15675 file,
15676 )
15677 });
15678
15679 let tags = mem::take(&mut runnable.tags);
15680 let language = runnable.language.clone();
15681 cx.spawn(async move |cx| {
15682 let mut templates_with_tags = Vec::new();
15683 if let Some(inventory) = inventory {
15684 for RunnableTag(tag) in tags {
15685 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15686 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15687 }) else {
15688 return templates_with_tags;
15689 };
15690 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15691 move |(_, template)| {
15692 template.tags.iter().any(|source_tag| source_tag == &tag)
15693 },
15694 ));
15695 }
15696 }
15697 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15698
15699 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15700 // Strongest source wins; if we have worktree tag binding, prefer that to
15701 // global and language bindings;
15702 // if we have a global binding, prefer that to language binding.
15703 let first_mismatch = templates_with_tags
15704 .iter()
15705 .position(|(tag_source, _)| tag_source != leading_tag_source);
15706 if let Some(index) = first_mismatch {
15707 templates_with_tags.truncate(index);
15708 }
15709 }
15710
15711 templates_with_tags
15712 })
15713 }
15714
15715 pub fn move_to_enclosing_bracket(
15716 &mut self,
15717 _: &MoveToEnclosingBracket,
15718 window: &mut Window,
15719 cx: &mut Context<Self>,
15720 ) {
15721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15722 self.change_selections(Default::default(), window, cx, |s| {
15723 s.move_offsets_with(|snapshot, selection| {
15724 let Some(enclosing_bracket_ranges) =
15725 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15726 else {
15727 return;
15728 };
15729
15730 let mut best_length = usize::MAX;
15731 let mut best_inside = false;
15732 let mut best_in_bracket_range = false;
15733 let mut best_destination = None;
15734 for (open, close) in enclosing_bracket_ranges {
15735 let close = close.to_inclusive();
15736 let length = close.end() - open.start;
15737 let inside = selection.start >= open.end && selection.end <= *close.start();
15738 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15739 || close.contains(&selection.head());
15740
15741 // If best is next to a bracket and current isn't, skip
15742 if !in_bracket_range && best_in_bracket_range {
15743 continue;
15744 }
15745
15746 // Prefer smaller lengths unless best is inside and current isn't
15747 if length > best_length && (best_inside || !inside) {
15748 continue;
15749 }
15750
15751 best_length = length;
15752 best_inside = inside;
15753 best_in_bracket_range = in_bracket_range;
15754 best_destination = Some(
15755 if close.contains(&selection.start) && close.contains(&selection.end) {
15756 if inside { open.end } else { open.start }
15757 } else if inside {
15758 *close.start()
15759 } else {
15760 *close.end()
15761 },
15762 );
15763 }
15764
15765 if let Some(destination) = best_destination {
15766 selection.collapse_to(destination, SelectionGoal::None);
15767 }
15768 })
15769 });
15770 }
15771
15772 pub fn undo_selection(
15773 &mut self,
15774 _: &UndoSelection,
15775 window: &mut Window,
15776 cx: &mut Context<Self>,
15777 ) {
15778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15779 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15780 self.selection_history.mode = SelectionHistoryMode::Undoing;
15781 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15782 this.end_selection(window, cx);
15783 this.change_selections(
15784 SelectionEffects::scroll(Autoscroll::newest()),
15785 window,
15786 cx,
15787 |s| s.select_anchors(entry.selections.to_vec()),
15788 );
15789 });
15790 self.selection_history.mode = SelectionHistoryMode::Normal;
15791
15792 self.select_next_state = entry.select_next_state;
15793 self.select_prev_state = entry.select_prev_state;
15794 self.add_selections_state = entry.add_selections_state;
15795 }
15796 }
15797
15798 pub fn redo_selection(
15799 &mut self,
15800 _: &RedoSelection,
15801 window: &mut Window,
15802 cx: &mut Context<Self>,
15803 ) {
15804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15805 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15806 self.selection_history.mode = SelectionHistoryMode::Redoing;
15807 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15808 this.end_selection(window, cx);
15809 this.change_selections(
15810 SelectionEffects::scroll(Autoscroll::newest()),
15811 window,
15812 cx,
15813 |s| s.select_anchors(entry.selections.to_vec()),
15814 );
15815 });
15816 self.selection_history.mode = SelectionHistoryMode::Normal;
15817
15818 self.select_next_state = entry.select_next_state;
15819 self.select_prev_state = entry.select_prev_state;
15820 self.add_selections_state = entry.add_selections_state;
15821 }
15822 }
15823
15824 pub fn expand_excerpts(
15825 &mut self,
15826 action: &ExpandExcerpts,
15827 _: &mut Window,
15828 cx: &mut Context<Self>,
15829 ) {
15830 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15831 }
15832
15833 pub fn expand_excerpts_down(
15834 &mut self,
15835 action: &ExpandExcerptsDown,
15836 _: &mut Window,
15837 cx: &mut Context<Self>,
15838 ) {
15839 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15840 }
15841
15842 pub fn expand_excerpts_up(
15843 &mut self,
15844 action: &ExpandExcerptsUp,
15845 _: &mut Window,
15846 cx: &mut Context<Self>,
15847 ) {
15848 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15849 }
15850
15851 pub fn expand_excerpts_for_direction(
15852 &mut self,
15853 lines: u32,
15854 direction: ExpandExcerptDirection,
15855
15856 cx: &mut Context<Self>,
15857 ) {
15858 let selections = self.selections.disjoint_anchors_arc();
15859
15860 let lines = if lines == 0 {
15861 EditorSettings::get_global(cx).expand_excerpt_lines
15862 } else {
15863 lines
15864 };
15865
15866 self.buffer.update(cx, |buffer, cx| {
15867 let snapshot = buffer.snapshot(cx);
15868 let mut excerpt_ids = selections
15869 .iter()
15870 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15871 .collect::<Vec<_>>();
15872 excerpt_ids.sort();
15873 excerpt_ids.dedup();
15874 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15875 })
15876 }
15877
15878 pub fn expand_excerpt(
15879 &mut self,
15880 excerpt: ExcerptId,
15881 direction: ExpandExcerptDirection,
15882 window: &mut Window,
15883 cx: &mut Context<Self>,
15884 ) {
15885 let current_scroll_position = self.scroll_position(cx);
15886 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15887 let mut should_scroll_up = false;
15888
15889 if direction == ExpandExcerptDirection::Down {
15890 let multi_buffer = self.buffer.read(cx);
15891 let snapshot = multi_buffer.snapshot(cx);
15892 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15893 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15894 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15895 {
15896 let buffer_snapshot = buffer.read(cx).snapshot();
15897 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15898 let last_row = buffer_snapshot.max_point().row;
15899 let lines_below = last_row.saturating_sub(excerpt_end_row);
15900 should_scroll_up = lines_below >= lines_to_expand;
15901 }
15902 }
15903
15904 self.buffer.update(cx, |buffer, cx| {
15905 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15906 });
15907
15908 if should_scroll_up {
15909 let new_scroll_position =
15910 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15911 self.set_scroll_position(new_scroll_position, window, cx);
15912 }
15913 }
15914
15915 pub fn go_to_singleton_buffer_point(
15916 &mut self,
15917 point: Point,
15918 window: &mut Window,
15919 cx: &mut Context<Self>,
15920 ) {
15921 self.go_to_singleton_buffer_range(point..point, window, cx);
15922 }
15923
15924 pub fn go_to_singleton_buffer_range(
15925 &mut self,
15926 range: Range<Point>,
15927 window: &mut Window,
15928 cx: &mut Context<Self>,
15929 ) {
15930 let multibuffer = self.buffer().read(cx);
15931 let Some(buffer) = multibuffer.as_singleton() else {
15932 return;
15933 };
15934 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15935 return;
15936 };
15937 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15938 return;
15939 };
15940 self.change_selections(
15941 SelectionEffects::default().nav_history(true),
15942 window,
15943 cx,
15944 |s| s.select_anchor_ranges([start..end]),
15945 );
15946 }
15947
15948 pub fn go_to_diagnostic(
15949 &mut self,
15950 action: &GoToDiagnostic,
15951 window: &mut Window,
15952 cx: &mut Context<Self>,
15953 ) {
15954 if !self.diagnostics_enabled() {
15955 return;
15956 }
15957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15958 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15959 }
15960
15961 pub fn go_to_prev_diagnostic(
15962 &mut self,
15963 action: &GoToPreviousDiagnostic,
15964 window: &mut Window,
15965 cx: &mut Context<Self>,
15966 ) {
15967 if !self.diagnostics_enabled() {
15968 return;
15969 }
15970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15971 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15972 }
15973
15974 pub fn go_to_diagnostic_impl(
15975 &mut self,
15976 direction: Direction,
15977 severity: GoToDiagnosticSeverityFilter,
15978 window: &mut Window,
15979 cx: &mut Context<Self>,
15980 ) {
15981 let buffer = self.buffer.read(cx).snapshot(cx);
15982 let selection = self.selections.newest::<usize>(cx);
15983
15984 let mut active_group_id = None;
15985 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15986 && active_group.active_range.start.to_offset(&buffer) == selection.start
15987 {
15988 active_group_id = Some(active_group.group_id);
15989 }
15990
15991 fn filtered<'a>(
15992 snapshot: EditorSnapshot,
15993 severity: GoToDiagnosticSeverityFilter,
15994 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15995 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15996 diagnostics
15997 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15998 .filter(|entry| entry.range.start != entry.range.end)
15999 .filter(|entry| !entry.diagnostic.is_unnecessary)
16000 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16001 }
16002
16003 let snapshot = self.snapshot(window, cx);
16004 let before = filtered(
16005 snapshot.clone(),
16006 severity,
16007 buffer
16008 .diagnostics_in_range(0..selection.start)
16009 .filter(|entry| entry.range.start <= selection.start),
16010 );
16011 let after = filtered(
16012 snapshot,
16013 severity,
16014 buffer
16015 .diagnostics_in_range(selection.start..buffer.len())
16016 .filter(|entry| entry.range.start >= selection.start),
16017 );
16018
16019 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16020 if direction == Direction::Prev {
16021 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16022 {
16023 for diagnostic in prev_diagnostics.into_iter().rev() {
16024 if diagnostic.range.start != selection.start
16025 || active_group_id
16026 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16027 {
16028 found = Some(diagnostic);
16029 break 'outer;
16030 }
16031 }
16032 }
16033 } else {
16034 for diagnostic in after.chain(before) {
16035 if diagnostic.range.start != selection.start
16036 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16037 {
16038 found = Some(diagnostic);
16039 break;
16040 }
16041 }
16042 }
16043 let Some(next_diagnostic) = found else {
16044 return;
16045 };
16046
16047 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16048 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16049 return;
16050 };
16051 self.change_selections(Default::default(), window, cx, |s| {
16052 s.select_ranges(vec![
16053 next_diagnostic.range.start..next_diagnostic.range.start,
16054 ])
16055 });
16056 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16057 self.refresh_edit_prediction(false, true, window, cx);
16058 }
16059
16060 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16061 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16062 let snapshot = self.snapshot(window, cx);
16063 let selection = self.selections.newest::<Point>(cx);
16064 self.go_to_hunk_before_or_after_position(
16065 &snapshot,
16066 selection.head(),
16067 Direction::Next,
16068 window,
16069 cx,
16070 );
16071 }
16072
16073 pub fn go_to_hunk_before_or_after_position(
16074 &mut self,
16075 snapshot: &EditorSnapshot,
16076 position: Point,
16077 direction: Direction,
16078 window: &mut Window,
16079 cx: &mut Context<Editor>,
16080 ) {
16081 let row = if direction == Direction::Next {
16082 self.hunk_after_position(snapshot, position)
16083 .map(|hunk| hunk.row_range.start)
16084 } else {
16085 self.hunk_before_position(snapshot, position)
16086 };
16087
16088 if let Some(row) = row {
16089 let destination = Point::new(row.0, 0);
16090 let autoscroll = Autoscroll::center();
16091
16092 self.unfold_ranges(&[destination..destination], false, false, cx);
16093 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16094 s.select_ranges([destination..destination]);
16095 });
16096 }
16097 }
16098
16099 fn hunk_after_position(
16100 &mut self,
16101 snapshot: &EditorSnapshot,
16102 position: Point,
16103 ) -> Option<MultiBufferDiffHunk> {
16104 snapshot
16105 .buffer_snapshot()
16106 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16107 .find(|hunk| hunk.row_range.start.0 > position.row)
16108 .or_else(|| {
16109 snapshot
16110 .buffer_snapshot()
16111 .diff_hunks_in_range(Point::zero()..position)
16112 .find(|hunk| hunk.row_range.end.0 < position.row)
16113 })
16114 }
16115
16116 fn go_to_prev_hunk(
16117 &mut self,
16118 _: &GoToPreviousHunk,
16119 window: &mut Window,
16120 cx: &mut Context<Self>,
16121 ) {
16122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16123 let snapshot = self.snapshot(window, cx);
16124 let selection = self.selections.newest::<Point>(cx);
16125 self.go_to_hunk_before_or_after_position(
16126 &snapshot,
16127 selection.head(),
16128 Direction::Prev,
16129 window,
16130 cx,
16131 );
16132 }
16133
16134 fn hunk_before_position(
16135 &mut self,
16136 snapshot: &EditorSnapshot,
16137 position: Point,
16138 ) -> Option<MultiBufferRow> {
16139 snapshot
16140 .buffer_snapshot()
16141 .diff_hunk_before(position)
16142 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16143 }
16144
16145 fn go_to_next_change(
16146 &mut self,
16147 _: &GoToNextChange,
16148 window: &mut Window,
16149 cx: &mut Context<Self>,
16150 ) {
16151 if let Some(selections) = self
16152 .change_list
16153 .next_change(1, Direction::Next)
16154 .map(|s| s.to_vec())
16155 {
16156 self.change_selections(Default::default(), window, cx, |s| {
16157 let map = s.display_map();
16158 s.select_display_ranges(selections.iter().map(|a| {
16159 let point = a.to_display_point(&map);
16160 point..point
16161 }))
16162 })
16163 }
16164 }
16165
16166 fn go_to_previous_change(
16167 &mut self,
16168 _: &GoToPreviousChange,
16169 window: &mut Window,
16170 cx: &mut Context<Self>,
16171 ) {
16172 if let Some(selections) = self
16173 .change_list
16174 .next_change(1, Direction::Prev)
16175 .map(|s| s.to_vec())
16176 {
16177 self.change_selections(Default::default(), window, cx, |s| {
16178 let map = s.display_map();
16179 s.select_display_ranges(selections.iter().map(|a| {
16180 let point = a.to_display_point(&map);
16181 point..point
16182 }))
16183 })
16184 }
16185 }
16186
16187 pub fn go_to_next_document_highlight(
16188 &mut self,
16189 _: &GoToNextDocumentHighlight,
16190 window: &mut Window,
16191 cx: &mut Context<Self>,
16192 ) {
16193 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16194 }
16195
16196 pub fn go_to_prev_document_highlight(
16197 &mut self,
16198 _: &GoToPreviousDocumentHighlight,
16199 window: &mut Window,
16200 cx: &mut Context<Self>,
16201 ) {
16202 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16203 }
16204
16205 pub fn go_to_document_highlight_before_or_after_position(
16206 &mut self,
16207 direction: Direction,
16208 window: &mut Window,
16209 cx: &mut Context<Editor>,
16210 ) {
16211 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16212 let snapshot = self.snapshot(window, cx);
16213 let buffer = &snapshot.buffer_snapshot();
16214 let position = self.selections.newest::<Point>(cx).head();
16215 let anchor_position = buffer.anchor_after(position);
16216
16217 // Get all document highlights (both read and write)
16218 let mut all_highlights = Vec::new();
16219
16220 if let Some((_, read_highlights)) = self
16221 .background_highlights
16222 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16223 {
16224 all_highlights.extend(read_highlights.iter());
16225 }
16226
16227 if let Some((_, write_highlights)) = self
16228 .background_highlights
16229 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16230 {
16231 all_highlights.extend(write_highlights.iter());
16232 }
16233
16234 if all_highlights.is_empty() {
16235 return;
16236 }
16237
16238 // Sort highlights by position
16239 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16240
16241 let target_highlight = match direction {
16242 Direction::Next => {
16243 // Find the first highlight after the current position
16244 all_highlights
16245 .iter()
16246 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16247 }
16248 Direction::Prev => {
16249 // Find the last highlight before the current position
16250 all_highlights
16251 .iter()
16252 .rev()
16253 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16254 }
16255 };
16256
16257 if let Some(highlight) = target_highlight {
16258 let destination = highlight.start.to_point(buffer);
16259 let autoscroll = Autoscroll::center();
16260
16261 self.unfold_ranges(&[destination..destination], false, false, cx);
16262 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16263 s.select_ranges([destination..destination]);
16264 });
16265 }
16266 }
16267
16268 fn go_to_line<T: 'static>(
16269 &mut self,
16270 position: Anchor,
16271 highlight_color: Option<Hsla>,
16272 window: &mut Window,
16273 cx: &mut Context<Self>,
16274 ) {
16275 let snapshot = self.snapshot(window, cx).display_snapshot;
16276 let position = position.to_point(&snapshot.buffer_snapshot());
16277 let start = snapshot
16278 .buffer_snapshot()
16279 .clip_point(Point::new(position.row, 0), Bias::Left);
16280 let end = start + Point::new(1, 0);
16281 let start = snapshot.buffer_snapshot().anchor_before(start);
16282 let end = snapshot.buffer_snapshot().anchor_before(end);
16283
16284 self.highlight_rows::<T>(
16285 start..end,
16286 highlight_color
16287 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16288 Default::default(),
16289 cx,
16290 );
16291
16292 if self.buffer.read(cx).is_singleton() {
16293 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16294 }
16295 }
16296
16297 pub fn go_to_definition(
16298 &mut self,
16299 _: &GoToDefinition,
16300 window: &mut Window,
16301 cx: &mut Context<Self>,
16302 ) -> Task<Result<Navigated>> {
16303 let definition =
16304 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16305 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16306 cx.spawn_in(window, async move |editor, cx| {
16307 if definition.await? == Navigated::Yes {
16308 return Ok(Navigated::Yes);
16309 }
16310 match fallback_strategy {
16311 GoToDefinitionFallback::None => Ok(Navigated::No),
16312 GoToDefinitionFallback::FindAllReferences => {
16313 match editor.update_in(cx, |editor, window, cx| {
16314 editor.find_all_references(&FindAllReferences, window, cx)
16315 })? {
16316 Some(references) => references.await,
16317 None => Ok(Navigated::No),
16318 }
16319 }
16320 }
16321 })
16322 }
16323
16324 pub fn go_to_declaration(
16325 &mut self,
16326 _: &GoToDeclaration,
16327 window: &mut Window,
16328 cx: &mut Context<Self>,
16329 ) -> Task<Result<Navigated>> {
16330 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16331 }
16332
16333 pub fn go_to_declaration_split(
16334 &mut self,
16335 _: &GoToDeclaration,
16336 window: &mut Window,
16337 cx: &mut Context<Self>,
16338 ) -> Task<Result<Navigated>> {
16339 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16340 }
16341
16342 pub fn go_to_implementation(
16343 &mut self,
16344 _: &GoToImplementation,
16345 window: &mut Window,
16346 cx: &mut Context<Self>,
16347 ) -> Task<Result<Navigated>> {
16348 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16349 }
16350
16351 pub fn go_to_implementation_split(
16352 &mut self,
16353 _: &GoToImplementationSplit,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) -> Task<Result<Navigated>> {
16357 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16358 }
16359
16360 pub fn go_to_type_definition(
16361 &mut self,
16362 _: &GoToTypeDefinition,
16363 window: &mut Window,
16364 cx: &mut Context<Self>,
16365 ) -> Task<Result<Navigated>> {
16366 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16367 }
16368
16369 pub fn go_to_definition_split(
16370 &mut self,
16371 _: &GoToDefinitionSplit,
16372 window: &mut Window,
16373 cx: &mut Context<Self>,
16374 ) -> Task<Result<Navigated>> {
16375 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16376 }
16377
16378 pub fn go_to_type_definition_split(
16379 &mut self,
16380 _: &GoToTypeDefinitionSplit,
16381 window: &mut Window,
16382 cx: &mut Context<Self>,
16383 ) -> Task<Result<Navigated>> {
16384 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16385 }
16386
16387 fn go_to_definition_of_kind(
16388 &mut self,
16389 kind: GotoDefinitionKind,
16390 split: bool,
16391 window: &mut Window,
16392 cx: &mut Context<Self>,
16393 ) -> Task<Result<Navigated>> {
16394 let Some(provider) = self.semantics_provider.clone() else {
16395 return Task::ready(Ok(Navigated::No));
16396 };
16397 let head = self.selections.newest::<usize>(cx).head();
16398 let buffer = self.buffer.read(cx);
16399 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16400 return Task::ready(Ok(Navigated::No));
16401 };
16402 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16403 return Task::ready(Ok(Navigated::No));
16404 };
16405
16406 cx.spawn_in(window, async move |editor, cx| {
16407 let Some(definitions) = definitions.await? else {
16408 return Ok(Navigated::No);
16409 };
16410 let navigated = editor
16411 .update_in(cx, |editor, window, cx| {
16412 editor.navigate_to_hover_links(
16413 Some(kind),
16414 definitions
16415 .into_iter()
16416 .filter(|location| {
16417 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16418 })
16419 .map(HoverLink::Text)
16420 .collect::<Vec<_>>(),
16421 split,
16422 window,
16423 cx,
16424 )
16425 })?
16426 .await?;
16427 anyhow::Ok(navigated)
16428 })
16429 }
16430
16431 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16432 let selection = self.selections.newest_anchor();
16433 let head = selection.head();
16434 let tail = selection.tail();
16435
16436 let Some((buffer, start_position)) =
16437 self.buffer.read(cx).text_anchor_for_position(head, cx)
16438 else {
16439 return;
16440 };
16441
16442 let end_position = if head != tail {
16443 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16444 return;
16445 };
16446 Some(pos)
16447 } else {
16448 None
16449 };
16450
16451 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16452 let url = if let Some(end_pos) = end_position {
16453 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16454 } else {
16455 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16456 };
16457
16458 if let Some(url) = url {
16459 cx.update(|window, cx| {
16460 if parse_zed_link(&url, cx).is_some() {
16461 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16462 } else {
16463 cx.open_url(&url);
16464 }
16465 })?;
16466 }
16467
16468 anyhow::Ok(())
16469 });
16470
16471 url_finder.detach();
16472 }
16473
16474 pub fn open_selected_filename(
16475 &mut self,
16476 _: &OpenSelectedFilename,
16477 window: &mut Window,
16478 cx: &mut Context<Self>,
16479 ) {
16480 let Some(workspace) = self.workspace() else {
16481 return;
16482 };
16483
16484 let position = self.selections.newest_anchor().head();
16485
16486 let Some((buffer, buffer_position)) =
16487 self.buffer.read(cx).text_anchor_for_position(position, cx)
16488 else {
16489 return;
16490 };
16491
16492 let project = self.project.clone();
16493
16494 cx.spawn_in(window, async move |_, cx| {
16495 let result = find_file(&buffer, project, buffer_position, cx).await;
16496
16497 if let Some((_, path)) = result {
16498 workspace
16499 .update_in(cx, |workspace, window, cx| {
16500 workspace.open_resolved_path(path, window, cx)
16501 })?
16502 .await?;
16503 }
16504 anyhow::Ok(())
16505 })
16506 .detach();
16507 }
16508
16509 pub(crate) fn navigate_to_hover_links(
16510 &mut self,
16511 kind: Option<GotoDefinitionKind>,
16512 definitions: Vec<HoverLink>,
16513 split: bool,
16514 window: &mut Window,
16515 cx: &mut Context<Editor>,
16516 ) -> Task<Result<Navigated>> {
16517 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16518 let mut first_url_or_file = None;
16519 let definitions: Vec<_> = definitions
16520 .into_iter()
16521 .filter_map(|def| match def {
16522 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16523 HoverLink::InlayHint(lsp_location, server_id) => {
16524 let computation =
16525 self.compute_target_location(lsp_location, server_id, window, cx);
16526 Some(cx.background_spawn(computation))
16527 }
16528 HoverLink::Url(url) => {
16529 first_url_or_file = Some(Either::Left(url));
16530 None
16531 }
16532 HoverLink::File(path) => {
16533 first_url_or_file = Some(Either::Right(path));
16534 None
16535 }
16536 })
16537 .collect();
16538
16539 let workspace = self.workspace();
16540
16541 cx.spawn_in(window, async move |editor, cx| {
16542 let locations: Vec<Location> = future::join_all(definitions)
16543 .await
16544 .into_iter()
16545 .filter_map(|location| location.transpose())
16546 .collect::<Result<_>>()
16547 .context("location tasks")?;
16548 let mut locations = cx.update(|_, cx| {
16549 locations
16550 .into_iter()
16551 .map(|location| {
16552 let buffer = location.buffer.read(cx);
16553 (location.buffer, location.range.to_point(buffer))
16554 })
16555 .into_group_map()
16556 })?;
16557 let mut num_locations = 0;
16558 for ranges in locations.values_mut() {
16559 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16560 ranges.dedup();
16561 num_locations += ranges.len();
16562 }
16563
16564 if num_locations > 1 {
16565 let Some(workspace) = workspace else {
16566 return Ok(Navigated::No);
16567 };
16568
16569 let tab_kind = match kind {
16570 Some(GotoDefinitionKind::Implementation) => "Implementations",
16571 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16572 Some(GotoDefinitionKind::Declaration) => "Declarations",
16573 Some(GotoDefinitionKind::Type) => "Types",
16574 };
16575 let title = editor
16576 .update_in(cx, |_, _, cx| {
16577 let target = locations
16578 .iter()
16579 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16580 .map(|(buffer, location)| {
16581 buffer
16582 .read(cx)
16583 .text_for_range(location.clone())
16584 .collect::<String>()
16585 })
16586 .filter(|text| !text.contains('\n'))
16587 .unique()
16588 .take(3)
16589 .join(", ");
16590 if target.is_empty() {
16591 tab_kind.to_owned()
16592 } else {
16593 format!("{tab_kind} for {target}")
16594 }
16595 })
16596 .context("buffer title")?;
16597
16598 let opened = workspace
16599 .update_in(cx, |workspace, window, cx| {
16600 Self::open_locations_in_multibuffer(
16601 workspace,
16602 locations,
16603 title,
16604 split,
16605 MultibufferSelectionMode::First,
16606 window,
16607 cx,
16608 )
16609 })
16610 .is_ok();
16611
16612 anyhow::Ok(Navigated::from_bool(opened))
16613 } else if num_locations == 0 {
16614 // If there is one url or file, open it directly
16615 match first_url_or_file {
16616 Some(Either::Left(url)) => {
16617 cx.update(|_, cx| cx.open_url(&url))?;
16618 Ok(Navigated::Yes)
16619 }
16620 Some(Either::Right(path)) => {
16621 let Some(workspace) = workspace else {
16622 return Ok(Navigated::No);
16623 };
16624
16625 workspace
16626 .update_in(cx, |workspace, window, cx| {
16627 workspace.open_resolved_path(path, window, cx)
16628 })?
16629 .await?;
16630 Ok(Navigated::Yes)
16631 }
16632 None => Ok(Navigated::No),
16633 }
16634 } else {
16635 let Some(workspace) = workspace else {
16636 return Ok(Navigated::No);
16637 };
16638
16639 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16640 let target_range = target_ranges.first().unwrap().clone();
16641
16642 editor.update_in(cx, |editor, window, cx| {
16643 let range = target_range.to_point(target_buffer.read(cx));
16644 let range = editor.range_for_match(&range);
16645 let range = collapse_multiline_range(range);
16646
16647 if !split
16648 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16649 {
16650 editor.go_to_singleton_buffer_range(range, window, cx);
16651 } else {
16652 let pane = workspace.read(cx).active_pane().clone();
16653 window.defer(cx, move |window, cx| {
16654 let target_editor: Entity<Self> =
16655 workspace.update(cx, |workspace, cx| {
16656 let pane = if split {
16657 workspace.adjacent_pane(window, cx)
16658 } else {
16659 workspace.active_pane().clone()
16660 };
16661
16662 workspace.open_project_item(
16663 pane,
16664 target_buffer.clone(),
16665 true,
16666 true,
16667 window,
16668 cx,
16669 )
16670 });
16671 target_editor.update(cx, |target_editor, cx| {
16672 // When selecting a definition in a different buffer, disable the nav history
16673 // to avoid creating a history entry at the previous cursor location.
16674 pane.update(cx, |pane, _| pane.disable_history());
16675 target_editor.go_to_singleton_buffer_range(range, window, cx);
16676 pane.update(cx, |pane, _| pane.enable_history());
16677 });
16678 });
16679 }
16680 Navigated::Yes
16681 })
16682 }
16683 })
16684 }
16685
16686 fn compute_target_location(
16687 &self,
16688 lsp_location: lsp::Location,
16689 server_id: LanguageServerId,
16690 window: &mut Window,
16691 cx: &mut Context<Self>,
16692 ) -> Task<anyhow::Result<Option<Location>>> {
16693 let Some(project) = self.project.clone() else {
16694 return Task::ready(Ok(None));
16695 };
16696
16697 cx.spawn_in(window, async move |editor, cx| {
16698 let location_task = editor.update(cx, |_, cx| {
16699 project.update(cx, |project, cx| {
16700 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16701 })
16702 })?;
16703 let location = Some({
16704 let target_buffer_handle = location_task.await.context("open local buffer")?;
16705 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16706 let target_start = target_buffer
16707 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16708 let target_end = target_buffer
16709 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16710 target_buffer.anchor_after(target_start)
16711 ..target_buffer.anchor_before(target_end)
16712 })?;
16713 Location {
16714 buffer: target_buffer_handle,
16715 range,
16716 }
16717 });
16718 Ok(location)
16719 })
16720 }
16721
16722 pub fn find_all_references(
16723 &mut self,
16724 _: &FindAllReferences,
16725 window: &mut Window,
16726 cx: &mut Context<Self>,
16727 ) -> Option<Task<Result<Navigated>>> {
16728 let selection = self.selections.newest::<usize>(cx);
16729 let multi_buffer = self.buffer.read(cx);
16730 let head = selection.head();
16731
16732 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16733 let head_anchor = multi_buffer_snapshot.anchor_at(
16734 head,
16735 if head < selection.tail() {
16736 Bias::Right
16737 } else {
16738 Bias::Left
16739 },
16740 );
16741
16742 match self
16743 .find_all_references_task_sources
16744 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16745 {
16746 Ok(_) => {
16747 log::info!(
16748 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16749 );
16750 return None;
16751 }
16752 Err(i) => {
16753 self.find_all_references_task_sources.insert(i, head_anchor);
16754 }
16755 }
16756
16757 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16758 let workspace = self.workspace()?;
16759 let project = workspace.read(cx).project().clone();
16760 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16761 Some(cx.spawn_in(window, async move |editor, cx| {
16762 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16763 if let Ok(i) = editor
16764 .find_all_references_task_sources
16765 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16766 {
16767 editor.find_all_references_task_sources.remove(i);
16768 }
16769 });
16770
16771 let Some(locations) = references.await? else {
16772 return anyhow::Ok(Navigated::No);
16773 };
16774 let mut locations = cx.update(|_, cx| {
16775 locations
16776 .into_iter()
16777 .map(|location| {
16778 let buffer = location.buffer.read(cx);
16779 (location.buffer, location.range.to_point(buffer))
16780 })
16781 .into_group_map()
16782 })?;
16783 if locations.is_empty() {
16784 return anyhow::Ok(Navigated::No);
16785 }
16786 for ranges in locations.values_mut() {
16787 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16788 ranges.dedup();
16789 }
16790
16791 workspace.update_in(cx, |workspace, window, cx| {
16792 let target = locations
16793 .iter()
16794 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16795 .map(|(buffer, location)| {
16796 buffer
16797 .read(cx)
16798 .text_for_range(location.clone())
16799 .collect::<String>()
16800 })
16801 .filter(|text| !text.contains('\n'))
16802 .unique()
16803 .take(3)
16804 .join(", ");
16805 let title = if target.is_empty() {
16806 "References".to_owned()
16807 } else {
16808 format!("References to {target}")
16809 };
16810 Self::open_locations_in_multibuffer(
16811 workspace,
16812 locations,
16813 title,
16814 false,
16815 MultibufferSelectionMode::First,
16816 window,
16817 cx,
16818 );
16819 Navigated::Yes
16820 })
16821 }))
16822 }
16823
16824 /// Opens a multibuffer with the given project locations in it
16825 pub fn open_locations_in_multibuffer(
16826 workspace: &mut Workspace,
16827 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16828 title: String,
16829 split: bool,
16830 multibuffer_selection_mode: MultibufferSelectionMode,
16831 window: &mut Window,
16832 cx: &mut Context<Workspace>,
16833 ) {
16834 if locations.is_empty() {
16835 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16836 return;
16837 }
16838
16839 let capability = workspace.project().read(cx).capability();
16840 let mut ranges = <Vec<Range<Anchor>>>::new();
16841
16842 // a key to find existing multibuffer editors with the same set of locations
16843 // to prevent us from opening more and more multibuffer tabs for searches and the like
16844 let mut key = (title.clone(), vec![]);
16845 let excerpt_buffer = cx.new(|cx| {
16846 let key = &mut key.1;
16847 let mut multibuffer = MultiBuffer::new(capability);
16848 for (buffer, mut ranges_for_buffer) in locations {
16849 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16850 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16851 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16852 PathKey::for_buffer(&buffer, cx),
16853 buffer.clone(),
16854 ranges_for_buffer,
16855 multibuffer_context_lines(cx),
16856 cx,
16857 );
16858 ranges.extend(new_ranges)
16859 }
16860
16861 multibuffer.with_title(title)
16862 });
16863 let existing = workspace.active_pane().update(cx, |pane, cx| {
16864 pane.items()
16865 .filter_map(|item| item.downcast::<Editor>())
16866 .find(|editor| {
16867 editor
16868 .read(cx)
16869 .lookup_key
16870 .as_ref()
16871 .and_then(|it| {
16872 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16873 })
16874 .is_some_and(|it| *it == key)
16875 })
16876 });
16877 let editor = existing.unwrap_or_else(|| {
16878 cx.new(|cx| {
16879 let mut editor = Editor::for_multibuffer(
16880 excerpt_buffer,
16881 Some(workspace.project().clone()),
16882 window,
16883 cx,
16884 );
16885 editor.lookup_key = Some(Box::new(key));
16886 editor
16887 })
16888 });
16889 editor.update(cx, |editor, cx| {
16890 match multibuffer_selection_mode {
16891 MultibufferSelectionMode::First => {
16892 if let Some(first_range) = ranges.first() {
16893 editor.change_selections(
16894 SelectionEffects::no_scroll(),
16895 window,
16896 cx,
16897 |selections| {
16898 selections.clear_disjoint();
16899 selections
16900 .select_anchor_ranges(std::iter::once(first_range.clone()));
16901 },
16902 );
16903 }
16904 editor.highlight_background::<Self>(
16905 &ranges,
16906 |theme| theme.colors().editor_highlighted_line_background,
16907 cx,
16908 );
16909 }
16910 MultibufferSelectionMode::All => {
16911 editor.change_selections(
16912 SelectionEffects::no_scroll(),
16913 window,
16914 cx,
16915 |selections| {
16916 selections.clear_disjoint();
16917 selections.select_anchor_ranges(ranges);
16918 },
16919 );
16920 }
16921 }
16922 editor.register_buffers_with_language_servers(cx);
16923 });
16924
16925 let item = Box::new(editor);
16926 let item_id = item.item_id();
16927
16928 if split {
16929 let pane = workspace.adjacent_pane(window, cx);
16930 workspace.add_item(pane, item, None, true, true, window, cx);
16931 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16932 let (preview_item_id, preview_item_idx) =
16933 workspace.active_pane().read_with(cx, |pane, _| {
16934 (pane.preview_item_id(), pane.preview_item_idx())
16935 });
16936
16937 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16938
16939 if let Some(preview_item_id) = preview_item_id {
16940 workspace.active_pane().update(cx, |pane, cx| {
16941 pane.remove_item(preview_item_id, false, false, window, cx);
16942 });
16943 }
16944 } else {
16945 workspace.add_item_to_active_pane(item, None, true, window, cx);
16946 }
16947 workspace.active_pane().update(cx, |pane, cx| {
16948 pane.set_preview_item_id(Some(item_id), cx);
16949 });
16950 }
16951
16952 pub fn rename(
16953 &mut self,
16954 _: &Rename,
16955 window: &mut Window,
16956 cx: &mut Context<Self>,
16957 ) -> Option<Task<Result<()>>> {
16958 use language::ToOffset as _;
16959
16960 let provider = self.semantics_provider.clone()?;
16961 let selection = self.selections.newest_anchor().clone();
16962 let (cursor_buffer, cursor_buffer_position) = self
16963 .buffer
16964 .read(cx)
16965 .text_anchor_for_position(selection.head(), cx)?;
16966 let (tail_buffer, cursor_buffer_position_end) = self
16967 .buffer
16968 .read(cx)
16969 .text_anchor_for_position(selection.tail(), cx)?;
16970 if tail_buffer != cursor_buffer {
16971 return None;
16972 }
16973
16974 let snapshot = cursor_buffer.read(cx).snapshot();
16975 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16976 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16977 let prepare_rename = provider
16978 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16979 .unwrap_or_else(|| Task::ready(Ok(None)));
16980 drop(snapshot);
16981
16982 Some(cx.spawn_in(window, async move |this, cx| {
16983 let rename_range = if let Some(range) = prepare_rename.await? {
16984 Some(range)
16985 } else {
16986 this.update(cx, |this, cx| {
16987 let buffer = this.buffer.read(cx).snapshot(cx);
16988 let mut buffer_highlights = this
16989 .document_highlights_for_position(selection.head(), &buffer)
16990 .filter(|highlight| {
16991 highlight.start.excerpt_id == selection.head().excerpt_id
16992 && highlight.end.excerpt_id == selection.head().excerpt_id
16993 });
16994 buffer_highlights
16995 .next()
16996 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16997 })?
16998 };
16999 if let Some(rename_range) = rename_range {
17000 this.update_in(cx, |this, window, cx| {
17001 let snapshot = cursor_buffer.read(cx).snapshot();
17002 let rename_buffer_range = rename_range.to_offset(&snapshot);
17003 let cursor_offset_in_rename_range =
17004 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17005 let cursor_offset_in_rename_range_end =
17006 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17007
17008 this.take_rename(false, window, cx);
17009 let buffer = this.buffer.read(cx).read(cx);
17010 let cursor_offset = selection.head().to_offset(&buffer);
17011 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17012 let rename_end = rename_start + rename_buffer_range.len();
17013 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17014 let mut old_highlight_id = None;
17015 let old_name: Arc<str> = buffer
17016 .chunks(rename_start..rename_end, true)
17017 .map(|chunk| {
17018 if old_highlight_id.is_none() {
17019 old_highlight_id = chunk.syntax_highlight_id;
17020 }
17021 chunk.text
17022 })
17023 .collect::<String>()
17024 .into();
17025
17026 drop(buffer);
17027
17028 // Position the selection in the rename editor so that it matches the current selection.
17029 this.show_local_selections = false;
17030 let rename_editor = cx.new(|cx| {
17031 let mut editor = Editor::single_line(window, cx);
17032 editor.buffer.update(cx, |buffer, cx| {
17033 buffer.edit([(0..0, old_name.clone())], None, cx)
17034 });
17035 let rename_selection_range = match cursor_offset_in_rename_range
17036 .cmp(&cursor_offset_in_rename_range_end)
17037 {
17038 Ordering::Equal => {
17039 editor.select_all(&SelectAll, window, cx);
17040 return editor;
17041 }
17042 Ordering::Less => {
17043 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17044 }
17045 Ordering::Greater => {
17046 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17047 }
17048 };
17049 if rename_selection_range.end > old_name.len() {
17050 editor.select_all(&SelectAll, window, cx);
17051 } else {
17052 editor.change_selections(Default::default(), window, cx, |s| {
17053 s.select_ranges([rename_selection_range]);
17054 });
17055 }
17056 editor
17057 });
17058 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17059 if e == &EditorEvent::Focused {
17060 cx.emit(EditorEvent::FocusedIn)
17061 }
17062 })
17063 .detach();
17064
17065 let write_highlights =
17066 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17067 let read_highlights =
17068 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17069 let ranges = write_highlights
17070 .iter()
17071 .flat_map(|(_, ranges)| ranges.iter())
17072 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17073 .cloned()
17074 .collect();
17075
17076 this.highlight_text::<Rename>(
17077 ranges,
17078 HighlightStyle {
17079 fade_out: Some(0.6),
17080 ..Default::default()
17081 },
17082 cx,
17083 );
17084 let rename_focus_handle = rename_editor.focus_handle(cx);
17085 window.focus(&rename_focus_handle);
17086 let block_id = this.insert_blocks(
17087 [BlockProperties {
17088 style: BlockStyle::Flex,
17089 placement: BlockPlacement::Below(range.start),
17090 height: Some(1),
17091 render: Arc::new({
17092 let rename_editor = rename_editor.clone();
17093 move |cx: &mut BlockContext| {
17094 let mut text_style = cx.editor_style.text.clone();
17095 if let Some(highlight_style) = old_highlight_id
17096 .and_then(|h| h.style(&cx.editor_style.syntax))
17097 {
17098 text_style = text_style.highlight(highlight_style);
17099 }
17100 div()
17101 .block_mouse_except_scroll()
17102 .pl(cx.anchor_x)
17103 .child(EditorElement::new(
17104 &rename_editor,
17105 EditorStyle {
17106 background: cx.theme().system().transparent,
17107 local_player: cx.editor_style.local_player,
17108 text: text_style,
17109 scrollbar_width: cx.editor_style.scrollbar_width,
17110 syntax: cx.editor_style.syntax.clone(),
17111 status: cx.editor_style.status.clone(),
17112 inlay_hints_style: HighlightStyle {
17113 font_weight: Some(FontWeight::BOLD),
17114 ..make_inlay_hints_style(cx.app)
17115 },
17116 edit_prediction_styles: make_suggestion_styles(
17117 cx.app,
17118 ),
17119 ..EditorStyle::default()
17120 },
17121 ))
17122 .into_any_element()
17123 }
17124 }),
17125 priority: 0,
17126 }],
17127 Some(Autoscroll::fit()),
17128 cx,
17129 )[0];
17130 this.pending_rename = Some(RenameState {
17131 range,
17132 old_name,
17133 editor: rename_editor,
17134 block_id,
17135 });
17136 })?;
17137 }
17138
17139 Ok(())
17140 }))
17141 }
17142
17143 pub fn confirm_rename(
17144 &mut self,
17145 _: &ConfirmRename,
17146 window: &mut Window,
17147 cx: &mut Context<Self>,
17148 ) -> Option<Task<Result<()>>> {
17149 let rename = self.take_rename(false, window, cx)?;
17150 let workspace = self.workspace()?.downgrade();
17151 let (buffer, start) = self
17152 .buffer
17153 .read(cx)
17154 .text_anchor_for_position(rename.range.start, cx)?;
17155 let (end_buffer, _) = self
17156 .buffer
17157 .read(cx)
17158 .text_anchor_for_position(rename.range.end, cx)?;
17159 if buffer != end_buffer {
17160 return None;
17161 }
17162
17163 let old_name = rename.old_name;
17164 let new_name = rename.editor.read(cx).text(cx);
17165
17166 let rename = self.semantics_provider.as_ref()?.perform_rename(
17167 &buffer,
17168 start,
17169 new_name.clone(),
17170 cx,
17171 )?;
17172
17173 Some(cx.spawn_in(window, async move |editor, cx| {
17174 let project_transaction = rename.await?;
17175 Self::open_project_transaction(
17176 &editor,
17177 workspace,
17178 project_transaction,
17179 format!("Rename: {} → {}", old_name, new_name),
17180 cx,
17181 )
17182 .await?;
17183
17184 editor.update(cx, |editor, cx| {
17185 editor.refresh_document_highlights(cx);
17186 })?;
17187 Ok(())
17188 }))
17189 }
17190
17191 fn take_rename(
17192 &mut self,
17193 moving_cursor: bool,
17194 window: &mut Window,
17195 cx: &mut Context<Self>,
17196 ) -> Option<RenameState> {
17197 let rename = self.pending_rename.take()?;
17198 if rename.editor.focus_handle(cx).is_focused(window) {
17199 window.focus(&self.focus_handle);
17200 }
17201
17202 self.remove_blocks(
17203 [rename.block_id].into_iter().collect(),
17204 Some(Autoscroll::fit()),
17205 cx,
17206 );
17207 self.clear_highlights::<Rename>(cx);
17208 self.show_local_selections = true;
17209
17210 if moving_cursor {
17211 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17212 editor.selections.newest::<usize>(cx).head()
17213 });
17214
17215 // Update the selection to match the position of the selection inside
17216 // the rename editor.
17217 let snapshot = self.buffer.read(cx).read(cx);
17218 let rename_range = rename.range.to_offset(&snapshot);
17219 let cursor_in_editor = snapshot
17220 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17221 .min(rename_range.end);
17222 drop(snapshot);
17223
17224 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17225 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17226 });
17227 } else {
17228 self.refresh_document_highlights(cx);
17229 }
17230
17231 Some(rename)
17232 }
17233
17234 pub fn pending_rename(&self) -> Option<&RenameState> {
17235 self.pending_rename.as_ref()
17236 }
17237
17238 fn format(
17239 &mut self,
17240 _: &Format,
17241 window: &mut Window,
17242 cx: &mut Context<Self>,
17243 ) -> Option<Task<Result<()>>> {
17244 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17245
17246 let project = match &self.project {
17247 Some(project) => project.clone(),
17248 None => return None,
17249 };
17250
17251 Some(self.perform_format(
17252 project,
17253 FormatTrigger::Manual,
17254 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17255 window,
17256 cx,
17257 ))
17258 }
17259
17260 fn format_selections(
17261 &mut self,
17262 _: &FormatSelections,
17263 window: &mut Window,
17264 cx: &mut Context<Self>,
17265 ) -> Option<Task<Result<()>>> {
17266 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17267
17268 let project = match &self.project {
17269 Some(project) => project.clone(),
17270 None => return None,
17271 };
17272
17273 let ranges = self
17274 .selections
17275 .all_adjusted(cx)
17276 .into_iter()
17277 .map(|selection| selection.range())
17278 .collect_vec();
17279
17280 Some(self.perform_format(
17281 project,
17282 FormatTrigger::Manual,
17283 FormatTarget::Ranges(ranges),
17284 window,
17285 cx,
17286 ))
17287 }
17288
17289 fn perform_format(
17290 &mut self,
17291 project: Entity<Project>,
17292 trigger: FormatTrigger,
17293 target: FormatTarget,
17294 window: &mut Window,
17295 cx: &mut Context<Self>,
17296 ) -> Task<Result<()>> {
17297 let buffer = self.buffer.clone();
17298 let (buffers, target) = match target {
17299 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17300 FormatTarget::Ranges(selection_ranges) => {
17301 let multi_buffer = buffer.read(cx);
17302 let snapshot = multi_buffer.read(cx);
17303 let mut buffers = HashSet::default();
17304 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17305 BTreeMap::new();
17306 for selection_range in selection_ranges {
17307 for (buffer, buffer_range, _) in
17308 snapshot.range_to_buffer_ranges(selection_range)
17309 {
17310 let buffer_id = buffer.remote_id();
17311 let start = buffer.anchor_before(buffer_range.start);
17312 let end = buffer.anchor_after(buffer_range.end);
17313 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17314 buffer_id_to_ranges
17315 .entry(buffer_id)
17316 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17317 .or_insert_with(|| vec![start..end]);
17318 }
17319 }
17320 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17321 }
17322 };
17323
17324 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17325 let selections_prev = transaction_id_prev
17326 .and_then(|transaction_id_prev| {
17327 // default to selections as they were after the last edit, if we have them,
17328 // instead of how they are now.
17329 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17330 // will take you back to where you made the last edit, instead of staying where you scrolled
17331 self.selection_history
17332 .transaction(transaction_id_prev)
17333 .map(|t| t.0.clone())
17334 })
17335 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17336
17337 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17338 let format = project.update(cx, |project, cx| {
17339 project.format(buffers, target, true, trigger, cx)
17340 });
17341
17342 cx.spawn_in(window, async move |editor, cx| {
17343 let transaction = futures::select_biased! {
17344 transaction = format.log_err().fuse() => transaction,
17345 () = timeout => {
17346 log::warn!("timed out waiting for formatting");
17347 None
17348 }
17349 };
17350
17351 buffer
17352 .update(cx, |buffer, cx| {
17353 if let Some(transaction) = transaction
17354 && !buffer.is_singleton()
17355 {
17356 buffer.push_transaction(&transaction.0, cx);
17357 }
17358 cx.notify();
17359 })
17360 .ok();
17361
17362 if let Some(transaction_id_now) =
17363 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17364 {
17365 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17366 if has_new_transaction {
17367 _ = editor.update(cx, |editor, _| {
17368 editor
17369 .selection_history
17370 .insert_transaction(transaction_id_now, selections_prev);
17371 });
17372 }
17373 }
17374
17375 Ok(())
17376 })
17377 }
17378
17379 fn organize_imports(
17380 &mut self,
17381 _: &OrganizeImports,
17382 window: &mut Window,
17383 cx: &mut Context<Self>,
17384 ) -> Option<Task<Result<()>>> {
17385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17386 let project = match &self.project {
17387 Some(project) => project.clone(),
17388 None => return None,
17389 };
17390 Some(self.perform_code_action_kind(
17391 project,
17392 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17393 window,
17394 cx,
17395 ))
17396 }
17397
17398 fn perform_code_action_kind(
17399 &mut self,
17400 project: Entity<Project>,
17401 kind: CodeActionKind,
17402 window: &mut Window,
17403 cx: &mut Context<Self>,
17404 ) -> Task<Result<()>> {
17405 let buffer = self.buffer.clone();
17406 let buffers = buffer.read(cx).all_buffers();
17407 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17408 let apply_action = project.update(cx, |project, cx| {
17409 project.apply_code_action_kind(buffers, kind, true, cx)
17410 });
17411 cx.spawn_in(window, async move |_, cx| {
17412 let transaction = futures::select_biased! {
17413 () = timeout => {
17414 log::warn!("timed out waiting for executing code action");
17415 None
17416 }
17417 transaction = apply_action.log_err().fuse() => transaction,
17418 };
17419 buffer
17420 .update(cx, |buffer, cx| {
17421 // check if we need this
17422 if let Some(transaction) = transaction
17423 && !buffer.is_singleton()
17424 {
17425 buffer.push_transaction(&transaction.0, cx);
17426 }
17427 cx.notify();
17428 })
17429 .ok();
17430 Ok(())
17431 })
17432 }
17433
17434 pub fn restart_language_server(
17435 &mut self,
17436 _: &RestartLanguageServer,
17437 _: &mut Window,
17438 cx: &mut Context<Self>,
17439 ) {
17440 if let Some(project) = self.project.clone() {
17441 self.buffer.update(cx, |multi_buffer, cx| {
17442 project.update(cx, |project, cx| {
17443 project.restart_language_servers_for_buffers(
17444 multi_buffer.all_buffers().into_iter().collect(),
17445 HashSet::default(),
17446 cx,
17447 );
17448 });
17449 })
17450 }
17451 }
17452
17453 pub fn stop_language_server(
17454 &mut self,
17455 _: &StopLanguageServer,
17456 _: &mut Window,
17457 cx: &mut Context<Self>,
17458 ) {
17459 if let Some(project) = self.project.clone() {
17460 self.buffer.update(cx, |multi_buffer, cx| {
17461 project.update(cx, |project, cx| {
17462 project.stop_language_servers_for_buffers(
17463 multi_buffer.all_buffers().into_iter().collect(),
17464 HashSet::default(),
17465 cx,
17466 );
17467 cx.emit(project::Event::RefreshInlayHints);
17468 });
17469 });
17470 }
17471 }
17472
17473 fn cancel_language_server_work(
17474 workspace: &mut Workspace,
17475 _: &actions::CancelLanguageServerWork,
17476 _: &mut Window,
17477 cx: &mut Context<Workspace>,
17478 ) {
17479 let project = workspace.project();
17480 let buffers = workspace
17481 .active_item(cx)
17482 .and_then(|item| item.act_as::<Editor>(cx))
17483 .map_or(HashSet::default(), |editor| {
17484 editor.read(cx).buffer.read(cx).all_buffers()
17485 });
17486 project.update(cx, |project, cx| {
17487 project.cancel_language_server_work_for_buffers(buffers, cx);
17488 });
17489 }
17490
17491 fn show_character_palette(
17492 &mut self,
17493 _: &ShowCharacterPalette,
17494 window: &mut Window,
17495 _: &mut Context<Self>,
17496 ) {
17497 window.show_character_palette();
17498 }
17499
17500 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17501 if !self.diagnostics_enabled() {
17502 return;
17503 }
17504
17505 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17506 let buffer = self.buffer.read(cx).snapshot(cx);
17507 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17508 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17509 let is_valid = buffer
17510 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17511 .any(|entry| {
17512 entry.diagnostic.is_primary
17513 && !entry.range.is_empty()
17514 && entry.range.start == primary_range_start
17515 && entry.diagnostic.message == active_diagnostics.active_message
17516 });
17517
17518 if !is_valid {
17519 self.dismiss_diagnostics(cx);
17520 }
17521 }
17522 }
17523
17524 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17525 match &self.active_diagnostics {
17526 ActiveDiagnostic::Group(group) => Some(group),
17527 _ => None,
17528 }
17529 }
17530
17531 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17532 if !self.diagnostics_enabled() {
17533 return;
17534 }
17535 self.dismiss_diagnostics(cx);
17536 self.active_diagnostics = ActiveDiagnostic::All;
17537 }
17538
17539 fn activate_diagnostics(
17540 &mut self,
17541 buffer_id: BufferId,
17542 diagnostic: DiagnosticEntryRef<'_, usize>,
17543 window: &mut Window,
17544 cx: &mut Context<Self>,
17545 ) {
17546 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17547 return;
17548 }
17549 self.dismiss_diagnostics(cx);
17550 let snapshot = self.snapshot(window, cx);
17551 let buffer = self.buffer.read(cx).snapshot(cx);
17552 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17553 return;
17554 };
17555
17556 let diagnostic_group = buffer
17557 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17558 .collect::<Vec<_>>();
17559
17560 let blocks =
17561 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17562
17563 let blocks = self.display_map.update(cx, |display_map, cx| {
17564 display_map.insert_blocks(blocks, cx).into_iter().collect()
17565 });
17566 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17567 active_range: buffer.anchor_before(diagnostic.range.start)
17568 ..buffer.anchor_after(diagnostic.range.end),
17569 active_message: diagnostic.diagnostic.message.clone(),
17570 group_id: diagnostic.diagnostic.group_id,
17571 blocks,
17572 });
17573 cx.notify();
17574 }
17575
17576 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17577 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17578 return;
17579 };
17580
17581 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17582 if let ActiveDiagnostic::Group(group) = prev {
17583 self.display_map.update(cx, |display_map, cx| {
17584 display_map.remove_blocks(group.blocks, cx);
17585 });
17586 cx.notify();
17587 }
17588 }
17589
17590 /// Disable inline diagnostics rendering for this editor.
17591 pub fn disable_inline_diagnostics(&mut self) {
17592 self.inline_diagnostics_enabled = false;
17593 self.inline_diagnostics_update = Task::ready(());
17594 self.inline_diagnostics.clear();
17595 }
17596
17597 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17598 self.diagnostics_enabled = false;
17599 self.dismiss_diagnostics(cx);
17600 self.inline_diagnostics_update = Task::ready(());
17601 self.inline_diagnostics.clear();
17602 }
17603
17604 pub fn disable_word_completions(&mut self) {
17605 self.word_completions_enabled = false;
17606 }
17607
17608 pub fn diagnostics_enabled(&self) -> bool {
17609 self.diagnostics_enabled && self.mode.is_full()
17610 }
17611
17612 pub fn inline_diagnostics_enabled(&self) -> bool {
17613 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17614 }
17615
17616 pub fn show_inline_diagnostics(&self) -> bool {
17617 self.show_inline_diagnostics
17618 }
17619
17620 pub fn toggle_inline_diagnostics(
17621 &mut self,
17622 _: &ToggleInlineDiagnostics,
17623 window: &mut Window,
17624 cx: &mut Context<Editor>,
17625 ) {
17626 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17627 self.refresh_inline_diagnostics(false, window, cx);
17628 }
17629
17630 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17631 self.diagnostics_max_severity = severity;
17632 self.display_map.update(cx, |display_map, _| {
17633 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17634 });
17635 }
17636
17637 pub fn toggle_diagnostics(
17638 &mut self,
17639 _: &ToggleDiagnostics,
17640 window: &mut Window,
17641 cx: &mut Context<Editor>,
17642 ) {
17643 if !self.diagnostics_enabled() {
17644 return;
17645 }
17646
17647 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17648 EditorSettings::get_global(cx)
17649 .diagnostics_max_severity
17650 .filter(|severity| severity != &DiagnosticSeverity::Off)
17651 .unwrap_or(DiagnosticSeverity::Hint)
17652 } else {
17653 DiagnosticSeverity::Off
17654 };
17655 self.set_max_diagnostics_severity(new_severity, cx);
17656 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17657 self.active_diagnostics = ActiveDiagnostic::None;
17658 self.inline_diagnostics_update = Task::ready(());
17659 self.inline_diagnostics.clear();
17660 } else {
17661 self.refresh_inline_diagnostics(false, window, cx);
17662 }
17663
17664 cx.notify();
17665 }
17666
17667 pub fn toggle_minimap(
17668 &mut self,
17669 _: &ToggleMinimap,
17670 window: &mut Window,
17671 cx: &mut Context<Editor>,
17672 ) {
17673 if self.supports_minimap(cx) {
17674 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17675 }
17676 }
17677
17678 fn refresh_inline_diagnostics(
17679 &mut self,
17680 debounce: bool,
17681 window: &mut Window,
17682 cx: &mut Context<Self>,
17683 ) {
17684 let max_severity = ProjectSettings::get_global(cx)
17685 .diagnostics
17686 .inline
17687 .max_severity
17688 .unwrap_or(self.diagnostics_max_severity);
17689
17690 if !self.inline_diagnostics_enabled()
17691 || !self.show_inline_diagnostics
17692 || max_severity == DiagnosticSeverity::Off
17693 {
17694 self.inline_diagnostics_update = Task::ready(());
17695 self.inline_diagnostics.clear();
17696 return;
17697 }
17698
17699 let debounce_ms = ProjectSettings::get_global(cx)
17700 .diagnostics
17701 .inline
17702 .update_debounce_ms;
17703 let debounce = if debounce && debounce_ms > 0 {
17704 Some(Duration::from_millis(debounce_ms))
17705 } else {
17706 None
17707 };
17708 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17709 if let Some(debounce) = debounce {
17710 cx.background_executor().timer(debounce).await;
17711 }
17712 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17713 editor
17714 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17715 .ok()
17716 }) else {
17717 return;
17718 };
17719
17720 let new_inline_diagnostics = cx
17721 .background_spawn(async move {
17722 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17723 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17724 let message = diagnostic_entry
17725 .diagnostic
17726 .message
17727 .split_once('\n')
17728 .map(|(line, _)| line)
17729 .map(SharedString::new)
17730 .unwrap_or_else(|| {
17731 SharedString::new(&*diagnostic_entry.diagnostic.message)
17732 });
17733 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17734 let (Ok(i) | Err(i)) = inline_diagnostics
17735 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17736 inline_diagnostics.insert(
17737 i,
17738 (
17739 start_anchor,
17740 InlineDiagnostic {
17741 message,
17742 group_id: diagnostic_entry.diagnostic.group_id,
17743 start: diagnostic_entry.range.start.to_point(&snapshot),
17744 is_primary: diagnostic_entry.diagnostic.is_primary,
17745 severity: diagnostic_entry.diagnostic.severity,
17746 },
17747 ),
17748 );
17749 }
17750 inline_diagnostics
17751 })
17752 .await;
17753
17754 editor
17755 .update(cx, |editor, cx| {
17756 editor.inline_diagnostics = new_inline_diagnostics;
17757 cx.notify();
17758 })
17759 .ok();
17760 });
17761 }
17762
17763 fn pull_diagnostics(
17764 &mut self,
17765 buffer_id: Option<BufferId>,
17766 window: &Window,
17767 cx: &mut Context<Self>,
17768 ) -> Option<()> {
17769 if !self.mode().is_full() {
17770 return None;
17771 }
17772 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17773 .diagnostics
17774 .lsp_pull_diagnostics;
17775 if !pull_diagnostics_settings.enabled {
17776 return None;
17777 }
17778 let project = self.project()?.downgrade();
17779 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17780 let mut buffers = self.buffer.read(cx).all_buffers();
17781 if let Some(buffer_id) = buffer_id {
17782 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17783 }
17784
17785 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17786 cx.background_executor().timer(debounce).await;
17787
17788 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17789 buffers
17790 .into_iter()
17791 .filter_map(|buffer| {
17792 project
17793 .update(cx, |project, cx| {
17794 project.lsp_store().update(cx, |lsp_store, cx| {
17795 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17796 })
17797 })
17798 .ok()
17799 })
17800 .collect::<FuturesUnordered<_>>()
17801 }) else {
17802 return;
17803 };
17804
17805 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17806 match pull_task {
17807 Ok(()) => {
17808 if editor
17809 .update_in(cx, |editor, window, cx| {
17810 editor.update_diagnostics_state(window, cx);
17811 })
17812 .is_err()
17813 {
17814 return;
17815 }
17816 }
17817 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17818 }
17819 }
17820 });
17821
17822 Some(())
17823 }
17824
17825 pub fn set_selections_from_remote(
17826 &mut self,
17827 selections: Vec<Selection<Anchor>>,
17828 pending_selection: Option<Selection<Anchor>>,
17829 window: &mut Window,
17830 cx: &mut Context<Self>,
17831 ) {
17832 let old_cursor_position = self.selections.newest_anchor().head();
17833 self.selections.change_with(cx, |s| {
17834 s.select_anchors(selections);
17835 if let Some(pending_selection) = pending_selection {
17836 s.set_pending(pending_selection, SelectMode::Character);
17837 } else {
17838 s.clear_pending();
17839 }
17840 });
17841 self.selections_did_change(
17842 false,
17843 &old_cursor_position,
17844 SelectionEffects::default(),
17845 window,
17846 cx,
17847 );
17848 }
17849
17850 pub fn transact(
17851 &mut self,
17852 window: &mut Window,
17853 cx: &mut Context<Self>,
17854 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17855 ) -> Option<TransactionId> {
17856 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17857 this.start_transaction_at(Instant::now(), window, cx);
17858 update(this, window, cx);
17859 this.end_transaction_at(Instant::now(), cx)
17860 })
17861 }
17862
17863 pub fn start_transaction_at(
17864 &mut self,
17865 now: Instant,
17866 window: &mut Window,
17867 cx: &mut Context<Self>,
17868 ) -> Option<TransactionId> {
17869 self.end_selection(window, cx);
17870 if let Some(tx_id) = self
17871 .buffer
17872 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17873 {
17874 self.selection_history
17875 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17876 cx.emit(EditorEvent::TransactionBegun {
17877 transaction_id: tx_id,
17878 });
17879 Some(tx_id)
17880 } else {
17881 None
17882 }
17883 }
17884
17885 pub fn end_transaction_at(
17886 &mut self,
17887 now: Instant,
17888 cx: &mut Context<Self>,
17889 ) -> Option<TransactionId> {
17890 if let Some(transaction_id) = self
17891 .buffer
17892 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17893 {
17894 if let Some((_, end_selections)) =
17895 self.selection_history.transaction_mut(transaction_id)
17896 {
17897 *end_selections = Some(self.selections.disjoint_anchors_arc());
17898 } else {
17899 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17900 }
17901
17902 cx.emit(EditorEvent::Edited { transaction_id });
17903 Some(transaction_id)
17904 } else {
17905 None
17906 }
17907 }
17908
17909 pub fn modify_transaction_selection_history(
17910 &mut self,
17911 transaction_id: TransactionId,
17912 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17913 ) -> bool {
17914 self.selection_history
17915 .transaction_mut(transaction_id)
17916 .map(modify)
17917 .is_some()
17918 }
17919
17920 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17921 if self.selection_mark_mode {
17922 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17923 s.move_with(|_, sel| {
17924 sel.collapse_to(sel.head(), SelectionGoal::None);
17925 });
17926 })
17927 }
17928 self.selection_mark_mode = true;
17929 cx.notify();
17930 }
17931
17932 pub fn swap_selection_ends(
17933 &mut self,
17934 _: &actions::SwapSelectionEnds,
17935 window: &mut Window,
17936 cx: &mut Context<Self>,
17937 ) {
17938 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17939 s.move_with(|_, sel| {
17940 if sel.start != sel.end {
17941 sel.reversed = !sel.reversed
17942 }
17943 });
17944 });
17945 self.request_autoscroll(Autoscroll::newest(), cx);
17946 cx.notify();
17947 }
17948
17949 pub fn toggle_focus(
17950 workspace: &mut Workspace,
17951 _: &actions::ToggleFocus,
17952 window: &mut Window,
17953 cx: &mut Context<Workspace>,
17954 ) {
17955 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17956 return;
17957 };
17958 workspace.activate_item(&item, true, true, window, cx);
17959 }
17960
17961 pub fn toggle_fold(
17962 &mut self,
17963 _: &actions::ToggleFold,
17964 window: &mut Window,
17965 cx: &mut Context<Self>,
17966 ) {
17967 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17968 let selection = self.selections.newest::<Point>(cx);
17969
17970 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17971 let range = if selection.is_empty() {
17972 let point = selection.head().to_display_point(&display_map);
17973 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17974 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17975 .to_point(&display_map);
17976 start..end
17977 } else {
17978 selection.range()
17979 };
17980 if display_map.folds_in_range(range).next().is_some() {
17981 self.unfold_lines(&Default::default(), window, cx)
17982 } else {
17983 self.fold(&Default::default(), window, cx)
17984 }
17985 } else {
17986 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17987 let buffer_ids: HashSet<_> = self
17988 .selections
17989 .disjoint_anchor_ranges()
17990 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17991 .collect();
17992
17993 let should_unfold = buffer_ids
17994 .iter()
17995 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17996
17997 for buffer_id in buffer_ids {
17998 if should_unfold {
17999 self.unfold_buffer(buffer_id, cx);
18000 } else {
18001 self.fold_buffer(buffer_id, cx);
18002 }
18003 }
18004 }
18005 }
18006
18007 pub fn toggle_fold_recursive(
18008 &mut self,
18009 _: &actions::ToggleFoldRecursive,
18010 window: &mut Window,
18011 cx: &mut Context<Self>,
18012 ) {
18013 let selection = self.selections.newest::<Point>(cx);
18014
18015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18016 let range = if selection.is_empty() {
18017 let point = selection.head().to_display_point(&display_map);
18018 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18019 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18020 .to_point(&display_map);
18021 start..end
18022 } else {
18023 selection.range()
18024 };
18025 if display_map.folds_in_range(range).next().is_some() {
18026 self.unfold_recursive(&Default::default(), window, cx)
18027 } else {
18028 self.fold_recursive(&Default::default(), window, cx)
18029 }
18030 }
18031
18032 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18033 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18034 let mut to_fold = Vec::new();
18035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18036 let selections = self.selections.all_adjusted(cx);
18037
18038 for selection in selections {
18039 let range = selection.range().sorted();
18040 let buffer_start_row = range.start.row;
18041
18042 if range.start.row != range.end.row {
18043 let mut found = false;
18044 let mut row = range.start.row;
18045 while row <= range.end.row {
18046 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18047 {
18048 found = true;
18049 row = crease.range().end.row + 1;
18050 to_fold.push(crease);
18051 } else {
18052 row += 1
18053 }
18054 }
18055 if found {
18056 continue;
18057 }
18058 }
18059
18060 for row in (0..=range.start.row).rev() {
18061 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18062 && crease.range().end.row >= buffer_start_row
18063 {
18064 to_fold.push(crease);
18065 if row <= range.start.row {
18066 break;
18067 }
18068 }
18069 }
18070 }
18071
18072 self.fold_creases(to_fold, true, window, cx);
18073 } else {
18074 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18075 let buffer_ids = self
18076 .selections
18077 .disjoint_anchor_ranges()
18078 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18079 .collect::<HashSet<_>>();
18080 for buffer_id in buffer_ids {
18081 self.fold_buffer(buffer_id, cx);
18082 }
18083 }
18084 }
18085
18086 pub fn toggle_fold_all(
18087 &mut self,
18088 _: &actions::ToggleFoldAll,
18089 window: &mut Window,
18090 cx: &mut Context<Self>,
18091 ) {
18092 if self.buffer.read(cx).is_singleton() {
18093 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18094 let has_folds = display_map
18095 .folds_in_range(0..display_map.buffer_snapshot().len())
18096 .next()
18097 .is_some();
18098
18099 if has_folds {
18100 self.unfold_all(&actions::UnfoldAll, window, cx);
18101 } else {
18102 self.fold_all(&actions::FoldAll, window, cx);
18103 }
18104 } else {
18105 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18106 let should_unfold = buffer_ids
18107 .iter()
18108 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18109
18110 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18111 editor
18112 .update_in(cx, |editor, _, cx| {
18113 for buffer_id in buffer_ids {
18114 if should_unfold {
18115 editor.unfold_buffer(buffer_id, cx);
18116 } else {
18117 editor.fold_buffer(buffer_id, cx);
18118 }
18119 }
18120 })
18121 .ok();
18122 });
18123 }
18124 }
18125
18126 fn fold_at_level(
18127 &mut self,
18128 fold_at: &FoldAtLevel,
18129 window: &mut Window,
18130 cx: &mut Context<Self>,
18131 ) {
18132 if !self.buffer.read(cx).is_singleton() {
18133 return;
18134 }
18135
18136 let fold_at_level = fold_at.0;
18137 let snapshot = self.buffer.read(cx).snapshot(cx);
18138 let mut to_fold = Vec::new();
18139 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18140
18141 let row_ranges_to_keep: Vec<Range<u32>> = self
18142 .selections
18143 .all::<Point>(cx)
18144 .into_iter()
18145 .map(|sel| sel.start.row..sel.end.row)
18146 .collect();
18147
18148 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18149 while start_row < end_row {
18150 match self
18151 .snapshot(window, cx)
18152 .crease_for_buffer_row(MultiBufferRow(start_row))
18153 {
18154 Some(crease) => {
18155 let nested_start_row = crease.range().start.row + 1;
18156 let nested_end_row = crease.range().end.row;
18157
18158 if current_level < fold_at_level {
18159 stack.push((nested_start_row, nested_end_row, current_level + 1));
18160 } else if current_level == fold_at_level {
18161 // Fold iff there is no selection completely contained within the fold region
18162 if !row_ranges_to_keep.iter().any(|selection| {
18163 selection.end >= nested_start_row
18164 && selection.start <= nested_end_row
18165 }) {
18166 to_fold.push(crease);
18167 }
18168 }
18169
18170 start_row = nested_end_row + 1;
18171 }
18172 None => start_row += 1,
18173 }
18174 }
18175 }
18176
18177 self.fold_creases(to_fold, true, window, cx);
18178 }
18179
18180 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18181 if self.buffer.read(cx).is_singleton() {
18182 let mut fold_ranges = Vec::new();
18183 let snapshot = self.buffer.read(cx).snapshot(cx);
18184
18185 for row in 0..snapshot.max_row().0 {
18186 if let Some(foldable_range) = self
18187 .snapshot(window, cx)
18188 .crease_for_buffer_row(MultiBufferRow(row))
18189 {
18190 fold_ranges.push(foldable_range);
18191 }
18192 }
18193
18194 self.fold_creases(fold_ranges, true, window, cx);
18195 } else {
18196 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18197 editor
18198 .update_in(cx, |editor, _, cx| {
18199 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18200 editor.fold_buffer(buffer_id, cx);
18201 }
18202 })
18203 .ok();
18204 });
18205 }
18206 }
18207
18208 pub fn fold_function_bodies(
18209 &mut self,
18210 _: &actions::FoldFunctionBodies,
18211 window: &mut Window,
18212 cx: &mut Context<Self>,
18213 ) {
18214 let snapshot = self.buffer.read(cx).snapshot(cx);
18215
18216 let ranges = snapshot
18217 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18218 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18219 .collect::<Vec<_>>();
18220
18221 let creases = ranges
18222 .into_iter()
18223 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18224 .collect();
18225
18226 self.fold_creases(creases, true, window, cx);
18227 }
18228
18229 pub fn fold_recursive(
18230 &mut self,
18231 _: &actions::FoldRecursive,
18232 window: &mut Window,
18233 cx: &mut Context<Self>,
18234 ) {
18235 let mut to_fold = Vec::new();
18236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18237 let selections = self.selections.all_adjusted(cx);
18238
18239 for selection in selections {
18240 let range = selection.range().sorted();
18241 let buffer_start_row = range.start.row;
18242
18243 if range.start.row != range.end.row {
18244 let mut found = false;
18245 for row in range.start.row..=range.end.row {
18246 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18247 found = true;
18248 to_fold.push(crease);
18249 }
18250 }
18251 if found {
18252 continue;
18253 }
18254 }
18255
18256 for row in (0..=range.start.row).rev() {
18257 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18258 if crease.range().end.row >= buffer_start_row {
18259 to_fold.push(crease);
18260 } else {
18261 break;
18262 }
18263 }
18264 }
18265 }
18266
18267 self.fold_creases(to_fold, true, window, cx);
18268 }
18269
18270 pub fn fold_at(
18271 &mut self,
18272 buffer_row: MultiBufferRow,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) {
18276 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18277
18278 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18279 let autoscroll = self
18280 .selections
18281 .all::<Point>(cx)
18282 .iter()
18283 .any(|selection| crease.range().overlaps(&selection.range()));
18284
18285 self.fold_creases(vec![crease], autoscroll, window, cx);
18286 }
18287 }
18288
18289 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18290 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18291 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18292 let buffer = display_map.buffer_snapshot();
18293 let selections = self.selections.all::<Point>(cx);
18294 let ranges = selections
18295 .iter()
18296 .map(|s| {
18297 let range = s.display_range(&display_map).sorted();
18298 let mut start = range.start.to_point(&display_map);
18299 let mut end = range.end.to_point(&display_map);
18300 start.column = 0;
18301 end.column = buffer.line_len(MultiBufferRow(end.row));
18302 start..end
18303 })
18304 .collect::<Vec<_>>();
18305
18306 self.unfold_ranges(&ranges, true, true, cx);
18307 } else {
18308 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18309 let buffer_ids = self
18310 .selections
18311 .disjoint_anchor_ranges()
18312 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18313 .collect::<HashSet<_>>();
18314 for buffer_id in buffer_ids {
18315 self.unfold_buffer(buffer_id, cx);
18316 }
18317 }
18318 }
18319
18320 pub fn unfold_recursive(
18321 &mut self,
18322 _: &UnfoldRecursive,
18323 _window: &mut Window,
18324 cx: &mut Context<Self>,
18325 ) {
18326 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18327 let selections = self.selections.all::<Point>(cx);
18328 let ranges = selections
18329 .iter()
18330 .map(|s| {
18331 let mut range = s.display_range(&display_map).sorted();
18332 *range.start.column_mut() = 0;
18333 *range.end.column_mut() = display_map.line_len(range.end.row());
18334 let start = range.start.to_point(&display_map);
18335 let end = range.end.to_point(&display_map);
18336 start..end
18337 })
18338 .collect::<Vec<_>>();
18339
18340 self.unfold_ranges(&ranges, true, true, cx);
18341 }
18342
18343 pub fn unfold_at(
18344 &mut self,
18345 buffer_row: MultiBufferRow,
18346 _window: &mut Window,
18347 cx: &mut Context<Self>,
18348 ) {
18349 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18350
18351 let intersection_range = Point::new(buffer_row.0, 0)
18352 ..Point::new(
18353 buffer_row.0,
18354 display_map.buffer_snapshot().line_len(buffer_row),
18355 );
18356
18357 let autoscroll = self
18358 .selections
18359 .all::<Point>(cx)
18360 .iter()
18361 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18362
18363 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18364 }
18365
18366 pub fn unfold_all(
18367 &mut self,
18368 _: &actions::UnfoldAll,
18369 _window: &mut Window,
18370 cx: &mut Context<Self>,
18371 ) {
18372 if self.buffer.read(cx).is_singleton() {
18373 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18374 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18375 } else {
18376 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18377 editor
18378 .update(cx, |editor, cx| {
18379 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18380 editor.unfold_buffer(buffer_id, cx);
18381 }
18382 })
18383 .ok();
18384 });
18385 }
18386 }
18387
18388 pub fn fold_selected_ranges(
18389 &mut self,
18390 _: &FoldSelectedRanges,
18391 window: &mut Window,
18392 cx: &mut Context<Self>,
18393 ) {
18394 let selections = self.selections.all_adjusted(cx);
18395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18396 let ranges = selections
18397 .into_iter()
18398 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18399 .collect::<Vec<_>>();
18400 self.fold_creases(ranges, true, window, cx);
18401 }
18402
18403 pub fn fold_ranges<T: ToOffset + Clone>(
18404 &mut self,
18405 ranges: Vec<Range<T>>,
18406 auto_scroll: bool,
18407 window: &mut Window,
18408 cx: &mut Context<Self>,
18409 ) {
18410 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18411 let ranges = ranges
18412 .into_iter()
18413 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18414 .collect::<Vec<_>>();
18415 self.fold_creases(ranges, auto_scroll, window, cx);
18416 }
18417
18418 pub fn fold_creases<T: ToOffset + Clone>(
18419 &mut self,
18420 creases: Vec<Crease<T>>,
18421 auto_scroll: bool,
18422 _window: &mut Window,
18423 cx: &mut Context<Self>,
18424 ) {
18425 if creases.is_empty() {
18426 return;
18427 }
18428
18429 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18430
18431 if auto_scroll {
18432 self.request_autoscroll(Autoscroll::fit(), cx);
18433 }
18434
18435 cx.notify();
18436
18437 self.scrollbar_marker_state.dirty = true;
18438 self.folds_did_change(cx);
18439 }
18440
18441 /// Removes any folds whose ranges intersect any of the given ranges.
18442 pub fn unfold_ranges<T: ToOffset + Clone>(
18443 &mut self,
18444 ranges: &[Range<T>],
18445 inclusive: bool,
18446 auto_scroll: bool,
18447 cx: &mut Context<Self>,
18448 ) {
18449 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18450 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18451 });
18452 self.folds_did_change(cx);
18453 }
18454
18455 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18456 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18457 return;
18458 }
18459 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18460 self.display_map.update(cx, |display_map, cx| {
18461 display_map.fold_buffers([buffer_id], cx)
18462 });
18463 cx.emit(EditorEvent::BufferFoldToggled {
18464 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18465 folded: true,
18466 });
18467 cx.notify();
18468 }
18469
18470 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18471 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18472 return;
18473 }
18474 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18475 self.display_map.update(cx, |display_map, cx| {
18476 display_map.unfold_buffers([buffer_id], cx);
18477 });
18478 cx.emit(EditorEvent::BufferFoldToggled {
18479 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18480 folded: false,
18481 });
18482 cx.notify();
18483 }
18484
18485 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18486 self.display_map.read(cx).is_buffer_folded(buffer)
18487 }
18488
18489 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18490 self.display_map.read(cx).folded_buffers()
18491 }
18492
18493 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18494 self.display_map.update(cx, |display_map, cx| {
18495 display_map.disable_header_for_buffer(buffer_id, cx);
18496 });
18497 cx.notify();
18498 }
18499
18500 /// Removes any folds with the given ranges.
18501 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18502 &mut self,
18503 ranges: &[Range<T>],
18504 type_id: TypeId,
18505 auto_scroll: bool,
18506 cx: &mut Context<Self>,
18507 ) {
18508 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18509 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18510 });
18511 self.folds_did_change(cx);
18512 }
18513
18514 fn remove_folds_with<T: ToOffset + Clone>(
18515 &mut self,
18516 ranges: &[Range<T>],
18517 auto_scroll: bool,
18518 cx: &mut Context<Self>,
18519 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18520 ) {
18521 if ranges.is_empty() {
18522 return;
18523 }
18524
18525 let mut buffers_affected = HashSet::default();
18526 let multi_buffer = self.buffer().read(cx);
18527 for range in ranges {
18528 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18529 buffers_affected.insert(buffer.read(cx).remote_id());
18530 };
18531 }
18532
18533 self.display_map.update(cx, update);
18534
18535 if auto_scroll {
18536 self.request_autoscroll(Autoscroll::fit(), cx);
18537 }
18538
18539 cx.notify();
18540 self.scrollbar_marker_state.dirty = true;
18541 self.active_indent_guides_state.dirty = true;
18542 }
18543
18544 pub fn update_renderer_widths(
18545 &mut self,
18546 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18547 cx: &mut Context<Self>,
18548 ) -> bool {
18549 self.display_map
18550 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18551 }
18552
18553 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18554 self.display_map.read(cx).fold_placeholder.clone()
18555 }
18556
18557 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18558 self.buffer.update(cx, |buffer, cx| {
18559 buffer.set_all_diff_hunks_expanded(cx);
18560 });
18561 }
18562
18563 pub fn expand_all_diff_hunks(
18564 &mut self,
18565 _: &ExpandAllDiffHunks,
18566 _window: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) {
18569 self.buffer.update(cx, |buffer, cx| {
18570 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18571 });
18572 }
18573
18574 pub fn toggle_selected_diff_hunks(
18575 &mut self,
18576 _: &ToggleSelectedDiffHunks,
18577 _window: &mut Window,
18578 cx: &mut Context<Self>,
18579 ) {
18580 let ranges: Vec<_> = self
18581 .selections
18582 .disjoint_anchors()
18583 .iter()
18584 .map(|s| s.range())
18585 .collect();
18586 self.toggle_diff_hunks_in_ranges(ranges, cx);
18587 }
18588
18589 pub fn diff_hunks_in_ranges<'a>(
18590 &'a self,
18591 ranges: &'a [Range<Anchor>],
18592 buffer: &'a MultiBufferSnapshot,
18593 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18594 ranges.iter().flat_map(move |range| {
18595 let end_excerpt_id = range.end.excerpt_id;
18596 let range = range.to_point(buffer);
18597 let mut peek_end = range.end;
18598 if range.end.row < buffer.max_row().0 {
18599 peek_end = Point::new(range.end.row + 1, 0);
18600 }
18601 buffer
18602 .diff_hunks_in_range(range.start..peek_end)
18603 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18604 })
18605 }
18606
18607 pub fn has_stageable_diff_hunks_in_ranges(
18608 &self,
18609 ranges: &[Range<Anchor>],
18610 snapshot: &MultiBufferSnapshot,
18611 ) -> bool {
18612 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18613 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18614 }
18615
18616 pub fn toggle_staged_selected_diff_hunks(
18617 &mut self,
18618 _: &::git::ToggleStaged,
18619 _: &mut Window,
18620 cx: &mut Context<Self>,
18621 ) {
18622 let snapshot = self.buffer.read(cx).snapshot(cx);
18623 let ranges: Vec<_> = self
18624 .selections
18625 .disjoint_anchors()
18626 .iter()
18627 .map(|s| s.range())
18628 .collect();
18629 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18630 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18631 }
18632
18633 pub fn set_render_diff_hunk_controls(
18634 &mut self,
18635 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18636 cx: &mut Context<Self>,
18637 ) {
18638 self.render_diff_hunk_controls = render_diff_hunk_controls;
18639 cx.notify();
18640 }
18641
18642 pub fn stage_and_next(
18643 &mut self,
18644 _: &::git::StageAndNext,
18645 window: &mut Window,
18646 cx: &mut Context<Self>,
18647 ) {
18648 self.do_stage_or_unstage_and_next(true, window, cx);
18649 }
18650
18651 pub fn unstage_and_next(
18652 &mut self,
18653 _: &::git::UnstageAndNext,
18654 window: &mut Window,
18655 cx: &mut Context<Self>,
18656 ) {
18657 self.do_stage_or_unstage_and_next(false, window, cx);
18658 }
18659
18660 pub fn stage_or_unstage_diff_hunks(
18661 &mut self,
18662 stage: bool,
18663 ranges: Vec<Range<Anchor>>,
18664 cx: &mut Context<Self>,
18665 ) {
18666 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18667 cx.spawn(async move |this, cx| {
18668 task.await?;
18669 this.update(cx, |this, cx| {
18670 let snapshot = this.buffer.read(cx).snapshot(cx);
18671 let chunk_by = this
18672 .diff_hunks_in_ranges(&ranges, &snapshot)
18673 .chunk_by(|hunk| hunk.buffer_id);
18674 for (buffer_id, hunks) in &chunk_by {
18675 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18676 }
18677 })
18678 })
18679 .detach_and_log_err(cx);
18680 }
18681
18682 fn save_buffers_for_ranges_if_needed(
18683 &mut self,
18684 ranges: &[Range<Anchor>],
18685 cx: &mut Context<Editor>,
18686 ) -> Task<Result<()>> {
18687 let multibuffer = self.buffer.read(cx);
18688 let snapshot = multibuffer.read(cx);
18689 let buffer_ids: HashSet<_> = ranges
18690 .iter()
18691 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18692 .collect();
18693 drop(snapshot);
18694
18695 let mut buffers = HashSet::default();
18696 for buffer_id in buffer_ids {
18697 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18698 let buffer = buffer_entity.read(cx);
18699 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18700 {
18701 buffers.insert(buffer_entity);
18702 }
18703 }
18704 }
18705
18706 if let Some(project) = &self.project {
18707 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18708 } else {
18709 Task::ready(Ok(()))
18710 }
18711 }
18712
18713 fn do_stage_or_unstage_and_next(
18714 &mut self,
18715 stage: bool,
18716 window: &mut Window,
18717 cx: &mut Context<Self>,
18718 ) {
18719 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18720
18721 if ranges.iter().any(|range| range.start != range.end) {
18722 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18723 return;
18724 }
18725
18726 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18727 let snapshot = self.snapshot(window, cx);
18728 let position = self.selections.newest::<Point>(cx).head();
18729 let mut row = snapshot
18730 .buffer_snapshot()
18731 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18732 .find(|hunk| hunk.row_range.start.0 > position.row)
18733 .map(|hunk| hunk.row_range.start);
18734
18735 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18736 // Outside of the project diff editor, wrap around to the beginning.
18737 if !all_diff_hunks_expanded {
18738 row = row.or_else(|| {
18739 snapshot
18740 .buffer_snapshot()
18741 .diff_hunks_in_range(Point::zero()..position)
18742 .find(|hunk| hunk.row_range.end.0 < position.row)
18743 .map(|hunk| hunk.row_range.start)
18744 });
18745 }
18746
18747 if let Some(row) = row {
18748 let destination = Point::new(row.0, 0);
18749 let autoscroll = Autoscroll::center();
18750
18751 self.unfold_ranges(&[destination..destination], false, false, cx);
18752 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18753 s.select_ranges([destination..destination]);
18754 });
18755 }
18756 }
18757
18758 fn do_stage_or_unstage(
18759 &self,
18760 stage: bool,
18761 buffer_id: BufferId,
18762 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18763 cx: &mut App,
18764 ) -> Option<()> {
18765 let project = self.project()?;
18766 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18767 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18768 let buffer_snapshot = buffer.read(cx).snapshot();
18769 let file_exists = buffer_snapshot
18770 .file()
18771 .is_some_and(|file| file.disk_state().exists());
18772 diff.update(cx, |diff, cx| {
18773 diff.stage_or_unstage_hunks(
18774 stage,
18775 &hunks
18776 .map(|hunk| buffer_diff::DiffHunk {
18777 buffer_range: hunk.buffer_range,
18778 diff_base_byte_range: hunk.diff_base_byte_range,
18779 secondary_status: hunk.secondary_status,
18780 range: Point::zero()..Point::zero(), // unused
18781 })
18782 .collect::<Vec<_>>(),
18783 &buffer_snapshot,
18784 file_exists,
18785 cx,
18786 )
18787 });
18788 None
18789 }
18790
18791 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18792 let ranges: Vec<_> = self
18793 .selections
18794 .disjoint_anchors()
18795 .iter()
18796 .map(|s| s.range())
18797 .collect();
18798 self.buffer
18799 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18800 }
18801
18802 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18803 self.buffer.update(cx, |buffer, cx| {
18804 let ranges = vec![Anchor::min()..Anchor::max()];
18805 if !buffer.all_diff_hunks_expanded()
18806 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18807 {
18808 buffer.collapse_diff_hunks(ranges, cx);
18809 true
18810 } else {
18811 false
18812 }
18813 })
18814 }
18815
18816 fn toggle_diff_hunks_in_ranges(
18817 &mut self,
18818 ranges: Vec<Range<Anchor>>,
18819 cx: &mut Context<Editor>,
18820 ) {
18821 self.buffer.update(cx, |buffer, cx| {
18822 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18823 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18824 })
18825 }
18826
18827 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18828 self.buffer.update(cx, |buffer, cx| {
18829 let snapshot = buffer.snapshot(cx);
18830 let excerpt_id = range.end.excerpt_id;
18831 let point_range = range.to_point(&snapshot);
18832 let expand = !buffer.single_hunk_is_expanded(range, cx);
18833 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18834 })
18835 }
18836
18837 pub(crate) fn apply_all_diff_hunks(
18838 &mut self,
18839 _: &ApplyAllDiffHunks,
18840 window: &mut Window,
18841 cx: &mut Context<Self>,
18842 ) {
18843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18844
18845 let buffers = self.buffer.read(cx).all_buffers();
18846 for branch_buffer in buffers {
18847 branch_buffer.update(cx, |branch_buffer, cx| {
18848 branch_buffer.merge_into_base(Vec::new(), cx);
18849 });
18850 }
18851
18852 if let Some(project) = self.project.clone() {
18853 self.save(
18854 SaveOptions {
18855 format: true,
18856 autosave: false,
18857 },
18858 project,
18859 window,
18860 cx,
18861 )
18862 .detach_and_log_err(cx);
18863 }
18864 }
18865
18866 pub(crate) fn apply_selected_diff_hunks(
18867 &mut self,
18868 _: &ApplyDiffHunk,
18869 window: &mut Window,
18870 cx: &mut Context<Self>,
18871 ) {
18872 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18873 let snapshot = self.snapshot(window, cx);
18874 let hunks = snapshot.hunks_for_ranges(
18875 self.selections
18876 .all(cx)
18877 .into_iter()
18878 .map(|selection| selection.range()),
18879 );
18880 let mut ranges_by_buffer = HashMap::default();
18881 self.transact(window, cx, |editor, _window, cx| {
18882 for hunk in hunks {
18883 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18884 ranges_by_buffer
18885 .entry(buffer.clone())
18886 .or_insert_with(Vec::new)
18887 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18888 }
18889 }
18890
18891 for (buffer, ranges) in ranges_by_buffer {
18892 buffer.update(cx, |buffer, cx| {
18893 buffer.merge_into_base(ranges, cx);
18894 });
18895 }
18896 });
18897
18898 if let Some(project) = self.project.clone() {
18899 self.save(
18900 SaveOptions {
18901 format: true,
18902 autosave: false,
18903 },
18904 project,
18905 window,
18906 cx,
18907 )
18908 .detach_and_log_err(cx);
18909 }
18910 }
18911
18912 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18913 if hovered != self.gutter_hovered {
18914 self.gutter_hovered = hovered;
18915 cx.notify();
18916 }
18917 }
18918
18919 pub fn insert_blocks(
18920 &mut self,
18921 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18922 autoscroll: Option<Autoscroll>,
18923 cx: &mut Context<Self>,
18924 ) -> Vec<CustomBlockId> {
18925 let blocks = self
18926 .display_map
18927 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18928 if let Some(autoscroll) = autoscroll {
18929 self.request_autoscroll(autoscroll, cx);
18930 }
18931 cx.notify();
18932 blocks
18933 }
18934
18935 pub fn resize_blocks(
18936 &mut self,
18937 heights: HashMap<CustomBlockId, u32>,
18938 autoscroll: Option<Autoscroll>,
18939 cx: &mut Context<Self>,
18940 ) {
18941 self.display_map
18942 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18943 if let Some(autoscroll) = autoscroll {
18944 self.request_autoscroll(autoscroll, cx);
18945 }
18946 cx.notify();
18947 }
18948
18949 pub fn replace_blocks(
18950 &mut self,
18951 renderers: HashMap<CustomBlockId, RenderBlock>,
18952 autoscroll: Option<Autoscroll>,
18953 cx: &mut Context<Self>,
18954 ) {
18955 self.display_map
18956 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18957 if let Some(autoscroll) = autoscroll {
18958 self.request_autoscroll(autoscroll, cx);
18959 }
18960 cx.notify();
18961 }
18962
18963 pub fn remove_blocks(
18964 &mut self,
18965 block_ids: HashSet<CustomBlockId>,
18966 autoscroll: Option<Autoscroll>,
18967 cx: &mut Context<Self>,
18968 ) {
18969 self.display_map.update(cx, |display_map, cx| {
18970 display_map.remove_blocks(block_ids, cx)
18971 });
18972 if let Some(autoscroll) = autoscroll {
18973 self.request_autoscroll(autoscroll, cx);
18974 }
18975 cx.notify();
18976 }
18977
18978 pub fn row_for_block(
18979 &self,
18980 block_id: CustomBlockId,
18981 cx: &mut Context<Self>,
18982 ) -> Option<DisplayRow> {
18983 self.display_map
18984 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18985 }
18986
18987 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18988 self.focused_block = Some(focused_block);
18989 }
18990
18991 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18992 self.focused_block.take()
18993 }
18994
18995 pub fn insert_creases(
18996 &mut self,
18997 creases: impl IntoIterator<Item = Crease<Anchor>>,
18998 cx: &mut Context<Self>,
18999 ) -> Vec<CreaseId> {
19000 self.display_map
19001 .update(cx, |map, cx| map.insert_creases(creases, cx))
19002 }
19003
19004 pub fn remove_creases(
19005 &mut self,
19006 ids: impl IntoIterator<Item = CreaseId>,
19007 cx: &mut Context<Self>,
19008 ) -> Vec<(CreaseId, Range<Anchor>)> {
19009 self.display_map
19010 .update(cx, |map, cx| map.remove_creases(ids, cx))
19011 }
19012
19013 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19014 self.display_map
19015 .update(cx, |map, cx| map.snapshot(cx))
19016 .longest_row()
19017 }
19018
19019 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19020 self.display_map
19021 .update(cx, |map, cx| map.snapshot(cx))
19022 .max_point()
19023 }
19024
19025 pub fn text(&self, cx: &App) -> String {
19026 self.buffer.read(cx).read(cx).text()
19027 }
19028
19029 pub fn is_empty(&self, cx: &App) -> bool {
19030 self.buffer.read(cx).read(cx).is_empty()
19031 }
19032
19033 pub fn text_option(&self, cx: &App) -> Option<String> {
19034 let text = self.text(cx);
19035 let text = text.trim();
19036
19037 if text.is_empty() {
19038 return None;
19039 }
19040
19041 Some(text.to_string())
19042 }
19043
19044 pub fn set_text(
19045 &mut self,
19046 text: impl Into<Arc<str>>,
19047 window: &mut Window,
19048 cx: &mut Context<Self>,
19049 ) {
19050 self.transact(window, cx, |this, _, cx| {
19051 this.buffer
19052 .read(cx)
19053 .as_singleton()
19054 .expect("you can only call set_text on editors for singleton buffers")
19055 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19056 });
19057 }
19058
19059 pub fn display_text(&self, cx: &mut App) -> String {
19060 self.display_map
19061 .update(cx, |map, cx| map.snapshot(cx))
19062 .text()
19063 }
19064
19065 fn create_minimap(
19066 &self,
19067 minimap_settings: MinimapSettings,
19068 window: &mut Window,
19069 cx: &mut Context<Self>,
19070 ) -> Option<Entity<Self>> {
19071 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19072 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19073 }
19074
19075 fn initialize_new_minimap(
19076 &self,
19077 minimap_settings: MinimapSettings,
19078 window: &mut Window,
19079 cx: &mut Context<Self>,
19080 ) -> Entity<Self> {
19081 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19082
19083 let mut minimap = Editor::new_internal(
19084 EditorMode::Minimap {
19085 parent: cx.weak_entity(),
19086 },
19087 self.buffer.clone(),
19088 None,
19089 Some(self.display_map.clone()),
19090 window,
19091 cx,
19092 );
19093 minimap.scroll_manager.clone_state(&self.scroll_manager);
19094 minimap.set_text_style_refinement(TextStyleRefinement {
19095 font_size: Some(MINIMAP_FONT_SIZE),
19096 font_weight: Some(MINIMAP_FONT_WEIGHT),
19097 ..Default::default()
19098 });
19099 minimap.update_minimap_configuration(minimap_settings, cx);
19100 cx.new(|_| minimap)
19101 }
19102
19103 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19104 let current_line_highlight = minimap_settings
19105 .current_line_highlight
19106 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19107 self.set_current_line_highlight(Some(current_line_highlight));
19108 }
19109
19110 pub fn minimap(&self) -> Option<&Entity<Self>> {
19111 self.minimap
19112 .as_ref()
19113 .filter(|_| self.minimap_visibility.visible())
19114 }
19115
19116 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19117 let mut wrap_guides = smallvec![];
19118
19119 if self.show_wrap_guides == Some(false) {
19120 return wrap_guides;
19121 }
19122
19123 let settings = self.buffer.read(cx).language_settings(cx);
19124 if settings.show_wrap_guides {
19125 match self.soft_wrap_mode(cx) {
19126 SoftWrap::Column(soft_wrap) => {
19127 wrap_guides.push((soft_wrap as usize, true));
19128 }
19129 SoftWrap::Bounded(soft_wrap) => {
19130 wrap_guides.push((soft_wrap as usize, true));
19131 }
19132 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19133 }
19134 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19135 }
19136
19137 wrap_guides
19138 }
19139
19140 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19141 let settings = self.buffer.read(cx).language_settings(cx);
19142 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19143 match mode {
19144 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19145 SoftWrap::None
19146 }
19147 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19148 language_settings::SoftWrap::PreferredLineLength => {
19149 SoftWrap::Column(settings.preferred_line_length)
19150 }
19151 language_settings::SoftWrap::Bounded => {
19152 SoftWrap::Bounded(settings.preferred_line_length)
19153 }
19154 }
19155 }
19156
19157 pub fn set_soft_wrap_mode(
19158 &mut self,
19159 mode: language_settings::SoftWrap,
19160
19161 cx: &mut Context<Self>,
19162 ) {
19163 self.soft_wrap_mode_override = Some(mode);
19164 cx.notify();
19165 }
19166
19167 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19168 self.hard_wrap = hard_wrap;
19169 cx.notify();
19170 }
19171
19172 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19173 self.text_style_refinement = Some(style);
19174 }
19175
19176 /// called by the Element so we know what style we were most recently rendered with.
19177 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19178 // We intentionally do not inform the display map about the minimap style
19179 // so that wrapping is not recalculated and stays consistent for the editor
19180 // and its linked minimap.
19181 if !self.mode.is_minimap() {
19182 let font = style.text.font();
19183 let font_size = style.text.font_size.to_pixels(window.rem_size());
19184 let display_map = self
19185 .placeholder_display_map
19186 .as_ref()
19187 .filter(|_| self.is_empty(cx))
19188 .unwrap_or(&self.display_map);
19189
19190 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19191 }
19192 self.style = Some(style);
19193 }
19194
19195 pub fn style(&self) -> Option<&EditorStyle> {
19196 self.style.as_ref()
19197 }
19198
19199 // Called by the element. This method is not designed to be called outside of the editor
19200 // element's layout code because it does not notify when rewrapping is computed synchronously.
19201 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19202 if self.is_empty(cx) {
19203 self.placeholder_display_map
19204 .as_ref()
19205 .map_or(false, |display_map| {
19206 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19207 })
19208 } else {
19209 self.display_map
19210 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19211 }
19212 }
19213
19214 pub fn set_soft_wrap(&mut self) {
19215 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19216 }
19217
19218 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19219 if self.soft_wrap_mode_override.is_some() {
19220 self.soft_wrap_mode_override.take();
19221 } else {
19222 let soft_wrap = match self.soft_wrap_mode(cx) {
19223 SoftWrap::GitDiff => return,
19224 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19225 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19226 language_settings::SoftWrap::None
19227 }
19228 };
19229 self.soft_wrap_mode_override = Some(soft_wrap);
19230 }
19231 cx.notify();
19232 }
19233
19234 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19235 let Some(workspace) = self.workspace() else {
19236 return;
19237 };
19238 let fs = workspace.read(cx).app_state().fs.clone();
19239 let current_show = TabBarSettings::get_global(cx).show;
19240 update_settings_file(fs, cx, move |setting, _| {
19241 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19242 });
19243 }
19244
19245 pub fn toggle_indent_guides(
19246 &mut self,
19247 _: &ToggleIndentGuides,
19248 _: &mut Window,
19249 cx: &mut Context<Self>,
19250 ) {
19251 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19252 self.buffer
19253 .read(cx)
19254 .language_settings(cx)
19255 .indent_guides
19256 .enabled
19257 });
19258 self.show_indent_guides = Some(!currently_enabled);
19259 cx.notify();
19260 }
19261
19262 fn should_show_indent_guides(&self) -> Option<bool> {
19263 self.show_indent_guides
19264 }
19265
19266 pub fn toggle_line_numbers(
19267 &mut self,
19268 _: &ToggleLineNumbers,
19269 _: &mut Window,
19270 cx: &mut Context<Self>,
19271 ) {
19272 let mut editor_settings = EditorSettings::get_global(cx).clone();
19273 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19274 EditorSettings::override_global(editor_settings, cx);
19275 }
19276
19277 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19278 if let Some(show_line_numbers) = self.show_line_numbers {
19279 return show_line_numbers;
19280 }
19281 EditorSettings::get_global(cx).gutter.line_numbers
19282 }
19283
19284 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19285 self.use_relative_line_numbers
19286 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19287 }
19288
19289 pub fn toggle_relative_line_numbers(
19290 &mut self,
19291 _: &ToggleRelativeLineNumbers,
19292 _: &mut Window,
19293 cx: &mut Context<Self>,
19294 ) {
19295 let is_relative = self.should_use_relative_line_numbers(cx);
19296 self.set_relative_line_number(Some(!is_relative), cx)
19297 }
19298
19299 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19300 self.use_relative_line_numbers = is_relative;
19301 cx.notify();
19302 }
19303
19304 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19305 self.show_gutter = show_gutter;
19306 cx.notify();
19307 }
19308
19309 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19310 self.show_scrollbars = ScrollbarAxes {
19311 horizontal: show,
19312 vertical: show,
19313 };
19314 cx.notify();
19315 }
19316
19317 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19318 self.show_scrollbars.vertical = show;
19319 cx.notify();
19320 }
19321
19322 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19323 self.show_scrollbars.horizontal = show;
19324 cx.notify();
19325 }
19326
19327 pub fn set_minimap_visibility(
19328 &mut self,
19329 minimap_visibility: MinimapVisibility,
19330 window: &mut Window,
19331 cx: &mut Context<Self>,
19332 ) {
19333 if self.minimap_visibility != minimap_visibility {
19334 if minimap_visibility.visible() && self.minimap.is_none() {
19335 let minimap_settings = EditorSettings::get_global(cx).minimap;
19336 self.minimap =
19337 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19338 }
19339 self.minimap_visibility = minimap_visibility;
19340 cx.notify();
19341 }
19342 }
19343
19344 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19345 self.set_show_scrollbars(false, cx);
19346 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19347 }
19348
19349 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19350 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19351 }
19352
19353 /// Normally the text in full mode and auto height editors is padded on the
19354 /// left side by roughly half a character width for improved hit testing.
19355 ///
19356 /// Use this method to disable this for cases where this is not wanted (e.g.
19357 /// if you want to align the editor text with some other text above or below)
19358 /// or if you want to add this padding to single-line editors.
19359 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19360 self.offset_content = offset_content;
19361 cx.notify();
19362 }
19363
19364 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19365 self.show_line_numbers = Some(show_line_numbers);
19366 cx.notify();
19367 }
19368
19369 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19370 self.disable_expand_excerpt_buttons = true;
19371 cx.notify();
19372 }
19373
19374 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19375 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19376 cx.notify();
19377 }
19378
19379 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19380 self.show_code_actions = Some(show_code_actions);
19381 cx.notify();
19382 }
19383
19384 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19385 self.show_runnables = Some(show_runnables);
19386 cx.notify();
19387 }
19388
19389 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19390 self.show_breakpoints = Some(show_breakpoints);
19391 cx.notify();
19392 }
19393
19394 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19395 if self.display_map.read(cx).masked != masked {
19396 self.display_map.update(cx, |map, _| map.masked = masked);
19397 }
19398 cx.notify()
19399 }
19400
19401 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19402 self.show_wrap_guides = Some(show_wrap_guides);
19403 cx.notify();
19404 }
19405
19406 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19407 self.show_indent_guides = Some(show_indent_guides);
19408 cx.notify();
19409 }
19410
19411 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19412 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19413 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19414 && let Some(dir) = file.abs_path(cx).parent()
19415 {
19416 return Some(dir.to_owned());
19417 }
19418 }
19419
19420 None
19421 }
19422
19423 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19424 self.active_excerpt(cx)?
19425 .1
19426 .read(cx)
19427 .file()
19428 .and_then(|f| f.as_local())
19429 }
19430
19431 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19432 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19433 let buffer = buffer.read(cx);
19434 if let Some(project_path) = buffer.project_path(cx) {
19435 let project = self.project()?.read(cx);
19436 project.absolute_path(&project_path, cx)
19437 } else {
19438 buffer
19439 .file()
19440 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19441 }
19442 })
19443 }
19444
19445 pub fn reveal_in_finder(
19446 &mut self,
19447 _: &RevealInFileManager,
19448 _window: &mut Window,
19449 cx: &mut Context<Self>,
19450 ) {
19451 if let Some(target) = self.target_file(cx) {
19452 cx.reveal_path(&target.abs_path(cx));
19453 }
19454 }
19455
19456 pub fn copy_path(
19457 &mut self,
19458 _: &zed_actions::workspace::CopyPath,
19459 _window: &mut Window,
19460 cx: &mut Context<Self>,
19461 ) {
19462 if let Some(path) = self.target_file_abs_path(cx)
19463 && let Some(path) = path.to_str()
19464 {
19465 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19466 } else {
19467 cx.propagate();
19468 }
19469 }
19470
19471 pub fn copy_relative_path(
19472 &mut self,
19473 _: &zed_actions::workspace::CopyRelativePath,
19474 _window: &mut Window,
19475 cx: &mut Context<Self>,
19476 ) {
19477 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19478 let project = self.project()?.read(cx);
19479 let path = buffer.read(cx).file()?.path();
19480 let path = path.display(project.path_style(cx));
19481 Some(path)
19482 }) {
19483 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19484 } else {
19485 cx.propagate();
19486 }
19487 }
19488
19489 /// Returns the project path for the editor's buffer, if any buffer is
19490 /// opened in the editor.
19491 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19492 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19493 buffer.read(cx).project_path(cx)
19494 } else {
19495 None
19496 }
19497 }
19498
19499 // Returns true if the editor handled a go-to-line request
19500 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19501 maybe!({
19502 let breakpoint_store = self.breakpoint_store.as_ref()?;
19503
19504 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19505 else {
19506 self.clear_row_highlights::<ActiveDebugLine>();
19507 return None;
19508 };
19509
19510 let position = active_stack_frame.position;
19511 let buffer_id = position.buffer_id?;
19512 let snapshot = self
19513 .project
19514 .as_ref()?
19515 .read(cx)
19516 .buffer_for_id(buffer_id, cx)?
19517 .read(cx)
19518 .snapshot();
19519
19520 let mut handled = false;
19521 for (id, ExcerptRange { context, .. }) in
19522 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19523 {
19524 if context.start.cmp(&position, &snapshot).is_ge()
19525 || context.end.cmp(&position, &snapshot).is_lt()
19526 {
19527 continue;
19528 }
19529 let snapshot = self.buffer.read(cx).snapshot(cx);
19530 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19531
19532 handled = true;
19533 self.clear_row_highlights::<ActiveDebugLine>();
19534
19535 self.go_to_line::<ActiveDebugLine>(
19536 multibuffer_anchor,
19537 Some(cx.theme().colors().editor_debugger_active_line_background),
19538 window,
19539 cx,
19540 );
19541
19542 cx.notify();
19543 }
19544
19545 handled.then_some(())
19546 })
19547 .is_some()
19548 }
19549
19550 pub fn copy_file_name_without_extension(
19551 &mut self,
19552 _: &CopyFileNameWithoutExtension,
19553 _: &mut Window,
19554 cx: &mut Context<Self>,
19555 ) {
19556 if let Some(file) = self.target_file(cx)
19557 && let Some(file_stem) = file.path().file_stem()
19558 {
19559 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19560 }
19561 }
19562
19563 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19564 if let Some(file) = self.target_file(cx)
19565 && let Some(name) = file.path().file_name()
19566 {
19567 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19568 }
19569 }
19570
19571 pub fn toggle_git_blame(
19572 &mut self,
19573 _: &::git::Blame,
19574 window: &mut Window,
19575 cx: &mut Context<Self>,
19576 ) {
19577 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19578
19579 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19580 self.start_git_blame(true, window, cx);
19581 }
19582
19583 cx.notify();
19584 }
19585
19586 pub fn toggle_git_blame_inline(
19587 &mut self,
19588 _: &ToggleGitBlameInline,
19589 window: &mut Window,
19590 cx: &mut Context<Self>,
19591 ) {
19592 self.toggle_git_blame_inline_internal(true, window, cx);
19593 cx.notify();
19594 }
19595
19596 pub fn open_git_blame_commit(
19597 &mut self,
19598 _: &OpenGitBlameCommit,
19599 window: &mut Window,
19600 cx: &mut Context<Self>,
19601 ) {
19602 self.open_git_blame_commit_internal(window, cx);
19603 }
19604
19605 fn open_git_blame_commit_internal(
19606 &mut self,
19607 window: &mut Window,
19608 cx: &mut Context<Self>,
19609 ) -> Option<()> {
19610 let blame = self.blame.as_ref()?;
19611 let snapshot = self.snapshot(window, cx);
19612 let cursor = self.selections.newest::<Point>(cx).head();
19613 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19614 let (_, blame_entry) = blame
19615 .update(cx, |blame, cx| {
19616 blame
19617 .blame_for_rows(
19618 &[RowInfo {
19619 buffer_id: Some(buffer.remote_id()),
19620 buffer_row: Some(point.row),
19621 ..Default::default()
19622 }],
19623 cx,
19624 )
19625 .next()
19626 })
19627 .flatten()?;
19628 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19629 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19630 let workspace = self.workspace()?.downgrade();
19631 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19632 None
19633 }
19634
19635 pub fn git_blame_inline_enabled(&self) -> bool {
19636 self.git_blame_inline_enabled
19637 }
19638
19639 pub fn toggle_selection_menu(
19640 &mut self,
19641 _: &ToggleSelectionMenu,
19642 _: &mut Window,
19643 cx: &mut Context<Self>,
19644 ) {
19645 self.show_selection_menu = self
19646 .show_selection_menu
19647 .map(|show_selections_menu| !show_selections_menu)
19648 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19649
19650 cx.notify();
19651 }
19652
19653 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19654 self.show_selection_menu
19655 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19656 }
19657
19658 fn start_git_blame(
19659 &mut self,
19660 user_triggered: bool,
19661 window: &mut Window,
19662 cx: &mut Context<Self>,
19663 ) {
19664 if let Some(project) = self.project() {
19665 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19666 && buffer.read(cx).file().is_none()
19667 {
19668 return;
19669 }
19670
19671 let focused = self.focus_handle(cx).contains_focused(window, cx);
19672
19673 let project = project.clone();
19674 let blame = cx
19675 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19676 self.blame_subscription =
19677 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19678 self.blame = Some(blame);
19679 }
19680 }
19681
19682 fn toggle_git_blame_inline_internal(
19683 &mut self,
19684 user_triggered: bool,
19685 window: &mut Window,
19686 cx: &mut Context<Self>,
19687 ) {
19688 if self.git_blame_inline_enabled {
19689 self.git_blame_inline_enabled = false;
19690 self.show_git_blame_inline = false;
19691 self.show_git_blame_inline_delay_task.take();
19692 } else {
19693 self.git_blame_inline_enabled = true;
19694 self.start_git_blame_inline(user_triggered, window, cx);
19695 }
19696
19697 cx.notify();
19698 }
19699
19700 fn start_git_blame_inline(
19701 &mut self,
19702 user_triggered: bool,
19703 window: &mut Window,
19704 cx: &mut Context<Self>,
19705 ) {
19706 self.start_git_blame(user_triggered, window, cx);
19707
19708 if ProjectSettings::get_global(cx)
19709 .git
19710 .inline_blame_delay()
19711 .is_some()
19712 {
19713 self.start_inline_blame_timer(window, cx);
19714 } else {
19715 self.show_git_blame_inline = true
19716 }
19717 }
19718
19719 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19720 self.blame.as_ref()
19721 }
19722
19723 pub fn show_git_blame_gutter(&self) -> bool {
19724 self.show_git_blame_gutter
19725 }
19726
19727 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19728 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19729 }
19730
19731 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19732 self.show_git_blame_inline
19733 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19734 && !self.newest_selection_head_on_empty_line(cx)
19735 && self.has_blame_entries(cx)
19736 }
19737
19738 fn has_blame_entries(&self, cx: &App) -> bool {
19739 self.blame()
19740 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19741 }
19742
19743 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19744 let cursor_anchor = self.selections.newest_anchor().head();
19745
19746 let snapshot = self.buffer.read(cx).snapshot(cx);
19747 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19748
19749 snapshot.line_len(buffer_row) == 0
19750 }
19751
19752 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19753 let buffer_and_selection = maybe!({
19754 let selection = self.selections.newest::<Point>(cx);
19755 let selection_range = selection.range();
19756
19757 let multi_buffer = self.buffer().read(cx);
19758 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19759 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19760
19761 let (buffer, range, _) = if selection.reversed {
19762 buffer_ranges.first()
19763 } else {
19764 buffer_ranges.last()
19765 }?;
19766
19767 let selection = text::ToPoint::to_point(&range.start, buffer).row
19768 ..text::ToPoint::to_point(&range.end, buffer).row;
19769 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19770 });
19771
19772 let Some((buffer, selection)) = buffer_and_selection else {
19773 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19774 };
19775
19776 let Some(project) = self.project() else {
19777 return Task::ready(Err(anyhow!("editor does not have project")));
19778 };
19779
19780 project.update(cx, |project, cx| {
19781 project.get_permalink_to_line(&buffer, selection, cx)
19782 })
19783 }
19784
19785 pub fn copy_permalink_to_line(
19786 &mut self,
19787 _: &CopyPermalinkToLine,
19788 window: &mut Window,
19789 cx: &mut Context<Self>,
19790 ) {
19791 let permalink_task = self.get_permalink_to_line(cx);
19792 let workspace = self.workspace();
19793
19794 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19795 Ok(permalink) => {
19796 cx.update(|_, cx| {
19797 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19798 })
19799 .ok();
19800 }
19801 Err(err) => {
19802 let message = format!("Failed to copy permalink: {err}");
19803
19804 anyhow::Result::<()>::Err(err).log_err();
19805
19806 if let Some(workspace) = workspace {
19807 workspace
19808 .update_in(cx, |workspace, _, cx| {
19809 struct CopyPermalinkToLine;
19810
19811 workspace.show_toast(
19812 Toast::new(
19813 NotificationId::unique::<CopyPermalinkToLine>(),
19814 message,
19815 ),
19816 cx,
19817 )
19818 })
19819 .ok();
19820 }
19821 }
19822 })
19823 .detach();
19824 }
19825
19826 pub fn copy_file_location(
19827 &mut self,
19828 _: &CopyFileLocation,
19829 _: &mut Window,
19830 cx: &mut Context<Self>,
19831 ) {
19832 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19833 if let Some(file) = self.target_file(cx) {
19834 let path = file.path().display(file.path_style(cx));
19835 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19836 }
19837 }
19838
19839 pub fn open_permalink_to_line(
19840 &mut self,
19841 _: &OpenPermalinkToLine,
19842 window: &mut Window,
19843 cx: &mut Context<Self>,
19844 ) {
19845 let permalink_task = self.get_permalink_to_line(cx);
19846 let workspace = self.workspace();
19847
19848 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19849 Ok(permalink) => {
19850 cx.update(|_, cx| {
19851 cx.open_url(permalink.as_ref());
19852 })
19853 .ok();
19854 }
19855 Err(err) => {
19856 let message = format!("Failed to open permalink: {err}");
19857
19858 anyhow::Result::<()>::Err(err).log_err();
19859
19860 if let Some(workspace) = workspace {
19861 workspace
19862 .update(cx, |workspace, cx| {
19863 struct OpenPermalinkToLine;
19864
19865 workspace.show_toast(
19866 Toast::new(
19867 NotificationId::unique::<OpenPermalinkToLine>(),
19868 message,
19869 ),
19870 cx,
19871 )
19872 })
19873 .ok();
19874 }
19875 }
19876 })
19877 .detach();
19878 }
19879
19880 pub fn insert_uuid_v4(
19881 &mut self,
19882 _: &InsertUuidV4,
19883 window: &mut Window,
19884 cx: &mut Context<Self>,
19885 ) {
19886 self.insert_uuid(UuidVersion::V4, window, cx);
19887 }
19888
19889 pub fn insert_uuid_v7(
19890 &mut self,
19891 _: &InsertUuidV7,
19892 window: &mut Window,
19893 cx: &mut Context<Self>,
19894 ) {
19895 self.insert_uuid(UuidVersion::V7, window, cx);
19896 }
19897
19898 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19900 self.transact(window, cx, |this, window, cx| {
19901 let edits = this
19902 .selections
19903 .all::<Point>(cx)
19904 .into_iter()
19905 .map(|selection| {
19906 let uuid = match version {
19907 UuidVersion::V4 => uuid::Uuid::new_v4(),
19908 UuidVersion::V7 => uuid::Uuid::now_v7(),
19909 };
19910
19911 (selection.range(), uuid.to_string())
19912 });
19913 this.edit(edits, cx);
19914 this.refresh_edit_prediction(true, false, window, cx);
19915 });
19916 }
19917
19918 pub fn open_selections_in_multibuffer(
19919 &mut self,
19920 _: &OpenSelectionsInMultibuffer,
19921 window: &mut Window,
19922 cx: &mut Context<Self>,
19923 ) {
19924 let multibuffer = self.buffer.read(cx);
19925
19926 let Some(buffer) = multibuffer.as_singleton() else {
19927 return;
19928 };
19929
19930 let Some(workspace) = self.workspace() else {
19931 return;
19932 };
19933
19934 let title = multibuffer.title(cx).to_string();
19935
19936 let locations = self
19937 .selections
19938 .all_anchors(cx)
19939 .iter()
19940 .map(|selection| {
19941 (
19942 buffer.clone(),
19943 (selection.start.text_anchor..selection.end.text_anchor)
19944 .to_point(buffer.read(cx)),
19945 )
19946 })
19947 .into_group_map();
19948
19949 cx.spawn_in(window, async move |_, cx| {
19950 workspace.update_in(cx, |workspace, window, cx| {
19951 Self::open_locations_in_multibuffer(
19952 workspace,
19953 locations,
19954 format!("Selections for '{title}'"),
19955 false,
19956 MultibufferSelectionMode::All,
19957 window,
19958 cx,
19959 );
19960 })
19961 })
19962 .detach();
19963 }
19964
19965 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19966 /// last highlight added will be used.
19967 ///
19968 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19969 pub fn highlight_rows<T: 'static>(
19970 &mut self,
19971 range: Range<Anchor>,
19972 color: Hsla,
19973 options: RowHighlightOptions,
19974 cx: &mut Context<Self>,
19975 ) {
19976 let snapshot = self.buffer().read(cx).snapshot(cx);
19977 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19978 let ix = row_highlights.binary_search_by(|highlight| {
19979 Ordering::Equal
19980 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19981 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19982 });
19983
19984 if let Err(mut ix) = ix {
19985 let index = post_inc(&mut self.highlight_order);
19986
19987 // If this range intersects with the preceding highlight, then merge it with
19988 // the preceding highlight. Otherwise insert a new highlight.
19989 let mut merged = false;
19990 if ix > 0 {
19991 let prev_highlight = &mut row_highlights[ix - 1];
19992 if prev_highlight
19993 .range
19994 .end
19995 .cmp(&range.start, &snapshot)
19996 .is_ge()
19997 {
19998 ix -= 1;
19999 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20000 prev_highlight.range.end = range.end;
20001 }
20002 merged = true;
20003 prev_highlight.index = index;
20004 prev_highlight.color = color;
20005 prev_highlight.options = options;
20006 }
20007 }
20008
20009 if !merged {
20010 row_highlights.insert(
20011 ix,
20012 RowHighlight {
20013 range,
20014 index,
20015 color,
20016 options,
20017 type_id: TypeId::of::<T>(),
20018 },
20019 );
20020 }
20021
20022 // If any of the following highlights intersect with this one, merge them.
20023 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20024 let highlight = &row_highlights[ix];
20025 if next_highlight
20026 .range
20027 .start
20028 .cmp(&highlight.range.end, &snapshot)
20029 .is_le()
20030 {
20031 if next_highlight
20032 .range
20033 .end
20034 .cmp(&highlight.range.end, &snapshot)
20035 .is_gt()
20036 {
20037 row_highlights[ix].range.end = next_highlight.range.end;
20038 }
20039 row_highlights.remove(ix + 1);
20040 } else {
20041 break;
20042 }
20043 }
20044 }
20045 }
20046
20047 /// Remove any highlighted row ranges of the given type that intersect the
20048 /// given ranges.
20049 pub fn remove_highlighted_rows<T: 'static>(
20050 &mut self,
20051 ranges_to_remove: Vec<Range<Anchor>>,
20052 cx: &mut Context<Self>,
20053 ) {
20054 let snapshot = self.buffer().read(cx).snapshot(cx);
20055 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20056 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20057 row_highlights.retain(|highlight| {
20058 while let Some(range_to_remove) = ranges_to_remove.peek() {
20059 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20060 Ordering::Less | Ordering::Equal => {
20061 ranges_to_remove.next();
20062 }
20063 Ordering::Greater => {
20064 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20065 Ordering::Less | Ordering::Equal => {
20066 return false;
20067 }
20068 Ordering::Greater => break,
20069 }
20070 }
20071 }
20072 }
20073
20074 true
20075 })
20076 }
20077
20078 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20079 pub fn clear_row_highlights<T: 'static>(&mut self) {
20080 self.highlighted_rows.remove(&TypeId::of::<T>());
20081 }
20082
20083 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20084 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20085 self.highlighted_rows
20086 .get(&TypeId::of::<T>())
20087 .map_or(&[] as &[_], |vec| vec.as_slice())
20088 .iter()
20089 .map(|highlight| (highlight.range.clone(), highlight.color))
20090 }
20091
20092 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20093 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20094 /// Allows to ignore certain kinds of highlights.
20095 pub fn highlighted_display_rows(
20096 &self,
20097 window: &mut Window,
20098 cx: &mut App,
20099 ) -> BTreeMap<DisplayRow, LineHighlight> {
20100 let snapshot = self.snapshot(window, cx);
20101 let mut used_highlight_orders = HashMap::default();
20102 self.highlighted_rows
20103 .iter()
20104 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20105 .fold(
20106 BTreeMap::<DisplayRow, LineHighlight>::new(),
20107 |mut unique_rows, highlight| {
20108 let start = highlight.range.start.to_display_point(&snapshot);
20109 let end = highlight.range.end.to_display_point(&snapshot);
20110 let start_row = start.row().0;
20111 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20112 && end.column() == 0
20113 {
20114 end.row().0.saturating_sub(1)
20115 } else {
20116 end.row().0
20117 };
20118 for row in start_row..=end_row {
20119 let used_index =
20120 used_highlight_orders.entry(row).or_insert(highlight.index);
20121 if highlight.index >= *used_index {
20122 *used_index = highlight.index;
20123 unique_rows.insert(
20124 DisplayRow(row),
20125 LineHighlight {
20126 include_gutter: highlight.options.include_gutter,
20127 border: None,
20128 background: highlight.color.into(),
20129 type_id: Some(highlight.type_id),
20130 },
20131 );
20132 }
20133 }
20134 unique_rows
20135 },
20136 )
20137 }
20138
20139 pub fn highlighted_display_row_for_autoscroll(
20140 &self,
20141 snapshot: &DisplaySnapshot,
20142 ) -> Option<DisplayRow> {
20143 self.highlighted_rows
20144 .values()
20145 .flat_map(|highlighted_rows| highlighted_rows.iter())
20146 .filter_map(|highlight| {
20147 if highlight.options.autoscroll {
20148 Some(highlight.range.start.to_display_point(snapshot).row())
20149 } else {
20150 None
20151 }
20152 })
20153 .min()
20154 }
20155
20156 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20157 self.highlight_background::<SearchWithinRange>(
20158 ranges,
20159 |colors| colors.colors().editor_document_highlight_read_background,
20160 cx,
20161 )
20162 }
20163
20164 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20165 self.breadcrumb_header = Some(new_header);
20166 }
20167
20168 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20169 self.clear_background_highlights::<SearchWithinRange>(cx);
20170 }
20171
20172 pub fn highlight_background<T: 'static>(
20173 &mut self,
20174 ranges: &[Range<Anchor>],
20175 color_fetcher: fn(&Theme) -> Hsla,
20176 cx: &mut Context<Self>,
20177 ) {
20178 self.background_highlights.insert(
20179 HighlightKey::Type(TypeId::of::<T>()),
20180 (color_fetcher, Arc::from(ranges)),
20181 );
20182 self.scrollbar_marker_state.dirty = true;
20183 cx.notify();
20184 }
20185
20186 pub fn highlight_background_key<T: 'static>(
20187 &mut self,
20188 key: usize,
20189 ranges: &[Range<Anchor>],
20190 color_fetcher: fn(&Theme) -> Hsla,
20191 cx: &mut Context<Self>,
20192 ) {
20193 self.background_highlights.insert(
20194 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20195 (color_fetcher, Arc::from(ranges)),
20196 );
20197 self.scrollbar_marker_state.dirty = true;
20198 cx.notify();
20199 }
20200
20201 pub fn clear_background_highlights<T: 'static>(
20202 &mut self,
20203 cx: &mut Context<Self>,
20204 ) -> Option<BackgroundHighlight> {
20205 let text_highlights = self
20206 .background_highlights
20207 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20208 if !text_highlights.1.is_empty() {
20209 self.scrollbar_marker_state.dirty = true;
20210 cx.notify();
20211 }
20212 Some(text_highlights)
20213 }
20214
20215 pub fn highlight_gutter<T: 'static>(
20216 &mut self,
20217 ranges: impl Into<Vec<Range<Anchor>>>,
20218 color_fetcher: fn(&App) -> Hsla,
20219 cx: &mut Context<Self>,
20220 ) {
20221 self.gutter_highlights
20222 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20223 cx.notify();
20224 }
20225
20226 pub fn clear_gutter_highlights<T: 'static>(
20227 &mut self,
20228 cx: &mut Context<Self>,
20229 ) -> Option<GutterHighlight> {
20230 cx.notify();
20231 self.gutter_highlights.remove(&TypeId::of::<T>())
20232 }
20233
20234 pub fn insert_gutter_highlight<T: 'static>(
20235 &mut self,
20236 range: Range<Anchor>,
20237 color_fetcher: fn(&App) -> Hsla,
20238 cx: &mut Context<Self>,
20239 ) {
20240 let snapshot = self.buffer().read(cx).snapshot(cx);
20241 let mut highlights = self
20242 .gutter_highlights
20243 .remove(&TypeId::of::<T>())
20244 .map(|(_, highlights)| highlights)
20245 .unwrap_or_default();
20246 let ix = highlights.binary_search_by(|highlight| {
20247 Ordering::Equal
20248 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20249 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20250 });
20251 if let Err(ix) = ix {
20252 highlights.insert(ix, range);
20253 }
20254 self.gutter_highlights
20255 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20256 }
20257
20258 pub fn remove_gutter_highlights<T: 'static>(
20259 &mut self,
20260 ranges_to_remove: Vec<Range<Anchor>>,
20261 cx: &mut Context<Self>,
20262 ) {
20263 let snapshot = self.buffer().read(cx).snapshot(cx);
20264 let Some((color_fetcher, mut gutter_highlights)) =
20265 self.gutter_highlights.remove(&TypeId::of::<T>())
20266 else {
20267 return;
20268 };
20269 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20270 gutter_highlights.retain(|highlight| {
20271 while let Some(range_to_remove) = ranges_to_remove.peek() {
20272 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20273 Ordering::Less | Ordering::Equal => {
20274 ranges_to_remove.next();
20275 }
20276 Ordering::Greater => {
20277 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20278 Ordering::Less | Ordering::Equal => {
20279 return false;
20280 }
20281 Ordering::Greater => break,
20282 }
20283 }
20284 }
20285 }
20286
20287 true
20288 });
20289 self.gutter_highlights
20290 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20291 }
20292
20293 #[cfg(feature = "test-support")]
20294 pub fn all_text_highlights(
20295 &self,
20296 window: &mut Window,
20297 cx: &mut Context<Self>,
20298 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20299 let snapshot = self.snapshot(window, cx);
20300 self.display_map.update(cx, |display_map, _| {
20301 display_map
20302 .all_text_highlights()
20303 .map(|highlight| {
20304 let (style, ranges) = highlight.as_ref();
20305 (
20306 *style,
20307 ranges
20308 .iter()
20309 .map(|range| range.clone().to_display_points(&snapshot))
20310 .collect(),
20311 )
20312 })
20313 .collect()
20314 })
20315 }
20316
20317 #[cfg(feature = "test-support")]
20318 pub fn all_text_background_highlights(
20319 &self,
20320 window: &mut Window,
20321 cx: &mut Context<Self>,
20322 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20323 let snapshot = self.snapshot(window, cx);
20324 let buffer = &snapshot.buffer_snapshot();
20325 let start = buffer.anchor_before(0);
20326 let end = buffer.anchor_after(buffer.len());
20327 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20328 }
20329
20330 #[cfg(any(test, feature = "test-support"))]
20331 pub fn sorted_background_highlights_in_range(
20332 &self,
20333 search_range: Range<Anchor>,
20334 display_snapshot: &DisplaySnapshot,
20335 theme: &Theme,
20336 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20337 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20338 res.sort_by(|a, b| {
20339 a.0.start
20340 .cmp(&b.0.start)
20341 .then_with(|| a.0.end.cmp(&b.0.end))
20342 .then_with(|| a.1.cmp(&b.1))
20343 });
20344 res
20345 }
20346
20347 #[cfg(feature = "test-support")]
20348 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20349 let snapshot = self.buffer().read(cx).snapshot(cx);
20350
20351 let highlights = self
20352 .background_highlights
20353 .get(&HighlightKey::Type(TypeId::of::<
20354 items::BufferSearchHighlights,
20355 >()));
20356
20357 if let Some((_color, ranges)) = highlights {
20358 ranges
20359 .iter()
20360 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20361 .collect_vec()
20362 } else {
20363 vec![]
20364 }
20365 }
20366
20367 fn document_highlights_for_position<'a>(
20368 &'a self,
20369 position: Anchor,
20370 buffer: &'a MultiBufferSnapshot,
20371 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20372 let read_highlights = self
20373 .background_highlights
20374 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20375 .map(|h| &h.1);
20376 let write_highlights = self
20377 .background_highlights
20378 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20379 .map(|h| &h.1);
20380 let left_position = position.bias_left(buffer);
20381 let right_position = position.bias_right(buffer);
20382 read_highlights
20383 .into_iter()
20384 .chain(write_highlights)
20385 .flat_map(move |ranges| {
20386 let start_ix = match ranges.binary_search_by(|probe| {
20387 let cmp = probe.end.cmp(&left_position, buffer);
20388 if cmp.is_ge() {
20389 Ordering::Greater
20390 } else {
20391 Ordering::Less
20392 }
20393 }) {
20394 Ok(i) | Err(i) => i,
20395 };
20396
20397 ranges[start_ix..]
20398 .iter()
20399 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20400 })
20401 }
20402
20403 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20404 self.background_highlights
20405 .get(&HighlightKey::Type(TypeId::of::<T>()))
20406 .is_some_and(|(_, highlights)| !highlights.is_empty())
20407 }
20408
20409 /// Returns all background highlights for a given range.
20410 ///
20411 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20412 pub fn background_highlights_in_range(
20413 &self,
20414 search_range: Range<Anchor>,
20415 display_snapshot: &DisplaySnapshot,
20416 theme: &Theme,
20417 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20418 let mut results = Vec::new();
20419 for (color_fetcher, ranges) in self.background_highlights.values() {
20420 let color = color_fetcher(theme);
20421 let start_ix = match ranges.binary_search_by(|probe| {
20422 let cmp = probe
20423 .end
20424 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20425 if cmp.is_gt() {
20426 Ordering::Greater
20427 } else {
20428 Ordering::Less
20429 }
20430 }) {
20431 Ok(i) | Err(i) => i,
20432 };
20433 for range in &ranges[start_ix..] {
20434 if range
20435 .start
20436 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20437 .is_ge()
20438 {
20439 break;
20440 }
20441
20442 let start = range.start.to_display_point(display_snapshot);
20443 let end = range.end.to_display_point(display_snapshot);
20444 results.push((start..end, color))
20445 }
20446 }
20447 results
20448 }
20449
20450 pub fn gutter_highlights_in_range(
20451 &self,
20452 search_range: Range<Anchor>,
20453 display_snapshot: &DisplaySnapshot,
20454 cx: &App,
20455 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20456 let mut results = Vec::new();
20457 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20458 let color = color_fetcher(cx);
20459 let start_ix = match ranges.binary_search_by(|probe| {
20460 let cmp = probe
20461 .end
20462 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20463 if cmp.is_gt() {
20464 Ordering::Greater
20465 } else {
20466 Ordering::Less
20467 }
20468 }) {
20469 Ok(i) | Err(i) => i,
20470 };
20471 for range in &ranges[start_ix..] {
20472 if range
20473 .start
20474 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20475 .is_ge()
20476 {
20477 break;
20478 }
20479
20480 let start = range.start.to_display_point(display_snapshot);
20481 let end = range.end.to_display_point(display_snapshot);
20482 results.push((start..end, color))
20483 }
20484 }
20485 results
20486 }
20487
20488 /// Get the text ranges corresponding to the redaction query
20489 pub fn redacted_ranges(
20490 &self,
20491 search_range: Range<Anchor>,
20492 display_snapshot: &DisplaySnapshot,
20493 cx: &App,
20494 ) -> Vec<Range<DisplayPoint>> {
20495 display_snapshot
20496 .buffer_snapshot()
20497 .redacted_ranges(search_range, |file| {
20498 if let Some(file) = file {
20499 file.is_private()
20500 && EditorSettings::get(
20501 Some(SettingsLocation {
20502 worktree_id: file.worktree_id(cx),
20503 path: file.path().as_ref(),
20504 }),
20505 cx,
20506 )
20507 .redact_private_values
20508 } else {
20509 false
20510 }
20511 })
20512 .map(|range| {
20513 range.start.to_display_point(display_snapshot)
20514 ..range.end.to_display_point(display_snapshot)
20515 })
20516 .collect()
20517 }
20518
20519 pub fn highlight_text_key<T: 'static>(
20520 &mut self,
20521 key: usize,
20522 ranges: Vec<Range<Anchor>>,
20523 style: HighlightStyle,
20524 cx: &mut Context<Self>,
20525 ) {
20526 self.display_map.update(cx, |map, _| {
20527 map.highlight_text(
20528 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20529 ranges,
20530 style,
20531 );
20532 });
20533 cx.notify();
20534 }
20535
20536 pub fn highlight_text<T: 'static>(
20537 &mut self,
20538 ranges: Vec<Range<Anchor>>,
20539 style: HighlightStyle,
20540 cx: &mut Context<Self>,
20541 ) {
20542 self.display_map.update(cx, |map, _| {
20543 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20544 });
20545 cx.notify();
20546 }
20547
20548 pub(crate) fn highlight_inlays<T: 'static>(
20549 &mut self,
20550 highlights: Vec<InlayHighlight>,
20551 style: HighlightStyle,
20552 cx: &mut Context<Self>,
20553 ) {
20554 self.display_map.update(cx, |map, _| {
20555 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20556 });
20557 cx.notify();
20558 }
20559
20560 pub fn text_highlights<'a, T: 'static>(
20561 &'a self,
20562 cx: &'a App,
20563 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20564 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20565 }
20566
20567 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20568 let cleared = self
20569 .display_map
20570 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20571 if cleared {
20572 cx.notify();
20573 }
20574 }
20575
20576 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20577 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20578 && self.focus_handle.is_focused(window)
20579 }
20580
20581 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20582 self.show_cursor_when_unfocused = is_enabled;
20583 cx.notify();
20584 }
20585
20586 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20587 cx.notify();
20588 }
20589
20590 fn on_debug_session_event(
20591 &mut self,
20592 _session: Entity<Session>,
20593 event: &SessionEvent,
20594 cx: &mut Context<Self>,
20595 ) {
20596 if let SessionEvent::InvalidateInlineValue = event {
20597 self.refresh_inline_values(cx);
20598 }
20599 }
20600
20601 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20602 let Some(project) = self.project.clone() else {
20603 return;
20604 };
20605
20606 if !self.inline_value_cache.enabled {
20607 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20608 self.splice_inlays(&inlays, Vec::new(), cx);
20609 return;
20610 }
20611
20612 let current_execution_position = self
20613 .highlighted_rows
20614 .get(&TypeId::of::<ActiveDebugLine>())
20615 .and_then(|lines| lines.last().map(|line| line.range.end));
20616
20617 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20618 let inline_values = editor
20619 .update(cx, |editor, cx| {
20620 let Some(current_execution_position) = current_execution_position else {
20621 return Some(Task::ready(Ok(Vec::new())));
20622 };
20623
20624 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20625 let snapshot = buffer.snapshot(cx);
20626
20627 let excerpt = snapshot.excerpt_containing(
20628 current_execution_position..current_execution_position,
20629 )?;
20630
20631 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20632 })?;
20633
20634 let range =
20635 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20636
20637 project.inline_values(buffer, range, cx)
20638 })
20639 .ok()
20640 .flatten()?
20641 .await
20642 .context("refreshing debugger inlays")
20643 .log_err()?;
20644
20645 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20646
20647 for (buffer_id, inline_value) in inline_values
20648 .into_iter()
20649 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20650 {
20651 buffer_inline_values
20652 .entry(buffer_id)
20653 .or_default()
20654 .push(inline_value);
20655 }
20656
20657 editor
20658 .update(cx, |editor, cx| {
20659 let snapshot = editor.buffer.read(cx).snapshot(cx);
20660 let mut new_inlays = Vec::default();
20661
20662 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20663 let buffer_id = buffer_snapshot.remote_id();
20664 buffer_inline_values
20665 .get(&buffer_id)
20666 .into_iter()
20667 .flatten()
20668 .for_each(|hint| {
20669 let inlay = Inlay::debugger(
20670 post_inc(&mut editor.next_inlay_id),
20671 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20672 hint.text(),
20673 );
20674 if !inlay.text().chars().contains(&'\n') {
20675 new_inlays.push(inlay);
20676 }
20677 });
20678 }
20679
20680 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20681 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20682
20683 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20684 })
20685 .ok()?;
20686 Some(())
20687 });
20688 }
20689
20690 fn on_buffer_event(
20691 &mut self,
20692 multibuffer: &Entity<MultiBuffer>,
20693 event: &multi_buffer::Event,
20694 window: &mut Window,
20695 cx: &mut Context<Self>,
20696 ) {
20697 match event {
20698 multi_buffer::Event::Edited {
20699 singleton_buffer_edited,
20700 edited_buffer,
20701 } => {
20702 self.scrollbar_marker_state.dirty = true;
20703 self.active_indent_guides_state.dirty = true;
20704 self.refresh_active_diagnostics(cx);
20705 self.refresh_code_actions(window, cx);
20706 self.refresh_selected_text_highlights(true, window, cx);
20707 self.refresh_single_line_folds(window, cx);
20708 refresh_matching_bracket_highlights(self, window, cx);
20709 if self.has_active_edit_prediction() {
20710 self.update_visible_edit_prediction(window, cx);
20711 }
20712 if let Some(project) = self.project.as_ref()
20713 && let Some(edited_buffer) = edited_buffer
20714 {
20715 project.update(cx, |project, cx| {
20716 self.registered_buffers
20717 .entry(edited_buffer.read(cx).remote_id())
20718 .or_insert_with(|| {
20719 project.register_buffer_with_language_servers(edited_buffer, cx)
20720 });
20721 });
20722 }
20723 cx.emit(EditorEvent::BufferEdited);
20724 cx.emit(SearchEvent::MatchesInvalidated);
20725
20726 if let Some(buffer) = edited_buffer {
20727 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20728 }
20729
20730 if *singleton_buffer_edited {
20731 if let Some(buffer) = edited_buffer
20732 && buffer.read(cx).file().is_none()
20733 {
20734 cx.emit(EditorEvent::TitleChanged);
20735 }
20736 if let Some(project) = &self.project {
20737 #[allow(clippy::mutable_key_type)]
20738 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20739 multibuffer
20740 .all_buffers()
20741 .into_iter()
20742 .filter_map(|buffer| {
20743 buffer.update(cx, |buffer, cx| {
20744 let language = buffer.language()?;
20745 let should_discard = project.update(cx, |project, cx| {
20746 project.is_local()
20747 && !project.has_language_servers_for(buffer, cx)
20748 });
20749 should_discard.not().then_some(language.clone())
20750 })
20751 })
20752 .collect::<HashSet<_>>()
20753 });
20754 if !languages_affected.is_empty() {
20755 self.refresh_inlay_hints(
20756 InlayHintRefreshReason::BufferEdited(languages_affected),
20757 cx,
20758 );
20759 }
20760 }
20761 }
20762
20763 let Some(project) = &self.project else { return };
20764 let (telemetry, is_via_ssh) = {
20765 let project = project.read(cx);
20766 let telemetry = project.client().telemetry().clone();
20767 let is_via_ssh = project.is_via_remote_server();
20768 (telemetry, is_via_ssh)
20769 };
20770 refresh_linked_ranges(self, window, cx);
20771 telemetry.log_edit_event("editor", is_via_ssh);
20772 }
20773 multi_buffer::Event::ExcerptsAdded {
20774 buffer,
20775 predecessor,
20776 excerpts,
20777 } => {
20778 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20779 let buffer_id = buffer.read(cx).remote_id();
20780 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20781 && let Some(project) = &self.project
20782 {
20783 update_uncommitted_diff_for_buffer(
20784 cx.entity(),
20785 project,
20786 [buffer.clone()],
20787 self.buffer.clone(),
20788 cx,
20789 )
20790 .detach();
20791 }
20792 if self.active_diagnostics != ActiveDiagnostic::All {
20793 self.update_lsp_data(false, Some(buffer_id), window, cx);
20794 }
20795 cx.emit(EditorEvent::ExcerptsAdded {
20796 buffer: buffer.clone(),
20797 predecessor: *predecessor,
20798 excerpts: excerpts.clone(),
20799 });
20800 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20801 }
20802 multi_buffer::Event::ExcerptsRemoved {
20803 ids,
20804 removed_buffer_ids,
20805 } => {
20806 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20807 let buffer = self.buffer.read(cx);
20808 self.registered_buffers
20809 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20810 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20811 cx.emit(EditorEvent::ExcerptsRemoved {
20812 ids: ids.clone(),
20813 removed_buffer_ids: removed_buffer_ids.clone(),
20814 });
20815 }
20816 multi_buffer::Event::ExcerptsEdited {
20817 excerpt_ids,
20818 buffer_ids,
20819 } => {
20820 self.display_map.update(cx, |map, cx| {
20821 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20822 });
20823 cx.emit(EditorEvent::ExcerptsEdited {
20824 ids: excerpt_ids.clone(),
20825 });
20826 }
20827 multi_buffer::Event::ExcerptsExpanded { ids } => {
20828 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20829 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20830 }
20831 multi_buffer::Event::Reparsed(buffer_id) => {
20832 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20833 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20834
20835 cx.emit(EditorEvent::Reparsed(*buffer_id));
20836 }
20837 multi_buffer::Event::DiffHunksToggled => {
20838 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20839 }
20840 multi_buffer::Event::LanguageChanged(buffer_id) => {
20841 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20842 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20843 cx.emit(EditorEvent::Reparsed(*buffer_id));
20844 cx.notify();
20845 }
20846 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20847 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20848 multi_buffer::Event::FileHandleChanged
20849 | multi_buffer::Event::Reloaded
20850 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20851 multi_buffer::Event::DiagnosticsUpdated => {
20852 self.update_diagnostics_state(window, cx);
20853 }
20854 _ => {}
20855 };
20856 }
20857
20858 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20859 if !self.diagnostics_enabled() {
20860 return;
20861 }
20862 self.refresh_active_diagnostics(cx);
20863 self.refresh_inline_diagnostics(true, window, cx);
20864 self.scrollbar_marker_state.dirty = true;
20865 cx.notify();
20866 }
20867
20868 pub fn start_temporary_diff_override(&mut self) {
20869 self.load_diff_task.take();
20870 self.temporary_diff_override = true;
20871 }
20872
20873 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20874 self.temporary_diff_override = false;
20875 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20876 self.buffer.update(cx, |buffer, cx| {
20877 buffer.set_all_diff_hunks_collapsed(cx);
20878 });
20879
20880 if let Some(project) = self.project.clone() {
20881 self.load_diff_task = Some(
20882 update_uncommitted_diff_for_buffer(
20883 cx.entity(),
20884 &project,
20885 self.buffer.read(cx).all_buffers(),
20886 self.buffer.clone(),
20887 cx,
20888 )
20889 .shared(),
20890 );
20891 }
20892 }
20893
20894 fn on_display_map_changed(
20895 &mut self,
20896 _: Entity<DisplayMap>,
20897 _: &mut Window,
20898 cx: &mut Context<Self>,
20899 ) {
20900 cx.notify();
20901 }
20902
20903 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20904 if self.diagnostics_enabled() {
20905 let new_severity = EditorSettings::get_global(cx)
20906 .diagnostics_max_severity
20907 .unwrap_or(DiagnosticSeverity::Hint);
20908 self.set_max_diagnostics_severity(new_severity, cx);
20909 }
20910 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20911 self.update_edit_prediction_settings(cx);
20912 self.refresh_edit_prediction(true, false, window, cx);
20913 self.refresh_inline_values(cx);
20914 self.refresh_inlay_hints(
20915 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20916 self.selections.newest_anchor().head(),
20917 &self.buffer.read(cx).snapshot(cx),
20918 cx,
20919 )),
20920 cx,
20921 );
20922
20923 let old_cursor_shape = self.cursor_shape;
20924 let old_show_breadcrumbs = self.show_breadcrumbs;
20925
20926 {
20927 let editor_settings = EditorSettings::get_global(cx);
20928 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20929 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20930 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20931 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20932 }
20933
20934 if old_cursor_shape != self.cursor_shape {
20935 cx.emit(EditorEvent::CursorShapeChanged);
20936 }
20937
20938 if old_show_breadcrumbs != self.show_breadcrumbs {
20939 cx.emit(EditorEvent::BreadcrumbsChanged);
20940 }
20941
20942 let project_settings = ProjectSettings::get_global(cx);
20943 self.serialize_dirty_buffers =
20944 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20945
20946 if self.mode.is_full() {
20947 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20948 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20949 if self.show_inline_diagnostics != show_inline_diagnostics {
20950 self.show_inline_diagnostics = show_inline_diagnostics;
20951 self.refresh_inline_diagnostics(false, window, cx);
20952 }
20953
20954 if self.git_blame_inline_enabled != inline_blame_enabled {
20955 self.toggle_git_blame_inline_internal(false, window, cx);
20956 }
20957
20958 let minimap_settings = EditorSettings::get_global(cx).minimap;
20959 if self.minimap_visibility != MinimapVisibility::Disabled {
20960 if self.minimap_visibility.settings_visibility()
20961 != minimap_settings.minimap_enabled()
20962 {
20963 self.set_minimap_visibility(
20964 MinimapVisibility::for_mode(self.mode(), cx),
20965 window,
20966 cx,
20967 );
20968 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20969 minimap_entity.update(cx, |minimap_editor, cx| {
20970 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20971 })
20972 }
20973 }
20974 }
20975
20976 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20977 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20978 }) {
20979 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20980 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20981 }
20982 self.refresh_colors(false, None, window, cx);
20983 }
20984
20985 cx.notify();
20986 }
20987
20988 pub fn set_searchable(&mut self, searchable: bool) {
20989 self.searchable = searchable;
20990 }
20991
20992 pub fn searchable(&self) -> bool {
20993 self.searchable
20994 }
20995
20996 fn open_proposed_changes_editor(
20997 &mut self,
20998 _: &OpenProposedChangesEditor,
20999 window: &mut Window,
21000 cx: &mut Context<Self>,
21001 ) {
21002 let Some(workspace) = self.workspace() else {
21003 cx.propagate();
21004 return;
21005 };
21006
21007 let selections = self.selections.all::<usize>(cx);
21008 let multi_buffer = self.buffer.read(cx);
21009 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21010 let mut new_selections_by_buffer = HashMap::default();
21011 for selection in selections {
21012 for (buffer, range, _) in
21013 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21014 {
21015 let mut range = range.to_point(buffer);
21016 range.start.column = 0;
21017 range.end.column = buffer.line_len(range.end.row);
21018 new_selections_by_buffer
21019 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21020 .or_insert(Vec::new())
21021 .push(range)
21022 }
21023 }
21024
21025 let proposed_changes_buffers = new_selections_by_buffer
21026 .into_iter()
21027 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21028 .collect::<Vec<_>>();
21029 let proposed_changes_editor = cx.new(|cx| {
21030 ProposedChangesEditor::new(
21031 "Proposed changes",
21032 proposed_changes_buffers,
21033 self.project.clone(),
21034 window,
21035 cx,
21036 )
21037 });
21038
21039 window.defer(cx, move |window, cx| {
21040 workspace.update(cx, |workspace, cx| {
21041 workspace.active_pane().update(cx, |pane, cx| {
21042 pane.add_item(
21043 Box::new(proposed_changes_editor),
21044 true,
21045 true,
21046 None,
21047 window,
21048 cx,
21049 );
21050 });
21051 });
21052 });
21053 }
21054
21055 pub fn open_excerpts_in_split(
21056 &mut self,
21057 _: &OpenExcerptsSplit,
21058 window: &mut Window,
21059 cx: &mut Context<Self>,
21060 ) {
21061 self.open_excerpts_common(None, true, window, cx)
21062 }
21063
21064 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21065 self.open_excerpts_common(None, false, window, cx)
21066 }
21067
21068 fn open_excerpts_common(
21069 &mut self,
21070 jump_data: Option<JumpData>,
21071 split: bool,
21072 window: &mut Window,
21073 cx: &mut Context<Self>,
21074 ) {
21075 let Some(workspace) = self.workspace() else {
21076 cx.propagate();
21077 return;
21078 };
21079
21080 if self.buffer.read(cx).is_singleton() {
21081 cx.propagate();
21082 return;
21083 }
21084
21085 let mut new_selections_by_buffer = HashMap::default();
21086 match &jump_data {
21087 Some(JumpData::MultiBufferPoint {
21088 excerpt_id,
21089 position,
21090 anchor,
21091 line_offset_from_top,
21092 }) => {
21093 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21094 if let Some(buffer) = multi_buffer_snapshot
21095 .buffer_id_for_excerpt(*excerpt_id)
21096 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21097 {
21098 let buffer_snapshot = buffer.read(cx).snapshot();
21099 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21100 language::ToPoint::to_point(anchor, &buffer_snapshot)
21101 } else {
21102 buffer_snapshot.clip_point(*position, Bias::Left)
21103 };
21104 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21105 new_selections_by_buffer.insert(
21106 buffer,
21107 (
21108 vec![jump_to_offset..jump_to_offset],
21109 Some(*line_offset_from_top),
21110 ),
21111 );
21112 }
21113 }
21114 Some(JumpData::MultiBufferRow {
21115 row,
21116 line_offset_from_top,
21117 }) => {
21118 let point = MultiBufferPoint::new(row.0, 0);
21119 if let Some((buffer, buffer_point, _)) =
21120 self.buffer.read(cx).point_to_buffer_point(point, cx)
21121 {
21122 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21123 new_selections_by_buffer
21124 .entry(buffer)
21125 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21126 .0
21127 .push(buffer_offset..buffer_offset)
21128 }
21129 }
21130 None => {
21131 let selections = self.selections.all::<usize>(cx);
21132 let multi_buffer = self.buffer.read(cx);
21133 for selection in selections {
21134 for (snapshot, range, _, anchor) in multi_buffer
21135 .snapshot(cx)
21136 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21137 {
21138 if let Some(anchor) = anchor {
21139 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21140 else {
21141 continue;
21142 };
21143 let offset = text::ToOffset::to_offset(
21144 &anchor.text_anchor,
21145 &buffer_handle.read(cx).snapshot(),
21146 );
21147 let range = offset..offset;
21148 new_selections_by_buffer
21149 .entry(buffer_handle)
21150 .or_insert((Vec::new(), None))
21151 .0
21152 .push(range)
21153 } else {
21154 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21155 else {
21156 continue;
21157 };
21158 new_selections_by_buffer
21159 .entry(buffer_handle)
21160 .or_insert((Vec::new(), None))
21161 .0
21162 .push(range)
21163 }
21164 }
21165 }
21166 }
21167 }
21168
21169 new_selections_by_buffer
21170 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21171
21172 if new_selections_by_buffer.is_empty() {
21173 return;
21174 }
21175
21176 // We defer the pane interaction because we ourselves are a workspace item
21177 // and activating a new item causes the pane to call a method on us reentrantly,
21178 // which panics if we're on the stack.
21179 window.defer(cx, move |window, cx| {
21180 workspace.update(cx, |workspace, cx| {
21181 let pane = if split {
21182 workspace.adjacent_pane(window, cx)
21183 } else {
21184 workspace.active_pane().clone()
21185 };
21186
21187 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21188 let editor = buffer
21189 .read(cx)
21190 .file()
21191 .is_none()
21192 .then(|| {
21193 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21194 // so `workspace.open_project_item` will never find them, always opening a new editor.
21195 // Instead, we try to activate the existing editor in the pane first.
21196 let (editor, pane_item_index) =
21197 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21198 let editor = item.downcast::<Editor>()?;
21199 let singleton_buffer =
21200 editor.read(cx).buffer().read(cx).as_singleton()?;
21201 if singleton_buffer == buffer {
21202 Some((editor, i))
21203 } else {
21204 None
21205 }
21206 })?;
21207 pane.update(cx, |pane, cx| {
21208 pane.activate_item(pane_item_index, true, true, window, cx)
21209 });
21210 Some(editor)
21211 })
21212 .flatten()
21213 .unwrap_or_else(|| {
21214 workspace.open_project_item::<Self>(
21215 pane.clone(),
21216 buffer,
21217 true,
21218 true,
21219 window,
21220 cx,
21221 )
21222 });
21223
21224 editor.update(cx, |editor, cx| {
21225 let autoscroll = match scroll_offset {
21226 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21227 None => Autoscroll::newest(),
21228 };
21229 let nav_history = editor.nav_history.take();
21230 editor.change_selections(
21231 SelectionEffects::scroll(autoscroll),
21232 window,
21233 cx,
21234 |s| {
21235 s.select_ranges(ranges);
21236 },
21237 );
21238 editor.nav_history = nav_history;
21239 });
21240 }
21241 })
21242 });
21243 }
21244
21245 // For now, don't allow opening excerpts in buffers that aren't backed by
21246 // regular project files.
21247 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21248 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21249 }
21250
21251 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21252 let snapshot = self.buffer.read(cx).read(cx);
21253 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21254 Some(
21255 ranges
21256 .iter()
21257 .map(move |range| {
21258 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21259 })
21260 .collect(),
21261 )
21262 }
21263
21264 fn selection_replacement_ranges(
21265 &self,
21266 range: Range<OffsetUtf16>,
21267 cx: &mut App,
21268 ) -> Vec<Range<OffsetUtf16>> {
21269 let selections = self.selections.all::<OffsetUtf16>(cx);
21270 let newest_selection = selections
21271 .iter()
21272 .max_by_key(|selection| selection.id)
21273 .unwrap();
21274 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21275 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21276 let snapshot = self.buffer.read(cx).read(cx);
21277 selections
21278 .into_iter()
21279 .map(|mut selection| {
21280 selection.start.0 =
21281 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21282 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21283 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21284 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21285 })
21286 .collect()
21287 }
21288
21289 fn report_editor_event(
21290 &self,
21291 reported_event: ReportEditorEvent,
21292 file_extension: Option<String>,
21293 cx: &App,
21294 ) {
21295 if cfg!(any(test, feature = "test-support")) {
21296 return;
21297 }
21298
21299 let Some(project) = &self.project else { return };
21300
21301 // If None, we are in a file without an extension
21302 let file = self
21303 .buffer
21304 .read(cx)
21305 .as_singleton()
21306 .and_then(|b| b.read(cx).file());
21307 let file_extension = file_extension.or(file
21308 .as_ref()
21309 .and_then(|file| Path::new(file.file_name(cx)).extension())
21310 .and_then(|e| e.to_str())
21311 .map(|a| a.to_string()));
21312
21313 let vim_mode = vim_enabled(cx);
21314
21315 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21316 let copilot_enabled = edit_predictions_provider
21317 == language::language_settings::EditPredictionProvider::Copilot;
21318 let copilot_enabled_for_language = self
21319 .buffer
21320 .read(cx)
21321 .language_settings(cx)
21322 .show_edit_predictions;
21323
21324 let project = project.read(cx);
21325 let event_type = reported_event.event_type();
21326
21327 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21328 telemetry::event!(
21329 event_type,
21330 type = if auto_saved {"autosave"} else {"manual"},
21331 file_extension,
21332 vim_mode,
21333 copilot_enabled,
21334 copilot_enabled_for_language,
21335 edit_predictions_provider,
21336 is_via_ssh = project.is_via_remote_server(),
21337 );
21338 } else {
21339 telemetry::event!(
21340 event_type,
21341 file_extension,
21342 vim_mode,
21343 copilot_enabled,
21344 copilot_enabled_for_language,
21345 edit_predictions_provider,
21346 is_via_ssh = project.is_via_remote_server(),
21347 );
21348 };
21349 }
21350
21351 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21352 /// with each line being an array of {text, highlight} objects.
21353 fn copy_highlight_json(
21354 &mut self,
21355 _: &CopyHighlightJson,
21356 window: &mut Window,
21357 cx: &mut Context<Self>,
21358 ) {
21359 #[derive(Serialize)]
21360 struct Chunk<'a> {
21361 text: String,
21362 highlight: Option<&'a str>,
21363 }
21364
21365 let snapshot = self.buffer.read(cx).snapshot(cx);
21366 let range = self
21367 .selected_text_range(false, window, cx)
21368 .and_then(|selection| {
21369 if selection.range.is_empty() {
21370 None
21371 } else {
21372 Some(selection.range)
21373 }
21374 })
21375 .unwrap_or_else(|| 0..snapshot.len());
21376
21377 let chunks = snapshot.chunks(range, true);
21378 let mut lines = Vec::new();
21379 let mut line: VecDeque<Chunk> = VecDeque::new();
21380
21381 let Some(style) = self.style.as_ref() else {
21382 return;
21383 };
21384
21385 for chunk in chunks {
21386 let highlight = chunk
21387 .syntax_highlight_id
21388 .and_then(|id| id.name(&style.syntax));
21389 let mut chunk_lines = chunk.text.split('\n').peekable();
21390 while let Some(text) = chunk_lines.next() {
21391 let mut merged_with_last_token = false;
21392 if let Some(last_token) = line.back_mut()
21393 && last_token.highlight == highlight
21394 {
21395 last_token.text.push_str(text);
21396 merged_with_last_token = true;
21397 }
21398
21399 if !merged_with_last_token {
21400 line.push_back(Chunk {
21401 text: text.into(),
21402 highlight,
21403 });
21404 }
21405
21406 if chunk_lines.peek().is_some() {
21407 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21408 line.pop_front();
21409 }
21410 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21411 line.pop_back();
21412 }
21413
21414 lines.push(mem::take(&mut line));
21415 }
21416 }
21417 }
21418
21419 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21420 return;
21421 };
21422 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21423 }
21424
21425 pub fn open_context_menu(
21426 &mut self,
21427 _: &OpenContextMenu,
21428 window: &mut Window,
21429 cx: &mut Context<Self>,
21430 ) {
21431 self.request_autoscroll(Autoscroll::newest(), cx);
21432 let position = self.selections.newest_display(cx).start;
21433 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21434 }
21435
21436 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21437 &self.inlay_hint_cache
21438 }
21439
21440 pub fn replay_insert_event(
21441 &mut self,
21442 text: &str,
21443 relative_utf16_range: Option<Range<isize>>,
21444 window: &mut Window,
21445 cx: &mut Context<Self>,
21446 ) {
21447 if !self.input_enabled {
21448 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21449 return;
21450 }
21451 if let Some(relative_utf16_range) = relative_utf16_range {
21452 let selections = self.selections.all::<OffsetUtf16>(cx);
21453 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21454 let new_ranges = selections.into_iter().map(|range| {
21455 let start = OffsetUtf16(
21456 range
21457 .head()
21458 .0
21459 .saturating_add_signed(relative_utf16_range.start),
21460 );
21461 let end = OffsetUtf16(
21462 range
21463 .head()
21464 .0
21465 .saturating_add_signed(relative_utf16_range.end),
21466 );
21467 start..end
21468 });
21469 s.select_ranges(new_ranges);
21470 });
21471 }
21472
21473 self.handle_input(text, window, cx);
21474 }
21475
21476 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21477 let Some(provider) = self.semantics_provider.as_ref() else {
21478 return false;
21479 };
21480
21481 let mut supports = false;
21482 self.buffer().update(cx, |this, cx| {
21483 this.for_each_buffer(|buffer| {
21484 supports |= provider.supports_inlay_hints(buffer, cx);
21485 });
21486 });
21487
21488 supports
21489 }
21490
21491 pub fn is_focused(&self, window: &Window) -> bool {
21492 self.focus_handle.is_focused(window)
21493 }
21494
21495 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21496 cx.emit(EditorEvent::Focused);
21497
21498 if let Some(descendant) = self
21499 .last_focused_descendant
21500 .take()
21501 .and_then(|descendant| descendant.upgrade())
21502 {
21503 window.focus(&descendant);
21504 } else {
21505 if let Some(blame) = self.blame.as_ref() {
21506 blame.update(cx, GitBlame::focus)
21507 }
21508
21509 self.blink_manager.update(cx, BlinkManager::enable);
21510 self.show_cursor_names(window, cx);
21511 self.buffer.update(cx, |buffer, cx| {
21512 buffer.finalize_last_transaction(cx);
21513 if self.leader_id.is_none() {
21514 buffer.set_active_selections(
21515 &self.selections.disjoint_anchors_arc(),
21516 self.selections.line_mode(),
21517 self.cursor_shape,
21518 cx,
21519 );
21520 }
21521 });
21522 }
21523 }
21524
21525 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21526 cx.emit(EditorEvent::FocusedIn)
21527 }
21528
21529 fn handle_focus_out(
21530 &mut self,
21531 event: FocusOutEvent,
21532 _window: &mut Window,
21533 cx: &mut Context<Self>,
21534 ) {
21535 if event.blurred != self.focus_handle {
21536 self.last_focused_descendant = Some(event.blurred);
21537 }
21538 self.selection_drag_state = SelectionDragState::None;
21539 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21540 }
21541
21542 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21543 self.blink_manager.update(cx, BlinkManager::disable);
21544 self.buffer
21545 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21546
21547 if let Some(blame) = self.blame.as_ref() {
21548 blame.update(cx, GitBlame::blur)
21549 }
21550 if !self.hover_state.focused(window, cx) {
21551 hide_hover(self, cx);
21552 }
21553 if !self
21554 .context_menu
21555 .borrow()
21556 .as_ref()
21557 .is_some_and(|context_menu| context_menu.focused(window, cx))
21558 {
21559 self.hide_context_menu(window, cx);
21560 }
21561 self.take_active_edit_prediction(cx);
21562 cx.emit(EditorEvent::Blurred);
21563 cx.notify();
21564 }
21565
21566 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21567 let mut pending: String = window
21568 .pending_input_keystrokes()
21569 .into_iter()
21570 .flatten()
21571 .filter_map(|keystroke| {
21572 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21573 keystroke.key_char.clone()
21574 } else {
21575 None
21576 }
21577 })
21578 .collect();
21579
21580 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21581 pending = "".to_string();
21582 }
21583
21584 let existing_pending = self
21585 .text_highlights::<PendingInput>(cx)
21586 .map(|(_, ranges)| ranges.to_vec());
21587 if existing_pending.is_none() && pending.is_empty() {
21588 return;
21589 }
21590 let transaction =
21591 self.transact(window, cx, |this, window, cx| {
21592 let selections = this.selections.all::<usize>(cx);
21593 let edits = selections
21594 .iter()
21595 .map(|selection| (selection.end..selection.end, pending.clone()));
21596 this.edit(edits, cx);
21597 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21598 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21599 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21600 }));
21601 });
21602 if let Some(existing_ranges) = existing_pending {
21603 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21604 this.edit(edits, cx);
21605 }
21606 });
21607
21608 let snapshot = self.snapshot(window, cx);
21609 let ranges = self
21610 .selections
21611 .all::<usize>(cx)
21612 .into_iter()
21613 .map(|selection| {
21614 snapshot.buffer_snapshot().anchor_after(selection.end)
21615 ..snapshot
21616 .buffer_snapshot()
21617 .anchor_before(selection.end + pending.len())
21618 })
21619 .collect();
21620
21621 if pending.is_empty() {
21622 self.clear_highlights::<PendingInput>(cx);
21623 } else {
21624 self.highlight_text::<PendingInput>(
21625 ranges,
21626 HighlightStyle {
21627 underline: Some(UnderlineStyle {
21628 thickness: px(1.),
21629 color: None,
21630 wavy: false,
21631 }),
21632 ..Default::default()
21633 },
21634 cx,
21635 );
21636 }
21637
21638 self.ime_transaction = self.ime_transaction.or(transaction);
21639 if let Some(transaction) = self.ime_transaction {
21640 self.buffer.update(cx, |buffer, cx| {
21641 buffer.group_until_transaction(transaction, cx);
21642 });
21643 }
21644
21645 if self.text_highlights::<PendingInput>(cx).is_none() {
21646 self.ime_transaction.take();
21647 }
21648 }
21649
21650 pub fn register_action_renderer(
21651 &mut self,
21652 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21653 ) -> Subscription {
21654 let id = self.next_editor_action_id.post_inc();
21655 self.editor_actions
21656 .borrow_mut()
21657 .insert(id, Box::new(listener));
21658
21659 let editor_actions = self.editor_actions.clone();
21660 Subscription::new(move || {
21661 editor_actions.borrow_mut().remove(&id);
21662 })
21663 }
21664
21665 pub fn register_action<A: Action>(
21666 &mut self,
21667 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21668 ) -> Subscription {
21669 let id = self.next_editor_action_id.post_inc();
21670 let listener = Arc::new(listener);
21671 self.editor_actions.borrow_mut().insert(
21672 id,
21673 Box::new(move |_, window, _| {
21674 let listener = listener.clone();
21675 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21676 let action = action.downcast_ref().unwrap();
21677 if phase == DispatchPhase::Bubble {
21678 listener(action, window, cx)
21679 }
21680 })
21681 }),
21682 );
21683
21684 let editor_actions = self.editor_actions.clone();
21685 Subscription::new(move || {
21686 editor_actions.borrow_mut().remove(&id);
21687 })
21688 }
21689
21690 pub fn file_header_size(&self) -> u32 {
21691 FILE_HEADER_HEIGHT
21692 }
21693
21694 pub fn restore(
21695 &mut self,
21696 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21697 window: &mut Window,
21698 cx: &mut Context<Self>,
21699 ) {
21700 let workspace = self.workspace();
21701 let project = self.project();
21702 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21703 let mut tasks = Vec::new();
21704 for (buffer_id, changes) in revert_changes {
21705 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21706 buffer.update(cx, |buffer, cx| {
21707 buffer.edit(
21708 changes
21709 .into_iter()
21710 .map(|(range, text)| (range, text.to_string())),
21711 None,
21712 cx,
21713 );
21714 });
21715
21716 if let Some(project) =
21717 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21718 {
21719 project.update(cx, |project, cx| {
21720 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21721 })
21722 }
21723 }
21724 }
21725 tasks
21726 });
21727 cx.spawn_in(window, async move |_, cx| {
21728 for (buffer, task) in save_tasks {
21729 let result = task.await;
21730 if result.is_err() {
21731 let Some(path) = buffer
21732 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21733 .ok()
21734 else {
21735 continue;
21736 };
21737 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21738 let Some(task) = cx
21739 .update_window_entity(workspace, |workspace, window, cx| {
21740 workspace
21741 .open_path_preview(path, None, false, false, false, window, cx)
21742 })
21743 .ok()
21744 else {
21745 continue;
21746 };
21747 task.await.log_err();
21748 }
21749 }
21750 }
21751 })
21752 .detach();
21753 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21754 selections.refresh()
21755 });
21756 }
21757
21758 pub fn to_pixel_point(
21759 &self,
21760 source: multi_buffer::Anchor,
21761 editor_snapshot: &EditorSnapshot,
21762 window: &mut Window,
21763 ) -> Option<gpui::Point<Pixels>> {
21764 let source_point = source.to_display_point(editor_snapshot);
21765 self.display_to_pixel_point(source_point, editor_snapshot, window)
21766 }
21767
21768 pub fn display_to_pixel_point(
21769 &self,
21770 source: DisplayPoint,
21771 editor_snapshot: &EditorSnapshot,
21772 window: &mut Window,
21773 ) -> Option<gpui::Point<Pixels>> {
21774 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21775 let text_layout_details = self.text_layout_details(window);
21776 let scroll_top = text_layout_details
21777 .scroll_anchor
21778 .scroll_position(editor_snapshot)
21779 .y;
21780
21781 if source.row().as_f64() < scroll_top.floor() {
21782 return None;
21783 }
21784 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21785 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21786 Some(gpui::Point::new(source_x, source_y))
21787 }
21788
21789 pub fn has_visible_completions_menu(&self) -> bool {
21790 !self.edit_prediction_preview_is_active()
21791 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21792 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21793 })
21794 }
21795
21796 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21797 if self.mode.is_minimap() {
21798 return;
21799 }
21800 self.addons
21801 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21802 }
21803
21804 pub fn unregister_addon<T: Addon>(&mut self) {
21805 self.addons.remove(&std::any::TypeId::of::<T>());
21806 }
21807
21808 pub fn addon<T: Addon>(&self) -> Option<&T> {
21809 let type_id = std::any::TypeId::of::<T>();
21810 self.addons
21811 .get(&type_id)
21812 .and_then(|item| item.to_any().downcast_ref::<T>())
21813 }
21814
21815 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21816 let type_id = std::any::TypeId::of::<T>();
21817 self.addons
21818 .get_mut(&type_id)
21819 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21820 }
21821
21822 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21823 let text_layout_details = self.text_layout_details(window);
21824 let style = &text_layout_details.editor_style;
21825 let font_id = window.text_system().resolve_font(&style.text.font());
21826 let font_size = style.text.font_size.to_pixels(window.rem_size());
21827 let line_height = style.text.line_height_in_pixels(window.rem_size());
21828 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21829 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21830
21831 CharacterDimensions {
21832 em_width,
21833 em_advance,
21834 line_height,
21835 }
21836 }
21837
21838 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21839 self.load_diff_task.clone()
21840 }
21841
21842 fn read_metadata_from_db(
21843 &mut self,
21844 item_id: u64,
21845 workspace_id: WorkspaceId,
21846 window: &mut Window,
21847 cx: &mut Context<Editor>,
21848 ) {
21849 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21850 && !self.mode.is_minimap()
21851 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21852 {
21853 let buffer_snapshot = OnceCell::new();
21854
21855 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21856 && !folds.is_empty()
21857 {
21858 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21859 self.fold_ranges(
21860 folds
21861 .into_iter()
21862 .map(|(start, end)| {
21863 snapshot.clip_offset(start, Bias::Left)
21864 ..snapshot.clip_offset(end, Bias::Right)
21865 })
21866 .collect(),
21867 false,
21868 window,
21869 cx,
21870 );
21871 }
21872
21873 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21874 && !selections.is_empty()
21875 {
21876 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21877 // skip adding the initial selection to selection history
21878 self.selection_history.mode = SelectionHistoryMode::Skipping;
21879 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21880 s.select_ranges(selections.into_iter().map(|(start, end)| {
21881 snapshot.clip_offset(start, Bias::Left)
21882 ..snapshot.clip_offset(end, Bias::Right)
21883 }));
21884 });
21885 self.selection_history.mode = SelectionHistoryMode::Normal;
21886 };
21887 }
21888
21889 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21890 }
21891
21892 fn update_lsp_data(
21893 &mut self,
21894 ignore_cache: bool,
21895 for_buffer: Option<BufferId>,
21896 window: &mut Window,
21897 cx: &mut Context<'_, Self>,
21898 ) {
21899 self.pull_diagnostics(for_buffer, window, cx);
21900 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21901 }
21902}
21903
21904fn edit_for_markdown_paste<'a>(
21905 buffer: &MultiBufferSnapshot,
21906 range: Range<usize>,
21907 to_insert: &'a str,
21908 url: Option<url::Url>,
21909) -> (Range<usize>, Cow<'a, str>) {
21910 if url.is_none() {
21911 return (range, Cow::Borrowed(to_insert));
21912 };
21913
21914 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21915
21916 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21917 Cow::Borrowed(to_insert)
21918 } else {
21919 Cow::Owned(format!("[{old_text}]({to_insert})"))
21920 };
21921 (range, new_text)
21922}
21923
21924fn vim_enabled(cx: &App) -> bool {
21925 vim_mode_setting::VimModeSetting::try_get(cx)
21926 .map(|vim_mode| vim_mode.0)
21927 .unwrap_or(false)
21928}
21929
21930fn process_completion_for_edit(
21931 completion: &Completion,
21932 intent: CompletionIntent,
21933 buffer: &Entity<Buffer>,
21934 cursor_position: &text::Anchor,
21935 cx: &mut Context<Editor>,
21936) -> CompletionEdit {
21937 let buffer = buffer.read(cx);
21938 let buffer_snapshot = buffer.snapshot();
21939 let (snippet, new_text) = if completion.is_snippet() {
21940 let mut snippet_source = completion.new_text.clone();
21941 // Workaround for typescript language server issues so that methods don't expand within
21942 // strings and functions with type expressions. The previous point is used because the query
21943 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21944 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
21945 let previous_point = if previous_point.column > 0 {
21946 cursor_position.to_previous_offset(&buffer_snapshot)
21947 } else {
21948 cursor_position.to_offset(&buffer_snapshot)
21949 };
21950 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21951 && scope.prefers_label_for_snippet_in_completion()
21952 && let Some(label) = completion.label()
21953 && matches!(
21954 completion.kind(),
21955 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21956 )
21957 {
21958 snippet_source = label;
21959 }
21960 match Snippet::parse(&snippet_source).log_err() {
21961 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21962 None => (None, completion.new_text.clone()),
21963 }
21964 } else {
21965 (None, completion.new_text.clone())
21966 };
21967
21968 let mut range_to_replace = {
21969 let replace_range = &completion.replace_range;
21970 if let CompletionSource::Lsp {
21971 insert_range: Some(insert_range),
21972 ..
21973 } = &completion.source
21974 {
21975 debug_assert_eq!(
21976 insert_range.start, replace_range.start,
21977 "insert_range and replace_range should start at the same position"
21978 );
21979 debug_assert!(
21980 insert_range
21981 .start
21982 .cmp(cursor_position, &buffer_snapshot)
21983 .is_le(),
21984 "insert_range should start before or at cursor position"
21985 );
21986 debug_assert!(
21987 replace_range
21988 .start
21989 .cmp(cursor_position, &buffer_snapshot)
21990 .is_le(),
21991 "replace_range should start before or at cursor position"
21992 );
21993
21994 let should_replace = match intent {
21995 CompletionIntent::CompleteWithInsert => false,
21996 CompletionIntent::CompleteWithReplace => true,
21997 CompletionIntent::Complete | CompletionIntent::Compose => {
21998 let insert_mode =
21999 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22000 .completions
22001 .lsp_insert_mode;
22002 match insert_mode {
22003 LspInsertMode::Insert => false,
22004 LspInsertMode::Replace => true,
22005 LspInsertMode::ReplaceSubsequence => {
22006 let mut text_to_replace = buffer.chars_for_range(
22007 buffer.anchor_before(replace_range.start)
22008 ..buffer.anchor_after(replace_range.end),
22009 );
22010 let mut current_needle = text_to_replace.next();
22011 for haystack_ch in completion.label.text.chars() {
22012 if let Some(needle_ch) = current_needle
22013 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22014 {
22015 current_needle = text_to_replace.next();
22016 }
22017 }
22018 current_needle.is_none()
22019 }
22020 LspInsertMode::ReplaceSuffix => {
22021 if replace_range
22022 .end
22023 .cmp(cursor_position, &buffer_snapshot)
22024 .is_gt()
22025 {
22026 let range_after_cursor = *cursor_position..replace_range.end;
22027 let text_after_cursor = buffer
22028 .text_for_range(
22029 buffer.anchor_before(range_after_cursor.start)
22030 ..buffer.anchor_after(range_after_cursor.end),
22031 )
22032 .collect::<String>()
22033 .to_ascii_lowercase();
22034 completion
22035 .label
22036 .text
22037 .to_ascii_lowercase()
22038 .ends_with(&text_after_cursor)
22039 } else {
22040 true
22041 }
22042 }
22043 }
22044 }
22045 };
22046
22047 if should_replace {
22048 replace_range.clone()
22049 } else {
22050 insert_range.clone()
22051 }
22052 } else {
22053 replace_range.clone()
22054 }
22055 };
22056
22057 if range_to_replace
22058 .end
22059 .cmp(cursor_position, &buffer_snapshot)
22060 .is_lt()
22061 {
22062 range_to_replace.end = *cursor_position;
22063 }
22064
22065 CompletionEdit {
22066 new_text,
22067 replace_range: range_to_replace.to_offset(buffer),
22068 snippet,
22069 }
22070}
22071
22072struct CompletionEdit {
22073 new_text: String,
22074 replace_range: Range<usize>,
22075 snippet: Option<Snippet>,
22076}
22077
22078fn insert_extra_newline_brackets(
22079 buffer: &MultiBufferSnapshot,
22080 range: Range<usize>,
22081 language: &language::LanguageScope,
22082) -> bool {
22083 let leading_whitespace_len = buffer
22084 .reversed_chars_at(range.start)
22085 .take_while(|c| c.is_whitespace() && *c != '\n')
22086 .map(|c| c.len_utf8())
22087 .sum::<usize>();
22088 let trailing_whitespace_len = buffer
22089 .chars_at(range.end)
22090 .take_while(|c| c.is_whitespace() && *c != '\n')
22091 .map(|c| c.len_utf8())
22092 .sum::<usize>();
22093 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22094
22095 language.brackets().any(|(pair, enabled)| {
22096 let pair_start = pair.start.trim_end();
22097 let pair_end = pair.end.trim_start();
22098
22099 enabled
22100 && pair.newline
22101 && buffer.contains_str_at(range.end, pair_end)
22102 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22103 })
22104}
22105
22106fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22107 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22108 [(buffer, range, _)] => (*buffer, range.clone()),
22109 _ => return false,
22110 };
22111 let pair = {
22112 let mut result: Option<BracketMatch> = None;
22113
22114 for pair in buffer
22115 .all_bracket_ranges(range.clone())
22116 .filter(move |pair| {
22117 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22118 })
22119 {
22120 let len = pair.close_range.end - pair.open_range.start;
22121
22122 if let Some(existing) = &result {
22123 let existing_len = existing.close_range.end - existing.open_range.start;
22124 if len > existing_len {
22125 continue;
22126 }
22127 }
22128
22129 result = Some(pair);
22130 }
22131
22132 result
22133 };
22134 let Some(pair) = pair else {
22135 return false;
22136 };
22137 pair.newline_only
22138 && buffer
22139 .chars_for_range(pair.open_range.end..range.start)
22140 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22141 .all(|c| c.is_whitespace() && c != '\n')
22142}
22143
22144fn update_uncommitted_diff_for_buffer(
22145 editor: Entity<Editor>,
22146 project: &Entity<Project>,
22147 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22148 buffer: Entity<MultiBuffer>,
22149 cx: &mut App,
22150) -> Task<()> {
22151 let mut tasks = Vec::new();
22152 project.update(cx, |project, cx| {
22153 for buffer in buffers {
22154 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22155 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22156 }
22157 }
22158 });
22159 cx.spawn(async move |cx| {
22160 let diffs = future::join_all(tasks).await;
22161 if editor
22162 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22163 .unwrap_or(false)
22164 {
22165 return;
22166 }
22167
22168 buffer
22169 .update(cx, |buffer, cx| {
22170 for diff in diffs.into_iter().flatten() {
22171 buffer.add_diff(diff, cx);
22172 }
22173 })
22174 .ok();
22175 })
22176}
22177
22178fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22179 let tab_size = tab_size.get() as usize;
22180 let mut width = offset;
22181
22182 for ch in text.chars() {
22183 width += if ch == '\t' {
22184 tab_size - (width % tab_size)
22185 } else {
22186 1
22187 };
22188 }
22189
22190 width - offset
22191}
22192
22193#[cfg(test)]
22194mod tests {
22195 use super::*;
22196
22197 #[test]
22198 fn test_string_size_with_expanded_tabs() {
22199 let nz = |val| NonZeroU32::new(val).unwrap();
22200 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22201 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22202 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22203 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22204 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22205 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22206 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22207 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22208 }
22209}
22210
22211/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22212struct WordBreakingTokenizer<'a> {
22213 input: &'a str,
22214}
22215
22216impl<'a> WordBreakingTokenizer<'a> {
22217 fn new(input: &'a str) -> Self {
22218 Self { input }
22219 }
22220}
22221
22222fn is_char_ideographic(ch: char) -> bool {
22223 use unicode_script::Script::*;
22224 use unicode_script::UnicodeScript;
22225 matches!(ch.script(), Han | Tangut | Yi)
22226}
22227
22228fn is_grapheme_ideographic(text: &str) -> bool {
22229 text.chars().any(is_char_ideographic)
22230}
22231
22232fn is_grapheme_whitespace(text: &str) -> bool {
22233 text.chars().any(|x| x.is_whitespace())
22234}
22235
22236fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22237 text.chars()
22238 .next()
22239 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22240}
22241
22242#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22243enum WordBreakToken<'a> {
22244 Word { token: &'a str, grapheme_len: usize },
22245 InlineWhitespace { token: &'a str, grapheme_len: usize },
22246 Newline,
22247}
22248
22249impl<'a> Iterator for WordBreakingTokenizer<'a> {
22250 /// Yields a span, the count of graphemes in the token, and whether it was
22251 /// whitespace. Note that it also breaks at word boundaries.
22252 type Item = WordBreakToken<'a>;
22253
22254 fn next(&mut self) -> Option<Self::Item> {
22255 use unicode_segmentation::UnicodeSegmentation;
22256 if self.input.is_empty() {
22257 return None;
22258 }
22259
22260 let mut iter = self.input.graphemes(true).peekable();
22261 let mut offset = 0;
22262 let mut grapheme_len = 0;
22263 if let Some(first_grapheme) = iter.next() {
22264 let is_newline = first_grapheme == "\n";
22265 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22266 offset += first_grapheme.len();
22267 grapheme_len += 1;
22268 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22269 if let Some(grapheme) = iter.peek().copied()
22270 && should_stay_with_preceding_ideograph(grapheme)
22271 {
22272 offset += grapheme.len();
22273 grapheme_len += 1;
22274 }
22275 } else {
22276 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22277 let mut next_word_bound = words.peek().copied();
22278 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22279 next_word_bound = words.next();
22280 }
22281 while let Some(grapheme) = iter.peek().copied() {
22282 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22283 break;
22284 };
22285 if is_grapheme_whitespace(grapheme) != is_whitespace
22286 || (grapheme == "\n") != is_newline
22287 {
22288 break;
22289 };
22290 offset += grapheme.len();
22291 grapheme_len += 1;
22292 iter.next();
22293 }
22294 }
22295 let token = &self.input[..offset];
22296 self.input = &self.input[offset..];
22297 if token == "\n" {
22298 Some(WordBreakToken::Newline)
22299 } else if is_whitespace {
22300 Some(WordBreakToken::InlineWhitespace {
22301 token,
22302 grapheme_len,
22303 })
22304 } else {
22305 Some(WordBreakToken::Word {
22306 token,
22307 grapheme_len,
22308 })
22309 }
22310 } else {
22311 None
22312 }
22313 }
22314}
22315
22316#[test]
22317fn test_word_breaking_tokenizer() {
22318 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22319 ("", &[]),
22320 (" ", &[whitespace(" ", 2)]),
22321 ("Ʒ", &[word("Ʒ", 1)]),
22322 ("Ǽ", &[word("Ǽ", 1)]),
22323 ("⋑", &[word("⋑", 1)]),
22324 ("⋑⋑", &[word("⋑⋑", 2)]),
22325 (
22326 "原理,进而",
22327 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22328 ),
22329 (
22330 "hello world",
22331 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22332 ),
22333 (
22334 "hello, world",
22335 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22336 ),
22337 (
22338 " hello world",
22339 &[
22340 whitespace(" ", 2),
22341 word("hello", 5),
22342 whitespace(" ", 1),
22343 word("world", 5),
22344 ],
22345 ),
22346 (
22347 "这是什么 \n 钢笔",
22348 &[
22349 word("这", 1),
22350 word("是", 1),
22351 word("什", 1),
22352 word("么", 1),
22353 whitespace(" ", 1),
22354 newline(),
22355 whitespace(" ", 1),
22356 word("钢", 1),
22357 word("笔", 1),
22358 ],
22359 ),
22360 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22361 ];
22362
22363 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22364 WordBreakToken::Word {
22365 token,
22366 grapheme_len,
22367 }
22368 }
22369
22370 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22371 WordBreakToken::InlineWhitespace {
22372 token,
22373 grapheme_len,
22374 }
22375 }
22376
22377 fn newline() -> WordBreakToken<'static> {
22378 WordBreakToken::Newline
22379 }
22380
22381 for (input, result) in tests {
22382 assert_eq!(
22383 WordBreakingTokenizer::new(input)
22384 .collect::<Vec<_>>()
22385 .as_slice(),
22386 *result,
22387 );
22388 }
22389}
22390
22391fn wrap_with_prefix(
22392 first_line_prefix: String,
22393 subsequent_lines_prefix: String,
22394 unwrapped_text: String,
22395 wrap_column: usize,
22396 tab_size: NonZeroU32,
22397 preserve_existing_whitespace: bool,
22398) -> String {
22399 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22400 let subsequent_lines_prefix_len =
22401 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22402 let mut wrapped_text = String::new();
22403 let mut current_line = first_line_prefix;
22404 let mut is_first_line = true;
22405
22406 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22407 let mut current_line_len = first_line_prefix_len;
22408 let mut in_whitespace = false;
22409 for token in tokenizer {
22410 let have_preceding_whitespace = in_whitespace;
22411 match token {
22412 WordBreakToken::Word {
22413 token,
22414 grapheme_len,
22415 } => {
22416 in_whitespace = false;
22417 let current_prefix_len = if is_first_line {
22418 first_line_prefix_len
22419 } else {
22420 subsequent_lines_prefix_len
22421 };
22422 if current_line_len + grapheme_len > wrap_column
22423 && current_line_len != current_prefix_len
22424 {
22425 wrapped_text.push_str(current_line.trim_end());
22426 wrapped_text.push('\n');
22427 is_first_line = false;
22428 current_line = subsequent_lines_prefix.clone();
22429 current_line_len = subsequent_lines_prefix_len;
22430 }
22431 current_line.push_str(token);
22432 current_line_len += grapheme_len;
22433 }
22434 WordBreakToken::InlineWhitespace {
22435 mut token,
22436 mut grapheme_len,
22437 } => {
22438 in_whitespace = true;
22439 if have_preceding_whitespace && !preserve_existing_whitespace {
22440 continue;
22441 }
22442 if !preserve_existing_whitespace {
22443 // Keep a single whitespace grapheme as-is
22444 if let Some(first) =
22445 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22446 {
22447 token = first;
22448 } else {
22449 token = " ";
22450 }
22451 grapheme_len = 1;
22452 }
22453 let current_prefix_len = if is_first_line {
22454 first_line_prefix_len
22455 } else {
22456 subsequent_lines_prefix_len
22457 };
22458 if current_line_len + grapheme_len > wrap_column {
22459 wrapped_text.push_str(current_line.trim_end());
22460 wrapped_text.push('\n');
22461 is_first_line = false;
22462 current_line = subsequent_lines_prefix.clone();
22463 current_line_len = subsequent_lines_prefix_len;
22464 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22465 current_line.push_str(token);
22466 current_line_len += grapheme_len;
22467 }
22468 }
22469 WordBreakToken::Newline => {
22470 in_whitespace = true;
22471 let current_prefix_len = if is_first_line {
22472 first_line_prefix_len
22473 } else {
22474 subsequent_lines_prefix_len
22475 };
22476 if preserve_existing_whitespace {
22477 wrapped_text.push_str(current_line.trim_end());
22478 wrapped_text.push('\n');
22479 is_first_line = false;
22480 current_line = subsequent_lines_prefix.clone();
22481 current_line_len = subsequent_lines_prefix_len;
22482 } else if have_preceding_whitespace {
22483 continue;
22484 } else if current_line_len + 1 > wrap_column
22485 && current_line_len != current_prefix_len
22486 {
22487 wrapped_text.push_str(current_line.trim_end());
22488 wrapped_text.push('\n');
22489 is_first_line = false;
22490 current_line = subsequent_lines_prefix.clone();
22491 current_line_len = subsequent_lines_prefix_len;
22492 } else if current_line_len != current_prefix_len {
22493 current_line.push(' ');
22494 current_line_len += 1;
22495 }
22496 }
22497 }
22498 }
22499
22500 if !current_line.is_empty() {
22501 wrapped_text.push_str(¤t_line);
22502 }
22503 wrapped_text
22504}
22505
22506#[test]
22507fn test_wrap_with_prefix() {
22508 assert_eq!(
22509 wrap_with_prefix(
22510 "# ".to_string(),
22511 "# ".to_string(),
22512 "abcdefg".to_string(),
22513 4,
22514 NonZeroU32::new(4).unwrap(),
22515 false,
22516 ),
22517 "# abcdefg"
22518 );
22519 assert_eq!(
22520 wrap_with_prefix(
22521 "".to_string(),
22522 "".to_string(),
22523 "\thello world".to_string(),
22524 8,
22525 NonZeroU32::new(4).unwrap(),
22526 false,
22527 ),
22528 "hello\nworld"
22529 );
22530 assert_eq!(
22531 wrap_with_prefix(
22532 "// ".to_string(),
22533 "// ".to_string(),
22534 "xx \nyy zz aa bb cc".to_string(),
22535 12,
22536 NonZeroU32::new(4).unwrap(),
22537 false,
22538 ),
22539 "// xx yy zz\n// aa bb cc"
22540 );
22541 assert_eq!(
22542 wrap_with_prefix(
22543 String::new(),
22544 String::new(),
22545 "这是什么 \n 钢笔".to_string(),
22546 3,
22547 NonZeroU32::new(4).unwrap(),
22548 false,
22549 ),
22550 "这是什\n么 钢\n笔"
22551 );
22552 assert_eq!(
22553 wrap_with_prefix(
22554 String::new(),
22555 String::new(),
22556 format!("foo{}bar", '\u{2009}'), // thin space
22557 80,
22558 NonZeroU32::new(4).unwrap(),
22559 false,
22560 ),
22561 format!("foo{}bar", '\u{2009}')
22562 );
22563}
22564
22565pub trait CollaborationHub {
22566 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22567 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22568 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22569}
22570
22571impl CollaborationHub for Entity<Project> {
22572 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22573 self.read(cx).collaborators()
22574 }
22575
22576 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22577 self.read(cx).user_store().read(cx).participant_indices()
22578 }
22579
22580 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22581 let this = self.read(cx);
22582 let user_ids = this.collaborators().values().map(|c| c.user_id);
22583 this.user_store().read(cx).participant_names(user_ids, cx)
22584 }
22585}
22586
22587pub trait SemanticsProvider {
22588 fn hover(
22589 &self,
22590 buffer: &Entity<Buffer>,
22591 position: text::Anchor,
22592 cx: &mut App,
22593 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22594
22595 fn inline_values(
22596 &self,
22597 buffer_handle: Entity<Buffer>,
22598 range: Range<text::Anchor>,
22599 cx: &mut App,
22600 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22601
22602 fn inlay_hints(
22603 &self,
22604 buffer_handle: Entity<Buffer>,
22605 range: Range<text::Anchor>,
22606 cx: &mut App,
22607 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22608
22609 fn resolve_inlay_hint(
22610 &self,
22611 hint: InlayHint,
22612 buffer_handle: Entity<Buffer>,
22613 server_id: LanguageServerId,
22614 cx: &mut App,
22615 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22616
22617 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22618
22619 fn document_highlights(
22620 &self,
22621 buffer: &Entity<Buffer>,
22622 position: text::Anchor,
22623 cx: &mut App,
22624 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22625
22626 fn definitions(
22627 &self,
22628 buffer: &Entity<Buffer>,
22629 position: text::Anchor,
22630 kind: GotoDefinitionKind,
22631 cx: &mut App,
22632 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22633
22634 fn range_for_rename(
22635 &self,
22636 buffer: &Entity<Buffer>,
22637 position: text::Anchor,
22638 cx: &mut App,
22639 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22640
22641 fn perform_rename(
22642 &self,
22643 buffer: &Entity<Buffer>,
22644 position: text::Anchor,
22645 new_name: String,
22646 cx: &mut App,
22647 ) -> Option<Task<Result<ProjectTransaction>>>;
22648}
22649
22650pub trait CompletionProvider {
22651 fn completions(
22652 &self,
22653 excerpt_id: ExcerptId,
22654 buffer: &Entity<Buffer>,
22655 buffer_position: text::Anchor,
22656 trigger: CompletionContext,
22657 window: &mut Window,
22658 cx: &mut Context<Editor>,
22659 ) -> Task<Result<Vec<CompletionResponse>>>;
22660
22661 fn resolve_completions(
22662 &self,
22663 _buffer: Entity<Buffer>,
22664 _completion_indices: Vec<usize>,
22665 _completions: Rc<RefCell<Box<[Completion]>>>,
22666 _cx: &mut Context<Editor>,
22667 ) -> Task<Result<bool>> {
22668 Task::ready(Ok(false))
22669 }
22670
22671 fn apply_additional_edits_for_completion(
22672 &self,
22673 _buffer: Entity<Buffer>,
22674 _completions: Rc<RefCell<Box<[Completion]>>>,
22675 _completion_index: usize,
22676 _push_to_history: bool,
22677 _cx: &mut Context<Editor>,
22678 ) -> Task<Result<Option<language::Transaction>>> {
22679 Task::ready(Ok(None))
22680 }
22681
22682 fn is_completion_trigger(
22683 &self,
22684 buffer: &Entity<Buffer>,
22685 position: language::Anchor,
22686 text: &str,
22687 trigger_in_words: bool,
22688 menu_is_open: bool,
22689 cx: &mut Context<Editor>,
22690 ) -> bool;
22691
22692 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22693
22694 fn sort_completions(&self) -> bool {
22695 true
22696 }
22697
22698 fn filter_completions(&self) -> bool {
22699 true
22700 }
22701}
22702
22703pub trait CodeActionProvider {
22704 fn id(&self) -> Arc<str>;
22705
22706 fn code_actions(
22707 &self,
22708 buffer: &Entity<Buffer>,
22709 range: Range<text::Anchor>,
22710 window: &mut Window,
22711 cx: &mut App,
22712 ) -> Task<Result<Vec<CodeAction>>>;
22713
22714 fn apply_code_action(
22715 &self,
22716 buffer_handle: Entity<Buffer>,
22717 action: CodeAction,
22718 excerpt_id: ExcerptId,
22719 push_to_history: bool,
22720 window: &mut Window,
22721 cx: &mut App,
22722 ) -> Task<Result<ProjectTransaction>>;
22723}
22724
22725impl CodeActionProvider for Entity<Project> {
22726 fn id(&self) -> Arc<str> {
22727 "project".into()
22728 }
22729
22730 fn code_actions(
22731 &self,
22732 buffer: &Entity<Buffer>,
22733 range: Range<text::Anchor>,
22734 _window: &mut Window,
22735 cx: &mut App,
22736 ) -> Task<Result<Vec<CodeAction>>> {
22737 self.update(cx, |project, cx| {
22738 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22739 let code_actions = project.code_actions(buffer, range, None, cx);
22740 cx.background_spawn(async move {
22741 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22742 Ok(code_lens_actions
22743 .context("code lens fetch")?
22744 .into_iter()
22745 .flatten()
22746 .chain(
22747 code_actions
22748 .context("code action fetch")?
22749 .into_iter()
22750 .flatten(),
22751 )
22752 .collect())
22753 })
22754 })
22755 }
22756
22757 fn apply_code_action(
22758 &self,
22759 buffer_handle: Entity<Buffer>,
22760 action: CodeAction,
22761 _excerpt_id: ExcerptId,
22762 push_to_history: bool,
22763 _window: &mut Window,
22764 cx: &mut App,
22765 ) -> Task<Result<ProjectTransaction>> {
22766 self.update(cx, |project, cx| {
22767 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22768 })
22769 }
22770}
22771
22772fn snippet_completions(
22773 project: &Project,
22774 buffer: &Entity<Buffer>,
22775 buffer_position: text::Anchor,
22776 cx: &mut App,
22777) -> Task<Result<CompletionResponse>> {
22778 let languages = buffer.read(cx).languages_at(buffer_position);
22779 let snippet_store = project.snippets().read(cx);
22780
22781 let scopes: Vec<_> = languages
22782 .iter()
22783 .filter_map(|language| {
22784 let language_name = language.lsp_id();
22785 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22786
22787 if snippets.is_empty() {
22788 None
22789 } else {
22790 Some((language.default_scope(), snippets))
22791 }
22792 })
22793 .collect();
22794
22795 if scopes.is_empty() {
22796 return Task::ready(Ok(CompletionResponse {
22797 completions: vec![],
22798 display_options: CompletionDisplayOptions::default(),
22799 is_incomplete: false,
22800 }));
22801 }
22802
22803 let snapshot = buffer.read(cx).text_snapshot();
22804 let executor = cx.background_executor().clone();
22805
22806 cx.background_spawn(async move {
22807 let mut is_incomplete = false;
22808 let mut completions: Vec<Completion> = Vec::new();
22809 for (scope, snippets) in scopes.into_iter() {
22810 let classifier =
22811 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22812
22813 const MAX_WORD_PREFIX_LEN: usize = 128;
22814 let last_word: String = snapshot
22815 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22816 .take(MAX_WORD_PREFIX_LEN)
22817 .take_while(|c| classifier.is_word(*c))
22818 .collect::<String>()
22819 .chars()
22820 .rev()
22821 .collect();
22822
22823 if last_word.is_empty() {
22824 return Ok(CompletionResponse {
22825 completions: vec![],
22826 display_options: CompletionDisplayOptions::default(),
22827 is_incomplete: true,
22828 });
22829 }
22830
22831 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22832 let to_lsp = |point: &text::Anchor| {
22833 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22834 point_to_lsp(end)
22835 };
22836 let lsp_end = to_lsp(&buffer_position);
22837
22838 let candidates = snippets
22839 .iter()
22840 .enumerate()
22841 .flat_map(|(ix, snippet)| {
22842 snippet
22843 .prefix
22844 .iter()
22845 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22846 })
22847 .collect::<Vec<StringMatchCandidate>>();
22848
22849 const MAX_RESULTS: usize = 100;
22850 let mut matches = fuzzy::match_strings(
22851 &candidates,
22852 &last_word,
22853 last_word.chars().any(|c| c.is_uppercase()),
22854 true,
22855 MAX_RESULTS,
22856 &Default::default(),
22857 executor.clone(),
22858 )
22859 .await;
22860
22861 if matches.len() >= MAX_RESULTS {
22862 is_incomplete = true;
22863 }
22864
22865 // Remove all candidates where the query's start does not match the start of any word in the candidate
22866 if let Some(query_start) = last_word.chars().next() {
22867 matches.retain(|string_match| {
22868 split_words(&string_match.string).any(|word| {
22869 // Check that the first codepoint of the word as lowercase matches the first
22870 // codepoint of the query as lowercase
22871 word.chars()
22872 .flat_map(|codepoint| codepoint.to_lowercase())
22873 .zip(query_start.to_lowercase())
22874 .all(|(word_cp, query_cp)| word_cp == query_cp)
22875 })
22876 });
22877 }
22878
22879 let matched_strings = matches
22880 .into_iter()
22881 .map(|m| m.string)
22882 .collect::<HashSet<_>>();
22883
22884 completions.extend(snippets.iter().filter_map(|snippet| {
22885 let matching_prefix = snippet
22886 .prefix
22887 .iter()
22888 .find(|prefix| matched_strings.contains(*prefix))?;
22889 let start = as_offset - last_word.len();
22890 let start = snapshot.anchor_before(start);
22891 let range = start..buffer_position;
22892 let lsp_start = to_lsp(&start);
22893 let lsp_range = lsp::Range {
22894 start: lsp_start,
22895 end: lsp_end,
22896 };
22897 Some(Completion {
22898 replace_range: range,
22899 new_text: snippet.body.clone(),
22900 source: CompletionSource::Lsp {
22901 insert_range: None,
22902 server_id: LanguageServerId(usize::MAX),
22903 resolved: true,
22904 lsp_completion: Box::new(lsp::CompletionItem {
22905 label: snippet.prefix.first().unwrap().clone(),
22906 kind: Some(CompletionItemKind::SNIPPET),
22907 label_details: snippet.description.as_ref().map(|description| {
22908 lsp::CompletionItemLabelDetails {
22909 detail: Some(description.clone()),
22910 description: None,
22911 }
22912 }),
22913 insert_text_format: Some(InsertTextFormat::SNIPPET),
22914 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22915 lsp::InsertReplaceEdit {
22916 new_text: snippet.body.clone(),
22917 insert: lsp_range,
22918 replace: lsp_range,
22919 },
22920 )),
22921 filter_text: Some(snippet.body.clone()),
22922 sort_text: Some(char::MAX.to_string()),
22923 ..lsp::CompletionItem::default()
22924 }),
22925 lsp_defaults: None,
22926 },
22927 label: CodeLabel {
22928 text: matching_prefix.clone(),
22929 runs: Vec::new(),
22930 filter_range: 0..matching_prefix.len(),
22931 },
22932 icon_path: None,
22933 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22934 single_line: snippet.name.clone().into(),
22935 plain_text: snippet
22936 .description
22937 .clone()
22938 .map(|description| description.into()),
22939 }),
22940 insert_text_mode: None,
22941 confirm: None,
22942 })
22943 }))
22944 }
22945
22946 Ok(CompletionResponse {
22947 completions,
22948 display_options: CompletionDisplayOptions::default(),
22949 is_incomplete,
22950 })
22951 })
22952}
22953
22954impl CompletionProvider for Entity<Project> {
22955 fn completions(
22956 &self,
22957 _excerpt_id: ExcerptId,
22958 buffer: &Entity<Buffer>,
22959 buffer_position: text::Anchor,
22960 options: CompletionContext,
22961 _window: &mut Window,
22962 cx: &mut Context<Editor>,
22963 ) -> Task<Result<Vec<CompletionResponse>>> {
22964 self.update(cx, |project, cx| {
22965 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22966 let project_completions = project.completions(buffer, buffer_position, options, cx);
22967 cx.background_spawn(async move {
22968 let mut responses = project_completions.await?;
22969 let snippets = snippets.await?;
22970 if !snippets.completions.is_empty() {
22971 responses.push(snippets);
22972 }
22973 Ok(responses)
22974 })
22975 })
22976 }
22977
22978 fn resolve_completions(
22979 &self,
22980 buffer: Entity<Buffer>,
22981 completion_indices: Vec<usize>,
22982 completions: Rc<RefCell<Box<[Completion]>>>,
22983 cx: &mut Context<Editor>,
22984 ) -> Task<Result<bool>> {
22985 self.update(cx, |project, cx| {
22986 project.lsp_store().update(cx, |lsp_store, cx| {
22987 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22988 })
22989 })
22990 }
22991
22992 fn apply_additional_edits_for_completion(
22993 &self,
22994 buffer: Entity<Buffer>,
22995 completions: Rc<RefCell<Box<[Completion]>>>,
22996 completion_index: usize,
22997 push_to_history: bool,
22998 cx: &mut Context<Editor>,
22999 ) -> Task<Result<Option<language::Transaction>>> {
23000 self.update(cx, |project, cx| {
23001 project.lsp_store().update(cx, |lsp_store, cx| {
23002 lsp_store.apply_additional_edits_for_completion(
23003 buffer,
23004 completions,
23005 completion_index,
23006 push_to_history,
23007 cx,
23008 )
23009 })
23010 })
23011 }
23012
23013 fn is_completion_trigger(
23014 &self,
23015 buffer: &Entity<Buffer>,
23016 position: language::Anchor,
23017 text: &str,
23018 trigger_in_words: bool,
23019 menu_is_open: bool,
23020 cx: &mut Context<Editor>,
23021 ) -> bool {
23022 let mut chars = text.chars();
23023 let char = if let Some(char) = chars.next() {
23024 char
23025 } else {
23026 return false;
23027 };
23028 if chars.next().is_some() {
23029 return false;
23030 }
23031
23032 let buffer = buffer.read(cx);
23033 let snapshot = buffer.snapshot();
23034 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23035 return false;
23036 }
23037 let classifier = snapshot
23038 .char_classifier_at(position)
23039 .scope_context(Some(CharScopeContext::Completion));
23040 if trigger_in_words && classifier.is_word(char) {
23041 return true;
23042 }
23043
23044 buffer.completion_triggers().contains(text)
23045 }
23046}
23047
23048impl SemanticsProvider for Entity<Project> {
23049 fn hover(
23050 &self,
23051 buffer: &Entity<Buffer>,
23052 position: text::Anchor,
23053 cx: &mut App,
23054 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23055 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23056 }
23057
23058 fn document_highlights(
23059 &self,
23060 buffer: &Entity<Buffer>,
23061 position: text::Anchor,
23062 cx: &mut App,
23063 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23064 Some(self.update(cx, |project, cx| {
23065 project.document_highlights(buffer, position, cx)
23066 }))
23067 }
23068
23069 fn definitions(
23070 &self,
23071 buffer: &Entity<Buffer>,
23072 position: text::Anchor,
23073 kind: GotoDefinitionKind,
23074 cx: &mut App,
23075 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23076 Some(self.update(cx, |project, cx| match kind {
23077 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23078 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23079 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23080 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23081 }))
23082 }
23083
23084 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23085 self.update(cx, |project, cx| {
23086 if project
23087 .active_debug_session(cx)
23088 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23089 {
23090 return true;
23091 }
23092
23093 buffer.update(cx, |buffer, cx| {
23094 project.any_language_server_supports_inlay_hints(buffer, cx)
23095 })
23096 })
23097 }
23098
23099 fn inline_values(
23100 &self,
23101 buffer_handle: Entity<Buffer>,
23102 range: Range<text::Anchor>,
23103 cx: &mut App,
23104 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23105 self.update(cx, |project, cx| {
23106 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23107
23108 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23109 })
23110 }
23111
23112 fn inlay_hints(
23113 &self,
23114 buffer_handle: Entity<Buffer>,
23115 range: Range<text::Anchor>,
23116 cx: &mut App,
23117 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23118 Some(self.update(cx, |project, cx| {
23119 project.inlay_hints(buffer_handle, range, cx)
23120 }))
23121 }
23122
23123 fn resolve_inlay_hint(
23124 &self,
23125 hint: InlayHint,
23126 buffer_handle: Entity<Buffer>,
23127 server_id: LanguageServerId,
23128 cx: &mut App,
23129 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23130 Some(self.update(cx, |project, cx| {
23131 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23132 }))
23133 }
23134
23135 fn range_for_rename(
23136 &self,
23137 buffer: &Entity<Buffer>,
23138 position: text::Anchor,
23139 cx: &mut App,
23140 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23141 Some(self.update(cx, |project, cx| {
23142 let buffer = buffer.clone();
23143 let task = project.prepare_rename(buffer.clone(), position, cx);
23144 cx.spawn(async move |_, cx| {
23145 Ok(match task.await? {
23146 PrepareRenameResponse::Success(range) => Some(range),
23147 PrepareRenameResponse::InvalidPosition => None,
23148 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23149 // Fallback on using TreeSitter info to determine identifier range
23150 buffer.read_with(cx, |buffer, _| {
23151 let snapshot = buffer.snapshot();
23152 let (range, kind) = snapshot.surrounding_word(position, None);
23153 if kind != Some(CharKind::Word) {
23154 return None;
23155 }
23156 Some(
23157 snapshot.anchor_before(range.start)
23158 ..snapshot.anchor_after(range.end),
23159 )
23160 })?
23161 }
23162 })
23163 })
23164 }))
23165 }
23166
23167 fn perform_rename(
23168 &self,
23169 buffer: &Entity<Buffer>,
23170 position: text::Anchor,
23171 new_name: String,
23172 cx: &mut App,
23173 ) -> Option<Task<Result<ProjectTransaction>>> {
23174 Some(self.update(cx, |project, cx| {
23175 project.perform_rename(buffer.clone(), position, new_name, cx)
23176 }))
23177 }
23178}
23179
23180fn inlay_hint_settings(
23181 location: Anchor,
23182 snapshot: &MultiBufferSnapshot,
23183 cx: &mut Context<Editor>,
23184) -> InlayHintSettings {
23185 let file = snapshot.file_at(location);
23186 let language = snapshot.language_at(location).map(|l| l.name());
23187 language_settings(language, file, cx).inlay_hints
23188}
23189
23190fn consume_contiguous_rows(
23191 contiguous_row_selections: &mut Vec<Selection<Point>>,
23192 selection: &Selection<Point>,
23193 display_map: &DisplaySnapshot,
23194 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23195) -> (MultiBufferRow, MultiBufferRow) {
23196 contiguous_row_selections.push(selection.clone());
23197 let start_row = starting_row(selection, display_map);
23198 let mut end_row = ending_row(selection, display_map);
23199
23200 while let Some(next_selection) = selections.peek() {
23201 if next_selection.start.row <= end_row.0 {
23202 end_row = ending_row(next_selection, display_map);
23203 contiguous_row_selections.push(selections.next().unwrap().clone());
23204 } else {
23205 break;
23206 }
23207 }
23208 (start_row, end_row)
23209}
23210
23211fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23212 if selection.start.column > 0 {
23213 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23214 } else {
23215 MultiBufferRow(selection.start.row)
23216 }
23217}
23218
23219fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23220 if next_selection.end.column > 0 || next_selection.is_empty() {
23221 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23222 } else {
23223 MultiBufferRow(next_selection.end.row)
23224 }
23225}
23226
23227impl EditorSnapshot {
23228 pub fn remote_selections_in_range<'a>(
23229 &'a self,
23230 range: &'a Range<Anchor>,
23231 collaboration_hub: &dyn CollaborationHub,
23232 cx: &'a App,
23233 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23234 let participant_names = collaboration_hub.user_names(cx);
23235 let participant_indices = collaboration_hub.user_participant_indices(cx);
23236 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23237 let collaborators_by_replica_id = collaborators_by_peer_id
23238 .values()
23239 .map(|collaborator| (collaborator.replica_id, collaborator))
23240 .collect::<HashMap<_, _>>();
23241 self.buffer_snapshot()
23242 .selections_in_range(range, false)
23243 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23244 if replica_id == AGENT_REPLICA_ID {
23245 Some(RemoteSelection {
23246 replica_id,
23247 selection,
23248 cursor_shape,
23249 line_mode,
23250 collaborator_id: CollaboratorId::Agent,
23251 user_name: Some("Agent".into()),
23252 color: cx.theme().players().agent(),
23253 })
23254 } else {
23255 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23256 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23257 let user_name = participant_names.get(&collaborator.user_id).cloned();
23258 Some(RemoteSelection {
23259 replica_id,
23260 selection,
23261 cursor_shape,
23262 line_mode,
23263 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23264 user_name,
23265 color: if let Some(index) = participant_index {
23266 cx.theme().players().color_for_participant(index.0)
23267 } else {
23268 cx.theme().players().absent()
23269 },
23270 })
23271 }
23272 })
23273 }
23274
23275 pub fn hunks_for_ranges(
23276 &self,
23277 ranges: impl IntoIterator<Item = Range<Point>>,
23278 ) -> Vec<MultiBufferDiffHunk> {
23279 let mut hunks = Vec::new();
23280 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23281 HashMap::default();
23282 for query_range in ranges {
23283 let query_rows =
23284 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23285 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23286 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23287 ) {
23288 // Include deleted hunks that are adjacent to the query range, because
23289 // otherwise they would be missed.
23290 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23291 if hunk.status().is_deleted() {
23292 intersects_range |= hunk.row_range.start == query_rows.end;
23293 intersects_range |= hunk.row_range.end == query_rows.start;
23294 }
23295 if intersects_range {
23296 if !processed_buffer_rows
23297 .entry(hunk.buffer_id)
23298 .or_default()
23299 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23300 {
23301 continue;
23302 }
23303 hunks.push(hunk);
23304 }
23305 }
23306 }
23307
23308 hunks
23309 }
23310
23311 fn display_diff_hunks_for_rows<'a>(
23312 &'a self,
23313 display_rows: Range<DisplayRow>,
23314 folded_buffers: &'a HashSet<BufferId>,
23315 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23316 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23317 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23318
23319 self.buffer_snapshot()
23320 .diff_hunks_in_range(buffer_start..buffer_end)
23321 .filter_map(|hunk| {
23322 if folded_buffers.contains(&hunk.buffer_id) {
23323 return None;
23324 }
23325
23326 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23327 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23328
23329 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23330 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23331
23332 let display_hunk = if hunk_display_start.column() != 0 {
23333 DisplayDiffHunk::Folded {
23334 display_row: hunk_display_start.row(),
23335 }
23336 } else {
23337 let mut end_row = hunk_display_end.row();
23338 if hunk_display_end.column() > 0 {
23339 end_row.0 += 1;
23340 }
23341 let is_created_file = hunk.is_created_file();
23342 DisplayDiffHunk::Unfolded {
23343 status: hunk.status(),
23344 diff_base_byte_range: hunk.diff_base_byte_range,
23345 display_row_range: hunk_display_start.row()..end_row,
23346 multi_buffer_range: Anchor::range_in_buffer(
23347 hunk.excerpt_id,
23348 hunk.buffer_id,
23349 hunk.buffer_range,
23350 ),
23351 is_created_file,
23352 }
23353 };
23354
23355 Some(display_hunk)
23356 })
23357 }
23358
23359 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23360 self.display_snapshot
23361 .buffer_snapshot()
23362 .language_at(position)
23363 }
23364
23365 pub fn is_focused(&self) -> bool {
23366 self.is_focused
23367 }
23368
23369 pub fn placeholder_text(&self) -> Option<String> {
23370 self.placeholder_display_snapshot
23371 .as_ref()
23372 .map(|display_map| display_map.text())
23373 }
23374
23375 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23376 self.scroll_anchor.scroll_position(&self.display_snapshot)
23377 }
23378
23379 fn gutter_dimensions(
23380 &self,
23381 font_id: FontId,
23382 font_size: Pixels,
23383 max_line_number_width: Pixels,
23384 cx: &App,
23385 ) -> Option<GutterDimensions> {
23386 if !self.show_gutter {
23387 return None;
23388 }
23389
23390 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23391 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23392
23393 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23394 matches!(
23395 ProjectSettings::get_global(cx).git.git_gutter,
23396 GitGutterSetting::TrackedFiles
23397 )
23398 });
23399 let gutter_settings = EditorSettings::get_global(cx).gutter;
23400 let show_line_numbers = self
23401 .show_line_numbers
23402 .unwrap_or(gutter_settings.line_numbers);
23403 let line_gutter_width = if show_line_numbers {
23404 // Avoid flicker-like gutter resizes when the line number gains another digit by
23405 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23406 let min_width_for_number_on_gutter =
23407 ch_advance * gutter_settings.min_line_number_digits as f32;
23408 max_line_number_width.max(min_width_for_number_on_gutter)
23409 } else {
23410 0.0.into()
23411 };
23412
23413 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23414 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23415
23416 let git_blame_entries_width =
23417 self.git_blame_gutter_max_author_length
23418 .map(|max_author_length| {
23419 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23420 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23421
23422 /// The number of characters to dedicate to gaps and margins.
23423 const SPACING_WIDTH: usize = 4;
23424
23425 let max_char_count = max_author_length.min(renderer.max_author_length())
23426 + ::git::SHORT_SHA_LENGTH
23427 + MAX_RELATIVE_TIMESTAMP.len()
23428 + SPACING_WIDTH;
23429
23430 ch_advance * max_char_count
23431 });
23432
23433 let is_singleton = self.buffer_snapshot().is_singleton();
23434
23435 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23436 left_padding += if !is_singleton {
23437 ch_width * 4.0
23438 } else if show_runnables || show_breakpoints {
23439 ch_width * 3.0
23440 } else if show_git_gutter && show_line_numbers {
23441 ch_width * 2.0
23442 } else if show_git_gutter || show_line_numbers {
23443 ch_width
23444 } else {
23445 px(0.)
23446 };
23447
23448 let shows_folds = is_singleton && gutter_settings.folds;
23449
23450 let right_padding = if shows_folds && show_line_numbers {
23451 ch_width * 4.0
23452 } else if shows_folds || (!is_singleton && show_line_numbers) {
23453 ch_width * 3.0
23454 } else if show_line_numbers {
23455 ch_width
23456 } else {
23457 px(0.)
23458 };
23459
23460 Some(GutterDimensions {
23461 left_padding,
23462 right_padding,
23463 width: line_gutter_width + left_padding + right_padding,
23464 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23465 git_blame_entries_width,
23466 })
23467 }
23468
23469 pub fn render_crease_toggle(
23470 &self,
23471 buffer_row: MultiBufferRow,
23472 row_contains_cursor: bool,
23473 editor: Entity<Editor>,
23474 window: &mut Window,
23475 cx: &mut App,
23476 ) -> Option<AnyElement> {
23477 let folded = self.is_line_folded(buffer_row);
23478 let mut is_foldable = false;
23479
23480 if let Some(crease) = self
23481 .crease_snapshot
23482 .query_row(buffer_row, self.buffer_snapshot())
23483 {
23484 is_foldable = true;
23485 match crease {
23486 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23487 if let Some(render_toggle) = render_toggle {
23488 let toggle_callback =
23489 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23490 if folded {
23491 editor.update(cx, |editor, cx| {
23492 editor.fold_at(buffer_row, window, cx)
23493 });
23494 } else {
23495 editor.update(cx, |editor, cx| {
23496 editor.unfold_at(buffer_row, window, cx)
23497 });
23498 }
23499 });
23500 return Some((render_toggle)(
23501 buffer_row,
23502 folded,
23503 toggle_callback,
23504 window,
23505 cx,
23506 ));
23507 }
23508 }
23509 }
23510 }
23511
23512 is_foldable |= self.starts_indent(buffer_row);
23513
23514 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23515 Some(
23516 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23517 .toggle_state(folded)
23518 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23519 if folded {
23520 this.unfold_at(buffer_row, window, cx);
23521 } else {
23522 this.fold_at(buffer_row, window, cx);
23523 }
23524 }))
23525 .into_any_element(),
23526 )
23527 } else {
23528 None
23529 }
23530 }
23531
23532 pub fn render_crease_trailer(
23533 &self,
23534 buffer_row: MultiBufferRow,
23535 window: &mut Window,
23536 cx: &mut App,
23537 ) -> Option<AnyElement> {
23538 let folded = self.is_line_folded(buffer_row);
23539 if let Crease::Inline { render_trailer, .. } = self
23540 .crease_snapshot
23541 .query_row(buffer_row, self.buffer_snapshot())?
23542 {
23543 let render_trailer = render_trailer.as_ref()?;
23544 Some(render_trailer(buffer_row, folded, window, cx))
23545 } else {
23546 None
23547 }
23548 }
23549}
23550
23551impl Deref for EditorSnapshot {
23552 type Target = DisplaySnapshot;
23553
23554 fn deref(&self) -> &Self::Target {
23555 &self.display_snapshot
23556 }
23557}
23558
23559#[derive(Clone, Debug, PartialEq, Eq)]
23560pub enum EditorEvent {
23561 InputIgnored {
23562 text: Arc<str>,
23563 },
23564 InputHandled {
23565 utf16_range_to_replace: Option<Range<isize>>,
23566 text: Arc<str>,
23567 },
23568 ExcerptsAdded {
23569 buffer: Entity<Buffer>,
23570 predecessor: ExcerptId,
23571 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23572 },
23573 ExcerptsRemoved {
23574 ids: Vec<ExcerptId>,
23575 removed_buffer_ids: Vec<BufferId>,
23576 },
23577 BufferFoldToggled {
23578 ids: Vec<ExcerptId>,
23579 folded: bool,
23580 },
23581 ExcerptsEdited {
23582 ids: Vec<ExcerptId>,
23583 },
23584 ExcerptsExpanded {
23585 ids: Vec<ExcerptId>,
23586 },
23587 BufferEdited,
23588 Edited {
23589 transaction_id: clock::Lamport,
23590 },
23591 Reparsed(BufferId),
23592 Focused,
23593 FocusedIn,
23594 Blurred,
23595 DirtyChanged,
23596 Saved,
23597 TitleChanged,
23598 SelectionsChanged {
23599 local: bool,
23600 },
23601 ScrollPositionChanged {
23602 local: bool,
23603 autoscroll: bool,
23604 },
23605 TransactionUndone {
23606 transaction_id: clock::Lamport,
23607 },
23608 TransactionBegun {
23609 transaction_id: clock::Lamport,
23610 },
23611 CursorShapeChanged,
23612 BreadcrumbsChanged,
23613 PushedToNavHistory {
23614 anchor: Anchor,
23615 is_deactivate: bool,
23616 },
23617}
23618
23619impl EventEmitter<EditorEvent> for Editor {}
23620
23621impl Focusable for Editor {
23622 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23623 self.focus_handle.clone()
23624 }
23625}
23626
23627impl Render for Editor {
23628 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23629 let settings = ThemeSettings::get_global(cx);
23630
23631 let mut text_style = match self.mode {
23632 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23633 color: cx.theme().colors().editor_foreground,
23634 font_family: settings.ui_font.family.clone(),
23635 font_features: settings.ui_font.features.clone(),
23636 font_fallbacks: settings.ui_font.fallbacks.clone(),
23637 font_size: rems(0.875).into(),
23638 font_weight: settings.ui_font.weight,
23639 line_height: relative(settings.buffer_line_height.value()),
23640 ..Default::default()
23641 },
23642 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23643 color: cx.theme().colors().editor_foreground,
23644 font_family: settings.buffer_font.family.clone(),
23645 font_features: settings.buffer_font.features.clone(),
23646 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23647 font_size: settings.buffer_font_size(cx).into(),
23648 font_weight: settings.buffer_font.weight,
23649 line_height: relative(settings.buffer_line_height.value()),
23650 ..Default::default()
23651 },
23652 };
23653 if let Some(text_style_refinement) = &self.text_style_refinement {
23654 text_style.refine(text_style_refinement)
23655 }
23656
23657 let background = match self.mode {
23658 EditorMode::SingleLine => cx.theme().system().transparent,
23659 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23660 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23661 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23662 };
23663
23664 EditorElement::new(
23665 &cx.entity(),
23666 EditorStyle {
23667 background,
23668 border: cx.theme().colors().border,
23669 local_player: cx.theme().players().local(),
23670 text: text_style,
23671 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23672 syntax: cx.theme().syntax().clone(),
23673 status: cx.theme().status().clone(),
23674 inlay_hints_style: make_inlay_hints_style(cx),
23675 edit_prediction_styles: make_suggestion_styles(cx),
23676 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23677 show_underlines: self.diagnostics_enabled(),
23678 },
23679 )
23680 }
23681}
23682
23683impl EntityInputHandler for Editor {
23684 fn text_for_range(
23685 &mut self,
23686 range_utf16: Range<usize>,
23687 adjusted_range: &mut Option<Range<usize>>,
23688 _: &mut Window,
23689 cx: &mut Context<Self>,
23690 ) -> Option<String> {
23691 let snapshot = self.buffer.read(cx).read(cx);
23692 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23693 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23694 if (start.0..end.0) != range_utf16 {
23695 adjusted_range.replace(start.0..end.0);
23696 }
23697 Some(snapshot.text_for_range(start..end).collect())
23698 }
23699
23700 fn selected_text_range(
23701 &mut self,
23702 ignore_disabled_input: bool,
23703 _: &mut Window,
23704 cx: &mut Context<Self>,
23705 ) -> Option<UTF16Selection> {
23706 // Prevent the IME menu from appearing when holding down an alphabetic key
23707 // while input is disabled.
23708 if !ignore_disabled_input && !self.input_enabled {
23709 return None;
23710 }
23711
23712 let selection = self.selections.newest::<OffsetUtf16>(cx);
23713 let range = selection.range();
23714
23715 Some(UTF16Selection {
23716 range: range.start.0..range.end.0,
23717 reversed: selection.reversed,
23718 })
23719 }
23720
23721 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23722 let snapshot = self.buffer.read(cx).read(cx);
23723 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23724 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23725 }
23726
23727 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23728 self.clear_highlights::<InputComposition>(cx);
23729 self.ime_transaction.take();
23730 }
23731
23732 fn replace_text_in_range(
23733 &mut self,
23734 range_utf16: Option<Range<usize>>,
23735 text: &str,
23736 window: &mut Window,
23737 cx: &mut Context<Self>,
23738 ) {
23739 if !self.input_enabled {
23740 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23741 return;
23742 }
23743
23744 self.transact(window, cx, |this, window, cx| {
23745 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23746 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23747 Some(this.selection_replacement_ranges(range_utf16, cx))
23748 } else {
23749 this.marked_text_ranges(cx)
23750 };
23751
23752 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23753 let newest_selection_id = this.selections.newest_anchor().id;
23754 this.selections
23755 .all::<OffsetUtf16>(cx)
23756 .iter()
23757 .zip(ranges_to_replace.iter())
23758 .find_map(|(selection, range)| {
23759 if selection.id == newest_selection_id {
23760 Some(
23761 (range.start.0 as isize - selection.head().0 as isize)
23762 ..(range.end.0 as isize - selection.head().0 as isize),
23763 )
23764 } else {
23765 None
23766 }
23767 })
23768 });
23769
23770 cx.emit(EditorEvent::InputHandled {
23771 utf16_range_to_replace: range_to_replace,
23772 text: text.into(),
23773 });
23774
23775 if let Some(new_selected_ranges) = new_selected_ranges {
23776 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23777 selections.select_ranges(new_selected_ranges)
23778 });
23779 this.backspace(&Default::default(), window, cx);
23780 }
23781
23782 this.handle_input(text, window, cx);
23783 });
23784
23785 if let Some(transaction) = self.ime_transaction {
23786 self.buffer.update(cx, |buffer, cx| {
23787 buffer.group_until_transaction(transaction, cx);
23788 });
23789 }
23790
23791 self.unmark_text(window, cx);
23792 }
23793
23794 fn replace_and_mark_text_in_range(
23795 &mut self,
23796 range_utf16: Option<Range<usize>>,
23797 text: &str,
23798 new_selected_range_utf16: Option<Range<usize>>,
23799 window: &mut Window,
23800 cx: &mut Context<Self>,
23801 ) {
23802 if !self.input_enabled {
23803 return;
23804 }
23805
23806 let transaction = self.transact(window, cx, |this, window, cx| {
23807 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23808 let snapshot = this.buffer.read(cx).read(cx);
23809 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23810 for marked_range in &mut marked_ranges {
23811 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23812 marked_range.start.0 += relative_range_utf16.start;
23813 marked_range.start =
23814 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23815 marked_range.end =
23816 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23817 }
23818 }
23819 Some(marked_ranges)
23820 } else if let Some(range_utf16) = range_utf16 {
23821 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23822 Some(this.selection_replacement_ranges(range_utf16, cx))
23823 } else {
23824 None
23825 };
23826
23827 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23828 let newest_selection_id = this.selections.newest_anchor().id;
23829 this.selections
23830 .all::<OffsetUtf16>(cx)
23831 .iter()
23832 .zip(ranges_to_replace.iter())
23833 .find_map(|(selection, range)| {
23834 if selection.id == newest_selection_id {
23835 Some(
23836 (range.start.0 as isize - selection.head().0 as isize)
23837 ..(range.end.0 as isize - selection.head().0 as isize),
23838 )
23839 } else {
23840 None
23841 }
23842 })
23843 });
23844
23845 cx.emit(EditorEvent::InputHandled {
23846 utf16_range_to_replace: range_to_replace,
23847 text: text.into(),
23848 });
23849
23850 if let Some(ranges) = ranges_to_replace {
23851 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23852 s.select_ranges(ranges)
23853 });
23854 }
23855
23856 let marked_ranges = {
23857 let snapshot = this.buffer.read(cx).read(cx);
23858 this.selections
23859 .disjoint_anchors_arc()
23860 .iter()
23861 .map(|selection| {
23862 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23863 })
23864 .collect::<Vec<_>>()
23865 };
23866
23867 if text.is_empty() {
23868 this.unmark_text(window, cx);
23869 } else {
23870 this.highlight_text::<InputComposition>(
23871 marked_ranges.clone(),
23872 HighlightStyle {
23873 underline: Some(UnderlineStyle {
23874 thickness: px(1.),
23875 color: None,
23876 wavy: false,
23877 }),
23878 ..Default::default()
23879 },
23880 cx,
23881 );
23882 }
23883
23884 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23885 let use_autoclose = this.use_autoclose;
23886 let use_auto_surround = this.use_auto_surround;
23887 this.set_use_autoclose(false);
23888 this.set_use_auto_surround(false);
23889 this.handle_input(text, window, cx);
23890 this.set_use_autoclose(use_autoclose);
23891 this.set_use_auto_surround(use_auto_surround);
23892
23893 if let Some(new_selected_range) = new_selected_range_utf16 {
23894 let snapshot = this.buffer.read(cx).read(cx);
23895 let new_selected_ranges = marked_ranges
23896 .into_iter()
23897 .map(|marked_range| {
23898 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23899 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23900 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23901 snapshot.clip_offset_utf16(new_start, Bias::Left)
23902 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23903 })
23904 .collect::<Vec<_>>();
23905
23906 drop(snapshot);
23907 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23908 selections.select_ranges(new_selected_ranges)
23909 });
23910 }
23911 });
23912
23913 self.ime_transaction = self.ime_transaction.or(transaction);
23914 if let Some(transaction) = self.ime_transaction {
23915 self.buffer.update(cx, |buffer, cx| {
23916 buffer.group_until_transaction(transaction, cx);
23917 });
23918 }
23919
23920 if self.text_highlights::<InputComposition>(cx).is_none() {
23921 self.ime_transaction.take();
23922 }
23923 }
23924
23925 fn bounds_for_range(
23926 &mut self,
23927 range_utf16: Range<usize>,
23928 element_bounds: gpui::Bounds<Pixels>,
23929 window: &mut Window,
23930 cx: &mut Context<Self>,
23931 ) -> Option<gpui::Bounds<Pixels>> {
23932 let text_layout_details = self.text_layout_details(window);
23933 let CharacterDimensions {
23934 em_width,
23935 em_advance,
23936 line_height,
23937 } = self.character_dimensions(window);
23938
23939 let snapshot = self.snapshot(window, cx);
23940 let scroll_position = snapshot.scroll_position();
23941 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23942
23943 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23944 let x = Pixels::from(
23945 ScrollOffset::from(
23946 snapshot.x_for_display_point(start, &text_layout_details)
23947 + self.gutter_dimensions.full_width(),
23948 ) - scroll_left,
23949 );
23950 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23951
23952 Some(Bounds {
23953 origin: element_bounds.origin + point(x, y),
23954 size: size(em_width, line_height),
23955 })
23956 }
23957
23958 fn character_index_for_point(
23959 &mut self,
23960 point: gpui::Point<Pixels>,
23961 _window: &mut Window,
23962 _cx: &mut Context<Self>,
23963 ) -> Option<usize> {
23964 let position_map = self.last_position_map.as_ref()?;
23965 if !position_map.text_hitbox.contains(&point) {
23966 return None;
23967 }
23968 let display_point = position_map.point_for_position(point).previous_valid;
23969 let anchor = position_map
23970 .snapshot
23971 .display_point_to_anchor(display_point, Bias::Left);
23972 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
23973 Some(utf16_offset.0)
23974 }
23975}
23976
23977trait SelectionExt {
23978 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23979 fn spanned_rows(
23980 &self,
23981 include_end_if_at_line_start: bool,
23982 map: &DisplaySnapshot,
23983 ) -> Range<MultiBufferRow>;
23984}
23985
23986impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23987 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23988 let start = self
23989 .start
23990 .to_point(map.buffer_snapshot())
23991 .to_display_point(map);
23992 let end = self
23993 .end
23994 .to_point(map.buffer_snapshot())
23995 .to_display_point(map);
23996 if self.reversed {
23997 end..start
23998 } else {
23999 start..end
24000 }
24001 }
24002
24003 fn spanned_rows(
24004 &self,
24005 include_end_if_at_line_start: bool,
24006 map: &DisplaySnapshot,
24007 ) -> Range<MultiBufferRow> {
24008 let start = self.start.to_point(map.buffer_snapshot());
24009 let mut end = self.end.to_point(map.buffer_snapshot());
24010 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24011 end.row -= 1;
24012 }
24013
24014 let buffer_start = map.prev_line_boundary(start).0;
24015 let buffer_end = map.next_line_boundary(end).0;
24016 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24017 }
24018}
24019
24020impl<T: InvalidationRegion> InvalidationStack<T> {
24021 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24022 where
24023 S: Clone + ToOffset,
24024 {
24025 while let Some(region) = self.last() {
24026 let all_selections_inside_invalidation_ranges =
24027 if selections.len() == region.ranges().len() {
24028 selections
24029 .iter()
24030 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24031 .all(|(selection, invalidation_range)| {
24032 let head = selection.head().to_offset(buffer);
24033 invalidation_range.start <= head && invalidation_range.end >= head
24034 })
24035 } else {
24036 false
24037 };
24038
24039 if all_selections_inside_invalidation_ranges {
24040 break;
24041 } else {
24042 self.pop();
24043 }
24044 }
24045 }
24046}
24047
24048impl<T> Default for InvalidationStack<T> {
24049 fn default() -> Self {
24050 Self(Default::default())
24051 }
24052}
24053
24054impl<T> Deref for InvalidationStack<T> {
24055 type Target = Vec<T>;
24056
24057 fn deref(&self) -> &Self::Target {
24058 &self.0
24059 }
24060}
24061
24062impl<T> DerefMut for InvalidationStack<T> {
24063 fn deref_mut(&mut self) -> &mut Self::Target {
24064 &mut self.0
24065 }
24066}
24067
24068impl InvalidationRegion for SnippetState {
24069 fn ranges(&self) -> &[Range<Anchor>] {
24070 &self.ranges[self.active_index]
24071 }
24072}
24073
24074fn edit_prediction_edit_text(
24075 current_snapshot: &BufferSnapshot,
24076 edits: &[(Range<Anchor>, String)],
24077 edit_preview: &EditPreview,
24078 include_deletions: bool,
24079 cx: &App,
24080) -> HighlightedText {
24081 let edits = edits
24082 .iter()
24083 .map(|(anchor, text)| {
24084 (
24085 anchor.start.text_anchor..anchor.end.text_anchor,
24086 text.clone(),
24087 )
24088 })
24089 .collect::<Vec<_>>();
24090
24091 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24092}
24093
24094fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24095 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24096 // Just show the raw edit text with basic styling
24097 let mut text = String::new();
24098 let mut highlights = Vec::new();
24099
24100 let insertion_highlight_style = HighlightStyle {
24101 color: Some(cx.theme().colors().text),
24102 ..Default::default()
24103 };
24104
24105 for (_, edit_text) in edits {
24106 let start_offset = text.len();
24107 text.push_str(edit_text);
24108 let end_offset = text.len();
24109
24110 if start_offset < end_offset {
24111 highlights.push((start_offset..end_offset, insertion_highlight_style));
24112 }
24113 }
24114
24115 HighlightedText {
24116 text: text.into(),
24117 highlights,
24118 }
24119}
24120
24121pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24122 match severity {
24123 lsp::DiagnosticSeverity::ERROR => colors.error,
24124 lsp::DiagnosticSeverity::WARNING => colors.warning,
24125 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24126 lsp::DiagnosticSeverity::HINT => colors.info,
24127 _ => colors.ignored,
24128 }
24129}
24130
24131pub fn styled_runs_for_code_label<'a>(
24132 label: &'a CodeLabel,
24133 syntax_theme: &'a theme::SyntaxTheme,
24134) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24135 let fade_out = HighlightStyle {
24136 fade_out: Some(0.35),
24137 ..Default::default()
24138 };
24139
24140 let mut prev_end = label.filter_range.end;
24141 label
24142 .runs
24143 .iter()
24144 .enumerate()
24145 .flat_map(move |(ix, (range, highlight_id))| {
24146 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24147 style
24148 } else {
24149 return Default::default();
24150 };
24151 let muted_style = style.highlight(fade_out);
24152
24153 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24154 if range.start >= label.filter_range.end {
24155 if range.start > prev_end {
24156 runs.push((prev_end..range.start, fade_out));
24157 }
24158 runs.push((range.clone(), muted_style));
24159 } else if range.end <= label.filter_range.end {
24160 runs.push((range.clone(), style));
24161 } else {
24162 runs.push((range.start..label.filter_range.end, style));
24163 runs.push((label.filter_range.end..range.end, muted_style));
24164 }
24165 prev_end = cmp::max(prev_end, range.end);
24166
24167 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24168 runs.push((prev_end..label.text.len(), fade_out));
24169 }
24170
24171 runs
24172 })
24173}
24174
24175pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24176 let mut prev_index = 0;
24177 let mut prev_codepoint: Option<char> = None;
24178 text.char_indices()
24179 .chain([(text.len(), '\0')])
24180 .filter_map(move |(index, codepoint)| {
24181 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24182 let is_boundary = index == text.len()
24183 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24184 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24185 if is_boundary {
24186 let chunk = &text[prev_index..index];
24187 prev_index = index;
24188 Some(chunk)
24189 } else {
24190 None
24191 }
24192 })
24193}
24194
24195pub trait RangeToAnchorExt: Sized {
24196 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24197
24198 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24199 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24200 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24201 }
24202}
24203
24204impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24205 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24206 let start_offset = self.start.to_offset(snapshot);
24207 let end_offset = self.end.to_offset(snapshot);
24208 if start_offset == end_offset {
24209 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24210 } else {
24211 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24212 }
24213 }
24214}
24215
24216pub trait RowExt {
24217 fn as_f64(&self) -> f64;
24218
24219 fn next_row(&self) -> Self;
24220
24221 fn previous_row(&self) -> Self;
24222
24223 fn minus(&self, other: Self) -> u32;
24224}
24225
24226impl RowExt for DisplayRow {
24227 fn as_f64(&self) -> f64 {
24228 self.0 as _
24229 }
24230
24231 fn next_row(&self) -> Self {
24232 Self(self.0 + 1)
24233 }
24234
24235 fn previous_row(&self) -> Self {
24236 Self(self.0.saturating_sub(1))
24237 }
24238
24239 fn minus(&self, other: Self) -> u32 {
24240 self.0 - other.0
24241 }
24242}
24243
24244impl RowExt for MultiBufferRow {
24245 fn as_f64(&self) -> f64 {
24246 self.0 as _
24247 }
24248
24249 fn next_row(&self) -> Self {
24250 Self(self.0 + 1)
24251 }
24252
24253 fn previous_row(&self) -> Self {
24254 Self(self.0.saturating_sub(1))
24255 }
24256
24257 fn minus(&self, other: Self) -> u32 {
24258 self.0 - other.0
24259 }
24260}
24261
24262trait RowRangeExt {
24263 type Row;
24264
24265 fn len(&self) -> usize;
24266
24267 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24268}
24269
24270impl RowRangeExt for Range<MultiBufferRow> {
24271 type Row = MultiBufferRow;
24272
24273 fn len(&self) -> usize {
24274 (self.end.0 - self.start.0) as usize
24275 }
24276
24277 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24278 (self.start.0..self.end.0).map(MultiBufferRow)
24279 }
24280}
24281
24282impl RowRangeExt for Range<DisplayRow> {
24283 type Row = DisplayRow;
24284
24285 fn len(&self) -> usize {
24286 (self.end.0 - self.start.0) as usize
24287 }
24288
24289 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24290 (self.start.0..self.end.0).map(DisplayRow)
24291 }
24292}
24293
24294/// If select range has more than one line, we
24295/// just point the cursor to range.start.
24296fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24297 if range.start.row == range.end.row {
24298 range
24299 } else {
24300 range.start..range.start
24301 }
24302}
24303pub struct KillRing(ClipboardItem);
24304impl Global for KillRing {}
24305
24306const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24307
24308enum BreakpointPromptEditAction {
24309 Log,
24310 Condition,
24311 HitCondition,
24312}
24313
24314struct BreakpointPromptEditor {
24315 pub(crate) prompt: Entity<Editor>,
24316 editor: WeakEntity<Editor>,
24317 breakpoint_anchor: Anchor,
24318 breakpoint: Breakpoint,
24319 edit_action: BreakpointPromptEditAction,
24320 block_ids: HashSet<CustomBlockId>,
24321 editor_margins: Arc<Mutex<EditorMargins>>,
24322 _subscriptions: Vec<Subscription>,
24323}
24324
24325impl BreakpointPromptEditor {
24326 const MAX_LINES: u8 = 4;
24327
24328 fn new(
24329 editor: WeakEntity<Editor>,
24330 breakpoint_anchor: Anchor,
24331 breakpoint: Breakpoint,
24332 edit_action: BreakpointPromptEditAction,
24333 window: &mut Window,
24334 cx: &mut Context<Self>,
24335 ) -> Self {
24336 let base_text = match edit_action {
24337 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24338 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24339 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24340 }
24341 .map(|msg| msg.to_string())
24342 .unwrap_or_default();
24343
24344 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24345 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24346
24347 let prompt = cx.new(|cx| {
24348 let mut prompt = Editor::new(
24349 EditorMode::AutoHeight {
24350 min_lines: 1,
24351 max_lines: Some(Self::MAX_LINES as usize),
24352 },
24353 buffer,
24354 None,
24355 window,
24356 cx,
24357 );
24358 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24359 prompt.set_show_cursor_when_unfocused(false, cx);
24360 prompt.set_placeholder_text(
24361 match edit_action {
24362 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24363 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24364 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24365 },
24366 window,
24367 cx,
24368 );
24369
24370 prompt
24371 });
24372
24373 Self {
24374 prompt,
24375 editor,
24376 breakpoint_anchor,
24377 breakpoint,
24378 edit_action,
24379 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24380 block_ids: Default::default(),
24381 _subscriptions: vec![],
24382 }
24383 }
24384
24385 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24386 self.block_ids.extend(block_ids)
24387 }
24388
24389 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24390 if let Some(editor) = self.editor.upgrade() {
24391 let message = self
24392 .prompt
24393 .read(cx)
24394 .buffer
24395 .read(cx)
24396 .as_singleton()
24397 .expect("A multi buffer in breakpoint prompt isn't possible")
24398 .read(cx)
24399 .as_rope()
24400 .to_string();
24401
24402 editor.update(cx, |editor, cx| {
24403 editor.edit_breakpoint_at_anchor(
24404 self.breakpoint_anchor,
24405 self.breakpoint.clone(),
24406 match self.edit_action {
24407 BreakpointPromptEditAction::Log => {
24408 BreakpointEditAction::EditLogMessage(message.into())
24409 }
24410 BreakpointPromptEditAction::Condition => {
24411 BreakpointEditAction::EditCondition(message.into())
24412 }
24413 BreakpointPromptEditAction::HitCondition => {
24414 BreakpointEditAction::EditHitCondition(message.into())
24415 }
24416 },
24417 cx,
24418 );
24419
24420 editor.remove_blocks(self.block_ids.clone(), None, cx);
24421 cx.focus_self(window);
24422 });
24423 }
24424 }
24425
24426 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24427 self.editor
24428 .update(cx, |editor, cx| {
24429 editor.remove_blocks(self.block_ids.clone(), None, cx);
24430 window.focus(&editor.focus_handle);
24431 })
24432 .log_err();
24433 }
24434
24435 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24436 let settings = ThemeSettings::get_global(cx);
24437 let text_style = TextStyle {
24438 color: if self.prompt.read(cx).read_only(cx) {
24439 cx.theme().colors().text_disabled
24440 } else {
24441 cx.theme().colors().text
24442 },
24443 font_family: settings.buffer_font.family.clone(),
24444 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24445 font_size: settings.buffer_font_size(cx).into(),
24446 font_weight: settings.buffer_font.weight,
24447 line_height: relative(settings.buffer_line_height.value()),
24448 ..Default::default()
24449 };
24450 EditorElement::new(
24451 &self.prompt,
24452 EditorStyle {
24453 background: cx.theme().colors().editor_background,
24454 local_player: cx.theme().players().local(),
24455 text: text_style,
24456 ..Default::default()
24457 },
24458 )
24459 }
24460}
24461
24462impl Render for BreakpointPromptEditor {
24463 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24464 let editor_margins = *self.editor_margins.lock();
24465 let gutter_dimensions = editor_margins.gutter;
24466 h_flex()
24467 .key_context("Editor")
24468 .bg(cx.theme().colors().editor_background)
24469 .border_y_1()
24470 .border_color(cx.theme().status().info_border)
24471 .size_full()
24472 .py(window.line_height() / 2.5)
24473 .on_action(cx.listener(Self::confirm))
24474 .on_action(cx.listener(Self::cancel))
24475 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24476 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24477 }
24478}
24479
24480impl Focusable for BreakpointPromptEditor {
24481 fn focus_handle(&self, cx: &App) -> FocusHandle {
24482 self.prompt.focus_handle(cx)
24483 }
24484}
24485
24486fn all_edits_insertions_or_deletions(
24487 edits: &Vec<(Range<Anchor>, String)>,
24488 snapshot: &MultiBufferSnapshot,
24489) -> bool {
24490 let mut all_insertions = true;
24491 let mut all_deletions = true;
24492
24493 for (range, new_text) in edits.iter() {
24494 let range_is_empty = range.to_offset(snapshot).is_empty();
24495 let text_is_empty = new_text.is_empty();
24496
24497 if range_is_empty != text_is_empty {
24498 if range_is_empty {
24499 all_deletions = false;
24500 } else {
24501 all_insertions = false;
24502 }
24503 } else {
24504 return false;
24505 }
24506
24507 if !all_insertions && !all_deletions {
24508 return false;
24509 }
24510 }
24511 all_insertions || all_deletions
24512}
24513
24514struct MissingEditPredictionKeybindingTooltip;
24515
24516impl Render for MissingEditPredictionKeybindingTooltip {
24517 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24518 ui::tooltip_container(cx, |container, cx| {
24519 container
24520 .flex_shrink_0()
24521 .max_w_80()
24522 .min_h(rems_from_px(124.))
24523 .justify_between()
24524 .child(
24525 v_flex()
24526 .flex_1()
24527 .text_ui_sm(cx)
24528 .child(Label::new("Conflict with Accept Keybinding"))
24529 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24530 )
24531 .child(
24532 h_flex()
24533 .pb_1()
24534 .gap_1()
24535 .items_end()
24536 .w_full()
24537 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24538 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24539 }))
24540 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24541 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24542 })),
24543 )
24544 })
24545 }
24546}
24547
24548#[derive(Debug, Clone, Copy, PartialEq)]
24549pub struct LineHighlight {
24550 pub background: Background,
24551 pub border: Option<gpui::Hsla>,
24552 pub include_gutter: bool,
24553 pub type_id: Option<TypeId>,
24554}
24555
24556struct LineManipulationResult {
24557 pub new_text: String,
24558 pub line_count_before: usize,
24559 pub line_count_after: usize,
24560}
24561
24562fn render_diff_hunk_controls(
24563 row: u32,
24564 status: &DiffHunkStatus,
24565 hunk_range: Range<Anchor>,
24566 is_created_file: bool,
24567 line_height: Pixels,
24568 editor: &Entity<Editor>,
24569 _window: &mut Window,
24570 cx: &mut App,
24571) -> AnyElement {
24572 h_flex()
24573 .h(line_height)
24574 .mr_1()
24575 .gap_1()
24576 .px_0p5()
24577 .pb_1()
24578 .border_x_1()
24579 .border_b_1()
24580 .border_color(cx.theme().colors().border_variant)
24581 .rounded_b_lg()
24582 .bg(cx.theme().colors().editor_background)
24583 .gap_1()
24584 .block_mouse_except_scroll()
24585 .shadow_md()
24586 .child(if status.has_secondary_hunk() {
24587 Button::new(("stage", row as u64), "Stage")
24588 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24589 .tooltip({
24590 let focus_handle = editor.focus_handle(cx);
24591 move |window, cx| {
24592 Tooltip::for_action_in(
24593 "Stage Hunk",
24594 &::git::ToggleStaged,
24595 &focus_handle,
24596 window,
24597 cx,
24598 )
24599 }
24600 })
24601 .on_click({
24602 let editor = editor.clone();
24603 move |_event, _window, cx| {
24604 editor.update(cx, |editor, cx| {
24605 editor.stage_or_unstage_diff_hunks(
24606 true,
24607 vec![hunk_range.start..hunk_range.start],
24608 cx,
24609 );
24610 });
24611 }
24612 })
24613 } else {
24614 Button::new(("unstage", row as u64), "Unstage")
24615 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24616 .tooltip({
24617 let focus_handle = editor.focus_handle(cx);
24618 move |window, cx| {
24619 Tooltip::for_action_in(
24620 "Unstage Hunk",
24621 &::git::ToggleStaged,
24622 &focus_handle,
24623 window,
24624 cx,
24625 )
24626 }
24627 })
24628 .on_click({
24629 let editor = editor.clone();
24630 move |_event, _window, cx| {
24631 editor.update(cx, |editor, cx| {
24632 editor.stage_or_unstage_diff_hunks(
24633 false,
24634 vec![hunk_range.start..hunk_range.start],
24635 cx,
24636 );
24637 });
24638 }
24639 })
24640 })
24641 .child(
24642 Button::new(("restore", row as u64), "Restore")
24643 .tooltip({
24644 let focus_handle = editor.focus_handle(cx);
24645 move |window, cx| {
24646 Tooltip::for_action_in(
24647 "Restore Hunk",
24648 &::git::Restore,
24649 &focus_handle,
24650 window,
24651 cx,
24652 )
24653 }
24654 })
24655 .on_click({
24656 let editor = editor.clone();
24657 move |_event, window, cx| {
24658 editor.update(cx, |editor, cx| {
24659 let snapshot = editor.snapshot(window, cx);
24660 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24661 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24662 });
24663 }
24664 })
24665 .disabled(is_created_file),
24666 )
24667 .when(
24668 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24669 |el| {
24670 el.child(
24671 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24672 .shape(IconButtonShape::Square)
24673 .icon_size(IconSize::Small)
24674 // .disabled(!has_multiple_hunks)
24675 .tooltip({
24676 let focus_handle = editor.focus_handle(cx);
24677 move |window, cx| {
24678 Tooltip::for_action_in(
24679 "Next Hunk",
24680 &GoToHunk,
24681 &focus_handle,
24682 window,
24683 cx,
24684 )
24685 }
24686 })
24687 .on_click({
24688 let editor = editor.clone();
24689 move |_event, window, cx| {
24690 editor.update(cx, |editor, cx| {
24691 let snapshot = editor.snapshot(window, cx);
24692 let position =
24693 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24694 editor.go_to_hunk_before_or_after_position(
24695 &snapshot,
24696 position,
24697 Direction::Next,
24698 window,
24699 cx,
24700 );
24701 editor.expand_selected_diff_hunks(cx);
24702 });
24703 }
24704 }),
24705 )
24706 .child(
24707 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24708 .shape(IconButtonShape::Square)
24709 .icon_size(IconSize::Small)
24710 // .disabled(!has_multiple_hunks)
24711 .tooltip({
24712 let focus_handle = editor.focus_handle(cx);
24713 move |window, cx| {
24714 Tooltip::for_action_in(
24715 "Previous Hunk",
24716 &GoToPreviousHunk,
24717 &focus_handle,
24718 window,
24719 cx,
24720 )
24721 }
24722 })
24723 .on_click({
24724 let editor = editor.clone();
24725 move |_event, window, cx| {
24726 editor.update(cx, |editor, cx| {
24727 let snapshot = editor.snapshot(window, cx);
24728 let point =
24729 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24730 editor.go_to_hunk_before_or_after_position(
24731 &snapshot,
24732 point,
24733 Direction::Prev,
24734 window,
24735 cx,
24736 );
24737 editor.expand_selected_diff_hunks(cx);
24738 });
24739 }
24740 }),
24741 )
24742 },
24743 )
24744 .into_any_element()
24745}
24746
24747pub fn multibuffer_context_lines(cx: &App) -> u32 {
24748 EditorSettings::try_get(cx)
24749 .map(|settings| settings.excerpt_context_lines)
24750 .unwrap_or(2)
24751 .min(32)
24752}